學完本章你將能夠:
把「命令列舉」轉成每一個設備的內部狀態(亮度、轉速、門角)。
處理相對指令(亮一點)與絕對指令(亮度 40)而不互相打架。
實作
ALL_OFF與故障時的安全降級。讓查詢類指令走通訊佇列,而不是在 Device 任務裡直接印溫度。
到這裡,語音管線已經會吐出 |cmd_msg_t|。本章是產品的「手」:所有 副作用都發生在 Device 任務,而且是可預測、可測試的。
每個設備一份狀態
typedef struct uint8_t light_on; uint8_t light_pct; /* 0–100,on=0 時仍記住上次亮度 */ uint8_t fan_on; uint8_t fan_pct; uint8_t door_open; /* 0 關 1 開 */ uint8_t relay_on; uint8_t buzzer_on; home_state_t;
相對指令改的是這份結構,再呼叫 |dev_*| 反映到硬體。重啟後的預設: 全關、亮度 60%、風扇 40%、門關閉——寫在 |device_reset()|,OTA 或看門狗復位後行為一致。
套用命令
void device_apply(home_state_t *s, const cmd_msg_t *m) switch (m->id) case CMD_LIGHT_ON: s->light_on = 1; if (s->light_pct < 10) s->light_pct = 60; break; case CMD_LIGHT_UP: /* 相對:「亮一點」 */ s->light_on = 1; s->light_pct = (uint8_t)MIN(100, s->light_pct + 15); break; case CMD_LIGHT_SET: /* 絕對:「亮度 40」、助理滑桿 */ s->light_pct = (m->arg > 100) ? 100 : m->arg; s->light_on = (s->light_pct > 0); break; case CMD_ALL_OFF: case CMD_ENTER_SLEEP: device_reset_outputs(s); /* 保留 pct 記憶,只關輸出 */ break; case CMD_QUERY_TEMP: /* 不改 actuator */ default: break;
注意這個函式沒有副作用:不碰 HAL、不發事件。副作用留在任務層—— |vTaskDevice| 取出訊息後先驗逾時,呼叫 |device_apply| 改狀態,再 |device_commit| 寫入 PWM/GPIO;QUERY_TEMP 由任務轉發遙測 請求、ENTER_SLEEP 由任務置位 |EVT_SLEEP_REQ|。相對 (UP/DOWN)與絕對(SET)指令都收斂到同一份 |home_state_t|,自然不會互相打架。
門的開與關寫成角度常數(例如 90 與 0),不要讓語音直接傳任意角度, 以免機構撞到底。
逾時命令
若 ASR 佇列短暫堵塞,一則 2 秒前的「開門」不應突然執行。 |cmd_msg_t.tick| 由 |vTaskDevice| 取出訊息後、呼叫 apply 之前檢查:
if ((xTaskGetTickCount() - m->tick) > pdMS_TO_TICKS(1500)) app_log("drop stale cmd̊"); return;
可單測的邏輯
|device_apply| 不要呼叫 HAL:它只改傳入的 |home_state_t|, |device_commit| 才碰硬體。這樣可以在 PC 上寫一組單元測試(本倉庫 firmware/lab09_voice_controller/host_test/)驗證 「亮一點三次不會超過 100」「風扇絕對值 10% 會被抬到最低轉速 25%」。 嵌入式課程很少做這件事,但狀態機是最值得測的一層——第 13 章的智慧助理 指令能直接沿用,也是因為這層早就被測過。
實驗:口令驅動全家電
firmware/lab09_voice_controller/ 把 lab02–08 接起來: UART 模擬 ASR(或接真模組),Device 任務維持 |g_home|,每次變更 印一行 JSON 方便你對照。
建議驗收腳本(口說或終端):
喚醒 \(\rightarrow\) 開燈 \(\rightarrow\) 亮一點 \(\rightarrow\) 暗一點。
開風扇 \(\rightarrow\) 開門 \(\rightarrow\) 開插座。
全部關閉:燈、風扇、繼電器、蜂鳴必須全停,門回關閉角。
不喚醒直接「開燈」:無動作。
喚醒後發呆 5 秒再「開燈」:無動作。
增加「離家模式」:一個命令依序關燈、關風扇、關繼電器、關門,每步間隔 150 ms(用 timer 或 Device 任務內的短狀態機,不要一次 blocking 600 ms 若不需要)。思考:若中途收到「開燈」,應中斷離家還是排隊?把你的選擇 寫進註解——這就是產品規格。