EMHASS: An Energy Management for Home Assistant

Not sure, as I mentioned I don’t know whether this is something appeared in the latest release or not.

If you restart the add-on do you see the same?

{
  "adjusted_pv_model_max_age": 24,
  "adjusted_pv_regression_model": "LassoRegression",
  "adjusted_pv_solar_elevation_threshold": 10,
  "battery_charge_efficiency": 0.95,
  "battery_charge_power_max": 7100,
  "battery_discharge_efficiency": 0.95,
  "battery_discharge_power_max": 7100,
  "battery_dynamic_max": 0.9,
  "battery_dynamic_min": -0.9,
  "battery_maximum_state_of_charge": 1,
  "battery_minimum_state_of_charge": 0,
  "battery_nominal_energy_capacity": 27000,
  "battery_stress_cost": 0,
  "battery_stress_segments": 10,
  "battery_target_state_of_charge": 0.2,
  "compute_curtailment": false,
  "continual_publish": false,
  "costfun": "self-consumption",
  "delta_forecast_daily": 3,
  "end_timesteps_of_each_deferrable_load": [
    0
  ],
  "historic_days_to_retrieve": 31,
  "influxdb_database": "homeassistant",
  "influxdb_host": "localhost",
  "influxdb_measurement": "W",
  "influxdb_password": "",
  "influxdb_port": 8086,
  "influxdb_retention_policy": "autogen",
  "influxdb_use_ssl": false,
  "influxdb_username": "",
  "influxdb_verify_ssl": false,
  "inverter_ac_input_max": 1000,
  "inverter_ac_output_max": 1000,
  "inverter_efficiency_ac_dc": 1,
  "inverter_efficiency_dc_ac": 1,
  "inverter_is_hybrid": false,
  "inverter_stress_cost": 0,
  "inverter_stress_segments": 10,
  "load_cost_forecast_method": "hp_hc_periods",
  "load_forecast_method": "naive",
  "load_negative": false,
  "load_offpeak_hours_cost": 0.1419,
  "load_peak_hour_periods": {
    "period_hp_1": [
      {
        "start": "07:00"
      },
      {
        "end": "23:00"
      }
    ]
  },
  "load_peak_hours_cost": 0.1907,
  "logging_level": "INFO",
  "lp_solver": "COIN_CMD",
  "lp_solver_path": "/usr/bin/cbc",
  "lp_solver_timeout": 45,
  "maximum_power_from_grid": 5000,
  "maximum_power_to_grid": 8000,
  "method_ts_round": "first",
  "minimum_power_of_deferrable_loads": [
    0,
    0
  ],
  "model_type": "load_forecast",
  "modules_per_string": [
    9,
    14
  ],
  "nominal_power_of_deferrable_loads": [
    0
  ],
  "num_lags": 48,
  "num_threads": 0,
  "number_of_deferrable_loads": 1,
  "open_meteo_cache_max_age": 30,
  "operating_hours_of_each_deferrable_load": [
    1
  ],
  "optimization_time_step": 15,
  "perform_backtest": false,
  "photovoltaic_production_sell_price": 0.065,
  "production_price_forecast_method": "constant",
  "pv_inverter_model": [
    "SMA_America__SB3000TL_US_22__240V_",
    "SMA_America__STP50_US_40__480V_"
  ],
  "pv_module_model": [
    "SunPower_SPR_E20_327",
    "SunPower_SPR_MAX3_400_COM"
  ],
  "sensor_linear_interp": [
    "sensor.consumption_filtered_w",
    "sensor.production_pv_w"
  ],
  "sensor_power_load_no_var_loads": "sensor.consumption_filtered_w",
  "sensor_power_photovoltaics": "sensor.production_pv_w",
  "sensor_power_photovoltaics_forecast": "sensor.p_pv_forecast",
  "sensor_replace_zero": [
    "sensor.production_pv_w"
  ],
  "set_battery_dynamic": false,
  "set_deferrable_load_single_constant": [
    false
  ],
  "set_deferrable_startup_penalty": [
    0
  ],
  "set_nocharge_from_grid": false,
  "set_nodischarge_to_grid": true,
  "set_total_pv_sell": false,
  "set_use_adjusted_pv": false,
  "set_use_battery": true,
  "set_use_pv": true,
  "set_zero_min": true,
  "sklearn_model": "KNeighborsRegressor",
  "split_date_delta": "48h",
  "start_timesteps_of_each_deferrable_load": [
    0
  ],
  "strings_per_inverter": [
    1,
    1
  ],
  "surface_azimuth": [
    225,
    225
  ],
  "surface_tilt": [
    17,
    17
  ],
  "treat_deferrable_load_as_semi_cont": [
    true
  ],
  "use_influxdb": false,
  "use_websocket": false,
  "var_model": "sensor.power_load_no_var_loads",
  "weather_forecast_method": "open-meteo",
  "weight_battery_charge": 0,
  "weight_battery_discharge": 0.05
}

