Saltar al contenido principal




La API serial permite que los sitios web se comuniquen con dispositivos seriales.


Actualizado

¿Qué es la API serial?

Un puerto serie es una interfaz de comunicación bidireccional que permite enviar y recibir datos byte a byte.

La API serial proporciona una forma para que los sitios web lean y escriban en un dispositivo serial con JavaScript. Los dispositivos serie se conectan a través de un puerto serie en el sistema del usuario o mediante dispositivos USB y Bluetooth extraíbles que emulan un puerto serie.

En otras palabras, la API serial une la web y el mundo físico al permitir que los sitios web se comuniquen con dispositivos seriales, como microcontroladores e impresoras 3D.

Esta API también es un gran compañero para WebUSB ya que los sistemas operativos requieren que las aplicaciones se comuniquen con algunos puertos serie utilizando su API serie nativa de nivel superior en lugar de la API USB de bajo nivel.

Este artículo refleja la API serial implementada en Chrome 86 y versiones posteriores. Algunos nombres de propiedades han cambiado con respecto a versiones anteriores.

Casos de uso sugeridos

En los sectores educativo, aficionado e industrial, los usuarios conectan dispositivos periféricos a sus computadoras. Estos dispositivos a menudo son controlados por microcontroladores a través de una conexión en serie utilizada por software personalizado. Algún software personalizado para controlar estos dispositivos está construido con tecnología web:

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

En todos estos casos, la experiencia del usuario se mejorará al proporcionar una comunicación directa entre el sitio web y el dispositivo que está controlando.

Estado actual

Usando la API serial

Habilitar el apoyo durante la fase de prueba de origen

La API serial está disponible en todas las plataformas de escritorio (Chrome OS, Linux, macOS y Windows) como una prueba de origen en Chrome 80. Se espera que la prueba de origen finalice justo antes de que Chrome 89 se establezca en febrero de 2021. La API también puede habilitarse mediante una bandera.

Las pruebas de Origin le permiten probar nuevas funciones y brindar comentarios sobre su usabilidad, practicidad y efectividad a la comunidad de estándares web. Para obtener más información, consulte el Guía de pruebas de Origin para desarrolladores web. Para inscribirse en esta u otra prueba de origen, visite el página de registro.

Regístrese para la prueba de origen

  1. Solicita un token por tu origen.
  2. Agrega el token a tus páginas. Hay dos maneras de hacerlo:
    • Agregar un origin-trial <meta> etiqueta al encabezado de cada página. Por ejemplo, esto puede verse así:
      <meta http-equiv="origin-trial" content="TOKEN_GOES_HERE">
    • Si puede configurar su servidor, también puede agregar el token usando un Origin-Trial Encabezado HTTP. El encabezado de respuesta resultante debería verse así:
      Origin-Trial: TOKEN_GOES_HERE

Habilitación a través de chrome: // flags

Para experimentar con la API serial localmente en todas las plataformas de escritorio, sin un token de prueba de origen, habilite el #experimental-web-platform-features bandera en
chrome://flags.

Detección de características

Para verificar si la API serial es compatible, use:

if ("serial" in navigator) {
}

Abra un puerto serial

La API serial es asincrónica por diseño. Esto evita que la interfaz de usuario del sitio web se bloquee cuando se espera una entrada, lo cual es importante porque los datos en serie se pueden recibir en cualquier momento, lo que requiere una forma de escucharlos.

Para abrir un puerto serie, primero acceda a un SerialPort objeto. Para ello, puede solicitar al usuario que seleccione un solo puerto serie llamando
navigator.serial.requestPort()o elige uno de navigator.serial.getPorts()
que devuelve una lista de puertos serie a los que el sitio web ha tenido acceso anteriormente.


const port = await navigator.serial.requestPort();


const ports = await navigator.serial.getPorts();

los navigator.serial.requestPort() La función toma un literal de objeto opcional que define filtros. Se utilizan para hacer coincidir cualquier dispositivo serie conectado a través de USB con un proveedor USB obligatorio (usbVendorId) e identificadores de productos USB opcionales (usbProductId).


const filters = [
{ usbVendorId: 0x2341, usbProductId: 0x0043 },
{ usbVendorId: 0x2341, usbProductId: 0x0001 }
];


const port = await navigator.serial.requestPort({ filters });

