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.
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.
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.
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.
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.
Open vervolgens HIDDevice
door de open()
methode aan te roepen.
await stadiaController.open();
Registreer HIDDevice
opnieuw en de opened
vlag wordt ingesteld op true
.
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 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.
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.
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 .