通知、門窗、氣候與異常處理實戰
通知與氣候控制會把感測資料轉成對人或設備的外部副作用。安全流程必須先驗證門窗與溫度,再做去重、速率限制、人工覆寫與 unavailable 分流。本章只產生「候選意圖」;通知與氣候 Action 全部停用,沒有真實通知 target、裝置 ID 或實體 ID。
為何需要本章
一個門窗感測器變成開啟,可以代表通風、忘記關門、感測器抖動或連線恢復後的舊狀態。溫度超過門檻也可能是字串解析錯誤、單位不同、陳舊資料或感測器 unavailable。若每次事件都直接送通知,使用者很快會忽略警報;若直接控制氣候裝置,錯誤資料可能造成耗能、設備頻繁啟停或與人工設定互相爭奪。
本章用三個非安全關鍵情境:
- 門窗持續開啟後產生一次候選提醒,關閉時清除事件;不控制鎖具或警報。
- 室內數值超過經核准門檻時產生氣候建議,最後仍需檢查門窗、模式與人工覆寫;不直接設定真實設備。
- 資料 unknown、unavailable、非數值、過期或通知過密時走異常分支;不把異常當成可執行條件。
PLACEHOLDER_NOTIFY_ACTION、PLACEHOLDER_CLIMATE_ENTITY 與 PLACEHOLDER_OPENING_ENTITY 不得替換成真值後直接部署。事件、異常、提醒與控制是四條支線
| 分支 | 輸入條件 | 可產生結果 | 禁止捷徑 |
|---|---|---|---|
| 狀態事件 | 門窗或氣候實體的已知新狀態 | 建立/更新候選事件 | 事件一到就呼叫 Action |
| 資料異常 | unknown、unavailable、null、非數值、過期 | 遮罩診斷與健康計數 | 把異常轉成 0、false 或 closed |
| 通知 | 事件持續、去重通過、速率額度存在 | 候選通知意圖 | 用動態上游 target 或無限重試 |
| 氣候控制 | 數值、單位、門窗、模式、覆寫都有效 | 候選控制意圖 | 只看單一溫度就操作設備 |
通知是 Action 的一種使用情境,但 action 名稱、可用 target、data schema 與實際回應由 Home Assistant 中已安裝的通知整合決定。不能宣稱每個通知 action 都接受相同欄位,也不能把「節點沒有報錯」當作收件端已收到。氣候 action 同樣沒有跨整合的通用成功回應;驗證要依核准整合的文件與後續狀態,且限制重試。
使用者可以從 Home Assistant、設備面板、遙控器或另一條自動化改變氣候設定。manual override 必須是流程中的第一級狀態,不是錯誤。覆寫有效期間,流程可以觀察與提出診斷,但不送反向控制意圖;到期時重新評估,不自動恢復舊設定。
八步完成安全規格
- 建立資料契約。
列出每個來源的 entity 類型、原始 state、attributes、單位、更新時間與允許值。使用整值佔位符,不在文件、Flow、Debug 放入真實家庭 ID 或通知目標。
- 把異常值先分流。
unknown、unavailable、null、空字串、NaN、Infinity、單位不符與超出物理合理範圍先進 INVALID;只有明確通過的值才進門窗或氣候邏輯。
- 加入持續時間與恢復事件。
門窗開啟需持續一段核准時間才建立提醒;關閉時取消尚未送出的候選並結束事件。氣候門檻使用回差,避免在邊界頻繁切換。
- 設計去重鍵與速率限制。
去重鍵以匿名區域、事件類型與事件 generation 組成;為每事件冷卻、每區域上限與全域上限設定有限窗。超額只記計數,不排隊補發大量舊通知。
- 檢查相依狀態。
氣候候選到期前,用 Current State 重查室內數值、門窗、設備模式、資料新鮮度與 manual override。任一查詢失敗都不控制。
- 只建立停用 Action。
依07-action.json審查 action、target、data、queue 與 Block Input Overrides;保留
d: true,target 只用佔位符,Action 不接上游。 - 用假資料做矩陣測試。
涵蓋開啟抖動、恢復、重複、通知爆量、非數值、單位錯誤、unavailable、手動覆寫、重啟與 Action 假失敗。測試只有手動 Inject 與 Debug,沒有外部連線。
- 分開核准觀察與動作。
若需測試環境,先只核准唯讀監聽;通知測試 target 與氣候測試設備各自另行審查。正式家庭或生產 target 不在本章測試範圍。
通知 Action:data、去重與速率
候選通知資料
先在純訊息層建立以下抽象物件,不直接傳給 Action:
{
"kind": "OPENING_STILL_OPEN",
"anonymous_area": "PLACEHOLDER_AREA_ALIAS",
"event_generation": "PLACEHOLDER_EVENT_GENERATION",
"severity": "informational",
"observed_at": "PLACEHOLDER_ISO_TIMESTAMP"
}
轉換層再依已核准通知 action 的官方 schema建立 data。一般情境可能需要文字內容,但欄位名稱與進階 data 不可跨整合假設;範例不填收件人、裝置、群組、URL、影像或可執行按鈕。訊息內容避免透露「家中無人」、精確地址、門窗 ID 或人員姓名。
Action 節點保險
- action 固定為
PLACEHOLDER_NOTIFY_ACTION,不允許從msg動態覆寫;Block Input Overrides 開啟。 - target 固定為
PLACEHOLDER_NOTIFY_TARGET,不使用真實 mobile app device ID、person、area、label 或群組。 - data 由允許清單(allowlist)建構,不合併整個
msg.payload、context 或原始事件。 - Queue 策略不是速率限制。斷線排隊可能在恢復後集中送出;通知流程通常應丟棄過期意圖,而不是 queue all。
- Action 輸出只表示節點呼叫流程的結果,不能宣稱所有通知服務都回傳相同內容或終端已顯示。
| 規則 | 例子 | 超出時 |
|---|---|---|
| 事件去重 | 同一 opening generation 只提醒一次 | 捨棄重複,累加匿名計數 |
| 事件冷卻 | 持續開啟期間不反覆提醒 | 等明確恢復後才能建立新 generation |
| 區域速率 | 單一匿名區域每時窗有限次 | 不送,標記 rate_limited |
| 全域速率 | 多感測器故障時保護收件端 | 熔斷通知 Action,保留摘要 |
| 過期窗 | 重連後舊事件超過容忍時間 | 不補發,只記恢復摘要 |
通知失敗不能無限重試。設定最多次數、退避與總時限,且重試仍受同一去重鍵與全域上限控制。敏感警報需求應使用專門且受監控的通道;本章的一般通知不是生命安全保證。
門窗:持續開啟、恢復與隱私
門窗通常由 binary sensor 實體呈現,但實際 state 與 device class 必須在你的整合上查證。不要根據圖示或名稱猜 on 必定代表 open。建立映射前,先在隔離環境觀察已知開與關各一次;本章仍以 PLACEHOLDER_OPENING_ENTITY 取代真實 ID。
已知 CLOSED → 已知 OPEN
→ debounce → 建立 generation → 等待持續時間
→ 重查仍 OPEN → 通知去重/速率 → 候選通知 → 停用 Action
OPEN → CLOSED
→ 取消未到期候選 → 標記 resolved
→ 只有先前確實送過提醒時,才可能建立候選恢復通知
任一 → unknown / unavailable / 缺資料
→ 取消控制意圖 → anomaly 分支 → 不宣稱門已關閉
如果門在持續時間內關閉,不送「仍開啟」提醒。若提醒已產生後才關閉,是否需要恢復通知由產品規則決定;恢復通知與原事件共用 correlation key,避免被當成新的警報。Node-RED 或 Home Assistant 重啟後,先查目前狀態與 last_changed 新鮮度,不根據 context 中舊 generation 補送。
門窗事件能推測出入與作息。Debug 只顯示匿名區域、OPEN/CLOSED/INVALID、持續級距與 decision;不要記錄門名、使用者 ID、精確時間序列或原始 attributes。若要把 Zone、Tag 或 Sentence 加入「有人靠近」條件,風險更高:Zone 涉及位置、Tag 可能含 tag/device/user ID、Sentence 可能含原句。它們只能在另行核准後輸出匿名布林條件,節點保持停用。
Calendar、Zone、Tag、Sentence 的實用但保守用途
氣候:數值驗證、回差與人工覆寫
數值進入比較前的六道閘門
- 值存在,且不是 unknown、unavailable、null 或空字串。
- 明確轉成有限數值;拒絕 NaN、Infinity 與混雜單位字串。
- 單位與流程設定一致;不能在未轉換時比較攝氏與華氏。
- 數值在該感測用途的合理範圍;異常高低不直接觸發控制。
- last_updated 或接收時間未超過新鮮度門檻;重連載入的舊值不當新事件。
- 來源在核准 exact 清單,未被 substring/regex 意外納入。
不要用單一門檻來回切換。例:高門檻產生「需要降溫」候選,只有降到較低恢復門檻並持續一段時間才清除;兩門檻之間保持現有決策。具體數值取決於空間、設備、健康與能源政策,本章不提供可直接套用的溫度。
候選控制前的相依檢查
- 相關門窗全部是已知 CLOSED;任何 OPEN/INVALID 都阻擋氣候控制。
- climate 實體存在且不是 unavailable;目前模式與可用屬性符合該整合文件。
- manual override 不在有效期;若使用者剛調整設定,流程不反向覆蓋。
- 最近同類意圖不在 cooldown,設備沒有短週期啟停風險。
- Action 與 data 已依該實體支援能力核對,不猜測通用 service response。
NO_ACTION_INVALID_DEPENDENCY。人工覆寫與恢復
人工覆寫資料至少包含匿名控制範圍、方向、建立時間、到期時間與來源類別,且有最大期限。覆寫期間仍可記錄「若無覆寫會產生的候選」供調整規則,但不得送 Action。覆寫到期時重新查詢,不重播期間累積的舊候選。若 Node-RED 重啟後無法確認覆寫狀態,預設不控制。
真正 Action 保持停用,使用 PLACEHOLDER_CLIMATE_ACTION 與 PLACEHOLDER_CLIMATE_ENTITY。data 不放實際 setpoint;只以 PLACEHOLDER_APPROVED_VALUE 標示待核准值。對設備的任何操作都要在非正式環境、有限 target、可人工中止的條件下另行驗證。
異常分支與 HA entity 節點清單
0.80.3 除了以一般 WebSocket 節點讀取既有 Home Assistant 實體,也能透過 Entity config 與另裝的 hass-node-red custom integration 建立或互動於 Node-RED 暴露的 entity 節點。只安裝 palette 套件不會滿足這個前提。這些節點不是「更安全的變數」;它們可能接受 Home Assistant 端外部操作或更新狀態,測試時全部停用。下表整理本情境可能用到的節點:
| 節點 | 本章可用角色 | 安全邊界 |
|---|---|---|
| Events: calendar | 核准時段的候選條件 | 輪詢延遲與行程隱私;不得作唯一保全條件 |
| Sentence | 人工延後提醒的候選輸入 | 原句、device/response ID 敏感;response 有外部副作用 |
| Tag | 人工確認候選輸入 | tag/device/user ID 敏感;掃描不等於強身分驗證 |
| Zone | 匿名位置條件 | 座標敏感且可能漂移;不直接控制門鎖或氣候 |
| Select entity | 暴露有限選項的覆寫介面 | listen/get/set 模式依設定;只接受已列選項且節點停用 |
| Binary Sensor entity | 暴露匿名異常旗標 | 輸入會更新整合實體;unknown 不可硬轉 false |
| Button entity | 人工確認候選按鈕 | 按下會觸發流程,不等同授權;不直連 Action |
| Number entity | 暴露經限制的門檻候選值 | 必須驗證數值、上下限與單位;set 有副作用 |
| Sensor entity | 發布匿名健康摘要 | state/attributes 可能由訊息覆寫;不發布敏感原始資料 |
| Switch entity | 有限期限的自動化啟用意圖 | 無 persisted value 時 state storage 預設 enabled;須另設 fail-closed startup gate |
| Text entity | 受限的非敏感註記 | listen/get/set 需驗證長度與內容;不可承載秘密或任意 action |
統一異常信封
{
"category": "INVALID_STATE_OR_VALUE",
"source_alias": "PLACEHOLDER_SOURCE_ALIAS",
"observed_at": "PLACEHOLDER_ISO_TIMESTAMP",
"decision": "NO_ACTION",
"detail_code": "PLACEHOLDER_NON_SENSITIVE_CODE"
}
異常信封不含原始 state、attributes、位置、行程、語句、通知 target 或 device ID。Catch/Status 可收節點錯誤與狀態,但不得形成「錯誤就重送 Action」迴圈。計數達門檻時熔斷該分支,人工審查後再恢復。
故障時的預設
- 通知整合 unavailable:不無限排隊,不切換到未核准 target。
- 氣候裝置 unavailable:不控制,也不假裝已達 setpoint。
- 門窗感測器 unavailable:不視為 closed,阻擋氣候候選。
- Entity config/integration 未載入:暴露 entity 分支停用,不影響核心不動作策略。
- 重連或重啟:清除過期候選,重新查詢;不補送舊通知、不重播舊控制。
故障排除
- 同一門窗事件反覆通知:停用通知 Action,檢查 generation 是否在 CLOSED 前保持不變、去重鍵是否含不穩定時間戳、持續事件是否誤建新鍵。修正後以假事件驗證一次 OPEN 最多一個候選。
- 重連後大量舊通知:Queue 或重試可能保存過期意圖。加入最大事件年齡、全域速率與恢復熔斷;超時事件只做匿名摘要,不補發。
- 溫度比較結果顛倒:檢查字串到數值轉換、攝氏/華氏、NaN、門檻與回差方向。保留原始值只在短期受控診斷中查看,確認後立即清除。
- 門窗 open 卻觸發氣候候選:確認整合的 on/off 語意、device class 映射、Current State 查詢輸出與 Switch 規則。OPEN/INVALID 都應阻擋,不能只排除字串 on。
- manual override 被忽略:檢查覆寫 scope、到期時間、時區與重啟後儲存。覆寫判斷必須在候選生成與 Action 前各檢查一次;無法讀取時採不控制。
- Action 節點無錯誤但沒有收到通知:不要推論通用成功回應。保持節點停用,依特定通知整合文件核對 action、target、data、授權與終端狀態;不得用真實 target 反覆試送。
- Number/Select/Text entity 接到意外值:停用節點,檢查 mode、Entity config 與訊息覆寫。建立選項、範圍、長度與型別允許清單;這些 entity 不是可信輸入邊界。
固定來源與相關節點
- HA WebSocket 0.80.3 固定提交 2cbbb69
- 官方 Action 文件、0.80.3 Binary Sensor entity 文件、0.80.3 Number entity 文件
- 0.80.3 Select entity 文件、Button entity 文件、Sensor entity 文件
- 0.80.3 Switch entity 文件、Text entity 文件;這些固定文件與上一列共同說明 entity family 的 custom integration 前提
- Home Assistant 官方 Climate 整合文件、Notify 整合文件
本章涵蓋 11 個相關節點:Events: calendar、Select entity、Sentence、Tag、Zone、Binary Sensor entity、Button entity、Number entity、Sensor entity、Switch entity、Text entity。上表整理各自角色與邊界;Sentence 與七種 entity 節點需要 hass-node-red custom integration,所有可寫、觸發或對外回應模式都必須保持停用。Action 是情境依賴,版本欄位參考安全範例 07,不宣稱各通知或氣候整合具有共同 data 或回應。