Skip to content

Battery Control

Manuel edited this page Sep 2, 2026 · 3 revisions

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.

If your inverter is a Fronius, a SolarEdge, an SMA or a Kostal

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.

What the controls can and cannot do

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.

Entities used on this page

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.

From watts to percent

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.

Recipe 1: allow charging only in cheap hours

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: 0

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

Recipe 2: spread the evening charge over the night

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: 100

If your device has no block 802, replace the capacity sensor with an input helper holding the number from the battery's data sheet.

Recipe 3: hold the battery for later

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.

Recipe 4: a reserve for power cuts

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.

Reading back

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.

Before you rely on it

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.

Clone this wiki locally