1. 简介
上次更新日期:2020 年 7 月 21 日
构建内容
在此 Codelab 中,您将构建一个使用 Web Serial API 与 BBC micro:bit 板互动的网页,以在其 5x5 LED 矩阵上显示图片。您将了解 Web Serial API,以及如何使用可读、可写和转换数据流通过浏览器与串行设备进行通信。

学习内容
- 如何打开和关闭 Web 串行端口
- 如何使用读取循环来处理输入流中的数据
- 如何通过写入流发送数据
所需条件
- 一块搭载最新 Espruino 固件的 BBC micro:bit 板
- 最新版 Chrome(80 或更高版本)
- 了解 HTML、CSS、JavaScript 和 Chrome 开发者工具
我们之所以选择使用 micro:bit 来完成此 Codelab,是因为它价格实惠,提供了一些输入(按钮)和输出(5x5 LED 显示屏),并且可以提供额外的输入和输出。如需详细了解 micro:bit 的功能,请参阅 Espruino 网站上的 BBC micro:bit 页面。
2. Web Serial API 简介
Web Serial API 为网站提供了一种使用脚本对串行设备执行读写操作的方式。该 API 允许网站与微控制器和 3D 打印机等串行设备进行通信,从而将网络与现实世界联系起来。
有许多使用 Web 技术构建的控制软件示例。例如:
在某些情况下,这些网站会通过用户手动安装的原生代理应用与设备通信。在其他情况下,应用通过 Electron 等框架以打包的原生应用形式交付。在其他情况下,用户需要执行额外的步骤,例如使用 USB 闪存盘将编译后的应用复制到设备。
通过在网站与其控制的设备之间提供直接通信,可以改善用户体验。
3. 准备工作
获取代码
我们已将您完成此 Codelab 所需的一切都放入一个 Glitch 项目中。
- 打开新的浏览器标签页,然后前往 https://web-serial-codelab-start.glitch.me/。
- 点击重新合成 Glitch 链接,创建您自己的初始项目版本。
- 点击显示按钮,然后选择在新窗口中,即可查看代码的实际效果。
4. 打开串行连接
检查是否支持 Web Serial API
首先,您需要检查当前浏览器是否支持 Web Serial API。为此,请检查 serial 是否在 navigator 中。
在 DOMContentLoaded 事件中,将以下代码添加到您的项目中:
script.js - DOMContentLoaded
// CODELAB: Add feature detection here.
const notSupported = document.getElementById('notSupported');
notSupported.classList.toggle('hidden', 'serial' in navigator);
此检查用于确定是否支持 Web Serial。如果支持,此代码会隐藏显示“不支持 Web Serial”的横幅。
试试看
- 加载网页。
- 验证该网页是否未显示红色横幅,指出 Web Serial 不受支持。
打开串行端口
接下来,我们需要打开串行端口。与大多数其他现代 API 一样,Web Serial API 是异步的。这可防止界面在等待输入时被阻塞,但也很重要,因为网页随时可能会收到串行数据,我们需要一种方法来监听这些数据。
由于一台计算机可能具有多个串行设备,因此当浏览器尝试请求端口时,会提示用户选择要连接的设备。
将以下代码添加到您的项目中:
script.js - connect()
// CODELAB: Add code to request & open port here.
// - Request a port and open a connection.
port = await navigator.serial.requestPort();
// - Wait for the port to open.
await port.open({ baudrate: 9600 });
requestPort 调用会提示用户选择要连接的设备。调用 port.open 会打开端口。我们还需要提供与串行设备通信的速度。BBC micro:bit 在 USB 转串行芯片和主处理器之间使用 9600 波特的连接。
我们还要关联连接按钮,让它在用户点击时调用 connect()。
将以下代码添加到您的项目中:
script.js - clickConnect()
// CODELAB: Add connect code here.
await connect();
试试看
现在,我们的项目已具备最基本的启动条件。点击连接按钮会提示用户选择要连接的串行设备,然后连接到 micro:bit。
- 重新加载页面。
- 点击连接按钮。
- 在“串行端口选择器”对话框中,选择 BBC micro:bit 设备,然后点击连接。
- 在该标签页上,您应该会看到一个图标,指示您已连接到串行设备:

设置输入流以监听来自串行端口的数据
连接建立后,我们需要设置一个输入流和一个读取器,以便从设备读取数据。首先,我们将通过调用 port.readable 从端口获取可读数据流。由于我们知道会从设备收到文本,因此我们会通过文本解码器对其进行管道传输。接下来,我们将获取一个 reader 并启动读取循环。
将以下代码添加到您的项目中:
script.js - connect()
// CODELAB: Add code to read the stream here.
let decoder = new TextDecoderStream();
inputDone = port.readable.pipeTo(decoder.writable);
inputStream = decoder.readable;
reader = inputStream.getReader();
readLoop();
读取循环是一个异步函数,它在循环中运行并等待内容,而不会阻塞主线程。当有新数据到达时,读取器会返回两个属性:value 和一个 done 布尔值。如果 done 为 true,则表示端口已关闭或没有更多数据传入。
将以下代码添加到您的项目中:
script.js - readLoop()
// CODELAB: Add read loop here.
while (true) {
const { value, done } = await reader.read();
if (value) {
log.textContent += value + '\n';
}
if (done) {
console.log('[readLoop] DONE', done);
reader.releaseLock();
break;
}
}
试试看
我们的项目现在可以连接到设备,并将从设备收到的任何数据附加到日志元素。
- 重新加载页面。
- 点击连接按钮。
- 在“串行端口选择器”对话框中,选择 BBC micro:bit 设备,然后点击连接。
- 您应该会看到 Espruino 徽标:

设置输出流以将数据发送到串行端口
串行通信通常是双向的。除了从串行端口接收数据之外,我们还希望向该端口发送数据。与输入流一样,我们只会通过输出流向 micro:bit 发送文本。
首先,创建一个文本编码器流,并将该流通过管道传输到 port.writeable。
script.js - connect()
// CODELAB: Add code setup the output stream here.
const encoder = new TextEncoderStream();
outputDone = encoder.readable.pipeTo(port.writable);
outputStream = encoder.writable;
通过串行与 Espruino 固件连接时,BBC micro:bit 板充当 JavaScript 读取-求值-输出循环 (REPL),类似于在 Node.js shell 中获得的效果。接下来,我们需要提供一种将数据发送到流的方法。以下代码从输出流中获取写入器,然后使用 write 发送每一行。发送的每一行都包含一个换行符 (\n),用于告知 micro:bit 评估发送的命令。
script.js - writeToStream()
// CODELAB: Write to output stream
const writer = outputStream.getWriter();
lines.forEach((line) => {
console.log('[SEND]', line);
writer.write(line + '\n');
});
writer.releaseLock();
为了使系统进入已知状态并停止回显我们发送的字符,我们需要发送 CTRL-C 并关闭回显。
script.js - connect()
// CODELAB: Send CTRL-C and turn off echo on REPL
writeToStream('\x03', 'echo(false);');
试试看
现在,我们的项目可以从 micro:bit 发送和接收数据了。我们来验证一下是否可以正常发送命令:
- 重新加载页面。
- 点击连接按钮。
- 在“串行端口选择器”对话框中,选择 BBC micro:bit 设备,然后点击连接。
- 在 Chrome 开发者工具中打开控制台标签页,然后输入
writeToStream('console.log("yes")');
您应该会在网页上看到类似以下内容:

