Connessione a dispositivi HID insoliti

L'API WebHID consente ai siti web di accedere a tastiere ausiliarie alternative e gamepad esotici.

François Beaufort
François Beaufort

È presente una lunga coda di dispositivi di interfaccia umana (HID), come tastiere alternative o gamepad esotici, che sono troppo nuovi, troppo vecchi o troppo insoliti per essere accessibili dai driver di dispositivo dei sistemi. L'API WebHID risolve questo problema fornendo un modo per implementare una logica specifica del dispositivo in JavaScript.

Casi d'uso suggeriti

Un dispositivo HID acquisisce input o fornisce output alle persone. Alcuni esempi di dispositivi sono le tastiere, i dispositivi di puntamento (mopi, touchscreen e così via) e i gamepad. Il protocollo HID consente di accedere a questi dispositivi su computer desktop utilizzando i driver del sistema operativo. La piattaforma web supporta i dispositivi HID basandosi su questi driver.

L'impossibilità di accedere a dispositivi HID insoliti è particolarmente grave quando si utilizza tastiere ausiliarie alternative (ad esempio Elgato Stream Deck, cuffie Jabra, tasti X) e il supporto di gamepad esotici. I gamepad progettati per l'uso su computer spesso utilizzano HID per gli input del gamepad (pulsanti, joystick, trigger) e per gli output (LED, rumble). Sfortunatamente, gli ingressi e gli output del gamepad non sono ben standardizzati e i browser web spesso richiedono una logica personalizzata per dispositivi specifici. Si tratta di un approccio insostenibile e comporta uno scarso supporto per la long tail dei dispositivi meno recenti e insoliti. Inoltre, fa sì che il browser dipenda da peculiarità nel comportamento di dispositivi specifici.

Terminologia

HID è costituito da due concetti fondamentali: report e descrittori dei report. I report sono i dati scambiati tra un dispositivo e un client software. Il descrittore del report descrive il formato e il significato dei dati supportati dal dispositivo.

Un HID (Human Interface Device) è un tipo di dispositivo che riceve input o fornisce output agli esseri umani. Si riferisce anche al protocollo HID, uno standard per la comunicazione bidirezionale tra un host e un dispositivo progettato per semplificare la procedura di installazione. Il protocollo HID è stato originariamente sviluppato per i dispositivi USB, ma è stato implementato su molti altri protocolli, tra cui il Bluetooth.

Applicazioni e dispositivi HID scambiano dati binari tramite tre tipi di rapporti:

Tipo di rapporto Descrizione
Report di input Dati che vengono inviati dal dispositivo all'applicazione (ad esempio la pressione di un pulsante).
Report di output Dati inviati dall'applicazione al dispositivo (ad esempio una richiesta di attivazione della retroilluminazione della tastiera).
Report sulle funzionalità Dati che possono essere inviati in entrambe le direzioni. Il formato è specifico per dispositivo.

Un descrittore di report descrive il formato binario dei report supportato dal dispositivo. La sua struttura è gerarchica e può raggruppare i report come raccolte distinte all'interno della raccolta di primo livello. Il formato del descrittore è definito dalla specifica HID.

Un utilizzo HID è un valore numerico che fa riferimento a un input o un output standardizzato. I valori di utilizzo consentono a un dispositivo di descrivere l'uso previsto del dispositivo e lo scopo di ogni campo nei report. Ad esempio, è definita per il pulsante sinistro di un mouse. Gli utilizzi sono inoltre organizzati in pagine che forniscono un'indicazione della categoria di alto livello del dispositivo o del report.

Utilizzo dell'API WebHID

Rilevamento delle funzionalità

Per verificare se l'API WebHID è supportata, utilizza:

if ("hid" in navigator) {
  // The WebHID API is supported.
}

Apri una connessione HID

L'API WebHID è progettata in modo asincrono per evitare che l'interfaccia utente del sito web si blocchi quando in attesa di input. Questo è importante perché i dati HID possono essere ricevuti in qualsiasi momento, quindi è necessario ascoltarli.

Per aprire una connessione HID, devi prima accedere a un oggetto HIDDevice. Per farlo, puoi chiedere all'utente di selezionare un dispositivo chiamando il numero navigator.hid.requestDevice() oppure sceglierne uno da navigator.hid.getDevices() per visualizzare un elenco dei dispositivi a cui è stato concesso l'accesso in precedenza al sito web.

La funzione navigator.hid.requestDevice() accetta un oggetto obbligatorio che definisce i filtri. Questi vengono utilizzati per trovare corrispondenze con qualsiasi dispositivo collegato a un identificatore del fornitore USB (vendorId), un codice identificativo di prodotto USB (productId), un valore della pagina di utilizzo (usagePage) e un valore di utilizzo (usage). Puoi trovarli nel repository ID USB e nel documento delle tabelle sull'utilizzo di HID.

I più oggetti HIDDevice restituiti da questa funzione rappresentano più interfacce HID sullo stesso dispositivo fisico.

// Filter on devices with the Nintendo Switch Joy-Con USB Vendor/Product IDs.
const filters = [
  {
    vendorId: 0x057e, // Nintendo Co., Ltd
    productId: 0x2006 // Joy-Con Left
  },
  {
    vendorId: 0x057e, // Nintendo Co., Ltd
    productId: 0x2007 // Joy-Con Right
  }
];

// Prompt user to select a Joy-Con device.
const [device] = await navigator.hid.requestDevice({ filters });
// Get all devices the user has previously granted the website access to.
const devices = await navigator.hid.getDevices();
Screenshot della richiesta di un dispositivo HID su un sito web.
Prompt utente per la selezione di un Joy-Con di Nintendo Switch.

Puoi anche usare la chiave facoltativa exclusionFilters in navigator.hid.requestDevice() per escludere dal selettore del browser alcuni dispositivi che notano per il loro malfunzionamento.

// Request access to a device with vendor ID 0xABCD. The device must also have
// a collection with usage page Consumer (0x000C) and usage ID Consumer
// Control (0x0001). The device with product ID 0x1234 is malfunctioning.
const [device] = await navigator.hid.requestDevice({
  filters: [{ vendorId: 0xabcd, usagePage: 0x000c, usage: 0x0001 }],
  exclusionFilters: [{ vendorId: 0xabcd, productId: 0x1234 }],
});

Un oggetto HIDDevice contiene gli ID fornitore e prodotto USB per l'identificazione del dispositivo. Il suo attributo collections viene inizializzato con una descrizione gerarchica dei formati dei report del dispositivo.

for (let collection of device.collections) {
  // An HID collection includes usage, usage page, reports, and subcollections.
  console.log(`Usage: ${collection.usage}`);
  console.log(`Usage page: ${collection.usagePage}`);

  for (let inputReport of collection.inputReports) {
    console.log(`Input report: ${inputReport.reportId}`);
    // Loop through inputReport.items
  }

  for (let outputReport of collection.outputReports) {
    console.log(`Output report: ${outputReport.reportId}`);
    // Loop through outputReport.items
  }

  for (let featureReport of collection.featureReports) {
    console.log(`Feature report: ${featureReport.reportId}`);
    // Loop through featureReport.items
  }

  // Loop through subcollections with collection.children
}

Per impostazione predefinita, i dispositivi HIDDevice vengono restituiti in stato "chiuso" e devono essere aperti chiamando open() prima di poter inviare o ricevere i dati.

// Wait for the HID connection to open before sending/receiving data.
await device.open();

Ricevi report di input

Una volta stabilita la connessione HID, puoi gestire i report degli input in entrata ascoltando gli eventi "inputreport" dal dispositivo. Questi eventi contengono i dati HID come un oggetto DataView (data), il dispositivo HID a cui appartiene (device) e l'ID report a 8 bit associato al report di input (reportId).

Foto del cambio Nintendo blu e rossa.
Dispositivi Joy-Con di Nintendo Switch
.

Continuando con l'esempio precedente, il codice riportato di seguito mostra come rilevare il pulsante premuto dall'utente su un dispositivo Joy-Con Right, possibilmente provare a farlo a casa.

device.addEventListener("inputreport", event => {
  const { data, device, reportId } = event;

  // Handle only the Joy-Con Right device and a specific report ID.
  if (device.productId !== 0x2007 && reportId !== 0x3f) return;

  const value = data.getUint8(0);
  if (value === 0) return;

  const someButtons = { 1: "A", 2: "X", 4: "B", 8: "Y" };
  console.log(`User pressed button ${someButtons[value]}.`);
});

Invia report di output

