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

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

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

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

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

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

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

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

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

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

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

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

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

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

Альтернативой описанному выше процессу является переход к chrome://device-log/ в строке URL-адреса, нажатие кнопки «Очистить» , подключение контроллера 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 ), похоже, связано с тем, нажата ли кнопка Assistant или нет.

Консоль 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. Как только вы освоите это, все остальное станет почти механической работой по отображению целых чисел.

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

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

Демо

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

Демонстрационное приложение по адресу https://stadia-controller-webhid-gamepad.glitch.me/, показывающее кнопки A, B, X и Y, управляемые API геймпада, а также кнопки «Помощник» и «Захват», управляемые ВебХИД API.

Выводы

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

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

Эту статью рецензировал Франсуа Бофор .