Créer un appareil pour WebUSB

Créez un appareil pour tirer pleinement parti de l'API WebUSB.

Reilly Grant
Reilly Grant

Cet article explique comment créer un appareil pour tirer pleinement parti de l'API WebUSB. Pour une brève présentation de l'API, consultez Accéder aux appareils USB sur le Web.

Contexte

Le bus série universel (USB) est devenu l'interface physique la plus courante pour connecter des périphériques aux ordinateurs de bureau et aux appareils informatiques mobiles. En plus de définir les caractéristiques électriques du bus et un modèle général de communication avec un appareil, les spécifications USB incluent un ensemble de spécifications de classe d'appareil. Il s'agit de modèles généraux pour des classes d'appareils particulières, telles que le stockage, l'audio, la vidéo, la mise en réseau, etc., que les fabricants d'appareils peuvent mettre en œuvre. L'avantage de ces spécifications de classe d'appareil est qu'un fournisseur de système d'exploitation peut implémenter un seul pilote en fonction de la spécification de classe (un "pilote de classe"), et tout appareil mettant en œuvre cette classe sera compatible. Il s'agissait d'une amélioration considérable par rapport à chaque fabricant ayant besoin d'écrire ses propres pilotes d'appareil.

Toutefois, certains appareils n'entrent dans aucune de ces catégories standardisées. À la place, un fabricant peut choisir d'étiqueter son appareil comme mettant en œuvre la classe spécifique au fournisseur. Dans ce cas, le système d'exploitation choisit le pilote de l'appareil à charger en fonction des informations fournies dans le package de pilotes du fournisseur. Il s'agit généralement d'un ensemble d'ID de fournisseur et de produit connus pour implémenter un protocole spécifique à un fournisseur.

Une autre fonctionnalité de l'USB est que les appareils peuvent fournir plusieurs interfaces à l'hôte auquel ils sont connectés. Chaque interface peut implémenter une classe standardisée ou être spécifique au fournisseur. Lorsqu'un système d'exploitation choisit les pilotes appropriés pour gérer l'appareil, chaque interface peut être revendiquée par un pilote différent. Par exemple, une webcam USB fournit généralement deux interfaces, l'une implémentant la classe vidéo USB (pour la caméra) et l'autre implémentant la classe audio USB (pour le micro). Le système d'exploitation ne charge pas un seul "pilote de webcam", mais des pilotes de classe vidéo et audio indépendants, qui sont responsables des fonctions distinctes de l'appareil. Cette composition de classes d'interface offre une plus grande flexibilité.

Principes de base des API

De nombreuses classes USB standards disposent d'API Web correspondantes. Par exemple, une page peut enregistrer une vidéo à partir d'un appareil de classe vidéo à l'aide de getUserMedia() ou recevoir des événements d'entrée d'un appareil de classe Interface humaine (HID) en écoutant KeyboardEvents ou PointerEvents, ou à l'aide de la Gamepad ou de l'API WebHID. De même que tous les appareils n'implémentent pas une définition de classe standardisée, ils n'implémentent pas tous des fonctionnalités correspondant aux API existantes de la plate-forme Web. Dans ce cas, l'API WebUSB peut combler cette lacune en permettant aux sites de revendiquer une interface spécifique au fournisseur et de la prendre en charge directement depuis leur page.

Les exigences spécifiques pour qu'un appareil soit accessible via WebUSB varient légèrement d'une plate-forme à l'autre en raison des différences dans la façon dont les systèmes d'exploitation gèrent les appareils USB. Toutefois, l'exigence de base est qu'un appareil ne doit pas déjà disposer d'un pilote revendiquant l'interface que la page souhaite contrôler. Il peut s'agir d'un pilote de classe générique fourni par le fournisseur de l'OS ou d'un pilote d'appareil fourni par le fournisseur. Étant donné que les périphériques USB peuvent fournir plusieurs interfaces, chacune pouvant avoir son propre pilote, il est possible de créer un appareil pour lequel certaines interfaces sont revendiquées par un pilote et d'autres restent accessibles au navigateur.

Par exemple, un clavier USB haut de gamme peut fournir une interface de classe HID qui sera revendiquée par le sous-système d'entrée du système d'exploitation et une interface spécifique au fournisseur qui reste à la disposition de WebUSB pour être utilisée par un outil de configuration. Cet outil peut être diffusé sur le site Web du fabricant, ce qui permet à l'utilisateur de modifier certains aspects du comportement de l'appareil, tels que les clés de macro et les effets d'éclairage, sans installer de logiciel spécifique à la plate-forme. Le descripteur de configuration d'un tel appareil ressemblerait à ceci:

Valeur Champ Description
Descripteur de configuration
0x09 bLength Taille de ce descripteur
0x02 bDescriptorType Descripteur de configuration
0x0039 wTotalLength Longueur totale de cette série de descripteurs
0x02 bNumInterfaces Nombre d'interfaces
0x01 bConfigurationValue Configuration 1
0x00 iConfiguration Nom de la configuration (aucun)
0b1010000 bmAttributes Appareil autonome avec réveil à distance
0x32 bMaxPower La puissance maximale est exprimée par incréments de 2 mA
Descripteur de l'interface
0x09 bLength Taille de ce descripteur
0x04 bDescriptorType Descripteur de l'interface
0x00 bInterfaceNumber Interface 0
0x00 bAlternateSetting Paramètre alternatif 0 (par défaut)
0x01 bNumEndpoints 1 point de terminaison
0x03 bInterfaceClass Classe d'interface HID
0x01 bInterfaceSubClass Sous-classe d'interface de démarrage
0x01 bInterfaceProtocol Clavier
0x00 iInterface Nom de l'interface (aucun)
Descripteur HID
0x09 bLength Taille de ce descripteur
0x21 bDescriptorType Descripteur HID
0x0101 bcdHID HID version 1.1
0x00 bCountryCode Pays cible du matériel
0x01 bNumDescriptors Nombre de descripteurs de classe HID à suivre
0x22 bDescriptorType Type de descripteur du rapport
0x003F wDescriptorLength Longueur totale du descripteur du rapport
Descripteur du point de terminaison
0x07 bLength Taille de ce descripteur
0x05 bDescriptorType Descripteur du point de terminaison
0b10000001 bEndpointAddress Point de terminaison 1 (IN)
0b00000011 bmAttributes Interrompre
0x0008 wMaxPacketSize Paquets de 8 octets
0x0A bInterval Intervalle de 10 ms
Descripteur de l'interface
0x09 bLength Taille de ce descripteur
0x04 bDescriptorType Descripteur de l'interface
0x01 bInterfaceNumber Interface 1
0x00 bAlternateSetting Paramètre alternatif 0 (par défaut)
0x02 bNumEndpoints 2 points de terminaison
0xFF bInterfaceClass Classe d'interface spécifique au fournisseur
0x00 bInterfaceSubClass
0x00 bInterfaceProtocol
0x00 iInterface Nom de l'interface (aucun)
Descripteur du point de terminaison
0x07 bLength Taille de ce descripteur
0x05 bDescriptorType Descripteur du point de terminaison
0b10000010 bEndpointAddress Point de terminaison 1 (IN)
0b00000010 bmAttributes En masse
0x0040 wMaxPacketSize Paquets de 64 octets
0x00 bInterval N/A pour les points de terminaison groupés
Descripteur du point de terminaison
0x07 bLength Taille de ce descripteur
0x05 bDescriptorType Descripteur du point de terminaison
0b00000011 bEndpointAddress Point de terminaison 3 (OUT)
0b00000010 bmAttributes En masse
0x0040 wMaxPacketSize Paquets de 64 octets
0x00 bInterval N/A pour les points de terminaison groupés

Le descripteur de configuration se compose de plusieurs descripteurs concaténés. Chacun d'entre eux commence par les champs bLength et bDescriptorType afin de pouvoir les identifier. La première interface est une interface HID associée à un descripteur HID et à un point de terminaison unique servant à transmettre les événements d'entrée au système d'exploitation. La deuxième interface est une interface spécifique au fournisseur avec deux points de terminaison qui peuvent être utilisés pour envoyer des commandes à l'appareil et recevoir des réponses en retour.

Descripteurs WebUSB

Bien que WebUSB puisse fonctionner avec de nombreux appareils sans modification du micrologiciel, des fonctionnalités supplémentaires sont activées en marquant l'appareil avec des descripteurs spécifiques indiquant la compatibilité avec WebUSB. Par exemple, vous pouvez spécifier une URL de page de destination vers laquelle le navigateur peut rediriger l'utilisateur lorsque votre appareil est branché.

Capture d'écran de la notification WebUSB dans Chrome
Notification WebUSB.

Le Binary Device Object Store (BOS) est un concept introduit dans USB 3.0, mais a également été rétroporté vers les appareils USB 2.0 dans la version 2.1. La déclaration de la compatibilité avec WebUSB commence par l'inclusion du descripteur de capacité de la plate-forme suivant dans le descripteur BOS:

Valeur Champ Description
Descripteur de magasin d'objets de l'appareil binaire
0x05 bLength Taille de ce descripteur
0x0F bDescriptorType Descripteur de magasin d'objets de l'appareil binaire
0x001D wTotalLength Longueur totale de cette série de descripteurs
0x01 bNumDeviceCaps Nombre de descripteurs de fonctionnalités de l'appareil dans le BOS
Descripteur de capacité de la plate-forme WebUSB
0x18 bLength Taille de ce descripteur
0x10 bDescriptorType Descripteur de fonctionnalité de l'appareil
0x05 bDevCapabilityType Descripteur des fonctionnalités de la plate-forme
0x00 bReserved
{0x38, 0xB6, 0x08, 0x34, 0xA9, 0x09, 0xA0, 0x47, 0x8B, 0xFD, 0xA0, 0x76, 0x88, 0x15, 0xB6, 0x65} PlatformCapablityUUID GUID du descripteur de capacité de la plate-forme WebUSB au format Little Endian
0x0100 bcdVersion Descripteur WebUSB version 1.0
0x01 bVendorCode Valeur bRequest pour WebUSB
0x01 iLandingPage URL de la page de destination

L'UUID de capacité de la plate-forme l'identifie comme un descripteur de capacité de la plate-forme WebUSB, qui fournit des informations de base sur l'appareil. Pour que le navigateur récupère plus d'informations sur l'appareil, il utilise la valeur bVendorCode afin d'envoyer des requêtes supplémentaires à l'appareil. La seule requête actuellement spécifiée est GET_URL, qui renvoie un descripteur d'URL. Ils sont semblables aux descripteurs de chaîne, mais sont conçus pour encoder les URL avec le moins d'octets possible. Un descripteur d'URL pour "https://google.com" ressemblerait à ceci:

Valeur Champ Description
Descripteur d'URL
0x0D bLength Taille de ce descripteur
0x03 bDescriptorType Descripteur d'URL
0x01 bScheme https://
"google.com" URL Contenu de l'URL encodé au format UTF-8

Lorsque votre appareil est branché pour la première fois dans le navigateur, il lit le descripteur BOS à l'aide de ce transfert de commande GET_DESCRIPTOR standard:

bmRequestType bRequest wValue wIndex wLength Données (réponse)
0b10000000 0x06 0x0F00 0x0000 * Le descripteur BOS

Cette requête est généralement effectuée deux fois : la première fois avec un wLength suffisamment grand pour que l'hôte puisse découvrir la valeur du champ wTotalLength sans effectuer un transfert important, puis à nouveau lorsque la longueur complète du descripteur est connue.

Si le champ iLandingPage du descripteur de fonctionnalité de la plate-forme WebUSB est défini sur une valeur non nulle, le navigateur exécute ensuite une requête GET_URL spécifique à WebUSB en émettant un transfert de commande avec bRequest défini sur la valeur bVendorCode à partir du descripteur de capacité de la plate-forme et wValue défini sur la valeur iLandingPage. Le code de demande pour GET_URL (0x02) va dans wIndex:

bmRequestType bRequest wValue wIndex wLength Données (réponse)
0b11000000 0x01 0x0001 0x0002 * Descripteur d'URL

Là encore, cette requête peut être envoyée deux fois afin de vérifier d'abord la longueur du descripteur en cours de lecture.

Considérations propres à la plate-forme

Bien que l'API WebUSB tente de fournir une interface cohérente pour l'accès aux appareils USB, les développeurs doivent toujours être conscients des exigences imposées aux applications, telles que les exigences des navigateurs Web pour accéder aux appareils.

macOS

Rien de spécial n'est nécessaire pour macOS. Un site Web utilisant WebUSB peut se connecter à l'appareil et revendiquer toutes les interfaces qui ne sont pas revendiquées par un pilote de noyau ou une autre application.

Linux

Linux est semblable à macOS, mais la plupart des distributions ne configurent pas par défaut les comptes utilisateur autorisés à ouvrir des appareils USB. Un daemon système appelé udev est chargé d'attribuer l'utilisateur et le groupe autorisés à accéder à un appareil. Une telle règle attribuera la propriété d'un appareil correspondant au fournisseur et aux ID produit donnés au groupe plugdev, qui est un groupe commun pour les utilisateurs ayant accès aux périphériques:

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

Remplacez XXXX par le fournisseur hexadécimal et l'ID produit de votre appareil. Par exemple, ATTR{idVendor}=="18d1", ATTR{idProduct}=="4e11" correspond à un téléphone Nexus One. Ceux-ci doivent être écrits sans le préfixe habituel "0x" et tout en minuscules pour être reconnus correctement. Pour trouver les ID de votre appareil, exécutez l'outil de ligne de commande lsusb.

Cette règle doit être placée dans un fichier du répertoire /etc/udev/rules.d et prend effet dès que l'appareil est branché. Il n'est pas nécessaire de redémarrer udev.

Android

La plate-forme Android est basée sur Linux, mais ne nécessite aucune modification de la configuration système. Par défaut, tout appareil qui n'a pas de pilote intégré au système d'exploitation est accessible au navigateur. Les développeurs doivent toutefois savoir que les utilisateurs devront faire l'objet d'une étape supplémentaire lorsqu'ils se connecteront à l'appareil. Lorsqu'un utilisateur sélectionne un appareil en réponse à un appel à requestDevice(), Android affiche une invite lui demandant s'il doit autoriser Chrome à y accéder. Cette invite réapparaît également si un utilisateur revient sur un site Web déjà autorisé à se connecter à un appareil et qu'il appelle open().

En outre, plus d'appareils seront accessibles sur Android que sur Linux pour ordinateur, car moins de pilotes sont inclus par défaut. Une omission notable, par exemple, est la classe USB CDC-ACM couramment implémentée par les adaptateurs USB vers série, car il n'existe aucune API dans le SDK Android permettant de communiquer avec un appareil série.

ChromeOS

ChromeOS est également basé sur Linux et ne nécessite aucune modification de la configuration système. Le service "permission_broker" contrôle l'accès aux appareils USB et autorise le navigateur à y accéder tant qu'il existe au moins une interface non revendiquée.

Windows

Le modèle de pilote Windows introduit une exigence supplémentaire. Contrairement aux plates-formes ci-dessus, la possibilité d'ouvrir un périphérique USB à partir d'une application utilisateur n'est pas l'option par défaut, même si aucun pilote n'est chargé. À la place, un pilote spécial, WinUSB, doit être chargé afin de fournir l'interface que les applications utilisent pour accéder à l'appareil. Pour ce faire, vous pouvez utiliser un fichier d'informations sur le pilote personnalisé (INF) installé sur le système, ou modifier le micrologiciel de l'appareil pour fournir les descripteurs de compatibilité de l'OS Microsoft lors de l'énumération.

Fichier d'informations sur le conducteur (INF)

Un fichier d'informations sur le pilote indique à Windows ce qu'il doit faire lorsqu'il rencontre un appareil pour la première fois. Étant donné que le système de l'utilisateur inclut déjà le pilote WinUSB, tout ce qui est nécessaire est que le fichier INF associe votre fournisseur et votre ID de produit à cette nouvelle règle d'installation. Le fichier ci-dessous est un exemple de base. Enregistrez-le dans un fichier avec l'extension .inf, modifiez les sections marquées de "X", puis effectuez un clic droit dessus et choisissez "Installer" dans le menu contextuel.

[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 section [Dev_AddReg] configure l'ensemble des DeviceInterfaceGUID pour l'appareil. Chaque interface d'appareil doit avoir un GUID pour qu'une application puisse la rechercher et s'y connecter via l'API Windows. Utilisez le cmdlet PowerShell New-Guid ou un outil en ligne pour générer un GUID aléatoire.

À des fins de développement, l'outil Zadig fournit une interface simple permettant de remplacer le pilote chargé pour une interface USB par le pilote WinUSB.

Descripteurs de compatibilité de l'OS Microsoft

L'approche du fichier INF ci-dessus est fastidieuse, car elle nécessite de configurer la machine de chaque utilisateur à l'avance. Windows 8.1 et versions ultérieures offrent une alternative via l'utilisation de descripteurs USB personnalisés. Ces descripteurs fournissent des informations au système d'exploitation Windows lors de la première connexion de l'appareil et qui devraient normalement être incluses dans le fichier INF.

Une fois les descripteurs WebUSB configurés, il est facile d'ajouter des descripteurs de compatibilité avec l'OS Microsoft. Commencez par étendre le descripteur BOS avec ce descripteur de fonctionnalité de plate-forme supplémentaire. Veillez à mettre à jour wTotalLength et bNumDeviceCaps pour en tenir compte.

Valeur Champ Description
Descripteur des fonctionnalités de la plate-forme Microsoft OS 2.0
0x1C bLength Taille de ce descripteur
0x10 bDescriptorType Descripteur de fonctionnalité de l'appareil
0x05 bDevCapabilityType Descripteur des fonctionnalités de la plate-forme
0x00 bReserved
{0xDF, 0x60, 0xDD, 0xD8, 0x89, 0x45, 0xC7, 0x4C, 0x9C, 0xD2, 0x65, 0x9D, 0x9E, 0x64, 0x8A, 0x9F} PlatformCapablityUUID GUID du descripteur de compatibilité avec la plate-forme Microsoft OS 2.0 au format few-endian
0x06030000 dwWindowsVersion Version Windows minimale compatible (Windows 8.1)
0x00B2 wMSOSDescriptorSetTotalLength Longueur totale de l'ensemble de descripteurs
0x02 bMS_VendorCode Valeur bRequest pour récupérer d'autres descripteurs Microsoft
0x00 bAltEnumCode L'appareil n'est pas compatible avec d'autres énumérations

Comme pour les descripteurs WebUSB, vous devez choisir une valeur bRequest qui sera utilisée par les transferts de contrôle liés à ces descripteurs. Dans cet exemple, j'ai choisi 0x02. 0x07, placé dans wIndex, est la commande permettant de récupérer l'ensemble de descripteurs Microsoft OS 2.0 à partir de l'appareil.

bmRequestType bRequest wValue wIndex wLength Données (réponse)
0b11000000 0x02 0x0000 0x0007 * Ensemble de descripteurs MS OS 2.0

Un périphérique USB peut avoir plusieurs fonctions. La première partie de l'ensemble de descripteurs décrit donc la fonction à laquelle les propriétés suivantes sont associées. L'exemple ci-dessous configure l'interface 1 d'un appareil composite. Le descripteur fournit au système d'exploitation deux informations importantes sur cette interface. Le descripteur d'ID compatible indique à Windows que cet appareil est compatible avec le pilote WinUSB. Le descripteur de propriété de registre fonctionne de la même manière que la section [Dev_AddReg] de l'exemple INF ci-dessus, en définissant une propriété de registre pour attribuer à cette fonction un GUID d'interface d'appareil.

Valeur Champ Description
En-tête de l'ensemble de descripteurs Microsoft OS 2.0
0x000A wLength Taille de ce descripteur
0x0000 wDescriptorType Descripteur d'en-tête de l'ensemble de descripteurs
0x06030000 dwWindowsVersion Version Windows minimale compatible (Windows 8.1)
0x00B2 wTotalLength Longueur totale de l'ensemble de descripteurs
En-tête du sous-ensemble de configuration de Microsoft OS 2.0
0x0008 wLength Taille de ce descripteur
0x0001 wDescriptorType Description de l'en-tête du sous-ensemble de configuration
0x00 bConfigurationValue S'applique à la configuration 1 (indexée à partir de 0 alors que les configurations sont normalement indexées à partir de 1)
0x00 bReserved Doit être défini sur 0
0x00A8 wTotalLength Longueur totale du sous-ensemble, y compris cet en-tête
En-tête du sous-ensemble de fonctions Microsoft OS 2.0
0x0008 wLength Taille de ce descripteur
0x0002 wDescriptorType Descripteur d'en-tête de sous-ensemble de fonctions
0x01 bFirstInterface Première interface de la fonction
0x00 bReserved Doit être défini sur 0
0x00A0 wSubsetLength Longueur totale du sous-ensemble, y compris cet en-tête
Descripteur d'ID compatible avec Microsoft OS 2.0
0x0014 wLength Taille de ce descripteur
0x0003 wDescriptorType Descripteur d'ID compatible
"WINUSB\0\0" CompatibileID Chaîne ASCII complétée jusqu'à 8 octets
"\0\0\0\0\0\0\0\0" SubCompatibleID Chaîne ASCII complétée jusqu'à 8 octets
Descripteur de propriété de registre Microsoft OS 2.0
0x0084 wLength Taille de ce descripteur
0x0004 wDescriptorType Descripteur de propriété de registre
0x0007 wPropertyDataType REG_MULTI_SZ
0x002A wPropertyNameLength Longueur du nom de la propriété
"DeviceInterfaceGUIDs\0" PropertyName Nom de propriété avec un délimiteur nul encodé au format UTF-16LE
0x0050 wPropertyDataLength Longueur de la valeur de la propriété
"{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}\0\0" PropertyData GUID plus deux terminateurs nuls encodés en UTF-16LE

Windows n'interroge l'appareil qu'une seule fois pour obtenir ces informations. Si l'appareil ne répond pas avec des descripteurs valides, il n'en demandera plus la prochaine fois qu'il sera connecté. Microsoft a fourni une liste d'entrées de registre d'appareils USB décrivant les entrées de registre créées lors de l'énumération d'un appareil. Lors des tests, supprimez les entrées créées pour un appareil afin de forcer Windows à réessayer de lire les descripteurs.

Pour en savoir plus, consultez l'article de blog de Microsoft sur l'utilisation de ces descripteurs.

Exemples

Vous trouverez des exemples de code mettant en œuvre des appareils compatibles WebUSB qui incluent à la fois des descripteurs WebUSB et des descripteurs d'OS Microsoft dans les projets suivants: