Разговор с контроллером Stadia через WebHID

Контроллер Stadia с подсветкой работает как стандартный геймпад, поэтому не все его кнопки доступны через API Gamepad. С помощью WebHID вы можете получить доступ к отсутствующим кнопкам.

С момента закрытия Stadia многие опасались, что контроллер окажется бесполезным железом на свалке. К счастью, команда Stadia решила вместо этого открыть контроллер Stadia, предоставив специальную прошивку, которую вы можете прошить на своем контроллере, перейдя на страницу режима Bluetooth Stadia . Это сделает ваш контроллер Stadia стандартным геймпадом, к которому можно подключиться по USB-кабелю или по беспроводной связи через Bluetooth. Сама страница Stadia Bluetooth, с гордостью представленная на Project Fugu API Showcase , использует WebHID и WebUSB , но это не тема этой статьи. В этой публикации я хочу объяснить, как можно общаться с контроллером Stadia через WebHID.

Контроллер Stadia как стандартный геймпад

После прошивки контроллер отображается в операционной системе как стандартный геймпад . На следующем снимке экрана показано расположение кнопок и осей на стандартном геймпаде. Согласно спецификации API геймпада , стандартные геймпады имеют кнопки с номерами от 0 до 16, то есть всего 17 (крестовина считается четырьмя кнопками). Если вы попробуете контроллер Stadia в демоверсии тестера геймпадов , вы заметите, что он работает просто идеально.

Схема стандартного геймпада с обозначенными различными осями и кнопками.

Однако, если посчитать кнопки на контроллере Stadia, их 19. Если вы будете систематически нажимать их одну за другой в тестере геймпадов, то обнаружите, что кнопки Assistant и Capture не работают. Даже если атрибут buttons геймпада, определённый в спецификации геймпада, не имеет ограничений, поскольку контроллер Stadia отображается как стандартный геймпад, назначены только кнопки с 0 по 16. Вы по-прежнему можете использовать остальные кнопки, но большинство игр не ожидают их наличия.

WebHID спешит на помощь

Благодаря API WebHID вы можете получить доступ к отсутствующим кнопкам 17 и 18. А при желании можно даже получить данные обо всех остальных кнопках и осях, которые уже доступны через API Gamepad. Первый шаг — выяснить, как контроллер Stadia сообщает о себе операционной системе. Один из способов сделать это — открыть консоль Chrome DevTools на любой случайной странице и запросить неотфильтрованный список устройств из API WebHID. Затем вы вручную выбираете контроллер Stadia для дальнейшего анализа. Чтобы получить неотфильтрованный список устройств, просто передайте пустой массив параметров filters .

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

В меню выбора предпоследняя запись выглядит как контроллер Stadia.

Средство выбора устройств API WebHID, на котором показаны некоторые несвязанные устройства и контроллер Stadia в предпоследней позиции.

Выбрав устройство «Stadia Controller rev. A», зарегистрируйте полученный объект HIDDevice в консоли. В результате будут отображены productId контроллера Stadia ( 37888 , что соответствует шестнадцатеричному коду 0x9400 ) и vendorId ( 6353 , что соответствует шестнадцатеричному коду 0x18d1 ). Если вы посмотрите vendorID в официальной таблице идентификаторов производителей USB-устройств , то увидите, что 6353 соответствует ожидаемому значению: Google Inc.

Консоль Chrome DevTools, показывающая выходные данные регистрации объекта HIDDevice.

Альтернативой описанному выше процессу является переход по адресу chrome://device-log/ в адресной строке, нажатие кнопки «Очистить» , подключение контроллера Stadia и нажатие кнопки «Обновить» . Это позволит получить ту же информацию.

Интерфейс отладки chrome://device-log, отображающий информацию о подключенном контроллере Stadia.

Еще одной альтернативой является использование инструмента HID Explorer , который позволяет вам изучить еще больше деталей об устройствах HID, подключенных к вашему компьютеру.

Используйте эти два идентификатора, vendorId и productId , чтобы уточнить то, что отображается в средстве выбора, путем правильной фильтрации для нужного устройства WebHID.

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

Теперь шум от всех посторонних устройств исчез, и виден только контроллер Stadia.

Средство выбора устройств API WebHID показывает только контроллер Stadia.

Далее откройте HIDDevice , вызвав метод open() .

await stadiaController.open();

Снова зарегистрируйте HIDDevice , и флагу opened будет присвоено значение true .

Консоль Chrome DevTools, показывающая выходные данные регистрации объекта HIDDevice после его открытия.

При открытом устройстве прослушивайте входящие события inputreport , подключив прослушиватель событий.

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

Когда вы нажимаете и отпускаете кнопку Ассистента на контроллере, в консоли регистрируются два события. Их можно представить как «Нажатие кнопки Ассистента » и «Отпускание кнопки Ассистента ». За исключением timeStamp , эти два события на первый взгляд неразличимы.

Консоль Chrome DevTools, на которой показаны регистрируемые объекты HIDInputReportEvent.

Свойство reportId интерфейса HIDInputReportEvent возвращает однобайтовый идентификационный префикс для данного отчёта или 0 , если интерфейс HID не использует идентификаторы отчётов. В данном случае это 3 Секрет кроется в свойстве data , которое представлено как объект DataView размером 10. DataView предоставляет низкоуровневый интерфейс для чтения и записи нескольких числовых типов в двоичном ArrayBuffer . Чтобы сделать это представление более удобочитаемым, можно создать массив Uint8Array из ArrayBuffer , чтобы можно было видеть отдельные 8-битные беззнаковые целые числа.

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

При повторном входе данных отчёта о событиях всё начинает проясняться, и события «Нажатие кнопки Ассистента » и «Отпускание кнопки Ассистента » становятся более понятными. Первое целое число ( 8 в обоих событиях), по-видимому, связано с нажатиями кнопок, а второе целое число ( 2 и 0 ), по-видимому, связано с тем, нажата ли кнопка Ассистента .

Консоль Chrome DevTools, показывающая объекты Uint8Array, регистрируемые для каждого HIDInputReportEvent.

Нажмите кнопку «Захват» вместо кнопки «Помощник» , и вы увидите, что второе целое число переключается с 1 при нажатии кнопки на 0 при её отпускании. Это позволяет написать очень простой «драйвер», который позволит использовать две недостающие кнопки.

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

Используя подобный подход обратного проектирования, вы можете, кнопка за кнопкой и ось за осью, разобраться, как взаимодействовать с контроллером Stadia через WebHID. Как только вы освоитесь, всё остальное будет практически механической работой по целочисленному сопоставлению.

Единственное, чего сейчас не хватает, — это плавного подключения, которое обеспечивает API Gamepad. Хотя из соображений безопасности вам всегда необходимо пройти первоначальный этап выбора для работы с устройством WebHID, таким как контроллер Stadia, для последующих подключений вы можете повторно подключаться к известным устройствам. Для этого вызовите метод getDevices() .

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

Демо

В созданной мной демонстрации вы можете увидеть контроллер Stadia, управляемый совместно API Gamepad и API WebHID. Обязательно ознакомьтесь с исходным кодом , который основан на фрагментах из этой статьи. Для простоты я показываю только кнопки A , B , X и Y (управляемые API Gamepad), а также кнопки Assistant и Capture (управляемые API WebHID). Под изображением контроллера вы видите необработанные данные WebHID, чтобы вы могли понять назначение всех кнопок и осей на контроллере.

Демонстрационное приложение контроллера Stadia, в котором показано, как кнопки A, B, X и Y управляются API Gamepad, а кнопки Assistant и Capture управляются API WebHID.

Выводы

Благодаря новой прошивке контроллер Stadia теперь можно использовать как стандартный геймпад с 17 кнопками, чего в большинстве случаев более чем достаточно для управления обычными веб-играми. Если по какой-либо причине вам нужны данные со всех 19 кнопок контроллера, WebHID позволяет получить доступ к низкоуровневым отчётам о вводе, которые можно расшифровать, проведя обратную разработку каждого из них по отдельности. Если после прочтения этой статьи вам удастся написать полноценный драйвер WebHID, обязательно свяжитесь со мной, и я с радостью размещу здесь ссылку на ваш проект. Удачного WebHID-а!

Благодарности

Рецензент этой статьи — Франсуа Бофор .