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

Esiste 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 di sistema. L'API WebHID risolve il problema fornendo un modo per implementare la logica specifica del dispositivo in JavaScript.

Casi d'uso suggeriti

Un dispositivo HID riceve input o fornisce output a persone. Esempi di dispositivi includere tastiere, dispositivi di puntamento (mouse, touchscreen e così via) e gamepad. Il protocollo HID consente di accedere a questi dispositivi su computer da tavolo utilizzando i driver del sistema operativo. La piattaforma web supporta i dispositivi HID facendo affidamento su questi driver.

L'impossibilità di accedere a dispositivi HID insoliti è particolarmente spiacevole quando si tratta di tastiere ausiliarie alternative (ad es. Elgato Stream Deck, cuffie Jabra, X-keys) e supporto di gamepad insoliti. I gamepad progettati per i computer spesso utilizzano HID per gli input (pulsanti, joystick, trigger) e le uscite (LED, vibrazione) del gamepad. Purtroppo, gli input e le uscite dei gamepad non sono ben standardizzati e i browser web spesso richiedono una logica personalizzata per dispositivi specifici. Questo approccio non è sostenibile e comporta un'assistenza limitata per la coda lunga di dispositivi meno recenti e insoliti. Inoltre, il browser dipende da peculiarità nel comportamento di dispositivi specifici.

Terminologia

HID è costituito da due concetti fondamentali: report e descrittori 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 dispositivo HID (Human Interface Device) è un tipo di dispositivo che riceve input o fornisce output a persone. 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 da allora è stato implementato su molti altri protocolli, incluso il Bluetooth.

Le applicazioni e i dispositivi HID scambiano dati binari tramite tre tipi di report:

Tipo di rapporto Descrizione
Report di input Dati inviati dal dispositivo all'applicazione (ad es. viene premuto un pulsante).
Report di output Dati inviati dall'applicazione al dispositivo (ad es. una richiesta di attivazione della retroilluminazione della tastiera).
Report sulla funzionalità Dati che possono essere inviati in entrambe le direzioni. Il formato è specifico del dispositivo.

Un descrittore report descrive il formato binario dei report supportati 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 output standardizzato. I valori di utilizzo consentono a un dispositivo di descrivere l'utilizzo previsto e lo scopo di ciascun campo nei report. Ad esempio, ne viene definito uno per il pulsante sinistro del mouse. Gli utilizzi sono inoltre organizzati in pagine di utilizzo, che forniscono un'indicazione della categoria di alto livello del dispositivo o del report.

Utilizzo dell'API WebHID

Rilevamento di funzionalità

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

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

Aprire una connessione HID

L'API WebHID è asincrona per impostazione predefinita per impedire il blocco dell'interfaccia utente del sito web in attesa di input. Questo è importante perché i dati HID possono essere ricevuti in qualsiasi momento, richiedendo un modo per ascoltarli.

Per aprire una connessione HID, accedi prima a un oggetto HIDDevice. Per farlo, puoi chiedere all'utente di selezionare un dispositivo chiamando navigator.hid.requestDevice() oppure sceglierne uno da navigator.hid.getDevices(), che restituisce un elenco di dispositivi a cui il sito web ha già concesso l'accesso.

La funzione navigator.hid.requestDevice() accetta un oggetto obbligatorio che definisce i filtri. Questi vengono utilizzati per associare qualsiasi dispositivo collegato a un ID fornitore USB (vendorId), a un ID prodotto USB (productId), a un valore della pagina di utilizzo (usagePage) e a un valore di utilizzo (usage). Puoi trovarli nel Repository ID USB e nel documento delle tabelle di utilizzo 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 di una richiesta relativa a un dispositivo HID su un sito web.
Prompt per l'utente per selezionare un Joy-Con di Nintendo Switch.

Puoi anche utilizzare la chiave facoltativa exclusionFilters in navigator.hid.requestDevice() per escludere alcuni dispositivi dal selettore del browser ad esempio quelli noti per non funzionare correttamente.

// 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 prodotto e del fornitore USB per l'identificazione del dispositivo. Il relativo 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 uno stato "chiuso" e devono essere aperti chiamando open() prima che i dati possano essere inviati o ricevuti.

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

Ricevere report sugli input

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

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

Continuando con l'esempio precedente, il codice seguente mostra come rilevare il pulsante premuto dall'utente su un Joy-Con destro in modo da poterlo provare 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]}.`);
});

Inviare report sull'output

Per inviare un report di output a un dispositivo HID, passa l'ID report di 8 bit associato al report di output (reportId) e i byte come BufferSource (data) a device.sendReport(). La promessa restituita viene risolta una volta inviato il report. Se il dispositivo HID non utilizza gli ID report, imposta reportId su 0.

L'esempio seguente si applica a un dispositivo Joy-Con e mostra come attivare il feedback aptico 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 sui dati HID che possono essere trasmessi in entrambe le direzioni. Consentono a dispositivi e applicazioni HID di scambiare dati HID non standardizzati. A differenza dei report di input e output, i report sulle funzionalità non vengono ricevuti o inviati dall'applicazione su base regolare.

Foto di un computer portatile nero e argento.
Tastiera del laptop

Per inviare un report sulle funzionalità a un dispositivo HID, passa l'ID report di 8 bit associato al report sulle funzionalità (reportId) e i byte come BufferSource (data) a device.sendFeatureReport(). La promessa restituita viene risolta una volta inviato il report. 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 sulle funzionalità da un dispositivo HID, passa l'ID report di 8 bit associato al report sulle funzionalità (reportId) a device.receiveFeatureReport(). La promessa restituita viene risolta con un oggetto DataView contenente 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...

Ascolta la connessione e la disconnessione

Quando al sito web è stata concessa l'autorizzazione per accedere a un dispositivo HID, 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ò ripulire le autorizzazioni di accesso a un dispositivo HID di cui non è più interessato a conservare chiamando forget() nell'istanza HIDDevice. Ad esempio, per un'applicazione web didattica utilizzata su un computer condiviso con molti dispositivi, un numero elevato di autorizzazioni generate dagli utenti accumulate crea un'esperienza utente negativa.

La chiamata a forget() su una singola istanza HIDDevice comporta la revoca dell'accesso a tutte le interfacce HID sullo stesso dispositivo fisico.

// 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 gli sviluppatori

Il debug HID in Chrome è facile con la pagina interna about://device-log dove puoi vedere tutti gli eventi relativi ai dispositivi HID e USB in un unico posto.

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

Consulta HID Explorer per scaricare le informazioni sul dispositivo HID in un formato leggibile. Esegue la mappatura dai valori di utilizzo ai nomi per 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 seguenti contenuti:

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

Nella riga sopra, [yourdevicevendor] è 057e se il tuo dispositivo è un Joy-Con di Nintendo Switch, ad esempio. ATTRS{idProduct} può essere aggiunto anche per una regola più specifica. Assicurati che user sia un membro del gruppo plugdev. Dopodiché, ricollega il dispositivo.

Supporto 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 della specifica hanno progettato e implementato l'API WebHID utilizzando i principi di base definiti in Controllo dell'accesso a potenti funzionalità della piattaforma web, tra cui il controllo utente, la trasparenza e l'ergonomia. La possibilità di utilizzare questa API è limitata 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 passaggi attivi per selezionare un determinato dispositivo HID.

Per comprendere i compromessi relativi alla sicurezza, consulta la sezione Considerazioni su sicurezza e privacy della specifica 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 in quella raccolta. L'elenco completo degli utilizzi protetti è disponibile pubblicamente.

Tieni presente che anche i dispositivi HID sensibili alla sicurezza (come i dispositivi HID FIDO utilizzati per un'autenticazione più forte) sono bloccati in Chrome. Consulta i file Lista bloccata USB e Lista bloccata HID.

Feedback

Il team di Chrome vorrebbe conoscere le tue opinioni ed esperienze con l'API WebHID.

Fornisci informazioni sul design dell'API

C'è qualcosa nell'API che non funziona come previsto? In alternativa, mancano metodi o proprietà necessari per implementare la tua idea?

Invia una segnalazione relativa alle specifiche nel repository GitHub dell'API WebHID o aggiungi il tuo parere a un problema esistente.

Segnalare un problema con l'implementazione

Hai trovato un bug nell'implementazione di Chrome? Oppure l'implementazione è diversa dalla specifica?

Consulta l'articolo Come segnalare i bug di WebHID. Assicurati di includere il maggior numero di dettagli possibile, fornisci istruzioni semplici per riprodurre il bug e imposta Componenti su Blink>HID. Glitch è ideale per condividere riproduzioni rapide e semplici.

Mostrare il proprio sostegno

Intendi utilizzare l'API WebHID? Il tuo 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 all'account @ChromiumDev utilizzando l'hashtag #WebHID e comunicaci dove e come lo stai utilizzando.

Link utili

Ringraziamenti

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