Fronius and BYD battery control via Modbus

 

The Fronius inverter can be read out and controlled via the integrated Modbus interface. For example, charging or discharging can be forced, or a charging or discharging limit can be set. Why? To get even more out of the system.

Normally, Fronius' automatic self-consumption optimization works for almost all everyday situations.

One reason why I still wanted to intervene in the charging behavior was the fact that my PV modules could supply more DC power at peak times than the inverter can process on the AC side. Unfortunately, the Gen24 is limited on the DC side. Even though the maximum panel power of the Gen24 10.0 Plus is specified as 15kW, the Gen24 can only handle a little more DC power than AC: at a total of 11kW DC, despite battery charging, that's it.

Without battery charging, the production ends at approx. 10.3 KW DC.

As a prerequisite for controlling the PV system, Modbus must be activated on the inverter, see: Fronius: Data & settings via the network (Modbus).

To control the charging behavior, I have put together buttons for the following operating modes in a Home Assistant Lovelance dashboard:

  • Forced charging: "Force charging"
  • Forced discharging: "Force discharge" and
  • Limit charging power to a defined value: "Charge-limit" and "charging_power"

The "Reset charging" button resets any settings to default:

On this basis, the battery can of course also be controlled via automation, for example using the PV forecast for the day in question.

Modbus setup prerequisite

There are two different SunSpec Model Types for Modbus: "int + SF" and "float":

With the new firmware, the terms were renamed slightly: "Master" / "Slave" became "Modbus Client and "Modbus Server":

All registers mentioned in this article refer to the SunSpec Model Type "int + SF". 

I have added the following lines to configuration.yaml so that Home Assistant can connect to the Modbus interface.

modbus:
  - type: tcp
    # Put your Gen24 IP address here
    host: 192.168.1.137
    port: 502
    name: gen24
    sensors:
      - name: reading_battery_settings
        slave: 1
        count: 24
        address: 40345
        scan_interval: 5
        data_type: custom
        structure: ">10H2h4H8h"
If you have set the SunSpec Model Type to "float", you must add 10 to the registers used here.
e.g. the start address for reading the battery is 40345 for "int + SF",
with "float" 40355 should be used as the address: "address: 40355". +10 applies to all registers mentioned in this article.

The sensor "sensor.reading_battery_settings" reads all relevant Modbus registers and serves as the basis for the template sensors mentioned later:

From the sensor "reading_battery_settings", the individual values can be extracted via a value_template:

[+]

Array no.

value

Unit

Unit Description

0

WChaMax

W

Setpoint for maximum load, the default setting is MaxChaRte.

1

WChaGra

% WChaMax/sec

Setpoint for the maximum charging rate. The default value is MaxChaRte.

2

WDisChaGra

% WChaMax/sec

Setpoint for the maximum discharge rate. The default value is MaxDisChaRte.

3

StorCtl_Mod

bit 0: CHARGE

bit 1: DiSCHARGE

Activates the control mode for holding/unloading/loading the memory. Bit field value.

4

VAChaMax

VA

Setpoint for maximum charging VA.

5

MinRsvPct

% WChaMax

Setpoint for the minimum reserve for storage as a percentage of the nominal maximum storage capacity.

6

ChaState

% AhrRtg

Currently available energy as a percent of the capacity rating.

7

StorAval

AH

State of charge (ChaState) minus storage reserve (MinRsvPct) times capacity rating (AhrRtg).

8

InBatV

V

Internal battery voltage.

9

ChaSt

1: OFF

2: EMPTY

3: DISCHAGING

4: CHARGING

5: FULL

6: HOLDING

7: TESTING

Charging status of the storage device. Enumeration value.

10

OutWRte

% WChaMax

Percentage of the maximum discharge rate.

11

InWRte

% WChaMax

Percentage of the maximum charging rate.

12

InOutWRte_WinTms

Secs

Time window for changing the charging/discharging rate.

13

InOutWRte_RvrtTms

Secs

Timeout time for the charge/discharge rate.

14

InOutWRte_RmpTms

Secs

Ramp time for the transition from the current setpoint to the new setpoint.

15

ChaGriSet

