各位观众老爷,大家好!今天咱们来聊聊 Web Serial API 里的那些“弯弯绕”,特别是关于数据流控制和错误处理,保证让大家听得明白,用得溜溜的!
开场白:串行通信的“前世今生”
在 USB 满地跑的今天,你可能觉得串口通信是个古董。但别忘了,嵌入式系统、物联网设备、某些工业控制领域,串口依然坚挺!而且,通过 Web Serial API,咱们也能在浏览器里直接和这些“老朋友”打交道,是不是感觉瞬间“文艺复兴”了?
Web Serial API 快速回顾
先简单回顾一下 Web Serial API 的基本流程:
- 请求端口:
navigator.serial.requestPort()
获得用户授权,拿到SerialPort
对象。 - 打开端口:
port.open(options)
设置波特率、数据位、停止位等参数,建立连接。 - 读写数据: 通过
port.readable
和port.writable
获取ReadableStream
和WritableStream
,进行数据收发。 - 关闭端口:
port.close()
断开连接,释放资源。
数据流控制:让数据“井然有序”
数据流控制,顾名思义,就是控制数据传输的节奏,防止发送方“一股脑”地把数据塞给接收方,导致接收方处理不过来,造成数据丢失。Web Serial API 提供了两种主要的数据流控制方式:
- 硬件流控制 (RTS/CTS): 通过 RTS (Request To Send) 和 CTS (Clear To Send) 两根信号线来实现。
- 软件流控制 (XON/XOFF): 通过特定的字符 (XON 和 XOFF) 来实现。
1. 硬件流控制 (RTS/CTS)
想象一下,RTS 就像发送方举起的小旗子,CTS 就像接收方回应的小旗子。
- RTS: 发送方想要发送数据时,将 RTS 信号线置为有效状态(通常是低电平)。
- CTS: 接收方准备好接收数据时,将 CTS 信号线置为有效状态。只有在 CTS 有效时,发送方才能发送数据。
代码示例:启用 RTS/CTS
async function connectSerialPort() {
try {
const port = await navigator.serial.requestPort();
await port.open({
baudRate: 115200,
flowControl: "hardware" // 启用硬件流控制
});
console.log("Serial port opened with hardware flow control.");
// 读写数据的代码 (稍后补充)
} catch (error) {
console.error("Error opening serial port:", error);
}
}
connectSerialPort();
RTS/CTS 的优缺点
特点 | 优点 | 缺点 |
---|---|---|
实现方式 | 硬件信号线 | 需要额外的硬件线路支持 |
效率 | 高 | 硬件故障会导致流控失效 |
适用场景 | 对实时性要求高,且硬件支持 RTS/CTS 的场合 | 不适用于只有数据线的情况 |
2. 软件流控制 (XON/XOFF)
XON/XOFF 就像发送方和接收方之间的暗号。
- XOFF (Transmit Off): 当接收方缓冲区快满时,发送 XOFF 字符(通常是
0x13
,即 Ctrl+S)给发送方,要求停止发送数据。 - XON (Transmit On): 当接收方缓冲区有空闲空间时,发送 XON 字符(通常是
0x11
,即 Ctrl+Q)给发送方,允许继续发送数据。
代码示例:实现 XON/XOFF
这个例子展示了如何在接收端实现XON/XOFF。发送端需要根据接收到的XON/XOFF字符来停止或继续发送数据。
async function connectSerialPort() {
try {
const port = await navigator.serial.requestPort();
await port.open({
baudRate: 115200,
// 注意:Web Serial API 本身不直接提供 XON/XOFF 流控制的选项。
// 需要我们自己手动实现。
});
console.log("Serial port opened.");
const reader = port.readable.getReader();
const writer = port.writable.getWriter();
const XON = 0x11; // Ctrl+Q
const XOFF = 0x13; // Ctrl+S
const BUFFER_SIZE = 256;
let buffer = new Uint8Array(BUFFER_SIZE);
let bufferIndex = 0;
let isTransmitting = true;
while (true) {
try {
const { value, done } = await reader.read();
if (done) {
console.log("Reader done.");
break;
}
for (let i = 0; i < value.length; i++) {
if (value[i] === XON) {
console.log("Received XON, resuming transmission.");
isTransmitting = true;
} else if (value[i] === XOFF) {
console.log("Received XOFF, pausing transmission.");
isTransmitting = false;
} else {
if (isTransmitting) {
buffer[bufferIndex++] = value[i];
if (bufferIndex >= BUFFER_SIZE - 10) { // 预留一些空间
console.log("Buffer almost full, sending XOFF.");
await writer.write(new Uint8Array([XOFF]));
// 实际应用中,这里应该更谨慎地处理,
// 例如,等待发送 XOFF 完成后再暂停。
}
if (bufferIndex >= BUFFER_SIZE) {
//处理缓冲区数据
console.log("Processing buffer data:", new TextDecoder().decode(buffer));
bufferIndex = 0; // 重置缓冲区
await writer.write(new Uint8Array([XON]));//发送XON 恢复发送
}
}
}
}
} catch (error) {
console.error("Error reading from serial port:", error);
break;
}
}
reader.releaseLock();
writer.releaseLock();
await port.close();
} catch (error) {
console.error("Error opening serial port:", error);
}
}
connectSerialPort();
XON/XOFF 的优缺点
特点 | 优点 | 缺点 |
---|---|---|
实现方式 | 软件字符 | 效率相对较低,因为需要额外传输控制字符 |
效率 | 低 | 如果数据中包含和 XON/XOFF 相同的字符,可能会导致误判(需要转义处理) |
适用场景 | 硬件不支持 RTS/CTS,或者只有数据线的情况下 | 对实时性要求不高的场合 |
数据流控制的“最佳实践”
- 优先选择硬件流控制: 如果硬件支持,RTS/CTS 效率更高,更可靠。
- 软件流控制的转义: 如果使用 XON/XOFF,一定要注意数据中可能包含和 XON/XOFF 相同的字符,需要进行转义处理,避免误判。
- 缓冲区管理: 无论是硬件流控制还是软件流控制,都需要合理管理缓冲区的大小,避免溢出。
错误处理:防患于未然
在使用 Web Serial API 的过程中,可能会遇到各种各样的错误,例如:
- 端口不存在或被占用: 用户可能拔掉了串口设备,或者有其他程序正在使用该端口。
- 参数错误: 波特率设置错误,或者数据位、停止位等参数不匹配。
- 读写错误: 串口通信过程中出现数据损坏或丢失。
错误处理的“正确姿势”
-
try...catch
块: 将可能出错的代码放在try
块中,使用catch
块捕获异常。try { // 可能出错的代码 await port.open({ baudRate: 9600 }); } catch (error) { console.error("Error opening port:", error); // 处理错误 }
-
检查
port.opened
属性: 在进行读写操作之前,先检查port.opened
属性,确保端口已经打开。if (port.opened) { // 读写数据 } else { console.error("Port is not opened."); }
-
监听
disconnect
事件: 当串口设备断开连接时,会触发disconnect
事件。可以监听该事件,及时释放资源。navigator.serial.addEventListener('disconnect', (event) => { console.log("Serial port disconnected."); // 清理资源 });
-
处理
ReadableStream
和WritableStream
的错误:ReadableStream
和WritableStream
也可能出现错误,需要在读取和写入数据时进行处理。const reader = port.readable.getReader(); try { while (true) { const { value, done } = await reader.read(); if (done) { break; } // 处理数据 } } catch (error) { console.error("Error reading data:", error); } finally { reader.releaseLock(); }
-
用户友好的错误提示: 不要直接把错误信息显示给用户,而是提供更友好的提示,例如:“串口设备未连接”、“参数设置错误”等。
代码示例:完整的错误处理
async function connectSerialPort() {
let port;
try {
port = await navigator.serial.requestPort();
port.addEventListener('disconnect', () => {
console.log("Serial port disconnected.");
// 清理资源,例如关闭 reader 和 writer
if (reader) {
reader.releaseLock();
}
if (writer) {
writer.releaseLock();
}
});
await port.open({ baudRate: 115200 });
console.log("Serial port opened.");
const reader = port.readable.getReader();
const writer = port.writable.getWriter();
try {
while (port.readable) { // 确保端口仍然是可读的
try {
const { value, done } = await reader.read();
if (done) {
console.log("Reader done.");
break;
}
console.log("Received:", new TextDecoder().decode(value));
// 这里可以添加数据处理逻辑
} catch (readError) {
console.error("Error reading from serial port:", readError);
break; // 退出读取循环
}
}
} catch (streamError) {
console.error("Error with stream:", streamError);
} finally {
reader.releaseLock();
writer.releaseLock();
await port.close();
console.log("Serial port closed.");
}
} catch (error) {
console.error("Error opening serial port:", error);
// 给用户友好的提示
if (error.message.includes("No serial port selected")) {
alert("请选择一个串口设备。");
} else if (error.message.includes("Failed to open serial port")) {
alert("无法打开串口,可能被其他程序占用。");
} else {
alert("发生未知错误:" + error.message);
}
}
}
connectSerialPort();
调试技巧:让问题“无处遁形”
- 使用串口调试助手: 在电脑上安装串口调试助手,例如 Serial Monitor、Putty 等,可以方便地查看串口数据,排查问题。
- 打印调试信息: 在代码中添加
console.log()
语句,打印关键变量的值,例如接收到的数据、缓冲区状态等。 - 使用 Chrome DevTools: Chrome DevTools 提供了强大的调试功能,可以查看网络请求、控制台输出、断点调试等。
总结:掌握 Web Serial API 的“精髓”
Web Serial API 为我们在浏览器中访问串口设备提供了强大的能力。通过合理地使用数据流控制和错误处理,可以构建稳定可靠的串口通信应用。记住,调试是程序员的“家常便饭”,遇到问题不要慌,冷静分析,总能找到解决方案!
这次的“串行通信之旅”就到这里了。希望大家有所收获,在 Web Serial API 的世界里“玩”得开心!
补充说明:
- 浏览器的兼容性: Web Serial API 的兼容性还在不断完善中,使用前请查阅相关文档,确认目标浏览器是否支持。
- 安全性: Web Serial API 需要用户授权才能访问串口设备,确保安全性。
-
实际应用: Web Serial API 可以应用于各种场景,例如:
- Web 控制台: 通过串口连接嵌入式设备,实现远程控制和调试。
- 数据采集: 从传感器读取数据,实时显示在网页上。
- 固件升级: 通过串口更新设备的固件。
最后,祝大家编程愉快!