Automate Fronius Soft Limit

Ok, if I remember this confused me too and I spent a lot of time doing trial by error.

For some reason the register is off by 1 on mine anyway!! Hoping somebody else who is a modbus guru can answer why this is!

So in the spreadsheet 40233 and 40237 are the actual entries. The modbus logic is:-

My guess is it is related to the ‘String control address offset’ and it starts at 100 therefore incremented by 1. [If others that understand modbus better could help that would be appreciated!!]

1 Like

Ahhhhh

Going to mess with this tomorrow as I have the same offset!

Thank you guys for sharing the above, took some inspiration and made a variant with multiple inverters.

This is like a Plant Controller where multiple inverters (different in size) need to be adjusted in parallel based on information coming from a single Fronius SmartMeter. Actually I think any kind of “smart” meter could be used with this approach, as it doesn’t interact directly with any of the inverters, only through HA automation.

See attached picture. The SmartMeter (in consumption path) is attached to one of the inverters with DataManager card, but there’s two new GEN24 inverters added to the system, which don’t connect to the SmartMeter.

I have no automated limiting options enabled in any of the inverters. I only define the priorty so that “Controlling via Modbus” will be first, and of course enable “Inverter control via Modbus”. SunSpec model tpye is “Int + SF”.

With these settings to limit manually the output power, one has to change the percentage output at holding register 40233, and then enables throttling by writing value 1 to register number 40237 (In Fronius’s docs the addresses are base1, but since HA uses base0, we use address numbers decreased by 1, that’s why also in the examples above we have addresses 40232 and 40236 accordingly).


As a cleaner approach, I decided to read the data from the inverters with the same modbus integration as is used to write the registers. This is independent from the Fronius or SunSpec integrations.

modbus:
- name: inverter_symo #repeat this section for other two gen24 inverters
  type: tcp
  host: 192.168.1.80
  port: 502
  retry_on_empty: true
  retries: 3
  delay: 3
  sensors:
    - name: smartmeter_w_raw  #adding this only to the inverter where the SmartMeter connects
      slave: 240 #(snapinverter symo: 240, gen24 default: 200)
      input_type: holding
      address: 40087 
      scan_interval: 5
      unique_id: smartmeter_w_raw
      count: 5
      data_type: custom
      structure: ">hhhhh"
    - name: inverter_symo_raw_lim
      slave: 1
      input_type: holding
      address: 40232
      scan_interval: 5
      unique_id: inverter_symo_raw_lim
      count: 5
      data_type: custom
      structure: ">hhhhh"
    - name: inverter_symo_raw_w
      slave: 1
      input_type: holding
      address: 40083 
      scan_interval: 5
      unique_id: inverter_symo_raw_w
      count: 2
      data_type: custom
      structure: ">hh"

Template sensors to calculate from modbus raw registers and scaling factors:

sensor:
    - name: "Smartmeter W"
      unique_id: smartmeter_W
      state_class: measurement
      device_class: power
      unit_of_measurement: W
      icon: mdi:home-lightning-bolt
      availability: "{{ has_value('sensor.smartmeter_w_raw') }}"
      state: >
        {{ 
        (states('sensor.smartmeter_w_raw').split(',')[0]) | int(default=0)
        /10**(states('sensor.smartmeter_w_raw').split(',')[4] | int(default=0)*-1)
        }}

    - name: "Inverter Symo W"
      unique_id: inverter_symo_W
      state_class: measurement
      device_class: power
      unit_of_measurement: W
      icon: mdi:solar-power
      availability: "{{ has_value('sensor.inverter_symo_raw_w') }}"
      state: >
        {{ 
        (states('sensor.inverter_symo_raw_w').split(',')[0]) | int(default=0)
        /10**(states('sensor.inverter_symo_raw_w').split(',')[1] | int(default=0)*-1)
        }}

    - name: "Inverter Symo limiter percentage"
      unique_id: inverter_symo_WMaxLimPct
      state_class: measurement
      unit_of_measurement: "%"
      icon: mdi:car-cruise-control
      availability: "{{ has_value('sensor.inverter_symo_raw_lim') }}"
      state: >
        {{ (states('sensor.inverter_symo_raw_lim').split(',')[0] | int(default=10000))/100 }}

binary_sensor:
    - name: "Inverter Symo limiter enabled" 
      unique_id: inverter_symo_limiter
      icon: mdi:car-speed-limiter
      availability: "{{ has_value('sensor.inverter_symo_raw_lim') }}"
      state: >
        {{ (states('sensor.inverter_symo_raw_lim').split(',')[4] | bool(default=false)) }}

The above is for the Symo, similar config is added for the other two inverters (without the SmartMeter sensor of course).

What I did so far is I decided to also have an input_number to configure the maximum export value. Note that this can also be negative, for some very strict conditions when you want to make 100% sure that absolutely no power is fed into the grid, but some little is always drawn:

input_number:
- max_export:
    name: Maximum export
    min: -300
    max: 10000
    step: 100
    icon: mdi:transmission-tower-import
    unit_of_measurement: 'W'

The approach here is that you can use a separate automation to simply adjust this number when you don’t want to feed in, or you want to feed just until a maximum value.

Calculation of the target percentage happens similarly as in the examples above. My smartmeter is in the consumption path.

Needed to define two sensors because the Symo is 3700W, the other two GEN24s are 6000W (they can use the same template sensor).
Note that since there are 3 inverters, we divide the sum by 3, so we distribute the limits accordingly to the inverters.

    - name: "Inverter Symo limit"
      unique_id: inverter_target_limit_sym
      state_class: measurement
      state: >
        {% set proc = ((states("sensor.smartmeter_w")|float(default=0)-states("input_number.max_export")|float(default=0))/3/3700)|multiply(-1)|multiply(10000)|int %}
        {% if proc > 10000 %}10000{% elif proc < 0 %}0{% else %}{{ proc }}{% endif -%}

    - name: "Inverter Gen24 limit"
      unique_id: inverter_target_limit_gen
      state_class: measurement
      state: >
        {% set proc = ((states("sensor.smartmeter_w")|float(default=0)-states("input_number.max_export")|float(default=0))/3/6000)|multiply(-1)|multiply(10000)|int %}
        {% if proc > 10000 %}10000{% elif proc < 0 %}0{% else %}{{ proc }}{% endif -%}

And the automations:

- alias: Inverter limiter Symo
  mode: single
  trigger:
    - platform: state
      entity_id:
        - sensor.inverter_symo_limit
  action:
    - choose:
        - conditions:  # this is for the case when export limit is to be disabled
            - condition: template
              value_template: "{{ states('sensor.inverter_symo_limit')|int == 10000}}"
          sequence:
            - service: modbus.write_register
              data:
                unit: 1
                hub: inverter_symo
                address: 40232
                value: "10000"
            - service: modbus.write_register
              data:
                unit: 1
                hub: inverter_symo
                address: 40236
                value: "0"
      default:
        - service: modbus.write_register
          data:
            unit: 1
            hub: inverter_symo
            address: 40232
            value: "{{ states.sensor.inverter_symo_limit.state }}"
        - service: modbus.write_register
          data:
            unit: 1
            hub: inverter_symo
            address: 40236
            value: "1"

- alias: Inverter limiter Gen24
  mode: single
  trigger:
    - platform: state
      entity_id:
        - sensor.inverter_gen24_limit
  action:
    - choose:
        - conditions:
            - condition: template
              value_template: "{{ states('sensor.inverter_gen24_limit')|int == 10000}}"
          sequence:
            - service: modbus.write_register
              data:
                unit: 1
                hub: inverter_gen24_a
                address: 40232
                value: "10000"
            - service: modbus.write_register
              data:
                unit: 1
                hub: inverter_gen24_b
                address: 40232
                value: "10000"
            - service: modbus.write_register
              data:
                unit: 1
                hub: inverter_gen24_a
                address: 40236
                value: "0"
            - service: modbus.write_register
              data:
                unit: 1
                hub: inverter_gen24_b
                address: 40236
                value: "0"
      default:
        - service: modbus.write_register
          data:
            unit: 1
            hub: inverter_gen24_a
            address: 40232
            value: "{{ states.sensor.inverter_gen24_limit.state }}"
        - service: modbus.write_register
          data:
            unit: 1
            hub: inverter_gen24_b
            address: 40232
            value: "{{ states.sensor.inverter_gen24_limit.state }}"
        - service: modbus.write_register
          data:
            unit: 1
            hub: inverter_gen24_a
            address: 40236
            value: "1"
        - service: modbus.write_register
          data:
            unit: 1
            hub: inverter_gen24_b
            address: 40236
            value: "1"

Result (with max_export set to 1700):

The production of the 3 inverters dynamically follows the house demand, always feeding in not more than 1700W.

Recently just setup a new standalone instance of HA on a new Raspberry Pi as my Qnap server was having some issues.

After much messing around I believe I finally have it working (Tesla app confirms solar production dropping to zero).

Many thanks to everyone that has contributed here. I was able to piece together different bits of info to make something that woks for me.

