JavaScript 与硬件设备交互的核心并不神秘:它依靠浏览器或 Node.js 暴露的底层接口,把硬件抽象成可读写的流或端点。浏览器端最常用的是 Web Serial API、WebUSB 和 WebHID,分别对应串口、USB 协议设备与人机接口设备;Node.js 端则主要使用 serialport、usb、node-hid 等原生模块。两者共同点是都需要用户授权或系统权限,并且读写操作均基于异步模型。

浏览器端:Web Serial API 的连接与读写
Web Serial API 允许网页在用户主动选择串口后获得访问权限。对 Arduino、ESP32、树莓派 Pico 这类常见开发板来说,USB 转串口芯片会把 USB 接口映射为一个串口设备,因此 Web Serial 是门槛最低的浏览器硬件入口。
连接流程开始于 navigator.serial.requestPort()。该方法必须由用户手势触发,例如点击按钮。拿到 SerialPort 对象后调用 port.open() 并传入波特率。读写通过 port.readable 和 port.writable 两个流完成,这意味着可以配合 TransformStream 或直接使用 reader 和 writer 处理数据。
// 请求串口并打开
const port = await navigator.serial.requestPort();
await port.open({ baudRate: 9600 });
// 读取数据
const reader = port.readable.getReader();
while (true) {
const { value, done } = await reader.read();
if (done) break;
console.log(new TextDecoder().decode(value));
}
写入通常使用 TextEncoder 将字符串转为 Uint8Array,再交给 writer.write()。串口数据没有消息边界,实际开发中经常按换行符或固定长度帧拆包。比如 Arduino 每秒钟发送一行 sensor:25.6,网页端可以累积 buffer 直到遇到 \n 再解析。
需要注意,Web Serial 在 iframe 中受到权限策略限制,而且只能通过 HTTPS 或 localhost 使用。关闭页面后连接会自动断开,硬件端不会保留来自浏览器的会话。
WebUSB 与 WebHID 的适用场景
WebUSB 解决的问题是:不是所有 USB 设备都呈现为串口。某些厂商自定义 USB 协议的设备在系统里没有标准驱动,传统网页无法访问它们。WebUSB 允许页面声明特定的 vendorId 和 productId,并通过 USB 控制传输、批量传输等方式与设备通信。
使用 WebUSB 前建议先了解 USB 描述符、端点和接口概念。开发者调用 navigator.usb.requestDevice() 时传入过滤器,用户从弹出的设备列表中选择。连接后调用 device.open()、device.selectConfiguration()、device.claimInterface(),然后通过 transferOut() 和 transferIn() 执行批量传输。
const device = await navigator.usb.requestDevice({
filters: [{ vendorId: 0x2341 }]
});
await device.open();
await device.selectConfiguration(1);
await device.claimInterface(0);
// 发送数据到端点 1
await device.transferOut(1, new Uint8Array([0x01, 0x02]));
WebHID 则面向键盘、鼠标、游戏手柄等人机接口设备。它的优势是系统 HID 驱动不要求设备提供厂商驱动,浏览器可以直接读取输入报告和发送输出报告。与 WebUSB 相比,WebHID 更轻量,但只适用于 HID 协议设备。
三者选择上,可以先判断系统是否将设备识别为串口;如果是则 Web Serial 最简单。若设备有自定义 USB 描述符且需要底层控制,用 WebUSB。若是游戏控制器、传感器面板等 HID 设备,用 WebHID。不要试图用 WebUSB 访问已经由操作系统独占的鼠标键盘,否则会因接口被占用而失败。
Node.js 下使用 serialport 与硬件通信
在桌面端或服务端,Node.js 更适合长期运行硬件网关。serialport 是最常用的 npm 包,它封装了不同操作系统的串口 API,并支持事件驱动的数据接收。安装时可能需要在 Windows 上保留构建工具,但预编译二进制通常能覆盖主流 Node 版本。
下面的示例监听 COM3 或 /dev/ttyUSB0,并在收到数据后打印。serialport 默认会触发 data 事件,每次回调得到的是一小段 Buffer,同样需要处理粘包和半包问题。
const { SerialPort } = require('serialport');
const { ReadlineParser } = require('@serialport/parser-readline');
const port = new SerialPort({
path: 'COM3',
baudRate: 9600
});
const parser = port.pipe(new ReadlineParser({ delimiter: '\n' }));
parser.on('data', line => {
console.log('收到:', line.trim());
});
Node.js 方案的优势是脱离浏览器沙箱,可以直接访问文件系统、TCP 服务,也能把硬件数据推送到 WebSocket。缺点是部署环境需要安装原生依赖,在受限服务器或边缘设备上可能增加维护成本。
serialport 的 list() 方法可以枚举本机串口,适合做设备自动发现。如果在 Linux 下遇到权限不足,通常需要把用户加入 dialout 组,或使用 udev 规则授权指定 USB 设备。
完整示例:用 Web Serial 读取 Arduino 温湿度数据
下面给出一个端到端的浏览器示例。Arduino 端使用 DHT11 读取温湿度,并通过 Serial 打印字符串。浏览器端点击按钮后连接串口,按行解析并更新页面。
Arduino 代码片段如下。它每秒发送一次数据,格式为 temp=26.5&humi=58。为了简化,示例不处理 DHT 读取失败的重试逻辑。
#include <DHT.h>
#define DHTPIN 2
#define DHTTYPE DHT11
DHT dht(DHTPIN, DHTTYPE);
void setup() {
Serial.begin(9600);
dht.begin();
}
void loop() {
float t = dht.readTemperature();
float h = dht.readHumidity();
Serial.print("temp=");
Serial.print(t);
Serial.print("&humi=");
Serial.println(h);
delay(1000);
}
浏览器端建立连接后,用 ReadableStream 的 reader 逐块读取。数据按 \n 拆分并判断是否包含 temp= 字段,然后更新 DOM。
async function startMonitor() {
const port = await navigator.serial.requestPort();
await port.open({ baudRate: 9600 });
const decoder = new TextDecoder();
let buffer = '';
const reader = port.readable.getReader();
while (true) {
const { value, done } = await reader.read();
if (done) {
reader.releaseLock();
break;
}
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop();
for (const line of lines) {
if (line.startsWith('temp=')) {
const [tempPart, humiPart] = line.split('&');
document.getElementById('temp').textContent = tempPart.split('=')[1];
document.getElementById('humi').textContent = humiPart.split('=')[1];
}
}
}
}
该示例展示了硬件交互中最常见的模式:连续流、文本协议、按行拆包。如果数据量大,可以改为二进制协议,例如前两个字节表示长度,后面跟随负载。这样能避免文本解析延迟,也适合传输固件或图像数据。
安全边界与常见坑
浏览器硬件 API 都遵循权限最小化原则。Web Serial 没有设备列表 API,必须由用户手动选择端口;WebUSB 和 WebHID 也要求用户手势发起请求。任意页面不能在后台静默连接硬件,否则会带来严重隐私风险。
常见坑之一是误用波特率。Arduino 默认 9600,但很多现代开发板使用 115200。如果串口打开成功但数据乱码,先检查两端波特率是否一致。另一个坑是忘记释放 reader 或 writer 锁,导致后续端口无法再次打开或写入。
在 Windows 平台,Web Serial 可能因为驱动问题无法枚举某些 USB 转串口芯片。此时应确认设备管理器里是否出现 COM 端口,并安装芯片厂商驱动。Node.js 下 serialport 对 CH340、CP2102 等芯片兼容性较好,但仍需注意 Native 模块与 Electron 版本匹配。
最后,不要在生产环境长期打开浏览器页来采集硬件数据。标签页被后台节流后定时器和部分流操作可能受影响。更适合的架构是 Node.js 服务作为硬件网关,浏览器只做展示与控制端,通过 WebSocket 或 HTTP 与网关通信。
JavaScript硬件交互Web Serial APIWebUSB修改时间:2026-10-05 02:01:54