La API de WebUSB lleva el USB a la Web, lo que lo hace más seguro y fácil de usar.
Si dije claramente "USB", es muy probable que pienses de inmediato en teclados, mouse, audio, video y dispositivos de almacenamiento. Tienes razón, pero hay otros tipos de dispositivos de bus universal en serie (USB).
Estos dispositivos USB no estandarizados requieren que los proveedores de hardware escriban controladores y SDKs específicos para cada plataforma para que tú (el desarrollador) puedas aprovecharlos. Lamentablemente, este código específico de la plataforma históricamente impidió que la Web usara estos dispositivos. Esa es una de las razones por las que se creó la API de WebUSB: para proporcionar una forma de exponer los servicios de dispositivos USB a la Web. Con esta API, los fabricantes de hardware podrán compilar SDK de JavaScript multiplataforma para sus dispositivos.
Pero lo más importante es que llevará el USB a la Web, lo que lo hará más seguro y fácil de usar.
Veamos el comportamiento que podrías esperar con la API de WebUSB:
- Compra un dispositivo USB.
- Conéctalo a tu computadora. Aparecerá una notificación de inmediato con el sitio web correcto al que debes ir para este dispositivo.
- Haz clic en la notificación. El sitio web está listo para usarse.
- Haz clic para conectarte, y se mostrará un selector de dispositivos USB en Chrome donde podrás elegir tu dispositivo.
Listo.
¿Cómo sería este procedimiento sin la API de WebUSB?
- Instala una aplicación específica de la plataforma.
- Si es compatible con mi sistema operativo, verifica que haya descargado el archivo correcto.
- Instala el dispositivo. Si tienes suerte, no recibirás mensajes del SO ni ventanas emergentes que te advertirán sobre la instalación de controladores o aplicaciones de Internet. Si tienes mala suerte, los controladores o las aplicaciones instalados fallan y dañan la computadora. (recuerda que la Web está diseñada para contener sitios web que no funcionan correctamente).
- Si solo usas la función una vez, el código permanecerá en tu computadora hasta que decidas quitarlo. (en la Web, el espacio para los elementos que no se usan se recupera con el tiempo).
Antes de comenzar
En este artículo, se da por sentado que tienes conocimientos básicos sobre el funcionamiento del USB. De lo contrario, te recomiendo que leas USB in a 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 la API de WebUSB en el campo, antes agregamos esta función en Chrome 54 y Chrome 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 que deberás compilar teniendo en cuenta TLS.
Se requiere un gesto del usuario
Como precaución de seguridad, solo se puede llamar a navigator.usb.requestDevice()
a través de un gesto del usuario, como un toque o un clic con el mouse.
Política de permisos
Una política de permisos es un mecanismo que les permite a los desarrolladores habilitar o inhabilitar de manera selectiva varias funciones y APIs del navegador. Se puede definir a través de un encabezado HTTP o un atributo "allow" de iframe.
Puedes definir una política de permisos que controle si el atributo usb
se expone 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 no se permite WebUSB:
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 USB:
<iframe allowpaymentrequest allow="usb; fullscreen"></iframe>
Comencemos a programar
La API de WebUSB depende en gran medida de las promesas de JavaScript. Si no estás familiarizado con ellas, consulta este excelente instructivo sobre promesas. Otra cosa, () => {}
son simplemente funciones de flecha de ECMAScript 2015.
Cómo obtener acceso a dispositivos USB
Puedes pedirle al usuario que seleccione un solo dispositivo USB conectado con navigator.usb.requestDevice()
o llamar a navigator.usb.getDevices()
para obtener una lista de todos los dispositivos USB conectados a los que se le otorgó acceso al sitio web.
La función navigator.usb.requestDevice()
toma un objeto JavaScript obligatorio que define filters
. Estos filtros se usan para hacer coincidir cualquier dispositivo USB con los identificadores del proveedor (vendorId
) y, de manera opcional, del producto (productId
) determinados.
Las claves classCode
, protocolCode
, serialNumber
y subclassCode
también se pueden definir allí.
Por ejemplo, aquí te mostramos cómo obtener acceso 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 lo preguntes, no encontré este número hexadecimal 0x2341
por arte de magia. Solo busqué la palabra “Arduino” en esta lista de IDs de USB.
El device
de USB que se muestra en la promesa cumplida anterior tiene información básica, pero importante, sobre el dispositivo, como la versión USB compatible, el tamaño máximo del paquete, los IDs del producto y del proveedor, la cantidad de configuraciones posibles 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 y define una URL de página de destino, Chrome mostrará una notificación persistente cuando se conecte el dispositivo USB. Si haces clic en esta notificación, se abrirá la página de destino.
Cómo comunicarse con una placa USB de Arduino
Muy bien, ahora veamos lo fácil que es comunicarse desde una placa Arduino compatible con WebUSB a través del puerto USB. Consulta las instrucciones en https://github.com/webusb/arduino para habilitar tus bocetos con WebUSB.
No te preocupes, explicaré todos los métodos de dispositivos WebUSB que se mencionan a continuación más adelante en 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 de WebUSB que uso solo implementa un protocolo de ejemplo (basado en el protocolo serie USB estándar) y que los fabricantes pueden crear cualquier conjunto y tipo de extremos que deseen. Las transferencias de control son especialmente útiles para comandos de configuración pequeños, ya que obtienen prioridad de bus y tienen una estructura bien definida.
Este es el boceto que se subió a la placa 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 hace básicamente dos cosas:
- El dispositivo actúa como un dispositivo WebUSB que permite que Chrome lea la URL de la página de destino.
- Expone una API de serie WebUSB que puedes usar para anular la predeterminada.
Vuelve a mirar el código JavaScript. Una vez que el usuario elige el device
, device.open()
ejecuta todos los pasos específicos de la plataforma para iniciar una sesión con el dispositivo USB. Luego, debo seleccionar una configuración de USB disponible con device.selectConfiguration()
. Recuerda que una configuración especifica cómo se alimenta el dispositivo, su consumo máximo de energía y la cantidad de interfaces.
A propósito de las interfaces, también debo solicitar acceso exclusivo con device.claimInterface()
, ya que los datos solo se pueden transferir a una interfaz o a los extremos asociados cuando se reclama la interfaz. Por último, es necesario llamar a device.controlTransferOut()
para configurar el dispositivo Arduino con los comandos adecuados para comunicarse a través de la API de serie de WebUSB.
A partir de ahí, device.transferIn()
realiza una transferencia masiva al dispositivo para informarle que el host está listo para recibir datos masivos. Luego, la promesa se completa con un objeto result
que contiene un data
DataView que se debe analizar de forma adecuada.
Si conoces el USB, todo esto debería resultarte familiar.
Quiero más
La API de WebUSB te permite interactuar con todos los tipos de transferencia o extremo USB:
- Las transferencias de CONTROL, que se usan para enviar o recibir parámetros de configuración o comando a un dispositivo USB, se controlan con
controlTransferIn(setup, length)
ycontrolTransferOut(setup, data)
. - Las transferencias de INTERRUPCIÓN, que se usan para una pequeña cantidad de datos sensibles al tiempo, se controlan con los mismos métodos que las transferencias masivas con
transferIn(endpointNumber, length)
ytransferOut(endpointNumber, data)
. - Las transferencias ISOCRONAS, que se usan para transmisiones de datos, como video y sonido, se controlan con
isochronousTransferIn(endpointNumber, packetLengths)
yisochronousTransferOut(endpointNumber, data, packetLengths)
. - Las transferencias masivas, que se usan para transferir una gran cantidad de datos no urgentes de forma confiable, se controlan con
transferIn(endpointNumber, length)
ytransferOut(endpointNumber, data)
.
También puedes consultar el proyecto WebLight de Mike Tsao, que proporciona un ejemplo básico de cómo compilar un dispositivo LED controlado por USB diseñado para la API de WebUSB (en este caso, no se usa Arduino). Encontrarás hardware, software y firmware.
Cómo revocar el acceso a un dispositivo USB
El sitio web puede limpiar los permisos de acceso a un dispositivo USB que ya no necesita llamando a forget()
en la instancia de USBDevice
. Por ejemplo, en el caso de una aplicación web educativa que se usa en una computadora compartida con muchos dispositivos, una gran cantidad de permisos acumulados generados por el usuario crea una mala experiencia del usuario.
// Voluntarily revoke access to this USB device.
await device.forget();
Como forget()
está disponible en Chrome 101 o versiones posteriores, verifica si esta función es compatible con lo siguiente:
if ("usb" in navigator && "forget" in USBDevice.prototype) {
// forget() is supported.
}
Límites de tamaño de transferencia
Algunos sistemas operativos imponen límites a la cantidad de datos que pueden formar parte de las transacciones USB pendientes. Dividir los datos en transacciones más pequeñas y enviar solo algunas a la vez ayuda a evitar esas limitaciones. También reduce la cantidad de memoria utilizada y permite que tu aplicación informe el progreso a medida que se completan las transferencias.
Debido a que las transferencias enviadas a un extremo siempre se ejecutan en orden, es posible mejorar la capacidad de procesamiento si se envían varios fragmentos en cola para evitar la latencia entre transferencias USB. Cada vez que se transmita un fragmento por completo, se notificará a tu código que debe proporcionar más datos, como se documenta en el siguiente ejemplo de la función auxiliar.
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 de USB en Chrome es más fácil con la página interna about://device-log
, en la que puedes ver todos los eventos relacionados con el dispositivo USB en un solo lugar.
La página interna about://usb-internals
también es útil y te permite simular la conexión y desconexión de dispositivos WebUSB virtuales.
Esto es útil para realizar pruebas de IU sin hardware real.
En la mayoría de los sistemas Linux, los dispositivos USB se asignan con permisos de solo lectura de forma predeterminada. Para permitir que Chrome abra un dispositivo USB, deberás agregar una nueva regla de udev. 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
si tu dispositivo es un Arduino, por ejemplo.
También se puede agregar ATTR{idProduct}
para una regla más específica. Asegúrate de que tu user
sea miembro del grupo plugdev
. Luego, solo vuelve a conectar el dispositivo.
Recursos
- Stack Overflow: https://stackoverflow.com/questions/tagged/webusb
- Especificación de la API de WebUSB: http://wicg.github.io/webusb/
- Estado de las funciones de Chrome: https://www.chromestatus.com/feature/5651917954875392
- Problemas con las especificaciones: https://github.com/WICG/webusb/issues
- Errores de implementación: http://crbug.com?q=component:Blink>USB
- WebUSB ❤ ️Arduino: https://github.com/webusb/arduino
- IRC: #webusb en el IRC del W3C
- Lista de distribución de WICG: https://lists.w3.org/Archives/Public/public-wicg/
- Proyecto WebLight: https://github.com/sowbug/weblight
Envía un tuit a @ChromiumDev con el hashtag #WebUSB
y cuéntanos dónde y cómo lo usas.
Agradecimientos
Agradecemos a Joe Medley por revisar este artículo.