EMHASS: An Energy Management for Home Assistant

I integrated into a rest_command and removed the sensors, besides the 24h one, as I use it to calculate the prediction horizon, maybe find another solution for that one:

service: rest_command.emhass_mpc_optim
data:
  alpha: 1
  beta: 0
  soc_init: "{{states('sensor.battery_batterijpercentage')|float(0)/100}}"
  soc_final: 0.12
  prediction_horizon: >-
    {{min(48,(states.sensor.electricity_consumption_price_24h_forecast.state.split(',')|count*2))}}
  pv_power_forecast: >-
    {% set ns_production_forecast3days = namespace(production_forecast3days =
    []) %}
        {% for i in range(0, 96) %}
          {% for item in state_attr('sensor.solar_forecast_estimate_watts', 'watts').items() %}
            {% if item[0] == (as_timestamp(now().strftime('%Y-%m-%d 00:00:00')) + (i * 1800)) | timestamp_custom("%Y-%m-%d %H:%M:00", True) %}
              {% set ns_production_forecast3days.production_forecast3days = ns_production_forecast3days.production_forecast3days + [item[1]] %}
            {% endif %}
          {% endfor %}
          {% if ns_production_forecast3days.production_forecast3days | length == i %}
            {% set ns_production_forecast3days.production_forecast3days = ns_production_forecast3days.production_forecast3days + [0] %}
          {% endif %}
        {% endfor %}
        
        {% set current_slot = now().hour * 2 + (1 if now().minute >= 30 else 0) %}
        {{ ns_production_forecast3days.production_forecast3days[current_slot:current_slot + 96] }}
  load_cost_forecast: >-
    {% set data = state_attr('sensor.nordpool', 'raw_today') |
    map(attribute='value') | list %}
           {% set values = namespace(all=[]) %}
           {% for i in range(now().hour+1,data | length) %}
             {% set v = ((0.011 + (0.001*(data[i] | float(0))*10)*1.06 + 0.01582*1.06 + 0.0538613 + 0.0020417 + 0.0503288) | round(4)) %}
             {% set values.all = values.all + [ v ] %}
           {% endfor %}
           {% set data = state_attr('sensor.nordpool', 'raw_tomorrow') | map(attribute='value') | list %}
           {% for i in range(data | length) %}
             {% set v = ((0.011 + (0.001*(data[i] | float(0))*10)*1.06 + 0.01582*1.06 + 0.0538613 + 0.0020417 + 0.0503288) | round(4)) %}
             {% set values.all = values.all + [ v ] %}
           {% endfor %}
           {% set values_15min = namespace(all=[]) %}
           {% for v in values.all %}
             {% set values_15min.all = values_15min.all + [v, v] %}
           {% endfor %}
           {{ values_15min.all[:48] }}
  prod_price_forecast: >-
    {% set data = state_attr('sensor.nordpool', 'raw_today') |
    map(attribute='value') | list %}
           {% set values = namespace(all=[]) %}
           {% for i in range(now().hour+1,data | length) %}
             {% set v = ((-0.00905 + (0.001*(data[i] | float(0))*10)) | round(4)) %}
             {% set values.all = values.all + [ v ] %}
           {% endfor %}
           {% set data = state_attr('sensor.nordpool', 'raw_tomorrow') | map(attribute='value') | list %}
           {% for i in range(data | length) %}
             {% set v = ((-0.00905 + (0.001*(data[i] | float(0))*10)) | round(4)) %}
             {% set values.all = values.all + [ v ] %}
           {% endfor %}
           {% set values_15min = namespace(all=[]) %}
           {% for v in values.all %}
             {% set values_15min.all = values_15min.all + [v, v] %}
           {% endfor %}
           {{ values_15min.all[:48] }}
  load_power_forecast: >-
    {{[states('sensor.power_load_no_var_loads')|int]
    +(states('input_text.fi_fo_buffer').split(',
    ')|map('multiply',1000)|map('int')|list)[1:]}}

Itā€™s 30 min optimisation. Next step is 15 min optimisation as my forecast.solar account has a 15 min resolution, I now set it to 30 min resolution in the API call.

the values_15min should be values_30min ā†’ will change that, just a variable name

Look like you are calling from a service, which is something I have wanted to experiment with as I would imagine you donā€™t need to restart HA every time you change a payload element, which would be nice.

Can you share your rest_command.emhass_mpc_option?

Hi, this is the code I got from @110hs, hope he doesnā€™t mind.

rest_command:
  emhass_publish_data:
    url: http://localhost:5000/action/publish-data
    method: POST
    content_type: 'application/json'
    payload: >-
      {"publish_prefix": {{ '"' ~ publish_prefix ~ '"' }}}

  emhass_mpc_optim:
    url: http://localhost:5000/action/naive-mpc-optim
    method: POST
    timeout: 15
    content_type: 'application/json'
    #model-type is ignored unless you use mlforecaster in the config file
    payload: >-
      { "prediction_horizon": {{ prediction_horizon }}, "pv_power_forecast": {{ pv_power_forecast }}, "soc_init": {{ soc_init }}, "soc_final": {{ soc_final }}, "load_power_forecast": {{ load_power_forecast }}, "load_cost_forecast": {{ load_cost_forecast }}, "prod_price_forecast": {{ prod_price_forecast }}, "alpha": {{ alpha }}, "beta": {{ beta }} }
2 Likes

This is a very elegant solution to something that doesnā€™t require you to restart after every changeā€¦

Nice work @110hs

1 Like

If interested, it works in combination with the automation. Iā€™m passing the parameters from there.
It looks scary but actually itā€™s not.
BEWARE: you have to create the automation in a dedicated file YAML, otherwise itā€™s not going to work (my experience at least).
When changing something you just reload the automations.

- id: 'mpc_optim_rolling_window'
  alias: "mpc_optim_rolling_window"
  description: "mpc_optim_rolling_window"
  trigger:
    - platform: time_pattern
      minutes: /30
  condition: []
  action:
    - service: rest_command.emhass_publish_data #publishing latest available data, useful if EMHASS can't run for any reason but I have data from previous executions I can use in the meantime
      data:
        publish_prefix: ''
    - service: homeassistant.update_entity
      data: {}
      target:
        entity_id:
          - sensor.forecast_solar_estimate_watts
          - sensor.forecast_solar_estimate_watt_hours_period
          - sensor.forecast_solar_estimate_watt_hours
          - sensor.forecast_solar_estimate_watt_hours_day
          - sensor.forecast_solar_estimate_message
          # The integration takes too long to automatically reload and the first hour after a change to/from a bank holiday usually shows wrong cost values
          - binary_sensor.giorno_lavorativo
          - binary_sensor.giorno_festivo
    - service: rest_command.emhass_mpc_optim
      data:
        num_def_loads: 1 #if you don't have any it's still better to set this to something and then set the power to 0
        P_deferrable_nom: [0]
        def_total_hours: [1] #if you don't have any it's still better to set this to something and then set the power to 0
        treat_def_as_semi_cont: [1]
        set_def_constant: [0]
        def_start_timestep: [0]
        def_end_timestep: [0]
        alpha: 0.25
        beta: 0.75
        model_type: 'KNN' # this parameter is ignored unless you use mlforecaster in the config file 
        soc_init: >-
          {{ (states('sensor.sonnen_usoc_filtered')|float(0)) / 100 }}
        soc_final: 0.3
        prediction_horizon: >-
          {# this is almost the same code to calculate "load_cost_forecast"/"prod_price_forecast" - I use it to be sure to get the right number of items in the list, till the end of the day #}
          {% set ns_load_cost_forecast_size = namespace(load_cost_forecast_size = 0) %}
          {% for item in states.sensor|selectattr('entity_id', 'search', 'pun_oggi_')|sort(attribute='entity_id', reverse= false )|map(attribute='entity_id')|list %} 
            {% if (now().time()) <= strptime(state_attr(item,'end'),'%H:%M:%S').time() %} {# I'm using start so the retrieved number is for the next hours, with end the current one is included as well. It works in combination with "latest" timestamp rounding of EMHASS and coputation triggered before the hour, with end you could keep "first" and computation at the hour.#}
              {#% set ns_load_cost_forecast.load_cost_forecast = ns_load_cost_forecast.load_cost_forecast + [item] %#}  {# this is for debugging and check which sensors I'm using in the loop - (un)comment as needed #}
              {% set ns_load_cost_forecast_size.load_cost_forecast_size = ns_load_cost_forecast_size.load_cost_forecast_size + 1 %} {# this is for 24h forecasts with 1h resolution #}
            {% endif %}
            {% if (now().time()) <= strptime(state_attr(item,'end')|replace(":59:", ":29:"),'%H:%M:%S').time() %} {# I'm using start so the retrieved number is for the next hours, with end the current one is included as well. It works in combination with "latest" timestamp rounding of EMHASS and coputation triggered before the hour, with end you could keep "first" and computation at the hour. If you use 'end' you also have to replace :59:, instead of :00:, with :29:#}             
              {% set ns_load_cost_forecast_size.load_cost_forecast_size = ns_load_cost_forecast_size.load_cost_forecast_size + 1 %} {# this is for 24h forecasts with 30min resolution #}
            {% endif %}
          {% endfor %}
          {% for item in states.sensor|selectattr('entity_id', 'search', 'pun_domani_')|sort(attribute='entity_id', reverse= false )|map(attribute='entity_id')|list %} 
            {% if (now().time()) >= strptime(state_attr(item,'end'),'%H:%M:%S').time() %} {# I'm using start so the retrieved number is for the next hours, with end the current one is included as well. It works in combination with "latest" timestamp rounding of EMHASS and coputation triggered before the hour, with end you could keep "first" and computation at the hour.#}
              {#% set ns_load_cost_forecast.load_cost_forecast = ns_load_cost_forecast.load_cost_forecast + [item] %#}  {# this is for debugging and check which sensors I'm using in the loop - (un)comment as needed #}
              {% set ns_load_cost_forecast_size.load_cost_forecast_size = ns_load_cost_forecast_size.load_cost_forecast_size + 1 %} {# this is for 24h forecasts with 1h resolution #}
            {% endif %}
            {% if (now().time()) >= strptime(state_attr(item,'end')|replace(":59:", ":29:"),'%H:%M:%S').time() %} {# I'm using start so the retrieved number is for the next hours, with end the current one is included as well. It works in combination with "latest" timestamp rounding of EMHASS and coputation triggered before the hour, with end you could keep "first" and computation at the hour. If you use 'end' you also have to replace :59:, instead of :00:, with :29:#}
              {% set ns_load_cost_forecast_size.load_cost_forecast_size = ns_load_cost_forecast_size.load_cost_forecast_size + 1 %} {# this is for 24h forecasts with 30min resolution #}
            {% endif %}
          {% endfor %}
          {{ ns_load_cost_forecast_size.load_cost_forecast_size }}
        pv_power_forecast: >-
          {% set ns_production_forecast2days = namespace(production_forecast2days = []) %}
          {% for i in range (0,48) %} {# 48 elements are the max covered by the free Forecast.solar - 2 days @ 1h resolution #}
            {% for item in state_attr('sensor.forecast_solar_estimate_watts','watts').items() %}
              {% if item[0] == (as_timestamp(now().strftime('%Y-%m-%d 00:00:00')) + (i*3600))| timestamp_custom("%Y-%m-%d %H:%M:00", True) %}
                {% set ns_production_forecast2days.production_forecast2days = ns_production_forecast2days.production_forecast2days + [item[1]] %}
              {% endif %}
            {% endfor %}
            {% if ns_production_forecast2days.production_forecast2days|length == i %}
              {% set ns_production_forecast2days.production_forecast2days = ns_production_forecast2days.production_forecast2days + [0] %}
            {% endif %}
          {% endfor %}
          {% set ns_production_forecast48 = namespace(production_forecast48 = []) %}
          {% for i in range(0,(ns_production_forecast2days.production_forecast2days|length)*2) %}
            {% if not(i%2) %}
              {% set ns_production_forecast48.production_forecast48 = ns_production_forecast48.production_forecast48 + [(ns_production_forecast2days.production_forecast2days[(i/2)|int])] %}
            {% else %}
              {% set ns_production_forecast48.production_forecast48 = ns_production_forecast48.production_forecast48 +
                [
                  (
                    (
                      (ns_production_forecast2days.production_forecast2days[(((i-1)/2)|round(0,'floor'))])
                      +
                      (ns_production_forecast2days.production_forecast2days[(i/2)|round(0,'ceil')])|default((ns_production_forecast2days.production_forecast2days[(((i-1)/2)|round(0,'floor'))]))
                    )/2
                  )|round
                ] 
              %}
            {%endif %}
          {% endfor %}
          {{ ns_production_forecast48.production_forecast48[(((now().hour * 60 + now().minute)/30)|round(0,'floor')+0):(((now().hour * 60 + now().minute)/30)|round(0,'floor')+0)+48] }} {# +1 if I want to start with the next time slot, +0 if I want to start with the current one #}
        load_cost_forecast: >-
          {% set ns_load_cost_forecast = namespace(load_cost_forecast=[]) %}
          {% for item in states.sensor|selectattr('entity_id', 'search', 'pun_oggi_')|sort(attribute='entity_id', reverse= false )|map(attribute='entity_id')|list %} 
            {% if (now().time()) <= strptime(state_attr(item,'end'),'%H:%M:%S').time() %} {# I'm using start so the retrieved number is for the next hours, with end the current one is included as well. It works in combination with "latest" timestamp rounding of EMHASS and coputation triggered before the hour, with end you could keep "first" and computation at the hour.#}
              {#% set ns_load_cost_forecast.load_cost_forecast = ns_load_cost_forecast.load_cost_forecast + [item] %#}  {# this is for debugging and check which sensors I'm using in the loop - (un)comment as needed #}
              {% set ns_load_cost_forecast.load_cost_forecast = ns_load_cost_forecast.load_cost_forecast +
                                                                    [( 
                                                                      (states('input_number.switch_prezzo_fisso_pun')|float)
                                                                      *
                                                                      (
                                                                        (states(item)|float)
                                                                        *
                                                                        ( 1 + ( states('input_number.lambda_fattore_correzione_fornitura_energia')|float ) )
                                                                      )
                                                                      +
                                                                      (states('input_number.corrispettivo_fornitura_energia')|float)
                                                                    )|round(6)] %} {# this is for 24h forecasts with 1h resolution #}
            {% endif %}
            {% if (now().time()) <= strptime(state_attr(item,'end')|replace(":59:", ":29:"),'%H:%M:%S').time() %} {# I'm using start so the retrieved number is for the next hours, with end the current one is included as well. It works in combination with "latest" timestamp rounding of EMHASS and coputation triggered before the hour, with end you could keep "first" and computation at the hour. If you use 'end' you also have to replace :59:, instead of :00:, with :29:#}
              {#% set ns_load_cost_forecast.load_cost_forecast = ns_load_cost_forecast.load_cost_forecast + [item] %#}  {# this is for debugging and check which sensors I'm using in the loop - (un)comment as needed #}
              {% set ns_load_cost_forecast.load_cost_forecast = ns_load_cost_forecast.load_cost_forecast +
                                                                    [( 
                                                                      (states('input_number.switch_prezzo_fisso_pun')|float)
                                                                      *
                                                                      (
                                                                        (states(item)|float)
                                                                        *
                                                                        ( 1 + ( states('input_number.lambda_fattore_correzione_fornitura_energia')|float ) )
                                                                      )
                                                                      +
                                                                      (states('input_number.corrispettivo_fornitura_energia')|float)
                                                                    )|round(6)] %} {# this is for 24h forecasts with 30min resolution #}
            {% endif %}
          {% endfor %}
          {% for item in states.sensor|selectattr('entity_id', 'search', 'pun_domani_')|sort(attribute='entity_id', reverse= false )|map(attribute='entity_id')|list %} 
            {% if (now().time()) >= strptime(state_attr(item,'end'),'%H:%M:%S').time() %} {# I'm using start so the retrieved number is for the next hours, with end the current one is included as well. It works in combination with "latest" timestamp rounding of EMHASS and coputation triggered before the hour, with end you could keep "first" and computation at the hour.#}
              {#% set ns_load_cost_forecast.load_cost_forecast = ns_load_cost_forecast.load_cost_forecast + [item] %#}  {# this is for debugging and check which sensors I'm using in the loop - (un)comment as needed #}
              {% set ns_load_cost_forecast.load_cost_forecast = ns_load_cost_forecast.load_cost_forecast +
                                                                    [( 
                                                                      (states('input_number.switch_prezzo_fisso_pun')|float)
                                                                      *
                                                                      (
                                                                        (states(item)|float)
                                                                        *
                                                                        ( 1 + ( states('input_number.lambda_fattore_correzione_fornitura_energia')|float ) )
                                                                      )
                                                                      +
                                                                      (states('input_number.corrispettivo_fornitura_energia')|float)
                                                                   )|round(6)] %} {# this is for 24h forecasts with 1h resolution #}
            {% endif %}
            {% if (now().time()) >= strptime(state_attr(item,'end')|replace(":59:", ":29:"),'%H:%M:%S').time() %} {# I'm using start so the retrieved number is for the next hours, with end the current one is included as well. It works in combination with "latest" timestamp rounding of EMHASS and coputation triggered before the hour, with end you could keep "first" and computation at the hour. If you use 'end' you also have to replace :59:, instead of :00:, with :29:#}
              {#% set ns_load_cost_forecast.load_cost_forecast = ns_load_cost_forecast.load_cost_forecast + [item] %#}  {# this is for debugging and check which sensors I'm using in the loop - (un)comment as needed #}
              {% set ns_load_cost_forecast.load_cost_forecast = ns_load_cost_forecast.load_cost_forecast +
                                                                    [( 
                                                                      (states('input_number.switch_prezzo_fisso_pun')|float)
                                                                      *
                                                                      (
                                                                        (states(item)|float)
                                                                        *
                                                                        ( 1 + ( states('input_number.lambda_fattore_correzione_fornitura_energia')|float ) )
                                                                      )
                                                                      +
                                                                      (states('input_number.corrispettivo_fornitura_energia')|float)
                                                                    )|round(6)] %} {# this is for 24h forecasts with 30min resolution #}
            {% endif %}
          {% endfor %}
          {{ ns_load_cost_forecast.load_cost_forecast }}
        prod_price_forecast: >
          {% set ns_prod_price_forecast = namespace(prod_price_forecast=[]) %}
          {% for item in states.sensor|selectattr('entity_id', 'search', 'prezzo_zonale_oggi_')|sort(attribute='entity_id', reverse= false )|map(attribute='entity_id')|list %} 
            {% if (now().time()) <= strptime(state_attr(item,'end'),'%H:%M:%S').time() %} {# I'm using start so the retrieved number is for the next hours, with end the current one is included as well. It works in combination with "latest" timestamp rounding of EMHASS and coputation triggered before the hour, with end you could keep "first" and computation at the hour.#}
              {#% set ns_prod_price_forecast.prod_price_forecast = ns_prod_price_forecast.prod_price_forecast + [item] %#}  {# this is for debugging and check which sensors I'm using in the loop - (un)comment as needed #}
              {% set ns_prod_price_forecast.prod_price_forecast = ns_prod_price_forecast.prod_price_forecast + 
                                                                    [( 
                                                                      max((states(item)|float),0)
                                                                      *
                                                                      (states('input_number.coefficiente_di_perdita_rete_rid')|float)
                                                                    )|round(6)] %} {# this is for 24h forecasts with 1h resolution #}
            {% endif %}
            {% if (now().time()) <= strptime(state_attr(item,'end')|replace(":59:", ":29:"),'%H:%M:%S').time() %} {# I'm using start so the retrieved number is for the next hours, with end the current one is included as well. It works in combination with "latest" timestamp rounding of EMHASS and coputation triggered before the hour, with end you could keep "first" and computation at the hour. If you use 'end' you also have to replace :59:, instead of :00:, with :29:#}
              {#% set ns_prod_price_forecast.prod_price_forecast = ns_prod_price_forecast.prod_price_forecast + [item] %#}  {# this is for debugging and check which sensors I'm using in the loop - (un)comment as needed #}
              {% set ns_prod_price_forecast.prod_price_forecast = ns_prod_price_forecast.prod_price_forecast + 
                                                                    [( 
                                                                      max((states(item)|float),0)
                                                                      *
                                                                      (states('input_number.coefficiente_di_perdita_rete_rid')|float)
                                                                    )|round(6)] %}  {# this is for 24h forecasts with 30min resolution #}
            {% endif %}
          {% endfor %}
          {% for item in states.sensor|selectattr('entity_id', 'search', 'prezzo_zonale_domani_')|sort(attribute='entity_id', reverse= false )|map(attribute='entity_id')|list %} 
            {% if (now().time()) >= strptime(state_attr(item,'end'),'%H:%M:%S').time() %} {# I'm using start so the retrieved number is for the next hours, with end the current one is included as well. It works in combination with "latest" timestamp rounding of EMHASS and coputation triggered before the hour, with end you could keep "first" and computation at the hour.#}
              {#% set ns_prod_price_forecast.prod_price_forecast = ns_prod_price_forecast.prod_price_forecast + [item] %#}  {# this is for debugging and check which sensors I'm using in the loop - (un)comment as needed #}
              {% set ns_prod_price_forecast.prod_price_forecast = ns_prod_price_forecast.prod_price_forecast + 
                                                                    [( 
                                                                      max((states(item)|float),0)
                                                                      *
                                                                      (states('input_number.coefficiente_di_perdita_rete_rid')|float)
                                                                    )|round(6)] %}  {# this is for 24h forecasts with 1h resolution #}
            {% endif %}
            {% if (now().time()) >= strptime(state_attr(item,'end')|replace(":59:", ":29:"),'%H:%M:%S').time() %} {# I'm using start so the retrieved number is for the next hours, with end the current one is included as well. It works in combination with "latest" timestamp rounding of EMHASS and coputation triggered before the hour, with end you could keep "first" and computation at the hour. If you use 'end' you also have to replace :59:, instead of :00:, with :29:#}
              {#% set ns_prod_price_forecast.prod_price_forecast = ns_prod_price_forecast.prod_price_forecast + [item] %#}  {# this is for debugging and check which sensors I'm using in the loop - (un)comment as needed #}
              {% set ns_prod_price_forecast.prod_price_forecast = ns_prod_price_forecast.prod_price_forecast + 
                                                                    [( 
                                                                      max((states(item)|float),0)
                                                                      *
                                                                      (states('input_number.coefficiente_di_perdita_rete_rid')|float)
                                                                    )|round(6)] %}  {# this is for 24h forecasts with 30min resolution #}
            {% endif %}
          {% endfor %}
          {{ ns_prod_price_forecast.prod_price_forecast }}
    - service: rest_command.emhass_publish_data
      data:
        publish_prefix: ''
  mode: restart
2 Likes

I replaced my EMHASS docker container with the latest one, 0.10.1
Now I get the error below in actionLogs.txt
Itā€™s clear that the new docker image is at the basis, but is there an upgrade process to follow, that would avoid this errorā€¦?

INFO - web_server - Publishing data to HASS instance
INFO - web_server - Successfully posted to sensor.p_pv_forecast = -0.55
INFO - web_server - Successfully posted to sensor.p_load_forecast = 462.75
ERROR - web_server - Exception on /action/publish-data [POST]
Traceback (most recent call last):
  File "/usr/local/lib/python3.11/dist-packages/pandas/core/indexes/base.py", line 3652, in get_loc
    return self._engine.get_loc(casted_key)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "pandas/_libs/index.pyx", line 147, in pandas._libs.index.IndexEngine.get_loc
  File "pandas/_libs/index.pyx", line 176, in pandas._libs.index.IndexEngine.get_loc
  File "pandas/_libs/hashtable_class_helper.pxi", line 7080, in pandas._libs.hashtable.PyObjectHashTable.get_item
  File "pandas/_libs/hashtable_class_helper.pxi", line 7088, in pandas._libs.hashtable.PyObjectHashTable.get_item
KeyError: 'P_PV_curtailment'

Why does the optimisation think it can simultaneously export and import power to the grid? I understood there is a constraint to prevent this from happening? Bad configuration on my part?

Iā€™m trying to simulate the Octopus Intelligent tariff using only Perfect optimisation + day-ahead. The day-ahead gives me this output, which shows that I can charge and discharge at the same time between 23:30 and 05:30

My config looks like this. The deferred load is a minimal / dummy load. Using HAOS with the add-on EMHASS 0.10.1

logging_level: DEBUG
data_path: default
costfun: profit
sensor_power_photovoltaics: sensor.weedon_total_solar_power
sensor_power_load_no_var_loads: sensor.power_load_no_var_loads
set_total_pv_sell: false
set_nocharge_from_grid: false
set_nodischarge_to_grid: false
maximum_power_from_grid: 20000
maximum_power_to_grid: 5000
number_of_deferrable_loads: 1
list_nominal_power_of_deferrable_loads:
  - nominal_power_of_deferrable_loads: 10
list_operating_hours_of_each_deferrable_load:
  - operating_hours_of_each_deferrable_load: 1
list_start_timesteps_of_each_deferrable_load:
  - start_timesteps_of_each_deferrable_load: 0
list_end_timesteps_of_each_deferrable_load:
  - end_timesteps_of_each_deferrable_load: 0
list_peak_hours_periods_start_hours:
  - peak_hours_periods_start_hours: "05:30"
list_peak_hours_periods_end_hours:
  - peak_hours_periods_end_hours: "23:30"
list_treat_deferrable_load_as_semi_cont:
  - treat_deferrable_load_as_semi_cont: false
list_set_deferrable_load_single_constant:
  - set_deferrable_load_single_constant: true
load_peak_hours_cost: 0.275
load_offpeak_hours_cost: 0.07
photovoltaic_production_sell_price: 0.15
list_pv_module_model:
  - pv_module_model: CSUN_Eurasia_Energy_Systems_Industry_and_Trade_CSUN295_60M
  - pv_module_model: CSUN_Eurasia_Energy_Systems_Industry_and_Trade_CSUN295_60M
list_pv_inverter_model:
  - pv_inverter_model: Fronius_International_GmbH__Fronius_Primo_5_0_1_208_240__240V_
  - pv_inverter_model: Fronius_International_GmbH__Fronius_Primo_5_0_1_208_240__240V_
list_surface_tilt:
  - surface_tilt: 40
  - surface_tilt: 40
list_surface_azimuth:
  - surface_azimuth: 180
  - surface_azimuth: 180
list_modules_per_string:
  - modules_per_string: 16
  - modules_per_string: 10
list_strings_per_inverter:
  - strings_per_inverter: 1
  - strings_per_inverter: 1
inverter_is_hybrid: false
set_use_battery: true
battery_nominal_energy_capacity: 9600
hass_url: empty
long_lived_token: empty
optimization_time_step: 30
historic_days_to_retrieve: 11
method_ts_round: nearest
lp_solver: COIN_CMD
lp_solver_path: /usr/bin/cbc
set_battery_dynamic: false
battery_dynamic_max: 0.9
battery_dynamic_min: -0.9
load_forecast_method: naive
battery_discharge_power_max: 4700
battery_charge_power_max: 4700
battery_discharge_efficiency: 0.9
battery_charge_efficiency: 0.95
battery_minimum_state_of_charge: 0.1
battery_maximum_state_of_charge: 0.95
battery_target_state_of_charge: 0.6
weather_forecast_method: scrapper

And an extract from the data tables showing import and export and hence profit during this period.

Very unusual, at the bottom of the table does it say the optimisation is Infeasable? If so then you should ignore all of the numbers presented and need to go into a bit of detective mode to try and locate the problem with the optimisation.

Iā€™ve been OS for a while so havenā€™t caught up with solcast changes. What is everybody doing now to replace the solcast HACS?

Looks like BJReplay is the way. Thanks BJ

correct, thatā€™s what most are using

per EMHASS: An Energy Management for Home Assistant - #2411 by stevenwhately there may be an issue with scrapper at the moment where a negative value can cause issues.

1 Like

Solcast in weather_forecast_cache (solcast) by GeoDerp Ā· Pull Request #300 Ā· davidusb-geek/emhass Ā· GitHub is working well.

2 Likes

Fixed in P_PV_curtailment, add minimum of 0 by GeoDerp Ā· Pull Request #316 Ā· davidusb-geek/emhass Ā· GitHub

1 Like

Yes infeasible. Iā€™ve never managed to see anything different. Any clues as to where / what to look for? I played with target SoC and that did not fix it.

Thank you both - looks like the minimum 0 fix has not made it to the release, Iā€™ll wait until this happens.

Hey Everyone, I just wanted to shoot an logging method for yā€™all:

alias: EMHASS Minute
description: "Run EMHASS MPC every minute, save entities under mpc_, publish all save entities if action was successful."
trigger:
  - minutes: /1
    platform: time_pattern
    enabled: true
action:
  - service: rest_command.naive_mpc_optim
    metadata: {}
    data: {}
    enabled: true
    response_variable: rest_response
  - if:
      - condition: template
        value_template: "{{ rest_response['status'] < 400  }}"
    then:
      - service: rest_command.publish_data
        data:
          prefix: all
    else:
      - service: system_log.write
        metadata: {}
        data:
          level: warning
          message: "{{ rest_response['content'] }}"
          logger: EMHASS.MINUTE

If you want each EMHASS action error to be a seperate log entry, you can do so by creating an core log entry when the http response of an action presents in an error code.

If an action responded with a error code (ie >= 400);
You can pipe the json response content (containing the python logs) with an system_log.write. Then can view this in the regular home assistant logs.
This is no where near the optimal method of parsing the error response, but it does the job.

4 Likes

Thank you @davidusb for the 1.10.2 release!
For add-on users, there will be a braking change, a new required parameter has been added to the configuration file:

list_set_deferrable_startup_penalty:
  - set_deferrable_startup_penalty: 0

Items in list = number of deferrable loads

Unfortunately I donā€™t have mutch time as of now, however I will add some code to utils.py to support list_set_deferrable_startup_penalty to be optional. Then review the Emhass-Add-on config and allow some of these deferrable parameters to be optional.

For example:

list_set_deferrable_load_single_constant
list_treat_deferrable_load_as_semi_cont
list_operating_hours_of_each_deferrable_load
list_nominal_power_of_deferrable_loads

Are parameters that already have support to be optional, but currently arenā€™t set in EMHASS-Add-on.

Automation Iā€™m using for the new Solcast weather-forecast-cache added in 0.10.2. This caches the Solcast results to a file that is used in all latter optimisations within EMHASS (cutting down the number of Solcast API calls).

When using the free home hobbyist access version of Solcast, the number calls limited to 10, reset at UTC. Set the variable api_request_limit to the number of times throughout the day you want this automation to call Solcast.
If you have a morning optimisation, where youā€™ve set ā€œweather_forecast_cacheā€: true (this calls Solcast and caches the result), then reduce the api_request_limit to the number of calls left in a day.
Note: Confirm you have Solcast set as your weather_forecast_method in the config before running this automation.

In the rest call.
ā€œweather_forecast_cache_onlyā€: true (only use the cached data do not make an API call). Will error if it canā€™t find any cached data.

This automation is based on Ozieeā€™s code at GitHub - BJReplay/ha-solcast-solar: Solcast Integration for Home Assistant
Big thanks to @GeoDerp and @davidusb for all your coding efforts.

alias: EMHASS Solcast Update
description: ""
trigger:
  - platform: template
    value_template: >-
      {% set nr = as_datetime(state_attr('sun.sun','next_rising')) | as_local %}
      {% set ns = as_datetime(state_attr('sun.sun','next_setting')) | as_local
      %} {% set api_request_limit = 10 %} {% if nr > ns %}
        {% set nr = nr - timedelta(hours = 24) %} 
      {% endif %} {% set hours_difference = (ns - nr) %} {% set interval_hours =
      hours_difference / api_request_limit %} {% set ns = namespace(match =
      false) %} {% for i in range(api_request_limit) %}
        {% set start_time = nr + (i * interval_hours) %}
        {% if ((start_time - timedelta(seconds=30)) <= now()) and (now() <= (start_time + timedelta(seconds=30))) %}
          {% set ns.match = true %}
        {% endif %}
      {% endfor %} {{ ns.match }}
condition:
  - condition: sun
    before: sunset
    after: sunrise
action:
  - delay:
      seconds: "{{ range(30, 360)|random|int }}"
  - service: automation.turn_off
    metadata: {}
    data:
      stop_actions: true
    target:
      entity_id: automation.emhass_publish_data
  - service: rest_command.weather_forecast_cache
    metadata: {}
    data: {}
    response_variable: rest_response
  - service: automation.turn_on
    target:
      entity_id:
        - automation.emhass_publish_data
    data: {}
  - if:
      - condition: template
        value_template: "{{ rest_response['status'] >= 400  }}"
    then:
      - service: system_log.write
        metadata: {}
        data:
          level: warning
          message: "{{ rest_response['content'] }}"
          logger: EMHASS.Forecast
mode: single

Rest calls

weather_forecast_cache:
  url: http://localhost:5000/action/weather-forecast-cache
  method: POST
  content_type: "application/json"
  payload: "{}"

naive_mpc_optim:
  url: http://localhost:5000/action/naive-mpc-optim
  method: POST
  timeout: 40
  content_type: "application/json"
  payload: >-
    {
      "weather_forecast_cache_only": true
    }
2 Likes

ok so am I right in assuming that upgrading from 0.8.6 to 0.10.2 requires two configuration changes?

I have to

  1. update the PV inverter model and
  2. set set_deferrable_startup_penalty: 0

Anything Iā€™m missing?