
1. 从浏览器调起硬件外设一次全新的实践起点先聊点实际的。过去浏览器要跟硬件打交道路径基本是固定的要么走 WebUSB要么走 Web Serial再要么绕一圈让用户本地装个 Agent 中转。WebHID 的出现把这些路径从“绕路”变成了“直连”。它让浏览器直接与 HID人机交互设备通信不需要驱动、不需要额外软件、不需要用户单独安装什么只需要用户授权一次页面就能直接和设备对话。我最初接触 WebHID 是做一个内部工具项目需要让网页直接读取一把自定义 HID 键盘的原始输入事件——不是键盘正常打字而是读取它上报的隐藏媒体键值和扩展数据。按传统思路就得写桌面端脚本或者配一个常驻服务。后来换成 WebHID 方案开发量直接少了一个数量级。从那之后我就开始持续关注这项技术也在这篇文章里把完整的思路与实践过程整理出来。WebHID 的核心能力可以归结为一句话浏览器直接访问 HID 设备完成数据收发。HID 设备是什么就是电脑上几乎所有输入外设——键盘、鼠标、游戏手柄、扫码枪、RFID 读卡器、工业控制面板、医疗设备、定制按键矩阵……它们都走 HID 协议。WebHID 让这一类外设第一次在 Web 平台上拥有了跨平台的直接通信能力。这篇文章适合几类人看前端工程师想了解浏览器新能力边界以及如何接外设需求产品经理或独立开发者需要在不写客户端的情况下搞定硬件联动测试/运维人员需要快速做硬件设备的网页诊断、校验、刷配置工具以及所有对“网页能控制硬件”这件事有好奇心的人。文章中我会拆开三层来讲底层协议与浏览器 API 的关系、实际开发流程中的关键步骤和代码形态、以及我在真实项目中踩过的坑和排查经验。尽量讲透也尽量让基础薄弱的朋友能跟着落地出第一个 Demo。2. 为什么选 WebHID技术背景与方案选型的权衡2.1 HID 协议和浏览器的交集点在 WebHID 出现之前前端想要和外设交互并非完全没有办法但每个方案都有自己的短板。WebUSB覆盖面广但它走的是 USB 原生协议设备必须实现 USB 规范中的传输端点。很多成熟 HID 设备只暴露 HID 接口并不按照 USB 通用端点设计所以 WebUSB 拿它们没办法或者需要额外的规避手段。Web Serial串口通信很强大但要求设备具备串口能力而且使用前需要用户手动指定端口、设置波特率体验上略重。除非是工厂调试或特殊工业硬件否则拿来当通用外设方案并不顺手。本地代理 WebSocket当然可行但需要用户安装客户端跨平台维护成本也高与“网页即产品”的初衷背道而驰。WebHID 瞄准的是 HID 设备这个最普及的外设类别。它在浏览器层面实现了 HID 报告的读取和发送用最简单的方式覆盖掉绝大多数输入设备。对使用者来说打开一个网页点两下授权设备就接上了。这是很明显的体验升级。2.2 为什么浏览器能做这件事浏览器本质上是一个安全沙箱。它不合适也不应该直接给网页任意硬件访问权。所以 WebHID 的设计里加了三道关卡HTTPS 强制WebHID API 只在安全上下文中可用本地开发时 localhost 算例外。保证了传输层环境可控。用户授权动作调用navigator.hid.requestDevice()必须是由用户手势触发点击、按键等浏览器弹出设备选择器用户明确选择后才能获得访问权。设备颗粒度授权是绑定到特定设备的 VID厂商 ID、PID产品 ID和 usage 页面/用法的组合。拿到的设备集合非常具体不是一次授权全部设备。这套设计让我想起操作系统的权限模型——用户不感知不授权应用就触碰不到硬件。它是浏览器的“应用层权限”但与系统级权限形成了良好互补既不影响桌面应用的既有模式又给 Web 赋予了外设控制能力。2.3 选 WebHID 而不是等 WebUSB如果团队不是一个新项目从零起很可能会遇到已经在用 WebUSB 的老代码。我的建议是如果目标设备是 HID 类设备且主要交互是“获取输入报告/发送输出报告”果断选 WebHID。理由有三HID 设备的抽象层级更匹配协议天然包含报告描述符能直接区分输入、输出、Feature 报告而 WebUSB 里这些都要自己拼端点逻辑。交互更统一免驱动能力在 HID 设备上最完整。Windows 也好、macOS 也好系统对 HID 的识别是天然的浏览器只是借助系统能力。开发与测试环境更轻WebHID 的 Device 对象直接暴露 collection 信息方便页面做动态渲染不像 WebUSB 那样需要层层处理和缓冲。当然如果设备本身就不是 HID 类比如海量存储、摄像头、自定义的裸 USB 芯片那就别硬套 HIDWebUSB 才是合适的。技术选型最怕“一竿子打翻一船人”按需选取才是正道。提示判断标准很简单——设备插入后系统设备管理器里是否显示为“HID 兼容设备”或者设备附带的说明书里有没有写“遵循 HID 协议”。如果有WebHID 基本就是最佳选择。3. WebHID 核心细节梳理从设备模型到数据通道3.1 HID 报告格式那棵必须理解的树HID 协议的数据模型核心其实就一个概念报告Report。设备通过报告把输入数据传输给主机主机也通过报告向设备发送指令。要理解 WebHID 的流程先得理解报告描述符Report Descriptor。它定义了这个设备有哪些 Report每个 Report 包含哪些字段分别是多少位取值范围是多少。浏览器会把这份描述符解析成层级结构暴露给 JavaScript 使用。以下是一个简化版游戏手柄报告描述符的样式十六进制表示0x05, 0x01, // Usage Page (Generic Desktop) 0x09, 0x05, // Usage (Game Pad) 0xA1, 0x01, // Collection (Application) 0x05, 0x09, // Usage Page (Button) 0x19, 0x01, // Usage Minimum (1) 0x29, 0x08, // Usage Maximum (8) 0x15, 0x00, // Logical Minimum (0) 0x25, 0x01, // Logical Maximum (1) 0x75, 0x01, // Report Size (1) 0x95, 0x08, // Report Count (8) 0x81, 0x02, // Input (Data, Variable, Absolute) 0x05, 0x01, // Usage Page (Generic Desktop) 0x09, 0x01, // Usage (Pointer) 0xA1, 0x00, // Collection (Physical) 0x09, 0x30, // Usage (X) 0x09, 0x31, // Usage (Y) 0x16, 0x00, 0x00, // Logical Minimum (0) 0x26, 0xFF, 0x03, // Logical Maximum (1023) 0x75, 0x0A, // Report Size (10) 0x95, 0x02, // Report Count (2) 0x81, 0x02, // Input (Data, Variable, Absolute) 0xC0, // End Collection 0xC0 // End Collection这段描述符定义了 8 个按键每个 1 位以及 X、Y 两个 10 位的模拟轴。WebHID 拿到后会在 Device 对象的collections属性里暴露这些结构。3.2 设备接入前的准备鉴权、枚举与过滤WebHID 提供两组关键接口hid.getDevices()用于获取已经授权过的设备列表hid.requestDevice()用于请求新设备授权。// 获取已授权设备列表 const grantedDevices await navigator.hid.getDevices(); console.log(已授权设备:, grantedDevices); // 请求用户授权只显示符合过滤条件的设备 let device; try { device await navigator.hid.requestDevice({ filters: [ { vendorId: 0x1234, // 厂商 ID productId: 0x5678, // 产品 ID usagePage: 0x0C, // 使用页面Consumer }, ], }); } catch (err) { // 用户取消授权 console.log(用户取消了设备选择); return; }filters参数是可选的。如果传入厂商 ID 和产品 ID那么浏览器弹出的选择列表里只会出现匹配的设备如果不传那就列出所有当前系统里可访问的 HID 设备。这种方式适合做通用型工具但用户选择时可能眼花缭乱。对于面向固定硬件的应用一定要加过滤器减少误操作。3.3 打开设备从选到了到能通信拿到 Device 对象后不能急着收发数据第一步是调用open()方法。if (!device.opened) { await device.open(); } console.log(设备已打开:, device.productName);open()是异步的会开始与系统底层交互。打开之后Device 对象会监听inputreport事件只要设备主动上报数据事件就会触发。这里有一个重要细节如果设备已经被系统“占用”比如键盘正在正常打字输入系统输入服务捕获了它浏览器打开时会失败。WebHID 在桌面端的策略是某些设备因为系统级独占网页是拿不到它们的输入流的。所以实践中通常会选那些系统没有默认消费的设备比如自定义按键板、扫码枪、RFID 读卡器、部分手柄。3.4 收发数据inputreport、sendReport、sendFeatureReportWebHID 的数据通道分三类Input Report设备主动向主机上报的数据按键状态、传感器数值等Output Report主机向设备发送的数据开灯、震动、设置参数等Feature Report双向配置型数据用于读取/写入设备的配置信息接收数据靠监听事件device.addEventListener(inputreport, (event) { const { data, device, reportId } event; // data 是 DataView 对象按字节解析 const bytes new Uint8Array(data.buffer); console.log(收到报告 ID${reportId}, 数据:, bytes); });发送数据使用sendReport或sendFeatureReport// 发送 Output ReportreportId 为 0 表示不使用编号报告 await device.sendReport(0, Uint8Array.from([0xFF, 0x01, 0x02])); // 读取 Feature Report const featureData await device.receiveFeatureReport(1); console.log(Feature 数据:, new Uint8Array(featureData.buffer));需要特别注意字节序和位域抽取。HID 协议中的模拟轴、开关组可能不是按整字节对齐的需要自己做移位和掩码运算。我在项目中封装了一套小工具来拆解位域省了很多事function extractBits(data, byteOffset, bitOffset, bitLength) { let value 0; for (let i 0; i bitLength; i) { const byteIndex byteOffset Math.floor((bitOffset i) / 8); const bitIndex (bitOffset i) % 8; const bit (data[byteIndex] (7 - bitIndex)) 0x01; value (value 1) | bit; } return value; }这种函数虽然小但特别好用。做设备的协议解析时你会反复用到它。3.5 设备断开与重连体验必须处理的边界外设最不可避免的事情就是“突然断开”。WebHID 通过disconnect事件感知掉线开发时需要处理好状态机navigator.hid.addEventListener(disconnect, (event) { const disconnectedDevice event.device; console.warn(设备断开:, disconnectedDevice.productName); // 更新 UI 状态停止轮询或等待 });同时也要处理浏览器页面刷新后的恢复。刷新页面之后requestDevice()不会再次弹出授权窗口因为授权是持久化的相对于源 origin。此时应调用getDevices()自动拿回设备列表并重新打开。const devices await navigator.hid.getDevices(); if (devices.length 0 !devices[0].opened) { await devices[0].open(); }这个恢复流程如果做得好用户几乎感知不到“外设连接”的状态变化体验会更自然。4. 实操从零构建一个浏览器端的 HID 通信 Demo4.1 需求定义与场景设定为了让实践过程更具体我自己拟了一个模拟项目 X做一个浏览器端的 RFID 读卡器测试面板。硬件是某公司生产的 USB HID 读卡器它会把卡号当作键盘事件逐字符“打出来”或者以 Input Report 形式把卡号一次性发送上来。我在这个示例里设定它启动后以固定报告格式上报 8 字节卡号。目标很简单浏览器打开页面授权设备读卡器一刷就显示卡号并附带历史记录。4.2 环境准备搭建这个 Demo 不需要任何框架。简单起一个静态服务器即可。mkdir webhid-rfid-demo cd webhid-rfid-demo # 任意静态文件服务均可比如 Python 自带模块 python3 -m http.server 8080页面文件结构webhid-rfid-demo/ ├── index.html ├── app.js └── style.css必须通过localhost或 HTTPS 访问。直接用file://打开是没有权限调用 WebHID 的。4.3 页面设计与状态管理我的核心页面设计分成三块区域顶部操作区授权按钮、设备连接状态显示数据展示区当前卡号、解析后的时间戳、解码结果历史记录区滚动列表展示最近 20 条记录对应 HTML 大体如下main section classcontrols button idconnectBtn授权并连接读卡器/button span iddeviceStatus未连接/span /section section classdata h2当前卡号/h2 p idcardNumber classcard-number--/p /section section classhistory h2刷卡记录/h2 ul idhistoryList/ul /section /main4.4 完整实现流程4.4.1 授权与打开在app.js里我把授权过程封装成了一个函数方便重复调用const connectBtn document.getElementById(connectBtn); const deviceStatus document.getElementById(deviceStatus); let device null; connectBtn.addEventListener(click, async () { try { const devices await navigator.hid.requestDevice({ filters: [ { vendorId: 0xABCD, productId: 0x1234 }, ], }); if (devices.length 0) return; device devices[0]; await device.open(); deviceStatus.textContent 已连接: ${device.productName}; device.addEventListener(inputreport, onInputReport); } catch (e) { console.error(授权失败:, e); } });这里的vendorId和productId是模拟值实际开发中请换成自己设备的真实数值。如果你不确定设备的 VID/PID可以先不传过滤器设备选择器会把所有 HID 设备列出来。4.4.2 解析输入报告假设读卡器以报告 ID 1 发送 8 字节的卡号数据BCD 码编码解析代码如下function onInputReport(event) { const bytes new Uint8Array(event.data.buffer); if (event.reportId ! 1 || bytes.length 8) return; const cardNumber parseBCD(bytes); renderCard(cardNumber); } function parseBCD(data) { let result ; for (let i 0; i data.length; i) { const high (data[i] 4) 0x0F; const low data[i] 0x0F; if (high 0 high 9) result high.toString(); if (low 0 low 9) result low.toString(); } return result; }BCG 解码是读卡器常用的编码方式之一。每一位十六进制数代表两个十进制数位所以一个 8 字节包能表示 16 位卡号。如果你的设备发送的是 ASCII 字符串那么直接String.fromCharCode(...bytes)即可更简单。4.4.3 渲染结果与历史记录function renderCard(cardNumber) { const cardEl document.getElementById(cardNumber); cardEl.textContent cardNumber; const listEl document.getElementById(historyList); const li document.createElement(li); li.textContent ${new Date().toLocaleTimeString()} - ${cardNumber}; listEl.insertBefore(li, listEl.firstChild); while (listEl.children.length 20) { listEl.removeChild(listEl.lastChild); } }这段代码很直接核心就是把 inputreport 事件变成用户可读的展示。4.4.4 刷新后恢复连接为了提升体验我在页面加载时尝试恢复已授权的设备async function restoreConnection() { const devices await navigator.hid.getDevices(); if (devices.length 0) { device devices[0]; await device.open(); deviceStatus.textContent 已恢复连接: ${device.productName}; device.addEventListener(inputreport, onInputReport); } } window.addEventListener(DOMContentLoaded, restoreConnection);加上这段之后我每次刷新页面都不需要重新点授权按钮设备还是那个设备连接直接恢复了。这个细节很提升工具类页面的实用性。4.5 参数选择的现场记录这个 Demo 里的关键参数有两个vendorId和reportId。vendorId是设备厂商 ID由 USB 标准组织分配。某公司的读卡器通常有自己的固定 VID你在后端协议文档或设备描述符里能找到。它在过滤条件中作用巨大能避免用户从一堆设备里选错。reportId是报告编号。由于 HID 设备可以定义多个报告我为了区分不同通道统一用了reportId1。这是自定义协议的一部分不是系统规定死的值。实际测试的时候我第一次拿到的数据字节序与文档不一致后来发现设备配置了字节逆序本地做一个reverse再解析即可。这是非常典型的设备差异问题。注意如果你发现拿到的数据出现“卡号顺序错乱”或“少一位”大概率不是 WebHID 的问题而是设备本身的字节序、掩码位宽的差异。请优先比对协议文档而不是反复调试前端解码。5. 踩坑实录与常见问题排查这部分内容来自我实践中的直接体验比文档更有参考价值。5.1 “设备被系统独占”导致打不开最常见的一种失败是device.open()抛错提示Unable to open HID device。原因往往是设备已被系统输入服务占用。键盘与鼠标日常输入设备系统占用严重网页通常拿不到输入报告。自定义按键板如果被识别为键盘也会遇到这个情况。解决办法在设备固件端设置正确的 Usage Page 和 Usage把它定义成“非键盘、非鼠标”的 HID 设备。或者干脆选择扫码枪、读卡器这类不太被系统抢占的设备。这是一个典型的硬件软件联调问题。如果研发阶段发现打不开不要纠结浏览器先确认设备描述符里自己是“什么身份”。5.2 页面刷新后提示授权失败有同学反映第一次页面能授权成功刷新后requestDevice()反而不能弹窗了。原因很直接requestDevice()必须由用户手势触发。刷新后如果立即在DOMContentLoaded里调用它浏览器会拦。正确做法加载后先尝试getDevices()恢复已授权设备只有用户点击“连接另一台设备”按钮时才去调用requestDevice()。5.3 inputreport 事件收不到数据设备明明在正常上报浏览器却没触发事件。大概率原因如下设备没有被成功打开用device.opened检查电脑上的其他程序抢先占用了设备关掉厂商自带的配置工具即可数据是 Feature Report不是 Input Report需要receiveFeatureReport()主动读取设备的上报格式是中断传输但浏览器没有正确绑定报告 ID。排查顺序建议是先重新打开设备 → 关闭其他占用程序 → 检查报告描述符 → 最后看代码。5.4 数据解析结果永远不对这不是 WebHID 的 bug而是协议理解的偏差。HID 数据包里的“位”和“字节”是严格按照描述符定义的。最容易出错的是模拟量轴比如摇杆 X/Y 轴各占 10 位可能跨字节。开关组多个按键连续放需要按位拆。符号位部分设备使用有符号数值需要按补码处理。解决方案就是我在前文写的extractBits函数配合设备文档逐字段联调。我踩过最大的坑是某个设备把“逻辑最大值”标成了 1023但实际发送时用的是 1024 范围内的值导致边界判断不准。后来直接打印所有字节并对齐硬件波形图才定位到问题。实践心得连接成功后的第一步永远不要做复杂业务解析。先把原始字节流完整打印出来再从文档里确认这些字节从哪里开始到哪里结束。一次能打通后面就不会出幺蛾子。5.5 Windows 上设备枚举正常但打不开Windows 对 HID 设备的管理有自己的脾气。某些设备在“系统设备”列表里可见但 WebHID 无法打开原因大多和“设备接口限制”或“驱动层的过滤器驱动”有关。遇到这种场景尝试以下顺序排查确认设备没有在其他进程中被独占尝试拔插一次设备让系统重新枚举查看设备管理器里有没有黄色感叹号换一个 USB 口尤其是用扩展坞时某些口供电不足会导致设备唤醒异常。5.6 常见问题速查表现象可能原因解决方案requestDevice不弹窗没有用户手势触发绑定到 click 事件授权成功但 open 失败设备被占用关闭驱动工具、换设备类型inputreport不触发Report ID 不匹配 / 未打开打印 event 对象检查 reportId数据错位字节序/位域解析错误使用位解析工具函数并对照文档刷新后连接丢失未调用getDevices()恢复DOMContentLoaded 里自动恢复局域网 https 下报错证书不受信任配置有效证书或改为 localhost 调试6. 多场景下的 WebHID 应用拓展思路6.1 游戏外设的网页直调游戏手柄是另一个很典型的 WebHID 场景。浏览器有 Gamepad API但它是相对高层、只提供标准键值。WebHID 则能拿到手柄的原始 HID 报文读取包括陀螺仪、扳机线性度、自定义背键在内的高级数据。想过没有网页游戏不靠插件就能让第三方手柄实现“宏按键”功能这在过去几乎不可想象。我见过一个开源项目用 WebHID 实现手柄调试器页面里直接画出按键矩阵与摇杆坐标还能发送配置包改写手柄震动曲线。这类工具如果做成桌面应用成本高不说分发、更新都很麻烦Web 版本一次部署到处用真的是降维打击。6.2 工业设备巡检页面工业场景中大量设备还是采用 HID 接口原因很简单免驱、成熟、可靠。过去的巡检软件往往需要 WinForm 或 WPF 客户端。用 WebHID 做巡检面板后只要浏览器开着员工扫码枪一刷设备参数、校准数值全部进入网页表格直接入库。省掉了“安装客户端”这个环节对产线的部署维护压力骤降。同类应用还可以做到设备的批量校准页面、抄表页面甚至可移动设备上用平板浏览器读取诊断状态。6.3 医疗外设与无障碍设备的 Web 集成医疗设备有很多以 HID 协议传输数据比如体征检测仪、呼末二氧化碳监测仪、康复训练手柄等。这些设备传统上由专业软件对接成本高。如果设备协议允许WebHID 可以让浏览器直接承担可视化与存储的职能。同样无障碍领域的辅助按键、轨迹球、头部追踪器等设备也可以靠 WebHID 打通浏览器内的控制链路。当然医疗领域有严格的合规要求WebHID 更适用的是内部研发验证和实验室环境真正的患者场景还是要走更严谨的审批路径。但做为工程师你知道浏览器能做这件事等于多了一张手感极佳的工具卡。6.4 浏览器作为硬件调试器我最看好的方向是“浏览器即调试终端”。硬件开发过程中调试 HID 描述符、验证报告数据、观察报文变化过去要么用 Bus Hound要么用厂商专用工具。现在 WebHID 完全可以做成一个跨平台通用调试台打开网页选择设备实时看到所有上报报文甚至能手动构造 Output Report 发送。对开发者来说这是一套“零安装、零配置”的排查工具。我自己就基于这个思路做过一个简化版调试页功能只有一个列表 一个发送框但整个硬件调试效率比之前用命令行工具快很多。7. 最后分享几个实践里的小技巧如果看完了整篇文章你已经可以用 WebHID 做很多事了。但有几个细节不在 API 文档里是我从项目里反复摸索出来的很值得再写一遍。7.1 善用collections字段做动态 UIDevice 对象上有一个collections字段直接描述了设备报告结构。你可以遍历它自动生成按键开关、数值滑杆的 UI 控件而不需要硬编码每种设备的布局。对于需要兼容“一个系统多设备型号”的场景这个字段几乎就是救命稻草。for (const collection of device.collections) { console.log(Usage Page:, collection.usagePage); console.log(Usage:, collection.usage); for (const inputReport of collection.inputReports) { console.log( 输入报告 ID:, inputReport.reportId, 大小:, inputReport.items.length, 字节); } for (const outputReport of collection.outputReports) { console.log( 输出报告 ID:, outputReport.reportId); } }有了这些信息页面能根据设备自动渲染调试面板比写死 UI 高出一个层次。7.2 不要轻视野外设备报告描述符的“内嵌数据”有些自定义 HID 设备会在 Report Descriptor 中嵌入厂商私有数据段。浏览器在解析时可能不会完整暴露这些内容但它有时会影响设备的工作模式。如果发现设备启动后表现异常先尝试用专业解析工具读取设备真实描述符而不是猜。7.3 加一个“强制刷新设备列表”的按钮调试阶段设备频繁插拔授权列表可能会残留下“已经插拔但未从 list 里消失”的记录。此时调一下getDevices()即可刷新。我在工具页面上会专门放一个“刷新设备状态”按钮来干这事省去每次刷新整个页面。7.4 注意 HID 设备“输出报告”的发送频率有的设备对输出报告的处理速度有上限疯狂sendReport会触发设备内部缓冲溢出效果是设备“死机”或丢消息。做连续控制比如调整灯光亮度、电机转速时记得做发送节流最小间隔限制在 10-20ms或采用 requestAnimationFrame 配合实际需要。const sendThrottle (fn, delay 20) { let last 0; return (...args) { const now Date.now(); if (now - last delay) { last now; fn(...args); } }; }; const throttledSend sendThrottle((data) { device.sendReport(0, data); }, 20);这个节流函数虽然简单但能防止你调试时手滑把设备搞挂。7.5 权限持久化后的撤销问题用户如果不想让某个网站再访问设备浏览器设置里可以撤销授权。但你的网页代码没办法主动触发“撤销”操作。所以产品设计上要明确告诉用户“撤销授权需要去浏览器设置”。这也算一个不算常见但值得记住的小知识。8. 结尾跳出框架看 WebHIDWebHID 只是浏览器能力扩展的一个缩影。它的价值不只是“前端能连接硬件”更深层的是它把硬件交互的门槛大幅降低。过去写一个外设工具散落的环节包括开发环境、驱动兼容、软件分发、跨平台适配。现在打开浏览器一切都在眼前。当然它也有自己的边界——不是所有设备都适合、用户授权机制有约束、某些平台对 HID 的访问限制仍然存在。但这不妨碍它成为一个具备战略价值的技术方向。对企业和独立开发者来说WebHID 意味着可以把手伸进物理世界而不用先爬过客户端的围墙。如果在读这篇文章的你也正打算尝试 WebHID我建议从最小的 Demo 开始找一个手边的 HID 设备打开网页看看能不能打印出第一段原始数据。一旦你把第一条inputreport打印出来整座桥就通了。之后不管是做产品工具、数据中心巡检还是硬件调试台都只是在这条数据通道上继续延伸。我个人的体会是浏览器和硬件的边界正在快速消融WebHID 是这条路上最实用的一把钥匙。拿着它去解锁你身边的外设吧。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。