Cómo compilar un dispositivo para WebUSB

Compila un dispositivo para aprovechar al máximo la API de WebUSB.

Reilly Grant
Reilly Grant

En este artículo, se explica cómo compilar un dispositivo para aprovechar al máximo la API de WebUSB. Para obtener una breve introducción a la API, consulta Cómo acceder a dispositivos USB en la Web.

Información general

El bus universal en serie (USB) se convirtió en la interfaz física más común para conectar periféricos a dispositivos de computación móviles y de escritorio. Además de definir las características eléctricas del bus y un modelo general para comunicarse con un dispositivo, las especificaciones USB incluyen un conjunto de especificaciones de clase de dispositivo. Estos son modelos generales para clases particulares de dispositivos, como almacenamiento, audio, video, redes, etc., que los fabricantes de dispositivos pueden implementar. La ventaja de estas especificaciones de clase de dispositivo es que un proveedor de sistema operativo puede implementar un solo controlador según la especificación de clase (un "controlador de clase") y cualquier dispositivo que implemente esa clase será compatible. Esta fue una gran mejora con respecto a los fabricantes que necesitan escribir sus propios controladores de dispositivos.

Sin embargo, algunos dispositivos no se ajustan a una de estas clases de dispositivos estandarizadas. En su lugar, el fabricante puede elegir etiquetar su dispositivo para indicar que implementa la clase específica del proveedor. En este caso, el sistema operativo elige qué controlador de dispositivo cargar según la información proporcionada en el paquete del controlador del proveedor (por lo general, un conjunto de ID del producto y del proveedor que se sabe que implementan un protocolo específico de proveedor en particular).

Otra característica del USB es que los dispositivos pueden proporcionar varias interfaces al host al que están conectados. Cada interfaz puede implementar una clase estandarizada o ser específica de un proveedor. Cuando un sistema operativo elige los controladores adecuados para controlar el dispositivo, un controlador diferente puede reclamar cada interfaz. Por ejemplo, una cámara web USB proporciona dos interfaces: una que implementa la clase de video USB (para la cámara) y otra que implementa la clase de audio USB (para el micrófono). El sistema operativo no carga un solo "controlador de cámara web", sino controladores de clase de video y audio independientes que asumen la responsabilidad de las funciones independientes del dispositivo. Esta composición de las clases de interfaz proporciona una mayor flexibilidad.

Conceptos básicos de API

Muchas de las clases USB estándar tienen APIs web correspondientes. Por ejemplo, una página puede capturar videos de un dispositivo de clase de video con getUserMedia() o recibir eventos de entrada desde un dispositivo de clase de interfaz humana (HID) escuchando KeyboardEvents o PointerEvents, o bien usando el Gamepad o la API de WebHID. Así como no todos los dispositivos implementan una definición de clase estandarizada, no todos implementan funciones que correspondan a las APIs de plataformas web existentes. En ese caso, la API de WebUSB puede llenar esa brecha proporcionando una forma para que los sitios reclamen una interfaz específica del proveedor e implementen la compatibilidad con ella directamente desde su página.

Los requisitos específicos para que un dispositivo sea accesible a través de WebUSB varían un poco de una plataforma a otra debido a diferencias en la forma en que los sistemas operativos administran los dispositivos USB. Sin embargo, el requisito básico es que un dispositivo no debería tener un controlador que reclame la interfaz que la página quiere controlar. Puede ser un controlador de clase genérico que proporcione el proveedor del SO o un controlador de dispositivo que proporcione el proveedor. Como los dispositivos USB pueden proporcionar varias interfaces, cada una de las cuales puede tener su propio controlador, es posible compilar un dispositivo para el cual un controlador reclama algunas interfaces y otras son accesibles para el navegador.

Por ejemplo, un teclado USB de alta gama puede proporcionar una interfaz de clase HID que reclamará el subsistema de entrada del sistema operativo y una interfaz específica del proveedor que permanece disponible para WebUSB para que la use una herramienta de configuración. Esta herramienta se puede publicar en el sitio web del fabricante, lo que permite al usuario cambiar aspectos del comportamiento del dispositivo, como las teclas macro y los efectos de iluminación, sin instalar ningún software específico de la plataforma. El descriptor de configuración de un dispositivo de este tipo debería verse de la siguiente manera:

Valor Campo Descripción
Descriptor de configuración
0x09 bLength Tamaño de este descriptor
0x02 bDescriptorType Descriptor de configuración
0x0039 wTotalLength Longitud total de esta serie de descriptores
0x02 bNumInterfaces Cantidad de interfaces
0x01 bConfigurationValue Configuration 1
0x00 iConfiguration Nombre de la configuración (ninguno)
0b1010000 bmAttributes Dispositivo autónomo con activación remota
0x32 bMaxPower La potencia máxima se expresa en incrementos de 2 mA
Descriptor de interfaz
0x09 bLength Tamaño de este descriptor
0x04 bDescriptorType Descriptor de interfaz
0x00 bInterfaceNumber Interfaz 0
0x00 bAlternateSetting Parámetro de configuración alternativo 0 (predeterminado)
0x01 bNumEndpoints 1 extremo
0x03 bInterfaceClass Clase de interfaz HID
0x01 bInterfaceSubClass Subclase de la interfaz de inicio
0x01 bInterfaceProtocol Teclado
0x00 iInterface Nombre de la interfaz (ninguno)
Descriptor de HID
0x09 bLength Tamaño de este descriptor
0x21 bDescriptorType Descriptor de HID
0x0101 bcdHID HID versión 1.1
0x00 bCountryCode País de segmentación del hardware
0x01 bNumDescriptors Cantidad de descriptores de clase HID que se deben seguir
0x22 bDescriptorType Tipo de descriptor de informe
0x003F wDescriptorLength Longitud total del descriptor del informe
Descriptor de extremo
0x07 bLength Tamaño de este descriptor
0x05 bDescriptorType Descriptor de extremo
0b10000001 bEndpointAddress Extremo 1 (IN)
0b00000011 bmAttributes Interrumpir
0x0008 wMaxPacketSize Paquetes de 8 bytes
0x0A bInterval Intervalo de 10 ms
Descriptor de interfaz
0x09 bLength Tamaño de este descriptor
0x04 bDescriptorType Descriptor de interfaz
0x01 bInterfaceNumber Interfaz 1
0x00 bAlternateSetting Parámetro de configuración alternativo 0 (predeterminado)
0x02 bNumEndpoints 2 extremos
0xFF bInterfaceClass Clase de interfaz específica del proveedor
0x00 bInterfaceSubClass
0x00 bInterfaceProtocol
0x00 iInterface Nombre de la interfaz (ninguno)
Descriptor de extremo
0x07 bLength Tamaño de este descriptor
0x05 bDescriptorType Descriptor de extremo
0b10000010 bEndpointAddress Extremo 1 (IN)
0b00000010 bmAttributes Subida masiva
0x0040 wMaxPacketSize Paquetes de 64 bytes
0x00 bInterval N/A para extremos masivos
Descriptor de extremo
0x07 bLength Tamaño de este descriptor
0x05 bDescriptorType Descriptor de extremo
0b00000011 bEndpointAddress Extremo 3 (SALIDA)
0b00000010 bmAttributes Subida masiva
0x0040 wMaxPacketSize Paquetes de 64 bytes
0x00 bInterval N/A para extremos masivos

El descriptor de configuración consta de varios descriptores concatenados. Cada uno comienza con los campos bLength y bDescriptorType para que se puedan identificar. La primera es una interfaz HID con un descriptor HID asociado y un único extremo que se usa para enviar eventos de entrada al sistema operativo. La segunda es una interfaz específica del proveedor con dos extremos que se pueden usar para enviar comandos al dispositivo y recibir respuestas a cambio.

Descriptores de WebUSB

Si bien WebUSB puede funcionar con muchos dispositivos sin modificaciones de firmware, se habilitan funciones adicionales marcando el dispositivo con descriptores específicos que indiquen compatibilidad con WebUSB. Por ejemplo, puedes especificar una URL de página de destino a la que el navegador pueda dirigir al usuario cuando tu dispositivo esté conectado.

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

