过去给一块 CircuitPython 板子刷固件、读传感器数据,你得先装个串口终端——macOS 上是 screen 或 minicom,Windows 上找 PuTTY,Linux 上折腾 stty 参数。现在 Firefox 和 Adafruit 联手把这件事搬进了浏览器:打开网页,选端口,直接读写串口,零桌面依赖。
Web Serial API:从 USB 到网页的一步直达
Web Serial API 是 Chromium 社区几年前提出的标准,允许网页通过 navigator.serial 直接与 USB/蓝牙串口设备通信。Firefox 此前一直没跟进,这次合作标志着它正式加入支持行列。
核心流程只有三步:
- 请求端口 —
navigator.serial.requestPort()弹出系统端口选择器,用户手动授权。 - 打开连接 — 设定波特率等参数,调用
port.open()。 - 读写数据 — 通过
ReadableStream/WritableStream收发字节流。
安全模型是关键:端口不会自动暴露给网页,必须由用户主动点击触发选择,且只对当前 origin 有效。这比桌面串口工具那种"谁都能读"的模型要严格得多。
Adafruit 的落地场景
Adafruit 的硬件生态以 CircuitPython 板子为主——Feather、ItsyBitsy、Trinket 系列。这些板子插上 USB 后,会以串口设备的形式出现在系统中,用户通常通过 REPL 交互。
合作带来的直接变化:
- Web-based REPL — 在 Adafruit 的网页工具里直接打开板子的 Python REPL,敲代码、看回显,不用装任何本地软件。
- 固件刷写 — 某些板子支持串口刷入
.uf2或.bin固件,网页可以直接完成这个流程。 - 传感器数据可视化 — 板子持续往外吐数据,网页用
ReadableStream持续读取,实时画图表。
这对教育场景尤其友好——学校机房不让随便装软件,但浏览器是现成的。
实战:在 Firefox 里连一块 CircuitPython 板子
下面这段代码可以在任何支持 Web Serial 的浏览器里运行。你只需要一块 CircuitPython 板子(或任何输出文本的串口设备),插上 USB,然后打开这个 HTML 文件。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Web Serial REPL</title>
<style>
body { font-family: monospace; max-width: 720px; margin: 2rem auto; }
#output { background: #1e1e1e; color: #d4d4d4; padding: 1rem;
height: 300px; overflow-y: auto; white-space: pre-wrap; }
#input-line { display: flex; gap: 0.5rem; margin-top: 0.5rem; }
#cmd { flex: 1; }
button { padding: 0.4rem 1rem; }
</style>
</head>
<body>
<h2>串口 REPL</h2>
<button id="connect">连接设备</button>
<button id="disconnect" disabled>断开</button>
<div id="output"></div>
<div id="input-line">
<input id="cmd" placeholder="输入命令,回车发送" disabled>
<button id="send" disabled>发送</button>
</div>
<script>
const connectBtn = document.getElementById('connect');
const disconnectBtn = document.getElementById('disconnect');
const outputDiv = document.getElementById('output');
const cmdInput = document.getElementById('cmd');
const sendBtn = document.getElementById('send');
let port = null;
let reader = null;
let writer = null;
let readableClosed = false;
let writableClosed = false;
// 连接串口
connectBtn.addEventListener('click', async () => {
if (!('serial' in navigator)) {
alert('当前浏览器不支持 Web Serial API');
return;
}
try {
port = await navigator.serial.requestPort();
// CircuitPython 板子默认 115200 波特率
await port.open({ baudRate: 115200 });
connectBtn.disabled = true;
disconnectBtn.disabled = false;
cmdInput.disabled = false;
sendBtn.disabled = false;
// 持续读取板子输出
const decoder = new TextDecoderStream();
readableClosed = port.readable.pipeTo(decoder.writable);
reader = decoder.readable.getReader();
readLoop();
// 准备写入通道
const encoder = new TextEncoderStream();
writableClosed = encoder.readable.pipeTo(port.writable);
writer = encoder.writable.getWriter();
} catch (e) {
outputDiv.textContent += `\n[错误] ${e.message}`;
}
});
// 持续读数据并显示
async function readLoop() {
try {
while (true) {
const { value, done } = await reader.read();
if (done) break;
outputDiv.textContent += value;
outputDiv.scrollTop = outputDiv.scrollHeight;
}
} catch (e) {
outputDiv.textContent += `\n[读取中断] ${e.message}`;
}
}
// 发送命令到板子
async function sendCommand(text) {
if (!writer) return;
await writer.write(text + '\r'); // REPL 需要 \r 而非 \n
}
sendBtn.addEventListener('click', () => {
sendCommand(cmdInput.value);
cmdInput.value = '';
});
cmdInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
sendCommand(cmdInput.value);
cmdInput.value = '';
}
});
// 断开连接
disconnectBtn.addEventListener('click', async () => {
reader.cancel();
await readableClosed.catch(() => {});
writer.close();
await writableClosed;
await port.close();
port = null; reader = null; writer = null;
connectBtn.disabled = false;
disconnectBtn.disabled = true;
cmdInput.disabled = true;
sendBtn.disabled = true;
outputDiv.textContent += '\n[已断开]';
});
</script>
</body>
</html>
使用前注意几点:
- Firefox 需要在设置中启用 Web Serial(目前可能仍在逐步 rollout,检查
dom.serial.enabled配置项)。 - 波特率
115200是 CircuitPython 的默认值;Arduino Uno 通常用9600,根据你的板子修改。 - REPL 交互用
\r(CR)而非\n(LF),这是 CircuitPython REPL 的约定,搞错了板子不会执行命令。 - 每次连接必须由用户点击"连接设备"按钮触发,这是安全模型的要求——网页不能静默连串口。
还能做什么:超出 REPL 的玩法
Web Serial 给出的只是字节流,怎么用是网页的事。几个值得尝试的方向:
- 实时数据仪表盘 — 板子上跑一段每秒输出 JSON 的代码,网页
JSON.parse后喂给 Chart.js,零延迟可视化。 - 批量固件部署 — 教室里有 20 块板子,学生逐个点击连接、网页自动刷入统一固件,比逐台装软件快得多。
- 自定义协议 — 不用 REPL,板子和网页约定二进制帧格式(头 + 长度 + 校验),网页侧用
DataView解析,适合传感器高频采样。
采纳前的权衡
| 优势 | 限制 |
|---|---|
| 零安装,任何有浏览器的机器都能用 | 浏览器必须支持 Web Serial,Safari 目前不支持 |
| 用户主动授权端口,安全边界清晰 | 每次刷新页面需重新连接(端口不持久化) |
| 流式 API 天然适合持续数据读取 | 大吞吐场景下流控需自行处理,没有硬件流控 RTS/CTS 的 API |
| 教育和工坊场景极度友好 | 复杂调试(断点、单步)仍需桌面 IDE |
如果你在做硬件工坊、STEM 课程,或者只是想快速读一块板子的传感器数据——现在可以直接打开 Firefox,不用再翻箱倒柜找串口驱动了。