For some reason the thermal model parsing treatment logic has always been inside a if set_type == "naive-mpc-optim" condition.

There is absolutely no reason for this.
I’m currently applying a patch to fix this: Fix the thermal load parsing to use it on any type of optimization by davidusb-geek · Pull Request #678 · davidusb-geek/emhass · GitHub

UPDATE: Should be fixed in new patched version v0.15.5

2 Likes
  • optimization_time_step: 30
  • delta_forecast_daily: 2
  • prediction_horizon: 96

first price in the 96 price forecast is current 5 min and the other 95 are from 30 min forecasts. The 96 forecast is bascially the 24 hour period repeated. For the original 24 hour period, missing values are replaced with a buy of 0.15 and sell of 0.05 as price data is usually missing for midday onwards (Amber provides a shrinking horizon forecast to midday).

After updating to v0.15.4 I see these warnings in the logs.

2026-01-17 06:00:09,632] WARNING in retrieve_hass: Sensor 'sensor.pv_power' in var_replace_zero not found in self.var_list and has been removed.
[2026-01-17 06:00:09,632] WARNING in retrieve_hass: Sensor 'sensor.pv_power' in var_interp not found in self.var_list and has been removed.
WARNING in retrieve_hass: Unable to find all the sensors in sensor_replace_zero parameter
[2026-01-17 07:00:02,250] WARNING in retrieve_hass: Confirm sure all sensors in sensor_replace_zero are sensor_power_photovoltaics and/or sensor_power_load_no_var_loads

Hello, is it possible to let EMHASS optimize a deferrable with different runtime constraints based on time of day? Or some way to do this with two deferrable loads and somehow choose to only run one of them? I think I can easily configure one of the two constraints, but not sure how to optimize for both:

I’ve got a dishwasher that has Power and a Quiet program. Both consume about 1kWh in total, Power takes 2h, Quiet 4h.
I usually want the dishwasher to be done by the time of the next meal:

  • start 5pm-4am: done by 8am
  • start 4am-11am: done by 1pm
  • start 11am-5pm: done by 7pm

and choose a program based on time of day:

  • start 8pm-8am: Quiet
  • start 8am-8pm: Power

The documentation mentions this:

  • desired_temperatures: (Legacy) A specific target temperature per timestep. Used with penalty_factor. → I tried penalty_factor at runtime, but is doesn’t seem to do anything. What is a ‘normal’ value?

Read this: Deferrable load thermal model — emhass 0.16.1 documentation
You need to pass the desired_temperatures not the penalty factor.
Just set your desired temperatures based on your comfort settings

I already pass desired_temperatures, but I was under the impression that you could add the penalty_factor next to it

Yes this should be possible with some rather complex templates.

Here is what Gemini came up with:

Yes, this is absolutely possible and is actually the intended use case for Runtime Parameters.

Instead of trying to configure two separate “Dishwasher” loads in EMHASS (which would confuse the solver, as it might try to run both or neither), you should configure one generic Deferrable Load (e.g., Load 0) and dynamically change its definition (Power, Duration, Deadlines) via Home Assistant templates every time you run the optimization.

Here is the complete solution using a Template Sensor to handle the logic, keeping your shell command clean.

