EMHASS: An Energy Management for Home Assistant

Hello RikBast
Yes, it’s the same here. (Huawei inverter)

But I run with @rcruikshank solution with limited power

{%- set third_row = state_attr('sensor.soc_batt_forecast', 'battery_scheduled_soc')[0] -%}
{%- set soc_value = third_row['soc_batt_forecast']|float(0) -%}
{%- set raw_power = ((states('sensor.battery_state_of_capacity')|float(0) - soc_value) / 100 * 10000 / 0.5 / 0.9)|round(0) -%}
{%- set limited_power = min(max(raw_power, -4500), 3500) -%}
{{ limited_power }}

some will know that I have been plagued by infeasible optimizer results. I am getting a very repeatable pattern now. For 2-4 hours in the middle of the day I get infeasible. The impact of this is that scheduling of pool pump and EV charging doesn’t get updated for those 2-4hours.

I think the trigger for infeasible to start is the battery getting to 100% SOC. In my MPC call I am passing in SOC_init as the current SOC and so I am passing in 1.0. Here are my battery related settings from my config:

set_battery_dynamic: false
battery_dynamic_max: 0.9
battery_dynamic_min: -0.9
weight_battery_discharge: 0
weight_battery_charge: 0
load_forecast_method: mlforecaster
battery_discharge_power_max: 15000
battery_charge_power_max: 15000
battery_discharge_efficiency: 0.95
battery_charge_efficiency: 0.95
battery_minimum_state_of_charge: 0.1
battery_maximum_state_of_charge: 0.9
battery_target_state_of_charge: 0.1

Here is an example call to MPC:

2024-09-19 13:30:00,120 - web_server - INFO - Passed runtime parameters: {'load_cost_forecast': [0.24915, 0.46178, 0.46178, 0.46178, 0.46178, 0.46178, 0.46178, 0.46178, 0.46178, 0.46178, 0.46178, 0.46178, 0.46178, 0.24915, 0.24915, 0.24915, 0.24915, 0.19239, 0.19239, 0.19239, 0.19239, 0.19239, 0.19239, 0.19239, 0.19239, 0.19239, 0.19239, 0.19239, 0.19239, 0.19239, 0.19239, 0.19239, 0.19239, 0.19239, 0.19239, 0.24915, 0.24915, 0.24915, 0.24915, 0.24915, 0.24915, 0.24915, 0.24915, 0.24915, 0.24915, 0.24915, 0.24915, 0.24915], 'weather_forecast_method': 'list', 'pv_power_forecast': [12564, 11102, 9934, 7598, 5984, 3492, 1132, 83, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 930, 3544, 6322, 8937, 11402, 13522, 15357, 16794, 13600, 13600, 13600, 13600, 13940, 13940, 13600, 13268, 12853, 11393, 10173, 8093, 6509, 3889, 1382, 101, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 'prediction_horizon': 48, 'soc_init': 1.0, 'def_total_hours': [0, 10]}
2024-09-19 13:30:00,121 - web_server - INFO -  >> Setting input data dict
2024-09-19 13:30:00,121 - web_server - INFO - Setting up needed data
2024-09-19 13:30:00,124 - web_server - INFO - Retrieve hass get data method initiated...
2024-09-19 13:30:00,124 - web_server - INFO - Successfully posted to sensor.p_pv_forecast = 12564.0
2024-09-19 13:30:00,141 - web_server - INFO - Successfully posted to sensor.p_load_forecast = 1418.46
2024-09-19 13:30:00,157 - web_server - INFO - Successfully posted to sensor.p_deferrable0 = 0.0
2024-09-19 13:30:00,165 - web_server - INFO - Successfully posted to sensor.p_deferrable1 = 6145.54
2024-09-19 13:30:00,172 - web_server - INFO - Successfully posted to sensor.p_batt_forecast = 0.0
2024-09-19 13:30:00,179 - web_server - INFO - Successfully posted to sensor.soc_batt_forecast = 10.0
2024-09-19 13:30:00,185 - web_server - INFO - Successfully posted to sensor.p_grid_forecast = -5000.0
2024-09-19 13:30:00,191 - web_server - INFO - Successfully posted to sensor.total_cost_fun_value = 6.0
2024-09-19 13:30:00,197 - web_server - INFO - Successfully posted to sensor.optim_status = Infeasible
2024-09-19 13:30:00,293 - web_server - INFO - Successfully posted to sensor.unit_load_cost = 0.2492
2024-09-19 13:30:00,436 - web_server - INFO - Successfully posted to sensor.unit_prod_price = 0.05
2024-09-19 13:30:00,939 - web_server - INFO - Retrieving weather forecast data using method = list
2024-09-19 13:30:00,940 - web_server - INFO - Retrieving data from hass for load forecast using method = mlforecaster
2024-09-19 13:30:00,940 - web_server - INFO - Retrieve hass get data method initiated...
2024-09-19 13:30:03,212 - web_server - DEBUG - Number of ML predict forcast data generated (lags_opt): 48
2024-09-19 13:30:03,213 - web_server - DEBUG - Number of forcast dates obtained: 48
2024-09-19 13:30:03,215 - web_server - INFO -  >> Performing naive MPC optimization...
2024-09-19 13:30:03,215 - web_server - INFO - Performing naive MPC optimization
2024-09-19 13:30:03,220 - web_server - INFO - Perform an iteration of a naive MPC controller
2024-09-19 13:30:03,295 - web_server - DEBUG - Deferrable load 0: Proposed optimization window: 0 --> 0
2024-09-19 13:30:03,296 - web_server - DEBUG - Deferrable load 0: Validated optimization window: 0 --> 0
2024-09-19 13:30:03,297 - web_server - DEBUG - Deferrable load 1: Proposed optimization window: 0 --> 0
2024-09-19 13:30:03,297 - web_server - DEBUG - Deferrable load 1: Validated optimization window: 0 --> 0
2024-09-19 13:30:03,354 - web_server - INFO - Status: Infeasible

Maybe change battery maximum state of charge to 1

battery_maximum_state_of_charge: 1

3 Likes

I find if the battery goes above the maximum or below the minimum it goes infeasible.

1 Like

Still sama issue. Tried alpha 0, beta 1 as well, but same.

Same here. If battery is being charged to 100% the max charge should be set to 1. Same with min.

Thanks Mark and Robert.

One other oddity if I may. When I pass in 0 as a value for one of my loads in def_total_hours it sometimes still gets scheduled in the forecast. I have a non-zero value in my config by I assume that the runtime value would always override the value from config.

That seems to be a bug in the latest release. def_total_hours of 0 used to disable a deferrable load but now I have to reduce p_deferrable_nom to 0 to disable a load.

I also have the same ERROR and a second:

• ERROR - web_server - The retrieved JSON is empty, A sensor:sensor.power_load_no_var_loads may have 0 days of history, passed sensor may not be correct, or days to retrieve is set too heigh


• ERROR - web_server - Unable to get sensor power photovoltaics, or sensor power load no var loads. Check HA sensors and their daily data

EMHASS was never running. I installed the Addon and configured the to sensors. The sensors have logdata for some days. I can’t understand the problem.

EMHASS retrieves 2 days of “consumption less deferrable loads” data from the HA recorder database. This can be troublesome if the recorder database is unwieldy.

I always had the same issue, especially after restarting HA, until I implemented an ‘include’ statement to restrict recording to a minimal list of sensors and deleted my recorder database to start afresh.

Details on how to do this here and here. I use the include statement in configuration.yaml to only record the entities I’m interested in. This reduces the size of the recorder database and somehow stabilizes and improves retrieval of data by EMHASS.

Alternatively, you can construct a variable to replace the data that EMHASS retrieves and pass that to the MPC calculation as the load_power_forecast dictionary key. This avoids using the recorder database altogether.

What this does is calculate an average power consumption every 30 minutes and adds this to a string variable that can be passed to EMHASS directly.

The way Mark does it is documented here (or just search for fifo in this topic) and the way I do it using Node-Red is documented in my configuration doco here or import this flow after creating an input_text helper called fifo_buffer.

Node-Red flow to create fifo_buffer:

[{"id":"e6833878191a3648","type":"api-current-state","z":"65840aa926d9c567","name":"","server":"afc27684.cf6ed8","version":3,"outputs":1,"halt_if":"","halt_if_type":"str","halt_if_compare":"is","entity_id":"input_text.fifo_buffer","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"}],"for":"0","forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":220,"y":3260,"wires":[["c2b7aef7a74ada86"]]},{"id":"c2b7aef7a74ada86","type":"function","z":"65840aa926d9c567","name":"FIFO the input text","func":"// Get the values from the previous nodes\nlet fiFoBuffer = msg.payload.substring(msg.payload.indexOf(',') + 1);\nlet avgHousePower = flow.get(\"flowAvgHousePower\");\nlet comerString = \",\"\n\n// Combine values, and comer and create a string\nlet newValue = fiFoBuffer.concat(comerString.concat(avgHousePower));\n\n// Set the payload to the new value\nmsg.payload = newValue;\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":550,"y":3240,"wires":[["3e2215cdd1b5a4ac","8d497005f65ec71b"]]},{"id":"3e2215cdd1b5a4ac","type":"api-call-service","z":"65840aa926d9c567","name":"","server":"afc27684.cf6ed8","version":7,"debugenabled":true,"action":"input_text.set_value","floorId":[],"areaId":[],"deviceId":[],"entityId":["input_text.fifo_buffer"],"labelId":[],"data":"{\"value\":\"{{payload}}\"}","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","blockInputOverrides":false,"domain":"input_text","service":"set_value","x":870,"y":3240,"wires":[[]]},{"id":"8d497005f65ec71b","type":"debug","z":"65840aa926d9c567","name":"debug 6","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":840,"y":3320,"wires":[]},{"id":"ab9db8fade2bc42c","type":"function","z":"65840aa926d9c567","name":"half hour average","func":"var sumHousePower = parseInt(flow.get(\"flowSumHousePower\")) || 0;\nvar iterationCount = flow.get(\"flowIterationCount\") || 0;\nvar avgHousePower = flow.get(\"flowAvgHousePower\") || 0;\nvar msg1 = { payload: 0 };\n\n\niterationCount = iterationCount + 1;\nsumHousePower += parseInt(msg.payload);\n\nflow.set(\"flowSumHousePower\", sumHousePower);\nflow.set(\"flowIterationCount\", iterationCount);\n\n// Determine exit path based on iteration count\nif (iterationCount < 30) {\n    var msg2 = { payload: [iterationCount,sumHousePower] };\n    return [null, msg2];\n    } \nelse {\n    // Calculate average power consumption\n    var averagePower = sumHousePower / 30;\n\n    // Reset flow variables for the next 30 iterations\n    sumHousePower = 0;\n    iterationCount = 0;\n    flow.set(\"flowSumHousePower\", sumHousePower);\n    flow.set(\"flowIterationCount\", iterationCount);\n\n    // Send the average power out the first exit\n    msg1.payload = parseInt(averagePower);\n    flow.set(\"flowAvgHousePower\", msg1.payload.toString());\n\n    return [msg1, null];\n    }\n","outputs":2,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":650,"y":3380,"wires":[["69a86b65b394b08c","e6833878191a3648"],["71f6efbade6f62f7"]]},{"id":"ab70d43f5a804319","type":"api-current-state","z":"65840aa926d9c567","name":"Current State house power less def","server":"afc27684.cf6ed8","version":3,"outputs":1,"halt_if":"","halt_if_type":"str","halt_if_compare":"is","entity_id":"sensor.house_power_consumption_less_deferrables","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"}],"for":"0","forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":400,"y":3380,"wires":[["ab9db8fade2bc42c"]]},{"id":"32ba8859361ba366","type":"inject","z":"65840aa926d9c567","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"60","crontab":"","once":false,"onceDelay":"60","topic":"","payload":"","payloadType":"date","x":170,"y":3380,"wires":[["ab70d43f5a804319"]]},{"id":"69a86b65b394b08c","type":"debug","z":"65840aa926d9c567","name":"debug 7","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":840,"y":3380,"wires":[]},{"id":"71f6efbade6f62f7","type":"debug","z":"65840aa926d9c567","name":"debug 8","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":840,"y":3440,"wires":[]},{"id":"48a7dfb873c1e2b8","type":"comment","z":"65840aa926d9c567","name":"FIFO Buffer for load_power_forecast ","info":"","x":220,"y":3220,"wires":[]},{"id":"afc27684.cf6ed8","type":"server","name":"Home Assistant","version":5,"addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true,"cacheJson":true,"heartbeat":false,"heartbeatInterval":30,"areaSelector":"friendlyName","deviceSelector":"friendlyName","entitySelector":"friendlyName","statusSeparator":"at: ","statusYear":"hidden","statusMonth":"short","statusDay":"numeric","statusHourCycle":"h23","statusTimeFormat":"h:m","enableGlobalContextStore":true}]

Thank you. I tried this now. Do reduce the recorder database isn’t fine for me.
That’s why I build two fi_fo_buffers: one for photovoltaik and one for Load_without_var_loads and prefilled them.
I have in the config:

sensor_power_photovoltaics: input_text.photovoltaik_fi_fo_48h
sensor_power_load_no_var_loads: input_text.load_without_var_load_fi_fo_48h

is this the correct way? How to avoid using the recorder database?

I still have the same errors… restart was done.

I assume you are are using the shell_command or rest_command method to POST your data to EMHASS and using a template to format the jason data to POST?

In that process you need to add something like this:

"load_power_forecast": {{([states('sensor.YOUR_CURRENT_CONSUMPTION_LESS_DEFERRABLE_LOADS')|int(0)] + states('input_text.fifo_buffer').split(',') | map('int') | list) }},

The .split(‘,’) in this template is if you use my node-red code as I have no spaces in my fifo_buffer and you need spaces between the numbers. Mark’s method is different so depending on which you are using you need to write the template to deliver the data correctly.

What this does is add something like this to the data being POSTed:

"load_power_forecast": [592, 386, 421, 410, 398, 428, 489, 474, 348, 337, 328, 334, 351, 313, 324, 329, 335, 343, 323, 317, 314, 318, 330, 549, 442, 425, 551, 407, 539, 507, 479, 588, 735, 2278, 3238, 4996, 1775, 678, 605, 575, 457, 495, 524, 539, 509, 492, 774, 586, 550],

This is my entire template used to format the data to pass to the MPC process:

{
  "prod_price_forecast": {{
    ([states('sensor.cecil_st_feed_in_price')|float(0)] +
    (state_attr('sensor.cecil_st_feed_in_forecast', 'forecasts')|map(attribute='per_kwh')|list))
    | tojson 
  }},
  {%- set current_month = now().month %}
  {%- if (3 <= current_month <= 8) or (11 <= current_month <= 12) %}
  "load_cost_forecast": {%- set current_time = now() %}
  {%- set demand_tariff_start = "15:00:00" %}
  {%- set demand_tariff_end = "21:00:00" %}

  {%- set start_time = current_time.replace(hour=0, minute=0, second=0, microsecond=0) + timedelta(hours=(demand_tariff_start.split(":")[0]|int), minutes=(demand_tariff_start.split(":")[1]|int)) %}
  {%- set end_time = current_time.replace(hour=0, minute=0, second=0, microsecond=0) + timedelta(hours=(demand_tariff_end.split(":")[0]|int), minutes=(demand_tariff_end.split(":")[1]|int)) %}

  {%- if end_time <= start_time %}
    {%- set end_time = end_time + timedelta(days=1) %}
  {%- endif %}

  {%- set values = ([states("sensor.cecil_st_general_price")|float(0)] + state_attr("sensor.cecil_st_general_forecast", "forecasts")|map(attribute="per_kwh")|list) %}
  {%- set ns = namespace(x=[]) %}

  {%- for i in range(values|length) %}
    {%- set future_time = current_time + timedelta(minutes=i * 30) %}
    {%- if start_time <= future_time < end_time and values[i] < 1 %}
      {%- set ns.x = ns.x + [1.0] %}
    {%- else %}
      {%- set ns.x = ns.x + [values[i]] %}
    {%- endif %}
  {%- endfor %}

  {{- ns.x | tojson }},

  {%- else %}
  "load_cost_forecast": {{
    ([states('sensor.cecil_st_general_price')|float(0)] + 
    state_attr('sensor.cecil_st_general_forecast', 'forecasts') |map(attribute='per_kwh')|list) 
    | tojson 
  }},
  {%- endif %}
  "pv_power_forecast": {{
    ([states('sensor.sonnenbatterie_84324_production_w')|int(0)] +
    state_attr('sensor.solcast_pv_forecast_forecast_today', 'detailedForecast')|selectattr('period_start','gt',utcnow()) | map(attribute='pv_estimate')|map('multiply',1000)|map('int')|list +
    state_attr('sensor.solcast_pv_forecast_forecast_tomorrow', 'detailedForecast')|selectattr('period_start','gt',utcnow()) | map(attribute='pv_estimate')|map('multiply',1000)|map('int')|list
    )| tojson
  }},
  "load_power_forecast": {{
    ([states('sensor.house_power_consumption_less_deferrables')|int(0)] +
    states('input_text.fifo_buffer').split(',') | map('int') | list)
  }},
  {#"load_power_forecast": {{"[500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500]" 
  }},#}
  "prediction_horizon": {{
    min(48, (state_attr('sensor.cecil_st_feed_in_forecast', 'forecasts')|map(attribute='per_kwh')|list|length)+1)
  }},
  "num_def_loads": 2,
  "def_total_hours": [
    {%- if 'unny' in states('sensor.denistone_east_extended_text_0') and states('sensor.cecil_st_general_price') | float(0) < 0.15 -%}
      {%- if is_state('sensor.season', 'winter') -%}
        {{2}}
      {%- elif is_state('sensor.season', 'summer') -%}
        {{4}}
      {%- else -%}
        {{3}}
      {%- endif -%}
    {%- else -%}
      {{1}}
    {%- endif -%},
    {%- if is_state('device_tracker.ynot_location_tracker', ['home']) -%}
      {%- if is_state('binary_sensor.ynot_charger', ['on']) -%}
        {{ ((states('number.ynot_charge_limit')|int(80)-(states('sensor.ynot_battery')|int(0)))/30*3)|round(0) }}
      {%- else -%} 
        0
      {%- endif -%}
    {%- else -%} 
      0
    {%- endif -%}
    ],
  "P_deferrable_nom": [1300, {{ (states('input_number.ev_amps') | int(0) * 230)|int(0) }}],
  "treat_def_as_semi_cont": [1, 0],
  "set_def_constant": [0, 0],
  "soc_init": {{ (states('sensor.sonnenbatterie_84324_state_charge_user')|int(0))/100 }},
  "soc_final": 0,
  "alpha": 1,
  "beta": 0
}

and what is prodices is this:

{
  "prod_price_forecast": [0.16, 0.14, 0.13, 0.13, 0.11, 0.09, 0.11, 0.09, 0.1, 0.09, 0.08, 0.07, 0.06, 0.06, 0.06, 0.06, 0.06, 0.06, 0.06, 0.06, 0.06, 0.07, 0.06, 0.01, 0.0, 0.0, 0.0, -0.05, -0.05, -0.06, -0.07, -0.08, -0.08, -0.09, -0.09, -0.09, -0.09, -0.09, -0.07, -0.07, -0.05, -0.05, -0.02, 0.0, 0.07, 0.13, 0.16, 0.2, 0.16],
  "load_cost_forecast": [0.22, 0.2, 0.19, 0.18, 0.19, 0.17, 0.19, 0.18, 0.18, 0.18, 0.16, 0.15, 0.14, 0.14, 0.14, 0.14, 0.14, 0.14, 0.14, 0.14, 0.14, 0.15, 0.14, 0.08, 0.07, 0.07, 0.07, 0.02, 0.01, 0.01, 0.0, 0.0, -0.01, -0.01, -0.01, -0.01, -0.01, -0.01, 0.01, 0.01, 0.02, 0.02, 0.02, 0.05, 0.12, 0.19, 0.22, 0.26, 0.22],
  "pv_power_forecast": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 105, 528, 1050, 1580, 2171, 2715, 3131, 3461, 3725, 3920, 4063, 4110, 4083, 3986, 3792, 3607, 3372, 3068, 2546, 1938, 1436, 914, 387, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  "load_power_forecast": [560, 386, 421, 410, 398, 428, 489, 474, 348, 337, 328, 334, 351, 313, 324, 329, 335, 343, 323, 317, 314, 318, 330, 549, 442, 425, 551, 407, 539, 507, 479, 588, 735, 2278, 3238, 4996, 1775, 678, 605, 575, 457, 495, 524, 539, 509, 492, 774, 586, 550],
  
  "prediction_horizon": 48,
  "num_def_loads": 2,
  "def_total_hours": [1,0],
  "P_deferrable_nom": [1300, 4140],
  "treat_def_as_semi_cont": [1, 0],
  "set_def_constant": [0, 0],
  "soc_init": 0.52,
  "soc_final": 0,
  "alpha": 1,
  "beta": 0
}

