Skip to content

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 结构:

// debug 节点配置
msg.payload  // 当前值
msg.topic    // 来源
JSON.stringify(msg, null, 2)  // 格式化输出

案例三:延迟节点 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。

延伸阅读