Comienza a usar la API de Web Serial

1. Introducción

Última actualización: 21/07/2020

Qué compilarás

En este codelab, compilarás una página web que usa la API de Web Serial para interactuar con una placa BBC micro:bit y mostrar imágenes en su matriz LED de 5 × 5. Aprenderás sobre la API de Web Serial y a usar transmisiones legibles, de escritura y de transformación para comunicarte con dispositivos en serie a través del navegador.

81167ab7c01d353d.png

Qué aprenderás

  • Cómo abrir y cerrar un puerto en serie web
  • Cómo usar un bucle de lectura para controlar datos de una transmisión de entrada
  • Cómo enviar datos mediante un flujo de escritura

Requisitos

Elegimos usar micro:bit para este codelab porque es asequible, ofrece algunas entradas (botones) y salidas (pantalla LED de 5 × 5) y puede proporcionar entradas y salidas adicionales. Consulta la página de micro:bit de la BBC en el sitio de Espruino para obtener detalles sobre lo que puede hacer micro:bit.

2. Información acerca de la API de Web Serial

La API de Web Serial proporciona una manera para que los sitios web lean y escriban en un dispositivo en serie con secuencias de comandos. La API conecta la Web y el mundo físico, ya que permite que los sitios web se comuniquen con dispositivos en serie, como impresoras 3D y microcontroladores.

Hay muchos ejemplos de software de control que se está creando con tecnología web. Por ejemplo:

En algunos casos, estos sitios web se comunican con el dispositivo a través de una aplicación de agente nativo que el usuario instala manualmente. En otros casos, la aplicación se entrega en una aplicación nativa empaquetada a través de un framework como Electron. En otros casos, el usuario debe realizar un paso adicional, como copiar una aplicación compilada al dispositivo con una unidad de memoria flash USB.

Se puede mejorar la experiencia del usuario proporcionando una comunicación directa entre el sitio y el dispositivo que controla.

3. Cómo prepararte

Obtén el código

Incluimos todo lo que necesitas para este codelab en un proyecto de Glitch.

  1. Abre una nueva pestaña del navegador y ve a https://web-serial-codelab-start.glitch.me/.
  2. Haz clic en el vínculo Remix Glitch para crear tu propia versión del proyecto de inicio.
  3. Haz clic en el botón Show y, luego, elige In a New Window para ver el código en acción.

4. Abre una conexión en serie

Verifica si se admite la API de Web Serial

Lo primero que debes hacer es verificar si el navegador actual admite la API de Web Serial. Para ello, verifica si serial está en navigator.

En el evento DOMContentLoaded, agrega el siguiente código a tu proyecto:

script.js - DOMContentLoaded

// CODELAB: Add feature detection here.
const notSupported = document.getElementById('notSupported');
notSupported.classList.toggle('hidden', 'serial' in navigator);

De esta manera, se verifica si se admite Web Serial. De ser así, este código ocultará el banner que indica que no se admite Web Serial.

Probar

  1. Carga la página.
  2. Verifica que la página no muestre un banner rojo que indique que no se admite Web Serial.

Abre el puerto en serie

A continuación, debemos abrir el puerto en serie. Como la mayoría de las otras APIs modernas, la API de Web Serial es asíncrona. Esto evita que la IU se bloquee cuando se espera una entrada, pero también es importante porque la página web puede recibir datos en serie en cualquier momento, y necesitamos una forma de escucharlos.

Debido a que una computadora puede tener varios dispositivos en serie, cuando el navegador intenta solicitar un puerto, le pide al usuario que elija con qué dispositivo conectarse.

Agrega el siguiente código a tu proyecto:

script.js - connect()

// CODELAB: Add code to request & open port here.
// - Request a port and open a connection.
port = await navigator.serial.requestPort();
// - Wait for the port to open.
await port.open({ baudrate: 9600 });

La llamada requestPort le solicita al usuario a qué dispositivo desea conectarse. Llamar a port.open abre el puerto. También necesitamos proporcionar la velocidad a la que queremos comunicarnos con el dispositivo en serie. El micro:bit de BBC utiliza una conexión de 9,600 baudios entre el chip USB a serie y el procesador principal.

También conectaremos el botón de conexión y hagamos que llame a connect() cuando el usuario haga clic en él.

Agrega el siguiente código a tu proyecto:

script.js - clickConnect()

// CODELAB: Add connect code here.
await connect();

