各位观众,大家好!我是你们的老朋友,今天咱们来聊聊一个有点“野性”的话题——Web Serial API。 啥叫“野性”?因为它能让你直接用浏览器跟硬件设备“勾搭”上,想想是不是有点刺激? 别担心,咱们会用最简单的方式,把这只“野兽”驯服。
开场白:串口是个啥?为啥需要 Web Serial?
在进入正题之前,先简单回顾一下串口。如果你玩过 Arduino、树莓派之类的东西,肯定对它不陌生。 串口,简单来说,就是一种古老的通信方式,用一根或者几根线来传输数据。 它的优点是简单、可靠,但缺点也很明显:速度慢,而且通常需要特定的驱动程序。
那么,Web Serial API 又是干啥的呢? 简单来说,它就是让浏览器也能直接访问串口设备的“桥梁”。 以前,如果你想用网页控制一个串口设备,比如一个 LED 灯,你得先装个驱动,然后写个桌面应用,通过某种方式(比如 WebSocket)和网页通信。 现在有了 Web Serial API,这一切都简化了! 你只需要在网页里写几行 JavaScript 代码,就能直接控制串口设备了。
Web Serial API 的基本用法:Hello, Serial!
好了,废话不多说,咱们直接上代码!
1. 请求串口访问权限
首先,我们需要向用户请求访问串口的权限。 这个过程就像你要去别人家做客,总得先敲敲门,问问主人欢不欢迎吧?
async function requestSerialPort() {
try {
const port = await navigator.serial.requestPort();
// 成功获取串口对象后,就可以进行后续操作了
console.log("串口已获取!");
return port;
} catch (error) {
console.error("获取串口失败:", error);
// 用户可能取消了选择,或者浏览器不支持 Web Serial API
return null;
}
}
这段代码里,navigator.serial.requestPort()
是一个异步函数,它会弹出一个对话框,让用户选择要连接的串口设备。 如果用户允许访问,函数会返回一个 SerialPort
对象,否则会抛出一个错误。
2. 连接串口
拿到 SerialPort
对象后,下一步就是连接串口。 这就像你进了别人家门,总得先跟主人打个招呼吧?
async function connectSerialPort(port, baudRate) {
try {
await port.open({ baudRate: baudRate });
console.log("串口已连接!");
return true;
} catch (error) {
console.error("连接串口失败:", error);
return false;
}
}
port.open()
函数用于打开串口连接。 它需要一个配置对象,里面至少要指定 baudRate
(波特率)。 波特率是串口通信的一个重要参数,它表示每秒传输多少个比特。 不同的串口设备可能需要不同的波特率,你需要根据设备的文档来设置。
3. 发送数据
连接成功后,就可以发送数据了。 这就像你跟主人聊起天来,开始说你想说的话。
async function sendData(port, data) {
try {
const writer = port.writable.getWriter();
await writer.write(new TextEncoder().encode(data));
writer.releaseLock();
console.log("数据已发送:", data);
} catch (error) {
console.error("发送数据失败:", error);
}
}
这段代码里,我们首先通过 port.writable.getWriter()
获取一个 WritableStreamDefaultWriter
对象,用于向串口写入数据。 然后,我们使用 TextEncoder
将字符串数据编码成 Uint8Array
类型的字节数组,再通过 writer.write()
函数发送出去。 最后,别忘了调用 writer.releaseLock()
释放锁,否则其他代码可能无法写入数据。
4. 接收数据
除了发送数据,我们还需要能够接收数据。 这就像主人在听你说话,然后给你回应。
async function receiveData(port, callback) {
try {
const reader = port.readable.getReader();
let partialData = ''; // 用于存储未完成的数据段
while (true) {
const { value, done } = await reader.read();
if (done) {
console.log("串口已断开连接");
break;
}
const textDecoder = new TextDecoder();
const newData = textDecoder.decode(value);
const completeData = partialData + newData;
// 假设每条完整数据以换行符 'n' 结尾
const lines = completeData.split('n');
partialData = lines.pop() || ''; // 将最后一段未完成的数据存起来
lines.forEach(line => {
if (line) {
callback(line); // 调用回调函数处理每行数据
}
});
}
} catch (error) {
console.error("接收数据失败:", error);
} finally {
if (reader) {
reader.releaseLock(); // 确保在出错或者循环结束时释放锁
}
}
}
这段代码里,我们通过 port.readable.getReader()
获取一个 ReadableStreamDefaultReader
对象,用于从串口读取数据。 reader.read()
函数会返回一个 Promise
,它会在有数据可读时 resolve。 我们使用 TextDecoder
将 Uint8Array
类型的字节数组解码成字符串。
注意: 这里我们假设串口设备发送的数据是以换行符 n
分隔的。 如果你的设备使用不同的分隔符,你需要修改代码来适应。 同时,我们需要处理 incomplete data,即数据可能被分割成多个块,需要将它们拼接起来才能得到完整的数据。
5. 关闭串口
最后,当我们不再需要使用串口时,应该关闭它。 这就像你要离开别人家了,总得跟主人告别一声吧?
async function closeSerialPort(port) {
try {
await port.close();
console.log("串口已关闭!");
} catch (error) {
console.error("关闭串口失败:", error);
}
}
port.close()
函数用于关闭串口连接。
一个完整的例子:串口回显
下面是一个完整的例子,它可以从串口接收数据,然后原封不动地发送回去。 这个例子就像一个“鹦鹉”,你说啥它就学啥。
<!DOCTYPE html>
<html>
<head>
<title>Web Serial Echo</title>
</head>
<body>
<h1>Web Serial Echo</h1>
<button id="connectButton">连接串口</button>
<button id="disconnectButton" disabled>断开串口</button>
<textarea id="receivedData" rows="10" cols="50" readonly></textarea>
<br>
<input type="text" id="sendData">
<button id="sendButton">发送数据</button>
<script>
let port;
let reader;
let writer;
const connectButton = document.getElementById("connectButton");
const disconnectButton = document.getElementById("disconnectButton");
const receivedDataTextarea = document.getElementById("receivedData");
const sendDataInput = document.getElementById("sendData");
const sendButton = document.getElementById("sendButton");
connectButton.addEventListener("click", async () => {
try {
port = await navigator.serial.requestPort();
await port.open({ baudRate: 9600 }); // 确保波特率与你的设备匹配
connectButton.disabled = true;
disconnectButton.disabled = false;
reader = port.readable.getReader();
writer = port.writable.getWriter();
// 接收数据
while (true) {
const { value, done } = await reader.read();
if (done) {
console.log("Reader done!");
break;
}
const textDecoder = new TextDecoder();
const receivedText = textDecoder.decode(value);
receivedDataTextarea.value += receivedText;
receivedDataTextarea.scrollTop = receivedDataTextarea.scrollHeight; // 自动滚动到最新内容
// 回显数据
await writer.write(value); // 直接将接收到的 Uint8Array 发送回去
}
} catch (error) {
console.error("Serial port error:", error);
} finally {
if (reader) {
reader.releaseLock();
}
if (writer) {
writer.releaseLock();
}
}
});
disconnectButton.addEventListener("click", async () => {
try {
await port.close();
console.log("Serial port closed");
connectButton.disabled = false;
disconnectButton.disabled = true;
} catch (error) {
console.error("Error closing serial port:", error);
}
});
sendButton.addEventListener("click", async () => {
const dataToSend = sendDataInput.value;
const encoder = new TextEncoder();
const encodedData = encoder.encode(dataToSend);
try {
await writer.write(encodedData);
console.log("Sent:", dataToSend);
sendDataInput.value = ""; // 清空输入框
} catch (error) {
console.error("Error sending data:", error);
}
});
</script>
</body>
</html>
将这段代码保存为 HTML 文件,然后在支持 Web Serial API 的浏览器中打开。 你需要连接一个串口设备,并确保波特率设置为 9600 (或者你设备实际使用的波特率)。 然后,点击“连接串口”按钮,选择你的串口设备。 之后,你就可以在输入框里输入数据,点击“发送数据”按钮,数据就会通过串口发送出去,并原封不动地显示在文本框里。
高级用法:更灵活的数据处理
上面的例子只是一个简单的回显,实际应用中,我们可能需要更灵活地处理数据。 比如,我们可能需要解析串口设备发送的二进制数据,或者将网页上的数据转换成特定的格式再发送给串口设备。
1. 解析二进制数据
如果串口设备发送的是二进制数据,我们需要使用 DataView
对象来解析它。 DataView
对象可以让我们以不同的数据类型(比如 Int8
、Uint16
、Float32
)来读取 ArrayBuffer
中的数据。
async function receiveBinaryData(port, callback) {
try {
const reader = port.readable.getReader();
while (true) {
const { value, done } = await reader.read();
if (done) {
console.log("串口已断开连接");
break;
}
const dataView = new DataView(value.buffer);
// 假设前两个字节是设备ID,后四个字节是温度值
const deviceId = dataView.getUint16(0);
const temperature = dataView.getFloat32(2);
callback(deviceId, temperature);
}
} catch (error) {
console.error("接收二进制数据失败:", error);
} finally {
if (reader) {
reader.releaseLock();
}
}
}
在这个例子里,我们假设串口设备发送的数据格式是:前两个字节是设备 ID(Uint16
),后四个字节是温度值(Float32
)。 我们使用 dataView.getUint16(0)
和 dataView.getFloat32(2)
函数来读取这些数据。
2. 格式化数据
在发送数据之前,我们可能需要将网页上的数据转换成特定的格式。 比如,我们可能需要将一个 JSON 对象转换成一个字符串,或者将一个数字转换成一个字节数组。
async function sendFormattedData(port, data) {
try {
// 将 JSON 对象转换成字符串
const jsonString = JSON.stringify(data);
// 将字符串转换成字节数组
const encoder = new TextEncoder();
const encodedData = encoder.encode(jsonString);
const writer = port.writable.getWriter();
await writer.write(encodedData);
writer.releaseLock();
console.log("数据已发送:", jsonString);
} catch (error) {
console.error("发送数据失败:", error);
}
}
在这个例子里,我们首先使用 JSON.stringify()
函数将一个 JSON 对象转换成一个字符串。 然后,我们使用 TextEncoder
将字符串转换成一个字节数组,再通过串口发送出去。
安全性 considerations
Web Serial API 提供了与硬件设备直接交互的能力,这在带来便利的同时,也带来了一些安全风险。 我们需要注意以下几点:
- 权限管理: Web Serial API 必须在安全上下文(HTTPS)中使用,并且需要用户明确授权才能访问串口设备。 浏览器会弹出一个对话框,让用户选择要连接的串口设备。
- 数据验证: 我们需要对从串口接收到的数据进行验证,防止恶意代码注入。
- 设备隔离: Web Serial API 只能访问用户选择的串口设备,无法访问其他设备。
兼容性
Web Serial API 并不是所有浏览器都支持。 目前,Chrome 和 Edge 浏览器已经支持 Web Serial API,但 Safari 和 Firefox 还没有完全支持。 你可以使用 navigator.serial
来检测浏览器是否支持 Web Serial API。
if ("serial" in navigator) {
console.log("Web Serial API is supported!");
} else {
console.log("Web Serial API is not supported!");
}
表格总结
为了方便大家理解,我把 Web Serial API 的一些关键概念和函数整理成了一个表格:
概念/函数 | 描述 |
---|---|
navigator.serial |
Web Serial API 的入口,提供访问串口设备的能力。 |
SerialPort |
串口对象,表示一个已经连接的串口设备。 |
port.open(options) |
打开串口连接。 options 对象包含串口配置信息,比如 baudRate 。 |
port.close() |
关闭串口连接。 |
port.readable |
ReadableStream 对象,用于从串口读取数据。 |
port.writable |
WritableStream 对象,用于向串口写入数据。 |
reader.read() |
从 ReadableStream 中读取数据。 返回一个 Promise ,它会在有数据可读时 resolve。 |
writer.write(data) |
向 WritableStream 中写入数据。 data 必须是 Uint8Array 类型的字节数组。 |
TextEncoder |
用于将字符串编码成 Uint8Array 类型的字节数组。 |
TextDecoder |
用于将 Uint8Array 类型的字节数组解码成字符串。 |
DataView |
用于以不同的数据类型读取 ArrayBuffer 中的数据,比如 Int8 、Uint16 、Float32 。 |
总结:Web Serial API 的未来
Web Serial API 的出现,为 Web 应用与硬件设备之间的交互打开了一扇新的大门。 它可以让开发者直接用浏览器控制各种串口设备,而无需安装额外的驱动程序和桌面应用。 虽然 Web Serial API 目前还处于发展阶段,但它已经展现出了巨大的潜力。 相信随着 Web Serial API 的不断完善和普及,它将在物联网、机器人、嵌入式系统等领域发挥越来越重要的作用。
好了,今天的讲座就到这里。 希望大家通过今天的学习,能够对 Web Serial API 有一个初步的了解,并尝试用它来做一些有趣的项目。 记住,编程的乐趣在于实践,只有不断地尝试和探索,才能真正掌握一门技术。 感谢大家的收听!我们下次再见!