Can you help explain how you set your EV charger up with deferable loads?
I’ve noticed your sending a sensor number of hours. how is this determine?
also do you give it a value to control or what its consuming?

like when you do any night charging how does emhass configure this and calculate

Thank you for your help.
I think I have a big gap how EMHASS works…
I thought for the first: I can config the relevant sensors at the Addon Page and it can work.

So at the moment I’ve never used a shell or rest command.
I tried to read the docs from EMHASS, but it doesn’t help much…
I can’t find a config_emhass.yaml or other config files. Only the one in the Addon page, where I can fill in the formulars or

I don’t understand why you write:

[states('sensor.YOUR_CURRENT_CONSUMPTION_LESS_DEFERRABLE_LOADS')|int(0)] + ...

Why adding the current number and the number of the fifo buffer?

I describe my configeration in detail in these documents here EMHASS config for sonnen users. Read the PDF or Word document. Its complex but so is the subject of energy management.

David’s manual is here.

EMHASS is like a calculator that sits on the side of Home Assistant and does nothing until you pass data and instructions to it via its REST API. Then nothing happens until you take action on the results of the calculation passed back into HA.

REST API is a way to interact with EMHASS using the http protocol.

You can do this manually via the EMHASS web interface but this is more for experimenting, not production.

EMHASS will pick up some basic data using the details you set in the configuration such as the ‘power consumption less deferrable loads’ (sensor_power_load_no_var_loads) and solar production (sensor_power_photovoltaics) and other battery details etc. But some of these settings are overwritten by data you POST via the API.