const { usbProductId, usbVendorId } = port.getInfo();

serial-port-prompt-1927133
Mensaje de usuario para seleccionar un micro: bit BBC

Vocación requestPort() solicita al usuario que seleccione un dispositivo y devuelve un
SerialPort objeto. Una vez que tienes un SerialPort objeto, llamando port.open()
con la velocidad en baudios deseada se abrirá el puerto serie. los baudRate El miembro de diccionario especifica qué tan rápido se envían los datos a través de una línea serial. Se expresa en unidades de bits por segundo (bps). Verifique la documentación de su dispositivo para el valor correcto ya que todos los datos que envía y recibe serán un galimatías si esto se especifica incorrectamente. Para algunos dispositivos USB y Bluetooth que emulan un puerto serie, este valor se puede establecer de forma segura en cualquier valor, ya que la emulación lo ignora.


const port = await navigator.serial.requestPort();


await port.open({ baudRate: 9600 });

También puede especificar cualquiera de las siguientes opciones al abrir un puerto serie. Estas opciones son opcionales y tienen valores predeterminados.

  • dataBits: El número de bits de datos por trama (7 u 8).
  • stopBits: El número de bits de parada al final de una trama (1 o 2).
  • parity: El modo de paridad (ya sea "none", "even" o "odd").
  • bufferSize: El tamaño de los búferes de lectura y escritura que se deben crear (debe ser inferior a 16 MB).
  • flowControl: El modo de control de flujo (ya sea "none" o "hardware").

Leer desde un puerto serie

Los flujos de entrada y salida en la API serial son manejados por la API de Streams.

Si las transmisiones son nuevas para usted, consulte Conceptos de la API de Streams. Este artículo apenas toca la superficie de los arroyos y su manejo.

Una vez establecida la conexión del puerto serie, readable y writable
propiedades del SerialPort objeto devuelve un ReadableStream y un
WritableStream. Se utilizarán para recibir datos y enviar datos al dispositivo serie. Ambos usan Uint8Array instancias para la transferencia de datos.

Cuando llegan nuevos datos del dispositivo serie, port.readable.getReader().read()
devuelve dos propiedades de forma asincrónica: el value y un done booleano. Si
done es cierto, el puerto serie se ha cerrado o no entran más datos. Llamando port.readable.getReader() crea un lector y bloquea readable lo. Mientras readable es bloqueado, el puerto serie no se puede cerrar.

const reader = port.readable.getReader();


while (true) {
const { value, done } = await reader.read();
if (done) {
reader.releaseLock();
break;
}
console.log(value);
}

Algunos errores de lectura del puerto serie no fatales pueden ocurrir bajo algunas condiciones, como desbordamiento del búfer, errores de trama o errores de paridad. Esos se lanzan como excepciones y se pueden detectar agregando otro bucle encima del anterior que verifica port.readable. Esto funciona porque mientras los errores no sean fatales, un nuevo ReadableStream se crea automáticamente. Si se produce un error fatal, como la extracción del dispositivo serie, port.readable se vuelve nulo.

while (port.readable) {
const reader = port.readable.getReader();

try {
while (true) {
const { value, done } = await reader.read();
if (done) {

reader.releaseLock();
break;
}
if (value) {
console.log(value);
}
}
} catch (error) {

}
}

Si el dispositivo serie envía texto de vuelta, puede canalizar port.readable a través de un
TextDecoderStream Como se muestra abajo. UNA TextDecoderStream es un transformar corriente
que agarra todo Uint8Array fragmenta y los convierte en cadenas.

const textDecoder = new TextDecoderStream();
const readableStreamClosed = port.readable.pipeTo(textDecoder.writable);
const reader = textDecoder.readable.getReader();


while (true) {
const { value, done } = await reader.read();
if (done) {

reader.releaseLock();
break;
}

console.log(value);
}

Escribir en un puerto serie

Para enviar datos a un dispositivo serie, pase los datos a
port.writable.getWriter().write(). Vocación releaseLock() en
port.writable.getWriter() es necesario para que el puerto serie se cierre más tarde.

const writer = port.writable.getWriter();

const data = new Uint8Array([104, 101, 108, 108, 111]);
await writer.write(data);


writer.releaseLock();