El Almacén de objetos del dispositivo binario (BOS) es un concepto introducido en la versión USB 3.0, pero también se adaptó a dispositivos USB 2.0 como parte de la versión 2.1. Para declarar la compatibilidad con WebUSB, se debe incluir el siguiente descriptor de capacidad de la plataforma en el descriptor de BOS:

Valor Campo Descripción
Descriptor de almacén de objetos del dispositivo binario
0x05 bLength Tamaño de este descriptor
0x0F bDescriptorType Descriptor de almacén de objetos del dispositivo binario
0x001D wTotalLength Longitud total de esta serie de descriptores
0x01 bNumDeviceCaps Cantidad de descriptores de capacidad del dispositivo en la BOS
Descriptor de capacidad de la plataforma WebUSB
0x18 bLength Tamaño de este descriptor
0x10 bDescriptorType Descriptor de capacidad del dispositivo
0x05 bDevCapabilityType Descriptor de capacidades de la plataforma
0x00 bReserved
{0x38, 0xB6, 0x08, 0x34, 0xA9, 0x09, 0xA0, 0x47, 0x8B, 0xFD, 0xA0, 0x76, 0x88, 0x15, 0xB6, 0x65} PlatformCapablityUUID GUID del descriptor de capacidad de la plataforma de WebUSB en formato Little-endian
0x0100 bcdVersion Descriptor de WebUSB versión 1.0
0x01 bVendorCode Valor de bRequest para WebUSB
0x01 iLandingPage URL de la página de destino

El UUID de capacidad de la plataforma lo identifica como un descriptor de capacidad de la plataforma WebUSB, que proporciona información básica sobre el dispositivo. Para que el navegador recupere más información sobre el dispositivo, usa el valor bVendorCode para emitir solicitudes adicionales al dispositivo. La única solicitud especificada actualmente es GET_URL, que muestra un descriptor de URL. Son similares a los descriptores de cadena, pero están diseñados para codificar URL con la menor cantidad de bytes. Un descriptor de URL para "https://google.com" debería verse de la siguiente manera:

Valor Campo Descripción
Descriptor de URL
0x0D bLength Tamaño de este descriptor
0x03 bDescriptorType Descriptor de URL
0x01 bScheme https://
"google.com" URL Contenido de la URL codificado en UTF-8

Cuando el dispositivo se conecta por primera vez, el navegador lee el descriptor de BOS mediante la emisión de esta transferencia de control GET_DESCRIPTOR estándar:

bmRequestType bRequest wValue wIndex wLength Datos (respuesta)
0b10000000 0x06 0x0F00 0x0000 * El descriptor de BOS

Por lo general, esta solicitud se realiza dos veces, la primera vez con un wLength lo suficientemente grande como para que el host descubra el valor del campo wTotalLength sin confirmar una transferencia grande y, luego, otra vez cuando se conoce la longitud completa del descriptor.

Si el descriptor de capacidad de la plataforma de WebUSB tiene el campo iLandingPage establecido en un valor distinto de cero, el navegador realizará una solicitud GET_URL específica de WebUSB mediante una transferencia de control con bRequest establecido en el valor bVendorCode del descriptor de capacidad de la plataforma y wValue establecido en el valor iLandingPage. El código de solicitud para GET_URL (0x02) va en wIndex:

bmRequestType bRequest wValue wIndex wLength Datos (respuesta)
0b11000000 0x01 0x0001 0x0002 * El descriptor de la URL

Nuevamente, esta solicitud se puede emitir dos veces para el primer sondeo de la longitud del descriptor que se está leyendo.

Consideraciones específicas de la plataforma

Si bien la API de WebUSB intenta proporcionar una interfaz coherente para acceder a dispositivos USB, los desarrolladores aún deben estar al tanto de los requisitos que se imponen a las aplicaciones, como los requisitos de navegadores web, para acceder a los dispositivos.

macOS

