-
Notifications
You must be signed in to change notification settings - Fork 1
Battery Control
Automations for a battery behind a SunSpec inverter, using the block
124 controls. These entities need no flag: they appear automatically
for any inverter that reports a battery (WChaMax above zero). Only
the export limit and grid connection controls, blocks 123 and 704,
still sit behind "Enable experimental export controls (BETA)" in
the options.
That does not mean confirmed. The maintainer has no battery hardware, and as of September 2026 nobody has reported back on the generic block 124 entities below either way. Everything on this page is written against the SunSpec specification. If you run it, the result belongs in #17 or #32, whichever way it went.
Battery and export control has the entity table with the register behind each one. Read its section on the revert timer; the battery rates have one too.
Both get a native alternative to some of the recipes below, built from community register dumps and vendor manuals rather than confirmed on project hardware either, and both ask for reports of their own.
A Fronius GEN24, Verto, Tauro or Symo Hybrid gets an 8-way Battery mode select (automatic, charge from grid, block discharging and more) with its setpoints in watts, and a Scheduled discharge that does what Recipe 2 below builds by hand: give the battery's surplus back over a window you set, no automation required. See docs/fronius.md for GEN24/Verto/Tauro, or docs/fronius-symo.md for the Symo Hybrid. The generic entities below still exist on a Fronius but ship disabled, since writing the same registers in percent from two places would let them disagree.
A SolarEdge battery is steered through its own Storage control mode instead of block 124 at all, and it is switched on per inverter by SolarEdge support rather than always present. See docs/solaredge.md.
An SMA Sunny Boy Storage, Sunny Island or Tripower Smart Energy is steered through an external active power setpoint in SMA's own register set, not block 124: positive watts discharge, negative watts charge. The inverter has to be put into the external setpoint operating mode first, and the setpoint lapses on the inverter's own timeout, so it comes with a switch that writes it again. See docs/sma.md.
A Kostal PLENTICORE or PIKO IQ has no block 124 at all. It gets a minimum and maximum state of charge and a charge and discharge power limit, which bound what the inverter does rather than drive it, and a DC power setpoint behind the export beta that drives it. On a G3 from SW 03.05 there is a second pair of limits with the inverter's own fallback watchdog behind them: a switch rewrites them every 20 seconds, and if Home Assistant stops, the inverter takes the battery back on its own. That makes it the one control on this page that cannot be left behind by accident. See docs/kostal.md.
Everyone else gets exactly the generic entities below.
Block 124 sets ceilings. The charge rate is the most the battery
may charge at, the discharge rate the most it may discharge at, both
as a percentage of WChaMax. A rate of 40 % lets the inverter charge
at up to 40 %; it does not make it charge. Whether it charges at all,
and from where, is decided by the inverter's own energy management
and its settings, for example whether charging from the grid is
permitted.
So the recipes below shape what the inverter is allowed to do. To make it do something it would not do on its own, look for a setting in the inverter's own interface first.
| Placeholder | Block 124 point | What it is |
|---|---|---|
select.inverter_battery_control_mode |
StorCtl_Mod |
off, charge, discharge, both: which of the two rates are in force |
number.inverter_battery_charge_rate |
InWRte |
Charge ceiling, % of WChaMax
|
number.inverter_battery_discharge_rate |
OutWRte |
Discharge ceiling, % of WChaMax
|
number.inverter_battery_max_charge_power |
WChaMax |
The watts those percentages refer to |
number.inverter_battery_rate_revert_time |
InOutWRte_RvrtTms |
Seconds until the rates lapse |
number.inverter_battery_minimum_reserve |
MinRsvPct |
Charge to hold back, %. Starts disabled |
switch.inverter_battery_grid_charging |
ChaGriSet |
Whether the battery may charge from the grid at all, not just the roof. Only on devices that implement the point |
sensor.battery_state_of_charge |
802 SoC, or 124 ChaState
|
State of charge, % |
sensor.battery_nameplate_energy_capacity |
802 WHRtg
|
Capacity in Wh, where the device publishes it |
The select's options are the lowercase words in the table when written from YAML; the UI shows them translated.
Every recipe that thinks in watts has to divide by WChaMax. A script
keeps that in one place:
script:
set_battery_discharge_ceiling:
alias: Set battery discharge ceiling
fields:
watts:
name: Watts
selector:
number:
min: 0
max: 50000
unit_of_measurement: W
sequence:
- variables:
wchamax: "{{ states('number.inverter_battery_max_charge_power') | float(0) }}"
pct: >
{{ 0 if wchamax <= 0
else ([100, watts | float(0) / wchamax * 100] | min) | round(0) }}
- action: number.set_value
target:
entity_id: number.inverter_battery_discharge_rate
data:
value: "{{ pct }}"Read WChaMax from its entity rather than typing the number in; some
inverters change it with the battery configuration.
Suppose your tariff is cheap between 02:00 and 05:00 and you want the battery filled from the grid then, and otherwise left to the sun.
Set Battery rate revert time to 900 once. The automation re-writes
the rates every 5 minutes while the window is open, so the ceiling
lapses by itself a quarter of an hour after Home Assistant stops.
alias: Battery cheap-hour charging
mode: single
triggers:
- trigger: time_pattern
minutes: "/5"
conditions:
- condition: time
after: "02:00:00"
before: "05:00:00"
- condition: numeric_state
entity_id: sensor.battery_state_of_charge
below: 95
actions:
- action: select.select_option
target:
entity_id: select.inverter_battery_control_mode
data:
option: both
- action: number.set_value
target:
entity_id: number.inverter_battery_charge_rate
data:
value: 100
- action: number.set_value
target:
entity_id: number.inverter_battery_discharge_rate
data:
value: 0Discharge at 0 keeps the battery from feeding the house while you are paying to fill it. At 05:00 the rates lapse and the inverter is back in charge. If your inverter does not lapse, add an automation at 05:00 that sets the discharge rate back to 100.
Whether the inverter actually pulls from the grid during the window is
up to its own settings; many hybrids have a "charge from grid"
permission that has to be on. Where the device has it, that is the
switch.inverter_battery_grid_charging entity above (ChaGriSet);
turn it on alongside the rates and off again with them.
On a Fronius GEN24, Verto, Tauro or Symo Hybrid this is now built in. Scheduled discharge does exactly this from three entities, no automation required: see docs/fronius.md. What follows is for every other inverter, or for anyone who would rather keep the logic in Home Assistant.
The scenario from #57: at 20:00 the battery is full, and instead of emptying it by midnight you want it to last until the sun comes back. Take what is above the reserve, divide it by the hours until morning, and make that the discharge ceiling.
alias: Battery overnight discharge ceiling
triggers:
- trigger: time
at: "20:00:00"
conditions:
- condition: numeric_state
entity_id: sensor.battery_state_of_charge
above: 30
variables:
soc: "{{ states('sensor.battery_state_of_charge') | float(0) }}"
reserve_pct: 10
capacity_wh: "{{ states('sensor.battery_nameplate_energy_capacity') | float(0) }}"
hours: 10
available_wh: "{{ (soc - reserve_pct) / 100 * capacity_wh }}"
watts: "{{ (available_wh / hours) | round(0) }}"
actions:
- action: select.select_option
target:
entity_id: select.inverter_battery_control_mode
data:
option: both
- action: script.set_battery_discharge_ceiling
data:
watts: "{{ watts }}"For this one the revert timer works against you: a ceiling that lapses
after 15 minutes is no overnight plan. Either set Battery rate revert time to 0 for the night and put it back in the morning, or turn the
automation into a keepalive with a time_pattern trigger and a time
condition, the way recipe 1 does it. And restore the ceiling in the
morning:
alias: Battery ceiling off in the morning
triggers:
- trigger: sun
event: sunrise
offset: "01:00:00"
actions:
- action: number.set_value
target:
entity_id: number.inverter_battery_discharge_rate
data:
value: 100If your device has no block 802, replace the capacity sensor with an input helper holding the number from the battery's data sheet.
You know a peak is coming, say the heat pump at 17:00, and you would rather the battery not spend itself on the afternoon dishwasher. Discharge at 0 while a helper is on, back to 100 when it goes off:
alias: Battery hold
triggers:
- trigger: state
entity_id: input_boolean.battery_hold
actions:
- action: select.select_option
target:
entity_id: select.inverter_battery_control_mode
data:
option: both
- action: number.set_value
target:
entity_id: number.inverter_battery_discharge_rate
data:
value: "{{ 0 if trigger.to_state.state == 'on' else 100 }}"Add the keepalive if the hold is meant to last longer than the revert time.
Battery minimum reserve is a setting rather than something to
automate. Enable the entity in its settings, set it to the percentage
you want kept back for backup, and leave it. It ships disabled for
exactly that reason.
The rates read back what was last written, like the export limit
does. What the battery is actually doing is in the block 802 power
and state of charge sensors, and in ChaState of block 124, the
available energy as a percentage of capacity. The InOutWRte_WinTms
and InOutWRte_RmpTms sensors show the timers the device applies.
Same test as for the export limit: with a ceiling active, stop Home Assistant for longer than the revert time and check that the inverter returns to its own plan. A battery stuck at discharge 0 through a winter evening is the failure mode to rule out.
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