5. 控制 LED 矩阵
构建矩阵网格字符串
如需控制 micro:bit 上的 LED 矩阵,我们需要调用 show()。此方法可在内置的 5x5 LED 屏幕上显示图形。此函数接受二进制数或字符串。
我们将迭代处理复选框,并生成一个包含 1 和 0 的数组,用于指示哪些复选框处于选中状态,哪些未处于选中状态。然后,我们需要反转数组,因为复选框的顺序与矩阵中 LED 的顺序相反。接下来,我们将数组转换为字符串,并创建要发送到 micro:bit 的命令。
script.js - sendGrid()
// CODELAB: Generate the grid
const arr = [];
ledCBs.forEach((cb) => {
arr.push(cb.checked === true ? 1 : 0);
});
writeToStream(`show(0b${arr.reverse().join('')})`);
将复选框与矩阵相关联以更新矩阵
接下来,我们需要监听复选框的变化,并在复选框发生变化时将相应信息发送到 micro:bit。在功能检测代码 (// CODELAB: Add feature detection here.) 中,添加以下行:
script.js - DOMContentLoaded
initCheckboxes();
我们还可以在 micro:bit 首次连接时重置网格,以便显示笑脸。我们已提供 drawGrid() 函数。此函数的工作方式与 sendGrid() 类似;它接受一个包含 1 和 0 的数组,并根据需要选中复选框。
script.js - clickConnect()
// CODELAB: Reset the grid on connect here.
drawGrid(GRID_HAPPY);
sendGrid();
试试看
现在,当网页打开与 micro:bit 的连接时,它会发送一个笑脸。点击复选框会更新 LED 矩阵上的显示内容。
- 重新加载页面。
- 点击连接按钮。
- 在“串行端口选择器”对话框中,选择 BBC micro:bit 设备,然后点击连接。
- 您应该会在 micro:bit LED 矩阵上看到一个笑脸。
- 通过更改复选框在 LED 矩阵上绘制不同的图案。
6. 连接 micro:bit 按钮
在 micro:bit 按钮上添加手表事件
micro:bit 上有两个按钮,分别位于 LED 矩阵的两侧。Espruino 提供了一个 setWatch 函数,用于在按下按钮时发送事件/回调。由于我们希望同时监听这两个按钮,因此我们将使函数具有通用性,并让其打印事件的详细信息。
script.js - watchButton()
// CODELAB: Hook up the micro:bit buttons to print a string.
const cmd = `
setWatch(function(e) {
print('{"button": "${btnId}", "pressed": ' + e.state + '}');
}, ${btnId}, {repeat:true, debounce:20, edge:"both"});
`;
writeToStream(cmd);
接下来,我们需要在每次将串行端口连接到设备时,连接两个按钮(在 micro:bit 板上分别命名为 BTN1 和 BTN2)。
script.js - clickConnect()
// CODELAB: Initialize micro:bit buttons.
watchButton('BTN1');
watchButton('BTN2');
试试看
除了在连接时显示笑脸外,按下 micro:bit 上的任一按钮都会在网页上添加文本,指明按下了哪个按钮。最有可能的是,每个字符都将单独占一行。
- 重新加载页面。
- 点击连接按钮。
- 在“串行端口选择器”对话框中,选择 BBC micro:bit 设备,然后点击连接。
- 您应该会在 micro:bit 的 LED 矩阵上看到笑脸。
- 按 micro:bit 上的按钮,并验证它是否将新文本附加到页面,其中包含所按按钮的详细信息。
7. 使用转换流解析传入的数据
基本流处理
当按下某个 micro:bit 按钮时,micro:bit 会通过数据流将数据发送到串行端口。流非常有用,但也会带来挑战,因为您不一定能一次性获取所有数据,而且数据可能会被任意分块。
该应用目前会按传入流的到达顺序(在 readLoop 中)打印传入流。在大多数情况下,每个字符都位于单独的一行中,但这并不是很有用。理想情况下,数据流应解析为单独的行,每条消息都显示为单独的一行。
使用 TransformStream 转换数据流
为此,我们可以使用转换流 ( TransformStream),这样就可以解析传入的流并返回解析后的数据。转换流可以位于流来源(在本例中为 micro:bit)和使用流的任何内容(在本例中为 readLoop)之间,并且可以在最终使用之前应用任意转换。您可以将其想象成一条装配线:当 widget 沿着装配线移动时,装配线中的每个步骤都会修改该 widget,这样当它到达最终目的地时,就成为一个功能齐全的 widget。
如需了解详情,请参阅 MDN 的 Streams API 概念。
使用 LineBreakTransformer 转换流
我们来创建一个 LineBreakTransformer 类,该类将接收一个信息流,并根据换行符 (\r\n) 将其分块。该类需要两个方法:transform 和 flush。每当流收到新数据时,系统都会调用 transform 方法。它可以将数据加入队列,也可以保存数据以供日后使用。当流关闭时,系统会调用 flush 方法,该方法会处理尚未处理的任何数据。
在 transform 方法中,我们将向 container 添加新数据,然后检查 container 中是否有任何换行符。如果有,则将其拆分为一个数组,然后遍历这些行,并调用 controller.enqueue() 以发送解析后的行。
script.js - LineBreakTransformer.transform()
// CODELAB: Handle incoming chunk
this.container += chunk;
const lines = this.container.split('\r\n');
this.container = lines.pop();
lines.forEach(line => controller.enqueue(line));
当流关闭时,我们只需使用 enqueue 将容器中的所有剩余数据刷新出去。
script.js - LineBreakTransformer.flush()
// CODELAB: Flush the stream.
controller.enqueue(this.container);
最后,我们需要通过新的 LineBreakTransformer 管道传输传入的流。原始输入流仅通过 TextDecoderStream 进行了管道传输,因此我们需要添加额外的 pipeThrough,以便通过新的 LineBreakTransformer 进行管道传输。
script.js - connect()
// CODELAB: Add code to read the stream here.
let decoder = new TextDecoderStream();
inputDone = port.readable.pipeTo(decoder.writable);
inputStream = decoder.readable
.pipeThrough(new TransformStream(new LineBreakTransformer()));
试试看
现在,当您按下某个 micro:bit 按钮时,打印的数据应以单行形式返回。
- 重新加载页面。
- 点击连接按钮。
- 在“串行端口选择器”对话框中,选择 BBC micro:bit 设备,然后点击连接。
- 您应该会在 micro:bit LED 矩阵上看到一个笑脸。
- 按 micro:bit 上的按钮,并验证您是否看到类似以下内容:

使用 JSONTransformer 转换流
我们可以在 readLoop 中尝试将字符串解析为 JSON,但我们不妨创建一个非常简单的 JSON 转换器,将数据转换为 JSON 对象。如果数据不是有效的 JSON,则直接返回传入的数据。
script.js - JSONTransformer.transform
// CODELAB: Attempt to parse JSON content
try {
controller.enqueue(JSON.parse(chunk));
} catch (e) {
controller.enqueue(chunk);
}
接下来,在流经过 LineBreakTransformer 后,通过 JSONTransformer 传输该流。这样一来,我们就可以保持 JSONTransformer 的简单性,因为我们知道 JSON 始终只会以单行形式发送。
script.js - connect
// CODELAB: Add code to read the stream here.
let decoder = new TextDecoderStream();
inputDone = port.readable.pipeTo(decoder.writable);
inputStream = decoder.readable
.pipeThrough(new TransformStream(new LineBreakTransformer()))
.pipeThrough(new TransformStream(new JSONTransformer()));
试试看
现在,当您按下 micro:bit 的某个按钮时,您应该会在网页上看到输出的 [object Object]。
- 重新加载页面。
- 点击连接按钮。
- 在“串行端口选择器”对话框中,选择 BBC micro:bit 设备,然后点击连接。
- 您应该会在 micro:bit LED 矩阵上看到一个笑脸。
- 按 micro:bit 上的按钮,并验证您是否看到类似以下内容:
响应按钮按下动作
为了响应 micro:bit 的按钮按压,请更新 readLoop 以检查其收到的数据是否为具有 button 属性的 object。然后,调用 buttonPushed 来处理按钮按压。
script.js - readLoop()
const { value, done } = await reader.read();
if (value && value.button) {
buttonPushed(value);
} else {
log.textContent += value + '\n';
}
当 micro:bit 按钮被按下时,LED 矩阵上的显示内容应发生变化。使用以下代码设置矩阵:
script.js - buttonPushed()
// CODELAB: micro:bit button press handler
if (butEvt.button === 'BTN1') {
divLeftBut.classList.toggle('pressed', butEvt.pressed);
if (butEvt.pressed) {
drawGrid(GRID_HAPPY);
sendGrid();
}
return;
}
if (butEvt.button === 'BTN2') {
divRightBut.classList.toggle('pressed', butEvt.pressed);
if (butEvt.pressed) {
drawGrid(GRID_SAD);
sendGrid();
}
}
试试看
现在,当您按下某个 micro:bit 按钮时,LED 矩阵应会变为笑脸或哭脸。
- 重新加载页面。
- 点击连接按钮。
- 在“串行端口选择器”对话框中,选择 BBC micro:bit 设备,然后点击连接。
- 您应该会在 micro:bit 的 LED 矩阵上看到笑脸。
- 按下 micro:bit 上的按钮,并验证 LED 矩阵是否发生变化。
8. 关闭串行端口
最后一步是将断开连接功能挂钩,以便在用户完成操作后关闭端口。
在用户点击“连接/断开连接”按钮时关闭端口
当用户点击连接/断开连接按钮时,我们需要关闭连接。如果端口已打开,请调用 disconnect() 并更新界面,以指示网页不再连接到串行设备。
script.js - clickConnect()
// CODELAB: Add disconnect code here.
if (port) {
await disconnect();
toggleUIConnected(false);
return;
}
关闭流和端口
在 disconnect 函数中,我们需要关闭输入流、关闭输出流和关闭端口。如需关闭输入源,请调用 reader.cancel()。对 cancel 的调用是异步的,因此我们需要使用 await 等待其完成:
script.js - disconnect()
// CODELAB: Close the input stream (reader).
if (reader) {
await reader.cancel();
await inputDone.catch(() => {});
reader = null;
inputDone = null;
}
如需关闭输出流,请获取 writer,调用 close(),然后等待 outputDone 对象关闭:
script.js - disconnect()
// CODELAB: Close the output stream.
if (outputStream) {
await outputStream.getWriter().close();
await outputDone;
outputStream = null;
outputDone = null;
}
最后,关闭串行端口并等待其关闭:
script.js - disconnect()
// CODELAB: Close the port.
await port.close();
port = null;
试试看
现在,您可以随意打开和关闭串行端口。
- 重新加载页面。
- 点击连接按钮。
- 在“串行端口选择器”对话框中,选择 BBC micro:bit 设备,然后点击连接。
- 您应该会在 micro:bit LED 矩阵上看到一个笑脸
- 按下断开连接按钮,验证 LED 矩阵是否关闭,以及控制台中是否没有错误。
9. 恭喜
恭喜!您已成功构建了第一个使用 Web Serial API 的 Web 应用。
请访问 https://goo.gle/fugu-api-tracker,及时了解 Web Serial API 以及 Chrome 团队正在开发的所有其他令人兴奋的新 Web 功能。