EMHASS: An Energy Management for Home Assistant

There is not a direct penalty to account for battery degradation in the objective function during the optimization.
However I can see two options that can be used directly. The first is to set your own values for SOC min and max. This will limit the depth of discharge (DOD) allowed which is directly related to the total number of cycles that you battery will endure. The second option is the dynamic power parameter. Limiting the dynamic power of the battery will certainly help to some extent to battery degradation.

1 Like

Is this correct? Exporting to the grid with Negative fit?

or is it because the battery is full at this time?

Im having the same issue, negative FIT and emhas wants to discharge the battery, mine is still half empty.

Yes same here. However I noticed a gap in solcast data at the same or similar time:

@markpurcell
? Now I’m getting these errors again?

2023-07-14 17:32:00,039 - web_server - INFO - Setting up needed data
2023-07-14 17:32:00,042 - web_server - INFO - Retrieve hass get data method initiated...
2023-07-14 17:32:00,573 - 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 2190, in wsgi_app
    response = self.full_dispatch_request()
  File "/usr/local/lib/python3.9/dist-packages/flask/app.py", line 1486, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/usr/local/lib/python3.9/dist-packages/flask/app.py", line 1484, in full_dispatch_request
    rv = self.dispatch_request()
  File "/usr/local/lib/python3.9/dist-packages/flask/app.py", line 1469, 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 174, 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-07-14 17:32:00,618 - web_server - INFO - Setting up needed data
2023-07-14 17:32:00,620 - web_server - INFO -  >> Publishing data...
2023-07-14 17:32:00,620 - web_server - INFO - Publishing data to HASS instance
2023-07-14 17:32:00,637 - web_server - INFO - Successfully posted to sensor.p_pv_forecast = 1398.0
2023-07-14 17:32:00,650 - web_server - INFO - Successfully posted to sensor.p_load_forecast = 765.76
2023-07-14 17:32:00,661 - web_server - INFO - Successfully posted to sensor.p_deferrable0 = 0.0
2023-07-14 17:32:00,673 - web_server - INFO - Successfully posted to sensor.p_batt_forecast = 3135.0
2023-07-14 17:32:00,685 - web_server - INFO - Successfully posted to sensor.soc_batt_forecast = 73.5
2023-07-14 17:32:00,700 - web_server - INFO - Successfully posted to sensor.p_grid_forecast = -3767.24
2023-07-14 17:32:00,714 - web_server - INFO - Successfully posted to sensor.total_cost_fun_value = 1.87
2023-07-14 17:32:00,724 - web_server - INFO - Successfully posted to sensor.unit_load_cost = 0.45
2023-07-14 17:32:00,734 - web_server - INFO - Successfully posted to sensor.unit_prod_price = 0.36

I can’t remember what the cause was last time this happened and can’t find the comments above.

Shell Commands where corrupt again.

Forecast data has dies again?

Seems to be solcast data missing. Rebooting the system too many times ends up using up all your API calls to solcast before the day is finished. Now I have no PV forecast until the clock strikes midnight in London.

Must implement oziee ha-solcast-solar asap

anybody got any good node-red flows to execute oziee solcast_solar.update_forecasts service only 10 times a day between sunrise and sunset despite system restarts or node-red reloads ets?

Anybody user this flow with solcast 10 API limit Node Red timer that persists after HA restart?

Or BigTimer ?

I’ve got two accounts… just swap the api key because I restarted HA multiple time

The only thing I can think of is the battery is close to full and solar is in excess.

ok good idea,

Not working at all now? Something wrong in post_mpc_optim_solcast? Been looking at it for hours.

2023-07-15 11:57:00,040 - web_server - INFO - Setting up needed data
2023-07-15 11:57:00,043 - web_server - INFO - Retrieve hass get data method initiated...
2023-07-15 11:57:00,543 - 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 2190, in wsgi_app
    response = self.full_dispatch_request()
  File "/usr/local/lib/python3.9/dist-packages/flask/app.py", line 1486, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/usr/local/lib/python3.9/dist-packages/flask/app.py", line 1484, in full_dispatch_request
    rv = self.dispatch_request()
  File "/usr/local/lib/python3.9/dist-packages/flask/app.py", line 1469, 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 174, 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-07-15 11:57:00,577 - web_server - INFO - Setting up needed data
2023-07-15 11:57:00,579 - web_server - INFO -  >> Publishing data...
2023-07-15 11:57:00,579 - web_server - INFO - Publishing data to HASS instance
2023-07-15 11:57:00,594 - web_server - INFO - Successfully posted to sensor.p_pv_forecast = 0.0
2023-07-15 11:57:00,603 - web_server - INFO - Successfully posted to sensor.p_load_forecast = 324.43
2023-07-15 11:57:00,614 - web_server - INFO - Successfully posted to sensor.p_deferrable0 = 0.0
2023-07-15 11:57:00,626 - web_server - INFO - Successfully posted to sensor.p_batt_forecast = 950.0
2023-07-15 11:57:00,635 - web_server - INFO - Successfully posted to sensor.soc_batt_forecast = 5.0
2023-07-15 11:57:00,644 - web_server - INFO - Successfully posted to sensor.p_grid_forecast = -625.57
2023-07-15 11:57:00,654 - web_server - INFO - Successfully posted to sensor.total_cost_fun_value = 1.87
2023-07-15 11:57:00,667 - web_server - INFO - Successfully posted to sensor.unit_load_cost = 0.17
2023-07-15 11:57:00,680 - web_server - INFO - Successfully posted to sensor.unit_prod_price = 0.07
shell_command:
  dayahead_optim: "curl -i -H \"Content-Type: application/json\" -X POST -d '{}' http://localhost:5000/action/dayahead-optim"
  publish_data: "curl -i -H \"Content-Type: application/json\" -X POST -d '{}' http://localhost:5000/action/publish-data"
  post_amber_forecast:
    "curl -i -H \"Content-Type: application/json\" -X POST -d '{\"prod_price_forecast\":{{(
    state_attr('sensor.cecil_st_feed_in_forecast', 'forecasts')|map(attribute='per_kwh')|list)
    }},\"load_cost_forecast\":{{(
    state_attr('sensor.cecil_st_general_forecast', 'forecasts') |map(attribute='per_kwh')|list)
    }},\"prediction_horizon\":33}' http://localhost:5000/action/dayahead-optim"
  post_emhass_forecast:
    "curl -i -H \"Content-Type: application/json\" -X POST -d '{\"prod_price_forecast\":{{(
    state_attr('sensor.cecil_st_feed_in_forecast', 'forecasts')|map(attribute='per_kwh')|list)
    }},{{states('sensor.solcast_24hrs_forecast')}},\"load_cost_forecast\":{{(
    state_attr('sensor.cecil_st_general_forecast', 'forecasts') |map(attribute='per_kwh')|list)
    }}}' http://localhost:5000/action/dayahead-optim"
  post_mpc_optim_solcast: 
    "curl -i -H \"Content-Type: application/json\" -X POST -d '{\"load_cost_forecast\":{{(
    ([states('sensor.cecil_st_general_price')|float(0)] +
    state_attr('sensor.cecil_st_general_forecast', 'forecasts') |map(attribute='per_kwh')|list)[:33])
    }}, \"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)[:33])
    }}, \"pv_power_forecast\":{{states('sensor.solcast_24hrs_forecast')
    }}, \"prediction_horizon\":33,\"soc_init\":{{(states('sensor.sonnenbatterie_84324_state_charge_user')|float(0))/100
    }},\"soc_final\":0.05,\"def_total_hours\":[2]}' http://localhost:5000/action/naive-mpc-optim"

Not sure if prediction horizon should be 33 only on the second last line or 33 for all elements in post_mpc_optim_solcast? doesn’t change the error either way.

try as per Marks suggestion for the prediction horizon

       "prediction_horizon": {{
          min(48, (state_attr('sensor.amber_feed_in_forecast', 'forecasts')|map(attribute='per_kwh')|list|length)+1)
        }},

had similar issues before

1 Like

Your load_cost_forecast and prod_price_forecast only have 32 elements.

Please switch to a dynamic calculation of prediction_horizon and ensure it matches your called values.

Great idea, then the results are cached on your local system.

Setup a time based automation to call five times a day at 0600, 0900, 1200, 1500 & 1800, any more often than that doesn’t add any value to the optimisation. To be honest you could just update the solcast integration once a day in the morning and it would be good enough.

At the end of 12:00-12:30 time period your battery was forecast to be at 98% SOC and you then had 90 minutes of maximum negative FIT -0.04 $/ kWh, with very little P_Load and high P_PV forecast over this period. You also don’t have any deferrable loads defined that EMHASS could turn on to consume this excess energy. Can you set up your hotwater system, or a bar radiator as a deferrable load for these types of situations?

EMHASS had very little choices available, so it chose to export for a cost of $0.13 over 12:30-13:00, so it didn’t need to export in the remaining two intervals. I suppose it could have chosen to export for a cost of $0.04 for each of the three thirty minute intervals, so the outcome would have been the same.

In my system I have zero export curtailment rules set up for my solar inverter (SolarEdge) for this type of situation which prevents my system exporting. EMHASS doesn’t know how to do curtailment, but in my case when my system does curtail my P_PV(now) value does go down, but I then move it back to my forecast so EMHASS can turn on my deferrable loads.

Thanks Mark, not really worried about this. just trying to understand how the system thinks.
I’ve handled this negative by monitory my battery SOC and CT Clamp going to the grid.
If its export morething 500W for 1 minute with negative fit… it will turn on the EV charging or if I fogot to plugin, it will turn on the RCAC in heater mode…

I’ll post this just for curiosity… again trying to understand how it works

Selling for $0.01??? or it could be the setting of the end SOC at the end of optimisation which is 5%

Mark
Thanks for your help.
Have you seen this from amber before? Do they change the size of the their forecast array?
Do you have an example of how to calculate the size of these array and include the variables in the post_mpc_optim_solcast template?
I’m not good at this template scripting I afraid. I’ll give it a go though.
Thanks
Rob

FIrst attempt:

{% set general_forecast_sensor = state_attr('sensor.cecil_st_general_forecast', 'forecasts') %}
{% set gfs = general_forecast_sensor | length %}
{{ gfs }}

{% set feedin_forecast_sensor = state_attr('sensor.cecil_st_feed_in_forecast', 'forecasts') %}
{% set ffs = feedin_forecast_sensor | length %}
{{ ffs }}

Prints 48
Not sure how to include this in:

state_attr('sensor.cecil_st_general_forecast', 'forecasts') |map(attribute='per_kwh')|list)[:???])

Why is there 48 now and only 32 before I wonder?

After 12:30 they publish the next 24 hours, which is 48 elements.

You will have the full 24 hours until 3:30 , at which points the number of elements reduces. So at 04:00 you will have 47 elements, then at 04:30 you will have 46 elements until you get to 12:00 when you have 32/33 elements which is all the timeslots between 12:00 and 03:30.

Then after 12:30 you get the full 24 hours again.

Should I just set it to 32 and be done with it? Can’t figure out how to include dynamic array counter template in shell command.

{% set general_forecast_sensor = state_attr('sensor.cecil_st_general_forecast', 'forecasts') %}
{% set gfs = general_forecast_sensor | length %}
{{ gfs }}

{% set feedin_forecast_sensor = state_attr('sensor.cecil_st_feed_in_forecast', 'forecasts') %}
{% set ffs = feedin_forecast_sensor | length %}
{{ ffs }}