Integration of a Fronius Symo Gen 24 plus Inverter via Modbus TCP

Hi all,
I’ve spent the last couple of days to integrate my inverter into Home Assistant and wanted to share with everyone who likes to go down the same route.

My integration relies on the ModbusTCP protocol which is supported by Home Assistant. I had a look at the Fronius integration which is also available but decided to go the Modbus way, mainly because:
a) I’m planning to dynamically manage battery charging settings (not available in the Fronius integration)
b) I’m pulling values from the inverter every 1-2 seconds, something for which Modbus was designed to work and I’m not sure if the http/JSON API which is used by the Fronius integration keeps up with such load over a longer time

Some comments / pre-requesites for the implementation:

  • if you are looking for the Fronius Modbus documentation, make sure to have the most recent one for the Gen 24. I struggled a lot because I was using old register information.
  • You need to enable Modbus TCP on the inverter (connectivity settings)
  • You need to configure the main meter address in the modbus TCP setting (mine is set to 200) - you need to use this in the modbus sensor configuration
  • Consumption is calculated as: Inverter AC Power Out - Power Delivered To Grid
  • If you have some ideas how to improve the template sensors or make the implementation more efficient I’m more than happy to listen to your suggestions :grinning:

Update July 2021: the modbus configuration style has changed and since version 2021.7 the old style won’t work anymore. I have updated the post with the current working style. I have also changed the way how to collect certain registers in one pass to avoid situations in which values change to quickly and you will get wrong scaling factors.

Update April 2022: due to changes of data type handling in the modbus integration starting from release 2022.4, the configuration needs to be adapted. (see 2022.4: Groups! Groups! Groups! - Home Assistant and Remove generic data types INT, UINT, FLOAT in modbus by janiversen · Pull Request #67268 · home-assistant/core · GitHub).

Modbus Configuration and Registers

modbus:
  - type: tcp
    host: 192.168.x.x
    port: 502
    name: fronius1
    sensors:
      - name: reading_energy_main_meter
        slave: 200
        scan_interval: 2
        count: 2
        address: 40097
        data_type: float32
        unit_of_measurement: W
      - name: reading_energy_inverter_ac_output  
        unit_of_measurement: W
        slave: 1
        count: 2
        data_type: float32
        address: 40091
        scan_interval: 2
      - name: reading_energy_battery_soc_scaled
        slave: 1
        count: 1
        data_type: uint16
        address: 40361
        scan_interval: 2
      - name: reading_inverter_multiple_raw
        slave: 1
        count: 88
        address: 40265
        scan_interval: 2
        data_type: custom
        # Registers and positions in custom structure
        # 0  DCA_SF
        # 1  DCV_SF
        # 2. DCW_SF DC Energy scaling factor
        # 3. DCWH_SF
        # 4  1_ID
        # 5  1_DCA
        # 6. 1_DCV
        # 7. 1_DCW Energy string 1
        # 8  2_ID
        # 9. 2_DCA
        # 10 2_DCV
        # 11 2_DCW Energy string 2
        # 12 3_ID
        # 13 3_DCA
        # 14 3_DCV
        # 15 3_DCW Energy to battery, charging
        # 16 4_ID
        # 17 4_DCA
        # 18 4_DCV
        # 19 4_DCW Energy from battery, discharging
        structure: ">hhhh8x H16xHHH16x H16xHHH16x H16xHHH16x H16xHHH16x" 

Derived Template Sensors

      derived_energy_consumption_total:
        friendly_name: "Currently consumed electrical energy"
        value_template: "{{ (states('sensor.reading_energy_inverter_ac_output') | int  + states('sensor.derived_energy_from_grid') | int - states('sensor.derived_energy_to_grid') | int) | int }}"
        unit_of_measurement: 'W'
      derived_energy_battery_soc:
        friendly_name: "Batterie SoC"
        value_template: "{{ ((states('sensor.reading_energy_battery_soc_scaled') | int) * (10 ** -2 )) | round(0)}}"
        unit_of_measurement: '%'
      derived_energy_pv_output_string2:
        friendly_name: "PV DC Output String 2"
        value_template: "{{ (((states('sensor.reading_inverter_multiple_raw').split(',')[11] | int)) * (10 ** ((states('sensor.reading_inverter_multiple_raw').split(',')[2] | int)) )) |round(0)}}"
        unit_of_measurement: 'W'
      derived_energy_pv_output_string1:
        friendly_name: "PV DC Output String 1"
        value_template: "{{ (((states('sensor.reading_inverter_multiple_raw').split(',')[7] | int)) * (10 ** ((states('sensor.reading_inverter_multiple_raw').split(',')[2] | int)) )) |round(0)}}"
        unit_of_measurement: 'W'
      derived_energy_pv_output_total:
        friendly_name: "PV DC Output String Total"
        value_template: "{{ ((states('sensor.derived_energy_pv_output_string1') | int + states('sensor.derived_energy_pv_output_string2') | int)) }}"
        unit_of_measurement: 'W'
      derived_energy_tobattery:
        friendly_name: "Energy To Battery"
        value_template: "{{ (((states('sensor.reading_inverter_multiple_raw').split(',')[15] | int)) * (10 ** ((states('sensor.reading_inverter_multiple_raw').split(',')[2] | int)) )) |round(0)}}"
        unit_of_measurement: 'W'
      derived_energy_frombattery:
        friendly_name: "Energy From Battery"
        value_template: "{{ (((states('sensor.reading_inverter_multiple_raw').split(',')[19] | int)) * (10 ** ((states('sensor.reading_inverter_multiple_raw').split(',')[2] | int)) )) |round(0)}}"
        unit_of_measurement: 'W'
      derived_energy_direct_consumption:
        friendly_name: "PV Direct Consumption"
        value_template: "{{ ((states('sensor.reading_energy_inverter_ac_output') | int - states('sensor.derived_energy_to_grid') | int))  }}"
        unit_of_measurement: 'W'
      derived_energy_to_grid:
        friendly_name: "Energy to grid"
        value_template: "{{ ((states('sensor.derived_energy_to_grid_inverted') | int) * -1  )}}"
        unit_of_measurement: 'W'
      derived_energy_combined_battery:
        # to satisfy power wheel input requirements
        friendly_name: "Energy to grid"
        value_template: "{{-1 * (states('sensor.derived_energy_frombattery') | int - states('sensor.derived_energy_tobattery')| int )}}"
        unit_of_measurement: 'W'

Filter Sensors

  - platform: filter
    name: "derived_energy_from_grid"
    entity_id: sensor.reading_energy_main_meter
    filters:
      - filter: range
        lower_bound: 0
        precision: 0
  - platform: filter
    name: "derived_energy_to_grid_inverted"
    entity_id: sensor.reading_energy_main_meter
    filters:
      - filter: range
        upper_bound: 0
        precision: 0

Sensor Overview

Inverter Modbus TCP Configuration

11 Likes

Hi,
thanks for this post, this sounds really great! I have the same reasons using Modbus to get data from my Fronius inverter as you have.
Currently I have adapters in my old iobroker to get data from my fronius inverter via modbus. That works so far, but now I wanted to integrate it into homeassistant like you did here. Only difference is that I have a Fronius Symo Hybrid inverter which is slightly different from the one you are using. However, I struggle already on the config for the modubus platform., Below I have a screenshot of how it is configured in my iobroker. When I ready your config and see “slave: 200” then I am not sure what to put in here in my case. Is it the Meter ID or what is it? Maybe you can help me with this, would be really great.

Hi, First of all great work for making this topic, it is funny that a lot of people is experimenting with MODBUS and I can find a new topic about it every time :smiley:
I don’t mean to hijack this topic or the answer, but maybe you could check this post I made a while ago, maybe it will shed some light on your config parameters:

hi @dietlman,
I think what you are looking for is the general modbus configuration to create the reference object for the modbus call (which I seemed to have missed to include in my original description).

For me, this looks like this (to put in configuration.yaml):

modbus:
  - type: tcp
    host: 192.168.x.x
    port: 502
    name: fronius1

The device ID (in my case: 1 for the inverter and 200 for the smart meter you will place in the specific call).
Hope this helps.

Hi Oscar, yes that is what I have missed, now at least the reference object has been created, I have added the other lines to my config, I see the sensors but there are no figures showing in any of them. I think the registers are wrong, I have also tried different register numbers which I am currently using in my iobroker modbus to gather data from my Fronius, but still no figures. I will just tray to continue investigation. Also I am not sure about the two ID’s you mentions. Where do I have to define those? 1 & 200 ?

Hi,
so the next logical step should be to add the polling for the registers. My suggestion is to start simple with something that you can rely on delivering fixed values (good examples are in the nameplate section)

  - platform: modbus
    scan_interval: 2
    registers:
    - name: reading_energy_main_meter
      hub: fronius1
      unit_of_measurement: W
      slave: 200
      count: 2
      data_type: float
      register: 40097
    - name: reading_energy_pv_output_string2_scaled
      hub: fronius1
      unit_of_measurement: W
      slave: 1
      count: 1
      data_type: uint
      register: 40304 

In above example, I’m reading two registers:
register 40097(+1) from slave 200 (which is my smart meter according to the meter address configured in the Fronius inverter webinterface) and 40304(+1) from slave 1 (the inverter itself). Mind the (+1) if you want to read register 40098 you need to decrease the register count by 1, so it becomes 40097 which is the address which you need to poll.

Hi Oscar,
thanks for your answer, I have tried the settings you suggested, but still no success. I don’t get any values .
It is really strange because in my other configuration in Iobroker it works. Maybe you can find something in my settings in the screenshot below from Iobroker.

Hi @dietlman, could you provide your modbus configuration from home assistant as well?

Hi @ipetrovits, question on your implementation: as far as I understand you are polling the DC Power values and the scaling factor to calculate the real DC Power with a template sensor. Did you observe issues that the scalingfactor did not match the value you have been reading from the DC Power Register because of a timing issue?
I have been observing this, especially in the early or late times of the day when the sun is rising or going down. In those times it happened often, that the scaling factor did not match the power value and I got values that where much too high.
I’m wondering if this only comes into effect, if short polling intervals are used (mine is 2s).

Here you go:

Fronius Modbus Sensor

modbus:
name: fronius
type: tcp
host: 192.168.1.22
port: 502

Fronius Modbus

  • platform: modbus
    scan_interval: 2
    registers:
    • name: reading_energy_main_meter
      hub: fronius1
      unit_of_measurement: W
      slave: 240
      count: 2
      data_type: float
      register: 40097
    • name: reading_energy_pv_output_string2_scaled
      hub: fronius1
      unit_of_measurement: W
      slave: 1
      count: 1
      data_type: uint
      register: 40304

You need to use the same value for your name of the hub in the modbus configuration and your register readings. So, if you change fronius1 to fronius it should work.

2 Likes

Of course, that was the problem, I didn’t see that.
Thank you so much, now I will continue adding more registers and see if I can get what I want.

Hi @soly3141 and @dietlman !
Firstly as an FYI, please take a look on this change, it will break your configs in one of the next major updates:

My earlier post was updated to reflect this config, you can use that as a template. @dietlman if you’re starting to build your sensors for the first time, I’d suggest you to stick with the new syntax.

To answer, I had many problems. To be honest I don’t think my datamanager is 100% perfect, but I cannot really prove it as official Fronius systems work well, i won’t get a replacement until it’s completely fried. :frowning:

I needed to use filters, because I have often max values (i.e. 65535) for unknown reasons. I have a range filter set up for that.

I ended up in a 15s polling for INT values, and a 10s polling for SF for the same reason you mentioned - this reduced my fluctuations, but there are still a few. I’m not triggering anything based on DC values, so this is only a cosmetic error in my case.
I tried outlier and moving average filters as well with no success, I think outlier will be the best option once it gets well documented and well implemented.

Overall I ended up using INT*SF instead of FLOAT for copy-paste sensor config, and if you’re willing to get realtime DC values, there is no option for FLOAT at all (at least there isn’t in my Symo)

@ipetrovits thanks for the hint!

There is a way to read multiple registers at once using the custom data type - I’ve already implemented that on my system but didn’t manage to update here. If you read the registers in one go, you will always get the correct scaling factors.

Oh, I see where you’re going with that, very smart idea. Seems a bit labour intensive to find out, but when it’s done it should be rock solid. If you could share code for that, that would be really great =)

