在上一篇 [Day 04 - 最古老也最可靠:使用 UART 打造邊緣設備的 Daisy Chain 通訊] 中,我們解決了 Edge Node(邊緣閘道器)與感測器之間的底層通訊問題。但在邊緣運算(Edge Computing)的實際部署場景中,我們面臨著另一個更直接的挑戰:這是一台沒有螢幕的無頭設備 (Headless Device)。
當這台 Raspberry Pi 被放置在偏鄉診所或是吵雜的醫療推車上,沒有 HDMI 螢幕、沒有鍵盤,現場的護理人員該如何知道「設備開機成功了嗎?」、「資料正在傳輸嗎?」還是「系統當機了?」
今天,我們就來聊聊無頭設備的設計哲學——當硬體失去螢幕,它該如何說話?
[問題情境] 失去螢幕的恐懼
想像一個場景:護理師將感測器接上 EdgeNode,接著把 EdgeNode 透過 USB 插上醫療電腦。此時,電腦螢幕上沒有跳出任何視窗,Raspberry Pi 只發出了微弱的電流聲。
「這台機器壞了嗎?」護理師心想。 於是她拔掉 USB 再重插一次。就在她拔掉的那一瞬間,其實系統剛好正在寫入最後一筆關鍵的生理特徵資料,結果因為不正常斷電,導致檔案系統損毀(這點我們會在第三階段的 FAT32 篇章詳細討論)。
沒有螢幕的設備,最大的敵人就是使用者的「不安全感」。 為了解決這個問題,我們決定為 EdgeNode 加上一顆簡單的 RGB LED 燈。我們天真地以為,只要會亮燈,問題就解決了。
[錯誤嘗試] 讓每個模組自己爭奪燈號控制權
一開始,我們的實作非常粗暴。既然我們有網路模組 (Network)、USB 控制模組 (USB OTG)、資料同步模組 (Data Sync),那我們就在每個 Python 腳本裡面都匯入 GPIO 控制套件。
當網路連線斷線時:網路模組呼叫 led.red_blink()
當 USB 偵測到隨身碟拔除時:USB 模組呼叫 led.yellow_blink()
當資料正在下載時:資料模組呼叫 led.blue_solid()
結果就是一場災難。
當網路斷線(應該閃紅燈)的同時,如果資料剛好下載完畢觸發了綠燈,結果就是紅燈閃到一半突然變綠燈,然後又變回紅燈。燈號變成了一場電子花車秀,不只開發者看不懂,使用者更是一頭霧水。
我們犯了一個嚴重的軟體工程錯誤:缺乏統一的單一信任源 (Single Source of Truth) 與優先權機制。
[底層原理] 建立共通的「視覺語彙」(Visual Vocabulary)
既然不能讓模組各自為政,我們必須退一步思考。人類對顏色的直覺反應是有共通性的,在設計 Headless 設備的 UI/UX 時,我們參考了工業標準,定義了一套 EdgeNode 燈號交互邏輯規範。
我們使用的是一顆常見的 VRBG (共陰極 Common Cathode) LED 模組。透過控制 R (紅)、G (綠)、B (藍) 腳位的 PWM (脈衝寬度調變) 或單純的 High/Low,我們可以組合出各種顏色。
我們將系統狀態簡化為幾種基礎語彙:
藍色 (Blue):代表「狀態改變中」或「工作中」。例如開機初始化、正在同步資料。
黃色 (Yellow):代表「警告」或「等待使用者介入」。例如找不到 Wi-Fi,等待連線。
綠色 (Green):代表「成功」或「安全」。例如資料同步完成,可以安全拔除。
紅色 (Red):代表「致命錯誤」。例如硬體損壞、檔案系統異常。
除了顏色,我們還加入了動態 (Animation) 的維度:
恆亮 (Solid):穩定的狀態。
慢閃 (Slow Blink - 1Hz):正在進行某個需時較長的背景動作。
快閃 (Fast Blink - 5Hz):緊急、需要立即注意(通常搭配紅燈)。
[最終解決方案] 狀態機與優先權的萌芽
有了視覺語彙後,我們制定了嚴謹的狀態優先權(Priority)。在無頭設備中,「讓使用者知道系統壞了」永遠比「告訴使用者系統正在待機」更重要。
我們定義的優先權順序如下 (數字越小優先權越高):
Priority 1 (最高):硬體損毀 / 致命錯誤 (紅燈快閃)
Priority 2:資料寫入中 / 絕對不能拔除電源 (黃/藍燈交替閃爍)
Priority 3:等待使用者配對 / 連線異常 (黃燈慢閃)
Priority 4:操作成功 / 閒置待機 (綠燈恆亮)
在軟體架構上,我們剝奪了各別模組直接控制 GPIO 的權力。所有的模組現在只能呼叫一個中央的 LedController 介面,發出「狀態變更請求」。
python
# 去敏化後的架構概念展示
class LedController:
def request_state(self, module_name, state_enum, priority):
# 1. 比較目前發光中的 priority
# 2. 如果新的 priority 更高(數值更小),則搶佔當前燈號
# 3. 將被搶佔的舊狀態放回等待佇列 (Queue)
pass
def release_state(self, module_name):
# 1. 解除該模組的燈號控制權
# 2. 從佇列中喚醒下一個最高優先權的狀態
pass
透過這種設計,如果系統因為網路異常亮起黃燈 (Priority 3),這時使用者啟動了資料同步 (Priority 2),藍燈會立刻搶佔 (Preempt) 黃燈;當同步結束 release_state 後,系統又會自動恢復成原本的黃燈。
這樣的體驗非常符合直覺,使用者再也不會看到錯亂的燈號秀了。
結語與預告
今天我們探討了 Headless 設備如何透過一顆簡單的 LED,搭配嚴謹的視覺語彙與優先權設計,來跟使用者進行無聲的溝通。這看似簡單的硬體燈號,背後其實隱藏著深奧的「軟即時 (Soft Real-Time)」排程哲學。
不過,關於軟即時排程器的完整實作細節,我們會保留到第四階段(Day 20)再來用 Python 徹底解析。
明天開始,我們將進入本系列最硬核、也最迷人的第二階段:Linux 底層與 USB OTG 黑魔法。 如果你曾經好奇,為什麼把一條 USB 線插進 Raspberry Pi,電腦就能把它識別成一顆隨身碟?甚至是一組虛擬網卡?千萬別錯過明天的 [Day 06 - USB OTG 是什麼?從 Host 到 Gadget 的角色切換術]!