跳转到内容

能力 · API

Web Serial API

发布于 更新于

有限可用不支持的浏览器: Firefox (Android)、Safari (macOS)、Safari (iOS)WICG 草案

Web Serial API 让页面通过 navigator.serial.requestPort() 向用户申请一个串口设备,然后经由返回的 SerialPort 的 readable 与 writable 流收发字节。它覆盖 USB 转串口适配器、板载 UART 口,以及(自 Chrome 117 起)蓝牙 RFCOMM 服务。规范由 WHATWG 发布,旧的 WICG 地址会重定向过去。

桌面端 Chrome 89 与 Edge 89 首发,Firefox 151 跟进;Android 上 Chrome 148 同时暴露 USB 与蓝牙串口,而 Chrome 138 至 147 以及 Samsung Internet 30 只列出蓝牙 RFCOMM 串口(BCD api.Serial)。Safari、Android 版 Firefox 与 Android WebView(crbug.com/40740509)没有实现。

navigator.serial.requestPort()
navigator.serial.requestPort(options)
navigator.serial.getPorts()
port.open(options)
port.close()
port.forget()
port.getInfo()
port.getSignals()
port.setSignals(signals)

requestPort() 以用户在浏览器选择器中选中的那一个 SerialPort 兑现;getPorts() 以本源已获授权且当前连着的串口数组兑现。open()、close()、forget()、getSignals() 与 setSignals() 都返回 Promise。SerialPort 还暴露 readable(Uint8Array 的 ReadableStream,关闭时为 null)、writable(WritableStream)与 connected(布尔值,Chrome 130 与 Firefox 151),并触发会冒泡到 navigator.serial 的 connect 与 disconnect 事件。所有接口都是 [SecureContext],requestPort() 另外要求瞬时用户激活。

requestPort(options) 接受 SerialPortRequestOptions 字典;open(options) 接受 SerialOptions 字典,其中只有 baudRate 是必填项。

字典 成员 类型 必填 说明
SerialPortRequestOptions filters sequence<SerialPortFilter> 否 把选择器限制为匹配的串口;不传则列出全部串口。
SerialPortRequestOptions allowedBluetoothServiceClassIds sequence<BluetoothServiceUUID> 否 在标准 Serial Port Profile 之外额外提供的蓝牙服务类 ID(Chrome 117)。
SerialPortFilter usbVendorId unsigned short 仅当设置了 usbProductId 时必填 适配器的 USB 厂商 ID。
SerialPortFilter usbProductId unsigned short 否 USB 产品 ID;需要同时给出 usbVendorId。
SerialPortFilter bluetoothServiceClassId BluetoothServiceUUID 否 蓝牙服务类;同一过滤器里再出现 USB 成员会以 TypeError 拒绝。
SerialOptions baudRate unsigned long 是 波特率(bit/s),必须大于 0。
SerialOptions dataBits octet 否,默认 8 7 或 8。
SerialOptions stopBits octet 否,默认 1 1 或 2。
SerialOptions parity ParityType 否,默认 "none" "none"、"even" 或 "odd"。
SerialOptions bufferSize unsigned long 否,默认 255 读写缓冲区大小(字节),必须大于 0。
SerialOptions flowControl FlowControlType 否,默认 "none" "none" 或 "hardware"(RTS/CTS)。

setSignals(signals) 接受 SerialOutputSignals 字典,含布尔成员 dataTerminalReady、requestToSend 与 break,至少要有一个。getSignals() 以 SerialInputSignals(dataCarrierDetect、clearToSend、ringIndicator、dataSetReady)兑现;getInfo() 在串口提供时返回 usbVendorId、usbProductId 与 bluetoothServiceClassId。

方法 异常 条件
requestPort() SecurityError 文档不被允许使用 serial Permissions Policy 特性,或调用时没有瞬时激活。
requestPort() TypeError 某个过滤器把 bluetoothServiceClassId 和 USB 成员混用,或只给了 usbProductId 而没有 usbVendorId。
requestPort() NotFoundError 用户没有选择串口就关闭了选择器。
getPorts() SecurityError serial Permissions Policy 特性被禁用。
open() InvalidStateError 串口状态不是 "closed"(已打开,或有 open()/close() 正在进行)。
open() TypeError baudRate 为 0、dataBits 不是 7 或 8、stopBits 不是 1 或 2、bufferSize 为 0 或超过实现上限。
open() NetworkError 操作系统打开串口失败。
readable(流出错) BufferOverrunError、BreakError、FramingError、ParityError 检测到对应的线路状况;流进入错误态,需要从 port.readable 重新获取。
readable / writable UnknownError 读或写时发生操作系统错误。
readable / writable NetworkError 设备被断开;writable 还会置一个致命标志,之后必须关闭串口。
writable TypeError 写入的数据块不是 BufferSource。
setSignals()、getSignals()、close() InvalidStateError 串口不处于 "opened"。
setSignals() TypeError 信号字典没有任何成员。
setSignals()、getSignals() NetworkError 操作系统拒绝设置或读取控制线。

每个示例都先检测 navigator.serial,并给出没有它时执行的分支。只有 requestPort() 需要用户手势,之后的步骤可以在普通代码里运行。

先复用已授权的串口,再申请新的

Section titled “先复用已授权的串口,再申请新的”

getPorts() 返回用户已经批准的串口,回访用户不该再看到选择器。API 缺失时(Safari、Android 版 Firefox、WebView),函数返回原因,由调用方提供桌面链接或原生配套应用。

async function connect(vendorId) {
if (!('serial' in navigator)) {
return { port: null, reason: '当前浏览器不提供 Web Serial。' };
}
const granted = await navigator.serial.getPorts();
let port = granted.find((p) => p.getInfo().usbVendorId === vendorId);
if (!port) {
try {
port = await navigator.serial.requestPort({ filters: [{ usbVendorId: vendorId }] });
} catch (err) {
if (err.name === 'NotFoundError') return { port: null, reason: '未选择设备。' };
throw err;
}
}
await port.open({ baudRate: 115200 });
return { port, reason: null };
}

filters 把无关串口挡在选择器之外;省略它则列出机器上的全部串口。

把 readable 通过 TextDecoderStream 转成文本,循环读取。断开会让流以 NetworkError 出错;奇偶或帧错误则以具体名称出错但串口仍然打开,所以循环重新获取 port.readable 继续读。

async function readLines(port, onLine) {
while (port.readable) {
const reader = port.readable.pipeThrough(new TextDecoderStream()).getReader();
let buffer = '';
try {
for (;;) {
const { value, done } = await reader.read();
if (done) break;
buffer += value;
const lines = buffer.split('\n');
buffer = lines.pop();
lines.forEach(onLine);
}
} catch (err) {
if (err.name === 'NetworkError') return; // 设备已断开
console.warn(`${err.name}: ${err.message}`); // 奇偶、帧、break、溢出
} finally {
reader.releaseLock();
}
}
}

finally 中的 releaseLock() 很关键:port.close() 会等待 readable 的锁释放,否则一直挂起。

写入走 WritableStream 的 writer;close() 要求两条流都未上锁,所以先释放 writer。

async function sendAndClose(port, command) {
const writer = port.writable.getWriter();
try {
await writer.write(new TextEncoder().encode(`${command}\r\n`));
} finally {
writer.releaseLock();
}
await port.close();
}

close() 兑现后,port.readable 与 port.writable 都变为 null,直到下一次 open()。

规范

规范状态
Web Serial API(网页串口)WICG 草案
Web Serial API: requestPort() methodWHATWG 现行标准
Web Serial API: open() methodWHATWG 现行标准
  • 图例
  • 支持
  • 部分支持
  • 需开启标志
  • 不支持
  • 未知
浏览器 / 平台支持版本置信度来源备注
Chrome (Desktop)支持89高来源—
Chrome (Android)支持148高来源—
Edge (Desktop)支持89高来源1
Firefox (Desktop)支持151高来源—
Firefox (Android)不支持—高来源2
Safari (macOS)不支持—高来源3
Safari (iOS)不支持—高来源45
Samsung Internet部分支持30.0高来源67
WebView (Android)不支持—高来源8
  1. 由 browser-compat-data 镜像自 Chrome 的数据推导。
  2. browser-compat-data 未记录 Firefox for Android 的支持。
  3. browser-compat-data 未记录 Safari 的支持。
  4. browser-compat-data 未记录 iOS 版 Safari 的支持。
  5. 由 browser-compat-data 镜像自 Safari 的数据推导。
  6. 仅当串口由蓝牙 RFCOMM 串口仿真提供时才可用。
  7. 由 browser-compat-data 镜像自 Chrome Android 的数据推导。
  8. 实现跟踪:https://crbug.com/40740509。

源数据: /compatibility/web-serial.json · 全球使用占比: 72 % (StatCounter 2026-05)

来源: 规范 · MDN · 最近核验 2026-10-03 · 置信度: 高 (由来源计算)