Per inviare un report di output a un dispositivo HID, passa l'ID report a 8 bit associato al report di output (reportId) e i byte come BufferSource (data) a device.sendReport(). La promessa restituita si risolve una volta che la segnalazione è stata inviata. Se il dispositivo HID non utilizza gli ID report, imposta reportId su 0.

L'esempio seguente riguarda un dispositivo Joy-Con e mostra come farlo vibrare con i report di output.

// First, send a command to enable vibration.
// Magical bytes come from https://github.com/mzyy94/joycon-toolweb
const enableVibrationData = [1, 0, 1, 64, 64, 0, 1, 64, 64, 0x48, 0x01];
await device.sendReport(0x01, new Uint8Array(enableVibrationData));

// Then, send a command to make the Joy-Con device rumble.
// Actual bytes are available in the sample below.
const rumbleData = [ /* ... */ ];
await device.sendReport(0x10, new Uint8Array(rumbleData));

Inviare e ricevere report sulle funzionalità

I report sulle funzionalità sono l'unico tipo di report di dati HID che può spostarsi in entrambe le direzioni. Consentono ai dispositivi e alle applicazioni HID di scambiare dati HID non standardizzati. A differenza dei report di input e di output, i report sulle funzionalità non vengono ricevuti o inviati dall'applicazione regolarmente.

Foto del computer portatile nera e argento.
Tastiera del laptop

Per inviare un report sulle funzionalità a un dispositivo HID, trasmetti l'ID report a 8 bit associato al report sulla funzionalità (reportId) e i byte come BufferSource (data) a device.sendFeatureReport(). La promessa restituita si risolve una volta inviata la segnalazione. Se il dispositivo HID non utilizza gli ID report, imposta reportId su 0.

L'esempio seguente illustra l'utilizzo dei report sulle funzionalità mostrando come richiedere un dispositivo con retroilluminazione della tastiera Apple, aprirlo e farlo lampeggiare.

const waitFor = duration => new Promise(r => setTimeout(r, duration));

// Prompt user to select an Apple Keyboard Backlight device.
const [device] = await navigator.hid.requestDevice({
  filters: [{ vendorId: 0x05ac, usage: 0x0f, usagePage: 0xff00 }]
});

// Wait for the HID connection to open.
await device.open();

// Blink!
const reportId = 1;
for (let i = 0; i < 10; i++) {
  // Turn off
  await device.sendFeatureReport(reportId, Uint32Array.from([0, 0]));
  await waitFor(100);
  // Turn on
  await device.sendFeatureReport(reportId, Uint32Array.from([512, 0]));
  await waitFor(100);
}

Per ricevere un report sulla funzionalità da un dispositivo HID, passa l'ID report a 8 bit associato al report sulla funzionalità (reportId) a device.receiveFeatureReport(). La promessa restituita si risolve con un oggetto DataView che contiene i contenuti del report sulle funzionalità. Se il dispositivo HID non utilizza gli ID report, imposta reportId su 0.

// Request feature report.
const dataView = await device.receiveFeatureReport(/* reportId= */ 1);

// Read feature report contents with dataView.getInt8(), getUint8(), etc...

Ascoltare la connessione e la disconnessione

Una volta concessa l'autorizzazione ad accedere a un dispositivo HID, il sito web può ricevere attivamente eventi di connessione e disconnessione ascoltando gli eventi "connect" e "disconnect".

navigator.hid.addEventListener("connect", event => {
  // Automatically open event.device or warn user a device is available.
});

navigator.hid.addEventListener("disconnect", event => {
  // Remove |event.device| from the UI.
});

Revocare l'accesso a un dispositivo HID

Il sito web può liberare le autorizzazioni di accesso a un dispositivo HID che non è più interessato a conservare chiamando forget() sull'istanza HIDDevice. Ad esempio, per un'applicazione web didattica utilizzata su un computer condiviso con molti dispositivi, un numero elevato di autorizzazioni accumulate dall'utente crea un'esperienza utente negativa.

Se chiami forget() su una singola istanza HIDDevice, l'accesso a tutte le interfacce HID sullo stesso dispositivo fisico verrà revocato.

// Voluntarily revoke access to this HID device.
await device.forget();

Poiché forget() è disponibile in Chrome 100 o versioni successive, controlla se questa funzionalità è supportata con quanto segue:

if ("hid" in navigator && "forget" in HIDDevice.prototype) {
  // forget() is supported.
}

Suggerimenti per sviluppatori

Eseguire il debug dell'HID in Chrome è facile con la pagina interna, about://device-log, dove puoi visualizzare tutti gli eventi relativi all'HID e ai dispositivi USB in un'unica posizione.

Screenshot della pagina interna per il debug dell&#39;HID.
Pagina interna in Chrome per il debug dell'HID.

Controlla lo strumento Explorer HID per scaricare le informazioni sul dispositivo HID in un formato leggibile. Mappa dai valori di utilizzo ai nomi di ogni utilizzo HID.

Sulla maggior parte dei sistemi Linux, i dispositivi HID sono mappati con autorizzazioni di sola lettura per impostazione predefinita. Per consentire a Chrome di aprire un dispositivo HID, devi aggiungere una nuova regola udev. Crea un file in /etc/udev/rules.d/50-yourdevicename.rules con i contenuti seguenti:

KERNEL=="hidraw*", ATTRS{idVendor}=="[yourdevicevendor]", MODE="0664", GROUP="plugdev"

Nella riga in alto, [yourdevicevendor] corrisponde a 057e se il tuo dispositivo è, ad esempio, Nintendo Switch Joy-Con. Puoi anche aggiungere ATTRS{idProduct} per una regola più specifica. Assicurati che user sia un membro del gruppo plugdev. Collega di nuovo il dispositivo.

Supporto del browser

L'API WebHID è disponibile su tutte le piattaforme desktop (ChromeOS, Linux, macOS e Windows) in Chrome 89.

Demo

Alcune demo di WebHID sono elencate all'indirizzo web.dev/hid-examples. Dai un'occhiata!

Sicurezza e privacy

Gli autori delle specifiche hanno progettato e implementato l'API WebHID utilizzando i principi fondamentali definiti nell'articolo Controllare l'accesso alle funzionalità avanzate della piattaforma web, tra cui controllo dell'utente, trasparenza ed ergonomia. La possibilità di utilizzare questa API è controllata principalmente da un modello di autorizzazione che concede l'accesso a un solo dispositivo HID alla volta. In risposta a una richiesta dell'utente, l'utente deve eseguire dei passaggi attivi per selezionare un determinato dispositivo HID.

Per comprendere i compromessi in termini di sicurezza, consulta la sezione Considerazioni su sicurezza e privacy nelle specifiche di WebHID.

Inoltre, Chrome controlla l'utilizzo di ogni raccolta di primo livello e, se una raccolta di primo livello ha un utilizzo protetto (ad es. tastiera generica, mouse), un sito web non potrà inviare e ricevere i report definiti nella raccolta. L'elenco completo degli utilizzi protetti è disponibile pubblicamente.

Tieni presente che anche i dispositivi HID sensibili alla sicurezza (ad esempio i dispositivi FIDO HID utilizzati per un'autenticazione più efficace) sono bloccati in Chrome. Consulta i file della lista bloccata USB e della lista bloccata HID.

Feedback

Il team di Chrome vorrebbe conoscere la tua opinione ed esperienza con l'API WebHID.

Parlaci della progettazione dell'API

C'è qualcosa nell'API che non funziona come previsto? Oppure mancano metodi o proprietà di cui hai bisogno per implementare la tua idea?

Segnala un problema relativo alle specifiche sul repository GitHub dell'API WebHID o aggiungi le tue opinioni su un problema esistente.

Segnala un problema con l'implementazione

Hai trovato un bug nell'implementazione di Chrome? Oppure l'implementazione è diversa dalle specifiche?

Leggi l'articolo su come segnalare bug di WebHID. Assicurati di includere il maggior numero di dettagli possibile, fornisci istruzioni semplici per riprodurre il bug e assicurati che i componenti siano impostati su Blink>HID. Glitch funziona benissimo per condividere riproduzioni rapide e semplici.

Mostra assistenza

Hai intenzione di utilizzare l'API WebHID? Il supporto pubblico aiuta il team di Chrome a dare la priorità alle funzionalità e mostra ad altri fornitori di browser quanto sia fondamentale supportarle.

Invia un tweet a @ChromiumDev usando l'hashtag #WebHID e facci sapere dove e come lo stai usando.

Link utili

Ringraziamenti

Ringraziamo Matt Reynolds e Joe Medley per le recensioni su questo articolo. Foto di Nintendo Switch in rosso e blu di Sara Kurfeß e foto di un laptop nero e argento di Athul Cyriac Ajay su Unsplash.