Feature to set power limit of SMA STP inverter from Home Assistant

The reset seems to be happening every hour based on the logs of SMA UI (see below)

All settings have been applied manually in the SMA UI (except the resets which have been happening automagically)

The shell commands are doing what you would do when you log in via a browsers using the inverter IP adress to manually do this:

  • log into the system
  • change some values
  • log out

If you go into your browser and do this, you can open your console. Under the ā€œnetworkā€ option (at least in Firefox) you can see these commands being set and their respective IDs.

I also have a Home Manager 2.0, so SMA is handling the zero export, with the annoying side effect of auto-resetting randomly to no power limitation at the grid connection point.

I have the below automations (I use the UI, because I like to use it to do some sanity checks on the code, adapt as required if you want to plug it into your automations.yaml).

1) Ensure SMA stays correctly configured:

alias: SMA inverter GCP config corrector
description: >-
  Periodically puts inverter back into correct mode, as SMA automatically
  disables it by itself
triggers:
  - entity_id: sensor.current_electricity_market_price
    trigger: state
  - value_template: |-
      {{ states('sensor.current_electricity_market_price') | float(0) < 0 and
         states('sensor.power_at_grid_connection_point') | float(0) < -100 }} #trigger when grid connection is lower than -100 (that means SMA switched of the GCP function by itself)
    trigger: template
conditions:
  - condition: numeric_state
    entity_id: sensor.current_electricity_market_price
    below: input_number.dynamic_price_limit
actions:
  - response_variable: auth_response
    data: {}
    action: shell_command.sma_authenticate
  - variables:
      login_successful: >- # AI wrote this, as the code posted previously didn't work for me. It works, but don't ask me how
        {% set success = false %} {% if auth_response is defined and
        auth_response.returncode == 0 and auth_response.stdout is defined %}
          {% set cleaned_stdout = auth_response.stdout | trim %}
          {% if cleaned_stdout %} {# Ensure cleaned_stdout is not empty before parsing #}
            {% set parsed_json = cleaned_stdout | from_json %}
            {% if parsed_json is mapping %}
              {% set result_val = parsed_json.get('result') %}
              {% if result_val is mapping %}
                {% set sid_val_check = result_val.get('sid') %}
                {% if sid_val_check is defined and sid_val_check | string | length > 0 %}
                  {% set success = true %}
                {% endif %}
              {% endif %}
            {% endif %}
          {% endif %}
        {% endif %} {{ success }}
      extracted_sid: >-
        {% set sid_val = "" %} {% if auth_response is defined and
        auth_response.returncode == 0 and auth_response.stdout is defined %}
          {% set cleaned_stdout_for_sid = auth_response.stdout | trim %}
          {% if cleaned_stdout_for_sid %} {# Ensure cleaned_stdout_for_sid is not empty before parsing #}
            {% set parsed_json_for_sid = cleaned_stdout_for_sid | from_json %}
            {% if parsed_json_for_sid is mapping %}
              {% set result_val_for_sid = parsed_json_for_sid.get('result') %}
              {% if result_val_for_sid is mapping %}
                {% set sid_from_get = result_val_for_sid.get('sid') %}
                {% if sid_from_get is defined %}
                  {% set sid_val = sid_from_get | string %}
                {% endif %}
              {% endif %}
            {% endif %}
          {% endif %}
        {% endif %} {{ sid_val }}
  - choose:
      - conditions:
          - condition: template
            value_template: "{{ login_successful == 'True' or login_successful == true }}"
        sequence:
          - target:
              entity_id: input_text.sma_session_id
            data:
              value: "{{ extracted_sid }}"
            action: input_text.set_value
          - data:
              sid: "{{ extracted_sid }}"
              payload: "{\"destDev\":[],\"values\":[{\"6800_0892D600\":{\"1\":[2137]}}]}"
            action: shell_command.sma_set_grid_power_limit_gcp
          - data:
              sid: "{{ extracted_sid }}"
            action: shell_command.sma_logout
mode: single


2) Control limit slider automatically depending on day-ahead price:

alias: Price based inverter control
description: >-
  Adjusts SMA power limit slider based on electricity market price, and aims for
  zero grid export/import when prices are low using the GCP power limit setting
  of pysmaplus integration
triggers:
  - entity_id: sensor.current_electricity_market_price
    id: price_change
    trigger: state
  - entity_id: input_boolean.dynamic_control_toggle
    id: toggle_change
    trigger: state
conditions: []
actions:
  - choose:
      - conditions:
          - condition: template
            value_template: "{{ not is_dynamic_control_on }}"
        sequence:
          - data:
              value: 100
            target:
              entity_id: input_number.sma_power_limit_slider
            action: input_number.set_value
      - conditions:
          - condition: template
            value_template: "{{ is_dynamic_control_on }}"
        sequence:
          - choose:
              - conditions:
                  - condition: template
                    value_template: "{{ current_price > dynamic_price_limit }}"
                sequence:
                  - data:
                      value: 100
                    target:
                      entity_id: input_number.sma_power_limit_slider
                    action: input_number.set_value
              - conditions:
                  - condition: template
                    value_template: "{{ current_price < dynamic_price_limit }}"
                sequence:
                  - data:
                      value: 0
                    target:
                      entity_id: input_number.sma_power_limit_slider
                    action: input_number.set_value
mode: single
variables:
  dynamic_price_limit: "{{ states(\"input_number.dynamic_price_limit\") | float(0) }}"
  current_price: "{{ states(\"sensor.current_electricity_market_price\") | float(0) }}"
  is_dynamic_control_on: "{{ is_state(\"input_boolean.dynamic_control_toggle\", \"on\") }}"
  current_sma_limit_slider: "{{ states(\"input_number.sma_power_limit_slider\") | float(100) }}"

3) Set the correct value whenever limit slider changes in SMA using pysmaplus integration

alias: SMA Power Limit Pysma GCP
description: Set SMA power limit using PysmaPlus integration
triggers:
  - entity_id: input_number.sma_power_limit_slider
    trigger: state
conditions:
  - condition: state
    entity_id: input_boolean.sma_power_limit_pysmaplus_automation_enabled
    state: "on"
actions:
  - data:
      entity_id: sensor.stp10_0_3av_40_947_active_power_limitation_gcp
      value: "{{ states('input_number.sma_power_limit_slider') | float(0) * 100 }}" #change the 100 depending on your inverter nominal power (I have 10kW)
    action: pysmaplus.set_value
mode: single

Shell commands in configuration.yaml (as posted before):

put your inverter IP and password where required.

shell_command:
  sma_authenticate: >
    curl -k -H 'Content-Type:application/json' -H "Accept: application/json" -X POST -d '{"right":"istl","pass":"YOURINSTALLERPASSWORD"}' https://YOURINVERTERIP/dyn/login.json
  sma_logout: >-
    curl -k -H 'Content-Type: application/json' -X POST -d '{}' 'https://YOURINVERTERIP/dyn/logout.json?sid={{sid}}'
  sma_set_grid_power_limit: >-
    curl -k -H 'Content-Type: application/json' -X POST -d '{{payload}}' 'https://YOURINVERTERIP/dyn/setParamValues.json?sid={{sid}}'
  sma_get_grid_power_limit: >-
    curl -k -H 'Content-Type: application/json' -X POST -d '{{payload}}' 'https://YOURINVERTERIP/dyn/getAllParamValues.json?sid={{sid}}'
  sma_set_grid_power_limit_gcp: >-
    curl -k -H 'Content-Type: application/json' -X POST -d '{{payload}}' 'https://YOURINVERTERIP/dyn/setParamValues.json?sid={{sid}}'
1 Like

Hi,
I am new in this environment, but since I have a SMA Sunny Tripower X 25 with Home Manager 2.0 running with Ennexos I would like to use Your impressive work. My aim is also to avoid Grid delivery when prices are low.
I tried adding the adjusted programming to automations.yaml, but I am qurious to where the sensor.current_electricity_market_price gets the price?
Is it Nord pool?
BR / Jan

Really cool to see all the automations! Going to continue/improve my setup with these shared insights soon.

@baecklund.jan I use the EnergyZero device for all energy pricing related sensors. This matches my dynamic contact of my energy provider. Adding it through HA is 10 seconds of work.

I have a SMA Tripower 8.0 and after some research, I’ve found a way to limit the power without writing to registers that are located in flash: SMA warns against cyclically writing to most registers, except a few in the sunspec modbus interface. (https://sunspec.org/wp-content/uploads/2025/01/SunSpec-Modbus-IEEE-1547-2018-Profile-Specification-and-Implementation-Guide-v1.1-1.pdf)
Access to the sunspec modbus (as opposed to the direct SMA modbus interface) is over slave address 126 (when the device address in the modbus setup is set to 3). The register to control is 40349 (40348 in Home Assistant) and contains the power as a percentage of the max power. 0 is off, 10000 is 100% The factor 1.25 for raw_power is the result of this calculation: (required_power / 8000)*10000 to map the required power on the scale from 0 to 10000 with a max power of 8000W.
With some AI assistance, this automation came out, is tested and seems 100% functional:

alias: limit PV power when injection price is negative
description: Dynamic power 8am-8pm, Max power otherwise
triggers:
  - minutes: /1
    trigger: time_pattern
actions:
  - data:
      hub: pv_oost-west
      slave: 126
      address: 40348
      value: >
        {% set p_pv = states('sensor.pv_avg_power') | float(0) %}
        {% set p_net = states('sensor.net_consumption_avg') | float(0) * 1000 %}   
        {% set ImportPrice = states('sensor.current_electricity_market_price') | float(3) %}
        {% set ExportPrice = states('sensor.injectie_current_electricity_market_price') | float(0) %}
        {% set is_daytime = today_at("08:00") <= now() <= today_at("20:00") %}

        {% if not is_daytime %}
          10000
        {% elif ImportPrice < 0 %}
          0
        {% elif ExportPrice > 0 %}
          10000
        {% else %}
          {% set raw_power = (250 + p_pv + p_net) | clamp(0, 8000) %}
          {{ (raw_power * 1.25) | int }}
        {% endif %}
    action: modbus.write_register
mode: restart

Thanks for sharing. Besides activating Modbus, are there any other settings that need to be enabled in the SMA local web interface? I’m using a SUNNY TRIPOWER 10.0, but on my side it doesn’t seem to respond to the register change.

I’ve (re)discovered @swante ā€˜s GitHub - littleyoda/ha-pysmaplus: home assistant custom integration for pysma-plus Ā· GitHub and I now successfully set limit, stop and start my SMA Tripower 4.0 using webconnect (with installer password) and firmware 4.10.10R.

It does require Home Assistant Community Store and please please make a backup before starting. Adding SMA Device Plus will make duplicate entities because the serial number is the same. On succes, consider restoring the backup and remove the SMA integration first and setting this up properly.

You might also have a ā€˜too much sessions’ error if you have too many tabs open or poll to often.

The Operator Mode value to start is 1467 (or 295) and to stop is 381. The automation is simple… if mode is 1467 and price < 0 set and production high, then set mode to 381 and vice versa.

It could be that different values are needed based on your country settings.

Ps the documentation is being updated Update documentation, Operation Mode: MPP/Start on SMA Default = 295 for Tripower Firmware version: 4.10.10.R Ā· Issue #106 Ā· littleyoda/ha-pysmaplus Ā· GitHub. If you have different operator mode values, why not share :partying_face:


This is my settings page.

Thanks for the tip.
I’ve tried that integration tto, but I cannot get it to communicate with the inverter for some unclear reason. Just writing to one modbus register allows me to deal with it using one less integration :wink:

I’m afraid it’s not working on my side due to the setting ā€œOperating mode of active power limitationā€ not being set to ā€œExternal active power setpointā€. Changing this setting requires an SMA Grid Guard code.

Although I can successfully write to and read from the configured register, the active power does not change in real time.

Update: The solution of @johanbos is working at my side. Thanks for sharing your insights!