Events: state 與狀態改變
Events: state 在 HA 的 state_changed 事件抵達時輸出,適合用來辨認「何時改變」;它不是輪詢,也不是重新查歷史。本章以 HA WebSocket 0.80.3 的 persisted type(匯出 flow JSON 中保存的節點 type)server-state-changed、節點版本 6 為準,完整處理 selector、old/new state、型別、輸出、持續時間與重新連線邊界。
為何需要本章
狀態事件常被誤讀為只有「開」與「關」。在 Home Assistant 原始 state_changed schema 中,一筆事件可能只改 attributes,也可能在 entity 建立時沒有舊狀態、刪除時以 new_state: null 表示;unknown 與 unavailable 又是存在的字串狀態,不等於缺少物件。不過本章固定的套件版本不會把刪除事件交給 Events: state,下文會明確區分原始 schema 與節點實際可收到的事件。
Events: state 是零輸入的事件節點。它訂閱符合 entity selector 的事件,依忽略條件、only-change、If State 與 For 判斷,最後依 Output Properties 建立新的 msg。這與收到 msg 才讀 cache 的 Current State 不同,也與定時讀取的 Poll State 不同。
PLACEHOLDER_*。先閱讀 JSON、確認 selector 與輸出,不連線、不部署、不接 Action;任何環境操作都必須在你完成現場審查後另行決定。v6 的處理順序與節點選型
0.80.3 的 v6 controller 可用下列順序理解:先確認節點啟用且 HA 正在 running,再檢查 entity selector 與五種 ignore 條件;需要時轉換 old/new state;一般事件才套用 only-change;接著計算 If State 與 For;最後將指定的 Output Properties 寫入訊息,If State 成立走第一輸出,不成立則在設定了條件時走第二輸出。
| 節點 | persisted type | 何時讀取 | 本章界線 |
|---|---|---|---|
| Events: state | server-state-changed | 符合的 state_changed 抵達時 | 本章主角;事件驅動,處理 old/new |
| Poll State | poll-state | 依 interval 週期讀取 | 只有事件來源不適用時才評估;限制頻率,詳見第 22 章 |
| Trigger: state | trigger-state | 狀態事件符合 constraints 時 | 較複合的 trigger/constraints;實戰見第 13 章 |
| Current State | api-current-state | 收到上游 msg 時查 cache | 單次查詢與條件,見第 10 章 |
上表同時比較 Events: state、Poll State 與 Trigger: state,方便依觸發需求選擇。選 Poll State 不會讓資料更「即時」,只會建立週期工作;選 Trigger: state 也不會自動取代你對 null、unknown 與 unavailable 的失敗政策。
安全檢視停用範例
- 下載後先以純文字檢查。
開啟 04-events-state.json,確認只有一個 Events: state 與 Debug;HA 節點含
"d": true,代表匯入後仍停用。這一步不執行 Node-RED 或 HA 操作。 - 核對固定版本欄位。
確認 type 是
server-state-changed、version是6,Server 為PLACEHOLDER_HA_SERVER_CONFIG,entity 為PLACEHOLDER_ENTITY_ID。不要用搜尋取代填入正式環境 ID。 - 先畫出輸出契約。
在紙面記錄
msg.payload為字串 state、msg.data為 event data、msg.topic為觸發 entity ID。Debug 目前只看 payload,避免完整 attributes 暴露位置、人物或裝置資訊。 - 逐項決定異常政策。
分別決定 old_state 為 null,以及舊/新 state 為
unknown或unavailable時的路由。不要用一個「有值」判斷混在一起;entity 刪除則需另選能觀察原始事件的入口,不能期待本版 Events: state 下游收到。 - 檢查啟動與時間條件。
範例的 Output on Connect 為 false、only-change 為 true、For 為 0。若未來要改,先依本章邊界模擬 reconnect 與 timer 取消情況,不要在生產事件上直接試。
- 完成審查後仍保持副作用斷開。
即使之後由你在隔離環境進行測試,也只接有限欄位 Debug 或無外部 I/O 的驗證節點;不得接Action、通知、HTTP、MQTT 或檔案寫入。先保存停用狀態與審查紀錄,再由現場變更流程決定後續。
entity_id: "PLACEHOLDER_ENTITY_ID",old/new 只放必要 state 與假時間。不要從 Debug 複製真實 HA event;完整 attributes 可能包含人名、位置、媒體或門鎖資訊。離線三列練習:Events: state 節點保持停用且不連 HA,Action 全程斷開;只在紙面判讀下列假事件與預期結果。
| 假事件 | 紙面設定 | 預期輸出/不動作 |
|---|---|---|
PLACEHOLDER_STATE_A → PLACEHOLDER_STATE_B | entity_id: "PLACEHOLDER_ENTITY_ID";only-change=true | 有限 Debug 收到 msg.payload="PLACEHOLDER_STATE_B" 與 placeholder topic;Action 仍斷開 |
state 維持 PLACEHOLDER_STATE_A,attribute 由 PLACEHOLDER_ATTRIBUTE_A 改為 PLACEHOLDER_ATTRIBUTE_B | only-change=true | 不輸出、不動作;若需求是屬性監看,先另寫明確契約 |
新 state 為 unknown 或 unavailable | 依紙面政策設定對應 ignore/異常分支 | 忽略時無輸出;未忽略時只進 NO_ACTION_INVALID_STATE Debug,不接 Action |
entity selector 與 old/new 模型
v6 的 entities 是三組 selector:entity 做 exact、substring 做片段比對、regex 做正規表示式。只有 exact 且其他兩組空白時,runtime 會為每個 entity 建立較具體的事件 topic listener;只要使用 substring 或 regex,就訂閱一般 state_changed topic,再逐事件用 shouldIncludeEvent 判斷。三組之間是「任一符合」即可,不是全部同時符合。
| selector | 適用情境 | 主要風險 | 安全做法 |
|---|---|---|---|
| exact entity | 已核准的單一或少量 ID | ID 遷移後漏接 | 首選;用 PLACEHOLDER_ENTITY_ID 文件化允許清單(allowlist) |
| substring | 命名規則穩定的一群 entity | 同名片段誤納新增 entity | 列出預期集合並對新增項目 fail closed |
| regex | 確實需要結構化集合 | 範圍過寬、效能與誤匹配 | 加起訖錨點,在假 ID 集合離線測試 |
Home Assistant 原始 event 的 event.old_state 與 event.new_state 是 state object 或 null:entity 新建常見 old_state 不存在,entity 刪除則 new_state 不存在。這和 state object 存在但其 state 字串是 unknown/unavailable 完全不同。然而 0.80.3 的 WebSocket 處理會在 new_state 物件或 entity ID 為 falsy 時先丟棄該筆 state_changed,之後才發送套件事件 topic;所以 Events: state v6 收不到原始的 new_state: null 刪除事件,也不能提供下游刪除分支保證。
不要假設 state_changed 一定是 state 字串改變。attributes 或時間欄位改變也可能產生事件;only-change 開啟時,controller 比較轉型後的 old/new state,相同便不輸出一般事件。因此若你的需求是監看 attributes,不能同時假設 only-change 仍會傳出 attribute-only 更新。
state 字串、轉型與 Output Properties
HA state 原始值是字串。v6 的預設 stateType 是 str;若使用非字串轉型,controller 會在 old/new entity 上保存 original_state,再改寫 state。數字使用 parseFloat;一般 boolean 是 JavaScript 的 !!value,因此任何非空字串都為 true,連 "off" 也不例外。這就是為什麼狀態判斷優先保留字串,或使用明確的 HA boolean 規則,而不是把任意 state 強轉 boolean。
| 範例映射 | 來源值 | 缺值與隱私邊界 |
|---|---|---|
msg.payload ← entity state(string) | 通過套件前置篩選後 new entity 的 state | unknown/unavailable 仍是字串;刪除事件不會抵達此節點 |
msg.data ← event data | old_state、new_state 等 event data | 資料量大且含 attributes;只在隔離 Debug 暫看 |
msg.topic ← entity ID | controller 的 triggerId | 屬環境資訊;公開紀錄改成 placeholder |
| 自訂 msg/flow/global property | entity state、event data、config、JSONata 等 | 寫入 context 會延長保存;先最小化與定 retention |
Output Properties 是明確映射,不是固定保證「所有東西都在 payload」。範例採三個預設映射;若你刪除或改名,下游也必須同步更新契約。下游只應依明確映射與已驗證的欄位運作,不把物件共享方式視為穩定契約。
msg.data 映射直接保存同一個 eventData object 參照;controller 之後在送出前加入 new_state.timeSinceChangedMs,因此這個預設映射會看見該欄位。下游若需要它,應明確驗證存在與型別,不要假設後續版本仍共享物件。若設定 If State,節點會有兩個輸出:成立走第一個,不成立走第二個;沒有條件時通常只有一個輸出。第二輸出不是 runtime error,而是條件 false。真正的設定/輸入錯誤應另由 Catch/Status 觀察,概念可銜接第 18 章。
ignore、only-change、If State 與 For
五個 ignore 選項在轉型之前判斷已抵達節點的事件:舊 state 不存在、舊 state 為 unknown、舊 state 為 unavailable、新 state 為 unknown、新 state 為 unavailable。它們只決定是否整筆略過。原始 new_state: null 確實不等於 unavailable,但 0.80.3 已在 Events: state 上游的 WebSocket 處理丟棄它,不能用這五個選項或下游分支補回。
outputOnlyOnStateChange 對一般事件比較 old/new state;若相等便返回。但 Output on Connect 產生的合成事件以 runAll=true 呼叫,會略過這個 only-change return,所以開啟 Output on Connect 時仍可能為每個符合 selector 的 cached entity 產生初始輸出。這是啟動洪峰的主要來源。
If State 比較的是轉型後的新 state,支援 is、is not、大小比較、includes/not in 與 JSONata。未設定 If State 時,不要把第一輸出稱為「條件成功」;它只是正常輸出。設定 If State 後,false 訊息仍會從第二輸出送出,不會自動被丟棄;第二輸出若接到副作用節點同樣危險。
For 的值可來自 number、JSONata、flow 或 global,必須是非負數;空字串或 0 代表不等待。有效 timer 以 entity ID 分開追蹤。一般事件開始 timer 後,若同 entity 出現不符合 If State 的事件,會把 active 標記關閉並走 false;新的有效事件會清除既有 timeout 再啟動。這是記憶體內 timer,不是 HA 歷史查詢,重啟不能當作持久倒數。
startup、reconnect 與安全測試邊界
Output on Connect 開啟後,listener 啟動時若 HA 已 running,會把目前 cache 轉成合成 state_changed;若尚未 ready,則等待 InitialConnectionReady 再產生。每筆合成事件的 old_state 與 new_state 都指向同一個目前 entity,並以 runAll=true 處理。它不是一段真實歷史,也不能告訴你在斷線期間發生過哪些中間變化。
runAll=true 不啟動 For timer;controller 在有效 timer 路徑會直接 return。若 Output on Connect 與 For 同時開啟,不能假設連線後每個目前符合狀態的 entity 都會等滿 For 再輸出。這個組合必須依 v6 程式行為審查,而不是依欄位名稱猜測。
controller 還要求 homeAssistant.isHomeAssistantRunning;連上 WebSocket 但 HA 尚未 running 時不處理正常事件。重新連線後 cache 是快照,事件流也不保證補齊斷線區間。因此安全自動化要採 fail closed:斷線期間不推論、重連先查目前狀態、需要歷史證據時使用受限的 Get History,而不是從兩筆事件猜中間過程。
安全測試至少涵蓋:state 真正改變、只改 attribute、old_state null、old/new 分別 unknown/unavailable、Output on Connect、短暫斷線重連、For 計時中反轉;另以 WebSocket 邊界測試確認原始 new_state: null 在進入 Events: state 前被丟棄,而不是期待節點輸出。測試只用停用 flow 與 placeholder 的紙面/單元事件;不要為了製造事件去切換真實燈、鎖、警報、空調或通知。
需要「條件成立後才觸發」可評估 Trigger: state;需要週期取樣才評估 Poll State;需要到期當下重查則接 Current State。三者都不會自動提供 exactly-once 保證。任何接到 Action 的設計,必須另加 idempotency、冷卻、重新連線政策與人工復原,詳見第 11 章。
故障排除
- 完全沒有輸出:先確認節點仍停用是預期狀態;若在另行核准的隔離測試中,依序核對 v6、Server ready、exact/substring/regex selector、五個 ignore 與 If State。不要先放寬 regex 或接真實副作用驗證。
- attribute 改變沒有訊息:檢查 only-change 是否為 true;它比較 old/new state,相同便略過一般事件。若需求真的是 attributes,建立明確 attribute 允許清單,避免 Debug 整個 event。
- 啟動後突然很多訊息:檢查 Output on Connect。合成事件會遍歷 cache 並可略過 only-change;關閉前先確認下游是否依賴初始快照,任何副作用支線先斷開。
- unknown 被當成缺值:
unknown是 state 字串;null 是 state object 不存在。分開記錄是哪一種情況,並檢查相對應 ignore checkbox,不用 truthy 判斷混合。 - For 到期後狀態其實已改:For 輸出保留起始事件,不是到期重查。先用 Current State 做唯讀確認並處理 cache missing;未確認前保持後續 Action 停用。
- 數字或 boolean 判斷異常:檢查 stateType 與
original_state。parseFloat可能產生 NaN,一般!!會把非空的 off 字串視為 true;優先回到字串與明確比較。 - 重連後以為漏掉事件:事件訂閱不等於歷史補送。記錄斷線窗口,若業務需要則以受限時間窗查 Get History;不要根據重連快照補造 Action。
固定來源
- HA WebSocket 0.80.3 固定提交 2cbbb69:本章核對
src/nodes/events-state、src/common/TransformState.ts,並確認 Poll State、Trigger State 註冊邊界。 - Events: state 官方節點文件:使用者欄位與輸入/輸出說明;若 rolling 文件與固定提交不同,以固定提交為本章基線。
- Home Assistant 官方 state object 文件:state 與 attributes 的資料模型。
- Home Assistant 官方 events 文件:event bus 與 state_changed 背景。
可下載的安全範例是 04-events-state.json。它使用 placeholder Server/entity 且 HA 節點停用,只供匯入前檢查 v6 欄位,不代表已核准在你的環境執行。