Comunicazione con il controller Stadia tramite WebHID

Il controller Stadia flashato si comporta come un gamepad standard, il che significa che non tutti i suoi pulsanti sono accessibili tramite l'API Gamepad. Con WebHID, ora puoi accedere ai pulsanti mancanti.

Dopo la chiusura di Stadia, molti temevano che il controller sarebbe finito come un pezzo di hardware inutile nella discarica. Fortunatamente, il team di Stadia ha deciso di aprire il controller Stadia fornendo un firmware personalizzato che puoi installare sul controller visitando la pagina della modalità Bluetooth di Stadia. In questo modo, il controller Stadia viene visualizzato come un gamepad standard che puoi connettere tramite cavo USB o in modalità wireless tramite Bluetooth. La pagina Bluetooth di Stadia, orgogliosamente presente nella vetrina delle API di Project Fugu, utilizza WebHID e WebUSB, ma questo non è l'argomento di questo articolo. In questo post, voglio spiegare come puoi comunicare con il controller Stadia tramite WebHID.

Il controller Stadia come gamepad standard

Dopo il flashing, il controller viene visualizzato come un gamepad standard per il sistema operativo. Consulta lo screenshot seguente per una disposizione comune di pulsanti e assi su un gamepad standard. Come definito nella specifica dell'API Gamepad, i gamepad standard hanno pulsanti da 0 a 16, quindi 17 in totale (il D-pad conta come quattro pulsanti). Se provi il controller Stadia nella demo del tester del gamepad, noterai che funziona alla perfezione.

Uno schema di un gamepad standard con i vari assi e pulsanti etichettati.

Tuttavia, se conti i pulsanti sul controller Stadia, sono 19. Se li provi sistematicamente uno alla volta nel tester del gamepad, ti accorgerai che i pulsanti Assistente e Acquisizione non funzionano. Anche se l'attributo gamepad buttons definito nella specifica del gamepad è aperto, poiché il controller Stadia viene visualizzato come un gamepad standard, vengono mappati solo i pulsanti da 0 a 16. Puoi comunque utilizzare gli altri pulsanti, ma la maggior parte dei giochi non si aspetta che esistano.

WebHID in soccorso

Grazie all'API WebHID, puoi comunicare con i pulsanti mancanti 17 e 18. Se vuoi, puoi anche ottenere dati su tutti gli altri pulsanti e assi già disponibili tramite l'API Gamepad. Il primo passaggio consiste nello scoprire come il controller Stadia si presenta al sistema operativo. Un modo per farlo è aprire la console Chrome DevTools in una pagina casuale e richiedere un elenco non filtrato di dispositivi dall'API WebHID. Quindi scegli manualmente il controller Stadia per un'ulteriore ispezione. Per ottenere un elenco non filtrato di dispositivi, è sufficiente passare un array di opzioni filters vuoto.

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

Nel selettore, la penultima voce assomiglia al controller Stadia.

Il selettore di dispositivi dell'API WebHID che mostra alcuni dispositivi non correlati e il controller Stadia in penultima posizione.

Dopo aver selezionato il dispositivo "Stadia Controller rev. A", registra l'oggetto HIDDevice risultante nella console. Vengono visualizzati l'productId (37888, ovvero 0x9400 in esadecimale) e l'vendorId (6353, ovvero 0x18d1 in esadecimale) del controller Stadia. Se cerchi vendorID nella tabella ufficiale degli ID fornitore USB, vedrai che 6353 corrisponde a quello che ti aspetteresti: Google Inc..

La console Chrome DevTools che mostra l'output della registrazione dell'oggetto HIDDevice.

Un'alternativa al flusso descritto sopra è andare su chrome://device-log/ nella barra degli URL, premere il pulsante Cancella, collegare il controller Stadia e poi premere Aggiorna. In questo modo, avrai le stesse informazioni.

L'interfaccia di debug chrome://device-log che mostra informazioni sul controller Stadia collegato.

Un'altra alternativa è l'utilizzo dello strumento HID Explorer, che ti consente di esplorare ancora più dettagli dei dispositivi HID connessi al computer.

Utilizza questi due ID, vendorId e productId, per perfezionare ciò che viene mostrato nel selettore filtrando correttamente il dispositivo WebHID giusto.

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

Ora il rumore di tutti i dispositivi non correlati è scomparso e viene visualizzato solo il controller Stadia.

Il selettore di dispositivi dell'API WebHID che mostra solo il controller Stadia.

Dopodiché, apri HIDDevice chiamando il metodo open().

await stadiaController.open();

Registra di nuovo HIDDevice e il flag opened è impostato su true.

La console Chrome DevTools che mostra l'output della registrazione dell'oggetto HIDDevice dopo l'apertura.

Con il dispositivo aperto, ascolta gli eventi inputreport in entrata collegando un listener di eventi.

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

Quando premi e rilasci il pulsante Assistente Google sul controller, vengono registrati due eventi nella console. Puoi considerarli come eventi "Pulsante Assistente Google premuto" e "Pulsante Assistente Google rilasciato". A parte timeStamp, i due eventi sembrano indistinguibili a prima vista.

La console Chrome DevTools che mostra gli oggetti HIDInputReportEvent registrati.

La proprietà reportId dell'interfaccia HIDInputReportEvent restituisce il prefisso di identificazione di un byte per questo report oppure 0 se l'interfaccia HID non utilizza gli ID report. In questo caso è 3. Il segreto sta nella proprietà data, che è rappresentata come un DataView di dimensione 10. Un DataView fornisce un'interfaccia di basso livello per la lettura e la scrittura di più tipi di numeri in un ArrayBuffer binario. Per ottenere una rappresentazione più comprensibile, puoi creare un Uint8Array a partire da ArrayBuffer, in modo da visualizzare i singoli numeri interi senza segno a 8 bit.

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

Quando registri di nuovo i dati degli eventi del report di input, le cose iniziano ad avere più senso e gli eventi "Pulsante Assistente premuto" e "Pulsante Assistente rilasciato" iniziano a diventare decifrabili. Il primo numero intero (8 in entrambi gli eventi) sembra essere correlato alle pressioni dei pulsanti, mentre il secondo numero intero (2 e 0) sembra essere correlato alla pressione o meno del pulsante Assistente.

La console Chrome DevTools che mostra gli oggetti Uint8Array registrati per ogni HIDInputReportEvent.

Premi il pulsante Acquisizione anziché il pulsante Assistente e vedrai che il secondo numero intero passa da 1 quando il pulsante viene premuto a 0 quando viene rilasciato. In questo modo puoi scrivere un "driver" molto semplice che ti consente di utilizzare i due pulsanti mancanti.

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

Utilizzando un approccio di reverse engineering come questo, puoi capire, pulsante per pulsante e asse per asse, come comunicare con il controller Stadia tramite WebHID. Una volta presa la mano, il resto è quasi un lavoro meccanico di mappatura degli interi.

L'unica cosa che manca ora è l'esperienza di connessione fluida offerta dall'API Gamepad. Per motivi di sicurezza, devi sempre eseguire la procedura di selezione iniziale una volta per poter utilizzare un dispositivo WebHID come il controller Stadia. Per le connessioni future, puoi riconnetterti ai dispositivi noti. Per farlo, chiama il metodo getDevices().

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

Demo

Puoi vedere il controller Stadia controllato congiuntamente dall'API Gamepad e dall'API WebHID in una demo che ho creato. Assicurati di consultare il codice sorgente, che si basa sugli snippet di questo articolo. Per semplicità, mostro solo i pulsanti A, B, X e Y (controllati dall'API Gamepad) e i pulsanti Assistente e Acquisizione (controllati dall'API WebHID). Sotto l'immagine del controller, puoi vedere i dati WebHID non elaborati, in modo da farti un'idea di tutti i pulsanti e gli assi del controller.

L'app demo del controller Stadia che mostra i pulsanti A, B, X e Y controllati dall'API Gamepad e i pulsanti Assistente e Acquisisci controllati dall'API WebHID.

Conclusioni

Grazie al nuovo firmware, il controller Stadia ora può essere utilizzato come gamepad standard con 17 pulsanti, che nella maggior parte dei casi sono più che sufficienti per controllare i giochi web più comuni. Se, per qualsiasi motivo, hai bisogno dei dati di tutti i 19 pulsanti del controller, WebHID ti consente di accedere a report di input di basso livello che puoi decifrare mediante il reverse engineering uno alla volta. Se dovessi scrivere un driver WebHID completo dopo aver letto questo articolo, non esitare a contattarmi e sarò felice di inserire un link al tuo progetto qui. Buon WebHIDing!

Ringraziamenti

Questo articolo è stato rivisto da François Beaufort.