However I must keep my limiter filters though, because that max value fluctuation is actually showing up in the register - crosschecked it with Schneider Electric’s modbus tester…

At the moment my polling looks like this:

  - platform: modbus
    scan_interval: 2
    registers:
    - name: reading_energy_main_meter
      hub: fronius1
      unit_of_measurement: W
      slave: 200
      count: 2
      data_type: float
      register: 40097
    - name: reading_energy_inverter_ac_output
      hub: fronius1
      unit_of_measurement: W
      slave: 1
      count: 2
      data_type: float
      register: 40091
    - name: reading_energy_battery_soc_scaled
      hub: fronius1
      slave: 1
      count: 1
      data_type: uint
      register: 40361
    - name: reading_inverter_multiple_raw
      hub: fronius1
      slave: 1
      count: 88
      register: 40265
      data_type: custom
      # Registers and positions in custom structure
      # 0  DCA_SF
      # 1  DCV_SF
      # 2. DCW_SF DC Energy scaling factor
      # 3. DCWH_SF
      # 4  1_ID
      # 5  1_DCA
      # 6. 1_DCV
      # 7. 1_DCW Energy string 1
      # 8  2_ID
      # 9. 2_DCA
      # 10 2_DCV
      # 11 2_DCW Energy string 2
      # 12 3_ID
      # 13 3_DCA
      # 14 3_DCV
      # 15 3_DCW Energy to battery, charging
      # 16 4_ID
      # 17 4_DCA
      # 18 4_DCV
      # 19 4_DCW Energy from battery, discharging
      structure: ">hhhh8x H16xHHH16x H16xHHH16x H16xHHH16x H16xHHH16x"    

You will need additional template sensors to parse the custom structure:

      derived_energy_pv_output_string1:
        friendly_name: "PV DC Output String 1"
        value_template: "{{ (((states('sensor.reading_inverter_multiple_raw').split(',')[7] | int)) * (10 ** ((states('sensor.reading_inverter_multiple_raw').split(',')[2] | int)) )) |round(0)}}"
        unit_of_measurement: 'W'

I gave the new configuration style a try yesterday evening, but I was not able to get it running. Have you been able to successfully test it?

3 Likes

Hi @soly3141,

Sorry for the late answer, but finally I had the time to test out this concept, adjust it to my inverter setup, and I’m happy to announce it is working very well!

I was able to get rid of a lot of additional template and filter sensors that did the error checking and finalization of the data to eliminate those unwanted false spikes :smiley:

As a sidenote, I did not implement the idea of reading multiple sensors at once, i do not trust my Fronius Datamanager that much - as i’m easily able to send it to a braindead state with too big and too frequent modbus queries.
I only bundled sensors together which are relying closely on each other. Also I’m not using the FLOAT setup, using INT+SF instead on the whole system.

My modbus setup:

modbus:
  - name: mb_fronius
    type: tcp
    host: fronius.
    port: 502
    retry_on_empty: true
    timeout: 14
    sensors:
      - name: mb_fronius_meter_total_raw
        slave: 1
        address: 40093
        count: 3
        data_type: custom
        structure: ">Ih"
        scan_interval: 30
      - name: mb_fronius_pac_raw
        slave: 1
        address: 40083
        count: 2
        data_type: custom
        structure: "Hh"
        swap: byte
        scan_interval: 15
      - name: mb_fronius_pdc_raw
        slave: 1
        address: 40257
        count: 38
        data_type: custom
        structure: "h32xH38xH"
        swap: byte
        scan_interval: 15

Template sensors:

template:
- sensor:
    - name: solar_meter_total
      unit_of_measurement: "kWh"
      device_class: energy
      icon: "{{ 'mdi:counter' }}"
      state: >-
         {{ ((( states('sensor.mb_fronius_meter_total_raw').split(',')[0] | int) * (10 ** (( states('sensor.mb_fronius_meter_total_raw').split(',')[1] | int )) )) | float /1000 ) | round(1) }}
    - name: solar_pac
      unit_of_measurement: "W"
      device_class: power
      state: >-
         {{ (( states('sensor.mb_fronius_pac_raw').split(',')[0] | int) * (10 ** (( states('sensor.mb_fronius_pac_raw').split(',')[1] | int )) )) | round(0) }}
    - name: solar_pdc1
      unit_of_measurement: "W"
      device_class: power
      state: >-
         {{ (( states('sensor.mb_fronius_pdc_raw').split(',')[1] | int) * (10 ** (( states('sensor.mb_fronius_pdc_raw').split(',')[0] | int )) )) | round(0) }}
    - name: solar_pdc2
      unit_of_measurement: "W"
      device_class: power
      state: >-
         {{ (( states('sensor.mb_fronius_pdc_raw').split(',')[2] | int) * (10 ** (( states('sensor.mb_fronius_pdc_raw').split(',')[0] | int )) )) | round(0) }}
1 Like

Hi,
I have used modbus integration for my Fronius inverter with the configuration below. Since a couple of weeks it stopped working, could it be that this configuration is no longer supported because of the new syntax? If so, do you think you could help me with this change of my configuration?

