EMHASS: An Energy Management for Home Assistant

I came across the post

Can you share your template formula for the PV forecast, as to build the pv_forecast array? I am trying to figure out how to build the array so I can use it for forecast.solar.
thank you

Happy to help, but the post you linked to has the template which Iā€™m using.

What else were you hoping to get?

I have coded this now:

\"pv_power_forecast\":{{[states('sensor.inverter_ingangsvermogen')|int(0)]
            + state_attr('sensor.template_test_solar_forecast','values_list')|selectattr('period_start','gt',utcnow())
              | map(attribute='pv_estimate')|map('multiply',2000)|map('int')|list }}

Resulting in this:

UndefinedError: 'int object' has no attribute 'period_start'

Can you tell me where to define the attributes period_start and pv_estimate

selectattr('period_start','gt',utcnow()

and

map(attribute='pv_estimate')

Clearly I am missing something here. thx

period_start is part of the output of this (in my setup):
state_attr(ā€˜sensor.solcast_pv_forecast_forecast_todayā€™, ā€˜detailedHourlyā€™)
It gives this:
[{ā€˜period_startā€™: datetime.datetime(2024, 6, 17, 0, 0, tzinfo=zoneinfo.ZoneInfo(key=ā€˜Europe/Amsterdamā€™)), ā€˜pv_estimateā€™: 0.0, ā€˜pv_estimate10ā€™: 0.0, ā€˜pv_estimate90ā€™: 0.0}, {ā€˜period_startā€™: datetime.datetime(2024, 6, 17, 1, 0, tzinfo=zoneinfo.ZoneInfo(key=ā€˜Europe/Amsterdamā€™)), ā€˜pv_estimateā€™: 0.0, ā€˜pv_estimate10ā€™: 0.0, ā€˜pv_estimate90ā€™: 0.0}, {ā€˜period_startā€™: datetime.datetime(2024, 6, 17, 2, 0, tzinfo=zoneinfo.ZoneInfo(key=ā€˜Europe/Amsterdamā€™)),
So most likely your ā€˜state_attr(ā€˜sensor.template_test_solar_forecastā€™,ā€˜values_listā€™)ā€™ does not contain the correct information. Is this also retrieved with the solcast integration?

Ah, that must be the issue. I am using Forecast.Solar now, so this wonā€™t work then.
Maybe go for solcast instead of forecast.solar to make things easy for me.

The solcast integration is having some issues at the moment.

Forecast solar should work you just need to get the data in a use able format.

The developer tools template editor is your friend.

1 Like

So far my setup using the MPC is working well, I did lose out this morning but Amber smartshift was going to do the same thing EMHASS wanted to do.

Going to look at the ML fit model. It is possbile to use both MPC and ML at the same time?

In my case I am trying to account for morning spikes that never eventuate.

1 Like

Yes you can use MPC & ML to get a comprehensive solution.

The morning false peaks are problematic as it drives EMHASS to hold back (or even charge overnight). A useful compromise I have is to set battery_discarge_weight to 0.15 (charge weight 0.00), which tends to hold charge overnight for morning peak rather than fully discharging at night, then recharging overnight at a high price.

One of the great things about EMHASS is you can tune/ modify the inputs so you can apply an 20% reduction to future price forecasts, or when Amber publish the advanced forecasts via the API we can use those.

1 Like

Thanks Mark,

Im sure this has been asked a heap of times, but do you have the code for your current apexcharts setup?

It looks so much easier to read compaired to my own.

1 Like

No worries:

type: custom:apexcharts-card
apex_config:
  chart:
    height: auto
header:
  show: true
  title: EMHASS Daily Forecast
  show_states: true
  colorize_states: true
graph_span: 36h
span:
  start: minute
  offset: +0h
all_series_config:
  stroke_width: 1
now:
  show: true
  label: Now
yaxis:
  - min: -7
    max: 13
    decimals: 0
    apex_config:
      forceNiceScale: true
      tick_amount: 4
series:
  - entity: sensor.consumption_savings_dmo
    float_precision: 0
    color: black
    name: Savings (vs DMO)
    show:
      legend_value: false
      in_chart: false
  - entity: sensor.total_cost_fun_value
    unit: $
    invert: true
    float_precision: 0
    name: Cost
    show:
      legend_value: false
      in_chart: false
    transform: return x *-1
  - entity: sensor.consumption_forecast
    unit: kWh
    float_precision: 0
    name: House
    show:
      legend_value: false
      in_chart: false
  - entity: sensor.pv_forecast_energy
    unit: kWh
    float_precision: 0
    color: orange
    name: Solar
    show:
      legend_value: false
      in_chart: false
  - entity: sensor.p_pv_forecast
    type: line
    stroke_width: 2
    color: orange
    extend_to: false
    show:
      in_header: false
      legend_value: false
    data_generator: |
      return entity.attributes.forecasts.map((entry) => {
        return [new Date(entry.date), entry.p_pv_forecast/1000];
      });
  - entity: sensor.p_load_forecast
    type: line
    color: purple
    extend_to: false
    show:
      in_header: false
      legend_value: false
    stroke_width: 1
    data_generator: |
      return entity.attributes.forecasts.map((entry) => {
        return [new Date(entry.date), entry.p_load_forecast/1000];
      });
  - entity: sensor.p_batt_forecast
    curve: stepline
    color: lightgreen
    extend_to: false
    show:
      in_header: false
      legend_value: false
    stroke_width: 1
    type: area
    data_generator: |
      return entity.attributes.battery_scheduled_power.map((entry) => {
        return [new Date(entry.date), entry.p_batt_forecast/1000];
      });
  - entity: sensor.p_grid_forecast
    curve: stepline
    color: lightgray
    type: area
    extend_to: false
    show:
      in_header: false
      legend_value: false
    stroke_width: 1
    data_generator: |
      return entity.attributes.forecasts.map((entry) => {
        return [new Date(entry.date), entry.p_grid_forecast/1000];
      });
  - entity: sensor.p_deferrable0
    curve: stepline
    color: blue
    name: Pool Pump
    extend_to: false
    show:
      in_header: false
      legend_value: false
    stroke_width: 1
    data_generator: |
      return entity.attributes.deferrables_schedule.map((entry) => {
        return [new Date(entry.date), entry.p_deferrable0/1000];
      });
  - entity: sensor.p_deferrable1
    curve: stepline
    color: red
    name: Pool Heater
    extend_to: false
    show:
      in_header: false
      legend_value: false
    stroke_width: 1
    data_generator: |
      return entity.attributes.deferrables_schedule.map((entry) => {
        return [new Date(entry.date), entry.p_deferrable1/1000];
      });
  - entity: sensor.p_deferrable3
    curve: stepline
    name: HVAC
    color: blue
    extend_to: false
    show:
      in_header: false
      legend_value: false
    stroke_width: 1
    data_generator: |
      return entity.attributes.deferrables_schedule.map((entry) => {
        return [new Date(entry.date), entry.p_deferrable3/1000];
      });
  - entity: sensor.p_deferrable4
    curve: stepline
    color: red
    name: Hot Water
    extend_to: false
    show:
      in_header: false
      legend_value: false
    stroke_width: 1
    data_generator: |
      return entity.attributes.deferrables_schedule.map((entry) => {
        return [new Date(entry.date), entry.p_deferrable4/1000];
      });
  - entity: sensor.p_deferrable2
    curve: stepline
    color: black
    name: Car
    extend_to: false
    show:
      in_header: false
      legend_value: false
    stroke_width: 1
    data_generator: |
      return entity.attributes.deferrables_schedule.map((entry) => {
        return [new Date(entry.date), entry.p_deferrable2/1000];
      });
  - entity: sensor.p_deferrable5
    curve: stepline
    name: Car2
    color: grey
    extend_to: false
    show:
      in_header: false
      legend_value: false
    stroke_width: 1
    data_generator: |
      return entity.attributes.deferrables_schedule.map((entry) => {
        return [new Date(entry.date), entry.p_deferrable5/1000];
      });
view_layout:
  position: main
type: custom:apexcharts-card
experimental:
  color_threshold: true
graph_span: 36h
span:
  start: minute
  offset: '-0h'
header:
  show: true
  title: Battery, Price & Cost Forecast
  show_states: true
  colorize_states: true
now:
  show: true
  label: Now
series:
  - entity: sensor.unit_load_cost
    float_precision: 2
    yaxis_id: first
    curve: stepline
    extend_to: false
    show:
      in_header: before_now
      legend_value: false
    stroke_width: 1
    color: red
    unit: $/kWh
    color_threshold:
      - value: 0
        color: cyan
      - value: 0.19
        color: green
      - value: 0.3
        color: yellow
      - value: 0.4
        color: red
    data_generator: |
      return entity.attributes.unit_load_cost_forecasts.map((entry) => {
        return [new Date(entry.date), entry.unit_load_cost];
      });
  - entity: sensor.unit_prod_price
    float_precision: 2
    yaxis_id: first
    curve: stepline
    unit: $/kWh
    extend_to: false
    show:
      in_header: before_now
      legend_value: false
    color: cyan
    stroke_width: 1
    data_generator: |
      return entity.attributes.unit_prod_price_forecasts.map((entry) => {
        return [new Date(entry.date), entry.unit_prod_price];
      });
  - entity: sensor.amber_feed_in_forecast
    float_precision: 2
    yaxis_id: first
    curve: stepline
    extend_to: false
    show:
      in_header: after_now
      legend_value: false
    color: orange
    stroke_width: 1
    name: feed in forecast
    data_generator: |
      return entity.attributes.forecasts.map((entry) => {
        return [new Date(entry.start_time), entry.per_kwh];
      });
  - entity: sensor.soc_batt_forecast
    float_precision: 0
    yaxis_id: second
    time_delta: +30m
    extend_to: false
    show:
      in_header: before_now
      legend_value: false
    color: green
    stroke_width: 1
    name: battery SOC
    data_generator: |
      return entity.attributes.battery_scheduled_soc.map((entry) => { 
        return [new Date(entry.date), entry.soc_batt_forecast]; });
  - entity: sensor.filtered_powerwall_soc
    yaxis_id: second
    type: line
    stroke_width: 1
    extend_to: false
    color: green
    show:
      in_header: false
      legend_value: false
      in_chart: true
  - entity: input_number.amber_closing_import_price
    curve: stepline
    yaxis_id: first
    extend_to: false
    stroke_width: 1
    color: red
    show:
      in_header: false
      legend_value: false
      in_chart: true
  - entity: input_number.amber_closing_export_price
    curve: stepline
    yaxis_id: first
    extend_to: false
    stroke_width: 1
    color: blue
    show:
      in_header: false
      legend_value: false
yaxis:
  - id: first
    decimals: 1
    max: 1
    min: 0
    apex_config:
      tickAmount: 2
      logarithmic: false
  - id: second
    show: false
    opposite: false
    decimals: 1
    max: 100
    min: 0
    apex_config:
      tickAmount: 2
view_layout:
  position: main
1 Like

Wonder if this will come to fruition in the morning? Do I allow EMHASS to take advantage or not?

Jeez, hope you have battery so you can make a buck.

@rcruikshank By the way, thatā€™s a nice way to show the electricity price, is it in the home assistant? If so, what kind of integration is it?

Morning spike has collapsed here, EMHASS is however getting seduced into charging overnight to meet these high forecasts that then disappear.

Iā€™m finding battery_discharge_weight is useful to get EMHASS to keep some reserve, but I would dearly love to be able to use the advanced price forecasts from Amber, when they include them in the API, or run local ML to generate my own tempered price forecasts.

1 Like

Unforunatley not EMHASS, thatā€™s from the Amber Electicity website.

iā€™m looking for a similar solution, think iā€™ll start with the discharge_weight

Thatā€™s the issue. I can charge the battery overnight at a cost but will the spike eventuate? It didnā€™t (again). So Iā€™ve once again charged the battery at an inflated cost (42c) and now itā€™s discharging at a loss so it can charge again from the sun for an even worse spike this evening. My battery is a bit tired at 6 years old and only contains about 7 or 8kWh and now it has to cover this tonight:

Iā€™ll have to sit under an electric blanket all night. Canā€™t run the aircon if the price is this high and itā€™s probably more likely to occur tonight. Seems the morning forecasts are furphies.

Sorry, furphy, Australian colloquialism for false report or improbable story; rumor .

1 Like

Hello everyone,
thank you so much for this awesome piece of software!

I know this has been discussed in the past but I just cannot get it to work.
So here is the deal:
I am running the lastes docker image of EMHASS:

htpc:~# docker inspect emhass | grep -i version
                "io.hass.base.version": "2024.03.0",
                "io.hass.version": "2024.03.0",
                "org.opencontainers.image.version": "v0.10.1"
htpc:/etc/emhass# docker pull davidusb/emhass-docker-standalone
Digest: sha256:607300ff655fa7e69b63890e64e77aec6f8726a1bf829a774976631a14ca8d68
Status: Image is up to date for davidusb/emhass-docker-standalone:latest

and this is the configuration I use:

retrieve_hass_conf:
  freq: 30 # The time step to resample retrieved data from hass in minutes
  days_to_retrieve: 2 # We will retrieve data from now and up to days_to_retrieve days
  var_PV: 'sensor.sw29_power' # Photovoltaic produced power sensor in Watts
  var_load: 'sensor.power_load_no_var_loads' # Household power consumption sensor in Watts (deferrable loads should be substracted)
  load_negative: False # Set to True if the retrived load variable is negative by convention
  set_zero_min: True # A special treatment for a minimum value saturation to zero. Values below zero are replaced by nans
  var_replace_zero: # A list of retrived variables that we would want  to replace nans with zeros
  - 'sensor.sw29_power'
  var_interp: # A list of retrived variables that we would want to interpolate nan values using linear interpolation
  - 'sensor.sw29_power'
  - 'sensor.power_load_no_var_loads'
  method_ts_round: 'nearest' # Set the method for timestamp rounding, options are: first, last and nearest
  continual_publish: False # Save published sensor data and check for state change every freq minutes

optim_conf:
  set_use_battery: False # consider a battery storage
  delta_forecast: 1 # days
  num_def_loads: 2
  P_deferrable_nom: # Watts
  - 450.0
  - 60.0
  def_total_hours: # hours
  - 5
  - 3.5
  def_start_timestep: # timesteps
  - 0
  - 0
  def_end_timestep: # timesteps
  - 40
  - 19
  treat_def_as_semi_cont: # treat this variable as semi continuous 
  - True
  - True
  set_def_constant: # set as a constant fixed value variable with just one startup for each 24h
  - False
  - True
#  weather_forecast_method: 'solcast' # options are 'scrapper', 'csv', 'list', 'solcast' and 'solar.forecast'
  weather_forecast_method: 'scrapper' # options are 'scrapper', 'csv', 'list', 'solcast' and 'solar.forecast'
  load_forecast_method: 'csv' # options are 'csv' to load a custom load forecast from a CSV file or 'naive' for a persistance model
  load_cost_forecast_method: 'hp_hc_periods' # options are 'hp_hc_periods' for peak and non-peak hours contracts and 'csv' to load custom cost from CSV file 
  list_hp_periods: # list of different tariff periods (only needed if load_cost_forecast_method='hp_hc_periods')
  - period_hp_1:
    - start: '00:00'
    - end: '23:59'
  load_cost_hp: 0.41 # peak hours load cost in ā‚¬/kWh (only needed if load_cost_forecast_method='hp_hc_periods')
  load_cost_hc: 0.41 # non-peak hours load cost in ā‚¬/kWh (only needed if load_cost_forecast_method='hp_hc_periods')
  prod_price_forecast_method: 'constant' # options are 'constant' for constant fixed value or 'csv' to load custom price forecast from a CSV file
  prod_sell_price: 0.0 # power production selling price in ā‚¬/kWh (only needed if prod_price_forecast_method='constant')
  set_total_pv_sell: False # consider that all PV power is injected to the grid (self-consumption with total sell)
  lp_solver: 'default' # set the name of the linear programming solver that will be used. Options are 'PULP_CBC_CMD', 'GLPK_CMD' and 'COIN_CMD'. 
  lp_solver_path: 'empty' # set the path to the LP solver, COIN_CMD default is /usr/bin/cbc
  set_nocharge_from_grid: False # avoid battery charging from the grid
  set_nodischarge_to_grid: True # avoid battery discharging to the grid
  set_battery_dynamic: False # add a constraint to limit the dynamic of the battery power in power per time unit
  battery_dynamic_max: 0.9 # maximum dynamic positive power variation in percentage of battery maximum power
  battery_dynamic_min: -0.9 # minimum dynamic negative power variation in percentage of battery maximum power
  weight_battery_discharge: 0.0 # weight applied in cost function to battery usage for discharge
  weight_battery_charge: 0.0 # weight applied in cost function to battery usage for charge

plant_conf:
  P_from_grid_max: 9000 # The maximum power that can be supplied by the utility grid in Watts
  P_to_grid_max: 9000 # The maximum power that can be supplied to the utility grid in Watts
  module_model: # The PV module model
  - 'JA_Solar_JAM54S30_415_MR'
  inverter_model: # The PV inverter model
  - 'Hoymiles_Power_Electronics_Inc___HMS_800_2T_NA__240V_'
  surface_tilt: # The tilt angle of your solar panels
  - 30
  surface_azimuth: # The azimuth angle of your PV installation
  - 180
  modules_per_string: # The number of modules per string
  - 1
  strings_per_inverter: # The number of used strings per inverter
  - 2
  inverter_is_hybrid: False # Set if it is a hybrid inverter (PV+batteries) or not

For simplicity reasons I am passing all zeros for load_forecast_method for now.

The two deferrable loads are a warm water heat pump (450W or off) and a dishwasher (~60W), which shall run once a day and be done before 5pm

Here is what I get:

My questions are:

  • Why is the heat pump (light blue) not being turned off (0W) and on (450W) although I am specifying treat_def_as_semi_cont = True ?
  • Why is the dishwasher (cyan) started more than once a day although I am specifying set_def_constant = True ?

I am getting that the optimization assignment is ā€œinfeasableā€ but the device contrainsts cannot be altered, so I would expect the optimization to honor these contrainst given in the configuration file. Can anyone shed some light on this? Any hints are greatly appreaciated! Cheers, Jan

When the assignment is infeasible then the graphs wonā€™t make sense or follow your parameters. It normally means that something isnā€™t quite right.
My simple guess is becuase your PV never gets above 450w itā€™s unable to optimise this correctly.
As a next step, Iā€™d try one of the following:

  1. Check that the PV config is correct, it looks like youā€™re only getting a very low amount of PV being generated (maybe you donā€™t have enough models or strings to add up to your correct PV array).
  2. If you are only expecting a very low amount of PV, then try running it with just the 60w load and see if you can get an optimal result.