Envíe texto al dispositivo a través de un TextEncoderStream canalizado a port.writable
Como se muestra abajo.

const textEncoder = new TextEncoderStream();
const writableStreamClosed = textEncoder.readable.pipeTo(port.writable);

const writer = textEncoder.writable.getWriter();

await writer.write("hello");

Cerrar un puerto serie

port.close() cierra el puerto serie si su readable y writable los miembros son desbloqueado, sentido releaseLock() Ha sido convocada por su respectivo lector y escritor.

await port.close();

Sin embargo, al leer continuamente datos de un dispositivo serie mediante un bucle,
port.readable siempre estará bloqueado hasta que encuentre un error. En este caso, llamando reader.cancel() obligará reader.read() para resolver inmediatamente con { value: undefined, done: true } y por lo tanto permitiendo que el bucle llame reader.releaseLock().



const reader = port.readable.getReader();


while (true) {
const { value, done } = await reader.read();
if (done) {
reader.releaseLock();
break;
}

console.log(value);
}



await reader.cancel();
await port.close();

Cerrar un puerto serie es más complicado cuando se usa transformar corrientes (me gusta
TextDecoderStream y TextEncoderStream). Llamada reader.cancel() como antes. Luego llame writer.close() y port.close(). Esto propaga los errores a través de los flujos de transformación al puerto serie subyacente. Debido a que la propagación de errores no ocurre de inmediato, debe utilizar el readableStreamClosed y
writableStreamClosed promesas creadas anteriormente para detectar cuándo port.readable
y port.writable han sido desbloqueados. Cancelar el reader hace que la secuencia sea abortada; es por eso que debe detectar e ignorar el error resultante.



const textDecoder = new TextDecoderStream();
const readableStreamClosed = port.readable.pipeTo(textDecoder.writable);
const reader = textDecoder.readable.getReader();


while (true) {
const { value, done } = await reader.read();
if (done) {
reader.releaseLock();
break;
}

console.log(value);
}

const textEncoder = new TextEncoderStream();
const writableStreamClosed = textEncoder.readable.pipeTo(port.writable);

reader.cancel();
await readableStreamClosed.catch(() => { });

writer.close();
await writableStreamClosed;

await port.close();

Escuche la conexión y la desconexión

Si un dispositivo USB proporciona un puerto serie, entonces ese dispositivo puede conectarse o desconectarse del sistema. Cuando se le ha otorgado permiso al sitio web para acceder a un puerto serie, debe monitorear el connect y disconnect eventos.

navigator.serial.addEventListener("connect", (event) => {
});

navigator.serial.addEventListener("disconnect", (event) => {
});

Manejar señales

Después de establecer la conexión del puerto serie, puede consultar y configurar explícitamente las señales expuestas por el puerto serie para la detección de dispositivos y el control de flujo. Estas señales se definen como valores booleanos. Por ejemplo, algunos dispositivos como Arduino entrarán en un modo de programación si se activa la señal Data Terminal Ready (DTR).

Ajuste señales de salida y conseguir señales de entrada se hacen respectivamente llamando port.setSignals() y port.getSignals(). Consulte los ejemplos de uso a continuación.


await port.setSignals({ break: false });


await port.setSignals({ dataTerminalReady: true });


await port.setSignals({ requestToSend: false });

const signals = await port.getSignals();
console.log(`Clear To Send: ${signals.clearToSend}`);
console.log(`Data Carrier Detect: ${signals.dataCarrierDetect}`);
console.log(`Data Set Ready: ${signals.dataSetReady}`);
console.log(`Ring Indicator: ${signals.ringIndicator}`);

Transformando arroyos

Cuando recibe datos del dispositivo serie, no necesariamente obtendrá todos los datos a la vez. Puede estar fragmentado arbitrariamente. Para más información, ver
Conceptos de la API de Streams.

Para lidiar con esto, puede usar algunas transmisiones de transformación integradas, como
TextDecoderStream o cree su propio flujo de transformación que le permite analizar el flujo entrante y devolver datos analizados. El flujo de transformación se encuentra entre el dispositivo serie y el bucle de lectura que consume el flujo. Puede aplicar una transformación arbitraria antes de que se consuman los datos. Piense en ello como una línea de montaje: a medida que un widget desciende por la línea, cada paso de la línea modifica el widget, de modo que cuando llega a su destino final, es un widget en pleno funcionamiento.

