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 serie di Human Interface Device (HID), come tastiere alternative o gamepad esotici, troppo nuovi, troppo vecchi o troppo rari per essere accessibili dai driver dei dispositivi dei sistemi. L'API WebHID risolve questo problema fornendo un modo per implementare la logica specifica del dispositivo in JavaScript.

Casi d'uso suggeriti

Un dispositivo HID riceve input da o fornisce output agli esseri umani. Esempi di dispositivi includono tastiere, dispositivi di puntamento (mouse, touchscreen e così via) e gamepad. Il protocollo HID consente di accedere a questi dispositivi sui computer 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 problematica quando si tratta di tastiere ausiliarie alternative (ad es. Elgato Stream Deck, cuffie Jabra, X-keys) e supporto per gamepad esotici. I gamepad progettati per il desktop spesso utilizzano HID per gli input (pulsanti, joystick, trigger) e gli output (LED, vibrazione) del gamepad. Purtroppo, gli input e gli output del gamepad non sono ben standardizzati e i browser web spesso richiedono una logica personalizzata per dispositivi specifici. Questa situazione è insostenibile e comporta un'assistenza scadente per la coda lunga di dispositivi meno recenti e non comuni. Inoltre, il browser dipende da anomalie nel comportamento di dispositivi specifici.

Terminologia

HID è costituito da due concetti fondamentali: report e descrittori di 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 da 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 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 input Dati inviati dal dispositivo all'applicazione (ad es. viene premuto un pulsante).
Report output Dati inviati dall'applicazione al dispositivo (ad es. una richiesta di attivazione della retroilluminazione della tastiera).
Report sulle funzionalità Dati che possono essere inviati in entrambe le direzioni. Il formato è specifico per il dispositivo.

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

Un utilizzo HID è un valore numerico che si riferisce 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, uno è definito per il pulsante sinistro di un mouse. Gli utilizzi sono organizzati anche in pagine di utilizzo, 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.
}

Aprire una connessione HID

L'API WebHID è asincrona per progettazione per impedire il blocco dell'interfaccia utente del sito web durante l'attesa dell'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() o sceglierne uno da navigator.hid.getDevices(), che restituisce un elenco di dispositivi a cui il sito web ha avuto accesso in precedenza.

La funzione navigator.hid.requestDevice() accetta un oggetto obbligatorio che definisce i filtri. Questi vengono utilizzati per abbinare qualsiasi dispositivo connesso con un identificatore fornitore USB (vendorId), un identificatore prodotto USB (productId), un valore della pagina di utilizzo (usagePage) e un valore di utilizzo (usage). Puoi ottenerli dal repository degli ID USB e dal 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 di dispositivo HID su un sito web.
Prompt per l'utente per la selezione di un Joy-Con di Nintendo Switch.

Puoi anche utilizzare il tasto exclusionFilters facoltativo in navigator.hid.requestDevice() per escludere alcuni dispositivi dal selettore del browser che sono noti per malfunzionamenti, ad esempio.

// 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 identificatori del fornitore e del prodotto 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
}

I dispositivi HIDDevice vengono restituiti per impostazione predefinita in 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 entrata ascoltando gli eventi "inputreport" dal dispositivo. Questi eventi contengono i dati HID come oggetto DataView (data), il dispositivo HID a cui appartiene (device) e l'ID report a 8 bit associato al report di input (reportId).

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

Continuando con l'esempio precedente, il codice riportato di seguito mostra come rilevare quale pulsante ha premuto l'utente su un Joy-Con destro, in modo che tu possa 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]}.`);
});

Guarda la demo webhid-joycon-button della penna.

Inviare report sugli output

Per inviare un report di output a un dispositivo HID, trasmetti l'ID report a 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 ID report, imposta reportId su 0.

L'esempio riportato di seguito si applica a un controller 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));

Guarda la demo di Pen webhid-joycon-rumble.

Inviare e ricevere report sulle funzionalità

I report sulle funzionalità sono l'unico tipo di report sui dati HID che può essere trasferito 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 regolarmente dall'applicazione.

Foto di un laptop nero 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 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 ID report, imposta reportId su 0.

L'esempio seguente illustra l'utilizzo dei report sulle funzionalità mostrando come richiedere un dispositivo di 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);
}

Guarda la demo webhid-apple-keyboard-backlight della penna.

Per ricevere un report sulle funzionalità da un dispositivo HID, trasmetti l'ID report a 8 bit associato al report sulle funzionalità (reportId) a device.receiveFeatureReport(). La promessa restituita viene risolta con un oggetto DataView che contiene i contenuti del report sulle funzionalità. Se il dispositivo HID non utilizza 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

Quando al sito web è stata concessa l'autorizzazione di accesso 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ò eliminare le autorizzazioni di accesso a un dispositivo HID che non gli interessa più 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 generate dagli utenti accumulate crea un'esperienza utente scadente.

La chiamata di forget() su una singola istanza di HIDDevice revoca l'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, verifica se questa funzionalità è supportata con quanto segue:

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

Suggerimenti per gli sviluppatori

Il debug di HID in Chrome è semplice con la pagina interna about://device-log, dove puoi visualizzare 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 di HID.

Dai un'occhiata a HID Explorer per scaricare le informazioni sul dispositivo HID in un formato leggibile. Mappa i valori di utilizzo ai nomi per ogni utilizzo HID.

Nella maggior parte dei sistemi Linux, i dispositivi HID vengono 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 il seguente contenuto:

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

Nella riga precedente, [yourdevicevendor] è 057e se il tuo dispositivo è, ad esempio, un Joy-Con di Nintendo Switch. ATTRS{idProduct} può essere aggiunto anche per una regola più specifica. Assicurati che il tuo user sia un membro del gruppo plugdev. Dopodiché, riconnetti 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. Vai a dare un'occhiata.

Sicurezza e privacy

Gli autori delle specifiche hanno progettato e implementato l'API WebHID utilizzando i principi fondamentali definiti in Controllo dell'accesso a potenti funzionalità della piattaforma web, tra cui controllo utente, trasparenza ed ergonomia. La possibilità di utilizzare questa API è principalmente regolata da un modello di autorizzazione che concede l'accesso a un solo dispositivo HID alla volta. In risposta a un prompt dell'utente, quest'ultimo deve eseguire passaggi attivi per selezionare un particolare dispositivo HID.

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

Inoltre, Chrome esamina l'utilizzo di ogni raccolta di primo livello e, se una raccolta di primo livello ha un utilizzo protetto (ad es. tastiera e mouse generici), un sito web non potrà inviare e ricevere 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ù efficace) sono bloccati in Chrome. Consulta i file USB blocklist e HID blocklist.

Feedback

Il team di Chrome vorrebbe conoscere la tua opinione e le tue esperienze con l'API WebHID.

Descrivi la progettazione dell'API

C'è qualcosa nell'API che non funziona come previsto? Oppure mancano metodi o proprietà che ti servono per implementare la tua idea?

Invia una segnalazione relativa alle specifiche nel repository GitHub dell'API WebHID o aggiungi i tuoi commenti a una segnalazione esistente.

Segnalare un problema relativo all'implementazione

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

Consulta Come segnalare 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.

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 a @ChromiumDev utilizzando l'hashtag #WebHID e facci sapere dove e come lo utilizzi.

Link utili

Ringraziamenti

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