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.
Segundo plano
El bus universal en serie (USB) se convirtió en la interfaz física más común para conectar periféricos a computadoras de escritorio y dispositivos móviles. Además de definir las características eléctricas del bus y un modelo general para comunicarse con un dispositivo, las especificaciones de 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 sistemas operativos puede implementar un solo controlador basado en la especificación de clase (un "controlador de clase") y se admitirá cualquier dispositivo que implemente esa clase. Esta fue una gran mejora en comparación con cada fabricante que necesitaba escribir sus propios controladores de dispositivos.
Sin embargo, algunos dispositivos no se ajustan a una de estas clases de dispositivos estandarizados. En su lugar, un fabricante puede optar por etiquetar su dispositivo como que implementa la clase específica del proveedor. En este caso, el sistema operativo elige qué controlador de dispositivo cargar en función de la información proporcionada en el paquete de controladores del proveedor, por lo general, un conjunto de IDs de proveedor y producto que se sabe que implementan un protocolo específico de un 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 del proveedor. Cuando un sistema operativo elige los controladores correctos para controlar el dispositivo, cada interfaz puede ser reclamada por un controlador diferente. Por ejemplo, una cámara web USB suele proporcionar 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 que carga controladores independientes de clase de audio y video que se responsabilizan de las funciones independientes del dispositivo. Esta composición de clases de interfaz proporciona una mayor flexibilidad.
Conceptos básicos de API
Muchas de las clases USB estándar tienen las 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 de un dispositivo de clase de interfaz humana (HID) escuchando KeyboardEvents o PointerEvents, o bien con Control de juegos o la API de WebHID.
Así como no todos los dispositivos implementan una definición de clase estandarizada, no todos los dispositivos implementan funciones que corresponden a las APIs de plataformas web existentes. Cuando este es el caso, la API de WebUSB puede llenar esa brecha, ya que proporciona una forma para que los sitios reclamen una interfaz específica del proveedor y la implementen directamente desde su página.
Los requisitos específicos para que se pueda acceder a un dispositivo a través de WebUSB varían ligeramente de una plataforma a otra debido a las diferencias en la forma en que los sistemas operativos administran los dispositivos USB, pero el requisito básico es que un dispositivo no debería tener un controlador que reclame la interfaz que la página desea controlar. Puede ser un controlador de clase genérico que proporciona el proveedor del SO o un controlador de dispositivo que proporciona 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 que un controlador reclame algunas interfaces y otras queden 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 permanecerá disponible para WebUSB para que la use una herramienta de configuración. Esta herramienta se puede entregar en el sitio web del fabricante, lo que permite al usuario cambiar aspectos del comportamiento del dispositivo, como las teclas de macro y los efectos de iluminación, sin instalar ningún software específico de la plataforma. El descriptor de configuración de ese dispositivo 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 | Es la 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 con alimentación propia 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 | Configuración alternativa 0 (predeterminada) |
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 HID | ||
0x09 |
bLength | Tamaño de este descriptor |
0x21 |
bDescriptorType | Descriptor 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 | Es la longitud total del descriptor de 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 | Configuración alternativa 0 (predeterminada) |
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 | Masiva |
0x0040 |
wMaxPacketSize | Paquetes de 64 bytes |
0x00 |
bInterval | N/A para extremos masivos |
Descriptor de extremos | ||
0x07 |
bLength | Tamaño de este descriptor |
0x05 |
bDescriptorType | Descriptor de extremo |
0b00000011 |
bEndpointAddress | Extremo 3 (SALIDA) |
0b00000010 |
bmAttributes | Masiva |
0x0040 |
wMaxPacketSize | Paquetes de 64 bytes |
0x00 |
bInterval | N/A para los extremos masivos |
El descriptor de configuración consta de varios descriptores concatenados
juntos. Cada uno comienza con los campos bLength
y bDescriptorType
para que se puedan identificar. La primera interfaz es una interfaz HID con un descriptor HID asociado y un solo extremo que se usa para entregar 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 habilita la funcionalidad adicional marcando el dispositivo con descriptores específicos que indican 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 de dispositivos binarios (BOS) es un concepto que se introdujo en USB 3.0, pero también se retroportó a dispositivos USB 2.0 como parte de la versión 2.1. La declaración de compatibilidad con WebUSB comienza con la inclusión del siguiente descriptor de capacidades de la plataforma en el descriptor de BOS:
Valor | Campo | Descripción |
---|---|---|
Descriptor de Object Store del dispositivo binario | ||
0x05 |
bLength | Tamaño de este descriptor |
0x0F |
bDescriptorType | Descriptor de almacén de objetos de dispositivos binarios |
0x001D |
wTotalLength | Es la longitud total de esta serie de descriptores. |
0x01 |
bNumDeviceCaps | Cantidad de descriptores de capacidad del dispositivo en el BOS |
Descriptor de funciones de la plataforma WebUSB | ||
0x18 |
bLength | Tamaño de este descriptor |
0x10 |
bDescriptorType | Descriptor de capacidades del dispositivo |
0x05 |
bDevCapabilityType | Descriptor de funciones 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 capacidades de la plataforma WebUSB en formato de orden de bytes menor |
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 la capability de la plataforma lo identifica como un descriptor de capability 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 enviar solicitudes adicionales al dispositivo. La única solicitud especificada actualmente es GET_URL
, que muestra un descriptor de URL. Son similares a los descriptores de cadenas, pero están diseñados para codificar URLs en la menor cantidad de bytes posible. Un descriptor de URL para "https://google.com"
se vería 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 emitiendo 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 para que el host descubra el valor del campo wTotalLength
sin comprometerse a una transferencia grande y, luego, cuando se conoce la longitud completa del descriptor.
Si el descriptor de capacidades de la plataforma WebUSB tiene el campo iLandingPage
configurado en un valor distinto de cero, el navegador realiza una solicitud GET_URL
específica de WebUSB mediante la emisión de una transferencia de control con bRequest
establecido en el valor bVendorCode
del descriptor de capacidades 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 URL |
Una vez más, esta solicitud se puede emitir dos veces para sondear primero 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 deben tener en cuenta los requisitos que se imponen a las aplicaciones, como los requisitos de navegadores web, para acceder a los dispositivos.
macOS
No es necesario hacer nada especial en macOS. Un sitio web que usa WebUSB puede conectarse al dispositivo y reclamar cualquier interfaz que no haya reclamado un controlador de kernel u 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 el usuario y el grupo que pueden 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 con el 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 del producto y del proveedor hexadecimales de tu dispositivo (p.ej., ATTR{idVendor}=="18d1", ATTR{idProduct}=="4e11"
coincidiría con un teléfono Nexus One). Para que se reconozcan correctamente, deben escribirse sin el prefijo "0x" habitual y en minúsculas. Para encontrar los IDs de tu dispositivo, ejecuta la herramienta de línea de comandos lsusb
.
Esta regla debe colocarse en un archivo del directorio /etc/udev/rules.d
y se aplica en cuanto se enchufa 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 está 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 en el que se le 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 el sitio web 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 los dispositivos USB y permitirá que el navegador acceda a ellos, siempre y cuando haya al menos una interfaz sin reclamar.
Windows
El modelo de controlador de Windows introduce un requisito adicional. A diferencia de las plataformas anteriores, la capacidad de abrir un dispositivo USB desde una aplicación de usuario no es la predeterminada, incluso si no hay un controlador cargado. En su lugar, 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 personalizado de información del controlador (INF) 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 controlador le indica a Windows qué hacer cuando detecta un dispositivo por primera vez. Como el sistema del usuario ya incluye el controlador WinUSB, lo único que se necesita es que el archivo INF asocie tu ID de proveedor y producto con esta nueva regla de instalación. El siguiente archivo es un ejemplo básico. Guárdalo en un archivo con la extensión .inf
, cambia las secciones marcadas con una “X” y, luego, haz clic con el botón derecho en él 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"
La sección [Dev_AddReg]
configura el conjunto de DeviceInterfaceGUIDs para el dispositivo. Cada interfaz de dispositivo debe tener un GUID para que una aplicación lo encuentre y se conecte a é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 sencilla en la que se reemplaza el controlador cargado para una interfaz USB por el controlador WinUSB.
Descriptores de compatibilidad del SO de Microsoft
El enfoque del archivo INF anterior es engorroso porque requiere configurar la máquina de cada usuario con anticipación. Windows 8.1 y versiones posteriores ofrecen una alternativa a través del 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 es fácil agregar los descriptores de compatibilidad del SO de Microsoft. Primero, extiende el descriptor de BOS con este descriptor de capacidades de plataforma adicional. Asegúrate de actualizar wTotalLength
y bNumDeviceCaps
para tenerlos en cuenta.
Valor | Campo | Descripción |
---|---|---|
Descriptor de capacidades de la plataforma del SO 2.0 de Microsoft | ||
0x1C |
bLength | Tamaño de este descriptor |
0x10 |
bDescriptorType | Descriptor de capacidades del dispositivo |
0x05 |
bDevCapabilityType | Descriptor de funciones 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 del SO 2.0 de Microsoft en formato little-endian |
0x06030000 |
dwWindowsVersion | Versión mínima compatible de Windows (Windows 8.1) |
0x00B2 |
wMSOSDescriptorSetTotalLength | Es la 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
que usarán las transferencias de control relacionadas con estos descriptores. En este ejemplo, elegí 0x02
. 0x07
, que se coloca en wIndex
, es el comando para recuperar el conjunto de descriptores del SO 2.0 de Microsoft del dispositivo.
bmRequestType | bRequest | wValue | wIndex | wLength | Datos (respuesta) |
---|---|---|---|---|---|
0b11000000 |
0x02 |
0x0000 |
0x0007 |
* | Conjunto de descriptores de SO de Microsoft 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 propiedades que siguen. En el siguiente ejemplo, se configura la interfaz 1 de un dispositivo compuesto. El descriptor le brinda 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 de WinUSB. El descriptor de propiedades del registro funciona de manera similar a la sección [Dev_AddReg]
del ejemplo de INF anterior, ya que establece una propiedad del registro para asignarle a esta función un GUID de interfaz de dispositivo.
Valor | Campo | Descripción |
---|---|---|
Encabezado del conjunto de descriptores del SO 2.0 de Microsoft | ||
0x000A |
wLength | Tamaño de este descriptor |
0x0000 |
wDescriptorType | Descriptor de encabezado del conjunto de descriptores |
0x06030000 |
dwWindowsVersion | Versión mínima compatible de Windows (Windows 8.1) |
0x00B2 |
wTotalLength | Es la longitud total del conjunto de descriptores. |
Encabezado del subconjunto de configuración del SO 2.0 de Microsoft | ||
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 | Se debe establecer en 0 |
0x00A8 |
wTotalLength | Es la longitud total del subconjunto, incluido este encabezado. |
Encabezado del subconjunto de funciones del SO 2.0 de Microsoft | ||
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 | Se debe establecer en 0 |
0x00A0 |
wSubsetLength | Es la longitud total del subconjunto, incluido este encabezado. |
Descriptor de ID compatible con el SO 2.0 de Microsoft | ||
0x0014 |
wLength | Tamaño de este descriptor |
0x0003 |
wDescriptorType | Descriptor de ID compatible |
"WINUSB\0\0" |
CompatibileID | Cadena ASCII rellenada a 8 bytes |
"\0\0\0\0\0\0\0\0" |
SubCompatibleID | Cadena ASCII rellenada a 8 bytes |
Descriptor de propiedades del registro del SO 2.0 de Microsoft | ||
0x0084 |
wLength | Tamaño de este descriptor |
0x0004 |
wDescriptorType | Descriptor de propiedades 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á esta información al dispositivo una vez. 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 del registro de dispositivos USB que describe las entradas del registro creadas cuando se enumera un dispositivo. Cuando realices pruebas, borra las entradas creadas para un dispositivo para forzar a Windows a intentar leer los descriptores nuevamente.
Para 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 SO de Microsoft: