Creazione di un dispositivo per WebUSB

Crea un dispositivo per sfruttare al massimo l'API WebUSB.

Reilly Grant
Reilly Grant

Questo articolo spiega come creare un dispositivo per sfruttare al meglio l'API WebUSB. Per una breve introduzione all'API, consulta Accedere a dispositivi USB sul web.

Contesto

L'Universal Serial Bus (USB) è diventato l'interfaccia fisica più comune per il collegamento delle periferiche a dispositivi informatici desktop e mobili. Oltre a definire le caratteristiche elettriche del bus e un modello generale per la comunicazione con un dispositivo, le specifiche USB includono un insieme di specifiche sulla classe dei dispositivi. Si tratta di modelli generali per classi specifiche di dispositivi che i produttori possono implementare, ad esempio archiviazione, audio, video, networking e così via. Il vantaggio di queste specifiche della classe dei dispositivi è che un fornitore di sistema operativo può implementare un singolo driver in base alla specifica della classe ("driver di classe") e sarà supportato qualsiasi dispositivo che implementa questa classe. Si tratta di un grande miglioramento rispetto a ogni produttore che deve scrivere i propri driver di dispositivo.

Tuttavia, alcuni dispositivi non rientrano in una di queste classi standardizzate. Un produttore potrebbe invece scegliere di etichettare il proprio dispositivo per implementare la classe specifica del fornitore. In questo caso, il sistema operativo sceglie il driver del dispositivo da caricare in base alle informazioni fornite nel pacchetto driver del fornitore, in genere un insieme di ID fornitore e prodotto che implementano un protocollo specifico del fornitore.

Un'altra caratteristica delle porte USB è che i dispositivi possono fornire più interfacce all'host a cui sono collegati. Ogni interfaccia può implementare una classe standardizzata o essere specifica del fornitore. Quando un sistema operativo sceglie i driver giusti per gestire il dispositivo, ogni interfaccia può essere richiesta da un driver diverso. Ad esempio, una webcam USB in genere fornisce due interfacce: una che implementa la classe video USB (per la videocamera) e una che implementa la classe audio USB (per il microfono). Il sistema operativo non carica un singolo "driver della webcam", ma carica driver di classe audio e video indipendenti che si assumono la responsabilità delle funzioni separate del dispositivo. Questa composizione di classi di interfaccia offre una maggiore flessibilità.

Nozioni di base sulle API

Molte delle classi USB standard hanno API web corrispondenti. Ad esempio, una pagina può acquisire video da un dispositivo di classe video utilizzando getUserMedia() o ricevere eventi di input da un dispositivo di classe Human Interface (HID) ascoltando KeyboardEvents o PointerEvents oppure usando l'API Gamepad o WebHID. Proprio come non tutti i dispositivi implementano una definizione standardizzata di classe, non tutti i dispositivi implementano funzionalità che corrispondono alle API delle piattaforme web esistenti. In questo caso, l'API WebUSB può colmare questa lacuna offrendo ai siti un modo per richiedere un'interfaccia specifica del fornitore e implementare il relativo supporto direttamente dalla loro pagina.

I requisiti specifici affinché un dispositivo sia accessibile tramite WebUSB variano leggermente da piattaforma a piattaforma a causa delle differenze nel modo in cui i sistemi operativi gestiscono i dispositivi USB, ma il requisito di base è che un dispositivo non debba avere già un driver che indichi l'interfaccia che la pagina vuole controllare. Potrebbe essere un driver di classe generico fornito dal fornitore del sistema operativo o un driver di dispositivo fornito dal fornitore. Poiché i dispositivi USB possono fornire più interfacce, ognuna delle quali può avere un proprio driver, è possibile creare un dispositivo per il quale alcune interfacce sono rivendicate da un driver e altre sono lasciate accessibili al browser.

Ad esempio, una tastiera USB di fascia alta potrebbe fornire un'interfaccia di classe HID che verrà rivendicata dal sottosistema di input del sistema operativo e un'interfaccia specifica del fornitore che rimane disponibile per WebUSB per l'utilizzo da parte di uno strumento di configurazione. Questo strumento può essere pubblicato sul sito web del produttore per consentire all'utente di modificare gli aspetti del comportamento del dispositivo, ad esempio chiavi macro ed effetti di illuminazione, senza installare alcun software specifico per la piattaforma. Il descrittore di configurazione di un dispositivo potrebbe avere il seguente aspetto:

Valore Campo Descrizione
Descrittore di configurazione
0x09 bLength Dimensioni di questo descrittore
0x02 bDescriptorType Descrittore di configurazione
0x0039 wTotalLength Lunghezza totale di questa serie di descrittori
0x02 bNumInterfaces Numero di interfacce
0x01 bConfigurationValue Configurazione 1
0x00 iConfiguration Nome configurazione (nessuno)
0b1010000 bmAttributes Dispositivo autonomo con riattivazione remota
0x32 bMaxPower La potenza massima è espressa in incrementi di 2 mA
Descrittore interfaccia
0x09 bLength Dimensioni di questo descrittore
0x04 bDescriptorType Descrittore interfaccia
0x00 bInterfaceNumber Interfaccia 0
0x00 bAlternateSetting Impostazione alternativa 0 (predefinita)
0x01 bNumEndpoints 1 endpoint
0x03 bInterfaceClass Classe interfaccia HID
0x01 bInterfaceSubClass Sottoclasse dell'interfaccia di avvio
0x01 bInterfaceProtocol Tastiera
0x00 iInterface Nome interfaccia (nessuno)
Descrittore HID
0x09 bLength Dimensioni di questo descrittore
0x21 bDescriptorType Descrittore HID
0x0101 bcdHID HID versione 1.1
0x00 bCountryCode Paese di destinazione dell'hardware
0x01 bNumDescriptors Numero di descrittori di classe HID da seguire
0x22 bDescriptorType Tipo di descrittore del report
0x003F wDescriptorLength Lunghezza totale del descrittore del report
Descrittore endpoint
0x07 bLength Dimensioni di questo descrittore
0x05 bDescriptorType Descrittore endpoint
0b10000001 bEndpointAddress Endpoint 1 (India)
0b00000011 bmAttributes Interrompi
0x0008 wMaxPacketSize Pacchetti da 8 byte
0x0A bInterval Intervallo di 10 ms
Descrittore interfaccia
0x09 bLength Dimensioni di questo descrittore
0x04 bDescriptorType Descrittore interfaccia
0x01 bInterfaceNumber Interfaccia 1
0x00 bAlternateSetting Impostazione alternativa 0 (predefinita)
0x02 bNumEndpoints 2 endpoint
0xFF bInterfaceClass Classe di interfaccia specifica del fornitore
0x00 bInterfaceSubClass
0x00 bInterfaceProtocol
0x00 iInterface Nome interfaccia (nessuno)
Descrittore endpoint
0x07 bLength Dimensioni di questo descrittore
0x05 bDescriptorType Descrittore endpoint
0b10000010 bEndpointAddress Endpoint 1 (India)
0b00000010 bmAttributes Collettivo
0x0040 wMaxPacketSize Pacchetti da 64 byte
0x00 bInterval N/D per endpoint collettivi
Descrittore endpoint
0x07 bLength Dimensioni di questo descrittore
0x05 bDescriptorType Descrittore endpoint
0b00000011 bEndpointAddress Endpoint 3 (OUT)
0b00000010 bmAttributes Collettivo
0x0040 wMaxPacketSize Pacchetti da 64 byte
0x00 bInterval N/D per endpoint collettivi

Il descrittore di configurazione è costituito da più descrittori concatenati. Ciascuno inizia con i campi bLength e bDescriptorType per poter essere identificati. La prima interfaccia è un'interfaccia HID con un descrittore HID associato e un singolo endpoint utilizzato per inviare eventi di input al sistema operativo. La seconda interfaccia è un'interfaccia specifica del fornitore con due endpoint che possono essere utilizzati per inviare comandi al dispositivo e ricevere risposte.

Descrittori WebUSB

Sebbene WebUSB possa funzionare con molti dispositivi senza modifiche firmware, viene attivata una funzionalità aggiuntiva contrassegnando il dispositivo con descrittori specifici che indicano il supporto di WebUSB. Ad esempio, puoi specificare un URL pagina di destinazione a cui il browser può indirizzare l'utente quando il dispositivo è collegato.

Screenshot della notifica WebUSB in Chrome
Notifica WebUSB.

Il Binary device Object Store (BOS) è un concetto introdotto in USB 3.0 ma nella versione 2.1 è stato eseguito anche il backporting su dispositivi USB 2.0. La dichiarazione del supporto per WebUSB inizia con l'inclusione del seguente descrittore delle funzionalità della piattaforma nel descrittore BOS:

Valore Campo Descrizione
Descrittore dell'archivio oggetti del dispositivo binario
0x05 bLength Dimensioni di questo descrittore
0x0F bDescriptorType Descrittore dell'archivio oggetti del dispositivo binario
0x001D wTotalLength Lunghezza totale di questa serie di descrittori
0x01 bNumDeviceCaps Numero di descrittori di funzionalità del dispositivo nel BOS
Descrittore delle funzionalità della piattaforma WebUSB
0x18 bLength Dimensioni di questo descrittore
0x10 bDescriptorType Descrittore funzionalità dispositivo
0x05 bDevCapabilityType Descrittore funzionalità della piattaforma
0x00 bReserved
{0x38, 0xB6, 0x08, 0x34, 0xA9, 0x09, 0xA0, 0x47, 0x8B, 0xFD, 0xA0, 0x76, 0x88, 0x15, 0xB6, 0x65} PlatformCapablityUUID GUID descrittore delle funzionalità della piattaforma WebUSB in formato small-endian
0x0100 bcdVersion Descrittore WebUSB versione 1.0
0x01 bVendorCode bRichiedi valore per WebUSB
0x01 iLandingPage URL per la pagina di destinazione

L'UUID delle funzionalità della piattaforma lo identifica come un descrittore delle funzionalità della piattaforma WebUSB, che fornisce informazioni di base sul dispositivo. Per consentire al browser di recuperare ulteriori informazioni sul dispositivo, utilizza il valore bVendorCode per inviare ulteriori richieste al dispositivo. L'unica richiesta attualmente specificata è GET_URL, che restituisce un descrittore URL. Sono simili ai descrittori di stringa, ma sono progettati per codificare gli URL nel minor numero di byte. Un descrittore URL per "https://google.com" avrebbe il seguente aspetto:

Valore Campo Descrizione
Descrittore URL
0x0D bLength Dimensioni di questo descrittore
0x03 bDescriptorType Descrittore URL
0x01 bScheme https://
"google.com" URL Contenuti dell'URL con codifica UTF-8

Quando il dispositivo viene collegato per la prima volta al browser, legge il descrittore BOS eseguendo questo trasferimento di controllo GET_DESCRIPTOR standard:

bmRequestType bRequest wValue wIndex wLength Dati (risposta)
0b10000000 0x06 0x0F00 0x0000 * Il descrittore BOS

Di solito, questa richiesta viene effettuata due volte, la prima con un valore wLength sufficientemente grande in modo che l'host individui il valore del campo wTotalLength senza impegnarsi per un trasferimento di grandi dimensioni e poi di nuovo quando è nota la lunghezza completa del descrittore.

Se il descrittore delle funzionalità della piattaforma WebUSB ha il campo iLandingPage impostato su un valore diverso da zero, il browser esegue una richiesta GET_URL specifica per WebUSB inviando un trasferimento di controllo con bRequest impostato sul valore bVendorCode dal descrittore delle funzionalità della piattaforma e wValue impostato sul valore iLandingPage. Il codice della richiesta di GET_URL (0x02) è nel formato wIndex:

bmRequestType bRequest wValue wIndex wLength Dati (risposta)
0b11000000 0x01 0x0001 0x0002 * Il descrittore dell'URL

Anche in questo caso, questa richiesta può essere inviata due volte per verificare prima la lunghezza del descrittore da leggere.

Considerazioni specifiche per le piattaforme

Sebbene l'API WebUSB tenti di fornire un'interfaccia coerente per l'accesso ai dispositivi USB, gli sviluppatori devono comunque essere consapevoli dei requisiti imposti alle applicazioni, ad esempio i requisiti dei browser web, per accedere ai dispositivi.

macOS

Non è necessario nulla di speciale per macOS. Un sito web che utilizza WebUSB può connettersi al dispositivo e rivendicare tutte le interfacce non rivendicate da un driver del kernel o da un'altra applicazione.

Linux

Linux è come macOS, ma per impostazione predefinita la maggior parte delle distribuzioni non configura account utente con l'autorizzazione per aprire i dispositivi USB. Un daemon di sistema chiamato udev è responsabile dell'assegnazione dell'utente e del gruppo autorizzato ad accedere a un dispositivo. Una regola come questa assegnerà la proprietà di un dispositivo corrispondente agli ID fornitore e prodotto indicati al gruppo plugdev, che è un gruppo comune per gli utenti con accesso alle periferiche:

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

Sostituisci XXXX con gli ID prodotto e fornitore esadecimale del dispositivo, ad esempio ATTR{idVendor}=="18d1", ATTR{idProduct}=="4e11" corrisponde a un telefono Nexus One. Per essere riconosciuti correttamente, questi caratteri devono essere scritti senza il consueto prefisso "0x" e contenere solo lettere minuscole. Per trovare gli ID del dispositivo, esegui lo strumento a riga di comando lsusb.

Questa regola dovrebbe essere inserita in un file nella directory /etc/udev/rules.d e avrà effetto non appena il dispositivo viene collegato. Non è necessario riavviare udev.

Android

La piattaforma Android è basata su Linux, ma non richiede alcuna modifica alla configurazione del sistema. Per impostazione predefinita, qualsiasi dispositivo che non dispone di un driver integrato nel sistema operativo è disponibile per il browser. Gli sviluppatori devono sapere, tuttavia che gli utenti dovranno affrontare un passaggio aggiuntivo quando si connetteranno al dispositivo. Quando un utente seleziona un dispositivo in risposta a una chiamata al numero requestDevice(), Android mostra un messaggio che chiede se consentire a Chrome di accedervi. Questo messaggio viene visualizzato di nuovo anche se un utente torna a un sito web già autorizzato a connettersi a un dispositivo e il sito web chiama open().

Inoltre, su Android saranno accessibili più dispositivi che su computer Linux perché per impostazione predefinita sono inclusi meno driver. Un'omissione notevole, ad esempio, è la classe USB CDC-ACM comunemente implementata dagli adattatori da USB a seriale, in quanto nell'SDK Android non esiste un'API per la comunicazione con un dispositivo seriale.

ChromeOS

ChromeOS è basato anche su Linux e non richiede modifiche alla configurazione del sistema. Il servizio autorizzazione_broker controlla l'accesso ai dispositivi USB e consente al browser di accedervi purché sia presente almeno un'interfaccia non rivendicata.

Windows

Il modello di driver di Windows introduce un requisito aggiuntivo. A differenza delle piattaforme riportate sopra, la possibilità di aprire un dispositivo USB da un'applicazione utente non è l'impostazione predefinita, anche se non sono stati caricati driver. È necessario invece caricare un driver speciale, WinUSB, per fornire l'interfaccia utilizzata dalle applicazioni per accedere al dispositivo. Per farlo, puoi utilizzare un file INF (driver information file) personalizzato installato sul sistema o modificare il firmware del dispositivo in modo da fornire i descrittori di compatibilità di Microsoft OS durante l'enumerazione.

File informazioni driver (INF)

Un file con le informazioni del driver indica a Windows cosa fare quando il dispositivo viene rilevato per la prima volta. Poiché il sistema dell'utente include già il driver WinUSB, è sufficiente che il file INF associ il fornitore e l'ID prodotto alla nuova regola di installazione. Il file seguente è un esempio di base. Salvalo in un file con l'estensione .inf, modifica le sezioni contrassegnate con la "X", fai clic con il tasto destro del mouse sul file e scegli "Installa" dal menu contestuale.

[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 sezione [Dev_AddReg] configura l'insieme di DeviceInterfaceGUID per il dispositivo. L'interfaccia di ogni dispositivo deve avere un GUID per consentire a un'applicazione di trovarla e connettersi tramite l'API Windows. Utilizza il cmdlet PowerShell New-Guid o uno strumento online per generare un GUID casuale.

A scopo di sviluppo, lo strumento Zadig fornisce un'interfaccia semplice per sostituire il driver caricato per un'interfaccia USB con il driver WinUSB.

Descrittori di compatibilità del sistema operativo Microsoft

L'approccio basato sui file INF sopra è complicato perché richiede di configurare in anticipo la macchina di ogni utente. Windows 8.1 e versioni successive offrono un'alternativa grazie all'uso di descrittori USB personalizzati. Questi descrittori forniscono al sistema operativo Windows quando il dispositivo viene collegato per la prima volta e che di solito vengono inclusi nel file INF.

Una volta configurati i descrittori WebUSB, è facile aggiungere anche i descrittori di compatibilità del sistema operativo Microsoft. Estendi prima il descrittore BOS con questo descrittore di funzionalità aggiuntivo della piattaforma. Assicurati di aggiornare wTotalLength e bNumDeviceCaps per tenerne conto.

Valore Campo Descrizione
Descrittore delle funzionalità della piattaforma Microsoft OS 2.0
0x1C bLength Dimensioni di questo descrittore
0x10 bDescriptorType Descrittore funzionalità dispositivo
0x05 bDevCapabilityType Descrittore funzionalità della piattaforma
0x00 bReserved
{0xDF, 0x60, 0xDD, 0xD8, 0x89, 0x45, 0xC7, 0x4C, 0x9C, 0xD2, 0x65, 0x9D, 0x9E, 0x64, 0x8A, 0x9F} PlatformCapablityUUID GUID descrittore di compatibilità della piattaforma Microsoft OS 2.0 in formato small-endian
0x06030000 dwWindowsVersion Versione minima di Windows compatibile (Windows 8.1)
0x00B2 wMSOSDescriptorSetTotalLength Lunghezza totale dell'insieme di descrittori
0x02 bMS_VendorCode bRichiedi valore per recuperare altri descrittori Microsoft
0x00 bAltEnumCode Il dispositivo non supporta l'enumerazione alternativa

Come per i descrittori WebUSB, devi scegliere un valore bRequest da utilizzare per i trasferimenti di controllo correlati a questi descrittori. In questo esempio ho scelto 0x02. 0x07, posizionato in wIndex, è il comando per recuperare il set di descrittori del sistema operativo Microsoft 2.0 dal dispositivo.

bmRequestType bRequest wValue wIndex wLength Dati (risposta)
0b11000000 0x02 0x0000 0x0007 * Set descrittori MS OS 2.0

Un dispositivo USB può avere più funzioni, pertanto la prima parte dell'insieme di descrittori descrive a quale funzione sono associate le proprietà che seguono. L'esempio seguente configura l'interfaccia 1 di un dispositivo composito. Il descrittore fornisce al sistema operativo due informazioni importanti su questa interfaccia. Il descrittore ID compatibile indica a Windows che questo dispositivo è compatibile con il driver WinUSB. Il descrittore della proprietà del registro funziona in modo simile alla sezione [Dev_AddReg] dell'esempio INF precedente, impostando una proprietà del registro per assegnare a questa funzione un GUID dell'interfaccia del dispositivo.

Valore Campo Descrizione
Intestazione set di descrittori Microsoft OS 2.0
0x000A wLength Dimensioni di questo descrittore
0x0000 wDescriptorType Descrittore di intestazione set di descrittori
0x06030000 dwWindowsVersion Versione minima di Windows compatibile (Windows 8.1)
0x00B2 wTotalLength Lunghezza totale dell'insieme di descrittori
Intestazione sottoinsieme configurazione Microsoft OS 2.0
0x0008 wLength Dimensioni di questo descrittore
0x0001 wDescriptorType Descrizione intestazione sottoinsieme configurazione
0x00 bConfigurationValue Si applica alla configurazione 1 (indicizzata da 0 nonostante le configurazioni normalmente indicizzate da 1)
0x00 bReserved Deve essere impostato su 0
0x00A8 wTotalLength Lunghezza totale del sottoinsieme inclusa questa intestazione
Intestazione sottoinsieme funzioni Microsoft OS 2.0
0x0008 wLength Dimensioni di questo descrittore
0x0002 wDescriptorType Descrittore intestazione sottoinsieme funzione
0x01 bFirstInterface Prima interfaccia della funzione
0x00 bReserved Deve essere impostato su 0
0x00A0 wSubsetLength Lunghezza totale del sottoinsieme inclusa questa intestazione
Descrittore ID compatibile con Microsoft OS 2.0
0x0014 wLength Dimensioni di questo descrittore
0x0003 wDescriptorType Descrittore ID compatibile
"WINUSB\0\0" CompatibileID Stringa ASCII con riempimento a 8 byte
"\0\0\0\0\0\0\0\0" SubCompatibleID Stringa ASCII con riempimento a 8 byte
Descrittore della proprietà del registro di Microsoft OS 2.0
0x0084 wLength Dimensioni di questo descrittore
0x0004 wDescriptorType Descrittore proprietà del registro
0x0007 wPropertyDataType REG_MULTI_SZ
0x002A wPropertyNameLength Lunghezza del nome della proprietà
"DeviceInterfaceGUIDs\0" PropertyName Nome della proprietà con carattere di terminazione null codificato in UTF-16LE
0x0050 wPropertyDataLength Lunghezza del valore della proprietà
"{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}\0\0" PropertyData GUID più due simboli di terminazione null codificati in UTF-16LE

Windows eseguirà una query sul dispositivo per ottenere queste informazioni solo una volta. Se il dispositivo non risponde con descrittori validi, non lo chiederà di nuovo alla successiva connessione. Microsoft ha fornito un elenco di voci del registro di dispositivi USB in cui sono descritte le voci di registro create durante l'enumerazione di un dispositivo. Durante il test, elimina le voci create per un dispositivo per forzare Windows a provare a leggere di nuovo i descrittori.

Per ulteriori informazioni, consulta il post del blog di Microsoft su come utilizzare questi descrittori.

Esempi

In questi progetti si può trovare un codice di esempio che implementa dispositivi compatibili con WebUSB che includono sia descrittori WebUSB sia descrittori di Microsoft OS: