訊息入口已經分好,今天要讓 Edge Service 不再印完就結束,而是留在 MPU 上等待事件。
前一天建立了 on_led_status() 與 on_toggle_led(),程式已經知道不同訊息應該進入不同函式。
今天要補上服務程式最重要的一步,讓 Python 持續運行,否則事件入口雖然寫好了,程式結束後也沒有人能接住後續訊息。
一般 Python 腳本會按照順序執行,最後一行完成後程序便結束,Day 11 與 Day 12 的目的只是確認 Python 能執行,因此這種結果沒有問題。
Edge Service 則要等待之後才發生的按鈕回報、Bridge 呼叫與 Web 事件,若程序已經結束,前面建立的 callback 只剩下程式碼外觀,實際上沒有機會被觸發。
App 位於 arduino.app_utils,使用前要先將它匯入,今天只加入保持服務運行的功能,尚未匯入 Bridge,也不傳送任何 MCU 命令。
from arduino.app_utils import App # 本日新增
service_name = "Intersection Edge Service"
service_ready = True
def on_led_status(state: bool):
pass
def on_toggle_led(client_id, data):
pass
if service_ready:
print("Service ready:", service_name)
else:
print("Service not ready")
App.run() # 本日新增
from ... import ... 會從指定模組取出需要的功能,這裡只匯入 App,讓程式可以呼叫 App.run(),沒有使用到的模組不提前加入,後續閱讀時比較容易看出當天增加了什麼。
App.run() 放在檔案最後,前面的變數、函式與啟動訊息會先完成,接著程式進入持續運行狀態,不再像一般腳本一樣立刻返回。
它不是把前面的程式從頭反覆執行,print() 仍只會在啟動時出現一次,兩個 callback 也只會在未來收到對應事件時才執行,這種模式稱為事件驅動,服務平常保持等待,有事件才交給指定函式處理。
這種等待不是反覆印出文字,也不是用無限迴圈不斷消耗資源,而是讓 App 保持可接收事件的狀態,直到使用者停止執行或重新部署專案。
初學時常會想到自己撰寫 while True 讓程式不要結束,但本專案後續還要交由 App 接收 Bridge 與 Web 事件,若自行建立一個不受管理的忙碌迴圈,反而可能占住執行流程,因此直接使用 App Lab 提供的 App.run() 會更符合這套架構。
因為 App.run() 會阻塞目前流程,正常情況下不應將還需要立即執行的初始化程式放在它後面,否則那些內容要等服務停止後才有機會執行。
按下 Run 後,Python Console 先顯示服務已準備完成,接著不再出現新的文字,但執行狀態會持續保留,這種安靜等待正是本日要得到的結果。
驗證時不要因為 Console 沒有繼續捲動就判斷程式停止,應先查看 App 是否仍在執行,再操作 D9 確認 MCU 仍有反應,本日的成功畫面本來就只有一次啟動訊息與持續待命狀態。
如果 Console 顯示匯入錯誤,先確認模組名稱是否為 arduino.app_utils,類別名稱是否使用大寫 App,以及 App.run() 的大小寫與括號是否完整。
Python 留在等待狀態時,按下 D9 實體按鈕,D2 LED 仍應一次切換一次,因為 App.run() 只讓 MPU 服務保持運作,並沒有接管 MCU 的 loop()。
這項測試再次確認兩顆大腦的工作邊界,MCU 繼續處理即時現場操作,MPU 則建立長時間運行的上層服務,任何一邊新增功能都不應讓另一邊原有能力失效。
arduino.app_utils 匯入 App。App.run() 讓 Python 長時間運行。現在 Edge Service 已經能長時間待命,但 MCU 與 MPU 仍各做各的事,下一篇要加入 Bridge,讓 Python 第一次向 MCU 下達切換號誌的命令。
下一篇|搭建 MPU 到 MCU 的第一座橋樑