No se necesita nada especial para macOS. Un sitio web que use WebUSB puede conectarse al dispositivo y reclamar cualquier interfaz que no haya reclamado un controlador de kernel ni otra aplicación.

Linux

Linux es como macOS, pero, de forma predeterminada, la mayoría de las distribuciones no configuran cuentas de usuario con permiso para abrir dispositivos USB. Un daemon del sistema llamado udev es responsable de asignar al usuario y al grupo permiso para acceder a un dispositivo. Una regla como esta asignará la propiedad de un dispositivo que coincida con los IDs del producto y del proveedor especificados al grupo plugdev, que es un grupo común para los usuarios con acceso a periféricos:

SUBSYSTEM=="usb", ATTR{idVendor}=="XXXX", ATTR{idProduct}=="XXXX", GROUP="plugdev"

Reemplaza XXXX por los IDs hexadecimales del proveedor y del producto del dispositivo, p.ej., ATTR{idVendor}=="18d1", ATTR{idProduct}=="4e11" coincidiría con un teléfono Nexus One. Se deben escribir sin el prefijo habitual “0x” y todas en minúsculas para que se reconozcan correctamente. Para encontrar los IDs de tu dispositivo, ejecuta la herramienta de línea de comandos lsusb.

Esta regla debe colocarse en un archivo en el directorio /etc/udev/rules.d y tendrá efecto en cuanto se conecte el dispositivo. No es necesario reiniciar udev.

Android

La plataforma de Android se basa en Linux, pero no requiere ninguna modificación en la configuración del sistema. De forma predeterminada, cualquier dispositivo que no tenga un controlador integrado en el sistema operativo estará disponible para el navegador. Sin embargo, los desarrolladores deben tener en cuenta que los usuarios encontrarán un paso adicional cuando se conecten al dispositivo. Una vez que un usuario selecciona un dispositivo en respuesta a una llamada a requestDevice(), Android mostrará un mensaje para preguntar si permite que Chrome acceda a él. Este mensaje también vuelve a aparecer si un usuario regresa a un sitio web que ya tiene permiso para conectarse a un dispositivo y este llama a open().

Además, se podrá acceder a más dispositivos en Android que en Linux de escritorio, ya que se incluyen menos controladores de forma predeterminada. Una omisión notable, por ejemplo, es la clase USB CDC-ACM que suelen implementar los adaptadores USB a serie, ya que no hay una API en el SDK de Android para comunicarse con un dispositivo en serie.

ChromeOS

ChromeOS también se basa en Linux y no requiere ninguna modificación en la configuración del sistema. El servicio permission_broker controla el acceso a dispositivos USB y permitirá que el navegador acceda a ellos siempre que haya al menos una interfaz no reclamada.

Windows

El modelo de controlador de Windows presenta un requisito adicional. A diferencia de las plataformas anteriores, la capacidad de abrir un dispositivo USB desde una aplicación del usuario no es la predeterminada, incluso si no hay controladores cargados. En cambio, hay un controlador especial, WinUSB, que se debe cargar para proporcionar la interfaz que usan las aplicaciones para acceder al dispositivo. Esto se puede hacer con un archivo de información del controlador (INF) personalizado instalado en el sistema o modificando el firmware del dispositivo para proporcionar los descriptores de compatibilidad del SO de Microsoft durante la enumeración.

Archivo de información del controlador (INF)

Un archivo de información del conductor le indica a Windows qué hacer cuando se encuentra un dispositivo por primera vez. Como el sistema del usuario ya incluye el controlador WinUSB, todo lo que se necesita es que el archivo INF asocie tu ID del producto y proveedor con esta regla de instalación nueva. El siguiente archivo es un ejemplo básico. Guárdala en un archivo con la extensión .inf, cambia las secciones marcadas con “X” y, luego, haz clic con el botón derecho y elige “Instalar” en el menú contextual.

[Version]
Signature   = "$Windows NT$"
Class       = USBDevice
ClassGUID   = {88BAE032-5A81-49f0-BC3D-A4FF138216D6}
Provider    = %ManufacturerName%
CatalogFile = WinUSBInstallation.cat
DriverVer   = 09/04/2012,13.54.20.543

