Skip to content

n8n Workflows

hilman2 edited this page Sep 1, 2026 · 1 revision

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.

Connecting n8n to Home Assistant

  1. In Home Assistant, open your profile, Security, and create a long-lived access token. Copy it once; it is not shown again.
  2. In n8n, create a credential of type Home Assistant API: host (homeassistant.local or the IP), port 8123, SSL on or off to match, and the token.
  3. For the HTTP Request node instead of the Home Assistant node, create a Header Auth credential with name Authorization and value Bearer <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.

The calls

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.

Workflow 1: charge the battery when the price is low

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.

Workflow 2: an inverter fault into n8n

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_fault

The 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.

Workflow 3: the export limit from n8n

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_value with {"entity_id": "input_number.export_limit_pct", "value": 40}, and POST /api/services/input_boolean/turn_on with {"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.

Clone this wiki locally