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"
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 |
Limitation mode bit 0: CHARGE bit 1: DiSCHARGE |
Template sensor name: BYD.StorCTL_Mod
|
MinRsvPct | 40350 |
Setpoint for the maximum discharge rate. Default is MaxDisChaRte WChaMax % |
Template sensor name: BYD.MinRsvPct
|
|
OutWRte | 40355 | Discharge power in % |
Template Sensor Name: BYD .OutWRte
|
|
InWRte | 40356 | Charging power in % |
Template sensor name: BYD.InWRte
|
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) / |
|
InWRte | 40356 |
{{ 65536 - (states('input_number.charging_power')|int(0) / |
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
{{percentage}} % positive
THANK YOU for your review!
Questions / Comments
(sorted by rating / date) [all comments (best rated first)]
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.
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
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
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!
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