第 10 章

Current State、Get Entities 與條件判斷

Current State v3 收到 msg 後從 HA WebSocket cache 讀單一 entity;Get Entities v1 則掃描 cache,依 state、device、area、floor、label 條件建立集合。兩者都不是即時 REST 查詢,也不是歷史資料。本章明確定義輸入覆寫、輸出 mapping、空結果與結果量上限。

為何需要本章

狀態查詢通常位於事件與副作用之間:上游說「有人移動」,Current State 再檢查模式是否允許;或 Get Entities 找出符合規則的候選集合。若把 cache 當成即時真相、把不存在當成 false,或允許不可信 msg.payload.* 改寫查詢,原本的安全條件就可能被繞過。

Current State 與 Get Entities 都有一個 input,但契約不同。Current State v3 可用 Block Input Overrides 鎖住設定的 entity;Get Entities v1 沒有相等的切換,固定會解析 msg.payload.rules 與多個 output 設定覆寫。這個差異決定了你應在哪裡清除輸入,以及能否把 HTTP、MQTT 或 webhook 訊息直接送入節點。

本章示例不執行:05-current-state.json 與 06-get-entities.json 的 HA 查詢節點均有 "d": true,Server/entity 是 PLACEHOLDER_*。只做文字檢視與匯入前審查,不連線、不部署、不接 Action。

cache、歷史與等待不是同一種查詢

節點persisted type資料來源/生命週期主要限制
Current State v3api-current-state收到 msg 時讀單一 cached statecache miss 會 error;可鎖 input override
Get Entities v1ha-get-entities收到 msg 時掃描 cached states,必要時查 registry無 Block Input Overrides;集合可能很大
Get Historyapi-get-history對 HA history 發出查詢沒有內建結果上限或請求 timeout;必須在架構外限制
Wait Untilha-wait-until保留一筆傳入 msg,等待條件或 timeout每節點只有一個 active slot;後來輸入會取代前一筆

本章同時比較 Current State、Get Entities、Get History 與 Wait Until。Current State/Get Entities 適合快速查看套件維護的 cache;Get History 適合由外部政策限制的過去區間;Wait Until 適合有明確 timeout 的單筆流程暫停。不要用 Get History 模擬高頻 current state,也不要把 Wait Until 當成多訊息等待佇列。

cache 由 WebSocket 連線與 state_changed 更新。它可能在啟動中尚未完整、斷線後陳舊、entity 移除後缺少,也可能保存 state 為 unknown/unavailable。節點輸出時間戳不等於你已證明資料新鮮;安全決策仍要把連線狀態與缺值納入 fail-closed 路徑。

安全檢視兩個停用範例

  1. 以文字工具閱讀檔案。

    先看 05-current-state.json 與 06-get-entities.json。確認各只有手動 Inject、停用的 HA node、有限 payload Debug;這一步不執行任何 HA 操作。

  2. 核對版本與 placeholder。

    Current State type/version 應為 api-current-state/3;Get Entities 應為 ha-get-entities/1。兩者的 Server 及 entity 都只能是完整 PLACEHOLDER_*。

  3. 檢查 Current State override 邊界。

    範例的 blockInputOverrides 是 true,因此設定的 entity_id 不接受 msg.payload.entity_id 或 msg.payload.entityId 取代。保留這個安全預設。

  4. 清除 Get Entities 覆寫欄位。

    在紙面契約列出並拒絕外部輸入的 payload.rules、outputType、outputEmptyResults、outputLocationType、outputLocation、outputResultsCount。v1 沒有一個 checkbox 可以代替這份允許清單(allowlist)。

  5. 定義空結果與上限。

    決定 0 筆是送空陣列、送數字 0、還是不送訊息;再為候選 entity 數、Debug 欄位與下游 fan-out 設定上限。不要把「測試環境只有幾個」當成永久限制。

  6. 先驗證失敗支線。

    用紙面/單元測試涵蓋 cache miss、unknown、unavailable、0 筆、多筆超限、registry metadata 缺少。所有輸出只進隔離 Debug,且在現場審查前維持 HA 節點停用與副作用斷開。

這兩份範例都不是按一下即可執行的配方。Import 本身只把 JSON 放進 editor;但在未完成安全審查前仍不要進行 Deploy。真實 entity ID、device/area/floor/label ID 與完整 attributes 不應出現在截圖、issue 或公開 flow。

離線三列練習:Current State 與 Get Entities 節點保持停用且不連 HA,Action 全程斷開;只用 placeholder 紙面資料比對下列輸出契約。

假情境紙面輸入預期輸出/不動作
Current State cache hitPLACEHOLDER_ENTITY_ID 對應 PLACEHOLDER_STATE有限 Debug 收到 msg.payload="PLACEHOLDER_STATE";只供判讀,Action 仍斷開
Current State cache misscache 中沒有 PLACEHOLDER_ENTITY_ID拋 InputError,由 Catch 進 NO_ACTION_CACHE_MISS;沒有正常輸出
Get Entities 為 0 筆或超過核准上限固定 rules 回傳 0,或 PLACEHOLDER_RESULT_COUNT > PLACEHOLDER_APPROVED_LIMITCount 可輸出 0;超限由 count gate 拒絕。兩者都不送 array/split、不接 Action

Current State v3:單一 entity、比較與 mapping

v3 的 InputService 定義唯一可覆寫輸入 entityId:先看 msg.payload.entity_id,也接受 camelCase 的 msg.payload.entityId,否則使用設定的 entity_id。entity ID 還會經 Mustache render。當 Block Input Overrides 為 true 時,InputService 停用 message override,設定值勝出;若關閉,任何能寫入上述 payload 欄位的上游都可能改查另一個 entity。

controller 用 WebSocket getState(entityId) 讀 cache。找不到時拋出 InputError「not found」,不會送出一個 state=false 的正常訊息。找到後加入 timeSinceChangedMs;若選非字串 state type,保存 original_state 再轉換。這個物件來自 cache,設計下游時不要隨意改寫它的巢狀 attributes。

欄位v3 行為安全建議
Entity ID設定值可含 Mustache;未 block 時可被兩種 payload key 覆寫使用完整 placeholder 文件化;Block Input Overrides 保持 true
If State比較目前 entity.state;設定後 true/false 分別走兩輸出字串明確比較,unknown/unavailable 另分流
For比較 timeSinceChangedMs 是否大於指定 duration是 cache timestamp 計算,不是等待 timer,也不是歷史證明
State Type預設 string;非字串保留 original_state避免一般 boolean 的非空字串陷阱
Output Properties預設 payload=state string、data=完整 entity改成最小允許清單,不固定假設 payload

If State 未使用時,通常只有正常輸出。使用 If State 或 HA boolean 類型後,成立送第一輸出,不成立送第二輸出;第二輸出不是 error。For 只在條件先成立,且 comparator 為 is、is not、includes、does not include 時生效;duration 可來自 number、JSONata、msg、flow 或 global,負數會 error。

時間判斷採 entity.timeSinceChangedMs > forDurationMs,是嚴格大於而非大於等於。它依 last_changed 計算,不會等待到時間到,也不會查 recorder。若你要「等待直到」,應評估 Wait Until 並設定 timeout;若要證明過去區間,使用受限 Get History。

Get Entities v1:規則、覆寫與 result modes

v1 每次收到 msg 都取得 cached states,逐一檢查規則。state condition 可讀 state object 的 property;device、area、floor、label condition 需要 entity registry 及相關 registry metadata。area 可來自 entity 自身或 device;floor 由 area 取得;label 會檢查 entity、device、area 上的 labels。metadata 缺少時,該條件不會憑空匹配。

多條 rules 是逐條檢查,只要一條失敗就排除,等同 AND。runtime 會先排序 label、state、device、area、floor 來執行,但你不應依排序製造副作用;rules 只應是純比較。state property 不存在時,除 JSONata 特例外會判定不符合。

outputType輸出形狀0 筆行為限制重點
arrayHassEntity 陣列寫到指定 msg/flow/global locationoutputEmptyResults=true 才送空陣列;否則不送沒有內建最大筆數;下游前必須自行限制
count數字寫到指定 location送出 0不包含 entity 明細,適合先做門檻
random上限 1 時為單一 entity;大於 1 時為陣列不送outputResultsCount 只控制 random;訊息覆寫須先驗證為整數且 ≥1
split每 entity 一個 msg,payload 為該 entity不送會 fan-out;必須先估算數量與 downstream 容量

Array、Count、Random 可把結果寫到 msg、flow 或 global 的指定 location;Split 不顯示 output location,固定逐筆放進 msg.payload。使用 flow/global 會讓資料跨訊息保存,可能留存完整 attributes;除非有明確 retention 與讀者,否則使用 msg 並立即縮減欄位。

Random 是抽樣,不是安全分配、公平輪替或密碼學隨機。Split 會 fan-out;選用前先限制候選量,下游若 Join 也要限制等待序列並設定 timeout。

0.80.3 版本細節:Random 會 shuffle 後截取。Split 會刪除原訊息的 _msgid,建立 msg.parts(含新的 sequence id、count 與逐筆 index),再 clone 每則訊息;下游 Join 應依 msg.parts 辨認序列。

Get History 與 Wait Until 的精確資源邊界

Get History 0.80.3 會讀取六個 msg.payload 覆寫:startDate、endDate、entityId、entityIdType、relativeTime、flatten。它沒有 Block Input Overrides,因此不可信入口前必須刪除這六欄,或依允許清單與日期 schema 重建乾淨 payload。這個節點也沒有內建結果筆數上限;底層 Axios client 未設定 request timeout。短時間窗、單一 entity、回應大小預算、代理/呼叫端 timeout 與中止政策都是你必須在節點外建立的控制,不能描述成節點保證。

Wait Until controller 每個節點只有一個 active slot,保存一組 message 與 config。後來的輸入會先取消現有 timer,再取代已保存的訊息;它不是 pending queue。timeout=0 表示完全不建立 timer,可能一直等到條件成立、收到 reset、被後來輸入取代或節點停止。安全設定使用有限的正 timeout,將成功與逾時接到不同輸出;逾時只代表期限內未觀察到條件,不得直接觸發副作用。

輸入 override、輸出 mapping 與最小資料

Current State 的 Block Input Overrides 是明確安全界線:true 時 message 不能改 entityId。Get Entities 沒有同等 toggle;其 InputService 固定把下列 message properties 當候選設定。只要上游 payload 存在合規型別,便可取代 editor 設定。

message property可改變什麼不可信輸入處理
msg.payload.rules整組篩選規則刪除;只由受信任 Change node 建立固定允許清單
msg.payload.outputTypearray/count/random/split刪除,避免改成 split 放大訊息
msg.payload.outputEmptyResults空陣列是否仍輸出刪除,避免改變控制流
msg.payload.outputLocationTypemsg/flow/global刪除,防止跨 scope 寫入
msg.payload.outputLocation實際 property/path刪除,防止覆寫其他訊息或 context 欄位
msg.payload.outputResultsCountrandom 回傳上限;runtime schema 只檢查 number刪除;若確實允許,先驗證為整數且 ≥1

「先驗證後轉送原 payload」仍不夠,因為危險 override key 還在。安全做法是建立全新的 msg 或先逐欄刪除,再由受信任節點寫入固定 rules。對 HTTP、MQTT、webhook 等來源,來源驗證與 schema 驗證都要做;即使 schema 合法,也不能允許任意 property path 或 global location。

輸出 mapping 要有資料契約。Current State 範例將 state 字串寫 payload、entity object 寫 data;Get Entities 範例將 array 寫 payload。若下游只需要 state 與 entity_id,就不要傳完整 attributes。Debug 設為指定 property,完成測試後停用;完整 entity 可能含位置、人物、裝置、媒體或診斷資訊。

一個安全的唯讀門檻可先用 Get Entities 的 count 模式確認候選數在預期區間,再在另一個受限查詢取得明細;但兩次 cache 掃描間仍可能變動,不能宣稱交易一致。需要原子業務語意時應在更合適的 HA action/automation 設計處理,而不是依 Node-RED 兩次讀取猜測。

missing entity、unknown/unavailable 與結果上限

Current State 的 entity 不在 cache 時會走 error,應由 Catch 捕捉並用 Status 觀察,只記錄清理後的 entity placeholder、節點名稱與錯誤類別,再終止控制流;不要把 error 自動改成 false 後接 Action。unknown 與 unavailable 則是找到 entity 後的正常字串值,應在 If State 或 Switch 分開路由到「不採取副作用」支線。

Get Entities 可能因 rules 太嚴、registry 尚未載入、metadata 缺少或 cache 空而得到 0 筆。Array 的 outputEmptyResults 會改變「有一則空訊息」與「完全不送」;Count 一定可以送 0;Random/Split 在 0 筆時不送。下游若有 timeout 或 Complete,必須知道是哪種契約,不能把沉默當成成功。

v1 的 array 與 split 沒有通用最大結果數。outputResultsCount 只用於 random,而且來自 message 時只通過 number 檢查;上游必須進一步拒絕小數、0 與負數,確保是整數且 ≥1。若 selector 可能涵蓋大量 entity,優先收窄 rules;再於下游用 count gate 拒絕超限,不要先 split 後才限流。對 flow/global output 另設保存期間與大小預算。

Get History 的 entity 允許清單、短時間窗、可接受筆數與請求 timeout 都是外部政策,不是 0.80.3 內建限制,本範例庫也沒有專用 Get History Flow。只有 Wait Until 有安全欄位範例 08-wait-until.json(節點停用、placeholder、10 秒 timeout、兩個輸出);它展示單一 active slot 的有限等待,不要拿來替代本章兩個 cache 範例。

Fail closed 決策表:cache missing、unknown、unavailable、結果超限、registry metadata 缺少、HA 斷線都進入無副作用支線;只有「資料存在、型別正確、時間可接受、候選數在上限內、條件明確成立」才可交給下一個純邏輯節點。是否允許 Action 仍需第 11 章的獨立審查。

故障排除

  • Current State 回報 not found:不要建立假 false 訊息。檢查 placeholder 尚未被誤當正式 ID、cache 是否 ready、entity 是否已移除/改名、Block Input Overrides 是否阻擋預期輸入;保持副作用支線停用。
  • 查到的不是設定 entity:檢查 Block Input Overrides 是否被關閉,以及上游是否帶 payload.entity_id 或 payload.entityId。重新開啟 block,清除 message key,再做紙面契約測試。
  • Get Entities 規則在 editor 正確,runtime 卻不同:查看上游 payload 是否帶六類 override。v1 沒有 block toggle;用 Change 建立新訊息而非沿用外部 payload。
  • 0 筆時下游沒有收到訊息:核對 outputType。Array 需要 outputEmptyResults=true 才送空陣列;Count 會送 0;Random/Split 都不送。不要把沉默誤判成節點故障。
  • Split 造成大量訊息:立即中斷下游副作用,先改用 Count 估算,收窄 rules 並設定你的硬上限。outputResultsCount 對 Split 無效。
  • For 邊界差一點:Current State 使用 timeSinceChangedMs 嚴格大於 duration,且依 last_changed。確認時鐘、state 是否真的 changed、duration 型別與單位,不以 recorder history 混解。
  • device/area/floor/label 條件漏掉 entity:registry entry 或關聯 metadata 可能不存在。依 state、entity registry、device、area、floor、label 順序查缺值;不要放寬成全域 state 規則繞過。

固定來源

rolling 官方頁若與 0.80.3 固定提交不同,以固定提交為本章精確語意。安全下載:05-current-state.json、06-get-entities.json;兩者均使用 placeholder 且 HA 節點停用。

常見問題

Current State 會即時向 HA API 查一次嗎?
不會。v3 從 HA WebSocket 整合維護的 cache 取單一 state。斷線、啟動或 cache miss 都要另行處理,不能把查到物件等同即時 API round trip。
Block Input Overrides 會保護 Get Entities 嗎?
不會。這個切換存在於 Current State 與 Action,Get Entities v1 沒有同等切換。你必須在上游刪除或驗證六類 msg.payload.* override。
outputResultsCount 是所有模式的結果上限嗎?
不是。controller 只在 Random 模式使用它;Array 仍可回傳完整集合,Split 仍可每筆 fan-out,Count 只回數量。若允許 message 覆寫,還必須先驗證為整數且 ≥1;僅通過 number 型別不足以形成安全上限。
Current State 找不到 entity 時會從第二輸出送 false 嗎?
不會。cache miss 會拋 InputError。第二輸出是有設定 If State 時的條件 false,不是 error channel;請用 Catch/Status 區分。
Split 模式如何識別同一組訊息?
v1 建立 msg.parts.id、count、index 並逐筆 clone,payload 是單一 entity。下游仍需限制序列數、Join 等待與 timeout。
可以把完整 entity array 寫進 global 供所有 flow 使用嗎?
技術上 output location 支援 global,但不建議當預設。這會延長 attributes 的保存並產生陳舊副本;優先把最小欄位放 msg,明確定義 retention 與讀者後才用 context。