Comunicazione con il controller Stadia tramite WebHID

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

Dal momento che Stadia è stato chiuso, molti temevano che il controller finisse come hardware inutile in discarica. Fortunatamente, il team di Stadia ha deciso di aprire il controller Stadia fornendo un firmware personalizzato che puoi installare sul controller dalla pagina Modalità Bluetooth di Stadia. In questo modo, il controller Stadia viene visualizzato come un gamepad standard a cui puoi collegarti tramite cavo USB o in modalità wireless tramite Bluetooth. Orgogliosamente in evidenza nella sezione di presentazione delle API di Project Fugu, la pagina Bluetooth di Stadia stessa utilizza WebHID e WebUSB, ma questo non è l'argomento di questo articolo. In questo post, voglio spiegarti come puoi comunicare con il controller Stadia tramite WebHID.

Il controller Stadia come gamepad standard

Dopo il flashing, il controller viene visualizzato come gamepad standard per il sistema operativo. Guarda 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 perfettamente.

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

Tuttavia, se conteggiate i pulsanti sul controller Stadia, sono 19. Se li provi sistematicamente uno per uno nel tester del gamepad, ti accorgerai che i pulsanti Assistente e Acquisisci non funzionano. Anche se l'attributo buttons del gamepad come 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 li prevede.

WebHID accorre in soccorso

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

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

Nel selettore, la penultima voce ha l'aspetto del controller Stadia.

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

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

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

Un'alternativa alla procedura descritta sopra è andare su chrome://device-log/ nella barra degli URL, premere il pulsante Cancella, collegare il controller Stadia e 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 è lo strumento HID Explorer, che ti consente di esaminare 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 in base al dispositivo WebHID corretto.

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.

A questo punto, 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 averlo aperto.

Con il dispositivo aperto, ascolta gli eventi inputreport in arrivo collegando un ascoltatore di eventi.

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

Quando premi e rilasci il pulsante Assistente sul controller, nella console vengono registrati due eventi. Puoi considerarli come eventi "Pulsante Assistente premuto" e "Pulsante Assistente 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 o 0 se l'interfaccia HID non utilizza gli ID report. In questo caso è 3. Il segreto si trova nella proprietà data, che è rappresentata come un DataView di 10 caratteri. 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 qualcosa di più comprensibile da questa rappresentazione, crea un Uint8Array dal ArrayBuffer, in modo da poter vedere i singoli numeri interi non firmati a 8 bit.

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

Quando registri di nuovo i dati sugli eventi del report di input, le cose iniziano ad avere più senso e gli eventi "Pulsante Assistente premuto" e "Pulsante Assistente rilasciato" iniziano a essere 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 al fatto che il pulsante Assistente sia premuto o meno.

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

Premi il pulsante Acquisisci 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 con WebHID. Una volta capito il funzionamento, il resto è quasi un lavoro meccanico di mappatura di interi.

L'unica cosa che manca ora è l'esperienza di connessione fluida offerta dall'API Gamepad. Per motivi di sicurezza, devi sempre completare l'esperienza iniziale del selettore una volta per poter utilizzare un dispositivo WebHID come il controller Stadia, ma per le connessioni future puoi riconnetterti ai dispositivi noti. A tal fine, 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 controllare 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 Acquisisci (controllati dall'API WebHID). Sotto l'immagine del controller, puoi vedere i dati non elaborati di WebHID, in modo da avere un'idea di tutti i pulsanti e gli assi del controller.

L'app di dimostrazione all'indirizzo https://stadia-controller-webhid-gamepad.glitch.me/ 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 è utilizzabile 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 e 19 i pulsanti del controller, WebHID ti consente di accedere a report di input a basso livello che puoi decifrare eseguendo il reverse engineering uno per uno. Se scrivi un driver WebHID completo dopo aver letto questo articolo, non esitare a contattarmi e sarò felice di collegare il tuo progetto qui. Buon WebHID!

Ringraziamenti

Questo articolo è stato esaminato da François Beaufort.