通过 WebHID 与 Stadia 控制器通信

刷写后的 Stadia 控制器的行为类似于标准游戏手柄,这意味着无法使用 Gamepad API 访问其所有按钮。借助 WebHID,您现在可以访问缺少的按钮。

自 Stadia 停用以来,许多人担心控制器最终会成为垃圾填埋场的一堆废弃硬件。幸运的是,Stadia 团队决定改为提供自定义固件,以便您前往 Stadia 蓝牙模式页面,在控制器上刷写该固件,从而解锁 Stadia 控制器。这样,Stadia 控制器就会显示为标准游戏手柄,您可以通过 USB 线或蓝牙无线连接到它。Stadia Bluetooth 页面本身使用 WebHIDWebUSB,并荣登 Project Fugu API 展示平台,但这不是本文的主题。在本文中,我想介绍如何通过 WebHID 与 Stadia 控制器通信。

将 Stadia 控制器用作标准游戏手柄

刷写后,控制器会显示为操作系统的标准游戏手柄。如需了解标准游戏手柄上的常见按钮和轴排列方式,请参阅以下屏幕截图。如 Gamepad API 规范中所定义,标准游戏手柄的按钮从 0 到 16,因此总共有 17 个按钮(十字键算作 4 个按钮)。如果您在游戏手柄测试工具演示版中试用 Stadia 控制器,会发现它非常顺畅。

标准游戏手柄的架构,其中标注了各种轴和按钮。

不过,如果您数 Stadia 控制器上的按钮,会发现有 19 个。如果您在游戏手柄测试工具中有条不紊地逐个尝试这些按钮,就会发现Google 助理截图按钮不起作用。即使游戏手柄规范中定义的游戏手柄 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 对象记录到控制台。这会显示 Stadia 控制器的 productId37888,十六进制为 0x9400)和 vendorId6353,十六进制为 0x18d1)。如果您在官方 USB 供应商 ID 表中查找 vendorID,会发现 6353 映射到您预期的值:Google Inc.

显示日志记录 HIDDevice 对象的输出的 Chrome DevTools 控制台。

上述流程的替代方法是,在网址栏中前往 chrome://device-log/,按 Clear 按钮,插入 Stadia 控制器,然后按 Refresh。这会为您提供相同的信息。

chrome://device-log 调试界面,显示已插入的 Stadia 控制器的相关信息。

另一种方法是使用 HID Explorer 工具,通过该工具,您可以探索与计算机连接的 HID 设备的更多详细信息。

现在,您可以使用这两个 ID(vendorIdproductId)正确过滤出正确的 WebHID 设备,从而优化选择器中显示的内容。

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

现在,所有不相关设备的噪声都消失了,只显示 Stadia 控制器。

仅显示 Stadia 控制器的 WebHID API 设备选择器。

接下来,通过调用 open() 方法打开 HIDDevice

await stadiaController.open();

再次记录 HIDDevice,并将 opened 标志设置为 true

Chrome 开发者工具控制台,其中显示了打开 HIDDevice 对象后对其进行日志记录的输出。

打开设备后,通过附加事件监听器监听传入的 inputreport 事件。

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

当您按下并松开控制器上的 Google 助理按钮时,控制台会记录两个事件。您可以将其视为“Google 助理按钮按下”和“Google 助理按钮松开”事件。除了 timeStamp 之外,这两个事件乍一看似乎没有区别。

Chrome 开发者工具控制台,其中显示了正在记录的 HIDInputReportEvent 对象。

HIDInputReportEvent 接口的 reportId 属性会返回此报告的一字节标识符前缀,如果 HID 接口不使用报告 ID,则返回 0。在本例中,该区域为 3。该密钥位于 data 属性中,该属性表示为大小为 10 的 DataViewDataView 提供了一个低级接口,用于在二进制 ArrayBuffer 中读取和写入多种数字类型。如需从此表示法中获取更易于理解的内容,可以从 ArrayBuffer 创建 Uint8Array,以便查看各个 8 位无符号整数。

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

然后,当您再次记录输入报告事件数据时,情况会变得更加清晰,并且“Google 助理按钮按下”和“Google 助理按钮松开”事件会变得易于解读。第一个整数(这两个事件中的 8)似乎与按钮按下相关,而第二个整数(20)似乎与 Google 助理按钮是否已按下相关。

Chrome 开发者工具控制台,显示为每个 HIDInputReportEvent 记录的 Uint8Array 对象。

Capture 按钮(而非 Assistant 按钮),您会看到第二个整数在按下按钮时从 1 切换为 0,在松开按钮时再切换回 1。这样,您就可以编写一个非常简单的“驱动程序”,以便使用缺少的两个按钮。

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

通过这种反向工程方法,您可以按按钮和轴逐一了解如何使用 WebHID 与 Stadia 控制器通信。一旦掌握了诀窍,其余工作几乎就是机械性的整数映射工作。

现在缺少的一点是 Gamepad API 提供的流畅连接体验。虽然出于安全考虑,您始终需要先完成一次初始选择器体验,才能使用 Stadia 控制器等 WebHID 设备,但在日后的连接中,您可以重新连接已知设备。为此,请调用 getDevices() 方法。

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

演示

您可以在我构建的演示版中看到由 Gamepad API 和 WebHID API 共同控制的 Stadia 控制器。请务必查看源代码,该代码基于本文中的代码段构建而成。为简单起见,我只会显示 ABXY 按钮(由 Gamepad API 控制),以及 AssistantCapture 按钮(由 WebHID API 控制)。在控制器图片下方,您可以看到原始 WebHID 数据,以便了解控制器上的所有按钮和轴。

演示应用 (https://stadia-controller-webhid-gamepad.glitch.me/),其中显示 A、B、X 和 Y 按钮由 Gamepad API 控制,而 Google 助理和“拍摄”按钮由 WebHID API 控制。

总结

借助新固件,Stadia 控制器现在可用作具有 17 个按钮的标准游戏手柄,在大多数情况下,这些按钮足以控制常见的网页游戏。如果您出于任何原因需要控制器上所有 19 个按钮的数据,WebHID 可让您访问低级输入报告,您可以通过逐个逆向工程来解读这些报告。如果您在阅读本文后恰巧编写了完整的 WebHID 驱动程序,请务必与我联系,我很乐意在此处链接您的项目。祝您 WebHIDing 愉快!

致谢

本文由 François Beaufort 审核。