1. 簡介
上次更新時間:2022 年 9 月 19 日
建構項目
在本程式碼研究室中,您將建構一個網頁,使用 Web Serial API 與 BBC micro:bit 面板互動,以便在 5x5 LED 矩陣上顯示圖片。您將瞭解 Web Serial API,以及如何使用可讀取、可寫入和轉換串流,透過瀏覽器與序列裝置通訊。
課程內容
- 如何開啟及關閉網路序列埠
- 如何使用讀取迴圈處理輸入串流的資料
- 如何透過寫入串流傳送資料
軟硬體需求
- BBC micro:bit v1 板,搭載 Espruino 韌體 2v04
- 最新版 Chrome (80 以上版本)
- 瞭解 HTML、CSS、JavaScript 和 Chrome 開發人員工具
我們選擇在本程式碼研究室中使用 micro:bit v1,因為這個程式庫價格實惠、提供幾項輸入 (按鈕) 和輸出內容 (5x5 LED 顯示螢幕),並提供額外的輸入和輸出內容。如要進一步瞭解 micro:bit 的功能,請參閱 Espruino 網站上的 BBC micro:bit 頁面。
2. 關於 Web Serial API
Web Serial API 可讓網站使用指令碼讀取序列裝置並寫入資料。這個 API 可讓網站與序列裝置 (例如微控制器和 3D 印表機) 通訊,連結網路與實體世界。
許多控制軟體都是使用網路技術建構而成。例如:
在某些情況下,這些網站會透過使用者手動安裝的原生代理程式應用程式與裝置進行通訊。在其他情況下,應用程式會透過 Electron 等架構,以封裝的原生應用程式的形式傳遞。而在其他情況下,使用者必須執行額外步驟,例如使用 USB 隨身碟將已編譯的應用程式複製到裝置上。
您可以讓網站與其控制的裝置直接通訊,藉此改善使用者體驗。
3. 開始設定
取得程式碼
我們已將本程式碼研究室所需的各項資源,都放在 Glitch 專案中。
- 開啟新的瀏覽器分頁,然後前往 https://web-serial-codelab-start.glitch.me/。
- 按一下「Remix 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 也是非同步的。這可避免 UI 在等待輸入時遭到封鎖,但這也非常重要,因為網頁隨時可能會收到序列資料,而我們需要能夠監聽這類資料。
由於電腦可能有多部序列裝置,當瀏覽器嘗試要求通訊埠時,系統會提示使用者選擇要連線的裝置。
將下列程式碼加入專案:
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。
- 請重新載入頁面。
- 按一下「Connect」按鈕。
- 在「序列埠選擇器」對話方塊中,選取 BBC micro:bit 裝置,然後按一下「Connect」。
- 分頁上會顯示一個圖示,表示您已連線至序列裝置:
設定輸入串流來監聽序列埠的資料
建立連線後,我們還需要設定輸入串流和讀取器,才能從裝置讀取資料。首先,透過呼叫 port.readable
從通訊埠取得可讀取的串流。我們知道系統會從裝置傳回文字,因此將以文字解碼器傳遞至文字。接下來,我們會取得讀取者並開始讀取迴圈。
將下列程式碼加入專案:
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;
}
}
試用
我們的專案現在可以連線至裝置,並將從裝置收到的任何資料附加至記錄元素。
- 請重新載入頁面。
- 按一下「Connect」按鈕。
- 在「序列埠選擇器」對話方塊中,選取 BBC micro:bit 裝置,然後按一下「Connect」。
- 您應該會看到 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;
當 BBC micro:bit 板由序列連線至 Espruino 韌體時,就會充當 JavaScript 讀取-評估-列印迴圈 (REPL),類似於 Node.js 殼層中的功能。接下來,我們需要提供一種方法,將資料傳送至串流。以下程式碼會從輸出串流取得寫入器,然後使用 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 的資料。我們來驗證是否能正確傳送指令:
- 請重新載入頁面。
- 按一下「Connect」按鈕。
- 在「序列埠選擇器」對話方塊中,選取 BBC micro:bit 裝置,然後按一下「Connect」。
- 在 Chrome 開發人員工具中開啟「Console」分頁,然後輸入
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();
我們也要讓微控制器首次連線時重設格線,以便顯示笑臉。先前已提供 drawGrid()
函式。這個函式的運作方式與 sendGrid()
類似;這個方法會採用 1 和 0 的陣列,並視情況勾選核取方塊。
script.js - clickConnect()
// CODELAB: Reset the grid on connect here.
drawGrid(GRID_HAPPY);
sendGrid();
試用
現在,當頁面開啟與 micro:bit 的連線時,就會傳送一個開心的臉。勾選核取方塊即可更新 LED 矩陣上的顯示畫面。
- 請重新載入頁面。
- 按一下「Connect」按鈕。
- 在「序列埠選擇器」對話方塊中,選取 BBC micro:bit 裝置,然後按一下「Connect」。
- 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);
接下來,每次在序列埠連接到裝置時,我們需要將兩個按鈕 (名為 BTN1 和 micro:bit 板上的 BTN2) 連接在一起。
script.js - clickConnect()
// CODELAB: Initialize micro:bit buttons.
watchButton('BTN1');
watchButton('BTN2');
試用
除了在連線時顯示笑臉外,只要按下 micro:bit 上的其中一個按鈕,就會在頁面中加入說明已按下哪個按鈕的文字。每個字元都會顯示在各自的一行。
- 請重新載入頁面。
- 按一下「Connect」按鈕。
- 在「Serial Port」(序列埠) 選擇器對話方塊中,選取 BBC micro:bit 裝置,然後按一下「Connect」(連線)。
- micro:bit LED 矩陣上應該會顯示笑臉。
- 按下 micro:bit 上的按鈕,確認系統會在頁面中附加新文字,並顯示按下按鈕的詳細資料。
7. 使用轉換串流剖析傳入的資料
基本串流處理
按下其中一個 micro:bit 按鈕時,micro:bit 會透過串流將資料傳送至序列埠。串流相當實用,但也很困難,因為您不一定一次取得所有資料,而且可能會任意分割。
應用程式目前會在收到串流時列印傳入串流 (在 readLoop
中)。在大多數情況下,每個字元都是獨立一行,但這樣沒有很實用。在理想情況下,串流應剖析為每一行,且每則訊息會分別以不同的行顯示。
使用 TransformStream
轉換串流
為此,我們可以使用轉換串流 (TransformStream
),這樣就能剖析傳入的串流並傳回剖析資料。轉換串流可位於串流來源 (在本例中為 micro:bit) 之間,以及正在使用串流的任何項目 (在本例中為 readLoop
),並可在最終使用前套用任意轉換。您可以將其視為裝配線:當小工具進入裝配線時,每個步驟都會修改小工具,因此在到達最終目的地時,小工具就會完全運作。
詳情請參閱 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 按鈕時,顯示的資料應以單行傳回。
- 請重新載入頁面。
- 按一下「Connect」按鈕。
- 在「序列埠選擇器」對話方塊中,選取 BBC micro:bit 裝置,然後按一下「Connect」。
- 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]
。
- 請重新載入頁面。
- 按一下「Connect」按鈕。
- 在「序列埠選擇器」對話方塊中,選取 BBC micro:bit 裝置,然後按一下「Connect」。
- 您應該會在 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 矩陣應會變更為開心的表情或難過的表情。
- 請重新載入頁面。
- 按一下「Connect」按鈕。
- 在「Serial Port」(序列埠) 選擇器對話方塊中,選取 BBC micro:bit 裝置,然後按一下「Connect」(連線)。
- micro:bit LED 矩陣上應該會顯示笑臉。
- 按下 micro:bit 上的按鈕,並確認 LED 矩陣有變。
8. 關閉序列埠
最後一步是連接中斷連線的功能,在使用者執行完畢後關閉通訊埠。
使用者點選「連線」/「中斷連線」按鈕時關閉通訊埠
當使用者按一下「連線」/「中斷連線」按鈕時,我們必須關閉連線。如果通訊埠已開啟,請呼叫 disconnect()
並更新 UI,表示頁面已不再與序列裝置連線。
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;
試用
您現在可以隨意開啟和關閉序列埠。
- 請重新載入頁面。
- 按一下「Connect」按鈕。
- 在「序列埠選擇器」對話方塊中,選取 BBC micro:bit 裝置,然後按一下「Connect」。
- 你應該會看到 micro:bit LED 矩陣上顯示的笑臉
- 按下「Disconnect」按鈕,確認 LED 矩陣關閉,且主控台沒有任何錯誤。
9. 恭喜
恭喜!您已成功建立第一個使用 Web Serial API 的網頁應用程式。
請留意 https://goo.gle/fugu-api-tracker,瞭解 Web Serial API 的最新消息,以及 Chrome 團隊目前正在開發的其他令人期待的新網路功能。