Connettiti 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

Data di pubblicazione: 15 settembre 2020

Browser Support

  • Chrome: 89.
  • Edge: 89.
  • Firefox: not supported.
  • Safari: not supported.

Source

Esistono molti 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 (come 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 supporto scadente per la coda lunga di dispositivi meno recenti e non comuni. Inoltre, il browser dipende da anomalie nel comportamento di dispositivi specifici.

Terminologia

Un Human Interface Device (HID) può ricevere input o offrire output agli utenti. Esiste un protocollo HID, uno standard per la comunicazione bidirezionale tra un host e un dispositivo progettato per semplificare la procedura di installazione.

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.

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 del 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.

Utilizzare l'API WebHID

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. A questo scopo, 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();
Prompt per la selezione di un Joy-Con di Nintendo Switch.

Puoi anche utilizzare il tasto facoltativo exclusionFilters in navigator.hid.requestDevice() per escludere alcuni dispositivi dal selettore del browser che sono noti per il 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 identificatori di fornitore e 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

Joy-Con di Nintendo Switch.

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).

Continuando con l'esempio precedente, questo codice ti aiuta a rilevare quale pulsante ha premuto l'utente su un Joy-Con destro, in modo che tu possa provarlo 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]}.`);
});

Fai riferimento alla demo su CodePen.

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 successivo 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.
const rumbleData = [ /* ... */ ];
await device.sendReport(0x10, new Uint8Array(rumbleData));

Fai riferimento alla demo su CodePen.

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.

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.

Questo esempio 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);
}

Fai riferimento alla demo su CodePen.

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

La pagina interna per eseguire il debug dell&#39;interfaccia HID.

Esegui il debug di HID in Chrome con la pagina interna about://device-log, dove puoi visualizzare tutti gli eventi relativi ai dispositivi HID e USB in un unico posto.

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"

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

Demo

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

Sicurezza e privacy

Gli autori delle specifiche hanno progettato e implementato l'API WebHID utilizzando i principi fondamentali definiti in Controlling Access to Powerful Web Platform Features, 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 della specifica 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?

Segnala un problema relativo alle specifiche nel repository GitHub dell'API WebHID o aggiungi i tuoi commenti a un problema 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 per riprodurre il bug e imposta Componenti su Blink>HID.

Link utili

Ringraziamenti

Grazie a Matt Reynolds e Joe Medley per le loro revisioni.