Probar

Nuestro proyecto ahora cuenta con lo mínimo para comenzar. Cuando se hace clic en el botón Connect, se le solicita al usuario que seleccione el dispositivo en serie al que se conectará y, luego, se conecta al micro:bit.

  1. Vuelve a cargar la página.
  2. Haz clic en el botón Conectar.
  3. En el diálogo del selector de puertos en serie, selecciona el dispositivo micro:bit de la BBC y haz clic en Conectar.
  4. En la pestaña, deberías ver un ícono que indica que te conectaste a un dispositivo en serie:

d9d0d3966960aeab.png

Configura una transmisión de entrada para escuchar datos del puerto en serie

Después de establecer la conexión, debemos configurar un flujo de entrada y un lector para leer los datos del dispositivo. Primero, obtendremos la transmisión legible del puerto mediante una llamada a port.readable. Como sabemos que recuperaremos el texto del dispositivo, lo canalizaremos a través de un decodificador de texto. A continuación, tenemos un lector y, luego, iniciaremos el bucle de lectura.

Agrega el siguiente código a tu proyecto:

script.js - connect()

// CODELAB: Add code to read the stream here.
let decoder = new TextDecoderStream();
inputDone = port.readable.pipeTo(decoder.writable);
inputStream = decoder.readable;

reader = inputStream.getReader();
readLoop();

El bucle de lectura es una función asíncrona que se ejecuta en bucle y espera el contenido sin bloquear el subproceso principal. Cuando llegan datos nuevos, el lector muestra dos propiedades: value y un valor booleano done. Si done es verdadero, el puerto se cerró o no ingresan más datos.

Agrega el siguiente código a tu proyecto:

script.js - readLoop()

// CODELAB: Add read loop here.
while (true) {
  const { value, done } = await reader.read();
  if (value) {
    log.textContent += value + '\n';
  }
  if (done) {
    console.log('[readLoop] DONE', done);
    reader.releaseLock();
    break;
  }
}

Probar

Nuestro proyecto ahora puede conectarse al dispositivo y adjuntará todos los datos recibidos del dispositivo al elemento de registro.

  1. Vuelve a cargar la página.
  2. Haz clic en el botón Conectar.
  3. En el diálogo del selector de puertos en serie, selecciona el dispositivo micro:bit de la BBC y haz clic en Conectar.
  4. Deberías ver el logotipo de Espruino:

93494fd58ea835eb.png

Configura una transmisión de salida para enviar datos al puerto en serie

La comunicación en serie suele ser bidireccional. Además de recibir datos del puerto en serie, también queremos enviar datos al puerto. Al igual que con la transmisión de entrada, solo enviaremos texto a través de la transmisión de salida al micro:bit.

Primero, crea una transmisión con codificador de texto y canalízala a port.writeable.

script.js - connect()

// CODELAB: Add code setup the output stream here.
const encoder = new TextEncoderStream();
outputDone = encoder.readable.pipeTo(port.writable);
outputStream = encoder.writable;

Cuando se conecta por serie con el firmware Espruino, la placa micro:bit de BBC funciona como un bucle de lectura-evaluación-impresión (REPL) de JavaScript, similar a lo que se obtiene en un shell de Node.js. A continuación, debemos proporcionar un método para enviar datos al flujo. El siguiente código obtiene un escritor de la transmisión de salida y, luego, usa write para enviar cada línea. Cada línea que se envía incluye un carácter de línea nueva (\n), para indicarle a micro:bit que evalúe el comando enviado.

script.js - writeToStream()

// CODELAB: Write to output stream
const writer = outputStream.getWriter();
lines.forEach((line) => {
  console.log('[SEND]', line);
  writer.write(line + '\n');
});
writer.releaseLock();

Para colocar el sistema en un estado conocido y evitar que reproduzca los caracteres que le enviamos, debemos enviar CTRL-C y desactivar el eco.

script.js - connect()

// CODELAB: Send CTRL-C and turn off echo on REPL
writeToStream('\x03', 'echo(false);');

Probar

Ahora, nuestro proyecto puede enviar y recibir datos de micro:bit. Comprobemos que podemos enviar correctamente un comando:

  1. Vuelve a cargar la página.
  2. Haz clic en el botón Conectar.
  3. En el diálogo del selector de puertos en serie, selecciona el dispositivo micro:bit de la BBC y haz clic en Conectar.
  4. Abre la pestaña de la consola en las Herramientas para desarrolladores de Chrome y escribe writeToStream('console.log("yes")');

