Maak verbinding met ongebruikelijke HID-apparaten

De WebHID API stelt websites in staat om toegang te krijgen tot alternatieve hulptoetsenborden en exotische gamepads.

François Beaufort
François Beaufort

Gepubliceerd: 15 september 2020

Browser Support

  • Chrome: 89.
  • Rand: 89.
  • Firefox: niet ondersteund.
  • Safari: niet ondersteund.

Source

Er zijn veel gebruikersinterfaceapparaten (HID's), zoals alternatieve toetsenborden of exotische gamepads, die te nieuw, te oud of te ongebruikelijk zijn om toegankelijk te zijn voor de apparaatstuurprogramma's van systemen. De WebHID API lost dit op door een manier te bieden om apparaatspecifieke logica in JavaScript te implementeren.

Voorgestelde gebruiksscenario's

Een HID-apparaat ontvangt invoer van of levert uitvoer aan mensen. Voorbeelden van dergelijke apparaten zijn toetsenborden, aanwijsapparaten (muizen, touchscreens, enz.) en gamepads. Het HID-protocol maakt het mogelijk om deze apparaten op desktopcomputers te gebruiken met behulp van stuurprogramma's van het besturingssysteem. Het webplatform ondersteunt HID-apparaten door gebruik te maken van deze stuurprogramma's.

Het onvermogen om toegang te krijgen tot minder gangbare HID-apparaten is met name frustrerend als het gaat om alternatieve hulptoetsenborden (zoals de Elgato Stream Deck , Jabra-headsets en X-keys ) en ondersteuning voor exotische gamepads. Gamepads die ontworpen zijn voor desktops gebruiken vaak HID voor zowel input (knoppen, joysticks, triggers) als output (leds, rumble).

Helaas zijn de invoer- en uitvoerparameters van gamepads niet goed gestandaardiseerd en vereisen webbrowsers vaak aangepaste logica voor specifieke apparaten. Dit is onhoudbaar en leidt tot gebrekkige ondersteuning voor een groot aantal oudere en minder gangbare apparaten. Het zorgt er ook voor dat de browser afhankelijk wordt van eigenaardigheden in het gedrag van specifieke apparaten.

Terminologie

Een HID ( Human Interface Device ) kan input ontvangen van of output leveren aan mensen. Er bestaat een HID-protocol, een standaard voor bidirectionele communicatie tussen een host en een apparaat, dat is ontworpen om de installatieprocedure te vereenvoudigen.

HID bestaat uit twee fundamentele concepten: rapporten en rapportbeschrijvingen. Rapporten zijn de gegevens die worden uitgewisseld tussen een apparaat en een softwareclient. De rapportbeschrijving beschrijft het formaat en de betekenis van de gegevens die het apparaat ondersteunt.

Applicaties en HID-apparaten wisselen binaire gegevens uit via drie rapporttypen:

Rapporttype Beschrijving
Invoerrapport Gegevens die van het apparaat naar de applicatie worden verzonden (bijvoorbeeld wanneer een knop wordt ingedrukt).
Uitvoerrapport Gegevens die vanuit de applicatie naar het apparaat worden verzonden (bijvoorbeeld een verzoek om de toetsenbordverlichting in te schakelen).
Functierapport Gegevens die in beide richtingen verzonden kunnen worden. Het formaat is apparaatspecifiek.

Een rapportbeschrijving beschrijft het binaire formaat van rapporten die door het apparaat worden ondersteund. De structuur is hiërarchisch en kan rapporten groeperen als afzonderlijke verzamelingen binnen de verzameling op het hoogste niveau. Het formaat van de beschrijving wordt gedefinieerd door de HID-specificatie.

Een HID-gebruik is een numerieke waarde die verwijst naar een gestandaardiseerde invoer of uitvoer. Gebruikswaarden stellen een apparaat in staat om het beoogde gebruik van het apparaat en het doel van elk veld in de rapporten te beschrijven. Zo is er bijvoorbeeld een gebruikswaarde gedefinieerd voor de linker muisknop. Gebruikswaarden zijn ook georganiseerd in gebruikspagina's, die een indicatie geven van de overkoepelende categorie van het apparaat of rapport.

Gebruik de WebHID API

Om te controleren of de WebHID API wordt ondersteund, kunt u het volgende gebruiken:

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

Open een HID-verbinding

De WebHID API is asynchroon ontworpen om te voorkomen dat de gebruikersinterface van de website vastloopt tijdens het wachten op invoer. Dit is belangrijk omdat HID-gegevens op elk moment kunnen worden ontvangen, waardoor een manier nodig is om ernaar te luisteren.

Om een ​​HID-verbinding tot stand te brengen, moet u eerst een HIDDevice object benaderen. Hiervoor kunt u de gebruiker vragen een apparaat te selecteren door navigator.hid.requestDevice() aan te roepen, of een apparaat kiezen uit navigator.hid.getDevices() , die een lijst retourneert van apparaten waartoe de website eerder toegang heeft gekregen.

De functie navigator.hid.requestDevice() vereist een verplicht object dat filters definieert. Deze filters worden gebruikt om elk apparaat te matchen dat is verbonden met een USB-leveranciers-ID ( vendorId ), een USB-product-ID ( productId ), een gebruikspagina-waarde ( usagePage ) en een gebruikswaarde ( usage ). U kunt deze gegevens verkrijgen via de USB ID Repository en het document met HID-gebruikstabellen .

De meerdere HIDDevice objecten die door deze functie worden geretourneerd, vertegenwoordigen meerdere HID-interfaces op hetzelfde fysieke apparaat.

// 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();
Gebruikersprompt voor het selecteren van een Nintendo Switch Joy-Con.

Je kunt ook de optionele sleutel exclusionFilters in navigator.hid.requestDevice() gebruiken om bepaalde apparaten die aantoonbaar niet goed functioneren uit de browserkiezer te verwijderen.

// 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 }],
});

Een HIDDevice object bevat USB-leveranciers- en productidentificaties voor apparaatidentificatie. Het attribuut collections wordt geïnitialiseerd met een hiërarchische beschrijving van de rapportindelingen van het apparaat.

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
}

De HIDDevice apparaten worden standaard in een "gesloten" toestand geretourneerd en moeten worden geopend door de functie open() aan te roepen voordat er gegevens kunnen worden verzonden of ontvangen.

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

Ontvang invoerrapporten

Nintendo Switch Joy-Cons.

Zodra de HID-verbinding tot stand is gebracht, kunt u binnenkomende invoerrapporten verwerken door te luisteren naar de "inputreport" -gebeurtenissen van het apparaat. Deze gebeurtenissen bevatten de HID-gegevens als een DataView object ( data ), het HID-apparaat waartoe ze behoren ( device ) en de 8-bits rapport-ID die is gekoppeld aan het invoerrapport ( reportId ).

Om verder te gaan met het vorige voorbeeld: deze code helpt je te detecteren welke knop de gebruiker heeft ingedrukt op een Joy-Con Right-apparaat, zodat je het thuis kunt uitproberen.

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]}.`);
});

Bekijk de demo op CodePen .

Verzend uitvoerrapporten

Om een ​​uitvoerrapport naar een HID-apparaat te sturen, geeft u de 8-bits rapport-ID die aan het uitvoerrapport is gekoppeld ( reportId ) en bytes als BufferSource ( data ) door aan device.sendReport() . De geretourneerde promise wordt opgelost zodra het rapport is verzonden. Als het HID-apparaat geen rapport-ID's gebruikt, stelt reportId in op 0.

Het volgende voorbeeld is van toepassing op een Joy-Con-apparaat en laat zien hoe je het kunt laten trillen met behulp van uitvoerrapporten.

// 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));

Bekijk de demo op CodePen .

Functierapporten verzenden en ontvangen

Functierapporten zijn het enige type HID-gegevensrapport dat in beide richtingen kan worden uitgewisseld. Ze stellen HID-apparaten en -applicaties in staat om niet-gestandaardiseerde HID-gegevens uit te wisselen. In tegenstelling tot invoer- en uitvoerrapporten worden functierapporten niet regelmatig door de applicatie ontvangen of verzonden.

Om een ​​functierapport naar een HID-apparaat te sturen, geeft u de 8-bits rapport-ID die aan het functierapport is gekoppeld ( reportId ) en bytes als BufferSource ( data ) door aan device.sendFeatureReport() . De geretourneerde promise wordt opgelost zodra het rapport is verzonden. Als het HID-apparaat geen rapport-ID's gebruikt, stelt reportId in op 0.

Dit voorbeeld illustreert het gebruik van functierapporten door te laten zien hoe u een Apple-toetsenbordverlichting kunt aanvragen, openen en laten knipperen.

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

Bekijk de demo op CodePen .

Om een ​​functierapport van een HID-apparaat te ontvangen, geeft u de 8-bits rapport-ID die aan het functierapport is gekoppeld ( reportId ) door aan device.receiveFeatureReport() . De geretourneerde promise wordt opgelost met een DataView object dat de inhoud van het functierapport bevat. Als het HID-apparaat geen rapport-ID's gebruikt, stelt reportId in op 0.

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

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

Luister naar verbinding en ontkoppeling.

Wanneer de website toestemming heeft gekregen om toegang te krijgen tot een HID-apparaat, kan deze actief verbindings- en ontkoppelingsgebeurtenissen ontvangen door te luisteren naar "connect" en "disconnect" -gebeurtenissen.

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

De toegang tot een HID-apparaat intrekken

De website kan toegangsrechten voor een HID-apparaat dat niet langer nodig is, opschonen door de functie forget() aan te roepen op de HIDDevice instantie. Bijvoorbeeld, voor een educatieve webapplicatie die wordt gebruikt op een gedeelde computer met veel apparaten, zorgt een groot aantal verzamelde, door gebruikers gegenereerde toegangsrechten voor een slechte gebruikerservaring.

Door de functie forget() aan te roepen op een enkele HIDDevice instantie wordt de toegang tot alle HID-interfaces op hetzelfde fysieke apparaat ingetrokken.

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

Aangezien forget() beschikbaar is in Chrome 100 of later, kunt u controleren of deze functie wordt ondersteund met het volgende:

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

Ontwikkelaarstips

De interne pagina voor het debuggen van HID.

Debug HID in Chrome met de interne pagina about://device-log , waar je alle gebeurtenissen met betrekking tot HID- en USB-apparaten op één plek kunt zien.

Bekijk de HID Explorer om informatie over HID-apparaten in een leesbaar formaat weer te geven. Het programma koppelt gebruikswaarden aan namen voor elk HID-gebruik.

Op de meeste Linux-systemen hebben HID-apparaten standaard alleen-leestoegang. Om Chrome toegang te geven tot een HID-apparaat, moet u een nieuwe udev-regel toevoegen. Maak een bestand aan op /etc/udev/rules.d/50-yourdevicename.rules met de volgende inhoud:

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

In deze code is [yourdevicevendor] bijvoorbeeld 057e , als uw apparaat een Nintendo Switch Joy-Con is. ATTRS{idProduct} kan worden toegevoegd voor een specifiekere regel. Zorg ervoor dat uw user lid is van de plugdev groep. Verbind vervolgens uw apparaat opnieuw.

Demo's

Enkele WebHID-demo's zijn te vinden op web.dev/hid-examples .

Beveiliging en privacy

De auteurs van de specificaties hebben de WebHID API ontworpen en geïmplementeerd volgens de kernprincipes die zijn vastgelegd in Controlling Access to Powerful Web Platform Features , waaronder gebruikerscontrole, transparantie en ergonomie. De mogelijkheid om deze API te gebruiken wordt voornamelijk beperkt door een permissiemodel dat slechts toegang verleent aan één HID-apparaat tegelijk. Na een prompt van de gebruiker moet de gebruiker actief een specifiek HID-apparaat selecteren.

Om de afwegingen op het gebied van beveiliging te begrijpen, raadpleegt u het gedeelte ' Beveiligings- en privacyoverwegingen' in de WebHID-specificatie.

Daarnaast controleert Chrome het gebruik van elke collectie op het hoogste niveau. Als een collectie op het hoogste niveau een beveiligd gebruik heeft (bijvoorbeeld een generiek toetsenbord of muis), kan een website geen rapporten verzenden of ontvangen die in die collectie zijn gedefinieerd. De volledige lijst met beveiligde gebruikswijzen is openbaar beschikbaar .

Houd er rekening mee dat beveiligingsgevoelige HID-apparaten (zoals FIDO HID-apparaten die worden gebruikt voor sterkere authenticatie) ook worden geblokkeerd in Chrome. Zie de bestanden met de USB-blokkeerlijst en de HID-blokkeerlijst .

Feedback

Het Chrome-team hoort graag uw mening en ervaringen met de WebHID API.

Vertel ons iets over het API-ontwerp.

Werkt de API op een of andere manier niet zoals verwacht? Of ontbreken er methoden of eigenschappen die je nodig hebt om je idee te implementeren?

Dien een specificatieprobleem in op de GitHub-repository van de WebHID API of voeg je mening toe aan een bestaand probleem.

Meld een probleem met de implementatie.

Heb je een bug gevonden in de implementatie van Chrome? Of wijkt de implementatie af van de specificatie?

Bekijk de handleiding 'WebHID-bugs melden' . Vermeld zoveel mogelijk details, geef instructies om de bug te reproduceren en stel 'Componenten' in op Blink>HID .

Handige links

Dankbetuigingen

Met dank aan Matt Reynolds en Joe Medley voor hun recensies.