EMHASS: An Energy Management for Home Assistant

True, however doesn’t mlforcaster method only accept a num_lags value that equals (Freq (hrs) * 24hrs) ?

Example:
optimization_time_step: 15
Therefore
num_lags must be >= 96?

My experience recently, have been getting issues with forecast_dates and forecast_out if they don’t match. (From: get_load_forecast)
ML of EMHASS is something I’m still trying to understand. I just found this as a doggy workaround that worked. Also i’m tired .

No, not at all. The num_lags can actually be whatever value you want.
The only limitation is that the higher this value is the more computation intensive it will be, because internally the model is fitting a ML model per lag.

Yes ok, in this sense they are related. But this would mean that we could adopt a reasoning this way: once that we have computed the optimal num_lags then we need to try to fix the correct forecast_dates to match what is needed according to the relation that you are pointing out with the optimization_time_step.
The thing is that this is just a special case when using the MLForecaster.
We need to work this out better.
Up until now it has been working well because people mostly focused on 24h or less forecasts. But this seems to be changing a bit.

Right, so should focus on changing the dates to match, then changing the num_lags?

Actually the other way around. changing the dates to match based on the used num_lags. Again this is just for the special case when using the MLForecaster

1 Like

Hi,
I have been reading through the docs of EMHASS and this thread for a few days but my understanding is still not there to build my own instance of EMHASS. I hope someone might be able to help me or refer me to a guide/tutorial. Or a example setup. (Blueprints …)

Some infos about my setup:

  • EMHASS as a addon in HA supervised
  • 10kWp Solar panels + Inverter
  • 12kWh Battery
  • Zappi Wallbox (not in HA)
  • A Heatpump (not online just jet)
  • some pond equipment which can be turned off for some hours a day to conserve battery power

Ideally I want to use the cheaper prices of my dynamic power plan in the winter to charge the battery as needed off of the cheap power, but still mostly use the inverters EMS for general self consumption as it is quite fast too react to changing loads.
Also it would be great to set aside some power per request for charging the next day.
And obviously turn off the pond equipment to conserve power but only if it is required (as if there is enough solar power it can be left on).
Selling solar power is not really profitable, therefore as much self consumption as possible is the goal.
Thanks for any input!

What is the best way to do it if I want to control my hot water production. It is a geothermal heat pump so it will not start immediately but when it needs to. I can control that with the help of programs, off eco or comfort. Now comes the tricky part, figuring out what it draws for hot water production and how long it needs to be on. I have previously run node-red with the lowest price node 00:00-12:00 for 5 hours and it has gone great. So now to my real question, how would you set it up in EMHASS?

Not exactly what you want but here’s my set up.
Configuration for sonnen battery

1 Like

