EMHASS: An Energy Management for Home Assistant

The delta_forecast parameter is in days, you are fixing it to 20?!!
Like I said you need to change your num_lags parameter in the MLForecaster.
Example: to forecast 2 days using the MLForecaster you’ll need to set delta_forecast=2 and num_lags=96

@davidusb i still get error:

2024-04-02 20:37:10,525 - web_server - INFO - Passed runtime parameters: {'load_cost_forecast': [0.173, 0.172, 0.091, 0.071, 0.042, 0.071, 0.065, 0.061, 0.056, 0.073, 0.124, 0.167, 0.199, 0.198, 0.168, 0.158, 0.154, 0.149, 0.146, 0.143, 0.146, 0.15, 0.169, 0.191, 0.206, 0.18, 0.131, 0.115], 'prod_price_forecast': [0.093, 0.093, 0.042, 0.026, 0.002, 0.025, 0.021, 0.017, 0.013, 0.027, 0.069, 0.088, 0.115, 0.114, 0.089, 0.081, 0.077, 0.074, 0.071, 0.069, 0.071, 0.075, 0.09, 0.108, 0.12, 0.099, 0.075, 0.061], 'prediction_horizon': 28, 'pv_power_forecast': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 151, 459, 907, 1427, 1844, 2111, 2195, 2108, 1958, 1461, 835, 410, 121, 0, 0, 0, 0], 'var_model': 'sensor.ss_load_power', 'var_load:': 'sensor.ss_load_power', 'delta_forecast': 2, 'num_lags': 96}
2024-04-02 20:37:10,526 - web_server - INFO -  >> Setting input data dict
2024-04-02 20:37:10,527 - web_server - INFO - Setting up needed data
2024-04-02 20:37:10,543 - web_server - INFO - Retrieve hass get data method initiated...
2024-04-02 20:37:10,916 - web_server - INFO - Retrieving weather forecast data using method = list
2024-04-02 20:37:10,917 - web_server - INFO - Retrieving data from hass for load forecast using method = mlforecaster
2024-04-02 20:37:10,918 - web_server - INFO - Retrieve hass get data method initiated...
2024-04-02 20:37:11,667 - web_server - INFO -  >> Performing naive MPC optimization...
2024-04-02 20:37:11,667 - web_server - INFO - Performing naive MPC optimization
2024-04-02 20:37:11,670 - 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 1463, in wsgi_app
    response = self.full_dispatch_request()
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/flask/app.py", line 872, in full_dispatch_request
    rv = self.handle_user_exception(e)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/flask/app.py", line 870, in full_dispatch_request
    rv = self.dispatch_request()
         ^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/flask/app.py", line 855, 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 145, in action_call
    opt_res = naive_mpc_optim(input_data_dict, app.logger)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/emhass/command_line.py", line 273, in naive_mpc_optim
    df_input_data_dayahead = input_data_dict['fcst'].get_load_cost_forecast(
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/emhass/forecast.py", line 715, in get_load_cost_forecast
    forecast_out = self.get_forecast_out_from_csv(
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/emhass/forecast.py", line 530, in get_forecast_out_from_csv
    forecast_out = pd.DataFrame(
                   ^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/pandas/core/frame.py", line 758, in __init__
    mgr = ndarray_to_mgr(
          ^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/pandas/core/internals/construction.py", line 337, in ndarray_to_mgr
    _check_values_indices_shape_match(values, index, columns)
  File "/usr/local/lib/python3.11/dist-packages/pandas/core/internals/construction.py", line 408, in _check_values_indices_shape_match
    raise ValueError(f"Shape of passed values is {passed}, indices imply {implied}")
ValueError: Shape of passed values is (8, 1), indices imply (4, 1)

Here are my shell commands. Could you please review them:

  mpc: "curl -i -H \"Content-Type:application/json\" -X POST -d '{
    \"load_cost_forecast\":{{((state_attr('sensor.nordpool_with_vat_and_tarifss', 'raw_today') | map(attribute='value') | list  + state_attr('sensor.nordpool_with_vat_and_tarifss', 'raw_tomorrow') | map(attribute='value') | list))[now().hour:][:48] }},
    \"prod_price_forecast\":{{((state_attr('sensor.nordpool_kwh_ee_eur_3_10_0', 'raw_today') | map(attribute='value') | list  + state_attr('sensor.nordpool_kwh_ee_eur_3_10_0', 'raw_tomorrow') | map(attribute='value') | list))[now().hour:][:48] }}, 
    \"prediction_horizon\":{{min(48, (((state_attr('sensor.nordpool_kwh_ee_eur_3_10_0', 'raw_today')|map(attribute='value')|list + state_attr('sensor.nordpool_kwh_ee_eur_3_10_0', '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}},
    \"var_model\":\"sensor.ss_load_power\",
    \"var_load:\":\"sensor.ss_load_power\",
    \"delta_forecast\":2,
    \"num_lags\":96
    }' http://192.168.1.35:5001/action/naive-mpc-optim"
  ml_forecast_model_fit: "curl -i -H \"Content-Type:application/json\" -X POST -d '{
    \"days_to_retrieve\":20,
    \"model_type\":\"load_forecast\",
    \"var_model\":\"sensor.ss_load_power\",
    \"num_lags\":96,
    \"split_date_delta\":\"48h\",
    \"perform_backtest\":\"True\"
    }' http://192.168.1.35:5001/action/forecast-model-fit"
  ml_forecast_model_predict: "curl -i -H \"Content-Type:application/json\" -X POST -d '{
    \"model_type\": \"load_forecast\",
    \"var_model\":\"sensor.ss_load_power\"
    }' http://192.168.1.35:5001/action/forecast-model-predict"
  ml_forecast_model_tune: "curl -i -H \"Content-Type:application/json\" -X POST -d '{
    \"days_to_retrieve\":20,
    \"model_type\":\"load_forecast\",
    \"var_model\":\"sensor.ss_load_power\",
    \"num_lags\":96,
    \"split_date_delta\":\"48h\",
    \"perform_backtest\":\"True\"
    }' http://192.168.1.35:5001/action/forecast-model-tune"