But the typical process is passing a lot of data (current and forecast info) and calling a calculation (dayahead_optim or MPC) and then publishing the result back into HA. Once published you then have to act on the result by interacting with your battery and household loads to manage energy flows in accordance with the forecasts calculated by EMHASS.

There are a couple of different ‘calcuations’ you can do. To start with you can do one calculation per day called a ‘Day-ahead Optimization’.

To do this with all the forecast data for the day ahead and get a useful output you need to pass or POST data dictionarys or lists of data and call the dayahead_optim process via REST API once at say 5am and then call the Publish process via REST API every 5 minutes or so.

I use the MPC process (not the dayahead_optim) which I call every 60 second so I get a more reactive management system but they both use similar lists of data.

The data you need are things like but not limited to:

  1. The current solar production followed by a forecast of the rest of the day which you have to get from solar forecast providers like solcast.com
  2. The current FiT (Feed in Tariff) and a forecast of FiT for the rest of the day
  3. The curren ST (Supply Tariff) and a forecast of ST
  4. and you can also supply the current power consumption and a forecast of the average power consumption per 30 mins for the rest of the day or leave EMHASS to extract that data from your recorder database in Home Assistant.
  5. You also pass details about household loads to be managed and other things depending on your environment. This data has to be JSON formated before posting and that’s what the jinja2 template does that I posted above.

Once this data is passed and the calcuation called by REST API POST command you also call the Publish POST command to pass the calculated data from EMHASS into Home Assistant sensors.

The sensors that are created and updated by the Publish command have the forecast current value and future values for that entity and are:

  1. sensor.p_batt_forecast - “Battery Power Forecast”
  2. sensor.p_grid_forecast - “Grid Power Forecast”
  3. sensor.p_load_forecast - “Load Power Forecast”
  4. sensor.p_pv_forecast - “PV Power Forecast”
  5. sensor.soc_batt_forecast - “Battery SoC Forecast”
  6. sensor.p_deferrableX - “Deferrable Load X” you can have multiple of these depending of what loads you want to control lile pool pump and EV charging etc.

Once you publish the coaulcations back into HA you can then act on them. If you have control over your battery you can charge or discharge the battery according to EMHASS forecast by following the sensor.p_batt_forecast which is the forecast power to be applied to charge or discharge the battery or by using the sensor.soc_batt_forecast to calculate what power should be applied to charge or discharge the battery to reach the forecast SoC etc.

You also use the sensor.p_deferrableX sensors to manage the deferrable loads in your home. Charging the EV or running the hot water system etc. These sensors are configured to simply turn an appliance on and off to achieve your desired period of operation or can pass an amount of power to apply for a particular period where the appliance can change it’s power consumption (e.g. charging an EV at XkW). This allows you to use an EV to soak up available solar power rather than exporting it.

2 Likes

I have a system with hybrid inverter and battery. Should the battery
charge/discharge power be included or excluded from “sensor_power_load_no_var_loads:”?

thanks for this excellent project, and you all who helped.

No, it’s just household load consumption without variable loads.

Should be total household consumption less deferrable loads only. Shouldn’t have to include battery charging or exclude it.

I understand that deferable loads are for the control aspect and to let the system calculate best timing,

But from an ev point. Sometimes you charge at night for a long trip the next day. How can this be controlled or calculated/ incorperated? As its now deleted from no loads calc. When its plugged in the system cant see it and it draws from the grid.

Team,

I’d appreciate any assistance or insight provided on the error I am receiving in the EMHASS log below:

2024-09-23 21:39:17,050 - web_server - INFO - Passed runtime parameters: {'prod_price_forecast': [0.18, 0.28, 0.18, 0.19, 0.15, 0.15, 0.14, 0.15, 0.14, 0.11, 0.1, 0.09, 0.09, 0.11, 0.11, 0.11, 0.11, 0.15, 0.11, 0.11, 0.09, 0.08, 0.06, 0.06, 0.06, 0.03, 0.01, -0.0, -0.0, -0.02, -0.05, -0.01, -0.0, 0.03, 0.05, 0.07, 0.06, 0.09, 0.12, 0.16, 0.3, 0.34, 0.34, 0.29, 0.16, 0.15, 0.13, 0.11, 0.11], 'load_cost_forecast': [0.27, 0.38, 0.27, 0.28, 0.23, 0.23, 0.23, 0.23, 0.23, 0.19, 0.19, 0.17, 0.17, 0.19, 0.19, 0.19, 0.2, 0.23, 0.19, 0.19, 0.17, 0.16, 0.14, 0.14, 0.14, 0.11, 0.1, 0.08, 0.08, 0.06, 0.03, 0.07, 0.08, 0.11, 0.14, 0.15, 0.14, 0.14, 0.17, 0.22, 0.38, 0.42, 0.42, 0.36, 0.22, 0.21, 0.19, 0.19, 0.19], 'pv_power_forecast': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 40, 172, 282, 451, 1235, 1517, 3903, 3926, 4798, 4854, 4802, 4692, 4446, 4124, 3740, 3318, 3090, 2991, 2646, 2056, 1340, 587, 133, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 'prediction_horizon': 48, 'num_def_loads': 1, 'def_total_hours': 3, 'treat_def_as_semi_cont': 1, 'set_def_constant': 0, 'soc_init': 0.42, 'soc_final': 0.24, 'alpha': 1, 'beta': 0}
2024-09-23 21:39:17,051 - web_server - INFO -  >> Setting input data dict
2024-09-23 21:39:17,051 - web_server - INFO - Setting up needed data
2024-09-23 21:39:17,052 - web_server - ERROR - Exception on /action/naive-mpc-optim [POST]
Traceback (most recent call last):
  File "/usr/local/lib/python3.11/dist-packages/flask/app.py", line 1473, in wsgi_app
    response = self.full_dispatch_request()
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/flask/app.py", line 882, in full_dispatch_request
    rv = self.handle_user_exception(e)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/flask/app.py", line 880, in full_dispatch_request
    rv = self.dispatch_request()
         ^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/flask/app.py", line 865, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)  # type: ignore[no-any-return]
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/emhass/web_server.py", line 122, in action_call
    input_data_dict = set_input_data_dict(emhass_conf, costfun,
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/emhass/command_line.py", line 57, in set_input_data_dict
    params, retrieve_hass_conf, optim_conf, plant_conf = utils.treat_runtimeparams(
                                                         ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/emhass/utils.py", line 436, in treat_runtimeparams
    optim_conf["treat_def_as_semi_cont"] = [
                                           ^
TypeError: 'int' object is not iterable
2024-09-23 21:39:17,059 - web_server - INFO - Passed runtime parameters: {}
2024-09-23 21:39:17,059 - web_server - INFO -  >> Setting input data dict
2024-09-23 21:39:17,059 - web_server - INFO - Setting up needed data
2024-09-23 21:39:17,061 - web_server - INFO -  >> Publishing data...
2024-09-23 21:39:17,061 - web_server - INFO - Publishing data to HASS instance
2024-09-23 21:39:17,072 - web_server - INFO - Successfully posted to sensor.p_pv_forecast = -6.0
2024-09-23 21:39:17,079 - web_server - INFO - Successfully posted to sensor.p_load_forecast = 509.87
2024-09-23 21:39:17,086 - web_server - INFO - Successfully posted to sensor.p_pv_curtailment = 0.0
2024-09-23 21:39:17,092 - web_server - INFO - Successfully posted to sensor.p_deferrable0 = 0.0
2024-09-23 21:39:17,100 - web_server - INFO - Successfully posted to sensor.p_batt_forecast = 515.87
2024-09-23 21:39:17,107 - web_server - INFO - Successfully posted to sensor.soc_batt_forecast = 58.83
2024-09-23 21:39:17,115 - web_server - INFO - Successfully posted to sensor.p_grid_forecast = 0.0
2024-09-23 21:39:17,121 - web_server - INFO - Successfully posted to sensor.total_cost_fun_value = -1.95
2024-09-23 21:39:17,127 - web_server - INFO - Successfully posted to sensor.optim_status = Optimal
2024-09-23 21:39:17,134 - web_server - INFO - Successfully posted to sensor.unit_load_cost = 0.4044
2024-09-23 21:39:17,141 - web_server - INFO - Successfully posted to sensor.unit_prod_price = 0.06

I can’t identify the error in the passed variables. If I take the “treat_def_as_semi_cont”: 1, out of the template, I still get the same error.

It seems to parse correctly in the developer tools. I’m using Node-Red to create, post and publish.

Thanks for the assistance.