EMHASS: An Energy Management for Home Assistant

PV curtailement is the amount of Watts that substracted to the current PV production. The amount of MPPT degradation Watts. It should be good as a last resort solution for situations where for example there is a lot of PV power but batteries are 100% full and prod prices are negative (an actual use case in some countries it seems).

But those results that you show are strange, where you on this condition: SOC=SOCmax and prod prices negative?. We have identified a missing limitation on PV curtailment in the code, it should not go beyond the PV produced power value. This will be fixed soon.

Not on purpose, thatā€™s just a horrible miss. It will be added in the following patched version

Thanks for the prompt reply David.
Thatā€™s the reason I didnā€™t fully understand the results as the SOC is not 100% (I run MPC every 30ā€™ with a 24h rolling window, SOC initial = current, SOC final = 30% not really interested in final value as I prioritize charging my battery and when itā€™s full I let it manage the extra PV power).

Iā€™m pasting here the MPC results from today, 10:30 AM CEST, maybe helpful (no negative prod prices).
Thanks!

index P_PV P_Load P_deferrable0 P_grid_pos P_grid_neg P_grid P_batt SOC_opt P_PV_curtailment unit_load_cost unit_prod_price cost_profit cost_fun_selfcons
2024-06-03 10:30:00+02:00 2616 723 0 0 -2616 -2616 723 0.884 0 0.126 0.118 0.154 0.154
2024-06-03 11:00:00+02:00 2305 709 0 0 -2305 -2305 709 0.870 0 0.126 0.109 0.126 0.126
2024-06-03 11:30:00+02:00 2406 326 0 0 -2406 -2406 326 0.864 0 0.126 0.109 0.132 0.132
2024-06-03 12:00:00+02:00 2508 2005 0 0 -2508 -2508 2005 0.825 0 0.126 0.094 0.117 0.117
2024-06-03 12:30:00+02:00 2631 1586 0 0 -2631 -2631 1586 0.794 0 0.126 0.094 0.123 0.123
2024-06-03 13:00:00+02:00 2754 1566 0 0 -2754 -2754 1566 0.763 0 0.126 0.095 0.130 0.130
2024-06-03 13:30:00+02:00 3012 986 0 0 -3012 -3012 986 0.744 0 0.126 0.095 0.143 0.143
2024-06-03 14:00:00+02:00 3271 360 0 0 -3271 -3271 360 0.737 0 0.126 0.102 0.167 0.167
2024-06-03 14:30:00+02:00 3330 350 0 0 -3330 -3330 350 0.730 0 0.126 0.102 0.170 0.170
2024-06-03 15:00:00+02:00 3389 353 0 0 -3389 -3389 353 0.723 0 0.126 0.112 0.189 0.189
2024-06-03 15:30:00+02:00 2898 268 0 0 -2898 -2898 268 0.718 0 0.126 0.112 0.162 0.162
2024-06-03 16:00:00+02:00 2406 312 0 0 -2406 -2406 312 0.712 0 0.126 0.117 0.140 0.140
2024-06-03 16:30:00+02:00 2063 975 0 0 -2063 -2063 975 0.693 0 0.126 0.117 0.120 0.120
2024-06-03 17:00:00+02:00 1720 1233 0 0 -1720 -1720 1233 0.669 0 0.126 0.113 0.097 0.097
2024-06-03 17:30:00+02:00 1449 483 0 0 -1449 -1449 483 0.659 0 0.126 0.113 0.082 0.082
2024-06-03 18:00:00+02:00 1178 180 0 0 -1178 -1178 180 0.656 0 0.126 0.122 0.072 0.072
2024-06-03 18:30:00+02:00 920 1005 0 0 -920 -920 1005 0.636 0 0.126 0.122 0.056 0.056
2024-06-03 19:00:00+02:00 661 1586 0 0 -661 -661 1586 0.605 0 0.132 0.148 0.049 0.049
2024-06-03 19:30:00+02:00 449 685 0 0 -449 -449 685 0.592 0 0.132 0.148 0.033 0.033
2024-06-03 20:00:00+02:00 237 248 0 0 -237 -237 5690 0.481 5441 0.132 0.158 0.019 0.019
2024-06-03 20:30:00+02:00 148 967 0 0 -148 -148 967 0.462 0 0.132 0.158 0.012 0.012
2024-06-03 21:00:00+02:00 59 584 0 0 -59 -59 584 0.451 0 0.132 0.156 0.005 0.005
2024-06-03 21:30:00+02:00 30 161 0 0 -30 -30 161 0.448 0 0.132 0.156 0.002 0.002
2024-06-03 22:00:00+02:00 0 277 0 0 0 0 277 0.442 0 0.132 0.126 -0.000 -0.000
2024-06-03 22:30:00+02:00 0 273 0 0 0 0 273 0.437 0 0.132 0.126 -0.000 -0.000
2024-06-03 23:00:00+02:00 0 147 0 0 0 0 147 0.434 0 0.113 0.121 -0.000 -0.000
2024-06-03 23:30:00+02:00 0 176 0 0 0 0 176 0.431 0 0.113 0.121 -0.000 -0.000
2024-06-04 00:00:00+02:00 0 173 0 0 0 0 173 0.427 0 0.113 0.096 -0.000 -0.000
2024-06-04 00:30:00+02:00 0 194 0 0 0 0 194 0.423 0 0.113 0.096 -0.000 -0.000
2024-06-04 01:00:00+02:00 0 237 0 0 0 0 237 0.419 0 0.113 0.096 -0.000 -0.000
2024-06-04 01:30:00+02:00 0 149 0 0 0 0 149 0.416 0 0.113 0.096 -0.000 -0.000
2024-06-04 02:00:00+02:00 0 143 0 0 0 0 143 0.413 0 0.113 0.096 -0.000 -0.000
2024-06-04 02:30:00+02:00 0 116 0 0 0 0 116 0.411 0 0.113 0.096 -0.000 -0.000
2024-06-04 03:00:00+02:00 0 152 0 0 0 0 152 0.408 0 0.113 0.094 -0.000 -0.000
2024-06-04 03:30:00+02:00 0 224 0 0 0 0 224 0.403 0 0.113 0.094 -0.000 -0.000
2024-06-04 04:00:00+02:00 0 156 0 0 0 0 156 0.400 0 0.113 0.092 -0.000 -0.000
2024-06-04 04:30:00+02:00 0 138 0 0 0 0 138 0.398 0 0.113 0.092 -0.000 -0.000
2024-06-04 05:00:00+02:00 0 121 0 0 0 0 121 0.395 0 0.113 0.104 -0.000 -0.000
2024-06-04 05:30:00+02:00 224 98 0 0 -224 -224 98 0.393 0 0.113 0.104 0.012 0.012
2024-06-04 06:00:00+02:00 449 151 0 0 -449 -449 151 0.390 0 0.113 0.125 0.028 0.028
2024-06-04 06:30:00+02:00 830 160 0 0 -830 -830 160 0.387 0 0.113 0.125 0.052 0.052
2024-06-04 07:00:00+02:00 1212 201 0 0 -1212 -1212 201 0.383 0 0.132 0.148 0.090 0.090
2024-06-04 07:30:00+02:00 1699 491 0 0 -1699 -1699 491 0.374 0 0.132 0.148 0.126 0.126
2024-06-04 08:00:00+02:00 2186 155 0 0 -2186 -2186 155 0.371 0 0.126 0.150 0.164 0.164
2024-06-04 08:30:00+02:00 2728 581 0 0 -2728 -2728 581 0.359 0 0.126 0.150 0.205 0.205
2024-06-04 09:00:00+02:00 3271 1311 0 0 -3271 -3271 1311 0.334 0 0.126 0.135 0.220 0.220
2024-06-04 09:30:00+02:00 3851 841 0 0 -3851 -3851 841 0.318 0 0.126 0.135 0.259 0.259
2024-06-04 10:00:00+02:00 4431 899 0 0 -4431 -4431 899 0.300 0 0.126 0.118 0.261 0.261

