瀏覽器已能接住點擊,今天要把這個操作送到 Python,再交給 MCU。
前一天完成 click 事件,每次點擊按鈕都能在瀏覽器 Console 留下紀錄,但命令仍停在前端。
今天加入 Socket.IO 與 WebUI,讓 JavaScript 發出 toggle_led,Python 收到後再呼叫 MCU 切換實體 LED。
JavaScript 需要先建立 Socket.IO 連線,才能將前端事件送到 MPU,io() 會建立 socket 物件,後續使用 socket.emit() 發出具名事件。
事件名稱固定使用 "toggle_led",這個名稱會同時出現在 JavaScript 與 Python,字串若有大小寫、底線或拼字差異,兩端就無法配對。
在 assets/index.html 的 app.js 前加入 Socket.IO 程式庫。
<script src="https://cdn.socket.io/4.7.2/socket.io.min.js"></script> <!-- 本日新增 -->
<script src="app.js"></script>
</body>
Socket.IO 必須先載入,app.js 才能使用 io(),若順序顛倒,瀏覽器會顯示找不到 io 的錯誤。
載入成功後,const socket = io(); 只建立一個通訊物件,不會自行送出切換命令,真正的事件仍要等 click callback 執行,這能將連線建立與使用者操作分成兩個清楚步驟。
HTML 的標題、狀態區與按鈕都維持 Day 24 版本,本日只增加通訊所需的 script。
將 assets/app.js 改成以下內容。
const socket = io(); // 本日新增
const statusEl = document.getElementById("status");
const toggleBtn = document.getElementById("toggle-btn");
toggleBtn.addEventListener("click", () => {
socket.emit("toggle_led", {}); // 本日新增
});
socket.emit("toggle_led", {}) 會送出事件名稱與一個空物件,本次切換不需要額外參數,因此資料內容保持 {}。
空物件仍是一份明確的事件資料,Python 端會在 data 參數收到它,日後若操作需要附帶其他內容,可以在同一個位置增加欄位,但本專案的切換命令不需要指定目標狀態,因為真正切換規則由 MCU 管理。
點擊時不再直接修改 statusEl,也不反轉 class,瀏覽器只提出要求,畫面要等 MCU 完成切換並回報後才更新。
Day 17 的 python/main.py 加入 WebUI,並登記前一天已準備好的 on_toggle_led()。
from arduino.app_utils import App, Bridge
from arduino.app_bricks.web_ui import WebUI
ui = WebUI()
def on_led_status(state: bool):
label = "ON" if state else "OFF"
message = {"status": label}
print("LED 狀態:", message["status"])
def on_toggle_led(client_id, data):
print("收到 Web 切換命令:", client_id, data) # 本日新增
Bridge.call("toggle_led")
Bridge.provide("led_status", on_led_status)
ui.on_message("toggle_led", on_toggle_led) # 本日新增
App.run()
ui.on_message() 將 Web 事件名稱與 Python callback 連在一起,收到事件時,WebUI 會提供 client_id 與 data,再由函式呼叫 MCU。
ui = WebUI() 必須在登記事件前建立,否則 Python 沒有可用的 WebUI 物件,ui.on_message() 也無法執行,匯入、建立物件、定義函式、登記事件與 App.run() 的順序應保持清楚。
client_id 可用來辨認是哪一個瀏覽器連線送出操作,data 則是 JavaScript 傳來的空物件,本篇先將兩者印出,方便確認事件確實抵達 MPU。
按下 Run 並開啟管理頁面,點擊「切換 LED」,Python Console 應顯示收到 Web 命令,D2 LED 也應切換一次。
完整路徑為瀏覽器 click、socket.emit()、Python on_toggle_led()、Bridge.call()、MCU on_toggle_led(),最後進入既有的 toggle_led()。
若 Console 沒有訊息,先檢查 Socket.IO 是否成功載入,以及 JavaScript 與 Python 是否都使用 "toggle_led",若 Python 收到訊息但 LED 不動,再檢查 Bridge 與 MCU 登記。
可先保留 Python 的除錯輸出,確認 client_id 與 {} 每次點擊都會出現一次,若瀏覽器連點五次,Python 也應收到五次事件,這能先驗證 Web 到 MPU,再繼續追蹤 Bridge。
畫面暫時仍維持原狀
MCU 切換後雖然會呼叫 led_status,Python 也能顯示 ON/OFF,但本日尚未將結果推送回瀏覽器,因此網頁可能仍顯示原本的 OFF。
這不是狀態錯誤,而是回傳路徑尚未完成,前端已能控制現場,卻還沒有接收真正結果,下一篇會補上 Python 到 Web 的主動更新。
本日驗收應分成兩段,先以瀏覽器 Console 確認 click callback 與 Socket.IO 沒有錯誤,再以 Python Console 與實體 LED 確認命令抵達 MPU 與 MCU,只有兩段都成立,才能判定 Web 控制鏈完成,而不能只因按鈕有 active 外觀就視為成功。
完成測試後,保留一筆 Web 命令與一次 LED 切換的對應紀錄,確認事件沒有遺失或重複送出,Python 每次都只呼叫 MCU 一次。
完成後應同時看到 Python 接收紀錄與實體 LED 變化,才能證明命令真的跨越兩層。
io() 建立 Socket.IO 連線。toggle_led 事件。ui.on_message() 接收 Web 命令。現在 Web 操作已能一路送到 MCU 並切換實體 LED,但管理畫面還不知道真正結果,下一篇要讓 Python 主動把 MCU 回報推回瀏覽器。
下一篇|用 Python 主動更新網頁狀態