My setup is as follows;

Fronius Primo 5.0 & Tesla PW2

The target addresses are 40236 - WMaxLim_Ena & 40232 - WMaxLimPct (addresses in documentation are 40237 & 40233 but as someone else posted above there is an offset of 1 number)

I added both of these into the modbus sensors in config.yaml so I could see if my automations were working.

Under Normal Conditions

And with Solar Curtailment Activated

modbus:
  - type: tcp
    name: "primo1_fronius"
    host: 192.168.1.14
    port: 502
    retry_on_empty: true
    retries: 3
    sensors:
      - name: Inverter Primo WMaxLimPct
        slave: 1
        input_type: holding
        address: 40232
        scale: 0.01
        scan_interval: 15
        state_class: measurement
        unique_id: inverter_primo_WMaxLimPct
        unit_of_measurement: "%"
      - name: Inverter Primo WMaxLimEna
        slave: 1
        input_type: holding
        address: 40236
        scale: 1
        scan_interval: 15
        state_class: measurement
        unique_id: inverter_primo_WMaxLimEna

I’ve just got a toggle button on my homepage for testing currently but I will link that to further automations based on battery charge level & feed in tarrif price.

And these are the Activation & De-Activation automations

alias: Solar Curtailment Activation
description: ""
trigger:
  - platform: state
    entity_id:
      - input_boolean.solar_curtailment
    from: "off"
    to: "on"
    for:
      hours: 0
      minutes: 0
      seconds: 5
condition: []
action:
  - service: modbus.write_register
    data:
      unit: 1
      hub: primo1_fronius
      address: 40232
      value: "0"
  - service: modbus.write_register
    data:
      unit: 1
      hub: primo1_fronius
      address: 40236
      value: "1"
alias: Solar Curtailment Deactivated
description: ""
trigger:
  - platform: state
    entity_id:
      - input_boolean.solar_curtailment
    from: "on"
    to: "off"
    for:
      hours: 0
      minutes: 0
      seconds: 5
condition: []
action:
  - service: modbus.write_register
    data:
      unit: 1
      hub: primo1_fronius
      address: 40236
      value: "0"
  - service: modbus.write_register
    data:
      unit: 1
      hub: primo1_fronius
      address: 40232
      value: "10000"

I’m sure there are improvements to be made but so happy to just have this basic form working for now and hopefully this can help anyone else with the same setup.

So I’ve been using the curtailment successfully since I last posted and have also managed to make another automation that adjusts the solar output based on the load being drawn in the house.

The issue I’m having and I’m not sure if anyone else has played with this is the time it takes to ramp up and down to the given setpoint. It appears to adjust approximately 100w every 5 seconds.

There were a couple of elements in the modbus address map that I thought might be the correct ones to speed it up but when activated they don’t seem to make any difference.

I tried setting both of them to 10 seconds and the value does change on my dashboard but it doesn’t seem to make any difference on how long it takes to adjust the output.

image

2 x automations below for setting those 2 elements

alias: Fronius Export Limit Activation Ramp Time
description: ""
trigger: []
condition: []
action:
  - service: modbus.write_register
    data:
      address: 40235
      slave: 1
      hub: primo1_fronius
      value: 10
mode: single

alias: Fronius Export Limit Set Activation Time
description: ""
trigger: []
condition: []
action:
  - service: modbus.write_register
    data:
      address: 40233
      slave: 1
      hub: primo1_fronius
      value: 10
mode: single

if anyone has any ideas I’d be glad to hear them so I can get the curtailment moving setpoints quicker.

First of all, thank you for all the contributions on the thread :slight_smile:

In my case, my preference is to normally provide as much energy as possible to the network, with exceptions where the electricity price is extremely low or negative, where my preference would be to set it to export a maximum of 150W.

