Über WebHID mit dem Stadia-Controller kommunizieren

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

Seit der Schließung von Stadia befürchteten viele, dass der Controller als nutzloses Stück Hardware auf der Mülldeponie landen würde. Glücklicherweise hat das Stadia-Team beschlossen, den Stadia-Controller stattdessen zu öffnen, indem es eine benutzerdefinierte Firmware bereitstellt, die Sie auf Ihren Controller flashen können, indem Sie die Seite Stadia-Bluetooth-Modus aufrufen. Dadurch wird Ihr Stadia-Controller als Standard-Gamepad angezeigt, das Sie über ein USB-Kabel oder kabellos über Bluetooth verbinden können. Die Stadia Bluetooth-Seite wird auf der Project Fugu API Showcase-Seite vorgestellt und verwendet WebHID und WebUSB. Das 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.

Der Stadia-Controller als Standard-Gamepad

Nach dem Flashen wird der Controller dem Betriebssystem als Standard-Gamepad angezeigt. Der folgende Screenshot zeigt eine gängige Anordnung von Tasten und Achsen auf einem Standard-Gamepad. Wie in der Gamepad API-Spezifikation definiert, haben Standard-Gamepads Tasten von 0 bis 16, also insgesamt 17 (das Steuerkreuz zählt als vier Tasten). Wenn Sie den Stadia-Controller mit der Gamepad-Tester-Demo ausprobieren, werden Sie feststellen, dass er einwandfrei funktioniert.

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

Wenn man die Tasten auf dem Stadia-Controller zählt, sind es jedoch 19. Wenn du sie im Controller-Tester systematisch einzeln ausprobierst, wirst du feststellen, dass die Assistant- und die Aufnahme-Taste nicht funktionieren. Auch wenn das buttons-Attribut des Gamepads gemäß der Gamepad-Spezifikation offen ist, werden nur die Tasten 0–16 zugeordnet, da der Stadia-Controller als Standard-Gamepad erkannt wird. Sie können die anderen Tasten weiterhin verwenden, aber die meisten Spiele sind nicht für diese Tasten ausgelegt.

WebHID als Lösung

Dank der WebHID API können Sie die fehlenden Tasten 17 und 18 ansprechen. Und wenn Sie möchten, können Sie sogar Daten zu allen anderen Schaltflächen und Achsen abrufen, die bereits über die Gamepad API verfügbar sind. Der erste Schritt besteht darin, herauszufinden, wie sich der Stadia-Controller beim Betriebssystem meldet. Eine Möglichkeit dazu ist, die Chrome-Entwicklertools auf einer beliebigen Seite zu öffnen und über die WebHID API eine ungefilterte Liste der Geräte anzufordern. Anschließend wählen Sie den Stadia-Controller manuell für die weitere Überprüfung aus. Wenn Sie ein leeres filters-Optionsarray übergeben, erhalten Sie eine ungefilterte Liste von Geräten.

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

Im Picker sieht der vorletzte Eintrag wie der Stadia-Controller aus.

Die Geräteauswahl der WebHID API zeigt einige nicht zugehörige Geräte und den Stadia-Controller an vorletzter Position.

Nachdem Sie das Gerät „Stadia Controller rev. A“ ausgewählt haben, protokollieren Sie das resultierende HIDDevice-Objekt in der Konsole. Dadurch werden die productId (37888, was 0x9400 in Hexadezimal ist) und vendorId (6353, was 0x18d1 in Hexadezimal ist) des Stadia-Controllers angezeigt. Wenn Sie die vendorID in der offiziellen USB-Anbieter-ID-Tabelle nachschlagen, sehen Sie, dass 6353 der erwarteten ID entspricht: Google Inc..

Die Chrome-Entwicklertools-Konsole mit der Ausgabe des HIDDevice-Objekts.

Alternativ zum oben beschriebenen Ablauf kannst du in der URL-Leiste zu chrome://device-log/ navigieren, auf die Schaltfläche Löschen klicken, deinen Stadia Controller anschließen und dann auf Aktualisieren klicken. Sie erhalten dieselben Informationen.

Die Debugging-Oberflä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 aufrufen können.

Verwenden Sie diese beiden IDs, die vendorId und die productId, um die Auswahl zu verfeinern, indem Sie jetzt korrekt nach dem richtigen WebHID-Gerät filtern.

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

Die Störgeräusche von allen nicht zugehörigen Geräten sind jetzt verschwunden und nur der Stadia-Controller wird angezeigt.

Die Geräteauswahl der WebHID API, in der nur der Stadia-Controller angezeigt wird.

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

await stadiaController.open();

Protokollieren Sie HIDDevice noch einmal. Das Flag opened ist auf true gesetzt.

Die Chrome-Entwicklertools-Konsole mit der Ausgabe des HIDDevice-Objekts nach dem Öffnen.

Wenn das Gerät geöffnet ist, können Sie auf eingehende inputreport-Ereignisse warten, indem Sie einen Event-Listener anhängen.

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

Wenn Sie die Assistant-Taste auf dem Controller drücken und loslassen, 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 sehen die beiden Ereignisse auf den ersten Blick identisch aus.

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

Die reportId-Eigenschaft der HIDInputReportEvent-Schnittstelle gibt das Ein-Byte-Identifikationspräfix für diesen Bericht zurück oder 0, wenn die HID-Schnittstelle keine Berichts-IDs verwendet. In diesem Fall ist es 3. Das Secret befindet sich in der Eigenschaft data, die als DataView der Größe 10 dargestellt wird. Ein DataView bietet eine Low-Level-Schnittstelle zum Lesen und Schreiben mehrerer Zahlentypen in einem binären ArrayBuffer. Um die Darstellung verständlicher zu machen, können Sie aus dem ArrayBuffer ein Uint8Array erstellen, damit die einzelnen vorzeichenlosen 8‑Bit-Ganzzahlen angezeigt werden.

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

Wenn Sie die Ereignisdaten des Eingabeberichts noch einmal protokollieren, wird die Sache klarer und die Ereignisse „Assistant button down“ (Assistant-Taste gedrückt) und „Assistant button up“ (Assistant-Taste losgelassen) werden nachvollziehbar. Die erste Ganzzahl (8 in beiden Ereignissen) scheint sich auf Tastendrücke zu beziehen und die zweite Ganzzahl (2 und 0) darauf, ob die Assistant-Taste gedrückt wird oder nicht.

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

Wenn Sie die Taste Aufnehmen anstelle der Taste Assistant drücken, sehen Sie, dass die zweite Ganzzahl von 1 auf 0 wechselt, wenn die Taste gedrückt wird, und wieder zurück, wenn sie losgelassen wird. So können Sie einen sehr einfachen „Treiber“ schreiben, mit dem Sie die beiden fehlenden Tasten nutzen 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 Reverse-Engineering-Ansatz können Sie Taste für Taste und Achse für Achse herausfinden, wie Sie mit WebHID mit dem Stadia-Controller kommunizieren. Wenn Sie den Dreh raus haben, ist der Rest fast nur noch mechanische Arbeit.

Was jetzt noch fehlt, ist die reibungslose Verbindung, die die Gamepad API bietet. Aus Sicherheitsgründen müssen Sie die erste Auswahl einmal durchlaufen, um ein WebHID-Gerät wie den Stadia-Controller zu verwenden. Bei zukünftigen 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, können Sie sehen, wie der Stadia Controller gemeinsam von der Gamepad API und der WebHID API gesteuert wird. Sehen Sie sich unbedingt den Quellcode an, der auf den Snippets aus diesem Artikel basiert. Der Einfachheit halber werden nur die Tasten A, B, X und Y (die von der Gamepad API gesteuert werden) sowie die Tasten Assistant und Capture (die von der WebHID API gesteuert werden) angezeigt. Unter dem Controller-Bild sehen Sie die Rohdaten von WebHID, damit Sie sich ein Bild von allen Tasten und Achsen des Controllers machen können.

Die Stadia-Controller-Demo-App, in der die A-, B-, X- und Y-Tasten über die Gamepad API und die Assistant- und Capture-Tasten über die WebHID API gesteuert werden.

Zusammenfassung

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 auf dem Controller 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 gerne hier. Viel Spaß mit WebHID!

Danksagungen

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