Summary opt. results:

Variable Value
cost_profit 3.717
cost_fun_selfcons 3.717
optim_status Optimal

Edit:
I have the feeling that the system is using curtailment to make the analysis ā€œoptimalā€. In fact previously the elaboration was infeasible as final SOC could not be 30% with summer high PV production and low loads.

What would be a breaking change?
I get following error when executing perfect optim in developer tools.

Timed out running command: `curl -i -H "Content-Type:application/json" -X POST -d '{}' http://localhost:5000/action/perfect-optim`, after: 60 second

It does seem to work when I push the button on the webpage.

Why would this be better than using the Solcast HA integration and call it at most 10 times a day? In all subsequent MPC calls you can just use the integration-values, so I donā€™t see the benefit to put this into EMHASS?

One thing I noticed was the new config variable:
inverter_is_hybrid: False
It took a while before this error is shown in the log, but once added it worked.
Overall moving to 0.10 was very smooth, very good work guys!

Unfortunately the original owner of the Solcast integration has deleted there GitHub repository. I believe it has been said they deleted it from peopleā€™s abuse.

The old Solcast Integration has been deleted by the owner.
It has been picked up by someone else here GitHub - BJReplay/ha-solcast-solar: Solcast Integration for Home Assistant

I would switch off using load_start/end as 96 is outside your prediction horizon. Start without those parameters.