aeroplane-factory-2628261
Fábrica de aviones Castle Bromwich de la Segunda Guerra Mundial

Por ejemplo, considere cómo crear una clase de transmisión de transformación que consume una transmisión y la fragmenta en función de los saltos de línea. Sus transform() se llama al método cada vez que el flujo recibe nuevos datos. Puede poner los datos en cola o guardarlos para más tarde. los flush() se llama al método cuando la secuencia está cerrada y maneja cualquier dato que aún no se haya procesado.

Para usar la clase de transmisión de transformación, debe canalizar una transmisión entrante a través de ella. En el tercer ejemplo de código en Leer desde un puerto serie, el flujo de entrada original solo se canalizó a través de un TextDecoderStream, entonces tenemos que llamar pipeThrough() para canalizarlo a través de nuestro nuevo LineBreakTransformer.

class LineBreakTransformer {
constructor() {
this.chunks = "";
}

transform(chunk, controller) {
this.chunks += chunk;
const lines = this.chunks.split("rn");
this.chunks = lines.pop();
lines.forEach((line) => controller.enqueue(line));
}

flush(controller) {
controller.enqueue(this.chunks);
}
}

const textDecoder = new TextDecoderStream();
const readableStreamClosed = port.readable.pipeTo(textDecoder.writable);
const reader = textDecoder.readable
.pipeThrough(new TransformStream(new LineBreakTransformer()))
.getReader();

Para depurar problemas de comunicación del dispositivo serie, utilice el tee() método de
port.readable para dividir las transmisiones que van hacia o desde el dispositivo serie. Los dos flujos creados se pueden consumir de forma independiente y esto le permite imprimir uno en la consola para su inspección.

const [appReadable, devReadable] = port.readable.tee();

Codelab

En el Laboratorio de código para desarrolladores de Google, usará la API serial para interactuar con un
BBC micro: bit tablero para mostrar imágenes en su matriz de LED de 5×5.

Polyfill

En Android, es posible admitir puertos serie basados ​​en USB mediante la API WebUSB y Polyfill serie API. Este polyfill está limitado a hardware y plataformas donde se puede acceder al dispositivo a través de la API WebUSB porque no ha sido reclamado por un controlador de dispositivo integrado.

Seguridad y privacidad

Los autores de especificaciones han diseñado e implementado la API serial utilizando los principios básicos definidos en Controlar el acceso a las potentes funciones de la plataforma web, incluido el control del usuario, la transparencia y la ergonomía. La capacidad de utilizar esta API está controlada principalmente por un modelo de permisos que otorga acceso a un solo dispositivo serie a la vez. En respuesta a una solicitud de usuario, el usuario debe tomar medidas activas para seleccionar un dispositivo serie en particular.

Para comprender las compensaciones de seguridad, consulte la seguridad y intimidad
secciones del Explicador de API en serie.

Realimentación

Al equipo de Chrome le encantaría conocer sus pensamientos y experiencias con la API serial.

Cuéntanos sobre el diseño de la API

¿Hay algo en la API que no funcione como se esperaba? ¿O faltan métodos o propiedades que necesita para implementar su idea?

Presentar un problema de especificaciones en el Repositorio de GitHub de API en serie o agregue sus pensamientos a un problema existente.

Informar un problema con la implementación

¿Encontraste un error con la implementación de Chrome? ¿O la implementación es diferente de la especificación?

Presentar un error en https://new.crbug.com. Asegúrese de incluir todos los detalles que pueda, proporcione instrucciones sencillas para reproducir el error y
Componentes ajustado a Blink>Serial. Falla funciona muy bien para compartir repros rápidos y fáciles.

Mostrar apoyo

¿Está planeando utilizar la API serial? Su apoyo público ayuda al equipo de Chrome a priorizar funciones y muestra a otros proveedores de navegadores lo importante que es brindarles soporte.

Enviar un tweet a @Cromodev con el hashtag
#SerialAPI

y háganos saber dónde y cómo lo está usando.

Enlaces Útiles

Población:

Agradecimientos

Gracias a Beca Reilly y Joe Medley por sus reseñas de este artículo. Foto de fábrica de aviones por Fideicomiso de los museos de Birmingham en Unsplash.