-
Notifications
You must be signed in to change notification settings - Fork 1
n8n Workflows
Driving the same controls from n8n, and getting inverter events out of Home Assistant into n8n. The integration has no n8n-specific interface; everything here goes through Home Assistant's REST API, which n8n speaks natively.
If the control logic can live in a Home Assistant automation, keep it there. The keepalive pattern the export limit needs runs every 15 s, and an n8n workflow at that rate produces a lot of executions. n8n earns its place when the decision needs data Home Assistant does not have, or when it is already where your price forecast, your calendar or your messaging lives.
- In Home Assistant, open your profile, Security, and create a long-lived access token. Copy it once; it is not shown again.
- In n8n, create a credential of type Home Assistant API: host
(
homeassistant.localor the IP), port8123, SSL on or off to match, and the token. - For the HTTP Request node instead of the Home Assistant node,
create a Header Auth credential with name
Authorizationand valueBearer <your token>.
Both node types do the same calls. The Home Assistant node is quicker to fill in; the HTTP Request node is what the importable workflow below uses because it has no hidden defaults.
| Purpose | Method and path | Body |
|---|---|---|
| Read a sensor | GET /api/states/sensor.inverter_ac_power |
none; the value is in state
|
| Set the export limit, percent | POST /api/services/sunspec2/set_export_limit |
{"config_entry_id": "...", "percent": 70, "enable": true} |
| Set any Number entity | POST /api/services/number/set_value |
{"entity_id": "number.inverter_battery_discharge_rate", "value": 40} |
| Flip a Switch |
POST /api/services/switch/turn_on or turn_off
|
{"entity_id": "switch.inverter_export_limit_enabled"} |
| Set the battery mode | POST /api/services/select/select_option |
{"entity_id": "select.inverter_battery_control_mode", "option": "both"} |
In the Home Assistant node the same thing is Resource: Service, Operation: Call, with the domain and service split out and the body keys entered as Service Attributes.
The config entry id for the export limit action: build the action once in Home Assistant's visual automation editor, pick the inverter, switch to Edit in YAML and copy the id. It is also the last part of the URL behind Download diagnostics.
Entity ids are placeholders here; Entity names and IDs explains how to find yours.
Every hour, read the current price from a Home Assistant sensor, and below a threshold open the battery for charging at full rate with discharge blocked; above it, put both rates back to 100.
The block 124 controls this drives are unconfirmed on real hardware,
and they set ceilings rather than forcing a charge; Battery
control has the
details. Set Battery rate revert time in Home Assistant to
something above one hour, or the rates lapse between runs.
Import this JSON through Workflows -> Import from file, then select your Header Auth credential on each HTTP Request node and replace the host, the entity ids and the threshold. Written against n8n 1.x; if your version refuses it, the table after it is enough to rebuild the five nodes by hand.
{
"name": "Battery cheap-hour charging",
"nodes": [
{
"parameters": {
"rule": { "interval": [ { "field": "hours", "hoursInterval": 1 } ] }
},
"name": "Every hour",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [ 0, 0 ]
},
{
"parameters": {
"url": "http://homeassistant.local:8123/api/states/sensor.electricity_price",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"options": {}
},
"name": "Read price",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [ 220, 0 ]
},
{
"parameters": {
"conditions": {
"options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict" },
"conditions": [
{
"id": "price",
"leftValue": "={{ Number($json.state) }}",
"rightValue": 0.15,
"operator": { "type": "number", "operation": "lt" }
}
],
"combinator": "and"
},
"options": {}
},
"name": "Cheap?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [ 440, 0 ]
},
{
"parameters": {
"method": "POST",
"url": "http://homeassistant.local:8123/api/services/number/set_value",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\"entity_id\": [\"number.inverter_battery_charge_rate\"], \"value\": 100}",
"options": {}
},
"name": "Charge rate 100",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [ 680, -120 ]
},
{
"parameters": {
"method": "POST",
"url": "http://homeassistant.local:8123/api/services/number/set_value",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\"entity_id\": [\"number.inverter_battery_discharge_rate\"], \"value\": 0}",
"options": {}
},
"name": "Discharge rate 0",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [ 900, -120 ]
},
{
"parameters": {
"method": "POST",
"url": "http://homeassistant.local:8123/api/services/number/set_value",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\"entity_id\": [\"number.inverter_battery_charge_rate\", \"number.inverter_battery_discharge_rate\"], \"value\": 100}",
"options": {}
},
"name": "Both rates 100",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [ 680, 120 ]
}
],
"connections": {
"Every hour": { "main": [ [ { "node": "Read price", "type": "main", "index": 0 } ] ] },
"Read price": { "main": [ [ { "node": "Cheap?", "type": "main", "index": 0 } ] ] },
"Cheap?": {
"main": [
[ { "node": "Charge rate 100", "type": "main", "index": 0 } ],
[ { "node": "Both rates 100", "type": "main", "index": 0 } ]
]
},
"Charge rate 100": { "main": [ [ { "node": "Discharge rate 0", "type": "main", "index": 0 } ] ] }
},
"settings": { "saveSuccessExecutions": "none" }
}The same five nodes, by hand:
| Node | Type | Settings |
|---|---|---|
| Every hour | Schedule Trigger | Hours, every 1 |
| Read price | HTTP Request | GET http://homeassistant.local:8123/api/states/sensor.electricity_price, Header Auth credential |
| Cheap? | If | Number, {{ Number($json.state) }} is less than 0.15
|
| Charge rate 100, Discharge rate 0 | HTTP Request | POST .../api/services/number/set_value, JSON body with entity_id and value
|
| Both rates 100 | HTTP Request | Same, both entity ids in the list, value 100 |
Home Assistant accepts a list of entity ids in entity_id, which is
why the last node needs one call rather than two.
The other direction. Home Assistant calls an n8n webhook when the inverter reports a fault, and n8n does whatever it does with alerts: Telegram, Signal, a ticket, a call.
In n8n, add a Webhook node, method POST, path sunspec-fault,
and connect your messaging node behind it. Activate the workflow and
copy the production URL.
In Home Assistant's configuration.yaml:
rest_command:
n8n_inverter_fault:
url: "https://n8n.example.com/webhook/sunspec-fault"
method: POST
content_type: "application/json"
payload: >
{"state": "{{ states('sensor.inverter_operating_state') }}",
"flags": {{ (state_attr('sensor.inverter_events', 'active_flags') or []) | to_json }},
"power_w": {{ states('sensor.inverter_ac_power') | float(0) }}}And the automation:
alias: Inverter fault to n8n
triggers:
- trigger: state
entity_id: sensor.inverter_operating_state
to: FAULT
actions:
- action: rest_command.n8n_inverter_faultThe webhook node receives state, flags and power_w under
$json.body. Add a header with a shared secret in both places if the
webhook is reachable from the internet.
If the number that decides the limit is computed in n8n, for example from a forecast API and tomorrow's prices, let n8n write the decision into a Home Assistant helper and leave the keepalive to Home Assistant:
- n8n:
POST /api/services/input_number/set_valuewith{"entity_id": "input_number.export_limit_pct", "value": 40}, andPOST /api/services/input_boolean/turn_onwith{"entity_id": "input_boolean.export_limit_active"} - Home Assistant: the keepalive automation from Zero export and export limiting
That way an n8n outage changes nothing until the next decision, and a Home Assistant outage lets the inverter's revert timer release the limit. Running the 15 s keepalive in n8n itself works, but every run is an execution: switch off Save successful executions in the workflow settings first, or the database fills at 5760 rows a day.
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