能力 · API
Web Serial API
发布于 更新于
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 把无关串口挡在选择器之外;省略它则列出机器上的全部串口。
逐行读取文本直到设备断开
Section titled “逐行读取文本直到设备断开”把 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 的锁释放,否则一直挂起。
发送命令并干净地关闭
Section titled “发送命令并干净地关闭”写入走 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()。
- WebUSB API,面向没有串口协议的设备
- WebHID API
- Web Bluetooth API,面向 GATT 而非 RFCOMM 设备
- Web Serial API: requestPort() method(whatwg.org)
- Web Serial API: SerialOptions dictionary(whatwg.org)
- Read from and write to a serial port(developer.chrome.com)
- Chromium bug 40740509: Web Serial in Android WebView(crbug.com)
规范
| 规范 | 状态 |
|---|---|
| Web Serial API(网页串口) | WICG 草案 |
| Web Serial API: requestPort() method | WHATWG 现行标准 |
| Web Serial API: open() method | WHATWG 现行标准 |
- 图例
- 支持
- 部分支持
- 需开启标志
- 不支持
- 未知
| 浏览器 / 平台 | 支持 | 版本 | 置信度 | 来源 | 备注 |
|---|---|---|---|---|---|
| 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 |
- 由 browser-compat-data 镜像自 Chrome 的数据推导。
- browser-compat-data 未记录 Firefox for Android 的支持。
- browser-compat-data 未记录 Safari 的支持。
- browser-compat-data 未记录 iOS 版 Safari 的支持。
- 由 browser-compat-data 镜像自 Safari 的数据推导。
- 仅当串口由蓝牙 RFCOMM 串口仿真提供时才可用。
- 由 browser-compat-data 镜像自 Chrome Android 的数据推导。
- 实现跟踪:https://crbug.com/40740509。