Happy to develop the use case, but unsure how to start on this journey.

Just an update for the Solcast, a Solcast caching PR should be up in the next few days.

2 Likes

Start by defining what will be the input variables and what will be the output variable that you want to predict.

From that continue by building a CSV file with the needed training data.

Example here: https://emhass.readthedocs.io/en/latest/mlregressor.html#storing-csv-files

Then use the MLRegressor class

1 Like

But you have to have something, If i try to remove that load start and end i get an error stating the config isnt valid

My MPC Call

service: shell_command.post_mpc_optim_solcast
data:
  command: >
    curl -i -H "Content-Type: application/json" -X POST -d '{
      "num_def_loads": 1,
      "load_cost_forecast": {{ ( [states('sensor.general_price')|float(0)] + state_attr('sensor.general_forecast', 'forecasts')|map(attribute='per_kwh')|list )|tojson }},
      "prod_price_forecast": {{ ( [states('sensor.feed_in_price')|float(0)] + state_attr('sensor.feed_in_forecast', 'forecasts')|map(attribute='per_kwh')|list )|tojson }},
      "pv_power_forecast": {{ (state_attr('sensor.solcast_24hrs_forecast', 'forecasts')|map(attribute='pv_estimate')|list)|tojson }},
      "prediction_horizon": 24,
      "soc_init": {{ (states('sensor.powerwall_charge')|float(0)) / 100 }},
      "soc_final": 0.20,
      "def_total_hours": [24]
    }' http://localhost:5000/action/naive-mpc-optim

and my config

logging_level: DEBUG
data_path: default
costfun: profit
sensor_power_photovoltaics: sensor.solar_energy_v2
sensor_power_load_no_var_loads: sensor.home_load_power
set_total_pv_sell: false
set_nocharge_from_grid: false
set_nodischarge_to_grid: false
maximum_power_from_grid: 35000
maximum_power_to_grid: 15000
number_of_deferrable_loads: 1
list_nominal_power_of_deferrable_loads:
  - nominal_power_of_deferrable_loads: 1800
list_operating_hours_of_each_deferrable_load:
  - operating_hours_of_each_deferrable_load: 24
list_start_timesteps_of_each_deferrable_load:
  - start_timesteps_of_each_deferrable_load: 0
list_end_timesteps_of_each_deferrable_load:
  - end_timesteps_of_each_deferrable_load: 96
list_peak_hours_periods_start_hours:
  - peak_hours_periods_start_hours: "16:54"
list_peak_hours_periods_end_hours:
  - peak_hours_periods_end_hours: "20:54"
list_treat_deferrable_load_as_semi_cont:
  - treat_deferrable_load_as_semi_cont: true
list_set_deferrable_load_single_constant:
  - set_deferrable_load_single_constant: false
load_peak_hours_cost: 0
load_offpeak_hours_cost: 0
photovoltaic_production_sell_price: 0
list_pv_module_model: []
list_pv_inverter_model: []
list_surface_tilt: []
list_surface_azimuth: []
list_modules_per_string: []
list_strings_per_inverter: []
set_use_battery: true
battery_nominal_energy_capacity: 13500
list_priority_of_deferrable_loads:
  - priority_of_deferrable_loads: 1
optimization_time_step: 15
battery_maximum_state_of_charge: 1
battery_target_state_of_charge: 0.5
battery_discharge_efficiency: 0.9
battery_charge_efficiency: 0.9
battery_charge_power_max: 5000
battery_discharge_power_max: 5000
set_battery_dynamic: true
battery_dynamic_max: 0.7
battery_dynamic_min: -0.7
battery_minimum_state_of_charge: 0.2
solcast_rooftop_id: ed9c-b64a-325c-2108
solcast_api_key: XXXX
production_price_forecast_method: constant
load_forecast_method: naive
weather_forecast_method: solcast
prediction_horizon: 24
method_ts_round: first

If you set to 0 then that switches off that capability.

Is there a way to turn off the pv_curtailement?
I donā€™t get money for solar energy, but I also donā€™t have to pay to put it on the grid.
I always set negative solar prices to force emhass to self-consume as much as possible.

Now I also see strange values in my deferrable loads
deferrable 2 has a nominal value of 1700
deferrable 3 has a nominal value of 900

This is my config

logging_level: INFO
data_path: /share/
costfun: profit
sensor_power_photovoltaics: sensor.emhass_huidige_opbrengst
sensor_power_load_no_var_loads: sensor.huidig_verbruik_zonder_wp
set_total_pv_sell: false
set_nocharge_from_grid: false
set_nodischarge_to_grid: false
maximum_power_from_grid: 14000
maximum_power_to_grid: 9000
number_of_deferrable_loads: 5
list_nominal_power_of_deferrable_loads:
  - nominal_power_of_deferrable_loads: 2000
  - nominal_power_of_deferrable_loads: 2000
  - nominal_power_of_deferrable_loads: 1700
  - nominal_power_of_deferrable_loads: 900
  - nominal_power_of_deferrable_loads: 2000
list_operating_hours_of_each_deferrable_load:
  - operating_hours_of_each_deferrable_load: 2
  - operating_hours_of_each_deferrable_load: 2
  - operating_hours_of_each_deferrable_load: 2.5
  - operating_hours_of_each_deferrable_load: 1
  - operating_hours_of_each_deferrable_load: 0
list_start_timesteps_of_each_deferrable_load:
  - start_timesteps_of_each_deferrable_load: 0
  - start_timesteps_of_each_deferrable_load: 0
  - start_timesteps_of_each_deferrable_load: 0
  - start_timesteps_of_each_deferrable_load: 0
  - start_timesteps_of_each_deferrable_load: 0
list_end_timesteps_of_each_deferrable_load:
  - end_timesteps_of_each_deferrable_load: 0
  - end_timesteps_of_each_deferrable_load: 0
  - end_timesteps_of_each_deferrable_load: 0
  - end_timesteps_of_each_deferrable_load: 0
  - end_timesteps_of_each_deferrable_load: 0
list_peak_hours_periods_start_hours:
  - peak_hours_periods_start_hours: "7:00"
  - peak_hours_periods_start_hours: "9:00"
  - peak_hours_periods_start_hours: "11:00"
  - peak_hours_periods_start_hours: "13:00"
  - peak_hours_periods_start_hours: "15:00"
list_peak_hours_periods_end_hours:
  - peak_hours_periods_end_hours: "8:00"
  - peak_hours_periods_end_hours: "10:00"
  - peak_hours_periods_end_hours: "12:00"
  - peak_hours_periods_end_hours: "14:00"
  - peak_hours_periods_end_hours: "16:00"
list_treat_deferrable_load_as_semi_cont:
  - treat_deferrable_load_as_semi_cont: true
  - treat_deferrable_load_as_semi_cont: true
  - treat_deferrable_load_as_semi_cont: true
  - treat_deferrable_load_as_semi_cont: true
  - treat_deferrable_load_as_semi_cont: true
list_set_deferrable_load_single_constant:
  - set_deferrable_load_single_constant: true
  - set_deferrable_load_single_constant: true
  - set_deferrable_load_single_constant: true
  - set_deferrable_load_single_constant: false
  - set_deferrable_load_single_constant: false
load_peak_hours_cost: 0.164497535
load_offpeak_hours_cost: 0.164497535
photovoltaic_production_sell_price: 0
list_pv_module_model:
  - pv_module_model: CSUN_Eurasia_Energy_Systems_Industry_and_Trade_CSUN295_60M
list_pv_inverter_model:
  - pv_inverter_model: SolarEdge_Technologies_Ltd___SE3000__240V_
  - pv_inverter_model: Chint_Solar_Zhejiang__CHPI4KTL_US__240V_
list_surface_tilt:
  - surface_tilt: 30
list_surface_azimuth:
  - surface_azimuth: 205
list_modules_per_string:
  - modules_per_string: 16
list_strings_per_inverter:
  - strings_per_inverter: 1
inverter_is_hybrid: false
set_use_battery: false
battery_nominal_energy_capacity: 5000
hass_url: empty
long_lived_token: empty
optimization_time_step: 30
historic_days_to_retrieve: 3
method_ts_round: first
lp_solver: COIN_CMD
lp_solver_path: /usr/bin/cbc
set_battery_dynamic: false
battery_dynamic_max: 0.9
battery_dynamic_min: -0.9
load_forecast_method: mlforecaster
battery_discharge_power_max: 1000
battery_charge_power_max: 1000
battery_discharge_efficiency: 0.95
battery_charge_efficiency: 0.95
battery_minimum_state_of_charge: 0.3
battery_maximum_state_of_charge: 0.9
battery_target_state_of_charge: 0.6

mpc rest command

naive_mpc_optim:
  url: http://localhost:5003/action/naive-mpc-optim
  method: POST
  content_type: "application/json"
  payload: >-
    {
      "prod_price_forecast": {{
        ([state_attr('sensor.prod_price_forecast_list', 'extra') | float] +
        state_attr('sensor.prod_price_forecast_list', 'list')[:states('sensor.prediction_horizon') | int - 1]) | tojson
        }},
      "load_cost_forecast": {{
        (state_attr('sensor.cost_forecast_list', 'list')[:states('sensor.prediction_horizon') | int]) | tojson
        }},
      "pv_power_forecast": {{
        ([states('sensor.huidige_opbrengst')|int(0)] +
        state_attr('sensor.pv_power_forecast_list', 'list')[:states('sensor.prediction_horizon') | int - 1]) | tojson
        }},
      "prediction_horizon": {{ states('sensor.prediction_horizon') | int }},
      "alpha": 1,
      "beta": 0,
      "num_def_loads": 5,
      "p_deferrable_nom": [2000, 2000, 1700, 900, 2000],
      "def_total_hours": [
        {{  states('sensor.wasmachien_uren') | float(0) }},
        {{  states('sensor.droogkast_uren') | float(0) }},
        {{  states('sensor.afwasmachien_uren') | float(0) }},
        {{  states('sensor.warmtepompboiler_uren') | float(0) }},
        {{  states('sensor.warmtepomp_uren') | float(0) }}],
      "set_def_constant":[true, true, true, false, false],
      "def_end_timestep": [
        {{  states('sensor.wasmachien_end_timesteps') | int(0) }},
        {{  states('sensor.droogkast_end_timesteps') | int(0) }},
        {{  states('sensor.afwasmachien_end_timesteps') | int(0) }},
        {{  states('sensor.warmtepompboiler_end_timesteps') | int(0) }},
        {{  states('sensor.warmtepomp_end_timesteps') | int(0) }}],
      "def_current_state": [
        {{  "true" if is_state('input_boolean.emhass_wasmachien', "on") else "false" }}, 
        {{  "true" if is_state('input_boolean.emhass_droogkast', "on") else "false" }}, 
        {{  "true" if is_state('input_boolean.emhass_afwasmachien', "on") else "false" }}, 
        {{  "true" if is_state('input_boolean.emhass_warmtepompboiler', "on") else "false" }}, 
        {{  "true" if is_state('input_boolean.emhass_warmtepomp', "on") else "false" }}]
    }
naive_mpc_optim:
  url: http://localhost:5003/action/naive-mpc-optim
  method: POST
  content_type: "application/json"
  payload: >-
    {
      "prod_price_forecast": [-0.11, -0.11, -0.11, -0.11, -0.11, -0.11, -0.11, -0.11, -0.11, -0.11, -0.11, -0.11, -0.11, -0.11, -0.11, -0.11, -0.11, -0.11, -0.11, -0.11, -0.11, -0.11, -0.11, -0.11, -0.11, -0.11, -0.11, -0.11, -0.11, -0.11],
      "load_cost_forecast": [0.174497535, 0.164497535, 0.164497535, 0.164497535, 0.164497535, 0.164497535, 0.164497535, 0.164497535, 0.164497535, 0.164497535, 0.164497535, 0.164497535, 0.164497535, 0.174497535, 0.174497535, 0.174497535, 0.174497535, 0.174497535, 0.174497535, 0.164497535, 0.164497535, 0.164497535, 0.164497535, 0.164497535, 0.164497535, 0.164497535, 0.164497535, 0.164497535, 0.164497535, 0.164497535],
      "pv_power_forecast": [1715, 3041, 3531, 3860, 3554, 2886, 2312, 1784, 1656, 1556, 1558, 1675, 1762, 1878, 1774, 1485, 1430, 1549, 1595, 1449, 1461, 1018, 576, 156, 88, 7, 0, 0, 0, 0],
      "prediction_horizon": 30,
      "alpha": 1,
      "beta": 0,
      "num_def_loads": 5,
      "p_deferrable_nom": [2000, 2000, 1700, 900, 2000],
      "def_total_hours": [
        0.2,
        0.0,
        2.1,
        2.1,
        0.0],
      "set_def_constant":[true, true, true, false, false],
      "def_end_timestep": [
        0,
        0,
        9,
        4,
        30],
      "def_current_state": [
        true, 
        false, 
        true, 
        false, 
        false]
    }

Also my deferrable2 gets planned in again while it is started 1 hour ago(def_current_state = true), it even gets splitted(set_def_constant = true)

Thanks for this - couldnā€™t see it in the doco yet.

image
/edit - had to change to >100w as the sensor is detecting some residual power
image

Hello, i am interested in your EMHASS, but we have specific aplication, is it possible to contact you directly, i would like to support the project, if it can do the our issue.
Thank you

For the moment Iā€™m back to v. 0.9.1 and the computation is infeasible again, without curtailment and high PV production and low loads.

A common use case is; I donā€™t want to discharge my battery unless the price spread is greater than a certain value. This is useful to account for the capital cost of the battery over its lifetime time.

For example I might not want to discharge my battery unless the price spread is greater than $0.15 / kWh.

I set this up using the weight_battery_discharge (set to 0.15) and weight_battery_charge (set to 0) configuration parameters.

This results in my overnight usage like the following:

2000-2100 high costs (0.37-0.39) battery is discharged to house load (red)
2100-0400 medium costs (0.15-0.27) battery saved for higher prices and grid is consumed (yellow)
0400-0430 lowest cost (0.14) battery is charged from the grid (green)
0700-0800 highest export price (0.30 - which is > 0.15+ lowest cost) battery is discharged to grid

I have also switched to this new maintained repository.
My actual feeling: It is now running much more reliable then before ā€¦

You can also create an solcast REST sensor:

#rest:
#  - resource: https://api.solcast.com.au/rooftop_sites/***YOUR-Side-ID***/forecasts?format=json&api_key=***Your-API-Key***&hours=120
#    scan_interval: '01:30:00'
#    sensor:
#      - name: "Solcast Forecast Data"
#        unique_id: solcast_pv_estimate
#        force_update: true
#        value_template: "{{ value_json.forecasts[0].pv_estimate|round(1) }}"
#        unit_of_measurement: kW
#        device_class: power
#        state_class: measurement
#        json_attributes:
#        - forecasts
#        icon: mdi:solar-power
#    
#      - name: "Solcast Forecast 10"
#        unique_id: solcast_pv_estimate10
#        force_update: true
#        value_template: "{{ value_json.forecasts[0].pv_estimate10|round(1) }}"
#        unit_of_measurement: kW
#        device_class: power
#        state_class: measurement
#        icon: mdi:solar-power
#        
#      - name: "Solcast Forecast 90"
#        unique_id: solcast_pv_estimate90
#        force_update: true
#        value_template: "{{ value_json.forecasts[0].pv_estimate90|round(1) }}"
#        unit_of_measurement: kW
#        device_class: power
#        state_class: measurement
#        icon: mdi:solar-power
1 Like