title: Node-RED + Home Assistant 复杂自动化流:可视化编排实战 description: Node-RED 与 HA 深度集成:从基础 flow 到条件分支、循环、外部 API 调用。含安防系统、能源管理、设备联动三个完整 flow 示例。 tags: [homeassistant, nodered, flow, automation, iot] lang: zh date: 2026-10-22 original: true answer: | Node-RED 是 HA 生态里最适合做复杂自动化的可视化工具,flow-based 编程比 YAML 灵活得多。 核心节点:ha-event、ha-call-service、switch、function(JS)、delay、trigger。 最佳实践:简单自动化用 HA 原生 YAML,复杂多步/多条件/需要外部 API 的用 Node-RED。 最容易翻车的是 token 过期和函数节点抛异常——必须加 error handler。 faq: - q: Node-RED 和 HA 原生自动化选哪个? a: 单条件单动作(如"温度 > 30 度开空调")用 HA 原生 YAML,10 秒搞定。多条件分支、循环、外部 API 调用、需要调试日志的用 Node-RED。两者可以共存,HA 触发 Node-RED flow 也行,Node-RED 调用 HA service 也行。 - q: Node-RED 在 Docker 里跑还是装 add-on? a: HA OS 用户用 Supervisor Add-on(最简单)。Docker 用户用官方 node-red 镜像,配 network_mode: host 方便访问 HA API。Add-on 版本内置 ha 节点,Docker 版需要手动装 node-red-contrib-home-assistant-websocket。 - q: flow 写错了怎么回滚? a: Node-RED 的 flow 数据存在 flows.json(默认 /data/flows.json),每次部署自动备份到 flows-backup.json。也可以在 UI 里 Export/Import 整个 flow。建议每改一次 flow 就 Export 一次到 Git。
Node-RED + Home Assistant 复杂自动化流:可视化编排实战
YAML 自动化在简单场景很高效,但一旦条件超过 3 层、需要调外部 API、或者要记录执行日志,YAML 就显得笨重。Node-RED 的 flow-based 编程是更好的选择——可视化、可调试、支持 JavaScript 函数节点。
架构:Node-RED 和 HA 怎么通信
Node-RED 通过 Home Assistant WebSocket API 与 HA 通信,不需要暴露 HTTP 端口。
┌─────────────┐ WebSocket ┌──────────────┐
│ Node-RED │ ←→ (ws://HA:8123) ←→ │ Home Assistant │
│ (port 1880)│ │ (port 8123) │
└──────┬──────┘ └──────────────┘
│
│ HTTP (可选)
▼
┌─────────────┐
│ 外部 API │ (气象数据、电价、摄像头 RTSP)
└─────────────┘
认证方式:Long-Lived Access Token(LLAT),在 HA → Profile → Access Tokens 里生成,有效期永久(除非手动撤销)。
# 生成 LLAT
# HA UI: Settings → People → Your name → Long-Lived Access Tokens
# 复制 token,存到 Node-RED 环境变量或 config.ini
环境搭建
Docker Compose 部署
# ~/nodered/docker-compose.yml
services:
nodered:
image: nodered/node-red:latest
restart: unless-stopped
ports:
- "1880:1880"
- "1881:1881" # Admin UI
environment:
- TZ=Asia/Shanghai
- NODE_RED_ENABLE_PROJECTS=true
- HOMEASSISTANT_URL=http://homeassistant:8123
- HOMEASSISTANT_TOKEN=your_llat_token_here
volumes:
- ./data:/data # flow 数据持久化
networks:
- ha-net
networks:
ha-net:
external: true # 和 HA 共用同一个 Docker network
# 安装 HA 节点
cd ~/nodered/data
npm install node-red-contrib-home-assistant-websocket
# 重启
docker compose up -d nodered
# 访问 http://你的IP:1880
HA Add-on 部署(更简单)
Settings → Add-ons → Home Assistant Community Store → 搜 Node-RED → Install → Start。无需额外配置,内置 ha 节点。
Flow 1:安防系统完整逻辑
这是一个"离家 → 布防 → 触发报警 → 通知 → 恢复"的完整安防流,涉及多个条件分支和延迟。
[
{
"id": "flow-security",
"nodes": [
{
"id": "trigger-motion",
"type": "ha-event",
"ha_events": ["state_changed"],
"entity_id_filter": "binary_sensor.*_motion",
"name": "检测到移动"
},
{
"id": "check-armed",
"type": "switch",
"name": "安防是否布防?",
"property": "payload",
"propertyType": "msg",
"rules": [
{ "t": "eq", "v": "armed_away", "vt": "str" },
{ "t": "else" }
]
},
{
"id": "check-time",
"type": "switch",
"name": "时间在布防时段?",
"rules": [
{ "t": "between", "v": "18:00", "v2": "06:00", "vc": "time" },
{ "t": "else" }
]
},
{
"id": "count-sensors",
"type": "function",
"name": "统计触发传感器数量",
"func": "let count = context.get('triggerCount') || 0;\ncount++;\ncontext.set('triggerCount', count);\nmsg.payload = count;\nreturn msg;"
},
{
"id": "alert-if-3plus",
"type": "switch",
"name": "触发 >= 3 个传感器?",
"rules": [
{ "t": "gte", "v": "3" },
{ "t": "else" }
]
},
{
"id": "send-alert",
"type": "ha-call-service",
"name": "发送报警通知",
"service": "notify.mobile_app",
"target": { "device_id": "phone-device-id" },
"data": {
"message": "⚠️ 安防触发!多传感器同时报警",
"title": "安防警报"
}
},
{
"id": "reset-counter",
"type": "function",
"name": "30 秒后重置计数",
"func": "setTimeout(function() {\n context.set('triggerCount', 0);\n}, 30000);\nreturn null;"
}
],
"edges": [
{ "from": "trigger-motion", "to": "check-armed" },
{ "from": "check-armed", "rule": "true", "to": "check-time" },
{ "from": "check-armed", "rule": "false", "to": "end-normal" },
{ "from": "check-time", "rule": "true", "to": "count-sensors" },
{ "from": "count-sensors", "to": "alert-if-3plus" },
{ "from": "alert-if-3plus", "rule": "true", "to": "send-alert" },
{ "from": "send-alert", "to": "reset-counter" }
]
}
]
节点说明:
- ha-event:监听 HA 事件(state_changed、call_service 等)
- switch:条件分支,支持 eq/between/gte 等操作
- function:JavaScript 函数节点,操作 msg.payload 和 context
- ha-call-service:调用 HA service(notify、light.turn_on 等)
调试技巧
# Node-RED debug 面板看 msg 内容
# UI: 点击右上角 Debug 面板
# 常用 debug 表达式
msg.payload # 当前值
msg.topic # 事件源 entity_id
msg.data.old_state.state # 状态变更前
msg.data.new_state.state # 状态变更后
Flow 2:太阳能 + 电池 能源管理
这是最复杂的 flow,涉及电价时段、太阳能发电预测、电池充放电策略。
[
{
"id": "flow-energy",
"nodes": [
{
"id": "timer-check",
"type": "inject",
"name": "每 5 分钟检查",
"topic": "energy_check",
"repeat": "300",
"at": "*/5 * * * *"
},
{
"id": "get-solar",
"type": "ha-get-state",
"name": "获取太阳能功率",
"entity": "sensor.solar_power",
"output": "payload"
},
{
"id": "get-battery",
"type": "ha-get-state",
"name": "获取电池电量",
"entity": "sensor.battery_level",
"output": "payload"
},
{
"id": "get-grid-price",
"type": "ha-get-state",
"name": "获取实时电价",
"entity": "sensor.electricity_price",
"output": "payload"
},
{
"id": "func-decide",
"type": "function",
"name": "充放电决策",
"func": "// 接收三个值\nconst solar = parseFloat(msg.solar?.payload || 0);\nconst battery = parseFloat(msg.battery?.payload || 0);\nconst price = parseFloat(msg.price?.payload || 0);\n\n// 电价阈值:> 0.8 元/kWh 为高峰\nconst PEAK_THRESHOLD = 0.8;\nconst BATTERY_LOW = 20;\nconst BATTERY_FULL = 90;\n\nlet action = 'idle';\nlet reason = '';\n\nif (solar > 2000) {\n // 太阳能充足,优先给电池充电\n if (battery < BATTERY_FULL) {\n action = 'charge_battery';\n reason = `太阳能 ${solar.toFixed(0)}W,开始充电`;\n } else {\n action = 'sell_to_grid';\n reason = `电池已满,剩余太阳能售电`;\n }\n} else if (price > PEAK_THRESHOLD && battery > BATTERY_LOW) {\n // 电价高峰,用电池供电\n action = 'discharge_battery';\n reason = `电价 ${price.toFixed(2)} 元,电池供电`;\n} else if (price < 0.3 && battery < BATTERY_LOW) {\n // 电价低谷,充电备用\n action = 'charge_from_grid';\n reason = `低价充电 ${price.toFixed(2)} 元`;\n} else {\n reason = '无需操作';\n}\n\nmsg.action = action;\nmsg.reason = reason;\nmsg.payload = { action, reason };\nreturn msg;"
},
{
"id": "call-charge",
"type": "ha-call-service",
"name": "执行充电",
"service": "homeassistant.turn_on",
"target": { "entity_id": "switch.charger" }
},
{
"id": "call-discharge",
"type": "ha-call-service",
"name": "执行放电",
"service": "homeassistant.turn_off",
"target": { "entity_id": "switch.charger" }
},
{
"id": "log-action",
"type": "debug",
"name": "记录决策日志",
"target": "console",
"targetType": "msg"
}
],
"edges": [
{ "from": "timer-check", "to": "get-solar" },
{ "from": "timer-check", "to": "get-battery" },
{ "from": "timer-check", "to": "get-grid-price" },
{ "from": "get-solar", "to": "func-decide" },
{ "from": "get-battery", "to": "func-decide" },
{ "from": "get-grid-price", "to": "func-decide" },
{ "from": "func-decide", "rule": "charge", "to": "call-charge" },
{ "from": "func-decide", "rule": "discharge", "to": "call-discharge" },
{ "from": "func-decide", "to": "log-action" }
]
}
]
决策逻辑说明: - 太阳能 > 2kW 且电池 < 90% → 充电 - 电价 > 0.8 元/kWh 且电池 > 20% → 放电供电 - 电价 < 0.3 元/kWh 且电池 < 20% → 低价充电 - 其他 → 保持现状
故障排查:action 没有执行
# 检查 call-charge 节点配置
# 1. 确认 switch.charger 实体存在
# HA → Developer Tools → States → 搜索 charger
# 2. 手动测试 service call
# curl -H "Authorization: Bearer YOUR_TOKEN" \
# -H "Content-Type: application/json" \
# http://localhost:8123/services/homeassistant/turn_on \
# -d '{"entity_id": "switch.charger"}'
# 3. 检查 Node-RED 日志
docker logs nodered | grep -i error
Flow 3:设备联动——"回家模式"
简单但实用,展示如何实现多设备顺序触发和延迟。
[
{
"id": "flow-home",
"nodes": [
{
"id": "geo-trigger",
"type": "ha-event",
"name": "主人进入 home zone",
"ha_events": ["state_changed"],
"entity_id_filter": "person.owner"
},
{
"id": "delay-wait",
"type": "delay",
"name": "等待 2 分钟确认在家",
"hold": true,
"delay": "2"
},
{
"id": "check-weather",
"type": "ha-get-state",
"name": "获取天气",
"entity": "weather.home"
},
{
"id": "func-choose",
"type": "function",
"name": "选择回家模式",
"func": "const temp = parseFloat(context.get('outdoor_temp') || 25);\nconst weather = msg.payload?.currently?.condition || 'clear';\n\nlet mode = 'comfort';\nif (temp > 30) mode = 'cool';\nelse if (temp < 10) mode = 'heat';\nelse if (weather === 'rain') mode = 'dry';\n\ncontext.set('home_mode', mode);\nmsg.mode = mode;\nreturn msg;"
},
{
"id": "seq-actions",
"type": "change",
"name": "顺序执行",
"rules": [
{ "act": "delete", "p": "payload" }
]
},
{
"id": "open-door",
"type": "ha-call-service",
"name": "开锁",
"service": "lock.unlock",
"target": { "entity_id": "lock.front_door" }
},
{
"id": "turn-on-light",
"type": "ha-call-service",
"name": "开灯 + 窗帘",
"service": "light.turn_on",
"target": { "entity_id": "light.living_room" },
"data": { "brightness_pct": 60, "color_temp": 3500 }
},
{
"id": "set-climate",
"type": "ha-call-service",
"name": "空调设定",
"service": "climate.set_temperature",
"target": { "entity_id": "climate.living_room" },
"data_template": "{\"temperature\": {{ mode_temp }}, \"hvac_mode\": \"{{ mode_hvac }}\"}"
},
{
"id": "notify-welcome",
"type": "ha-call-service",
"name": "欢迎通知",
"service": "notify.mobile_app",
"data": { "message": "欢迎回家,客厅已准备好" }
}
],
"edges": [
{ "from": "geo-trigger", "to": "delay-wait" },
{ "from": "delay-wait", "to": "check-weather" },
{ "from": "check-weather", "to": "func-choose" },
{ "from": "func-choose", "to": "open-door" },
{ "from": "open-door", "to": "turn-on-light" },
{ "from": "turn-on-light", "to": "set-climate" },
{ "from": "set-climate", "to": "notify-welcome" }
]
}
]
3 个真实翻车案例
案例一:token 过期后 flow 静默失败
现象:flow 之前正常,某天突然不触发任何 action,但日志里没有错误。
根因:LLAT token 被撤销(可能因为修改密码或安全扫描),WebSocket 连接断开但 flow 没有 error handler。
# 检查 Node-RED 与 HA 的连接状态
docker logs nodered | grep -i "websocket\|connect\|error"
# 重新生成 token
# HA → Settings → People → Long-Lived Access Tokens → Create Token
# 更新 Node-RED 环境变量 HOMEASSISTANT_TOKEN
docker compose restart nodered
预防:在 flow 里加 error handler 节点:
{
"id": "error-handler",
"type": "catch",
"name": "错误处理",
"scope": ["func-decide"],
"to": "log-action"
}
案例二:function 节点 JavaScript 报错
现象:某些 flow 节点不执行,Node-RED 日志报 TypeError: Cannot read property 'payload' of undefined。
根因:msg 对象结构不符合预期,通常是因为上游节点没有输出 payload。
// ❌ 危险写法
const solar = msg.payload.solar_power;
// ✅ 安全写法
const solar = (msg && msg.payload && msg.payload.solar_power) ? msg.payload.solar_power : 0;
排查:在每个 function 节点前加 debug 节点,查看 msg 结构:
案例三:延迟节点 hold=true 导致消息堆积
现象:flow 执行越来越慢,最终卡死。
根因:delay 节点设置 hold: true,消息在队列里排队但不释放内存。
// ❌ 错误配置
"type": "delay",
"hold": true,
"delay": "2"
// ✅ 正确配置:用 timeout 替代 hold
"type": "delay",
"timeout": "2",
"timeoutUnits": "seconds",
"name": "等待确认"
判断:delay 节点的 hold 模式适合一次性延迟;循环触发的场景用 timeout 模式,不会堆积消息。
总结
Node-RED 的核心价值是可视化调试和复杂逻辑表达。一个 flow 出问题,直接在 UI 里看哪条线断了、哪个节点没输出,比翻 YAML 日志快得多。
我的判断:Node-RED 适合以下场景——多条件分支、需要外部 API 调用、需要记录执行历史、或者 YAML 写起来很别扭的复杂流。简单自动化还是用 HA 原生,别什么都扔给 Node-RED。
延伸阅读
- HA 自动化入门:trigger/condition/action 三段式
- AI Agent 智能家居自动化:Blueprint AI 2.0
- 智能家居能源管理:Solar + Battery 联动与峰谷调度