Über WebHID mit dem Stadia-Controller kommunizieren

Der geflashte Stadia-Controller funktioniert wie ein Standard-Gamepad. Das bedeutet, dass nicht alle Schaltflächen über die Gamepad API zugänglich sind. Mit WebHID können Sie jetzt auf die fehlenden Schaltflächen zugreifen.

Seit der Einstellung von Stadia befürchteten viele, dass der Controller als nutzloses Stück Hardware auf der Mülldeponie landen würde. Glücklicherweise hat sich das Stadia-Team dazu entschieden, den Stadia-Controller zu öffnen und eine benutzerdefinierte Firmware bereitzustellen, die du auf deinem Controller installieren kannst. Rufe dazu die Seite Stadia-Bluetooth-Modus auf. Dadurch wird Ihr Stadia-Controller als Standard-Gamepad angezeigt, das Sie per USB-Kabel oder kabellos über Bluetooth verbinden können. Die Stadia-Bluetooth-Seite, die im Project Fugu API Showcase vorgestellt wurde, verwendet WebHID und WebUSB. Dies ist jedoch nicht das Thema dieses Artikels. In diesem Beitrag möchte ich erklären, wie Sie über WebHID mit dem Stadia-Controller kommunizieren können.

Stadia-Controller als Standard-Gamepad

Nach dem Flashen wird der Controller für das Betriebssystem als Standard-Gamepad angezeigt. Der folgende Screenshot zeigt eine gängige Anordnung von Schaltflächen und Achsen auf einem Standard-Gamepad. Gemäß der Gamepad API-Spezifikation haben Standard-Gamepads die Tasten 0 bis 16, also insgesamt 17 Tasten (das D-Pad zählt als vier Tasten). Wenn Sie den Stadia-Controller in der Gamepad-Testdemo ausprobieren, werden Sie feststellen, dass er einwandfrei funktioniert.

Ein Schema eines Standard-Gamepads mit den verschiedenen Achsen und Schaltflächen

Wenn Sie jedoch die Tasten auf dem Stadia-Controller zählen, sind es 19. Wenn Sie sie im Gamepad-Tester nacheinander ausprobieren, stellen Sie fest, dass die Schaltflächen Assistant und Aufzeichnen nicht funktionieren. Auch wenn das in der Gamepad-Spezifikation definierte Gamepad-Attribut buttons offen ist, werden nur die Tasten 0–16 zugeordnet, da der Stadia-Controller als Standard-Gamepad angezeigt wird. Sie können die anderen Schaltflächen weiterhin verwenden, aber in den meisten Spielen werden sie nicht erwartet.

WebHID als Retter in der Not

Dank der WebHID API können Sie mit den fehlenden Schaltflächen 17 und 18 sprechen. Wenn Sie möchten, können Sie sogar Daten zu allen anderen Tasten und Achsen abrufen, die bereits über die Gamepad API verfügbar sind. Der erste Schritt besteht darin, herauszufinden, wie sich der Stadia-Controller dem Betriebssystem meldet. Eine Möglichkeit dazu besteht darin, die Chrome DevTools-Konsole auf einer beliebigen Seite zu öffnen und eine ungefilterte Liste der Geräte von der WebHID API anzufordern. Wählen Sie dann den Stadia-Controller manuell für eine weitere Prüfung aus. Wenn du eine unbefilterte Liste der Geräte abrufen möchtest, musst du einfach ein leeres filters-Optionsarray übergeben.

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

In der Auswahl sieht der vorletzte Eintrag aus wie der Stadia-Controller.

Die Geräteauswahl der WebHID API mit einigen nicht zugehörigen Geräten und dem Stadia-Controller auf der vorletzten Position.

Nachdem Sie das Gerät „Stadia Controller rev. A“ ausgewählt haben, loggen Sie das resultierende HIDDevice-Objekt in der Console. Dadurch werden die productId (37888, also 0x9400 im Hexadezimalformat) und vendorId (6353, also 0x18d1 im Hexadezimalformat) des Stadia-Controllers angezeigt. Wenn Sie die vendorID in der offiziellen Tabelle der USB-Anbieter-IDs nachschlagen, sehen Sie, dass 6353 der erwarteten Google Inc. zugeordnet ist.

Chrome DevTools-Konsole mit der Ausgabe der Protokollierung des HIDDevice-Objekts

Alternativ kannst du in der URL-Leiste auf chrome://device-log/ gehen, die Schaltfläche Löschen drücken, deinen Stadia-Controller anschließen und dann auf Aktualisieren klicken. Sie erhalten dieselben Informationen.

Die Debug-Benutzeroberfläche chrome://device-log mit Informationen zum angeschlossenen Stadia-Controller

Eine weitere Alternative ist das Tool HID Explorer, mit dem Sie noch mehr Details zu den mit Ihrem Computer verbundenen HID-Geräten sehen können.

Mit diesen beiden IDs, vendorId und productId, können Sie die Auswahl in der Auswahl treffen, indem Sie jetzt richtig nach dem richtigen WebHID-Gerät filtern.

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

Jetzt ist das Rauschen von allen nicht zugehörigen Geräten verschwunden und nur der Stadia-Controller wird angezeigt.

In der Geräteauswahl der WebHID API wird nur der Stadia-Controller angezeigt.

Öffnen Sie als Nächstes die HIDDevice, indem Sie die Methode open() aufrufen.

await stadiaController.open();

Wenn Sie HIDDevice noch einmal erfassen, wird das Flag opened auf true gesetzt.

Die Chrome DevTools-Konsole mit der Ausgabe des Loggings des HIDDevice-Objekts nach dem Öffnen

Wenn das Gerät geöffnet ist, können Sie mit einem Event-Listener auf eingehende inputreport-Ereignisse warten.

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

Wenn du die Assistant-Taste auf dem Controller drückst und loslässt, werden zwei Ereignisse in der Console protokolliert. Sie können sie sich als Ereignisse vom Typ „Assistant-Taste gedrückt“ und „Assistant-Taste losgelassen“ vorstellen. Abgesehen von der timeStamp sind die beiden Ereignisse auf den ersten Blick nicht zu unterscheiden.

Die Chrome-Entwicklertools-Konsole, in der HIDInputReportEvent-Objekte protokolliert werden

Die Eigenschaft reportId der HIDInputReportEvent-Schnittstelle gibt das ein Byte lange Identifikationspräfix für diesen Bericht zurück oder 0, wenn die HID-Schnittstelle keine Berichts-IDs verwendet. In diesem Fall ist das 3. Das Secret befindet sich in der Eigenschaft data, die als DataView mit einer Größe von 10 dargestellt wird. Eine DataView bietet eine Low-Level-Schnittstelle zum Lesen und Schreiben mehrerer Zahlentypen in einem binären ArrayBuffer. Sie können diese Darstellung übersichtlicher gestalten, indem Sie aus dem ArrayBuffer ein Uint8Array erstellen. So sehen Sie die einzelnen 8‑Bit-Ganzzahlen ohne Vorzeichen.

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

Wenn Sie die Ereignisdaten des Eingabeberichts dann noch einmal protokollieren, ergibt das Ganze mehr Sinn und die Ereignisse „Assistant-Schaltfläche gedrückt“ und „Assistant-Schaltfläche gedrückt“ lassen sich besser entschlüsseln. Die erste Ganzzahl (8 in beiden Ereignissen) scheint sich auf das Drücken von Schaltflächen zu beziehen und die zweite Ganzzahl (2 und 0) darauf, ob die Schaltfläche Assistant gedrückt wurde oder nicht.

Die Chrome DevTools-Konsole, in der für jedes HIDInputReportEvent Uint8Array-Objekte protokolliert werden

Wenn Sie die Schaltfläche Aufzeichnen anstelle der Schaltfläche Assistant drücken, sehen Sie, dass die zweite Ganzzahl von 1 zu 0 wechselt, wenn die Taste gedrückt oder losgelassen wird. So können Sie einen sehr einfachen „Treiber“ schreiben, mit dem Sie die beiden fehlenden Schaltflächen verwenden können.

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

Mit einem solchen Ansatz der Rückwärtsentwicklung können Sie Taste für Taste und Achse für Achse herausfinden, wie Sie mit dem Stadia-Controller über WebHID kommunizieren. Sobald Sie den Dreh raus haben, ist der Rest fast mechanische Ganzzahlzuordnung.

Was jetzt noch fehlt, ist die einfache Verbindung, die die Gamepad API bietet. Aus Sicherheitsgründen müssen Sie die Auswahl immer einmal durchlaufen, um mit einem WebHID-Gerät wie dem Stadia-Controller arbeiten zu können. Für zukünftige Verbindungen können Sie jedoch eine Verbindung zu bekannten Geräten herstellen. Rufen Sie dazu die Methode getDevices() auf.

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

Demo

In einer Demo, die ich erstellt habe, sehen Sie den Stadia-Controller, der gemeinsam von der Gamepad API und der WebHID API gesteuert wird. Sehen Sie sich auch den Quellcode an, der auf den Snippets aus diesem Artikel basiert. Der Einfachheit halber zeige ich nur die Tasten A, B, X und Y (von der Gamepad API gesteuert) sowie die Tasten Assistant und Capture (von der WebHID API gesteuert). Unter dem Bild des Controllers sehen Sie die Rohdaten von WebHID. So können Sie sich ein Bild von allen Tasten und Achsen des Controllers machen.

Die Demo-App unter https://stadia-controller-webhid-gamepad.glitch.me/, in der die Tasten „A“, „B“, „X“ und „Y“ über die Gamepad API und die Assistant- und die Aufnahmetaste über die WebHID API gesteuert werden.

Schlussfolgerungen

Dank der neuen Firmware kann der Stadia-Controller jetzt als Standard-Gamepad mit 17 Tasten verwendet werden. Das ist in den meisten Fällen mehr als genug, um gängige Webspiele zu steuern. Wenn Sie aus irgendeinem Grund Daten von allen 19 Tasten des Controllers benötigen, können Sie mit WebHID auf Low-Level-Eingabeberichte zugreifen, die Sie einzeln durch Reverse-Engineering entschlüsseln können. Wenn Sie nach dem Lesen dieses Artikels einen vollständigen WebHID-Treiber schreiben, kontaktieren Sie mich bitte. Ich verlinke Ihr Projekt dann hier. Viel Spaß mit WebHID!

Danksagungen

Dieser Artikel wurde von François Beaufort überprüft.