Praten met de Stadia-controller met WebHID

De geflashte Stadia-controller werkt als een standaard gamepad, wat betekent dat niet alle knoppen toegankelijk zijn via de Gamepad API. Met WebHID heb je nu toegang tot de ontbrekende knoppen.

Sinds Stadia gesloten werd, vreesden velen dat de controller als nutteloos stuk hardware op de vuilnisbelt zou belanden. Gelukkig heeft het Stadia-team besloten om de Stadia-controller open te stellen door een aangepaste firmware te leveren die je op je controller kunt flashen door naar de Stadia Bluetooth- moduspagina te gaan. Hierdoor verschijnt je Stadia-controller als een standaard gamepad waarmee je verbinding kunt maken via een USB-kabel of draadloos via Bluetooth. De Stadia Bluetooth-pagina, die met trots werd gepresenteerd in de Project Fugu API Showcase , gebruikt WebHID en WebUSB , maar dat is niet het onderwerp van dit artikel. In dit bericht wil ik uitleggen hoe je via WebHID met de Stadia-controller kunt communiceren.

De Stadia-controller als standaard gamepad

Na het flashen verschijnt de controller als een standaard gamepad voor het besturingssysteem. Zie de volgende schermafbeelding voor een gangbare knop- en asindeling op een standaard gamepad. Zoals gedefinieerd in de Gamepad API- specificatie, hebben standaard gamepads 0 tot 16 knoppen, dus 17 in totaal (de D-pad telt als vier knoppen). Als je de Stadia-controller probeert in de demo van de gamepadtester , zul je merken dat hij perfect werkt.

Een schema van een standaard gamepad met de verschillende assen en knoppen gelabeld.

Als je echter de knoppen op de Stadia-controller telt, zijn het er 19. Als je ze systematisch één voor één uitprobeert in de gamepad-tester, zul je merken dat de Assistent- en de Capture- knoppen niet werken. Zelfs als het kenmerk van de gamepad- buttons zoals gedefinieerd in de Gamepad-specificatie open is, omdat de Stadia-controller als een standaard gamepad wordt weergegeven, zijn alleen de knoppen 0-16 toegewezen. Je kunt de andere knoppen nog steeds gebruiken, maar de meeste games verwachten niet dat ze bestaan.

WebHID komt te hulp

Dankzij de WebHID API kun je met de ontbrekende knoppen 17 en 18 communiceren. En als je dat echt wilt, kun je zelfs gegevens opvragen over alle andere knoppen en assen die al beschikbaar zijn via de Gamepad API. De eerste stap is uitzoeken hoe de Stadia-controller zichzelf aan het besturingssysteem rapporteert. Dit kun je doen door de Chrome DevTools Console op een willekeurige pagina te openen en een ongefilterde lijst met apparaten op te vragen via de WebHID API. Vervolgens kies je handmatig de Stadia-controller voor verdere inspectie. Je krijgt een ongefilterde lijst met apparaten door simpelweg een lege filters options array door te geven.

const [device] = await navigator.hid.requestDevice({filters: []});

In de picker lijkt het voorlaatste item op de Stadia-controller.

De WebHID API-apparaatkiezer toont een aantal niet-gerelateerde apparaten en de Stadia-controller op de voorlaatste positie.

Nadat u het apparaat "Stadia Controller rev. A" hebt geselecteerd, logt u het resulterende HIDDevice object in de console. Dit onthult de productId ( 37888 , wat 0x9400 in hex is) en vendorId ( 6353 , wat 0x18d1 in hex is) van de Stadia-controller. Als u de vendorID opzoekt in de officiële USB-leveranciers-ID-tabel , ziet u dat 6353 overeenkomt met wat u zou verwachten: Google Inc.

Chrome DevTools Console toont de uitvoer van het loggen van het HIDDevice-object.

Een alternatief voor de hierboven beschreven procedure is om naar chrome://device-log/ in de URL-balk te navigeren, op de knop Wissen te drukken, je Stadia-controller aan te sluiten en vervolgens op Vernieuwen te drukken. Dit geeft je dezelfde informatie.

De chrome://device-log debug-interface met informatie over de aangesloten Stadia-controller.

Een ander alternatief is de tool HID Explorer , waarmee u nog meer details kunt bekijken van de HID-apparaten die op uw computer zijn aangesloten.

Gebruik deze twee ID's, vendorId en productId , om te verfijnen wat er in de kiezer wordt weergegeven door nu correct te filteren op het juiste WebHID-apparaat.

const [stadiaController] = await navigator.hid.requestDevice({filters: [{
  vendorId: 6353,
  productId: 37888,
}]});

Nu is het geluid van alle niet-gerelateerde apparaten verdwenen en wordt alleen de Stadia-controller weergegeven.

De WebHID API-apparaatkiezer toont alleen de Stadia-controller.

Open vervolgens HIDDevice door de open() methode aan te roepen.

await stadiaController.open();

Registreer HIDDevice opnieuw en de opened vlag wordt ingesteld op true .

De Chrome DevTools Console toont de uitvoer van het loggen van het HIDDevice-object nadat het is geopend.

Terwijl het apparaat open is, luistert u naar binnenkomende inputreport door een gebeurtenislistener te koppelen.

stadiaController.addEventListener('inputreport', (e) => {
  console.log(e);
});

Wanneer je de Assistent- knop op de controller indrukt en loslaat, worden er twee gebeurtenissen geregistreerd op de console. Je kunt ze zien als de gebeurtenissen ' Assistent -knop ingedrukt houden' en ' Assistent- knop ingedrukt houden'. Afgezien van het timeStamp lijken de twee gebeurtenissen op het eerste gezicht niet van elkaar te onderscheiden.

De Chrome DevTools Console toont welke HIDInputReportEvent-objecten worden geregistreerd.

De eigenschap reportId van de HIDInputReportEvent interface retourneert het identificatieprefix van één byte voor dit rapport, of 0 als de HID-interface geen rapport-ID's gebruikt. In dit geval is het 3 Het geheim zit in de eigenschap data , die wordt weergegeven als een DataView van grootte 10. Een DataView biedt een low-level interface voor het lezen en schrijven van meerdere getaltypen in een binaire ArrayBuffer . De manier om deze weergave beter te begrijpen, is door een Uint8Array te maken van de ArrayBuffer , zodat u de afzonderlijke 8-bits unsigned integers kunt zien.

const data = new Uint8Array(event.data.buffer);

Wanneer u vervolgens de gebeurtenisgegevens van het invoerrapport opnieuw registreert, beginnen de zaken duidelijker te worden en worden de gebeurtenissen " Assistent -knop ingedrukt" en " Assistent- knop omhoog" ontcijferbaar. Het eerste gehele getal ( 8 in beide gebeurtenissen) lijkt verband te houden met het aantal keren dat de Assistent-knop wordt ingedrukt, en het tweede gehele getal ( 2 en 0 ) lijkt verband te houden met het al dan niet indrukken van de Assistent- knop.

De Chrome DevTools Console toont welke Uint8Array-objecten worden geregistreerd voor elke HIDInputReportEvent.

Druk op de Capture -knop in plaats van de Assistent -knop, en je zult zien dat het tweede gehele getal van 1 verandert wanneer de knop wordt ingedrukt naar 0 wanneer deze wordt losgelaten. Dit stelt je in staat om een ​​zeer eenvoudige "driver" te schrijven waarmee je de ontbrekende twee knoppen kunt gebruiken.

stadia.addEventListener('inputreport', (event) => {
  if (!e.reportId === 3) {
    return;
  }
  const data = new Uint8Array(event.data.buffer);
  if (data[0] === 8) {
    if (data[1] === 1) {
      hidButtons[1].classList.add('highlight');
    } else if (data[1] === 2) {
      hidButtons[0].classList.add('highlight');
    } else if (data[1] === 3) {
      hidButtons[0].classList.add('highlight');
      hidButtons[1].classList.add('highlight');
    } else {
      hidButtons[0].classList.remove('highlight');
      hidButtons[1].classList.remove('highlight');
    }
  }
});

Met een dergelijke reverse-engineering-aanpak kun je, knop voor knop en as voor as, uitvogelen hoe je met WebHID met de Stadia-controller kunt communiceren. Als je het eenmaal onder de knie hebt, is de rest bijna een kwestie van mechanisch integer mapping.

Wat nu nog ontbreekt, is de soepele verbindingservaring die de Gamepad API je biedt. Hoewel je om veiligheidsredenen altijd de initiële picker-ervaring één keer moet doorlopen om met een WebHID-apparaat zoals de Stadia-controller te werken, kun je voor toekomstige verbindingen opnieuw verbinding maken met bekende apparaten. Doe dat door de getDevices() methode aan te roepen.

let stadiaController;
const [device] = await navigator.hid.getDevices();
if (device && device.vendorId === 6353 && device.productId === 37888) {
  stadiaController = device;
}

Demonstratie

Je kunt de Stadia-controller zien, die gezamenlijk wordt aangestuurd door de Gamepad API en de WebHID API, in een demo die ik heb gebouwd. Bekijk zeker ook de broncode , die is gebaseerd op de fragmenten uit dit artikel. Voor de eenvoud toon ik alleen de A- , B- , X- en Y- knoppen (aangestuurd door de Gamepad API) en de Assistent- en Capture- knoppen (aangestuurd door de WebHID API). Onder de controllerafbeelding zie je de onbewerkte WebHID-gegevens, zodat je een idee krijgt van alle knoppen en assen op de controller.

De demo-app voor de Stadia-controller laat zien dat de knoppen A, B, X en Y worden aangestuurd door de Gamepad API, en dat de Assistent- en opnameknoppen worden aangestuurd door de WebHID API.

Conclusies

Dankzij de nieuwe firmware is de Stadia-controller nu bruikbaar als een standaard gamepad met 17 knoppen, wat in de meeste gevallen meer dan voldoende is om gangbare webgames te bedienen. Mocht je om welke reden dan ook gegevens van alle 19 knoppen op de controller nodig hebben, dan geeft WebHID je toegang tot low-level inputrapporten die je kunt ontcijferen door ze één voor één te reverse-engineeren. Mocht je na het lezen van dit artikel een complete WebHID-driver hebben geschreven, neem dan contact met me op. Ik link je project hier graag naartoe. Veel plezier met WebHIDen!

Dankbetuigingen

Dit artikel is beoordeeld door François Beaufort .