Cómo acceder a dispositivos USB en la Web

La API de WebUSB hace que el USB sea más seguro y fácil de usar, ya que lo lleva a la Web.

François Beaufort
François Beaufort

Si dije claramente "USB", es muy probable que piense inmediatamente en teclados, mouse, audio, video y dispositivos de almacenamiento. Está bien, pero encontrarás otros tipos de dispositivos de bus universal en serie (USB) ahí.

Estos dispositivos USB no estandarizados requieren que los proveedores de hardware escriban información específica de la plataforma. y SDKs para que tú (el desarrollador) los aproveches. Lamentablemente, este código específico de la plataforma siempre impidió que se usaran estos dispositivos. por la Web. Este es uno de los motivos por los que se creó la API de WebUSB: proporcionan una forma de exponer los servicios de dispositivos USB en la Web. Con esta API, el hardware los fabricantes podrán crear SDK de JavaScript multiplataforma para sus dispositivos.

Pero lo más importante es que esto hará que el USB sea más seguro y fácil de usar al llevar a la Web.

Veamos el comportamiento que podrías esperar con la API de WebUSB:

  1. Compra un dispositivo USB.
  2. Conéctalo a tu computadora. Aparecerá una notificación de inmediato con la al que ir para este dispositivo.
  3. Haz clic en la notificación. El sitio web está ahí y listo para usar.
  4. Haz clic para conectarte y aparecerá un selector de dispositivos USB en Chrome donde podrás elige tu dispositivo.

Listo.

¿Cómo sería este procedimiento sin la API de WebUSB?

  1. Instalar una aplicación específica para cada plataforma
  2. Si es compatible con mi sistema operativo, verifico que lo descargué lo correcto.
  3. Instala el dispositivo. Con suerte, no recibirás mensajes emergentes ni mensajes emergentes del SO advirtiéndote sobre la instalación de controladores o aplicaciones de Internet. Si no tienes suerte, las aplicaciones o los controladores instalados en tu computadora. (Recuerda que la Web está creada para contener fallas sitios web).
  4. Si solo usas la función una vez, el código permanecerá en tu computadora hasta que y piensas eliminarlo. (En la Web, el espacio para el espacio sin usar se reclaimed.)

Antes de comenzar

En este artículo, se da por sentado que tienes conocimientos básicos sobre el funcionamiento del USB. Si no, yo te recomendamos que leas USB en un NutShell. Para obtener información general sobre USB, consulta las especificaciones oficiales de USB.

La API de WebUSB está disponible en Chrome 61.

Disponible para pruebas de origen

Para obtener la mayor cantidad de comentarios posible de los desarrolladores que usan WebUSB en el campo, ya habíamos agregado esta función en Chrome 54 y 57 como prueba de origen.

La prueba más reciente finalizó correctamente en septiembre de 2017.

Privacidad y seguridad

Solo HTTPS

Debido a la potencia de esta función, solo funciona en contextos seguros. Esto significa deberás desarrollarla teniendo en cuenta TLS.

Se requiere un gesto del usuario

Como medida de seguridad, navigator.usb.requestDevice() solo puede llamarse a través de un gesto del usuario, como un toque o un clic del mouse.

Política de Permisos

Una política de permisos es un mecanismo que les permite a los desarrolladores habilitar e inhabilitar varias funciones y APIs del navegador. Se puede definir a través de un protocolo encabezado o un iframe "allow" .

Puedes definir una política de permisos que controle si el atributo usb está en el objeto Navigator o, en otras palabras, si permites WebUSB.

A continuación, se muestra un ejemplo de una política de encabezado en la que WebUSB no está permitido:

Feature-Policy: fullscreen "*"; usb "none"; payment "self" https://payment.example.com

A continuación, se muestra otro ejemplo de una política de contenedor en la que se permite el USB:

<iframe allowpaymentrequest allow="usb; fullscreen"></iframe>

Comencemos a programar

La API de WebUSB depende en gran medida de las promesas de JavaScript. Si desconocen con ellos, consulta este excelente instructivo sobre promesas. Una cosa más, () => {} son solo funciones de flecha de ECMAScript 2015.

Cómo obtener acceso a dispositivos USB

Puedes pedirle al usuario que seleccione un solo dispositivo USB conectado navigator.usb.requestDevice() o llama a navigator.usb.getDevices() para obtener un una lista de todos los dispositivos USB conectados a los que tiene acceso el sitio web

La función navigator.usb.requestDevice() toma un objeto de JavaScript obligatorio que define filters. Estos filtros se usan para hacer coincidir cualquier dispositivo USB con el identificadores de proveedor (vendorId) y, opcionalmente, de productos (productId). Las claves classCode, protocolCode, serialNumber y subclassCode pueden definirse allí también.

Captura de pantalla de la solicitud del usuario del dispositivo USB en Chrome
Mensaje del usuario del dispositivo USB.

Por ejemplo, aquí te mostramos cómo acceder a un dispositivo Arduino conectado y configurado para permitir el origen.

navigator.usb.requestDevice({ filters: [{ vendorId: 0x2341 }] })
.then(device => {
  console.log(device.productName);      // "Arduino Micro"
  console.log(device.manufacturerName); // "Arduino LLC"
})
.catch(error => { console.error(error); });

Antes de que me preguntes, no se me ocurrió por arte de magia este código hexadecimal 0x2341 de la fila. Simplemente busqué la palabra "Arduino" de esta lista de ID de USB.

El device USB que se muestra en la promesa cumplida anterior tiene información básica, información importante sobre el dispositivo, como la versión de USB compatible, tamaño máximo del paquete, ID del producto y proveedor, la cantidad de ID del producto configuraciones que puede tener el dispositivo. Básicamente, contiene todos los campos del descriptor USB del dispositivo.

// Get all connected USB devices the website has been granted access to.
navigator.usb.getDevices().then(devices => {
  devices.forEach(device => {
    console.log(device.productName);      // "Arduino Micro"
    console.log(device.manufacturerName); // "Arduino LLC"
  });
})

Por cierto, si un dispositivo USB anuncia su compatibilidad con WebUSB, así como al definir una URL de página de destino, Chrome mostrará una notificación persistente cuando la URL El dispositivo USB está conectado. Si haces clic en esta notificación, se abrirá la página de destino.

Captura de pantalla de la notificación de WebUSB en Chrome
Notificación de WebUSB.

Cómo hablar con una placa Arduino USB

Ahora veamos lo fácil que es comunicarse desde un dispositivo compatible con WebUSB Placa Arduino a través del puerto USB. Consulta las instrucciones en https://github.com/webusb/arduino para habilitar tus bocetos con WebUSB.

No te preocupes, te explicaré todos los métodos de los dispositivos WebUSB que se mencionan más adelante en esta sección. este artículo.

let device;

navigator.usb.requestDevice({ filters: [{ vendorId: 0x2341 }] })
.then(selectedDevice => {
    device = selectedDevice;
    return device.open(); // Begin a session.
  })
.then(() => device.selectConfiguration(1)) // Select configuration #1 for the device.
.then(() => device.claimInterface(2)) // Request exclusive control over interface #2.
.then(() => device.controlTransferOut({
    requestType: 'class',
    recipient: 'interface',
    request: 0x22,
    value: 0x01,
    index: 0x02})) // Ready to receive data
.then(() => device.transferIn(5, 64)) // Waiting for 64 bytes of data from endpoint #5.
.then(result => {
  const decoder = new TextDecoder();
  console.log('Received: ' + decoder.decode(result.data));
})
.catch(error => { console.error(error); });

Ten en cuenta que la biblioteca WebUSB que estoy usando solo está implementando un protocolo de ejemplo (basado en el protocolo en serie USB estándar) y que los fabricantes pueden crear cualquier conjunto y tipo de endpoints que deseen. Las transferencias de control son especialmente útiles para comandos de configuración pequeños, como tienen prioridad en el autobús y tienen una estructura bien definida.

Y aquí está el boceto que se cargó en la placa de Arduino.

// Third-party WebUSB Arduino library
#include <WebUSB.h>

WebUSB WebUSBSerial(1 /* https:// */, "webusb.github.io/arduino/demos");

#define Serial WebUSBSerial

void setup() {
  Serial.begin(9600);
  while (!Serial) {
    ; // Wait for serial port to connect.
  }
  Serial.write("WebUSB FTW!");
  Serial.flush();
}

void loop() {
  // Nothing here for now.
}

La biblioteca de Arduino WebUSB de terceros que se usa en el código de muestra anterior no básicamente dos cosas:

  • El dispositivo actúa como un dispositivo WebUSB que permite que Chrome lea la URL de página de destino.
  • Expone una API de WebUSB Serial que puedes usar para anular la predeterminada.

Observa el código JavaScript de nuevo. Una vez que el usuario elija el elemento device, device.open() ejecuta todos los pasos específicos de la plataforma para iniciar una sesión con el USB. dispositivo. Luego, debo seleccionar una configuración USB disponible con device.selectConfiguration() Recuerda que una configuración especifica cómo el dispositivo esté encendido, su consumo máximo de energía y la cantidad de interfaces. Hablando de interfaces, también necesito solicitar acceso exclusivo con device.claimInterface(), ya que los datos solo se pueden transferir a una interfaz o extremos asociados cuando se reclama la interfaz. Por último, estoy llamando Se necesita device.controlTransferOut() para configurar el dispositivo Arduino con el comandos adecuados para comunicarse a través de la API de WebUSB Serial.

Desde allí, device.transferIn() realiza una transferencia masiva a la para informarle que el host está listo para recibir datos masivos. Luego, la prometida se completa con un objeto result que contiene un data de DataView que se debe analizar adecuadamente.

Si estás familiarizado con el USB, todo esto te debería resultar bastante familiar.

Quiero más

La API de WebUSB te permite interactuar con todos los tipos de extremos o transferencia USB:

  • CONTROL, que se usan para enviar o recibir configuraciones o comandos parámetros a un dispositivo USB, se controlan con controlTransferIn(setup, length) y controlTransferOut(setup, data).
  • Las transferencias INTERRUPT, que se usan para una pequeña cantidad de datos urgentes, se con los mismos métodos que las transferencias masivas con transferIn(endpointNumber, length) y transferOut(endpointNumber, data).
  • Las transferencias ISOCHRONÓS, que se usan para transmisiones de datos como video y sonido, son se maneja con isochronousTransferIn(endpointNumber, packetLengths) y isochronousTransferOut(endpointNumber, data, packetLengths)
  • Transferencias masivas, que se usan para transferir una gran cantidad de datos no urgentes en de manera confiable, se manejan con transferIn(endpointNumber, length) y transferOut(endpointNumber, data)

También puedes consultar el proyecto WebLight de Mike Tsao, que brinda un ejemplo desde cero de la construcción de un dispositivo LED controlado por USB y diseñado para la API de WebUSB (en este caso, no se usa Arduino). Encontrarás hardware, software, y el firmware.

Cómo revocar el acceso a un dispositivo USB

El sitio web puede limpiar los permisos para acceder a un dispositivo USB que ya no necesita llamando a forget() en la instancia USBDevice. Por ejemplo, para un aplicación web educativa utilizada en una computadora compartida con muchos dispositivos, una gran de permisos acumulados generados por el usuario crea una mala experiencia del usuario.

// Voluntarily revoke access to this USB device.
await device.forget();

Dado que forget() está disponible en Chrome 101 o versiones posteriores, verifica si esta función está disponible compatibles con lo siguiente:

if ("usb" in navigator && "forget" in USBDevice.prototype) {
  // forget() is supported.
}

Límites en el tamaño de transferencia

Algunos sistemas operativos imponen límites sobre la cantidad de datos que pueden formar parte transacciones USB pendientes. Dividir los datos en transacciones más pequeñas y solo enviar varios a la vez ayuda a evitar esas limitaciones. También reduce la cantidad de memoria usada y permite que tu app informe el progreso como se completan las transferencias.

Debido a que las transferencias múltiples enviadas a un endpoint siempre se ejecutan en orden, es es posible mejorar la capacidad de procesamiento enviando varios fragmentos en cola para evitar latencia entre transferencias USB. Cada vez que un fragmento se transmita por completo, notificar a tu código que debe proporcionar más datos, como se documenta en el asistente a continuación.

const BULK_TRANSFER_SIZE = 16 * 1024; // 16KB
const MAX_NUMBER_TRANSFERS = 3;

async function sendRawPayload(device, endpointNumber, data) {
  let i = 0;
  let pendingTransfers = [];
  let remainingBytes = data.byteLength;
  while (remainingBytes > 0) {
    const chunk = data.subarray(
      i * BULK_TRANSFER_SIZE,
      (i + 1) * BULK_TRANSFER_SIZE
    );
    // If we've reached max number of transfers, let's wait.
    if (pendingTransfers.length == MAX_NUMBER_TRANSFERS) {
      await pendingTransfers.shift();
    }
    // Submit transfers that will be executed in order.
    pendingTransfers.push(device.transferOut(endpointNumber, chunk));
    remainingBytes -= chunk.byteLength;
    i++;
  }
  // And wait for last remaining transfers to complete.
  await Promise.all(pendingTransfers);
}

Sugerencias

La depuración por USB en Chrome es más fácil con la página interna about://device-log donde puedes ver todos los eventos relacionados con dispositivos USB en un solo lugar.

Captura de pantalla de la página de registro del dispositivo para depurar WebUSB en Chrome
Página de registro del dispositivo en Chrome para depurar la API de WebUSB.

La página interna about://usb-internals también es útil y te permite para simular la conexión y desconexión de dispositivos WebUSB virtuales. Esto resulta útil para realizar pruebas de IU sin hardware real.

Captura de pantalla de la página interna para depurar WebUSB en Chrome
Página interna en Chrome para depurar la API de WebUSB.

En la mayoría de los sistemas Linux, los dispositivos USB se asignan con permisos de solo lectura según de forma predeterminada. Para permitir que Chrome abra un dispositivo USB, deberás agregar un nuevo udev estándar. Crea un archivo en /etc/udev/rules.d/50-yourdevicename.rules con el siguiente contenido:

SUBSYSTEM=="usb", ATTR{idVendor}=="[yourdevicevendor]", MODE="0664", GROUP="plugdev"

donde [yourdevicevendor] es 2341, por ejemplo, si tu dispositivo es Arduino. También se puede agregar ATTR{idProduct} para una regla más específica. Asegúrate de que user es un miembro del grupo plugdev. Luego, solo vuelve a conectar el dispositivo.

Recursos

Envía un tweet a @ChromiumDev con el hashtag #WebUSB y cuéntanos dónde y cómo la utilizas.

Agradecimientos

Gracias a Joe Medley por leer este artículo.