# Fronius Modbus 
  - platform: modbus
    scan_interval: 3
    registers:
    - name: reading_energy_main_meter
      hub: fronius1
      unit_of_measurement: W
      slave: 240
      count: 2
      data_type: float
      register: 40097
    - name: reading_energy_inverter_ac_output
      hub: fronius1
      unit_of_measurement: W
      slave: 1
      count: 2
      data_type: float
      register: 40091
    - name: reading_energy_pv_output_string2_scaled
      hub: fronius1
      unit_of_measurement: W
      slave: 1
      count: 1
      data_type: uint
      register: 40304  
    - name: reading_energy_pv_output_string1_scaled
      hub: fronius1
      unit_of_measurement: W
      slave: 1
      count: 1
      data_type: uint
      register: 40284
    - name: reading_energy_inverter_dcw_scalingfactor
      hub: fronius1
      slave: 1
      count: 1
      data_type: int
      register: 40267
    - name: reading_energy_battery_soc_scaled
      hub: fronius1
      slave: 1
      count: 1
      data_type: uint
      register: 40321
    - name: reading_energy_tobattery_scaled
      hub: fronius1
      slave: 1
      count: 1
      data_type: uint
      register: 40324
    - name: reading_energy_frombattery_scaled
      hub: fronius1
      slave: 1
      count: 1
      data_type: uint
      register: 40344
  
# Modbus Template

  - platform: template
    sensors:
      derived_energy_consumption_total:
        friendly_name: "Current Energy Consumption"
        value_template: "{{ (states('sensor.reading_energy_inverter_ac_output') | int  + states('sensor.derived_energy_from_grid') | int) | int }}"
        unit_of_measurement: 'W'
      derived_energy_battery_soc:
        friendly_name: "Battery SoC"
        value_template: "{{ ((states('sensor.reading_energy_battery_soc_scaled') | int) * (10 ** -2 )) | round(0)}}"
        unit_of_measurement: '%'
      derived_energy_pv_output_string2:
        friendly_name: "PV DC Output String 2"
        value_template: "{{ ((states('sensor.reading_energy_pv_output_string2_scaled') | int) * (10 ** ((states('sensor.reading_energy_inverter_dcw_scalingfactor')| int )) )) |round(0)}}"
        unit_of_measurement: 'W'
      derived_energy_pv_output_string1:
        friendly_name: "PV DC Output String 1"
        value_template: "{{ ((states('sensor.reading_energy_pv_output_string1_scaled') | int) * (10 ** ((states('sensor.reading_energy_inverter_dcw_scalingfactor')| int )) )) |round(0)}}"
        unit_of_measurement: 'W'
      derived_energy_pv_output_total:
        friendly_name: "PV DC Output Total"
        value_template: "{{ ((states('sensor.derived_energy_pv_output_string1') | int + states('sensor.derived_energy_pv_output_string2') | int)) }}"
        unit_of_measurement: 'W'
      derived_energy_tobattery:
        friendly_name: "Energy To Battery"
        value_template: "{{ ((states('sensor.reading_energy_tobattery_scaled') | int) * (10 ** ((states('sensor.reading_energy_inverter_dcw_scalingfactor')| int )) )) |round(0)}}"
        unit_of_measurement: 'W'
      derived_energy_frombattery:
        friendly_name: "Energy From Battery"
        value_template: "{{ ((states('sensor.reading_energy_frombattery_scaled') | int) * (10 ** ((states('sensor.reading_energy_inverter_dcw_scalingfactor')| int )) )) |round(0)}}"
        unit_of_measurement: 'W'
      derived_energy_direct_consumption:
        friendly_name: "PV direct consumption"
        value_template: "{{ ((states('sensor.reading_energy_inverter_ac_output') | int - states('sensor.derived_energy_to_grid') | int))  }}"
        unit_of_measurement: 'W'
      derived_energy_to_grid:
        friendly_name: "Energy To Battery"
        value_template: "{{ ((states('sensor.derived_energy_to_grid_inverted') | int) * -1  )}}"
        unit_of_measurement: 'W'
        
        
# Fronius Filter

  - platform: filter
    name: "derived_energy_from_grid"
    entity_id: sensor.reading_energy_main_meter
    filters:
      - filter: range
        lower_bound: 0
        precision: 0
  - platform: filter
    name: "derived_energy_to_grid_inverted"
    entity_id: sensor.reading_energy_main_meter
    filters:
      - filter: range
        upper_bound: 0
        precision: 0

Hey Guys,

is it possible to integrate the Fronius Ohmpilot and Wattpilot in HA?

nice greetings from Austria

If you mean via the Fronius integration then no, currently not. Could be added at a later time but getting anything for this into core is quite time consuming. Nevertheless you could open an issue at https://github.com/nielstron/pyfronius and provide some information your system is returning from api calls.

Via Modbus it should be possible. Fronius provides spreadsheets as download on their website with register definitions.

Greetings from Austria as well :wink:

1 Like