Skip to content

Node RED Workflows

hilman2 edited this page Sep 2, 2026 · 1 revision

Node-RED workflows

Driving the same controls from Node-RED, and getting inverter events out of Home Assistant into Node-RED. The integration has no Node-RED specific interface; everything here goes through node-red-contrib-home-assistant-websocket, the palette almost everyone means by "the Home Assistant nodes."

It keeps one websocket connection to Home Assistant open, so unlike a plain REST integration a flow can react to a state change the moment it happens, no polling and no webhook relay needed. That changes which side of a workflow belongs in Node-RED: reacting to events is a genuine strength here, where the n8n page has to relay them out through a webhook. A tight write loop is still better off in a Home Assistant automation for the same reason as there: the export limit's keepalive runs every 15 s, and that is a lot of flow executions for logic that does not need Node-RED's own data.

Entities used on this page

Placeholders, current as of the 2026.9/2026.10 vendor-profile release. Entity names and IDs explains how to find yours; block 124's battery entities need no beta flag, the export ones still do, see Battery and export control.

Placeholder What it is
number.inverter_battery_charge_rate, ..._discharge_rate Block 124, percent of WChaMax
select.inverter_battery_control_mode Block 124, off/charge/discharge/both
switch.inverter_battery_grid_charging Block 124 ChaGriSet, where the device has it
sensor.inverter_operating_state FAULT among its states
number.inverter_export_limit, switch.inverter_export_limit_enabled Export beta, blocks 123/704

And the sunspec2.set_export_limit action, taking config_entry_id, percent and an optional enable.

Connecting Node-RED to Home Assistant

  1. Install the palette: Menu -> Manage palette -> Install, search for node-red-contrib-home-assistant-websocket. The Home Assistant Node-RED add-on ships it already.
  2. In Home Assistant, open your profile, Security, and create a long-lived access token. Copy it once; it is not shown again.
  3. Drag any Home Assistant node onto the canvas and open its Server field to add a new server config node: Base URL is your instance (http://homeassistant.local:8123, or leave it to the add-on default if Node-RED runs as a Home Assistant add-on), and paste the token into Access Token. Every Home Assistant node you add afterwards points at the same config node.

The nodes

Purpose Node Type name in a flow file
Write a value, call a service Action api-call-service
Read an entity's state once Current State api-current-state
React to a state change Events: State server-state-changed
The connection itself Server (a config node, not dropped on the canvas) server

The Action node's Action field takes domain.service, e.g. number.set_value. Target the entity in its Entity list, and put anything beyond the entity id, such as value, in Data as JSON: {"value": 100}.

Events: State fires on every change of the entities you list. Its If State field turns it into a filter, true to output one, false (if wired) to output two, blank for every change; For demands the state hold that long first.

Workflow 1: charge the battery when the price is low

Every hour, read the current price, and below a threshold open the battery for charging at full rate with discharge blocked; above it, put both rates back to 100. Block 124 needs no beta flag, but the controls it drives are unconfirmed on real hardware and they set ceilings rather than forcing a charge; Battery control has the details.

Import through the menu, Import, paste, then open the Server config node and fill in your host and token, and replace the entity ids and the threshold. It is a bare array of nodes, the format Node-RED expects on the clipboard; the table after it rebuilds the same six by hand if your palette version renders it differently.

[
  {
    "id": "ha-server",
    "type": "server",
    "name": "Home Assistant",
    "host": "http://homeassistant.local:8123",
    "access_token": ""
  },
  {
    "id": "trigger-hourly",
    "type": "inject",
    "name": "Every hour",
    "props": [{ "p": "payload" }],
    "repeat": "3600",
    "once": false,
    "payloadType": "date",
    "wires": [["read-price"]]
  },
  {
    "id": "read-price",
    "type": "api-current-state",
    "name": "Read price",
    "server": "ha-server",
    "entity_id": "sensor.electricity_price",
    "outputProperties": [
      { "property": "payload", "propertyType": "msg", "value": "", "valueType": "entityState" }
    ],
    "wires": [["cheap-check"]]
  },
  {
    "id": "cheap-check",
    "type": "switch",
    "name": "Cheap?",
    "property": "payload",
    "propertyType": "msg",
    "rules": [
      { "t": "lt", "v": "0.15", "vt": "num" },
      { "t": "gte", "v": "0.15", "vt": "num" }
    ],
    "outputs": 2,
    "wires": [["charge-full"], ["both-hundred"]]
  },
  {
    "id": "charge-full",
    "type": "api-call-service",
    "name": "Charge rate 100",
    "server": "ha-server",
    "action": "number.set_value",
    "entityId": ["number.inverter_battery_charge_rate"],
    "data": "{\"value\":100}",
    "dataType": "json",
    "wires": [["discharge-zero"]]
  },
  {
    "id": "discharge-zero",
    "type": "api-call-service",
    "name": "Discharge rate 0",
    "server": "ha-server",
    "action": "number.set_value",
    "entityId": ["number.inverter_battery_discharge_rate"],
    "data": "{\"value\":0}",
    "dataType": "json",
    "wires": [[]]
  },
  {
    "id": "both-hundred",
    "type": "api-call-service",
    "name": "Both rates 100",
    "server": "ha-server",
    "action": "number.set_value",
    "entityId": ["number.inverter_battery_charge_rate", "number.inverter_battery_discharge_rate"],
    "data": "{\"value\":100}",
    "dataType": "json",
    "wires": [[]]
  }
]

The same six nodes, by hand:

Node Type Settings
Every hour inject (core) Repeat every 3600 seconds
Read price Current State Entity sensor.electricity_price, output payload as the entity's state
Cheap? switch (core) msg.payload less than 0.15, else the second output
Charge rate 100 Action number.set_value, entity number.inverter_battery_charge_rate, data {"value": 100}
Discharge rate 0 Action Same service, entity number.inverter_battery_discharge_rate, data {"value": 0}
Both rates 100 Action Same service, both entity ids in the Entity list, data {"value": 100}

Home Assistant accepts a list of entity ids in one call, which is why the last node needs one Action rather than two. If your device has the switch.inverter_battery_grid_charging entity, add a switch.turn_on Action alongside the charge step and switch.turn_off alongside the "both 100" branch, the same way the number entities are called.

Workflow 2: an inverter fault straight into Node-RED

The direction Node-RED is built for: no webhook, no rest_command in Home Assistant, the flow reacts the moment the state changes over the same websocket connection.

[
  {
    "id": "ha-server",
    "type": "server",
    "name": "Home Assistant",
    "host": "http://homeassistant.local:8123",
    "access_token": ""
  },
  {
    "id": "fault-trigger",
    "type": "server-state-changed",
    "name": "Inverter fault",
    "server": "ha-server",
    "entities": { "entity": ["sensor.inverter_operating_state"], "substring": [], "regex": [] },
    "outputInitially": false,
    "ifState": "FAULT",
    "ifStateType": "str",
    "outputOnlyOnStateChange": true,
    "outputProperties": [
      { "property": "payload", "propertyType": "msg", "value": "", "valueType": "entityState" },
      { "property": "data", "propertyType": "msg", "value": "", "valueType": "entity" }
    ],
    "wires": [[]]
  }
]

Wire the empty output to whatever you already use for alerts: Telegram, email, a webhook out to something else. After import, open Events: State and check If State reads FAULT with the comparison set to is; the palette's editor normalises that field on its own version, and the JSON above only has to get you close.

msg.payload is the new state string, msg.data.new_state.attributes carries everything else, including the event bitfield if you also listen for sensor.inverter_events.

Workflow 3: the export limit from Node-RED

If the number that decides the limit is computed in Node-RED, for example from a forecast API and tomorrow's prices, let Node-RED write the decision into a Home Assistant helper and leave the keepalive to Home Assistant, the same hand-off as the n8n page:

  • Node-RED: an Action node, input_number.set_value, entity input_number.export_limit_pct, data {"value": 40}; a second Action node, input_boolean.turn_on, entity input_boolean.export_limit_active
  • Home Assistant: the keepalive automation from Zero export and export limiting

A Node-RED outage then changes nothing until the next decision, and a Home Assistant outage still lets the inverter's own revert timer release the limit. Running the 15 s keepalive as a Node-RED flow instead works too, since it costs no HTTP round trip over the websocket, but put it behind a Trigger: State or a Poll State node watching Home Assistant's own availability rather than a bare 15 s inject, or a Node-RED restart loses the loop with nothing to notice it.

Clone this wiki locally