Action 節點、target、data 與回傳值
0.80.3 編輯器顯示 Action,匯出 JSON 的 persisted type 仍是 api-call-service,節點版本為 7。Action 會造成 HA 外部副作用;本章只用停用節點與完整 placeholder 解釋 action、target、data、input override、queue、response 與錯誤契約,不提供可直接執行的真實目標。
為何需要本章
Action 是事件/查詢流程跨入副作用的邊界。前面章節的 Events: state 與 Current State 主要讀取資料;Action 則可能控制裝置、變更 helper、傳送通知或呼叫其他 HA 功能。錯一個 target、允許 payload 覆寫,或把 reconnect queue 當成可靠工作佇列,都可能把一次測試放大成多次實際操作。
一筆呼叫至少有三個不同問題:action 決定要做哪一種 HA 操作;target 決定對哪個 entity/device/area/floor/label;data 放該 action 專屬參數。把 entity ID 塞進任意 data、把 message 文字放 target,或相信所有 action 都回傳 payload,都是錯誤契約。
"d": true,Server、action、entity 全是 PLACEHOLDER_*。只可離線檢查;安全審查、目標擁有者核准、復原計畫完成前不得 Deploy,也不得移除停用旗標或接入真實觸發。Action UI、persisted type 與 v7 資料流
固定版的 palette label 是 action,NodeType key 也是 Action,但 Node-RED flow JSON 的 type 仍為 api-call-service。舊流程/舊文件中的 Call Service 只用來辨認 legacy 名稱;不要把現行 UI 稱回舊名,也不要手改 JSON type 成 action。v7 migration 保留相容欄位,但新設計應使用單一 action,不再依賴分離的 domain/service message 欄位。
controller 解析 action 後以第一個句點分成 domain 與 service;缺任一段便是 invalid action format。設定來源的 action 會做 Mustache render,之後轉成小寫。target 由 v7 的五組設定與允許的 msg.payload.target 合併;data 則依 JSON/JSONata、context 與 payload 規則合併。最後呼叫 HA WebSocket call_service。
| 層 | v7 persisted 欄位 | 責任 | 安全預設 |
|---|---|---|---|
| 操作種類 | action | domain.service 形式的 HA action | PLACEHOLDER_HA_ACTION,不接受輸入覆寫 |
| 目標 | entityId、deviceId、areaId、floorId、labelId | 選擇呼叫作用範圍 | 最小單一 placeholder;禁止寬範圍預設 |
| 參數 | data/dataType | action-specific arguments | 固定 schema、固定型別、拒絕額外欄位 |
| 動態合併 | mergeContext、mustacheAltTags | context 或 JSON template/JSONata | 不用就留空;使用時限定 key 與資料擁有者 |
| 執行邊界 | queue、blockInputOverrides | 斷線行為與 message override | none、true |
| 輸出 | outputProperties | 映射 sent data、results 或其他值 | 只輸出必要欄位,不輸出秘密或完整 attributes |
範例正好呈現安全基線:v7、queue none、Block Input Overrides true、debugenabled false、所有目標只有 placeholder,且整個 Action 停用。它的 data 是靜態示意字串,不代表 action 接受該參數,也不會因匯入而自動送出。
以非執行草稿完成 Action 審查
- 只讀檢查安全範例。
開啟 07-action.json,核對
type: "api-call-service"、version: 7、d: true。Inject 是手動且不排程,但 Action 仍必須停用;不要把「手動」誤認為無副作用。 - 寫下具體但非執行的情境。
例如「經核准的測試通知草稿,只對單一測試接收者,內容固定為 TEST_ONLY_NO_SEND」。JSON 中仍只寫
PLACEHOLDER_HA_ACTION與PLACEHOLDER_ENTITY_ID,不填真實 action、notify endpoint 或裝置。 - 分離 action、target、data。
action 只放 placeholder 操作名;target 只選一類最小 placeholder ID;data 只列該 action 官方 schema 允許的固定欄位。未知欄位、template 輸入、完整 msg 一律拒絕。
- 鎖住輸入與 queue。
保留 Block Input Overrides=true、queue=none。列出若未鎖定時可影響 action/target/data 的 payload keys,但不要為「方便」關閉。斷線時寧可明確失敗,不把控制操作留到不確定的未來。
- 定義輸出與錯誤契約。
若 action 沒有 response,只保留已清理的 correlation ID;若有 response,映射到例如
msg.actionResult並驗證 shape。Catch/Status 只記錄 placeholder action、錯誤類別與時間,不記 data 內容或真實 target。 - 完成風險與復原審查。
由目標擁有者確認作用範圍、重複呼叫是否安全、queue/reconnect 政策、rate limit、人工停止與復原。審查紀錄必須先於任何環境變更;本章不指示啟用、部署或送出 Action。
{
"status": "DISABLED_NON_EXECUTED_DRAFT",
"action": "PLACEHOLDER_HA_ACTION",
"target": { "entity_id": ["PLACEHOLDER_ENTITY_ID"] },
"data": { "message": "TEST_ONLY_NO_SEND" },
"blockInputOverrides": true,
"queue": "none"
}
上方是審查文件,不是可匯入 flow,也不是 HA 可執行請求。action 與 target 都不可替換成真值後直接使用;data 是否有效完全取決於日後核准的 action schema。
input override、資料合併與副作用門檻
v7 InputService 可從 message 讀 msg.payload.action、msg.payload.target、msg.payload.data;legacy 相容還辨認 payload domain/service。當 Block Input Overrides=true,InputService 停用這些 message override,action 改用 config、target 改用五組 config selector,data 合併也不採 payload。這是 Action 必須保留的安全預設。
若 block=false,payload action 可取代設定 action;payload target 會與 config target 深度合併;payload data 在 data 合併時優先級最高。即使上游只打算改一個文字欄位,攻擊者或錯誤節點也可能改 action 與目標。對任何 HTTP、MQTT、webhook、Dashboard、Assist 或跨 flow input,不能只做型別檢查;必須建立 action 允許清單(allowlist)、target 允許清單、data schema,並產生新的乾淨 msg。
Data 可選 JSONata 或 JSON。JSON 模式會先做 Mustache render,再 parse;可選 alternate tags 只改 data 欄位的 template delimiters。JSONata 則由 Node-RED evaluator 執行。兩者都不是 HA Jinja,也不是 JavaScript。不要把不可信字串當 template,亦不要把 token、headers 或 credential 放進 data/context。
mergeContext 指定同一個 context key:先讀 global object,再以 flow object 覆蓋,最後在允許 input override 時由 payload data 覆蓋;config data 的優先級最低。block=true 只移除 payload 這一層,context 仍可覆蓋 config。因此啟用 mergeContext 前還要限制誰能寫該 flow/global key、物件 shape、保存期限與清除流程。
entity、device、area、floor、label target
v7 editor 的 Target selector 支援五種 ID。config 欄位會轉成 HA target keys:floor_id、area_id、device_id、entity_id、label_id。設定陣列為空時不送該 key;只有一筆時 runtime 可能轉成字串,多筆保持集合。值可渲染 environment variable 或 Mustache,因此 env/context 也屬審查邊界。
| target 類型 | 非執行 placeholder | 範圍風險 | 審查問題 |
|---|---|---|---|
| entity | PLACEHOLDER_ENTITY_ID | 最精確,但 ID 改名/替換可能失效 | 是否為專用測試 entity、是否允許此 action |
| device | PLACEHOLDER_DEVICE_ID | 一個 device 可含多個 entities/capabilities | action 對 device 的實際作用面是否明確 |
| area | PLACEHOLDER_AREA_ID | 成員會隨 entity/device 指派改變 | 新增成員是否會未經審查被納入 |
| floor | PLACEHOLDER_FLOOR_ID | 比 area 更廣,可能跨多個空間 | 是否真的需要整層;預設應拒絕 |
| label | PLACEHOLDER_LABEL_ID | label membership 可動態擴張 | 誰能改 label、是否有變更監控 |
「UI 顯示可選」不代表每個 action 都接受所有 target。editor 會依 HA service metadata 過濾或隱藏 target selector;未知 action 也可能沒有可靠 metadata。你必須以目標 HA 版本的 action 文件與 services metadata 核對,依 action schema 明確放置 entity ID,不能依截圖捏造欄位或依賴相容搬移。
fields.entity_id 且 data 沒有 entity_id,只有在合併後的 target.entity_id 是單一字串時,才會發警告並暫時移到 data;陣列 target 不會由這條相容路徑搬移。此行為註明將在 1.0 停止,不要依賴,並在升級前修正。target 可同時合併多種類型,作用範圍可能是聯集而非「五選一縮小」。安全草稿一次只用一種最精確 target;area/floor/label 一律視為動態群組,呼叫前重新核對成員。文件、Debug 與 issue 只出現 placeholder,不公開真實 ID。
action-dependent data、queue 與具體安全情境
Data schema 由所選 HA action 決定,沒有通用的 message、brightness 或 temperature 保證。editor 可從 HA services metadata 顯示描述與 example data,但這是連線環境的 metadata,不應編造 UI 或截圖。載入示例資料後也只能當草稿,必須移除非必要欄位並核對型別、範圍與單位。
三個具體安全情境都應保持非執行:
- 測試通知草稿:action=
PLACEHOLDER_HA_ACTION,target=PLACEHOLDER_ENTITY_ID,data 只有固定TEST_ONLY_NO_SEND。核對接收者最小化、內容不含 state attributes、重複送出政策;範例節點仍停用。 - 測試裝置狀態變更草稿:target 只用
PLACEHOLDER_DEVICE_ID,不列出任何可操作 domain/entity。先證明重複呼叫可復原、現場有人監看、停止路徑獨立;不連真實事件。 - 區域維護草稿:area/floor/label 只用對應 placeholder,先匯出成員清單供人工審查。任何成員超出單一測試範圍即拒絕,不使用 queue 補送。
Queue 只在 HA 未連線時影響訊息:none 立即 NoConnectionError;first 只保留第一筆;last 每次改成最後一筆;all 全部加入記憶體陣列。連線 ready 後 controller 以 pop() 取出,因此累積多筆時是從陣列尾端處理;不要假設 FIFO。queue 不持久、沒有本章可驗證的硬上限,也不是 exactly-once。
| queue | 斷線時 | 風險 | 本章建議 |
|---|---|---|---|
none | 直接 error | 需要明確失敗處理 | 安全預設;不延後控制 |
first | 只在空 queue 時保留第一筆 | 後續意圖消失,恢復時可能已過期 | 除非有嚴格時效證明,否則不用 |
last | 覆成最新一筆 | 中間事件消失,最新意圖仍可能過期 | 需要 version/timestamp gate 才能評估 |
all | 全部留在記憶體 | 無界累積、重連突發、處理順序不是 FIFO | 控制類 Action 禁用 |
若業務真的需要可靠工作佇列,應採具持久化、順序、去重、截止時間、取消與 dead-letter 契約的專用架構;Action 的 queue 四選項不能取代。斷線恢復時更要先重新查 Current State,而不是盲目重播過期控制。
output properties、return_response 與錯誤
回應能力與 shape 由指定 action 決定;不要假設每個 action 都回資料,也不要假設結果固定寫入 msg.payload。只有在 Output Properties 明確映射後,指定 property 才會被寫入。
response,送出的 call_service 會將 return_response 設為 true;沒有 response metadata 時為 false。較舊 HA 則不設定這個值。本章基線仍遵守套件 README 的 HA 2024.3+ prerequisite,這段判斷只說明 runtime 邊界。呼叫 promise resolve 後,controller 把 response?.response 提供為 Output Properties 的 results,並把實際送出的 domain、service、data、target 組成 sentData 來源。
| output value type | 內容 | 可能情況 | 建議 mapping |
|---|---|---|---|
sentData | domain、service、data、target | 含環境 ID 或敏感參數 | 不要整包輸出;只取清理後 correlation 欄位 |
results | HA call response 的 response 部分 | action-dependent,可能 undefined | 寫 msg.actionResult 後驗證 shape |
msg | 輸入訊息可供 mapping | 可能帶外部資料 | 只用允許清單保留必要欄位 |
| 無 output properties | 成功後送出原 message | payload 不會自動變成結果 | 下游不可假設 payload 是 response |
return_response 是 wrapper 根據 action metadata 決定的 WebSocket 呼叫旗標,不是你應塞進 data 的通用參數。action 是否支援/要求 response 以及 response shape 都由 HA action 定義;rolling 行為要以目標 HA 官方文件與 services metadata 核對。
常見 error 包括未連線且 queue=none、action 格式無效、JSON 無法解析、輸入 schema 不合、HA call 拒絕或連線途中失敗。這些情況不會產生成功 output mapping。用 Catch 捕捉、Status 觀察,錯誤支線終止於有限 Debug/告警設計;絕不自動改 target 重試,也不把完整 data、response、token 或真實 ID 寫入 log。
重試必須知道 action 是否 idempotent。網路 timeout 可能發生在 HA 已執行但 Node-RED 尚未收到 response 的時刻;直接重試可能重複副作用。安全設計需要 request/correlation ID、action-specific 查證、重試上限、退避與人工復原,不能只看 output 是否收到。
故障排除
- palette 顯示 Action,匯出卻是 api-call-service:這是 0.80.3 的預期 persisted type。不要手改成 action,也不要因 legacy 名稱重建節點。
- action 格式無效:保持節點停用,確認草稿使用完整
PLACEHOLDER_HA_ACTION且日後核准值須為 domain.service。不要用真實 action 猜測測試。 - target 比預期更廣:檢查 entity/device/area/floor/label 是否同時存在、payload.target 是否在 block=false 時合併、area/floor/label 成員是否改變。中止變更並回到單一 placeholder 允許清單。
- 設定 data 被另一個值取代:檢查 mergeContext;即使 Block Input Overrides=true,flow/global context 仍可覆蓋 config data。清除受污染 key,盤點 writers,改用固定 schema。
- 成功後 msg.payload 沒 response:這是可能的正常行為。確認 action metadata 是否有 response,並在 Output Properties 明確把 results 映射到指定欄位;沒有 response 的 action 不可捏造結果。
- 斷線恢復後出現過期操作:立即停止副作用支線,檢查 queue 是否非 none。first/last/all 都可能保存過期意圖,all 還以 pop 處理;控制操作回到 queue=none。
- 只看到 timeout,不確定 HA 是否已執行:不要直接重試。以 action-specific 唯讀查詢確認結果,保留清理後 correlation,依 idempotency 與人工復原政策決定。
- Debug 或 log 含真實 target/data:立即停用完整 Debug,清理 sidebar/log/匯出與 issue;若 credential 可能曝光,走事故輪替流程,不把值再次貼出。
固定來源
- HA WebSocket 0.80.3 固定提交 2cbbb69:核對
src/nodes/action、src/homeAssistant/Websocket.ts、shared InputService 與 persisted registration。 - Action 官方節點文件:欄位、input/output 與 queue 使用者說明;若 rolling 文件不同,以固定提交為本章基線。
- Home Assistant 官方 WebSocket API 文件:
call_service與 response protocol 背景。 - Home Assistant 官方 actions/services 開發文件:target、service data 與 response data 邊界。
唯一相關下載是 07-action.json;它只供離線審查,Action 節點停用且所有環境欄位使用 placeholder。本站不提供真實 action、entity、device、area、floor、label 或可執行截圖。
常見問題
為什麼 UI 叫 Action,JSON type 不是 action?
api-call-service 以相容既有 flow。palette label 與 persisted registration 可以不同;不要手改 JSON type。Block Input Overrides=true 後就完全不會被動態資料影響嗎?
target 可以同時使用 entity、area 與 label 嗎?
Queue all 是可靠的離線 FIFO 嗎?
pop() 處理,且沒有 exactly-once、截止時間或持久化保證。控制 Action 使用 queue=none。每個 Action 都會把回傳值寫到 msg.payload 嗎?
response?.response 提供為 results 來源,還要用 Output Properties 明確映射。沒有 response 時應容許 undefined。