跳转到内容

能力 · API

WebHID API

发布于

navigator.hid.requestDevice() 弹出一个由浏览器托管的选择器,列出已连接且匹配所传过滤器的人机接口设备(HID),并以用户授权的那些 HIDDevice 对象兑现。设备打开后,输入报告以 inputreport 事件送达,输出报告与特征报告以字节缓冲区发送,PWA 因此能驱动宏键盘、游戏手柄、条码扫描器,或任何通过 USB 或蓝牙讲 HID 协议的实验仪器。

支持仅限桌面 Chromium:Windows、macOS、Linux 与 ChromeOS 上的 Chrome 89 和 Edge 89 实现了 HID、HIDDevice、requestDevice() 与 getDevices();Chrome 100 加入 HIDDevice.forget(),Chrome 131 在专用 worker 中暴露 navigator.hid(BCD api.HID、api.HID.worker_support)。Android 版 Chrome、Android WebView、Firefox 与 Safari 均无支持,WebKit 已提交正式的「反对」立场(standards-positions #510)。

navigator.hid.requestDevice(options)
navigator.hid.getDevices()
device.open()
device.close()
device.forget()
device.sendReport(reportId, data)
device.sendFeatureReport(reportId, data)
device.receiveFeatureReport(reportId)

requestDevice() 与 getDevices() 返回 Promise<sequence<HIDDevice>>;用户关闭选择器时 requestDevice() 以空序列兑现而不是拒绝。open()、close()、forget()、sendReport() 与 sendFeatureReport() 返回 Promise<undefined>;receiveFeatureReport() 返回 Promise<DataView>,其首字节可能是报告 ID。navigator.hid 标注为 [SecureContext],requestDevice() 还要求在带瞬时用户激活的 Window 全局对象中调用。每个 HIDDevice 暴露 opened、vendorId、productId、productName 与 collections,并触发 inputreport(HIDInputReportEvent,带 device、reportId、data);navigator.hid 对已授权设备触发 connect 与 disconnect(HIDConnectionEvent,带 device)。

requestDevice() 接受一个 HIDDeviceRequestOptions 字典。

成员 类型 必填 说明
filters sequence<HIDDeviceFilter> 是 匹配任一过滤器的设备会被列出;[] 列出浏览器未封禁的全部已连接 HID 设备。
exclusionFilters sequence<HIDDeviceFilter> 否 匹配其中任一项的设备即使匹配 filters 也会被隐藏。给出时必须非空。

HIDDeviceFilter 的每个成员都是可选的,但只有当 productId 与 vendorId 同时出现、usage 与 usagePage 同时出现时过滤器才有效。

成员 类型 说明
vendorId unsigned long USB-IF 厂商 ID,例如 Logitech 为 0x046d。
productId unsigned short 产品 ID;需要同时给出 vendorId。
usagePage unsigned short 顶层集合的 HID usage page,例如 0x0001 为 Generic Desktop,0xff00 及以上为厂商自定义页。
usage unsigned short usagePage 内的 usage ID;需要同时给出 usagePage。

sendReport()、sendFeatureReport() 与 receiveFeatureReport() 接受 reportId(octet,接口不使用报告 ID 时为 0);两个发送方法还接受 BufferSource 类型的 data,即不含 ID 字节的报告载荷。

出现下列情况之一时,requestDevice() 与 getDevices() 在任何选择器出现之前即拒绝。

异常 条件
SecurityError 文档不被允许使用 hid Permissions Policy 特性,或(仅 requestDevice())调用时没有瞬时用户激活。
NotSupportedError requestDevice() 在非 Window 的全局对象(如 worker)中被调用。
TypeError filters 或 exclusionFilters 中有无效过滤器(只有 productId 没有 vendorId,或只有 usage 没有 usagePage),或 exclusionFilters 给出但为空。

HIDDevice 各方法的拒绝条件如下。

方法 异常 条件
open() InvalidStateError 设备状态不是 "closed"(已打开、正在打开或已遗忘)。
open() NetworkError 操作系统拒绝打开设备。
close() 无 正常兑现;任何挂起的 sendReport() 或特征报告 Promise 以 AbortError 拒绝。
forget() InvalidStateError 设备已处于 "forgotten" 或 "forgetting" 状态。
sendReport()、sendFeatureReport()、receiveFeatureReport() InvalidStateError 设备不处于 "opened" 状态。
同上三个 TypeError 接口使用报告 ID 却传了 0,或接口不使用报告 ID 却传了非零值。
同上三个 NotAllowedError 该报告在浏览器的封禁列表上(例如受保护的键盘或 FIDO 集合上的报告)。
同上三个 NetworkError 操作系统写入或读取报告失败。

被封禁的输入报告不会引发任何异常:浏览器只是从不为它触发 inputreport。

每个示例都先检查 navigator.hid,并说明在该属性不存在的 Firefox、Safari 或 Android 上页面怎么做。

按厂商 ID 过滤,选择器只列出该厂商的设备;取第一台授权设备并打开。调用必须放在 click 处理函数内。没有 WebHID 时,处理函数改为提示用户使用厂商的桌面工具。

const LOGITECH = 0x046d;
document.querySelector('#pair').addEventListener('click', async () => {
const note = document.querySelector('#hid-note');
if (!('hid' in navigator)) {
note.textContent = '此浏览器无法与 HID 设备通信,请用厂商工具进行配置。';
return;
}
const [device] = await navigator.hid.requestDevice({ filters: [{ vendorId: LOGITECH }] });
if (!device) return; // 选择器被关闭:返回空数组,不是错误
if (!device.opened) await device.open();
note.textContent = `已连接 ${device.productName}(${device.vendorId.toString(16)}:${device.productId.toString(16)})`;
});

用户关闭选择器时 requestDevice() 以空数组兑现,所以解构后判断 device 就能覆盖取消的情况,不需要 try/catch。

open() 之后,设备发送的每份报告都以 inputreport 事件到达,data 是载荷的 DataView。下面的处理函数把一份两字节的厂商报告解码为旋钮位置和按键位图;布局来自设备的报告描述符,运行时可通过 device.collections 查看。

function listenToDial(device, onChange) {
if (!('hid' in navigator)) return () => {};
const handler = (event) => {
if (event.reportId !== 0x01) return;
const position = event.data.getUint8(0);
const buttons = event.data.getUint8(1);
onChange({ position, pressed: (buttons & 0x01) !== 0 });
};
device.addEventListener('inputreport', handler);
return () => device.removeEventListener('inputreport', handler);
}

event.data 不含报告 ID 字节;它单独作为 event.reportId 提供,接口不给报告编号的设备上该值为 0。

授权在刷新后仍然有效,所以 getDevices() 无需弹窗也无需手势就能返回此前授权的设备。配合 connect 与 disconnect 事件,可以在页面打开期间重新打开被拔掉又插回的设备。

async function restoreDevices(onDevice, onGone) {
if (!('hid' in navigator)) return;
for (const device of await navigator.hid.getDevices()) {
await device.open();
onDevice(device);
}
navigator.hid.addEventListener('connect', async ({ device }) => {
await device.open();
onDevice(device);
});
navigator.hid.addEventListener('disconnect', ({ device }) => onGone(device));
}

forget() 可以从脚本撤销授权,所以「移除设备」按钮不必把用户送去站点设置页就能清掉它。

规范

规范状态
WebHID: requestDevice() methodWHATWG 现行标准
WebHID: HIDDevice open() methodWHATWG 现行标准
WebHID: HIDDeviceRequestOptions dictionaryWHATWG 现行标准