0: PV (Charging from grid disabled)

1: GRID (Charging from grid enabled)

Setpoint for activating/deactivating charging from the grid

16

WChaMax_SF

  Scaling factor for maximum charging.

17

WChaDisChaGra_SF

  Scaling factor for the maximum charging and discharging rate.

18

VAChaMax_SF

  Scaling factor for the maximum charge VA.

19

MinRsvPct_SF

  Scaling factor for the minimum reserve ratio.

20

ChaState_SF

  Scaling factor for the available energy in percent.

21

StorAval_SF

  Scaling factor for the state of charge.

22

InBatV_SF

  Scaling factor for the battery voltage.

23

InOutWRte_SF

  Scaling factor for the percentage charge/discharge rate.

Source / Inspired by: github.com/bigramonk/byd_charging and forum.iobroker.net/topic/65205/modbus-fronius-gen24

change the charging behavior: Changing BYD Charging

The settings are changed via Modbus registers.

Relevant Modbus registers for controlling the charging behavior

These 4 registers are essentially relevant for controlling the battery:

According to Fronius documentation: Register Register Value Register Value Description Home Assistant Template Sensor
StorCTLMod 40348

0 ... no limitation
1 .. Load limitation
2 .. Discharge limitation
3 .. Charge/discharge power

Limitation mode

bit 0: CHARGE

bit 1: DiSCHARGE

Template sensor name: BYD.StorCTL_Mod
{% set storCTL_mod= states('sensor.reading_battery_settings').split(',')[3] | int%}
{{ "in" if storCTL_mod == 1 else
"out" if storCTL_mod == 2 else
"in and out" if storCTL_mod == 3 
else "auto"  }}
MinRsvPct 40350  

Setpoint for the maximum discharge rate. Default is MaxDisChaRte

WChaMax %

Template sensor name: BYD.MinRsvPct

{{ states('sensor.reading_battery_settings').split(',')[5]|int / 100 }}
OutWRte 40355   Discharge power in %

Template Sensor Name: BYD .OutWRte

{{ states('sensor.reading_battery_settings').split(',')[10]|int / 100 }}
InWRte 40356   Charging power in %

Template sensor name: BYD.InWRte

{{ states('sensor.reading_battery_settings').split(',')[11]|int / 100 }}

So that the registers in HA can be displayed individually, I have created a template sensor for each of the registers in the "Settings", "Helpers" menu: "BYD.StorCTL_Mod", "BYD.MinRsvPct","BYD.OutWRte", "BYD.InWRte". The content for the template sensor can be taken from the previous table.

I have created scripts for the individual actions so that several registers can be adjusted in one process:

Home Assistant scripts

Set loading settings to default

The following tabs set the loading behavior to the default values:

  Register Value Register Value Description
StorCTLMod 40348 0 No limitation
MinRsvPct 40350

500

Sets MinRsvPct to 5.0 % WChaMax
OutWRte 40355

10000

Sets outwrte to 100%
InWRte 40356

10000

Sets inwrte to 100%

A script can be used to set the registers: "Settings", "Automations & scenes", "Scripts", "ADD SCRIPT":

Content:

[+]
alias: Reset charging
sequence:
  - service: modbus.write_register
    data:
      slave: 1
      address: 40348
      value: 0
      hub: gen24
  - service: modbus.write_register
    data:
      address: 40355
      slave: 1
      value: 10000
      hub: gen24
  - service: modbus.write_register
    data:
      slave: 1
      address: 40350
      value: 500
      hub: gen24
  - service: modbus.write_register
    data:
      address: 40356
      slave: 1
      value: 10000
      hub: gen24
mode: single
icon: mdi:home-battery

Set SoC (lower charge limit) to 30%

To keep a little more reserve capacity in the battery, the default values can be adjusted slightly by setting the value for MinRsvPct to e.g. 3000 for 30% charge reserve. The value set via Modbus competes with the settings in the Fronius web interface: the higher value wins:

The following registers set the charging behavior to the default values with 30% charging reserve (SoC):

  Register Register Value Register Value Description
StorCTLMod 40348 0 No limitation
MinRsvPct 40350

3000

Sets MinRsvPct to 30.0 % WChaMax
OutWRte 40355

10000

Sets outwrte to 100%
InWRte 40356

10000

Sets inwrte to 100%

A script can be used to set the registers: "Settings", "Automations & scenes", "Scripts", "CREATE NEW SCRIPT":

Contents:

[+]
alias: Reset charging 30%
sequence:
  - service: modbus.write_register
    data:
      slave: 1
      address: 40348
      value: 0
      hub: gen24
  - service: modbus.write_register
    data:
      address: 40355
      slave: 1
      value: 10000
      hub: gen24
  - service: modbus.write_register
    data:
      slave: 1
      address: 40350
      value: 3000
      hub: gen24
  - service: modbus.write_register
    data:
      address: 40356
      slave: 1
      value: 10000
      hub: gen24
mode: single
icon: mdi:home-battery

Charging the battery from the mains

To charge the battery independently of the current power consumption or the current PV power, the following 3 registers can be set as follows. I have created an input helper in advance so that the charging power can be changed via the Lovelance dashboard:

Integrated in the dashboard, the charging power can be conveniently adjusted in the interface:

Here are the necessary tab settings for forced charging:

  Register Wert Beschreibung
StorCTLMod 40348 2 2 = Entladebegrenzung
MinRsvPct 40350 9900 Setzt MinRsvPct auf 99,0 % WChaMax
OutWRte 40355 {{ 65536 - (states('input_number.charging_power')|int(0) / states('sensor.wchamax')|int(1) * 10000)|int }}  
InWRte 40356  {{ (states('input_number.charging_power')|int(0) / states('sensor.reading_battery_settings').split(',')[0]|int(1) * 10000) | int }}  

[+]
alias: Force charging
sequence:
  - data:
      slave: 1
      address: 40348
      value: 2
      hub: gen24
    action: modbus.write_register
  - data:
      address: 40355
      slave: 1
      hub: gen24
      value: "{{ 65536 - (states('input_number.charging_power')|int(0) / states('sensor.wchamax')|int(1) * 10000)|int }}"
    action: modbus.write_register
  - data:
      address: 40356
      slave: 1
      hub: gen24
      value: "{{ (states('input_number.charging_power')|int(0) / states('sensor.reading_battery_settings').split(',')[0]|int(1) * 10000) | int }}"
    action: modbus.write_register
  - data:
      slave: 1
      address: 40350
      value: 9900
      hub: gen24
    action: modbus.write_register
mode: single
icon: mdi:battery-charging

Limit charging power

To limit the charging power to the previously created helper, the following registers can be set via another script:

  register Value Value Description
StorCTLMod 40348 1 1 = Load limitation
InWRte 40356

"{{ states('input_number.charging_power') | int(100) * 100 }}"

 
[+]
alias: Charge-limit
sequence:
  - service: modbus.write_register
    data:
      address: 40356
      slave: 1
      hub: gen24
      value: "  {{ (states('input_number.charging_power')|int(0) / states('sensor.reading_battery_settings').split(',')[0]|int(1) * 10000) | int }}"
  - service: modbus.write_register
    data:
      slave: 1
      address: 40348
      value: 1
      hub: gen24
mode: single
icon: mdi:battery-charging

Discharging (forcing) the battery with a certain power

Here are the necessary register settings for forced discharging:

  Register Value Description
StorCTLMod 40348 3 3 = Charge and final charge limitation
OutWRte 40355

  {{ (states('input_number.charging_power')|int(0) /
   states('sensor.reading_battery_settings').split(',')[0]|int(1) * 10000) | int
   }}

 
InWRte 40356

  {{ 65536 - (states('input_number.charging_power')|int(0) /
  states('sensor.reading_battery_settings').split(',')[0]|int(1) * 10000) | int
  }}

 
[+]
alias: ForceDischarge
sequence:
  - service: modbus.write_register
    data:
      address: 40356
      slave: 1
      hub: gen24
      value: >-
        {{ 65536 - (states('input_number.charging_power')|int(0) /
        states('sensor.reading_battery_settings').split(',')[0]|int(1) * 10000)
        | int }}
  - service: modbus.write_register
    data:
      address: 40355
      slave: 1
      value: |-
        {{ (states('input_number.charging_power')|int(0) /
         states('sensor.reading_battery_settings').split(',')[0]|int(1) * 10000) | int
         }}
      hub: gen24
  - service: modbus.write_register
    data:
      slave: 1
      address: 40348
      value: 3
      hub: gen24
mode: single
icon: mdi:home-battery

Examples

The following examples are taken from the Fronius documentation: 42,0410,2649.pdf and have been supplemented with the register numbers for use:

Only allow charging of the energy storage

This behavior can be achieved by limiting the maximum discharge power to 0% => results in window [-3300 W, 0 W]

  Register Value Description
StorCTLMod 40348 2 Switches discharge limit value active, bit pattern: 10
OutWRte 40355

0

Set discharge limit to 0% of WchaMax
InWRte 40356

is not relevant in this case

 

Source: 42,0410,2649.pdf

Only allow discharging of the energy storage device

This behavior can be achieved by limiting the maximum charging power to 0% => results in window [0 W, 3300 W]

  Register Value Description
StorCTLMod 40348 1 Bit 1 switches load limit value active, bit pattern: 01
OutWRte 40355

is not relevant in this case

 
InWRte 40356

0

Set charge limit to 0% of WchaMax

Allow neither charging nor discharging

This behavior can be achieved by limiting the maximum charging power to 0% and limiting the maximum discharging power to 0% => results in window [0 W, 0 W]

  Register Value Value Description
StorCTLMod 40348 3 Switches both limit values active, bit pattern: 11
OutWRte 40355

0

Set discharge limit to 0% of WchaMax
InWRte 40356

0

Set charging limit to 0% of WchaMax

Source: 42,0410,2649.pdf

Charging and discharging with a maximum of 50% of the nominal power

This behavior can be achieved by limiting the maximum charging power to 50% and limiting the maximum discharging power to 50% => results in window [-1650 W, 1650 W]

  Register Value Description
StorCTLMod 40348 3 Switches both limit values active, bit pattern: 11
OutWRte 40355

50

Set discharge limit to 50% of WchaMax
InWRte 40356

50

Set charging limit to 50% of WchaMax

Source: 42,0410,2649.pdf

Charging in the range from 50% to 75% of the nominal power

This behavior can be achieved by limiting the maximum charging power to 75% and limiting the maximum discharging power to -50% => results in window [1650 W, 2475 W]

  Register Value Description
StorCTLMod 40348 3 Switches both limit values active, bit pattern: 11
OutWRte 40355

-50

Set discharge limit to -50% of WchaMax
InWRte 40356

75

Set charging limit to 75% of WchaMax

The battery status in Fronius Solar.web changes to "Forced recharge"

Source: 42,0410,2649.pdf

Discharging with 50% of the nominal power

This behavior can be achieved by limiting the maximum charging power to -50% and limiting the maximum discharging power to 50% => results in window [-1650 W, -1650 W] 44

  Register Value Description
StorCTLMod 40348 3 Switches both limit values active, bit pattern: 11
OutWRte 40355

50

Set discharge limit to 50% of WchaMax
InWRte 40356

-50

Set charging limit to -50% of WchaMax

Source: 42,0410,2649.pdf

Charging with 50% to 100% of the nominal power

This behavior can be achieved by limiting the maximum discharge power to -50% => results in window [1650 W, 3300 W]

  Register Value Description
StorCTLMod 40348 3 Switches both limit values active, bit pattern: 11
OutWRte 40355

-50

Set discharge limit to -50% of WchaMax
InWRte 40356

is not relevant in this case

 

The battery status in Fronius Solar.web changes to "Forced recharge"

Source: 42,0410,2649.pdf

Use in practice: Home Assistant - Automation

In order to keep a residual capacity of 30% in the battery in winter and thus be able to use the PV point in an emergency and also not waste any battery capacity, I have come up with the following automation: 

Action Action Description

When the battery level reaches 30% -> Script: Set SoC (lower charge limit) to 30%

On days when the battery is not fully charged, it should only be discharged to 30%.

Battery fully charged -> Script: Set charging settings to default

If the battery is fully charged, the full capacity of the battery should also be available; accordingly, the charge limit should be set to the default value of 5% when a charge level of 100% is reached.

PV forecast at night greater than 20kW -> Script: Set charging settings to default

To ensure that any 30% charging reserve set on sunny days is not wasted, the limit could be set to the default value of 5% based on the PV forecast at night: This would allow the 30% charging reserve to be consumed before the PV system produces electricity and refills the battery.

The automation as YAML code:

[+]
alias: Batterie Management
description: ""
trigger:
  - platform: numeric_state
    entity_id:
      - sensor.byd_battery_box_premium_hv_ladezustand
    above: 99
    id: voll
  - platform: numeric_state
    entity_id:
      - sensor.byd_battery_box_premium_hv_ladezustand
    above: 30
    id: 30p
  - platform: time
    at: "03:00:00"
    id: Nacht
condition: []
action:
  - if:
      - condition: trigger
        id:
          - voll
    then:
      - service: script.reset_byd_charging
        metadata: {}
        data: {}
  - if:
      - condition: trigger
        id:
          - 30p
    then:
      - service: script.reset_charging_25
        data: {}
  - if:
      - condition: trigger
        id:
          - Nacht
      - condition: numeric_state
        entity_id: sensor.pv_remaining_today
        above: 20
    then:
      - service: script.reset_byd_charging
        metadata: {}
        data: {}
mode: single

The sensor for the PV forecast comes from the Forecast.Solar integration.

For details on the automations, see: Home Assistant automation - possibilities & basics

positive Bewertung({{pro_count}})
Rate Post:
{{percentage}} % positive
negative Bewertung({{con_count}})

THANK YOU for your review!

Questions / Comments


(sorted by rating / date) [all comments (best rated first)]

✍anonym
2024-01-26 12:10
Is there a way to limit the discharging of the Battery to the exact amount of the consumption of the house?
So when I charge my cars is it possible to click on a button to prevent charging the car with my battery? 
Btw. good job and many Thanks.
✍Bernhard
comment date 26.01.2024 12:21
Hi, i think this can in theory be done, if you have the consumption-Values from the house via smart-meter.
But if the consumption changes very fast, for example if the stove is constantly turning on and off: The automation can then only react with a delay and changing the charging would have an extra delay.

created by Bernhard
✍anonym
comment date 26.01.2024 12:25
Oh, you are fast...
I already have those variables... (sensor.solarnet_verbrauchsleistung in my case)
I tried things like:
{{ 65536 - (states('sensor.solarnet_verbrauchsleistung')|int(0) / states('sensor.wchamax')|int(1) * 1)|int }}
but it never worked for me.

created by anonym

✍Lobotschobi
2024-01-03 23:23, changed 2024-01-04 07:51
Dear Bernhard, thanks for the interesting article. I already implemented it to my HA installation, too. A point that took me a while: The above process does not allow to have the HACS repository SunSpec installed in parallel as described in your other article. When having removed it, it worked immediately (I assume it is a port or module conflict when both is running - SunSpec and Modbus).
As I am not experienced I struggled with the feature at the beginning of your article but not described in detail: "force discharge".
The battery management of the fronius inverter accessible by the web interface allows to set a "minimum discharge power" ("minimale Entladeleistung"). What I do not use for myself is sold. As I am limited in selling electricity this helps to force the battery to discharge in the morning before sun rises in summer. So I can optimize the outcome of my system.
How did you implement this feature? Is there a related value to MinRsvPct for maximum charge?
Thanks a lot and again appreciated your article!
✍Bernhard
comment date 07.01.2024 12:09
Thank you for your feedback: 
This is strange, as i have SunSpec in parallel to the described Modbus-Setup in configuration.yaml without any issues. Regarding force charge and discharge: the input_number: charging_power is used to limit force charging and forced discharging. MinRsvPct sets the state of charge in percent for force charge till the battery should be loaded. For now i implemented the Feature only via buttons und the input_number to start it manually. In future i plan to trigger this via automation and plan to write an update on this.

created by Bernhard

By continuing to browse the site, you agree to our use of cookies. More Details