; ========== Manufacturer/Models sections ===========

[Manufacturer]
%ManufacturerName% = Standard,NTx86,NTia64,NTamd64

[Standard.NTx86]
%USB\MyCustomDevice.DeviceDesc% = USB_Install,USB\VID_XXXX&PID_XXXX

[Standard.NTia64]
%USB\MyCustomDevice.DeviceDesc% = USB_Install,USB\VID_XXXX&PID_XXXX

[Standard.NTamd64]
%USB\MyCustomDevice.DeviceDesc% = USB_Install,USB\VID_XXXX&PID_XXXX

; ========== Class definition ===========

[ClassInstall32]
AddReg = ClassInstall_AddReg

[ClassInstall_AddReg]
HKR,,,,%ClassName%
HKR,,NoInstallClass,,1
HKR,,IconPath,%REG_MULTI_SZ%,"%systemroot%\system32\setupapi.dll,-20"
HKR,,LowerLogoVersion,,5.2

; =================== Installation ===================

[USB_Install]
Include = winusb.inf
Needs   = WINUSB.NT

[USB_Install.Services]
Include = winusb.inf
Needs   = WINUSB.NT.Services

[USB_Install.HW]
AddReg = Dev_AddReg

[Dev_AddReg]
HKR,,DeviceInterfaceGUIDs,0x10000,"{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}"

; =================== Strings ===================

[Strings]
ManufacturerName              = "Your Company Name Here"
ClassName                     = "Your Company Devices"
USB\MyCustomDevice.DeviceDesc = "Your Device Name Here"

En la sección [Dev_AddReg], se configura el conjunto de DeviceInterfaceGUIDs para el dispositivo. Cada interfaz del dispositivo debe tener un GUID para que una aplicación pueda encontrarlo y conectarse con él a través de la API de Windows. Usa el cmdlet New-Guid de PowerShell o una herramienta en línea para generar un GUID aleatorio.

Para fines de desarrollo, la herramienta Zadig proporciona una interfaz fácil de reemplazar el controlador cargado por una interfaz USB por el controlador WinUSB.

Descriptores de compatibilidad de Microsoft OS

El enfoque de archivo INF anterior es engorroso porque requiere que se configure la máquina de cada usuario con anticipación. Windows 8.1 y las versiones posteriores ofrecen una alternativa mediante el uso de descriptores USB personalizados. Estos descriptores proporcionan información al sistema operativo Windows cuando se conecta el dispositivo por primera vez, que normalmente se incluiría en el archivo INF.

Una vez que hayas configurado los descriptores de WebUSB, también será fácil agregar los descriptores de compatibilidad del SO de Microsoft. Primero, extiende el descriptor de BOS con este descriptor de capacidad de plataforma adicional. Asegúrate de actualizar wTotalLength y bNumDeviceCaps para considerarlo.

Valor Campo Descripción
Descriptor de capacidades de la plataforma Microsoft OS 2.0
0x1C bLength Tamaño de este descriptor
0x10 bDescriptorType Descriptor de capacidad del dispositivo
0x05 bDevCapabilityType Descriptor de capacidades de la plataforma
0x00 bReserved
{0xDF, 0x60, 0xDD, 0xD8, 0x89, 0x45, 0xC7, 0x4C, 0x9C, 0xD2, 0x65, 0x9D, 0x9E, 0x64, 0x8A, 0x9F} PlatformCapablityUUID GUID del descriptor de compatibilidad de la plataforma de Microsoft OS 2.0 en formato Little-endian
0x06030000 dwWindowsVersion Versión mínima de Windows compatible (Windows 8.1)
0x00B2 wMSOSDescriptorSetTotalLength Longitud total del conjunto de descriptores
0x02 bMS_VendorCode Valor de bRequest para recuperar más descriptores de Microsoft
0x00 bAltEnumCode El dispositivo no admite la enumeración alternativa

Al igual que con los descriptores de WebUSB, debes elegir un valor bRequest para que se use en las transferencias de control relacionadas con estos descriptores. En este ejemplo, elegí 0x02. 0x07, ubicado en wIndex, es el comando para recuperar el conjunto de descriptores del SO de Microsoft 2.0 del dispositivo.

bmRequestType bRequest wValue wIndex wLength Datos (respuesta)
0b11000000 0x02 0x0000 0x0007 * Conjunto de descriptores de MS OS 2.0

Un dispositivo USB puede tener varias funciones, por lo que la primera parte del conjunto de descriptores describe con qué función están asociadas las siguientes propiedades. En el siguiente ejemplo, se configura la interfaz 1 de un dispositivo compuesto. El descriptor le proporciona al SO dos datos importantes sobre esta interfaz. El descriptor de ID compatible le indica a Windows que este dispositivo es compatible con el controlador WinUSB. El descriptor de propiedades de registro funciona de manera similar a la sección [Dev_AddReg] del ejemplo de INF anterior y configura una propiedad de registro para asignar a esta función un GUID de la interfaz de dispositivo.

Valor Campo Descripción
Encabezado del conjunto de descriptores de Microsoft OS 2.0
0x000A wLength Tamaño de este descriptor
0x0000 wDescriptorType Descriptor de encabezado de conjunto de descriptores
0x06030000 dwWindowsVersion Versión mínima de Windows compatible (Windows 8.1)
0x00B2 wTotalLength Longitud total del conjunto de descriptores
Encabezado del subconjunto de configuración de Microsoft OS 2.0
0x0008 wLength Tamaño de este descriptor
0x0001 wDescriptorType Descripción del encabezado del subconjunto de configuración
0x00 bConfigurationValue Se aplica a la configuración 1 (indexada desde 0 a pesar de las configuraciones que normalmente se indexan desde 1)
0x00 bReserved Debe establecerse en 0
0x00A8 wTotalLength Longitud total del subconjunto que incluye este encabezado
Encabezado del subconjunto de funciones de Microsoft OS 2.0
0x0008 wLength Tamaño de este descriptor
0x0002 wDescriptorType Descriptor de encabezado del subconjunto de funciones
0x01 bFirstInterface Primera interfaz de la función
0x00 bReserved Debe establecerse en 0
0x00A0 wSubsetLength Longitud total del subconjunto que incluye este encabezado
Descriptor de ID compatible con Microsoft OS 2.0
0x0014 wLength Tamaño de este descriptor
0x0003 wDescriptorType Descriptor de ID compatible
"WINUSB\0\0" CompatibileID Cadena ASCII con relleno de 8 bytes
"\0\0\0\0\0\0\0\0" SubCompatibleID Cadena ASCII con relleno de 8 bytes
Descriptor de propiedad del registro de Microsoft OS 2.0
0x0084 wLength Tamaño de este descriptor
0x0004 wDescriptorType Descriptor de propiedad del registro
0x0007 wPropertyDataType REG_MULTI_SZ
0x002A wPropertyNameLength Longitud del nombre de la propiedad
"DeviceInterfaceGUIDs\0" PropertyName Nombre de la propiedad con terminador nulo codificado en UTF-16LE
0x0050 wPropertyDataLength Longitud del valor de la propiedad
"{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}\0\0" PropertyData GUID más dos terminadores nulos codificados en UTF-16LE

Windows solo consultará el dispositivo una vez para obtener esta información. Si el dispositivo no responde con descriptores válidos, no volverá a preguntar la próxima vez que se conecte. Microsoft proporcionó una lista de entradas de registro de dispositivos USB en las que se describen las entradas de registro creadas cuando se enumera un dispositivo. Cuando pruebes, borra las entradas creadas para un dispositivo para forzar a Windows a que intente volver a leer los descriptores.

Si quieres obtener más información, consulta la entrada de blog de Microsoft sobre cómo usar estos descriptores.

Ejemplos

En estos proyectos, puedes encontrar código de ejemplo que implementa dispositivos compatibles con WebUSB que incluyen descriptores de WebUSB y descriptores de Microsoft OS: