Compila un dispositivo para aprovechar al máximo la API de WebUSB.
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.
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: