-
Notifications
You must be signed in to change notification settings - Fork 1
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.
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.
- 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. - In Home Assistant, open your profile, Security, and create a long-lived access token. Copy it once; it is not shown again.
- 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.
| 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.
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.
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.
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, entityinput_number.export_limit_pct, data{"value": 40}; a second Action node,input_boolean.turn_on, entityinput_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.
Recipes
- Energy dashboard and statistics
- Zero export and export limiting
- Battery control
- Alerts and notifications
- Dashboard cards
- n8n workflows
- Node-RED workflows
- Sharing the inverter
Reference
- SunSpec models explained
- Entity names and IDs
- Diagnostics file reference
- Vendor notes
- Hardware reports
- FAQ
Under the hood
In the repository