Thank you so much! This was a very valuable resource. Could you maybe also share your apex charts config?
For some reason I can only make forecast PV and Household work. The rest of the forecasts just don‘t load and only have the state N/A. I currently use @markpurcell `s config:

thanks in advance!

Hi Mark
Do you still run with fifo buffer?
I tried a bit but not sure where you put it. Do you do it in naive-mpc-optim

type: custom:apexcharts-card
experimental:
  color_threshold: true
graph_span: 24h
span:
  start: minute
now:
  show: true
  label: now
  color: red
yaxis:
  - id: first
    min: -0.2
    max: 0.6
    decimals: 1
    apex_config:
      forceNiceScale: true
  - id: second
    opposite: true
    min: -4000
    max: 6000
    decimals: 1
    apex_config:
      forceNiceScale: true
header:
  show: true
  title: Energy flow
  show_states: true
  colorize_states: true
series:
  - entity: sensor.cecil_st_general_forecast
    yaxis_id: first
    curve: stepline
    float_precision: 2
    show:
      in_header: after_now
    stroke_width: 1
    name: price kWh
    data_generator: |
      return entity.attributes.forecasts.map((entry) => {
        return [new Date(entry.start_time), entry.per_kwh];
      });
  - entity: sensor.cecil_st_feed_in_forecast
    yaxis_id: first
    curve: stepline
    float_precision: 2
    show:
      in_header: after_now
    stroke_width: 1
    name: FiT kWh
    data_generator: |
      return entity.attributes.forecasts.map((entry) => {
        return [new Date(entry.start_time), entry.per_kwh];
      });
  - entity: sensor.p_batt_forecast
    yaxis_id: second
    curve: stepline
    type: area
    show:
      in_header: before_now
    stroke_width: 1
    opacity: 0.1
    color: rgb(0,255,0)
    name: Batt Forecast
    data_generator: |
      return entity.attributes.battery_scheduled_power.map((entry) => {
        return [new Date(entry.date), entry.p_batt_forecast];
      });
  - entity: sensor.p_deferrable0
    yaxis_id: second
    curve: stepline
    stroke_width: 1
    name: Pool
    show:
      in_header: before_now
    data_generator: |
      return entity.attributes.deferrables_schedule.map((entry) => {
        return [new Date(entry.date), entry.p_deferrable0];
      });
  - entity: sensor.p_deferrable1
    yaxis_id: second
    curve: stepline
    stroke_width: 1
    name: Car
    color: white
    show:
      in_header: before_now
    data_generator: |
      return entity.attributes.deferrables_schedule.map((entry) => {
        return [new Date(entry.date), entry.p_deferrable1];
      });
  - entity: sensor.p_pv_forecast
    yaxis_id: second
    curve: stepline
    show:
      in_header: before_now
    stroke_width: 1
    name: PV Forecast
    color: rgb(255,255,0)
    data_generator: |
      return entity.attributes.forecasts.map((entry) => {
        return [new Date(entry.date), entry.p_pv_forecast];
      });
  - entity: sensor.p_load_forecast
    yaxis_id: second
    curve: stepline
    show:
      in_header: before_now
    stroke_width: 1
    name: Load Forecast
    color: rgb(255,0,0)
    data_generator: |
      return entity.attributes.forecasts.map((entry) => {
        return [new Date(entry.date), entry.p_load_forecast];
      });
  - entity: sensor.p_grid_forecast
    yaxis_id: second
    curve: stepline
    opacity: 0.1
    color: rgb(0,255,255)
    type: area
    show:
      in_header: before_now
    stroke_width: 1
    name: Grid Forecast
    data_generator: |
      return entity.attributes.forecasts.map((entry) => {
        return [new Date(entry.date), entry.p_grid_forecast];
      });

1 Like

The way I did it:
Set up a helper of type input_text with whatever name you want and maximum length of 255 and prepopulates with 47 values that represent half hour power consumption average just to kick it off. These numbers will be replace in 24 hours with real numbers.

e.g. 375,401,452,419,356,388,379,344,341,298,291,291,294,291,297,291,295,291,295,293,292,282,330,301,276,898,413,474,1128,1947,1185,921,635,520,355,395,465,436,387,353,391,420,386,424,660,631,509

I’ve done it in Watts without spaces and due to low consumption in my household this allows me to fit that string into 255 characters max length of an input_text entity. If your half hour average consumption plus a comer times 47 it greater than 255 you may have to use kW.

I use Node-Red so I set up a flow that runs every 60 seconds and adds the current power consumption to the last 60 second window’s average for 29 cycles and on the 30th divides the total by 30 to get the average power consumption for the last 30 minutes.

This number is then passed to a function that deletes the first number from the lefthand end of the input_text string and adds the newly calculated number as a string to the end of the input_text.

Then in the MPC POST I’ve added this to the jinja template:

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

This adds the actual power consumption at the time of the POST to the front of the input_text string which is the whole purpose of the exercise (to get EMHASS to react to turning the coffee machine on in the morning or follow the sun when charging the car).

The half hour average function looks like this.

var sumHousePower = parseInt(flow.get("flowSumHousePower")) || 0;
var iterationCount = flow.get("flowIterationCount") || 0;
var avgHousePower = flow.get("flowAvgHousePower") || 0;
var msg1 = { payload: 0 };


iterationCount = iterationCount + 1;
sumHousePower += parseInt(msg.payload);

flow.set("flowSumHousePower", sumHousePower);
flow.set("flowIterationCount", iterationCount);

// Determine exit path based on iteration count
if (iterationCount < 30) {
    var msg2 = { payload: [iterationCount,sumHousePower] };
    return [null, msg2];
    } 
else {
    // Calculate average power consumption
    var averagePower = sumHousePower / 30;

    // Reset flow variables for the next 30 iterations
    sumHousePower = 0;
    iterationCount = 0;
    flow.set("flowSumHousePower", sumHousePower);
    flow.set("flowIterationCount", iterationCount);

    // Send the average power out the first exit
    msg1.payload = parseInt(averagePower);
    flow.set("flowAvgHousePower", msg1.payload.toString());

    return [msg1, null];
    }

The FIFO the input text function looks like this:

msg.payload.lastIndexOf(',')));
let fiFoBuffer = msg.payload.substring(msg.payload.indexOf(',') + 1);
let avgHousePower = flow.get("flowAvgHousePower");
let comerString = ","

// Combine values, and comer and create a string
let newValue = fiFoBuffer.concat(comerString.concat(avgHousePower));

// Set the payload to the new value
msg.payload = newValue;

return msg;

Entire flow:

[
    {
        "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\n//let fiFoBuffer = msg.payload.slice(msg.payload.lastIndexOf(',') + 1);\n//let fiFoBuffer = msg.payload.substring(0, Math.max(0, msg.payload.lastIndexOf(',')));\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\n// let newValue = avgHousePower.concat(comerString.concat(fiFoBuffer));\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": 5,
        "debugenabled": true,
        "domain": "input_text",
        "service": "set_value",
        "areaId": [],
        "deviceId": [],
        "entityId": [
            "input_text.fifo_buffer"
        ],
        "data": "{\"value\":\"{{payload}}\"}",
        "dataType": "json",
        "mergeContext": "",
        "mustacheAltTags": false,
        "outputProperties": [],
        "queue": "none",
        "x": 820,
        "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
    }
]

Mark uses an automation that he explains here

Thanks, I think I solved it with Mark’s variant. Your solution sounds tempting but when I tried your flows last time it didn’t go so well, node-red is not my favorite.

# post_naive_mpc_shell: >
#   curl -i -H "Content-Type: application/json" -X POST -d '{
#     "load_cost_forecast":{{((state_attr("sensor.nordpool_kwh_se3_sek_3_10_025", "raw_today") | map(attribute="value") | list  + state_attr("sensor.nordpool_kwh_se3_sek_3_10_025", "raw_tomorrow") | map(attribute="value") | list))[now().hour:][:24] }},
#     "prod_price_forecast":{{((state_attr("sensor.nordpool_kwh_se3_sek_3_10_025", "raw_today") | map(attribute="value") | list  + state_attr("sensor.nordpool_kwh_se3_sek_3_10_025", "raw_tomorrow") | map(attribute="value") | list))[now().hour:][:24] }},
#     "prediction_horizon":{{min(24, (((state_attr("sensor.nordpool_kwh_se3_sek_3_10_025", "raw_today")|map(attribute="value")|list + state_attr("sensor.nordpool_kwh_se3_sek_3_10_025", "raw_tomorrow") | map(attribute="value")| list)[now().hour:][:48]|list|length)))}},
#     "pv_power_forecast":{{([states("sensor.solcast_pv_forecast_power_now")|int(0)] + state_attr("sensor.solcast_pv_forecast_forecast_today", "detailedHourly")|selectattr("period_start","gt",utcnow()) | map(attribute="pv_estimate")|map("multiply",1000)|map("int")|list + state_attr("sensor.solcast_pv_forecast_forecast_tomorrow", "detailedHourly")|selectattr("period_start","gt",utcnow()) | map(attribute="pv_estimate")|map("multiply",1000)|map("int")|list)| tojson}},
#     "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:]}},
#     "soc_init":{{ max(0,states("sensor.battery_state_of_capacity")|int(0))/100 }},
#     "soc_final":{{ max(100,states("number.battery_end_of_discharge_soc")|int(0))/100 }},
#     "delta_forecast":2
#   }' http://localhost:5000/action/naive-mpc-opti

I actually have to admit that I’m a little proud of myself when I got my control of the battery (Huawei, Luna2000) so it plays nicely with EMHASS It would be nice to get everything in a blueprint though.
Another thing that would be neat but perhaps not solvable is that in emhass you would put in all the basic values ​​as you do today via shell script. It would simplify EMHASS significantly.

003

1 Like

Yes, I think Mark’s is simpler. Main difference is I’m calculating the average power over 30 seconds when I think Mark’s is taking the power at time of execution.

Hello,
I hvae another question. When running the MPC optimizer every minute it compleatly ignores that it has currently a constant load running at the moment. Therefore it replans this load to another point in time which is a problem for a load that should only be turned on once per day. Is there a way around that? @davidusb
@rcruikshank I am curious if your setup experiences similar problems. If your pool pump for example should run for 4h then on each optimisation step this gets reset if the behaviour is the same as at my setup. Therefore the plan is made to run for 4 hours, but the result is a continous run as long as enough power is there due to constant reoptimisation due to running the MCP optimizer every few minutes. (?)
Thanks!

I get an error when I add load_power_forecast, anyone know why

2024-04-11 14:35:01,731 - web_server - INFO - Status: Optimal
2024-04-11 14:35:01,731 - web_server - INFO - Total value of the Cost function = -5095.18
2024-04-11 14:35:06,935 - web_server - INFO - Passed runtime parameters: {}
2024-04-11 14:35:06,935 - web_server - INFO -  >> Setting input data dict
2024-04-11 14:35:06,935 - web_server - INFO - Setting up needed data
2024-04-11 14:35:06,937 - web_server - INFO -  >> Publishing data...
2024-04-11 14:35:06,937 - web_server - INFO - Publishing data to HASS instance
2024-04-11 14:35:06,949 - web_server - INFO - Successfully posted to sensor.p_pv_forecast = 3223.55
2024-04-11 14:35:06,958 - web_server - INFO - Successfully posted to sensor.p_load_forecast = 1409.6
2024-04-11 14:35:06,965 - web_server - INFO - Successfully posted to sensor.p_deferrable0 = 2200.0
2024-04-11 14:35:06,972 - web_server - INFO - Successfully posted to sensor.p_deferrable1 = 2220.0
2024-04-11 14:35:06,980 - web_server - INFO - Successfully posted to sensor.p_batt_forecast = -2315.79
2024-04-11 14:35:06,988 - web_server - INFO - Successfully posted to sensor.soc_batt_forecast = 100.0
2024-04-11 14:35:06,996 - web_server - INFO - Successfully posted to sensor.p_grid_forecast = 4921.84
2024-04-11 14:35:07,003 - web_server - INFO - Successfully posted to sensor.total_cost_fun_value = -5.09
2024-04-11 14:35:07,010 - web_server - INFO - Successfully posted to sensor.optim_status = Optimal
2024-04-11 14:35:07,017 - web_server - INFO - Successfully posted to sensor.unit_load_cost = 0.004
2024-04-11 14:35:07,024 - web_server - INFO - Successfully posted to sensor.unit_prod_price = 0.004
2024-04-11 14:38:09,976 - web_server - INFO - Passed runtime parameters: {'load_cost_forecast': [0.004, 0.06, 0.124, 0.319, 0.367, 0.364, 0.301, 0.269, 0.071, 0.05, 0.06, 0.06, 0.063, 0.071, 0.224, 0.396, 0.569, 0.68, 0.924, 0.945, 0.608, 0.359, 0.289, 0.072], 'prod_price_forecast': [0.004, 0.06, 0.124, 0.319, 0.367, 0.364, 0.301, 0.269, 0.071, 0.05, 0.06, 0.06, 0.063, 0.071, 0.224, 0.396, 0.569, 0.68, 0.924, 0.945, 0.608, 0.359, 0.289, 0.072], 'prediction_horizon': 24, 'pv_power_forecast': [3006, 1560, 1520, 2192, 1078, 243, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 154, 653, 1222, 1941, 2600, 3141, 3187, 3026, 2797, 2352, 1699, 1161, 630, 148, 0, 0, 0, 0], 'load_power_forecast': [1172, 0, 5300, 4400, 5000, 4000, 3700, 3700, 3700, 4100, 4600, 3300, 3400, 3800, 3200, 3400, 3400, 4100, 500, 600, 1400, 1400, 1400, 400], 'soc_init': 0.79, 'soc_final': 1.0, 'delta_forecast': 2}
2024-04-11 14:38:09,976 - web_server - INFO -  >> Setting input data dict
2024-04-11 14:38:09,976 - web_server - INFO - Setting up needed data
2024-04-11 14:38:09,978 - web_server - ERROR - The passed action argument and hence the set_type parameter for setup is not valid
2024-04-11 14:38:09,978 - web_server - ERROR - ERROR: passed action is not valid
2024-04-11 14:38:14,997 - web_server - INFO - Passed runtime parameters: {}
2024-04-11 14:38:14,997 - web_server - INFO -  >> Setting input data dict
2024-04-11 14:38:14,997 - web_server - INFO - Setting up needed data
2024-04-11 14:38:14,998 - web_server - INFO -  >> Publishing data...
2024-04-11 14:38:14,999 - web_server - INFO - Publishing data to HASS instance
2024-04-11 14:38:15,010 - web_server - INFO - Successfully posted to sensor.p_pv_forecast = 3223.55
2024-04-11 14:38:15,019 - web_server - INFO - Successfully posted to sensor.p_load_forecast = 1409.6
2024-04-11 14:38:15,029 - web_server - INFO - Successfully posted to sensor.p_deferrable0 = 2200.0
2024-04-11 14:38:15,039 - web_server - INFO - Successfully posted to sensor.p_deferrable1 = 2220.0
2024-04-11 14:38:15,048 - web_server - INFO - Successfully posted to sensor.p_batt_forecast = -2315.79
2024-04-11 14:38:15,058 - web_server - INFO - Successfully posted to sensor.soc_batt_forecast = 100.0
2024-04-11 14:38:15,067 - web_server - INFO - Successfully posted to sensor.p_grid_forecast = 4921.84
2024-04-11 14:38:15,076 - web_server - INFO - Successfully posted to sensor.total_cost_fun_value = -5.09
2024-04-11 14:38:15,084 - web_server - INFO - Successfully posted to sensor.optim_status = Optimal
2024-04-11 14:38:15,093 - web_server - INFO - Successfully posted to sensor.unit_load_cost = 0.004
2024-04-11 14:38:15,102 - web_server - INFO - Successfully posted to sensor.unit_prod_price = 0.004

My shell :

post_naive_mpc_shell_v2: >
  curl -i -H "Content-Type: application/json" -X POST -d '{
    "load_cost_forecast":{{((state_attr("sensor.nordpool_kwh_se3_sek_3_10_025", "raw_today") | map(attribute="value") | list  + state_attr("sensor.nordpool_kwh_se3_sek_3_10_025", "raw_tomorrow") | map(attribute="value") | list))[now().hour:][:24] }},
    "prod_price_forecast":{{((state_attr("sensor.nordpool_kwh_se3_sek_3_10_025", "raw_today") | map(attribute="value") | list  + state_attr("sensor.nordpool_kwh_se3_sek_3_10_025", "raw_tomorrow") | map(attribute="value") | list))[now().hour:][:24] }},
    "prediction_horizon":{{min(24, (((state_attr("sensor.nordpool_kwh_se3_sek_3_10_025", "raw_today")|map(attribute="value")|list + state_attr("sensor.nordpool_kwh_se3_sek_3_10_025", "raw_tomorrow") | map(attribute="value")| list)[now().hour:][:48]|list|length)))}},
    "pv_power_forecast":{{([states("sensor.solcast_pv_forecast_power_now")|int(0)] + state_attr("sensor.solcast_pv_forecast_forecast_today", "detailedHourly")|selectattr("period_start","gt",utcnow()) | map(attribute="pv_estimate")|map("multiply",1000)|map("int")|list + state_attr("sensor.solcast_pv_forecast_forecast_tomorrow", "detailedHourly")|selectattr("period_start","gt",utcnow()) | map(attribute="pv_estimate")|map("multiply",1000)|map("int")|list)| tojson}},
    "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:]}},
    "soc_init":{{ max(0,states("sensor.battery_state_of_capacity")|int(0))/100 }},
    "soc_final":{{ max(100,states("number.battery_end_of_discharge_soc")|int(0))/100 }},
    "delta_forecast":2
  }' http://localhost:5000/action/naive-mpc-opti

Hmm totally crazy a typo has missed a letter, m in optim. (http://localhost:5000/action/naive-mpc-opti)
Shame on.

So you’re saying that the 4 hour setting is ignored and the load is run continuously as long as there is PV to run it? No I don’t see that happening. This time of year I run the pool pump for two hours and that is what occurs.


I think you can restrict the load between two time slots but I don’t do that. The curve of the tariff prices due to two way tariff plan naturally hold the deferrable loads between 10:00 and 14:00.

I think you can also configure the load to be one occurrence rather than multiple in one 24 hours period as well. Not sure how that’s done.

Not sure what this means:

  • def_total_hours for the list of deferrable loads functioning hours. These values can decrease as the day advances to take into account receding horizon daily energy objectives for each deferrable load.

This either means that that:

  1. as the day goes on you can reduce the hours passed in def_total_hours to coincide with a receding horizon or
  2. the system will reduce the hours automatically?

Perhaps its the former and you can write some template that will dynamically reduce the hours you pass in def_total_hours to match a receding horizon?

In my system the horizon does not change much. Its always fluctuates between 48 30-minute intervals and 31 30-minute intervals.

Is your horizon changing throughout the day?

Thanks for the reply! Interesting that you don’t have the problem. At the moment I have a fixed 48h horizon with 30 min intervals. The supply price also does not vary as I am not quite ready to switch to a dynamic power plan.
I have also tried to reduce def_total_hours but the following beahviour then resuts:

  1. deferrable load gets switched on.
  2. 10 min later the window gets moved to a point in the future even tough it has only one allowed startup per day and is running
    → this results in a unusable behaviour.

The workaround I have successfully implemented is that I also reduce the def_end_timestep to force the load to the beginning. This seems to be very buggy as the constant load then gets sometimes multiplied or devided by 2! Also the optimzer state then goes to infeasble.
Strange…
Do you still run the optimzer every minute?
Do you know if the optimizer checks if the device is already running and therefore needs to be proceed for the time set? (or is the startup meant by optimization task which would then lead to the question how this should ever work becuase there is most likely often a better solution sometime in the future or equal fitting situations the device could be moved to throughout the day!)
@davidusb Is that a intended behaviour and the optimizer should run less often?(even though this would be bad for accuracy of the prediction)
Edit: an example:
grafik
→ half an hour later:
grafik

Configuration is mostly standart. I took a lot of inspiration from @rcruikshank and @markpurcell

set_deferrable_load_single_constant: true
treat_deferrable_load_as_semi_cont: true
operating_hours_of_each_deferrable_load: 9 <- fixed for the test

MCP optimizer gets run every minute via post command, because I let the battery run its own charge algorythm which is faster than EMHASS. I therfore need to update the current battery %

Thanks

I have problems with the config from may PV with the scrape + PVLib method. I can’t find my modules and the inverter in the DB (https://emhass-pvlib-database.streamlit.app/). Can somebody help?
Module: THE FRAMED 144 LAYOUT MODULE (TSM-DE17M(II)) from Trinasolar
Inverter: DEYE SUN HYBRID 8KW SG01LP1-EU 2

Thanx
Oliver

The original intent was to run dayahead optimisation once every 24 hours and then run publish every 5 minutes or so. In that context it schedules the single block of operation and switches on and off with the publish function.

Enter MPC optimisation with the intent to schedule say the next day 6 hours and run publish say every 30 minutes, so you still have the continuous block of operation.

EMHASS doesn’t retain any state data about your deferrable loads, so it doesn’t ‘renember’ what was scheduled for last optimisation so as a consequences you see the behavior that it can switch deferrable loads on and off with each optimisation.

At my site I am running MPC every minute with real time pricing, solar and household load values. EMHASS does a fantastic job and each optimisation will very precisely allocate to the Watt a feasible solution. If it calculates it has 2467 W to allocate to deferrable loads it will allocate exactly this number to my EV charging, your issue which I do see regularly is the next optimisation, one minute later, it can allocate 2467 W to my HVAC and the EV is allocated 0 W.

I compensate for this with some of my automations that have hard relay start/ stop actions with a 5-15 minute delay (hysteresis) between state changes. Automations that have soft changes, such as charging_amps for my EV I let them change.

You can see this impact on my hot water heat pump system. Deferrable4 forecast from EMHASS switches on and off excessively at the end and start of the day and is solid on during the middle of the day. Whilst the actual power consumption doesn’t switch on/ off.

So you can either run your EMHASS optimisation less frequently, or account for a lag in your automations so your devices don’t change state too much. You could extend this logic that the first time a device is started, that the automation doesn’t switch it off until it has run for the full cycle.

1 Like