Step 1: Create the Logic Sensor

Add this to your configuration.yaml (or templates.yaml). This sensor calculates the correct power, duration, and deadline window based on the current time of day.

Assumptions:

  • Power Mode: 1kWh / 2h = 500 Watts
  • Quiet Mode: 1kWh / 4h = 250 Watts
  • Time Step: 30 minutes
template:
  - sensor:
      - name: "Dishwasher Dynamic Profile"
        unique_id: emhass_dishwasher_dynamic_profile
        state: "OK"
        attributes:
          # 1. Determine the Mode (Power vs Quiet)
          # 8am - 8pm: Power (500W, 2h). Otherwise: Quiet (250W, 4h)
          mode: >
            {% set h = now().hour %}
            {% if 8 <= h < 20 %} Power {% else %} Quiet {% endif %}
          
          nominal_power: >
            {% set h = now().hour %}
            {% if 8 <= h < 20 %} 500 {% else %} 250 {% endif %}
            
          total_hours: >
            {% set h = now().hour %}
            {% if 8 <= h < 20 %} 2 {% else %} 4 {% endif %}

          # 2. Calculate the Deadline (End Timestep)
          # Logic: Calculate seconds until deadline, divide by 1800 (30min), round up.
          end_timestep: >
            {% set h = now().hour %}
            {% set ts_now = as_timestamp(now()) %}
            {% set midnight = as_timestamp(today_at("00:00")) %}
            
            {# Define Target Hours based on start time rules #}
            {% if 4 <= h < 11 %}
              {# Start 4am-11am -> Done by 1pm (13:00) Today #}
              {% set target = midnight + (13 * 3600) %}
            {% elif 11 <= h < 17 %}
              {# Start 11am-5pm -> Done by 7pm (19:00) Today #}
              {% set target = midnight + (19 * 3600) %}
            {% else %}
              {# Start 5pm-4am -> Done by 8am Tomorrow #}
              {# If currently late (>=17), target is tomorrow 8am. If early (<4), target is today 8am #}
              {% if h >= 17 %}
                {% set target = midnight + (24 + 8) * 3600 %}
              {% else %}
                {% set target = midnight + (8 * 3600) %}
              {% endif %}
            {% endif %}
            
            {# Calculate difference in 30-min chunks #}
            {% set delta_seconds = target - ts_now %}
            {% if delta_seconds < 0 %} 
              0 {# Deadline already passed #}
            {% else %}
              {{ (delta_seconds / 1800) | round(0, 'ceil') | int }}
            {% endif %}

Step 2: Update the Shell Command

Now, map the values from this sensor into your EMHASS call.

  • def_total_hours comes from the sensor.
  • nominal_power_of_deferrable_loads comes from the sensor.
  • end_timesteps_of_each_deferrable_load comes from the sensor.
shell_command:
  dayahead_optim: >-
    curl -i -H "Content-Type: application/json" -X POST -d '{
      "def_total_hours": [
        {{ state_attr('sensor.emhass_dishwasher_dynamic_profile', 'total_hours') }},
        {{ states('sensor.other_load_hours') }}, 
        0, 0, 0
      ],
      "nominal_power_of_deferrable_loads": [
        {{ state_attr('sensor.emhass_dishwasher_dynamic_profile', 'nominal_power') }}, 
        3000, 
        1000, 1000, 1000
      ],
      "start_timesteps_of_each_deferrable_load": [0, 0, 0, 0, 0],
      "end_timesteps_of_each_deferrable_load": [
        {{ state_attr('sensor.emhass_dishwasher_dynamic_profile', 'end_timestep') }}, 
        0, 
        0, 0, 0
      ]
    }' http://localhost:5000/action/dayahead-optim

How it works

  1. Automation Trigger: You run this command (e.g., at 7:00 AM).
  2. Sensor Calculation:
  • 7:00 AM falls in the 4am-11am window.
  • Target is 1pm (13:00).
  • Time diff = 6 hours.
  • end_timestep = 12 steps.
  • Time is before 8am (Quiet rule), so nominal_power = 250W, hours = 4.
  1. Optimization: EMHASS receives a request to optimize a 250W load that lasts 4 hours and must finish within 12 steps.
  2. Result: It finds the cheapest 4-hour block between 7:00 AM and 1:00 PM.

Launched v0.16.0!
A hot fix patch was needed, so v0.16.1 is up.
A summary of new features:

:rocket: Major Optimization Engine Overhaul

This release marks a significant milestone in the evolution of EMHASS. We have completely re-engineered the core optimization backend, moving from PuLP to CVXPY. This modern, vectorized architecture unlocks substantial performance improvements and paves the way for advanced energy management features.

Key Highlights

  • :zap: CVXPY & Vectorization: The constraint generation logic has been rewritten using vectorization. This mathematical streamlining allows EMHASS to construct optimization problems significantly faster, reducing overhead especially for complex configurations or long prediction horizons. Benchmarks show optimization times are 4-5x faster, clocking in at approximately 0.1s per iteration for standard use cases compared to previous implementation.

  • :racing_car: HiGHS Solver Standard: We have adopted HiGHS as the new default solver. HiGHS is a state-of-the-art, open-source linear optimization solver that offers vastly superior performance and stability compared to the legacy CBC/GLPK solvers.

  • :package: Simplified Dependencies: No more system-level dependencies! Because HiGHS is bundled as a Python wheel, the Docker image is lighter, and installation is more robust—you no longer need to install coinor-cbc or glpk via apt.

  • :wrench: Commercial Solver Support: Power users with licenses for Gurobi or CPLEX can now easily plug them in via environment variables (LP_SOLVER=GUROBI) without code modifications, thanks to the standardized interface of CVXPY.

  • :building_construction: Modular Architecture: The optimization class has been refactored into smaller, testable helper methods, improving code maintainability and making it easier for contributors to add new constraints (like the new internal gains logic for thermal batteries) without breaking existing features.

Improvement

  • Refactor optimization.py to use cvxpy

  • Added internal gains factor to heating demand calculation (@sokorn)

  • Add vector support for maximum power to or from grid (@mk2lehe)

:warning: BREAKING CHANGE: Solver Configuration

We have modernized the optimization backend to use CVXPY. As a result, the solver configuration has been simplified and both lp_solver and lp_solver_path parameters have been removed.

  • Removed: lp_solver and lp_solver_path are no longer supported in the configuration.

  • New Default: The application now defaults to the HiGHS solver, which is bundled directly with EMHASS. No external binary paths or apt packages are required.

  • Commercial Solvers: If you wish to use Gurobi or CPLEX (requiring your own license), you must now specify this using the LP_SOLVER environment variable (e.g., LP_SOLVER=GUROBI), rather than the configuration file.

Action Required:

  1. Remove lp_solver and lp_solver_path from your configuration files (config.json or options.json) to avoid validation warnings.

  2. If you previously relied on a custom solver path, ensure you are satisfied with the new default HiGHS solver (recommended) or configure the environment variable for a commercial alternative.

3 Likes

Hi, following these instructions – I’ve managed to get the HA/EMHASS integration mostly working.

The first couple of times I ran the script the process appeared to work. I got a plan back from EMHASS into HA.

However now I am getting the following errors.

emhass  | [2026-01-24 00:07:51,307] INFO in command_line: Publishing data to HASS instance
emhass  | [2026-01-24 00:07:51,315] ERROR in app: Exception on request POST /action/publish-data
emhass  | Traceback (most recent call last):
emhass  |   File "/app/.venv/lib/python3.12/site-packages/pandas/core/indexes/base.py", line 3641, in get_loc
emhass  |     return self._engine.get_loc(casted_key)
emhass  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
emhass  |   File "pandas/_libs/index.pyx", line 168, in pandas._libs.index.IndexEngine.get_loc
emhass  |   File "pandas/_libs/index.pyx", line 197, in pandas._libs.index.IndexEngine.get_loc
emhass  |   File "pandas/_libs/hashtable_class_helper.pxi", line 7668, in pandas._libs.hashtable.PyObjectHashTable.get_item
emhass  |   File "pandas/_libs/hashtable_class_helper.pxi", line 7676, in pandas._libs.hashtable.PyObjectHashTable.get_item
emhass  | KeyError: 'P_PV'
emhass  |
emhass  | The above exception was the direct cause of the following exception:
emhass  |
emhass  | Traceback (most recent call last):
emhass  |   File "/app/.venv/lib/python3.12/site-packages/quart/app.py", line 1464, in handle_request
emhass  |     return await self.full_dispatch_request(request_context)
emhass  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
emhass  |   File "/app/.venv/lib/python3.12/site-packages/quart/app.py", line 1502, in full_dispatch_request
emhass  |     result = await self.handle_user_exception(error)
emhass  |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
emhass  |   File "/app/.venv/lib/python3.12/site-packages/quart/app.py", line 1059, in handle_user_exception
emhass  |     raise error
emhass  |   File "/app/.venv/lib/python3.12/site-packages/quart/app.py", line 1500, in full_dispatch_request
emhass  |     result = await self.dispatch_request(request_context)
emhass  |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
emhass  |   File "/app/.venv/lib/python3.12/site-packages/quart/app.py", line 1597, in dispatch_request
emhass  |     return await self.ensure_async(handler)(**request_.view_args)  # type: ignore[return-value]
emhass  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
emhass  |   File "/app/src/emhass/web_server.py", line 652, in action_call
emhass  |     msg, status = await _handle_action_dispatch(
emhass  |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
emhass  |   File "/app/src/emhass/web_server.py", line 506, in _handle_action_dispatch
emhass  |     _ = await publish_data(input_data_dict, logger)
emhass  |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
emhass  |   File "/app/src/emhass/command_line.py", line 1974, in publish_data
emhass  |     cols_published.extend(await _publish_standard_forecasts(ctx, opt_res_latest))
emhass  |                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
emhass  |   File "/app/src/emhass/command_line.py", line 1671, in _publish_standard_forecasts
emhass  |     opt_res_latest["P_PV"],
emhass  |     ~~~~~~~~~~~~~~^^^^^^^^
emhass  |   File "/app/.venv/lib/python3.12/site-packages/pandas/core/frame.py", line 4378, in __getitem__
emhass  |     indexer = self.columns.get_loc(key)
emhass  |               ^^^^^^^^^^^^^^^^^^^^^^^^^
emhass  |   File "/app/.venv/lib/python3.12/site-packages/pandas/core/indexes/base.py", line 3648, in get_loc
emhass  |     raise KeyError(key) from err
emhass  | KeyError: 'P_PV'

Any help on solving this issue would be appreciated.

Please provide more context. What script are you talking about?
Share your config.
That log shows an error on the publish action. So your optimization action performs well?

Hi,
I get 2 warnings that I do not undertsand.
The optimisation run competes normally.
WARNING in retrieve_hass: Unable to find all the sensors in sensor_replace_zero parameter
[WARNING in retrieve_hass: Confirm sure all sensors in sensor_replace_zero are sensor_power_photovoltaics and/or sensor_power_load_no_var_loads
If I look in the configuration:
“sensor_replace_zero”: [
“sensor.sun_east_and_west_power”,
“sensor.power_load_no_var_loads”,
“sensor.p_pv_forecast”
],
The sensors do exist. What could be wrong?

Thanks @davidusb and the other contributors for your great work! I upgrade from 1.15.5 to 1.16.1. At first I got a strange error during startup of the docker container, indicating it could not stat /app/data/long_train_data.pkl
When looking at the volume containing this file, I did have a long_train_data_mlf.pkl file, which I copied to long_train_data.pkl.
After a restart everything worked again, so perhaps helpfull for others.

1 Like

After updating to 16.1, I have encountered some issues. Below is a description of the problems. Has anyone else experienced similar issues?

If I disable “auto publish” (automation and rest comand to send sesordata), everything continues to work except the web UI, which for some reason keeps crashing all the time. Otherwise, all optimizations seem to work much better now—it uses much less CPU power and is significantly faster.

Bug Report: EMHASS v0.16.1 - Technical Issues Observed

1. Race Condition / Deadlock in continual_publish

  • Symptom: When continual_publish is set to True, the EMHASS server becomes unresponsive, and the add-on often crashes or enters a boot-loop.
  • Observation: The rapid, non-stop publishing to the Home Assistant REST API creates a bottleneck that prevents the optimization loop and the web server from processing other requests.

2. Web Server Crash: EOFError: Ran out of input

  • Symptom: The Web UI (index.html) fails to load with a 500 Internal Server Error.
  • Log Trace: File "/app/src/emhass/web_server.py", line 192, in index injection_dict = pickle.loads(content) EOFError: Ran out of input.
  • Observation: The pickle file used for UI data injection becomes truncated or empty (0 bytes), causing the pickle.loads function to fail and crash the entire web interface.

I do not experience any symptoms with the web UI.
I’m not using (never been) the auto publish feature, so for me it’s been off since ever.
I’m using EMHASS as a HA add-on.
Cheers!

Hello, question for the community.
Does anybody know if there is a way to tune/force the typical load forecasting method?
I find it interesting (for example Sundays consumption for me is really different from others days) but the forecast overall (not just on Sunday) is too low if compared to the current typical consumption.
I was wondering if there is a way to control how far back the data is retrieved (if too far back maybe lower consumption is limiting the forecast - unless there is any strategy to balance the importance of the data, relative to its age)… in general if there is a way to control and tune this process.
Cheers!

Hello! I’m trying to implement the new thermal_battery model for my house with underfloor heating and a heat pump. Figuring out the parameters I need was a bit difficult, but I think I have a good base line to tune once I start using it.

However, there are two things that I can’t really get working. Both have to do with heat pumps preferring longer runs instead of multiple shorter runs.

The first thing is that I can’t get the startup penalty working. For example, I just tested my configuration and I got this forecast:


The run from 2:30 to 5:30 is a great choice considering the prices, but the stop in the middle at 3:30 does not make sense. 3:30 is the most expensive period, but the price difference to the second most expensive period is 0,001 euro. I have configured a startup penalty for this load, which probably would’ve fixed this run, but it seems that that just gets ignored. Is that a bug or by design? If by design, is there any alternative for getting longer runs?

The other thing is that I can’t set the nominal power of my heat pump to the true value. If I set it too high, EMHASS will give me multiple super short runs, instead of one long run. Even though it still has a lot of room before hitting the max temperatures. I have settled on 1000W for now, which gives me reasonable schedules, even though my heat pump can actually ramp up to 2500W if necessary. Is there any way I could fix this?
This behaviour might be fixed by using startup penalties, but from my example above, I think that doesn’t work with thermal_battery.

Is there someone that has a working thermal_battery configuration with reasonable schedules? Are there any tips you could give me for getting this to work?

These are my thermal_battery parameters (thermal_battery config at the bottom):

rest_command:
  emhass_npc_optim_with_pv_15m_with_def_heat_pump:
    url: http://localhost:5000/action/naive-mpc-optim
    method: post
    timeout: 300
    payload: >
      {% set horizon = min(144, state_attr('sensor.entsoe_prices_forecast_30', 'prices_36h_15min') | length | int) %}
      {% set charging_on_or_planned = is_state('switch.ev_smart_charging_ev_connected', 'on') and (is_state('sensor.ev_smart_charging_charging', 'on') or is_state_attr('sensor.ev_smart_charging_charging', 'charging_is_planned', true)) %}
      {% set charging_load = (state_attr('sensor.ev_smart_charging_charging', 'charging_number_of_hours') | float(0) / 0.25) | int if charging_on_or_planned else 0 %}
      {% set charging_on_or_planned_2 = is_state('switch.ev_smart_charging_ev_connected_2', 'on') and (is_state('sensor.ev_smart_charging_charging_2', 'on') or is_state_attr('sensor.ev_smart_charging_charging_2', 'charging_is_planned', true)) %}
      {% set charging_load_2 = (state_attr('sensor.ev_smart_charging_charging_2', 'charging_number_of_hours') | float(0) / 0.25) | int if charging_on_or_planned_2 else 0 %}
      {% set dhw_load = 
        6 if is_state('binary_sensor.mitsubishi_next_dhw_run_is_legionella_run', 'on') else
        2 if states('sensor.mitsubishi_tank_water_temperature') | float(0) - states('input_number.dhw_temperature_setpoint') | float(0) < -2 else
        0 %}
      {% set heat_pump_load = 
        8 if is_state('sensor.mitsubishi_operating_mode', 'Heating') or is_state('sensor.mitsubishi_operating_mode', 'Cooling') else
        0 %}
      {% set heat_pump_flow_temp = states('number.mitsubishi_h_c_thermostat_target_temperature') | float(25) %}
      {
        "load_cost_forecast": {{ state_attr('sensor.entsoe_prices_forecast_30', 'prices_36h_15min') | tojson }},
        "prod_price_forecast": {{ state_attr('sensor.entsoe_prices_forecast_30', 'prices_36h_15min_return') | tojson }},
        {% if state_attr('sensor.solcast_forecast_data', 'forecasts') != [] %}"pv_power_forecast": {{ state_attr('sensor.solcast_24hrs_forecast_15m', 'forecasts')[:horizon] | tojson }},{% endif %}
        "prediction_horizon": {{ horizon | tojson }},
        "soc_init": {{ (states('sensor.hyper_2000_electric_level') | float(0) / 100) | round(2) | tojson }},
        "soc_final": {{ 0.4 if is_state('sensor.season', ['autumn', 'winter']) else 0.6 }},
        "operating_timesteps_of_each_deferrable_load": {{ [
          charging_load, 
          charging_load_2, 
          dhw_load, 
          heat_pump_load
        ] | tojson }},
        "def_load_config": [
          {},
          {},
          {},
          {
            "thermal_battery": {
              "supply_temperature": {{ heat_pump_flow_temp | tojson }},
              "volume": 22.0,
              "start_temperature": 21.5,
              "min_temperatures": {{ [21.0] * horizon }},
              "max_temperatures": {{ [26.0] * horizon }},
              "carnot_efficiency": 0.44,
              "u_value": 0.33,
              "envelope_area": 258.8,
              "ventilation_rate": 0.5,
              "heated_volume": 380.7,
              "window_area": 22.8,
              "shgc": 0.5,
              "internal_gains_factor": 0
            }
          }
        ]
      }

And this is my config (fourth deferrable load is my heat pump):

{
  "adjusted_pv_model_max_age": 24,
  "adjusted_pv_regression_model": "LassoRegression",
  "adjusted_pv_solar_elevation_threshold": 10,
  "battery_charge_efficiency": 0.9,
  "battery_charge_power_max": 1201,
  "battery_discharge_efficiency": 0.89,
  "battery_discharge_power_max": 899,
  "battery_dynamic_max": 0.9,
  "battery_dynamic_min": -0.9,
  "battery_maximum_state_of_charge": 1,
  "battery_minimum_state_of_charge": 0.2,
  "battery_nominal_energy_capacity": 3840,
  "battery_stress_cost": 0,
  "battery_stress_segments": 10,
  "battery_target_state_of_charge": 0.5,
  "compute_curtailment": true,
  "continual_publish": false,
  "costfun": "profit",
  "delta_forecast_daily": 2,
  "end_timesteps_of_each_deferrable_load": [
    0,
    0,
    0,
    0
  ],
  "historic_days_to_retrieve": 2,
  "influxdb_database": "homeassistant",
  "influxdb_host": "localhost",
  "influxdb_measurement": "W",
  "influxdb_port": 8086,
  "influxdb_retention_policy": "autogen",
  "influxdb_use_ssl": false,
  "influxdb_verify_ssl": false,
  "inverter_ac_input_max": 1200,
  "inverter_ac_output_max": 800,
  "inverter_efficiency_ac_dc": 1,
  "inverter_efficiency_dc_ac": 1,
  "inverter_is_hybrid": false,
  "inverter_stress_cost": 0,
  "inverter_stress_segments": 10,
  "load_cost_forecast_method": "hp_hc_periods",
  "load_forecast_method": "naive",
  "load_negative": false,
  "load_offpeak_hours_cost": 0.1419,
  "load_peak_hour_periods": {
    "period_hp_1": [
      {
        "start": "02:54"
      },
      {
        "end": "15:24"
      }
    ],
    "period_hp_2": [
      {
        "start": "17:24"
      },
      {
        "end": "20:24"
      }
    ]
  },
  "load_peak_hours_cost": 0.1907,
  "logging_level": "DEBUG",
  "lp_solver_timeout": 45,
  "maximum_power_from_grid": 17000,
  "maximum_power_to_grid": 17000,
  "method_ts_round": "nearest",
  "minimum_power_of_deferrable_loads": [
    0,
    0,
    0,
    0,
    0
  ],
  "model_type": "load_forecast",
  "modules_per_string": [
    8
  ],
  "nominal_power_of_deferrable_loads": [
    11000,
    7200,
    2500,
    1000
  ],
  "num_lags": 48,
  "num_threads": 0,
  "number_of_deferrable_loads": 4,
  "open_meteo_cache_max_age": 30,
  "operating_hours_of_each_deferrable_load": [
    0,
    0,
    0,
    0
  ],
  "optimization_time_step": 15,
  "perform_backtest": false,
  "photovoltaic_production_sell_price": 0.1419,
  "production_price_forecast_method": "constant",
  "pv_inverter_model": [
    "GoodWe_Technologies_Co___Ltd___GW5000A_MS__240V_"
  ],
  "pv_module_model": [
    "JA_Solar_JAM72S01_375_PR"
  ],
  "sensor_linear_interp": [
    "sensor.power_load_no_var_loads_filter"
  ],
  "sensor_power_load_no_var_loads": "sensor.power_load_no_var_loads_filter",
  "sensor_power_photovoltaics": "sensor.pv_power",
  "sensor_power_photovoltaics_forecast": "sensor.p_pv_forecast",
  "sensor_replace_zero": [
    "sensor.pv_power"
  ],
  "set_battery_dynamic": false,
  "set_deferrable_load_single_constant": [
    false,
    false,
    true,
    false
  ],
  "set_deferrable_startup_penalty": [
    0,
    0,
    10,
    10
  ],
  "set_nocharge_from_grid": false,
  "set_nodischarge_to_grid": false,
  "set_total_pv_sell": false,
  "set_use_adjusted_pv": false,
  "set_use_battery": true,
  "set_use_pv": true,
  "set_zero_min": true,
  "sklearn_model": "KNeighborsRegressor",
  "split_date_delta": "48h",
  "ssl_no_verify": false,
  "start_timesteps_of_each_deferrable_load": [
    0,
    0,
    0,
    0
  ],
  "strings_per_inverter": [
    2
  ],
  "surface_azimuth": [
    169
  ],
  "surface_tilt": [
    40
  ],
  "treat_deferrable_load_as_semi_cont": [
    false,
    false,
    true,
    false
  ],
  "use_influxdb": false,
  "use_websocket": true,
  "var_model": "sensor.power_load_no_var_loads",
  "weather_forecast_method": "open-meteo",
  "weight_battery_charge": 0,
  "weight_battery_discharge": 0.05
}

Thanks!

Hi All

Why does the pv curtailing happen when the rates are negative then charge when rates are ok.

This is so odd that the system would consider charging when it could be discharging.

Would this be a setting?


This morning I see what appears to be EMHASS crashing?
Has anyone else seen this? The watchdog didn’t reboot it.

[2026-01-26 21:45:00 +0100] [34] [WARNING] Maximum request limit of 1030 exceeded. Terminating process.
[2026-01-26 21:45:00,674] INFO in web_server: No WebSocket connection to close
[2026-01-26 21:45:00,674] INFO in web_server: Quart shutdown complete
[2026-01-26 21:45:03 +0100] [22] [ERROR] Unhandled signal: cld
[2026-01-26 22:26:50 +0100] [22] [CRITICAL] WORKER TIMEOUT (pid:34)