Deberías ver algo como esto impreso en la página:

a13187e7e6260f7f.png

5. Controla la matriz de LED

Compila la cadena de cuadrícula de la matriz

Para controlar la matriz de LED en micro:bit, debemos llamar a show(). Este método muestra gráficos en la pantalla LED integrada de 5 x 5. Toma un número binario o una cadena.

Iteraremos las casillas de verificación y generaremos un array de 1 y 0 que indique cuál está marcado y cuál no. Luego, debemos invertir el array, ya que el orden de las casillas es el opuesto al de los LED de la matriz. A continuación, convertimos el array en una cadena y creamos el comando para enviarlo a micro:bit.

script.js - sendGrid()

// CODELAB: Generate the grid
const arr = [];
ledCBs.forEach((cb) => {
  arr.push(cb.checked === true ? 1 : 0);
});
writeToStream(`show(0b${arr.reverse().join('')})`);

Conecta las casillas de verificación para actualizar la matriz

A continuación, debemos detectar los cambios en las casillas de verificación y, si cambian, enviar esa información a micro:bit. En el código de detección de funciones (// CODELAB: Add feature detection here.), agrega la siguiente línea:

script.js - DOMContentLoaded

initCheckboxes();

También restablezcamos la cuadrícula cuando se conecte el micro:bit por primera vez, de modo que muestre una cara feliz. Ya se proporcionó la función drawGrid(). Esta función funciona de manera similar a sendGrid(). se necesita un array de 1 y 0 y se marcan las casillas de verificación según corresponda.

script.js - clickConnect()

// CODELAB: Reset the grid on connect here.
drawGrid(GRID_HAPPY);
sendGrid();

Probar

Ahora, cuando la página abra una conexión a micro:bit, enviará una cara feliz. Si haces clic en las casillas de verificación, se actualizará la pantalla de la matriz de LED.

  1. Vuelve a cargar la página.
  2. Haz clic en el botón Conectar.
  3. En el diálogo del selector de puertos en serie, selecciona el dispositivo micro:bit de la BBC y haz clic en Conectar.
  4. Deberías ver una sonrisa en la matriz de LED de micro:bit.
  5. Cambia las casillas de verificación para dibujar un patrón diferente en la matriz de LED.

6. Conecta los botones de micro:bit

Cómo agregar un evento de observación en los botones de micro:bit

Hay dos botones en el micro:bit, uno a cada lado de la matriz de LED. Espruino proporciona una función setWatch que envía un evento o una devolución de llamada cuando se presiona el botón. Como queremos escuchar ambos botones, haremos que nuestra función sea genérica y también imprimiremos los detalles del evento.

script.js - watchButton()

// CODELAB: Hook up the micro:bit buttons to print a string.
const cmd = `
  setWatch(function(e) {
    print('{"button": "${btnId}", "pressed": ' + e.state + '}');
  }, ${btnId}, {repeat:true, debounce:20, edge:"both"});
`;
writeToStream(cmd);

A continuación, debemos conectar ambos botones (llamados BTN1 y BTN2 en la placa micro:bit) cada vez que se conecte el puerto en serie al dispositivo.

script.js - clickConnect()

// CODELAB: Initialize micro:bit buttons.
watchButton('BTN1');
watchButton('BTN2');

Probar

Además de mostrar una cara feliz cuando está conectado, si presionas cualquiera de los botones de micro:bit, se agregará texto a la página que indica qué botón se presionó. Lo más probable es que cada carácter esté en su propia línea.

  1. Vuelve a cargar la página.
  2. Haz clic en el botón Conectar.
  3. En el diálogo del selector de puertos en serie, selecciona el dispositivo micro:bit de la BBC y haz clic en Conectar.
  4. Deberías ver una sonrisa en la matriz de LED de micro:bits.
  5. Presiona los botones en micro:bit y verifica que agregue texto nuevo a la página con los detalles del botón presionado.

7. Usar una transmisión de transformación para analizar los datos entrantes

Control básico de transmisión

Cuando se presiona uno de los botones micro:bit, el micro:bit envía datos al puerto en serie a través de una transmisión. Las transmisiones son muy útiles, pero también pueden ser un desafío, ya que no necesariamente obtendrás todos los datos a la vez y podrían fragmentarse de forma arbitraria.

Actualmente, la app imprime la transmisión entrante a medida que llega (en readLoop). En la mayoría de los casos, cada carácter está en su propia línea, pero eso no es muy útil. Lo ideal es que la transmisión se analice en líneas individuales y que cada mensaje se muestre en su propia línea.

Cómo transformar transmisiones con TransformStream

Para ello, podemos usar una transmisión de transformación ( TransformStream), que permite analizar la transmisión entrante y mostrar los datos analizados. Una transmisión de transformación puede encontrarse entre la fuente de transmisión (en este caso, el micro:bit) y lo que consuma la transmisión (en este caso, readLoop), y puede aplicar una transformación arbitraria antes de que se consuma. Piénsalo como una línea de montaje: a medida que un widget desciende en la línea, cada paso en la línea modifica el widget de modo que, cuando llegue a su destino final, sea un widget completamente funcional.

Para obtener más información, consulta Conceptos de la API de transmisiones de MDN.

Transforma el flujo con LineBreakTransformer

Crearemos una clase LineBreakTransformer, que tomará una transmisión y la fragmentará en función de los saltos de línea (\r\n). La clase necesita dos métodos: transform y flush. Se llama al método transform cada vez que la transmisión recibe datos nuevos. Puedes poner los datos en cola o guardarlos para más adelante. Cuando se cierra la transmisión, se llama al método flush, que controla los datos que aún no se procesan.

En nuestro método transform, agregaremos datos nuevos a container y, luego, verificaremos si hay saltos de línea en container. Si las hay, divídelo en un array y, luego, itera entre las líneas y llama a controller.enqueue() para enviar las líneas analizadas.

script.js - LineBreakTransformer.transform()

// CODELAB: Handle incoming chunk
this.container += chunk;
const lines = this.container.split('\r\n');
this.container = lines.pop();
lines.forEach(line => controller.enqueue(line));

Cuando se cierra la transmisión, simplemente vaciaremos los datos restantes del contenedor mediante enqueue.

script.js - LineBreakTransformer.flush()

// CODELAB: Flush the stream.
controller.enqueue(this.container);

Por último, debemos canalizar la transmisión entrante a través del nuevo LineBreakTransformer. Nuestra transmisión de entrada original solo se canalizaba a través de un TextDecoderStream, por lo que debemos agregar un pipeThrough adicional para canalizarlo a través de nuestro nuevo LineBreakTransformer.

script.js - connect()

// CODELAB: Add code to read the stream here.
let decoder = new TextDecoderStream();
inputDone = port.readable.pipeTo(decoder.writable);
inputStream = decoder.readable
  .pipeThrough(new TransformStream(new LineBreakTransformer()));

Probar

Ahora, cuando presiones uno de los botones micro:bit, los datos impresos deberían devolverse en una sola línea.

  1. Vuelve a cargar la página.
  2. Haz clic en el botón Conectar.
  3. En el diálogo del selector de puertos en serie, selecciona el dispositivo micro:bit de la BBC y haz clic en Conectar.
  4. Deberías ver una sonrisa en la matriz de LED de micro:bit.
  5. Presiona los botones de micro:bit y verifica que veas algo como lo siguiente:

6c2193880c748412.png

Transforma el flujo con JSONTransformer

Podríamos intentar analizar la cadena en JSON en readLoop, pero, en cambio, crearemos un transformador JSON muy simple que transformará los datos en un objeto JSON. Si los datos no tienen un formato JSON válido, solo muestra lo que vino.

script.js - JSONTransformer.transform

// CODELAB: Attempt to parse JSON content
try {
  controller.enqueue(JSON.parse(chunk));
} catch (e) {
  controller.enqueue(chunk);
}

A continuación, canaliza la transmisión a través de JSONTransformer, después de que haya pasado por LineBreakTransformer. Esto nos permite mantener nuestro JSONTransformer simple, ya que sabemos que el JSON solo se enviará en una línea.

script.js - connect

// CODELAB: Add code to read the stream here.
let decoder = new TextDecoderStream();
inputDone = port.readable.pipeTo(decoder.writable);
inputStream = decoder.readable
  .pipeThrough(new TransformStream(new LineBreakTransformer()))
  .pipeThrough(new TransformStream(new JSONTransformer()));

Probar

Ahora, cuando presiones uno de los botones micro:bit, deberías ver [object Object] impreso en la página.

  1. Vuelve a cargar la página.
  2. Haz clic en el botón Conectar.
  3. En el diálogo del selector de puertos en serie, selecciona el dispositivo micro:bit de la BBC y haz clic en Conectar.
  4. Deberías ver una sonrisa en la matriz de LED de micro:bit.
  5. Presiona los botones de micro:bit y verifica que veas algo como lo siguiente:

Cómo responder cuando se presiona un botón

Para responder cuando se presione el botón micro:bit, actualiza readLoop para verificar si los datos que recibió son un object con una propiedad button. Luego, llama a buttonPushed para controlar la acción del botón.

script.js - readLoop()

const { value, done } = await reader.read();
if (value && value.button) {
  buttonPushed(value);
} else {
  log.textContent += value + '\n';
}

Cuando se presiona el botón micro:bit, la pantalla de la matriz LED debería cambiar. Usa el siguiente código para establecer la matriz:

script.js - buttonPushed()

// CODELAB: micro:bit button press handler
if (butEvt.button === 'BTN1') {
  divLeftBut.classList.toggle('pressed', butEvt.pressed);
  if (butEvt.pressed) {
    drawGrid(GRID_HAPPY);
    sendGrid();
  }
  return;
}
if (butEvt.button === 'BTN2') {
  divRightBut.classList.toggle('pressed', butEvt.pressed);
  if (butEvt.pressed) {
    drawGrid(GRID_SAD);
    sendGrid();
  }
}

Probar

Ahora, cuando presionas uno de los botones micro:bit, la matriz de LED debería cambiar a una cara feliz o una cara triste.

  1. Vuelve a cargar la página.
  2. Haz clic en el botón Conectar.
  3. En el diálogo del selector de puertos en serie, selecciona el dispositivo micro:bit de la BBC y haz clic en Conectar.
  4. Deberías ver una sonrisa en la matriz de LED de micro:bits.
  5. Presiona los botones del micro:bit y verifica que la matriz de LED cambie.

8. Cómo cerrar el puerto en serie

El último paso es conectar la función de desconexión para cerrar el puerto cuando el usuario haya terminado.

Cierra el puerto cuando el usuario haga clic en el botón Conectar/Desconectar.

Cuando el usuario hace clic en el botón Conectar o Desconectar, debemos cerrar la conexión. Si el puerto ya está abierto, llama a disconnect() y actualiza la IU para indicar que la página ya no está conectada al dispositivo en serie.

script.js - clickConnect()

// CODELAB: Add disconnect code here.
if (port) {
  await disconnect();
  toggleUIConnected(false);
  return;
}

Cierra las transmisiones y el puerto

En la función disconnect, debemos cerrar la transmisión de entrada y salida, y cerrar el puerto. Para cerrar la transmisión de entrada, llama a reader.cancel(). La llamada a cancel es asíncrona, por lo que debemos usar await para esperar a que se complete:

script.js - disconnect()

// CODELAB: Close the input stream (reader).
if (reader) {
  await reader.cancel();
  await inputDone.catch(() => {});
  reader = null;
  inputDone = null;
}

Para cerrar la transmisión de salida, obtén un writer, llama a close() y espera a que se cierre el objeto outputDone:

script.js - disconnect()

// CODELAB: Close the output stream.
if (outputStream) {
  await outputStream.getWriter().close();
  await outputDone;
  outputStream = null;
  outputDone = null;
}

Por último, cierra el puerto en serie y espera a que se cierre:

script.js - disconnect()

// CODELAB: Close the port.
await port.close();
port = null;

Probar

Ahora, puedes abrir y cerrar el puerto en serie cuando quieras.

  1. Vuelve a cargar la página.
  2. Haz clic en el botón Conectar.
  3. En el diálogo del selector de puertos en serie, selecciona el dispositivo micro:bit de la BBC y haz clic en Conectar.
  4. Deberías ver una sonrisa en la matriz de LED de micro:bit.
  5. Presiona el botón Desconectar y verifica que la matriz de LED se apague y que no haya errores en la consola.

9. Felicitaciones

¡Felicitaciones! Creaste correctamente tu primera app web que usa la API de Web Serial.

Visita https://goo.gle/fugu-api-tracker para ver las últimas novedades de la API de Web Serial y todas las otras emocionantes funciones web nuevas en las que está trabajando el equipo de Chrome.