-
-
Notifications
You must be signed in to change notification settings - Fork 6
ev surplus charging automation
This guide explains how to wire your physical EV charger to follow HSEM's
surplus-charging recommendations. HSEM calculates how much power the EV should
draw (ev_charger_calculated_power), but the charger itself needs a different
signal — it needs to know the grid surplus so it can dynamically adjust.
HSEM recalculates ev_charger_calculated_power every 5 minutes (the planner's
update interval). It tells you the target AC power the EV should draw right now —
e.g. 2900 means "charge at 2.9 kW."
There are two ways to control a dynamic charger with this value:
Option A — HSEM directly controls the charge rate:
Feed ev_charger_calculated_power into the charger. The charger draws exactly
that amount. Simple, but if a cloud passes between HSEM updates the charger
keeps drawing the old target — importing from the grid for up to 5 minutes.
Option B — Charger chases real surplus, HSEM sets a ceiling: Feed your real grid power sensor into the charger's surplus input so it responds to clouds instantly. Use HSEM's value as a maximum current limit so the charger never exceeds what the MILP planned. Requires two automations (or one script) per charger.
Both options are documented below. Choose Option B if your goal is "never import from grid for the EV."
For Option A (HSEM direct control), the charger needs to know the remaining surplus after accounting for what it's already drawing:
pGrid = (ev_charger_calculated_power × -1) + current_charge_power
| Variable | Source | Example |
|---|---|---|
ev_charger_calculated_power |
state_attr('sensor.hsem_workingmode_sensor', 'hourly_recommendation').ev_charger_calculated_power |
2900 W |
current_charge_power |
Your charger's power sensor (W) |
2000 W |
pGrid |
Sent to charger |
-900 W |
Worked example:
- HSEM says charge at 2900 W →
target = 2900 - Charger is currently drawing 2000 W →
charge = 2000 pGrid = -2900 + 2000 = -900
The charger sees 900 W of remaining surplus and increases its draw. When it
reaches 2900 W, pGrid = -2900 + 2900 = 0, and the charger holds steady —
grid is balanced.
When HSEM says 0 W (no surplus), pGrid = 0 + 2000 = +2000, signaling
the charger that it's importing from the grid and should back off.
go-e chargers accept pGrid, pAkku, and pPv via MQTT on the
go-eCharger/<serial>/ids/set topic. The ids value decays after 10–15
seconds, so it must be refreshed continuously — the charger's PID loop then
adjusts the actual charge power to drive pGrid toward zero.
Why
pGridinstead of setting amps? Theidstopic uses RAM-only registers designed for frequent writes. Setting the charge current directly via config keys likeamp/amawrites to persistent storage (flash) on every update. See the go-eCharger MQTT docs for details.
| Entity | Purpose |
|---|---|
sensor.hsem_workingmode_sensor |
HSEM hourly recommendation with ev_charger_calculated_power
|
binary_sensor.go_echarger_<serial>_car |
on when car is plugged in |
sensor.go_echarger_<serial>_nrg_12 |
Current charging power in watts |
Single automation — HSEM's ev_charger_calculated_power directly drives the
charge rate. The go-e charger sees remaining surplus and ramps up/down to
match the target.
alias: go-e Surplus Charging from HSEM
description: >-
Sends grid surplus to go-e charger so it dynamically follows HSEM's
EV charging recommendation.
triggers:
- trigger: time_pattern
seconds: /3
conditions:
- condition: state
entity_id: binary_sensor.go_echarger_222819_car
state: "on"
actions:
- data:
qos: "0"
topic: go-eCharger/222819/ids/set
retain: false
payload: |-
{% set rec = state_attr('sensor.hsem_workingmode_sensor', 'hourly_recommendation') %}
{% set target = rec.ev_charger_calculated_power | int(0) if rec is not none else 0 %}
{% set charge = states('sensor.go_echarger_222819_nrg_12') | int(0) %}
{
"pGrid": "{{ (target * -1 + charge) }}",
"pAkku": "0",
"pPv": "0"
}
action: mqtt.publish
mode: singleTwo automations working together:
-
Surplus signal (every 3s) — feeds real grid power into
pGridso the charger's PID loop chases actual surplus second-by-second. -
HSEM ceiling (on state change) — sets
number.go_echarger_<serial>_pgt(grid target in watts) to HSEM'sev_charger_calculated_power. The charger will never exceed this, but will draw less when real surplus is low.
Automation B1 — Real-time surplus signal:
alias: go-e Surplus Signal from Grid Power
description: >-
Feeds real grid power into go-e charger's pGrid so it dynamically
follows actual solar surplus second-by-second.
triggers:
- trigger: time_pattern
seconds: /3
conditions:
- condition: state
entity_id: binary_sensor.go_echarger_222819_car
state: "on"
actions:
- data:
qos: "0"
topic: go-eCharger/222819/ids/set
retain: false
payload: |-
{
"pGrid": "{{ states('sensor.power_meter_active_power') | int(0) }}",
"pAkku": "0",
"pPv": "0"
}
action: mqtt.publish
mode: singleAutomation B2 — HSEM ceiling:
alias: go-e Surplus Charging from HSEM
description: >-
Simple automation to update values needed for using solar surplus with go-e
Chargers
triggers:
- trigger: time_pattern
seconds: /3
conditions:
- condition: state
entity_id: binary_sensor.go_echarger_222819_car
state: "on"
actions:
- data:
qos: "0"
topic: go-eCharger/222819/ids/set
retain: false
payload: |-
{
"pGrid": "{{ (state_attr('sensor.hsem_workingmode_sensor', 'hourly_recommendation').ev_charger_calculated_power | int(0) * -1 + states('sensor.go_echarger_222819_nrg_12') | int(0)) }}",
"pAkku": "0",
"pPv": "0"
}
action: mqtt.publish
mode: singleNotes:
- Replace
222819with your charger's serial number.- Replace
sensor.power_meter_active_powerwith your actual grid power sensor (negative = export, positive = import).pgtexpects a negative value for surplus (export), so HSEM's positive watt target is negated (target * -1).- The
pAkkuandpPvfields are set to0because HSEM manages the home battery separately — the charger only needs the grid surplus signal.
Easee chargers are controlled via the Easee EV Charger
integration. The integration exposes a easee.set_charger_dynamic_limit service
that sets the maximum charging current in amps.
Unlike go-e, Easee does not use a grid-surplus signal. Instead, you convert HSEM's power target directly to amps and set it as the charger's dynamic limit.
- Convert HSEM's
ev_charger_calculated_power(watts) to amps - Call
easee.set_charger_dynamic_limitwith the amp value - Use
time_to_liveso the limit expires if HA stops updating
Amps formula:
amps = ev_charger_calculated_power / (voltage × phases)
For a typical European 3-phase setup: amps = power_w / (230 × 3) ≈ power_w / 690.
For single-phase: amps = power_w / 230.
| Entity | Purpose |
|---|---|
sensor.hsem_workingmode_sensor |
HSEM hourly recommendation with ev_charger_calculated_power
|
binary_sensor.easee_<id>_cable_connected |
on when car is plugged in |
sensor.easee_<id>_power |
Current charging power in watts (for diagnostics) |
Note: You also need an Easee Equalizer (HAN/Nevion) installed for dynamic current limiting to work. Without it, the charger ignores dynamic current commands.
alias: Easee Surplus Charging from HSEM
description: >-
Sets Easee charger dynamic current limit based on HSEM's
EV charging recommendation.
triggers:
- trigger: state
entity_id:
- sensor.hsem_workingmode_sensor
conditions:
- condition: state
entity_id: binary_sensor.easee_12345_cable_connected
state: "on"
actions:
- action: easee.set_charger_dynamic_limit
data:
charger_id: "12345"
current: >-
{% set rec = state_attr('sensor.hsem_workingmode_sensor', 'hourly_recommendation') %}
{% set target = rec.ev_charger_calculated_power | int(0) if rec is not none else 0 %}
{% set amps = (target / 690) | round(0, 'floor') | int %}
{{ ([0, amps, 32] | sort)[1] }}
time_to_live: 30
mode: singleNotes:
- Replace
12345with your charger ID (find it in the Easee integration device list).- Replace
690with230if you have a single-phase installation.- The
([0, amps, 32] | sort)[1]pattern clamps between 0 and 32 A (Easee's range is 0–40 A; adjust the upper bound to match your circuit breaker).time_to_live: 30means the limit expires after 30 seconds if HA stops sending updates — a safety net that prevents the charger from getting stuck at a high limit.
If you have multiple chargers on one circuit, use easee.set_circuit_dynamic_limit
instead:
actions:
- action: easee.set_circuit_dynamic_limit
data:
circuit_id: 12345
current_p1: >-
{% set amps = (states('...') | int(0) / 690) | round(0, 'floor') | int %}
{{ ([0, amps, 32] | sort)[1] }}
current_p2: "{{ ([0, amps, 32] | sort)[1] }}"
current_p3: "{{ ([0, amps, 32] | sort)[1] }}"
time_to_live: 30Zaptec chargers are controlled via the Zaptec
integration. The integration exposes a number.<name>_available_current entity
on the Installation device that sets the maximum charging current in amps.
Like Easee, Zaptec does not use a grid-surplus signal. You convert HSEM's power target to amps and set it as the available current.
Important: Zaptec recommends not changing the available current more often than every 15 minutes. The automation below triggers on HSEM state changes only.
| Entity | Purpose |
|---|---|
sensor.hsem_workingmode_sensor |
HSEM hourly recommendation with ev_charger_calculated_power
|
binary_sensor.zaptec_<name>_connected |
on when car is plugged in |
number.<installation_name>_available_current |
Sets max current for the installation |
Before you start: Disable Zaptec Sense (APM/Automatic Power Management) and stand-alone mode in the Zaptec Portal. The charger must be in cloud-managed mode for the current limit to take effect.
alias: Zaptec Surplus Charging from HSEM
description: >-
Sets Zaptec installation available current based on HSEM's
EV charging recommendation.
triggers:
- trigger: state
entity_id:
- sensor.hsem_workingmode_sensor
conditions:
- condition: state
entity_id: binary_sensor.zaptec_my_charger_connected
state: "on"
actions:
- action: number.set_value
target:
entity_id: number.my_installation_available_current
data:
value: >-
{% set rec = state_attr('sensor.hsem_workingmode_sensor', 'hourly_recommendation') %}
{% set target = rec.ev_charger_calculated_power | int(0) if rec is not none else 0 %}
{% set amps = (target / 690) | round(0, 'floor') | int %}
{{ ([6, amps, 32] | sort)[1] }}
mode: singleNotes:
- Replace
my_chargerandmy_installationwith your actual Zaptec entity names.- Replace
690with230if you have a single-phase installation.([6, amps, 32] | sort)[1]clamps between 6 A (minimum most EVs accept) and 32 A (typical installation limit). Adjust to match your circuit breaker.- If you have multiple chargers on one installation, changing the installation-level current affects all of them.
If you need per-charger control (e.g. different cars on different schedules),
use the zaptec.limit_current service instead:
actions:
- action: zaptec.limit_current
data:
device_id: abc123def456
available_current_phase1: >-
{% set amps = (states('...') | int(0) / 690) | round(0, 'floor') | int %}
{{ ([6, amps, 32] | sort)[1] }}
available_current_phase2: "{{ ([6, amps, 32] | sort)[1] }}"
available_current_phase3: "{{ ([6, amps, 32] | sort)[1] }}"This sets the current on a specific charger rather than the whole installation. Find the
device_idin the Zaptec charger device page.
- Home — User-facing overview: features, FAQ, working modes, battery schedules, excess export, consumption sensors
- Battery Charging Economics — How to calculate the minimum charging price for a battery schedule
- Architecture Overview — System context, layered architecture, module map, planning pipeline
- Planner Specification — Normative — all planner invariants, rules, and constraints
- Planner Technical Guide — How the planner works with worked examples
- Cost Function Math — Complete mathematical formulation of the 8-term cost function
- Energy Accounting — Physical energy flow model, SoC simulation, efficiency math
- Candidate Generation — How candidates are generated, assumptions, partial-SoC
- MILP Optimization — Full LP formulation, variable layout, constraints, and solver pipeline
- Consumption Prediction — Weighted-average model, IQR outlier detection, spike suppression
- Safety Modes — Degraded mode, read-only gate, write-verify applier, runtime resolver
- Price Scaling — EDS price scaling, eds_share conversion factor
- Services Reference — All 4 HSEM services with examples
- Sensors Reference — Complete entity reference: all sensor, select, switch, number, and time entities
- Dashboard Setup — Step-by-step ApexCharts dashboard with full YAML, layout reference, and troubleshooting
- Config Flow Reference — Every config/options flow step and field
- EV Charge Plan Setup — EV planned load configuration guide
- EV Surplus Charging Automation — Wire your physical EV charger (go-e, Easee, Zaptec) to follow HSEM surplus recommendations
- EV Optimal Charging Template — Legacy Home Assistant template sensor for cost-optimal EV charging
- Forecast Accuracy Tracking — Forecast vs actual tracking system
- Huawei Entities — Canonical HA entity ID reference
- Troubleshooting Guide — Diagnose and fix common problems: missing data, wrong prices, write failures, battery behaviour
- Quality Checks — Static quality tools and CI configuration