Result from HA developer tools

mpc: "curl -i -H \"Content-Type:application/json\" -X POST -d '{
    \"load_cost_forecast\":[0.173, 0.172, 0.091, 0.071, 0.042, 0.071, 0.065, 0.061, 0.056, 0.073, 0.124, 0.167, 0.199, 0.198, 0.168, 0.158, 0.154, 0.149, 0.146, 0.143, 0.146, 0.15, 0.169, 0.191, 0.206, 0.18, 0.131, 0.115],
    \"prod_price_forecast\":[0.093, 0.093, 0.042, 0.026, 0.002, 0.025, 0.021, 0.017, 0.013, 0.027, 0.069, 0.088, 0.115, 0.114, 0.089, 0.081, 0.077, 0.074, 0.071, 0.069, 0.071, 0.075, 0.09, 0.108, 0.12, 0.099, 0.075, 0.061], 
    \"prediction_horizon\":28,    
    \"pv_power_forecast\":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 151, 459, 907, 1427, 1844, 2111, 2195, 2108, 1958, 1461, 835, 410, 121, 0, 0, 0, 0],
    \"var_model\":\"sensor.ss_load_power\",
    \"var_load:\":\"sensor.ss_load_power\",
    \"delta_forecast\":2,
    \"num_lags\":96
    }' http://192.168.1.35:5001/action/naive-mpc-optim"
  ml_forecast_model_fit: "curl -i -H \"Content-Type:application/json\" -X POST -d '{
    \"days_to_retrieve\":20,
    \"model_type\":\"load_forecast\",
    \"var_model\":\"sensor.ss_load_power\",
    \"num_lags\":96,
    \"split_date_delta\":\"48h\",
    \"perform_backtest\":\"True\"
    }' http://192.168.1.35:5001/action/forecast-model-fit"
  ml_forecast_model_predict: "curl -i -H \"Content-Type:application/json\" -X POST -d '{
    \"model_type\": \"load_forecast\",
    \"var_model\":\"sensor.ss_load_power\"
    }' http://192.168.1.35:5001/action/forecast-model-predict"
  ml_forecast_model_tune: "curl -i -H \"Content-Type:application/json\" -X POST -d '{
    \"days_to_retrieve\":20,
    \"model_type\":\"load_forecast\",
    \"var_model\":\"sensor.ss_load_power\",
    \"num_lags\":96,
    \"split_date_delta\":\"48h\",
    \"perform_backtest\":\"True\"
    }' http://192.168.1.35:5001/action/forecast-model-tune"

For some reason my mlforecaster model fit fails as well

2024-04-02 20:39:34,758 - web_server - INFO - Passed runtime parameters: {'days_to_retrieve': 20, 'model_type': 'load_forecast', 'var_model': 'sensor.ss_load_power', 'num_lags': 96, 'split_date_delta': '48h', 'perform_backtest': 'True'}
2024-04-02 20:39:34,758 - web_server - INFO -  >> Setting input data dict
2024-04-02 20:39:34,758 - web_server - INFO - Setting up needed data
2024-04-02 20:39:34,761 - web_server - INFO - Retrieve hass get data method initiated...
2024-04-02 20:39:34,765 - web_server - ERROR - The retrieved JSON is empty, A sensor:sensor.ss_load_power may have 0 days of history or passed sensor may not be correct

It worked before 0.8.5 update and DST. sensor.ss_load_power has data

1 Like

Tried to troubleshoot and few findings:

  1. Looks like “delta_forecast” in shell or rest command does not do anything. If i Change it in config_emhass.yaml, it works. Changing it to 1 results my dayahead to run as this does not have 48 datapoints (i’m using hourly, so 24h = 24 datapoints). If i have delta_forecast: 2 in my config_emhass.yaml and “delta_forecast”: 1 in my rest_command it fails and tells i need 48 values which i don’t have.

  2. If i succesfully run dayahead with 24 datapoints once, MPC with more than 24 datapoints will fail “IndexError: index 27 is out of bounds for axis 0 with size 24”. If in run it with max 24 datapoints everything works as expected

  3. If i dont run dayahead after restarting and run MPC with more than 24 datapoints. (again have to change delta_forecast: 2 in my config_emhass.yaml because it does not take “delta_forecast”: 1 in my rest_command) I get “ValueError: Shape of passed values is (4, 1), indices imply (2, 1)”

Sorted out the error with MLforecaster. Somehow i did not have data for 20 anymore. 19 days worked.

I’ve tried to emulate this in node-red.

  1. Injection Node - executing the flow every 60 seconds
  2. Current State Node - retrieves current “house power less deferrable” value and passes it next node
  3. Function Node - Java script that sums 30 values into a flow variable (30 minutes worth) and exits flow first 29 iterations then on the 30th calculates to average and passes to next node
  4. Current State Node - retrieves input_text.fifo_buffer and passes to next node
  5. Function Node - takes current state of input_text.fifo_buffer and trims last value from last comer on and concatenates latest average power value + comer to front of string
  6. Call Service Node - replaces value of input_text.fifo_buffer with new string

JSON for flow is below


Last step is MPC call that splits the string at comers and maps the integers into a list with the current power consumption at the front so 60 second MPC call has the current consumption.

Now to see if it works.

New line in MPC call:

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

Flow JSON:

[
  {
    "id": "4b476c19801c52f6",
    "type": "api-current-state",
    "z": "17f6ed58.217203",
    "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": 540,
    "wires": [
      [
        "0c76ee9016f6a2c4"
      ]
    ]
  },
  {
    "id": "0c76ee9016f6a2c4",
    "type": "function",
    "z": "17f6ed58.217203",
    "name": "function 2",
    "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": 720,
    "y": 540,
    "wires": [
      [
        "1d42c14dfe62c6f4",
        "fd4a91a0fffda99a"
      ]
    ]
  },
  {
    "id": "1d42c14dfe62c6f4",
    "type": "api-call-service",
    "z": "17f6ed58.217203",
    "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": 1000,
    "y": 520,
    "wires": [
      []
    ]
  },
  {
    "id": "fd4a91a0fffda99a",
    "type": "debug",
    "z": "17f6ed58.217203",
    "name": "debug 3",
    "active": true,
    "tosidebar": true,
    "console": false,
    "tostatus": false,
    "complete": "false",
    "statusVal": "",
    "statusType": "auto",
    "x": 840,
    "y": 600,
    "wires": []
  },
  {
    "id": "37533cd895b19638",
    "type": "function",
    "z": "17f6ed58.217203",
    "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 };\nvar msg2 = { payload: 'exit' };\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    // Send empty message out the second exit for iterations 1-59\n    // (Assuming second exit is configured as \"2nd\")\n    return [null, msg2];\n    } \nelse {\n    // Calculate average power consumption\n    var averagePower = sumHousePower / 30;\n\n    // Reset flow variables for the next 60 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": 660,
    "wires": [
      [
        "43e35c7b49d1ca13",
        "4b476c19801c52f6"
      ],
      [
        "71d58c1183654303"
      ]
    ]
  },
  {
    "id": "afd8fcf24f1de5ff",
    "type": "api-current-state",
    "z": "17f6ed58.217203",
    "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": 660,
    "wires": [
      [
        "37533cd895b19638"
      ]
    ]
  },
  {
    "id": "6059deafd4417563",
    "type": "inject",
    "z": "17f6ed58.217203",
    "name": "",
    "props": [
      {
        "p": "payload"
      },
      {
        "p": "topic",
        "vt": "str"
      }
    ],
    "repeat": "60",
    "crontab": "",
    "once": false,
    "onceDelay": "60",
    "topic": "",
    "payload": "",
    "payloadType": "date",
    "x": 170,
    "y": 660,
    "wires": [
      [
        "afd8fcf24f1de5ff"
      ]
    ]
  },
  {
    "id": "43e35c7b49d1ca13",
    "type": "debug",
    "z": "17f6ed58.217203",
    "name": "debug 4",
    "active": true,
    "tosidebar": true,
    "console": false,
    "tostatus": false,
    "complete": "false",
    "statusVal": "",
    "statusType": "auto",
    "x": 840,
    "y": 660,
    "wires": []
  },
  {
    "id": "71d58c1183654303",
    "type": "debug",
    "z": "17f6ed58.217203",
    "name": "debug 5",
    "active": true,
    "tosidebar": true,
    "console": false,
    "tostatus": false,
    "complete": "false",
    "statusVal": "",
    "statusType": "auto",
    "x": 840,
    "y": 720,
    "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
  }
]

Oops yes sorry about this, that parameter can only be set in the configuration file.
There are other parameters that can be set directly live on the shell or REST commands, here is the list: https://github.com/davidusb-geek/emhass/tree/master?tab=readme-ov-file#passing-other-data

So that’s it, I don’t see anything wrong here.
If you set delta_forecast=2 then provide the correct amount of data in yours lists.
From what I see in the logs that you posted the error is with the load_cost_forecast length

Ok, thats good to know that delta_forecast can be only set in comfiguration file.

So looks like if I want to use dynamic lenght of the oprimization, then i would not use dayahead and use only MPC.

But maybe you have and idea why this happened:

If i dont run dayahead after restarting and run MPC with more than 24 datapoints. (again have to change delta_forecast: 2 in my config_emhass.yaml because it does not take “delta_forecast”: 1 in my rest_command) I get “ValueError: Shape of passed values is (4, 1), indices imply (2, 1)”

Hi!

I currently use naive-mpc-optim for a 12 hour window, publishing the data every 5 minutes.
This is using the load forecast method = naive afaik.
So far I passed my own data for pv_power_forecast, prod_price_forecast and load_cost_forecast, but I also want to use load_power_forecast using the machine learning forecaster.
From the docs I’m not sure if I understood correctly…

These are the commands I prepared so far and which work):

curl -i -H "Content-Type:application/json" -X POST -d '{"days_to_retrieve":28, "var_model": "sensor.derived_power_consumption_total_no_var_loads"}' http://localhost:5000/action/forecast-model-fit
curl -i -H "Content-Type:application/json" -X POST -d '{"days_to_retrieve":28, "var_model": "sensor.derived_power_consumption_total_no_var_loads", "perform_backtest": "True"}' http://localhost:5000/action/forecast-model-fit
curl -i -H "Content-Type:application/json" -X POST -d '{"model_type": "load_forecast", "var_model": "sensor.derived_power_consumption_total_no_var_loads"}' http://localhost:5000/action/forecast-model-predict
curl -i -H "Content-Type:application/json" -X POST -d '{"var_model": "sensor.derived_power_consumption_total_no_var_loads"}' http://localhost:5000/action/forecast-model-tune

Questions:
Is the second needed, or could it replace the first (backtest)?
How often should I run them? Which time (is it relevant when)?
When executing naive-mpc-optim, it still uses the “naive” data. Would it be needed to publish the sensor with “model_predict_publish”: True? And then to use the resulting sensor with naive-mpc-optim?

maybe dumb questions, but I do not 100% understand what the machine learning forecaster does…

The backtest computes the R2 metric on the complete train dataset. This can take some time for long dataset. You are just using 28 days (not enough for me) so you don’t care about this. Also you should not care about this if your server has enough compute power. Just always set "perform_backtest": "True".

The recommendation is one time a day. I only use day-ahead optimization, so I run this 10 min before I run the optimization.

You need to change the method parameter for load power forecasting in the configuration file to mlforecaster.
No don’t use “model_predict_publish”: True. The publish will be handled by EMHASS internally. This publish option is there because you can actually use the MLForecaster to forecast any other variable in your HomeAssistant. So this publish method will be needed in that case to publish the forecast/predictions to HA.

No question is dumb here :wink:
This method uses machine learning models trained on past history data to produce a forecast on future time steps of your variable of choice. In EMHASS the default variable for this is your HA sensor for load power. The machine learning models are basically regression models but a recursive auto-regression methodology is used to keep track of the time series nature of the forecasted variable.

thank you for your answers!

how many days would make more sense? I have a history of 30 days (instead of default 10) in Home Assistant. It worked for me with 28, but a higher value failed…
do you explicitly increase the history for the load sensor?

running all 3 commands once a day (one by one) would be enough then, correct?

I’ll try your suggestions. Thanks!

As much days as you can. I train it with 200 days.

Of course, tweaking the recorder. You can specify the list of sensor that you want to record longer history data. Check it here: https://www.home-assistant.io/integrations/recorder/

No, just run the fit and then the tune method, once a day, one after the other.

thanks again! :slightly_smiling_face:

hmmm… had a look at it, but I see no option to have a longer history for a single sensor. could you please share your config?

It is there, even with examples.
You just need to use the include and exclude options

I do now, after looking deep into this.
It’s a bug!
But already solved it here: https://github.com/davidusb-geek/emhass/pull/250
Should be available in the following release

1 Like

Thats great. Thank you for looking into it

@davidusb , do i need to put this

        "var_model": "sensor.ss_load_power",
        "var_load": "sensor.ss_load_power"

in the dayahead and mpc rest_command payload when i’m using mlforecaster?

Normally no, those are needed in the predict and tune REST commands, but not for MPC and day-ahead.
But you can test it yourself, that’s why the webui is there for. Launch you day-ahead or MPC optimization and check the results in the webui. The load power should be the output from the ML model

1 Like

I tried to implement what you did but it has troubles when I try to schedule something in the evening for the next day eg 1 hour deferrable load with window of 10am - 2pm

Your code would insist the start window was the past morning and the end was tomorrow 2pm … and then EMHASS would schedule it anytime between now and tomorrow 2pm.

I ended up compromising and using the code below.
It doesn’t allow windows to span over midnight (which none of my loads need to) but it does know that if both the start and end window is in the past, I must be talking about the next day.

- platform: template
  sensors:
    timeslot_deferrable0_start :
      value_template: >-
        {% set ts = (((today_at(states('input_datetime.time_deferrable0_start'))|as_timestamp - now()|as_timestamp))/1800)|round(0, "ceil", 0) %}
        {% if now() > today_at(states('input_datetime.time_deferrable0_start')) and now() > today_at(states('input_datetime.time_deferrable0_end')) %}
          {% set ts = ts + 48 %}
        {% endif %} 
        {{ ts * is_state('input_boolean.tog_deferrable0','on') }} 

- platform: template
  sensors:
    timeslot_deferrable0_end :
      value_template: >-
        {% set ts = (((today_at(states('input_datetime.time_deferrable0_end'))|as_timestamp - now()|as_timestamp))/1800) |round(0, "ceil", 0) %}
        {% if now() > today_at(states('input_datetime.time_deferrable0_start')) and now() > today_at(states('input_datetime.time_deferrable0_end')) %}
          {% set ts = ts + 48 %}
        {% endif %} 
        {{ ts * is_state('input_boolean.tog_deferrable0','on') }}

You are right about problems with time windows crossing midnight. I now have rewritten the sensors as follows:

  - name: def_0_start_timestep
    state: >
      {% set start_time = states('input_datetime.emhass_p_deferrable_0_start_time') %}
      {% set end_time = states('input_datetime.emhass_p_deferrable_0_end_time') %}
      {% set start_timestep = (((today_at(start_time)|as_timestamp - now()|as_timestamp) + 900)  / 1800) | round(0)  | int(0) %}
      {% if end_time > start_time and now() >= today_at(end_time) %} 
        {% set start_timestep = start_timestep +48 %}
      {% endif %}
      {{start_timestep}}

  - name: def_0_end_timestep
    state: >
      {% set start_time = states('input_datetime.emhass_p_deferrable_0_start_time') %}
      {% set end_time = states('input_datetime.emhass_p_deferrable_0_end_time') %}
      {% set total_hours = states('input_number.emhass_p_deferrable_0_total_hours') | int(0) %}
      {% set end_timestep = (((today_at(end_time)|as_timestamp - now()|as_timestamp) + 900)  / 1800) | round(0)  | int(0) %}
      {% if end_time > start_time and now() >= today_at(end_time) %} 
        {% set end_timestep = end_timestep +48 %}
      {% elif end_time < start_time %}
        {% set end_timestep = end_timestep +48 %}
      {% endif %}
      {{ max(total_hours*2,end_timestep) }}

I also included the total_hours of each deferrable in the sensor.

{{ max(total_hours*2,end_timestep) }}

This way the forecast will never become “infeasible” , but the end_timestep will be extended untill now() has passed end_time.
I will take a look at the change you proposed …

|round(0, "ceil", 0)

hey @davidusb, from 0/10, how dumb of an idea is this :rofl:

machine_learning_forecaster.py: (tune function)

def tune(self, debug: Optional[bool] = False) -> pd.DataFrame:
...
- lags_grid = [6, 12, 24, 36, 48, 60, 72]
+ lags_grid = [int((60/(self.data_exo.index.freq.delta.seconds/60))*24)]

This is not returning a list of values.
The goal of that list is to find the optimal value for num_lags