I did not find the possibility of configuring this as @aldifesu showed on his screenshot, so I decided to try different possibilities. (https://community-assets.home-assistant.io/original/4X/d/0/c/d0c7856ce08ce3a387d2984ff9f4fe72d823689b.png)

As a possible solution to the problem, I have written a small tool that serves as an authentication proxy for the Fronius controller, making it easier to perform HTTP calls to the Fronius from Home Assistant as if it was a web browser: GitHub - sergioperez/fronius-auth-proxy: Authentication proxy to avoid using the HTTP digest implementation used on the Fronius inverter controllers

This makes it possible to configure settings on the Fronius that are not available via the standard integration or the Modbus interface, as the exact power that the Fronius feeds to the network:

imagen

There are many points to improve in the project, but I hope it’s useful for more people than just myself :slight_smile:

2 Likes

Hi all. I’ve been reading this thread great progress. I need a similar setup. The Challenge is, that I have several sensors. That should be taken into account.

  1. Electricity Price ( Current price for grid import )
  2. Power Consumption Current ( range from -300 kWh to 600 kWh)
  3. 5x 100 kWh Fronius Tauro Inverters.

The Challenge here, is when the Price is between 0.10 and 0.19 (example), I need to limit the inverters, so the Power output equals the Power consumption, so the inverters produce enough power to keep me a float, but not to export.

If the price drops below 0, the inverters should stop production. I have established Solar API, and modbus access. I’ve reached out to Fronius, to get a more specific explanation regarding modbus, but haven’t got an answer yet. But maybe some of you guys, have an idea how to build this. I would prefer, to do the calculations myself, as I don’t have Fronius smartmeter, but another similar meter, that I poll from modbus.

My idea is to do it like this.

every 5 seconds, I read the meter, and the inverters production. So if the price triggers. then it should compare these, and send a limiter command to the inverters. I’ve been looking at the same excel modbus register map, and the 40233 reads 0 and 40237 reads 1000. So in my head the logic is flawed.

any help is highly appreciated.

As an alternative. I can read the hourly Import/export values, and limit the inverters if the Export exceeds the import. As I’m charged by the summariced Hourly values.

I have achieved the same by monitoring the feed in price and when it drops to negative then reducing the inverter to cover only what is required, no export.

It is far easier to get the inverter to do this heavy lifting and either limit the export to matching the house demand (if feed-in price is negative) or stop the inverted (when price is negative).

Can I see you programming for the Fronius modbus, also of course the code, that compares Inverter production with Consumption :slight_smile:

Have a look at this above post. Automate Fronius Soft Limit - #39 by tux43

Oh, so you use the build-in function of the inverter. I was hoping to not use that, as my power usage, can be between 6kWh an Hour or 500 kWh an hour, so I need to limit it by percentage, this is why I wanted to use the modbus limter register instead.

I’ll see if I can find out how to do that then :slight_smile:

I’m not sure I follow why you would want to do this based on demand. I assumed the problem you are trying to solve is not to export when Amber charge c/kWh for feed in?

Anyway I actually started with HA controlling my inverter until @juande showed me the more elegant solution.

It is here Automate Fronius Soft Limit - #7 by tux43

If the wholesale price is negative enough (to offset various fixed and network charges) then you can be paid to consume energy.

Yes, understand this however it is driven from Amber price rather than power usage as per @MichaelBach comment.

I must have misinterpreted. I thought your question was about why would you want to curtail PV production, not just exports.

I might have stated my wish incorrectly. But my task is almost complete, I’ve managed to control and limit my inverters. Where they take into account our Consumption, and production. Then I calculate, what the current limit on the inverters should be, to match the current demand, so we produce exactly the correct amount of kWh an hour, to match our demands. This is only necessary when the Electrical price is in a specific sweet spot. This is very specific for our needs, But we can produce as much as 800 kWh an hour, so the cost if they are not limted would be rather expensive.

The code for upturning this is as follows :

- sensor:
    - name: "40007 Grid consumption calculator"
      unique_id: 40007_grid_consumption_calculator
      state: >-
        {% set gridUsage40007 = states('sensor.40007_power_all_total')|int %}
        {% set solarProduction = states('sensor.solar_inverters_ac_total')|int %}
        {% set oneActualConsumption = gridUsage40007 + (solarProduction/1000) %}
        {% set solar500scaleFactor = 500 %}
        {% set sliderOneValue = 10000-(((oneActualConsumption * 100)/solar500scaleFactor)*100)|round  %}
        {% if sliderOneValue > 0 %}
          {{ sliderOneValue }}
        {% else %}
          100
        {% endif %}
- sensor:
    - name: "40008 Grid consumption calculator"
      unique_id: 40008_grid_consumption_calculator
      state: >-
        {% set gridUsage40008 = states('sensor.40008_power_all_total')|int %}
        {% set solarProduction300 = states('sensor.solar_inverters_ac_total_300_kw')|int %}
        {% set twoActualConsumption = gridUsage40008 + (solarProduction300/1000) %}
        {% set solar300scaleFactor = 300 %}
        {% set sliderTwoValue = 10000-(((twoActualConsumption * 100)/solar300scaleFactor)*100)|round  %}
        {% if sliderTwoValue > 0 %}
          {{ sliderTwoValue }}
        {% else %}
          100
        {% endif %}
- id: '1693377673167'
  alias: Solar Array 500 kWh - Set production limit
  description: ''
  trigger:
  - platform: state
    entity_id:
    - input_boolean.solar_array_limit_export
    from: 'off'
    to: 'on'
  condition: []
  action:
  - service: modbus.write_register
    data:
      hub: Inverter_1
      address: 40232
      value: '{{ states(''sensor.40007_grid_consumption_calculator'') }}'
      slave: 1
  - service: modbus.write_register
    data:
      hub: Inverter_2
      address: 40232
      value: '{{ states(''sensor.40007_grid_consumption_calculator'') }}'
      slave: 1
  - service: modbus.write_register
    data:
      hub: Inverter_3
      address: 40232
      value: '{{ states(''sensor.40007_grid_consumption_calculator'') }}'
      slave: 1
  - service: modbus.write_register
    data:
      hub: Inverter_4
      address: 40232
      value: '{{ states(''sensor.40007_grid_consumption_calculator'') }}'
      slave: 1
  - service: modbus.write_register
    data:
      hub: Inverter_5
      address: 40232
      value: '{{ states(''sensor.40007_grid_consumption_calculator'') }}'
      slave: 1
  - service: modbus.write_register
    data:
      hub: Inverter_1
      address: 40234
      value: 600
      slave: 1
  - service: modbus.write_register
    data:
      hub: Inverter_2
      address: 40234
      value: 600
      slave: 1
  - service: modbus.write_register
    data:
      hub: Inverter_3
      address: 40234
      value: 600
      slave: 1
  - service: modbus.write_register
    data:
      hub: Inverter_4
      address: 40234
      value: 600
      slave: 1
  - service: modbus.write_register
    data:
      hub: Inverter_5
      address: 40234
      value: 600
      slave: 1
  - service: modbus.write_register
    data:
      hub: Inverter_1
      address: 40236
      value: 1
      slave: 1
  - service: modbus.write_register
    data:
      hub: Inverter_2
      address: 40236
      value: 1
      slave: 1
  - service: modbus.write_register
    data:
      hub: Inverter_3
      address: 40236
      value: 1
      slave: 1
  - service: modbus.write_register
    data:
      hub: Inverter_4
      address: 40236
      value: 1
      slave: 1
  - service: modbus.write_register
    data:
      hub: Inverter_5
      address: 40236
      value: 1
      slave: 1
  mode: single
- id: '1693378390950'
  alias: Solar Array 500 kWh - Stop Export Limit
  description: ''
  trigger:
  - platform: state
    entity_id:
    - input_boolean.solar_array_limit_export
    from: 'on'
    to: 'off'
  condition: []
  action:
  - service: modbus.write_register
    data:
      hub: Inverter_1
      address: 40232
      value: 10000
      slave: 1
  - service: modbus.write_register
    data:
      hub: Inverter_2
      address: 40232
      value: 10000
      slave: 1
  - service: modbus.write_register
    data:
      hub: Inverter_3
      address: 40232
      value: 10000
      slave: 1
  - service: modbus.write_register
    data:
      hub: Inverter_4
      address: 40232
      value: 10000
      slave: 1
  - service: modbus.write_register
    data:
      hub: Inverter_5
      address: 40232
      value: 10000
      slave: 1
  - service: modbus.write_register
    data:
      hub: Inverter_1
      address: 40236
      value: 0
      slave: 1
  - service: modbus.write_register
    data:
      hub: Inverter_2
      address: 40236
      value: 0
      slave: 1
  - service: modbus.write_register
    data:
      hub: Inverter_3
      address: 40236
      value: 0
      slave: 1
  - service: modbus.write_register
    data:
      hub: Inverter_4
      address: 40236
      value: 0
      slave: 1
  - service: modbus.write_register
    data:
      hub: Inverter_5
      address: 40236
      value: 0
      slave: 1
  mode: single

Modbus registers explained

40232 = Rate limit from 10000 (100%) to 100 (1%) the lower number the lower output
40234 = Timeout in seconds for the output limit to expire (if HA fails to set this for any reason)
40236 = Enable / disable ratelimit 1=Enable 0=Disable.

For any change to register 40232, you need to set 40236 to 0 then 1 to accept the new value.

You have a 800kW system? Wow!

It’s a 1250 kWp but “only” 800 kWh Inverters. 8x Fronius Tauro 3-P 100 kWh. So yes

I like your ‘slider’ solution & now understand what you are trying to achieve. I’ll give my 6kW inverter a pat when I get home and make sure that it understands I still think it is great :stuck_out_tongue: