EMHASS add-on: An energy management optimization add-on for Home Assistant OS and supervised

Yes the only option besides the configuration file is as a list of values using the load_cost_forecast flag.
You should be able to build your list of values for peak start/end hours prices using template sensor.
Then pass that sensor data in the curl command.

@davidusb I still get this issue where after rebooting Home Assistant I canā€™t post naive-mpc-optim for two days. I just get a ā€œValueError: could not convert string to float: ā€˜NOTRUNā€™ā€ error.
After two days is just starts working again? I have to use day ahead for two days.

Schermafbeelding 2023-10-29 220000

I canā€™t save the configuration. Can anyone tell me why not.

Mislukt om add-onconfiguratie op te slaan: not a valid value. Got {'hass_url': 'empty', 'long_lived_token': 'empty', 'costfun': 'profit', 'logging_level': 'INFO', 'optimization_time_step': 30, 'historic_days_to_retrieve': 2, 'method_ts_round': 'nearest', 'set_total_pv_sell': False, 'lp_solver': 'COIN_CMD', 'lp_solver_path': '/usr/bin/cbc', 'set_nocharge_from_grid': False, 'set_nodischarge_to_grid': False, 'set_battery_dynamic': False, 'battery_dynamic_max': 0.9, 'battery_dynamic_min': -0.9, 'load_forecast_method': 'naive', 'sensor_power_photovoltaics': 'sen...
hass_url: empty
long_lived_token: empty
costfun: profit
logging_level: INFO
optimization_time_step: 30
historic_days_to_retrieve: 2
method_ts_round: nearest
set_total_pv_sell: false
lp_solver: COIN_CMD
lp_solver_path: /usr/bin/cbc
set_nocharge_from_grid: false
set_nodischarge_to_grid: false
set_battery_dynamic: false
battery_dynamic_max: 0.9
battery_dynamic_min: -0.9
load_forecast_method: naive
sensor_power_photovoltaics: sensor.total_production_power
sensor_power_load_no_var_loads: sensor.total_consumption_power
number_of_deferrable_loads: 2
list_nominal_power_of_deferrable_loads:
  - nominal_power_of_deferrable_loads: 3000
  - nominal_power_of_deferrable_loads: 750
list_operating_hours_of_each_deferrable_load:
  - operating_hours_of_each_deferrable_load: 5
  - operating_hours_of_each_deferrable_load: 8
list_peak_hours_periods_start_hours:
  - peak_hours_periods_start_hours: "05:54"
  - peak_hours_periods_start_hours: 624
list_peak_hours_periods_end_hours:
  - peak_hours_periods_end_hours: "09:24"
  - peak_hours_periods_end_hours: 714
list_treat_deferrable_load_as_semi_cont:
  - treat_deferrable_load_as_semi_cont: true
  - treat_deferrable_load_as_semi_cont: true
load_peak_hours_cost: 0.1907
load_offpeak_hours_cost: 0.1419
photovoltaic_production_sell_price: 0.065
maximum_power_from_grid: 9000
list_pv_module_model:
  - pv_module_model: CSUN_Eurasia_Energy_Systems_Industry_and_Trade_CSUN295_60M
list_pv_inverter_model:
  - pv_inverter_model: Fronius_International_GmbH__Fronius_Primo_5_0_1_208_240__240V_
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
set_use_battery: false
battery_discharge_power_max: 1000
battery_charge_power_max: 1000
battery_discharge_efficiency: 0.95
battery_charge_efficiency: 0.95
battery_nominal_energy_capacity: 5000
battery_minimum_state_of_charge: 0.3
battery_maximum_state_of_charge: 0.9
battery_target_state_of_charge: 0.6

Maybe itā€™s this?
My config:

list_peak_hours_periods_start_hours:
  - peak_hours_periods_start_hours: "10:00"
  - peak_hours_periods_start_hours: "10:00"
list_peak_hours_periods_end_hours:
  - peak_hours_periods_end_hours: "16: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: false

I donā€™t actually use the peak hours settings, but I think they are meant to be from hour to hour, so from 10am to 4pm. I think there are two because thatā€™s the default example and I havenā€™t changed it.

Also, the semi cont settings set to true means that the load is constant, canā€™t be varied. In my case the first is a pool pump with a constant 1300 W load and the second is an EV that can be charged from 1 amp to 32 amps and I translate the returned Wattage to amps to set the car.

Thanks a lot. I overlookt that format.

1 Like

Interestingly I still canā€™t run MPC after a reboot for two days? I have to revert to dayahead for two days and then I can go back to MPC method.

2023-11-11 09:17:51,187 - web_server - ERROR - Exception on /action/naive-mpc-optim [POST]
Traceback (most recent call last):
  File "/usr/local/lib/python3.9/dist-packages/flask/app.py", line 1455, in wsgi_app
    response = self.full_dispatch_request()
  File "/usr/local/lib/python3.9/dist-packages/flask/app.py", line 869, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/usr/local/lib/python3.9/dist-packages/flask/app.py", line 867, in full_dispatch_request
    rv = self.dispatch_request()
  File "/usr/local/lib/python3.9/dist-packages/flask/app.py", line 852, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
  File "/usr/local/lib/python3.9/dist-packages/emhass/web_server.py", line 179, in action_call
    input_data_dict = set_input_data_dict(config_path, str(data_path), costfun,
  File "/usr/local/lib/python3.9/dist-packages/emhass/command_line.py", line 110, in set_input_data_dict
    rh.get_data(days_list, var_list,
  File "/usr/local/lib/python3.9/dist-packages/emhass/retrieve_hass.py", line 140, in get_data
    df_tp = df_raw.copy()[['state']].replace(
  File "/usr/local/lib/python3.9/dist-packages/pandas/core/generic.py", line 5920, in astype
    new_data = self._mgr.astype(dtype=dtype, copy=copy, errors=errors)
  File "/usr/local/lib/python3.9/dist-packages/pandas/core/internals/managers.py", line 419, in astype
    return self.apply("astype", dtype=dtype, copy=copy, errors=errors)
  File "/usr/local/lib/python3.9/dist-packages/pandas/core/internals/managers.py", line 304, in apply
    applied = getattr(b, f)(**kwargs)
  File "/usr/local/lib/python3.9/dist-packages/pandas/core/internals/blocks.py", line 580, in astype
    new_values = astype_array_safe(values, dtype, copy=copy, errors=errors)
  File "/usr/local/lib/python3.9/dist-packages/pandas/core/dtypes/cast.py", line 1292, in astype_array_safe
    new_values = astype_array(values, dtype, copy=copy)
  File "/usr/local/lib/python3.9/dist-packages/pandas/core/dtypes/cast.py", line 1237, in astype_array
    values = astype_nansafe(values, dtype, copy=copy)
  File "/usr/local/lib/python3.9/dist-packages/pandas/core/dtypes/cast.py", line 1098, in astype_nansafe
    result = astype_nansafe(flat, dtype, copy=copy, skipna=skipna)
  File "/usr/local/lib/python3.9/dist-packages/pandas/core/dtypes/cast.py", line 1181, in astype_nansafe
    return arr.astype(dtype, copy=True)
ValueError: could not convert string to float: 'NOTRUN'
2023-11-11 09:17:51,216 - web_server - INFO - Setting up needed data
2023-11-11 09:17:51,217 - web_server - INFO -  >> Publishing data...
2023-11-11 09:17:51,217 - web_server - INFO - Publishing data to HASS instance
2023-11-11 09:17:51,237 - web_server - INFO - Successfully posted to sensor.p_pv_forecast = 0
2023-11-11 09:17:51,250 - web_server - INFO - Successfully posted to sensor.p_load_forecast = 0.0
2023-11-11 09:17:51,260 - web_server - INFO - Successfully posted to sensor.p_deferrable0 = 0.0
2023-11-11 09:17:51,270 - web_server - INFO - Successfully posted to sensor.p_deferrable1 = 0.0
2023-11-11 09:17:51,280 - web_server - INFO - Successfully posted to sensor.p_batt_forecast = 0.0
2023-11-11 09:17:51,290 - web_server - INFO - Successfully posted to sensor.soc_batt_forecast = 16.0
2023-11-11 09:17:51,299 - web_server - INFO - Successfully posted to sensor.p_grid_forecast = 0.0
2023-11-11 09:17:51,310 - web_server - INFO - Successfully posted to sensor.total_cost_fun_value = 3.79
2023-11-11 09:17:51,320 - web_server - INFO - Successfully posted to sensor.unit_load_cost = 0.06
2023-11-11 09:17:51,332 - web_server - INFO - Successfully posted to sensor.unit_prod_price = -0.03

Thatā€™s strange.
I rebooted quite often in the last few days and Iā€™ve not experienced that.
I guess you already tried to uninstall, clean the share folder and install back

Iā€™m on the latest version of the add-on

No, not recently. Did do a reinstall some time ago. I suspect itā€™s got something to do with the complexity of other configuration. My home assistant must be 6 years old now and so many projects and experiments carried out Iā€™m thinking I should set up a separate VM running HA just for energy management as itā€™s more production like than many other thinks going on in my old HA.

Hello @davidusb
Because of my setup (two batteries running in parallel) I would need to install two instances of the add-on, so each can control a specific battery. I can split loads and all the rest so it would work from that side. At the moment Iā€™m managing everything with just one instance but having two would allow me to optimize everything.
I could already make HAOS recognize the addon twice and I also checked I can choose a different port for the EMHASS server.
My last concern is about the published results; as they have the same name one instance would probably overwrite the results from the other when pushing to HA.
Is there any possibility to customize the name of the published values (maybe a feature request/enhancement)? Or to push them to specific sensors I can create for this purpose?
I would even be open to options such as forking the github project and make some changes to the code, use it as repository for the second instance of the add-on, as a short term workaround.
Please let me know your thoughts and thank you!

EDIT:
Never mind. While having a look at your code I found a feature request and then back to the documentation. Publish_prefix will do the work. Thanks!

I have three batteries, but just treat them as one virtual battery with the combined energy and power capacities, which seems to work well in EMHASS.

What other functionality are you seeking, would you like to charge/ discarge them at different rates?

1 Like

I am as well at the moment.
My use case is the following one: I use EMHASS to charge at night when the next day PV is not enough to cover my consumption. The discharge is managed by the batteries themselves.

The problem is the different capacities, inverter power and the overall absorption limit.
For example often one battery if fully discharged while the other is not and the average overall SOC left is the average of that.
I was thinking that if I can split the management of the batteries I can probably optimise the charge levels and have less headaches when it comes to dynamically change the charge power (depending on the load) keeping everything into considerations and under control.

FYI Iā€™ve just discovered that the possibility to customise the name of the published values is already implemented:

curl -i -H "Content-Type: application/json" -X POST -d '{"publish_prefix": "prefix_here_"}' http://localhost:5000/action/publish-data

Woke up to this this morning?

{
ā€œ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
}},
ā€œ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
}},
ā€œ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
}},
ā€œ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 is_state(ā€˜sensor.openweathermap_forecast_conditionā€™, [ā€˜rainyā€™, ā€˜cloudyā€™]) -%}
0
{%- elif is_state(ā€˜sensor.seasonā€™, ā€˜winterā€™) -%}
2
{%- elif is_state(ā€˜sensor.seasonā€™, ā€˜summerā€™) -%}
4
{%- else -%}
3
{%- endif -%},
{%- if is_state(ā€˜device_tracker.ynot_location_trackerā€™, [ā€˜homeā€™]) -%}
{%- if is_state(ā€˜cover.ynot_charger_doorā€™, [ā€˜openā€™]) -%}
{{ ((90-(states(ā€˜sensor.ynot_batteryā€™)|int(0)))/30*3)|int(0) }}
{%- else -%}
0
{%- endif -%}
{%- else -%}
0
{%- endif -%}
],
ā€œP_deferrable_nomā€: [1300, 7360],
ā€œ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.13,
ā€œalphaā€: 0,
ā€œbetaā€: 1
})

Output:

{
ā€œprod_price_forecastā€: [0.0, 0.01, 0.01, -0.01, -0.04, -0.07, -0.07, -0.07, -0.07, -0.07, -0.07, -0.06, -0.06, 0.25, 0.28, 0.28, 0.28, 0.28, 0.27, 0.29, 0.32, 0.32, 0.32, 0.33, 0.33, 0.09, 0.1, 0.1, 0.1, 0.09, 0.06, 0.06, 0.06, 0.06, 0.06, 0.07, 0.06, 0.06, 0.06, 0.06, 0.06, 0.06, 0.06],
ā€œload_cost_forecastā€: [0.09, 0.1, 0.1, 0.08, 0.05, 0.04, 0.04, 0.04, 0.04, 0.04, 0.04, 0.04, 0.04, 0.33, 0.36, 0.37, 0.36, 0.36, 0.35, 0.38, 0.4, 0.41, 0.41, 0.42, 0.42, 0.2, 0.2, 0.2, 0.2, 0.19, 0.16, 0.16, 0.16, 0.16, 0.16, 0.16, 0.16, 0.16, 0.16, 0.16, 0.16, 0.16, 0.16],
ā€œpv_power_forecastā€: [325, 1581, 2100, 2558, 2966, 3307, 3497, 3624, 3799, 3886, 3784, 3681, 3545, 3283, 2963, 2664, 2384, 2080, 1752, 1417, 1047, 672, 311, 82, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 77, 187, 570, 1024, 1498, 1970, 2366, 2740, 3036, 3222, 3360, 3492, 3564, 3586, 3580, 3516, 3410, 3321, 3197, 2945, 2593, 2187, 1671, 1192, 743, 340, 86, 5, 0, 0, 0, 0, 0, 0, 0, 0],
ā€œprediction_horizonā€: 43,
ā€œnum_def_loadsā€: 2,
ā€œdef_total_hoursā€: [0,0],
ā€œP_deferrable_nomā€: [1300, 7360],
ā€œtreat_def_as_semi_contā€: [1, 0],
ā€œset_def_constantā€: [0, 0],
ā€œsoc_initā€: 0.04,
ā€œsoc_finalā€: 0.13,
ā€œalphaā€: 0,
ā€œbetaā€: 1
})

Logs:

2023-11-26 07:30:52,178 - web_server - INFO - Setting up needed data
2023-11-26 07:30:52,181 - web_server - INFO - Retrieve hass get data method initiatedā€¦
2023-11-26 07:30:54,733 - web_server - INFO - Retrieving weather forecast data using method = list
2023-11-26 07:30:54,734 - web_server - INFO - Retrieving data from hass for load forecast using method = naive
2023-11-26 07:30:54,735 - web_server - INFO - Retrieve hass get data method initiatedā€¦
2023-11-26 07:30:59,737 - web_server - INFO - >> Performing naive MPC optimizationā€¦
2023-11-26 07:30:59,737 - web_server - INFO - Performing naive MPC optimization
2023-11-26 07:30:59,748 - web_server - INFO - Perform an iteration of a naive MPC controller
2023-11-26 07:30:59,851 - web_server - INFO - Status: Infeasible
2023-11-26 07:30:59,852 - web_server - INFO - Total value of the Cost function = 14.77
2023-11-26 07:31:00,171 - web_server - INFO - Setting up needed data
2023-11-26 07:31:00,174 - web_server - INFO - >> Publishing dataā€¦
2023-11-26 07:31:00,174 - web_server - INFO - Publishing data to HASS instance
2023-11-26 07:31:00,189 - web_server - INFO - Successfully posted to sensor.p_pv_forecast = 384.76
2023-11-26 07:31:00,200 - web_server - INFO - Successfully posted to sensor.p_load_forecast = 409.16
2023-11-26 07:31:00,210 - web_server - INFO - Successfully posted to sensor.p_deferrable0 = 14465.6
2023-11-26 07:31:00,222 - web_server - INFO - Successfully posted to sensor.p_deferrable1 = 0.0
2023-11-26 07:31:00,232 - web_server - INFO - Successfully posted to sensor.p_batt_forecast = 0.0
2023-11-26 07:31:00,242 - web_server - INFO - Successfully posted to sensor.soc_batt_forecast = 4.0
2023-11-26 07:31:00,252 - web_server - INFO - Successfully posted to sensor.p_grid_forecast = 14490.0
2023-11-26 07:31:00,261 - web_server - INFO - Successfully posted to sensor.total_cost_fun_value = 14.77
2023-11-26 07:31:00,271 - web_server - INFO - Successfully posted to sensor.unit_load_cost = 0.1
2023-11-26 07:31:00,281 - web_server - INFO - Successfully posted to sensor.unit_prod_price = 0.01

For the records, if anybody tries to install the add-on twice on the same HA OS instance: itā€™s possible to do that but itā€™s not possible to run them in parallel. The reason I found is that both instances would refer to the same resources and config files, thus resulting in conflicts when performing the optimizations and publishing the data (basically you do not know which parameters are being used for the optimizations and both instances would publish the same data).

Thatā€™s a pity as it would have been a nice workaround to separately mange different batteries or portions of the system but I understand itā€™s a really specific use-case.

What is your install? Are you on HA OS or supervised?
This can totally be possible using the docker container installation method for EMHASS and not the HAOS add-on.

Iā€™m using the add-on under HA OS.

Edit: I clarified that in my message for future readers. Thanks for pointing out the difference.

Hello @davidusb
I fear there are some issues with the latest update of the add-on:
This is what I get after updating:

s6-rc: info: service s6rc-oneshot-runner: starting
s6-rc: info: service s6rc-oneshot-runner successfully started
s6-rc: info: service fix-attrs: starting
s6-rc: info: service fix-attrs successfully started
s6-rc: info: service legacy-cont-init: starting
s6-rc: info: service legacy-cont-init successfully started
s6-rc: info: service legacy-services: starting
services-up: info: copying legacy longrun emhass (no readiness notification)
s6-rc: info: service legacy-services successfully started
Traceback (most recent call last):
  File "/usr/lib/python3.9/runpy.py", line 197, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/usr/lib/python3.9/runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "/usr/local/lib/python3.9/dist-packages/emhass/web_server.py", line 15, in <module>
    from emhass.command_line import set_input_data_dict
  File "/usr/local/lib/python3.9/dist-packages/emhass/command_line.py", line 20, in <module>
    from emhass.forecast import forecast
  File "/usr/local/lib/python3.9/dist-packages/emhass/forecast.py", line 15, in <module>
    import pvlib
  File "/usr/local/lib/python3.9/dist-packages/pvlib/__init__.py", line 3, in <module>
    from pvlib import (  # noqa: F401
  File "/usr/local/lib/python3.9/dist-packages/pvlib/clearsky.py", line 14, in <module>
    import h5py
  File "/usr/lib/python3/dist-packages/h5py/__init__.py", line 21, in <module>
    from . import _debian_h5py_serial as _h5py
  File "/usr/lib/python3/dist-packages/h5py/_debian_h5py_serial/__init__.py", line 46, in <module>
    from ._conv import register_converters as _register_converters
  File "h5py/_debian_h5py_serial/_conv.pyx", line 1, in init h5py._debian_h5py_serial._conv
  File "h5py/_debian_h5py_serial/h5t.pyx", line 293, in init h5py._debian_h5py_serial.h5t
  File "/usr/local/lib/python3.9/dist-packages/numpy/__init__.py", line 333, in __getattr__
    raise AttributeError("module {!r} has no attribute "
AttributeError: module 'numpy' has no attribute 'typeDict'
s6-supervise emhass: warning: unable to spawn ./finish: Permission denied

And I canā€™t open the webserver interface (not ready).
Iā€™m now rolling back to the previous version.

After following this thread for months Iā€™ve finally set this up and have had good success over the last few days. Iā€™d like to thank @davidusb and other contributors to the project and in the thread.

One scenario that Iā€™m not sure if Iā€™m able to model in emhass is the following:

  1. I run a dayahead-optim each evening.
  2. I have a deferrable load (hot water heater) that should heat water for 5 hours over a 24h period.
  3. However, it is not appropriate to run the heater during a period of 5 consecutive hours. This is because there is a maximum safe temperature to heat the water to (similar to a battery that can not charge past 100% SOC). So after approximately 2h the thermostat switches off and while the deferrable load is ā€œonā€ (from emhassā€™s point of view) there is no heating taking place.

Now, some days this works out alright because the least costly hours are spread throughout the day. But depending on energy prices the load may be scheduled in a consecutive 5h block when in reality it will need to come on again later in the day (and that would ideally happen in the hours when energy is least expensive, e.g. early afternoon). This is what I mean, visually:

Can this be achieved using MPC, perhaps by passing def_total_hours that only decreases by the time when the heater was actually drawing power? If not, what is the recommended approach?

You could use MPC to pass a reducing number of hours, which I was doing for a while.

Now I just overschedule the hot water system for additional hours (6 hours instead of 4 hours) and as you say the thermostat will kick in and switch off the heater and then turn back on when/ if the lower set point is reached.

1 Like

@davidusb
Just installed the EMHASS Add-On and was struckling to get it running at allā€¦ I found the error after scrolling this thread: Default config isnā€™t still in the right format.

Can someone fix this. Would make it easier to getting stated :smiley: