With the recent high energy prices, I wanted to optimize my energy usage costs.
The biggest consumer is the house heating and hot water production, all lights and other appliances pale in comparison on how much energy they draw.
My system is:
- CTC geothermal heat pump (onoff)
- 500 liter accumulator tank
- radiators using heat from the accumulator tank to heat the house
- hot water production from coils in the accumulator tank (produced on demand, minimal risks for bacteria)
The accumulator tank works as a battery, so why not charge it up when prices are low, and use the stored energy while the prices are high. Given enough price variance, this should give good savings!
Early on I tried a schedule with an on/off automation for the pump, where I manually scheduled the run times. It is hard though to estimate energy used for different days and times, and much work to check prices and set up the schedule every day.
Additionally, the higher the water temperature, the lower the efficiency of the heat pump, and higher losses from the accumulator tank.
So I wanted a “smart” system that could predict the energy consumption hour by hour, and produce and store just enough heat to get through the expensive hours.
Since high water temperature is a big cost, I decided to use a homeassistant local calendar to schedule when we want hot water for showers. Otherwise the minimum temperature is what the radiators need given the outdoor temperature, which is musch lower.
So I created a model with the following inputs:
- heat pump production capacity (W) and power draw (W)
- desired indoor temperature
- current indoor temperature
- current outdoor temperature
- outdoor temperature hourly forecast the coming 24 hours
- schedule with times for showers, wanted minimum temp and approximate power usage
- minimum wanted hot water temperature
- hourly electricity prices (end prices with tax and all, or the optimization will make bad decisions)
Using these parameters it is rather easy to estimate heat consumption in W, and minimum required temperature in the tank hour by hour
Given the the consumption, energy prices and pump capacity, one can create a linear optimization model for when to run the pump.
However, I don’t know how to make that run as a script in HA, so I created a REST web API inside a docker image running on the same server as the HA docker, which I could call using the HA Rest API and sensors.
Said and done, wiring it all together, here is the generated plan for the upcoming 24 hours, visualized by HA:
First is the graph showing predicted heat water consumption in Watts, by hot water, radiators, heat loss and showers.
Second is the planned pump production (output) in W period by period, plotted against the electricity price for each period.
Lastly is a graph showing the prediced accumulator tank temperature, radiator temperature, pump thermostat setpoint, and outside temp.
On the very top is shown the predicted cost for running the pump according to this plan the coming 24 hours. It is quite fun, as the min hot water temp and indoor temp sliders are interactive, and reflects “immediately” on the estimated cost.
There is also a hidden slider, where changing the accumulator tank volume shows immediately how cost could be affected.
Configuration in HA is a bit special, as REST sensors cannot send a template payload as body.
To get around that I first defined a rest_command which can use a template body, and the defined the rest sensors to get the last state from the rest api (I can do this since I control the webserver).
An automation is triggered every 20 minutes to recalculate the optimization, and update the sensors
The entire configuration looks like this:
input_boolean:
use_optimizer:
name: Använd Optimerare
icon: mdi:graph
input_number:
warm_water_min_temp:
name: Min varmvatten temp
icon: mdi:thermometer
min: 20
max: 40
step: 1
unit_of_measurement: "°C"
mode: slider
acc_tank_volume:
name: Acc volym
icon: mdi:thermometer
min: 1
max: 1001
step: 100
unit_of_measurement: "liter"
mode: slider
rest_command:
calculate_optimization_result2:
url: "http://172.18.0.8:3000/optimize"
method: POST
content_type: 'application/json'
payload: >
{% set indoor_target_temp = states('number.indoor_target_temp') | float(18) %}
{% set current_outdoor_temp = states('sensor.torild_air_temperature') | float(18) %}
{% set forecast = state_attr('weather.forecast_home_hourly', 'forecast') %}
{% set idle_min_temp = states('input_number.warm_water_min_temp') | float(20) %}
{% set data = namespace(outdoor = [{"time": now().isoformat(), "temp": current_outdoor_temp}]) %}
{% for f in forecast %}
{% set data.outdoor = data.outdoor + [{ "time": f.datetime, "temp": f.temperature}] %}
{% endfor %}
{% set body = {
"time": now().isoformat(),
"split_hours_into": 3,
"plan_hours": 24,
"acc_spec": {
"liters": states('input_number.acc_tank_volume') | float(500),
"max_temp": 55,
"min_temp": 20,
"current_temp": states('sensor.acc_tank_avg_temp'),
"temp_loss_per_hour_degrees": 0.02189
},
"pump_spec": {
"start_delay_minutes": 2,
"max_temp": 55,
"power": [{
"below_degrees": 35,
"heating_power_watt": 8200,
"consumed_power_watt": 1980
}, {
"below_degrees": 45,
"heating_power_watt": 8000,
"consumed_power_watt": 2200
}, {
"below_degrees": 50,
"heating_power_watt": 7900,
"consumed_power_watt": 3000
}, {
"heating_power_watt": 7800,
"consumed_power_watt": 3100
}
]
},
"indoor": {
"target_temp": indoor_target_temp,
"passive_heating_degrees": states('number.passive_heating_degrees') | float(3),
"current_temp": states('sensor.house_hall_temp') | float(18),
},
"outdoor": data.outdoor,
"radiator": {
"c_flow": 0.45,
"power_per_temp_delta": 400
},
"prices":
{
"today":state_attr('sensor.nordpool_kwh_se3_sek_3_095_025', 'today'),
"tomorrow":state_attr('sensor.nordpool_kwh_se3_sek_3_095_025', 'tomorrow'),
},
"showers": [{
"start": state_attr('calendar.shower', 'start_time'),
"end": state_attr('calendar.shower', 'end_time'),
"temp": 35,
"energy": 1000
}],
"hot_water": {
"min_temp": states('input_number.warm_water_min_temp') | float(20),
"average_power": 100
}
}
%}
{{ body | tojson(2) }}
automation:
- alias: calculate_optimal_heating_2
id: 9cad5a69-4a9e-4d31-83c3-c2530776ee63
mode: restart
trigger:
- platform: time_pattern
minutes: /20
- platform: state
entity_id:
- calendar.shower
- input_boolean.use_optimizer
- number.indoor_target_temp
- input_number.warm_water_min_temp
- number.passive_heating_degrees
- input_number.acc_tank_volume
action:
- service: rest_command.calculate_optimization_result2
- service: homeassistant.update_entity
entity_id: sensor.optimized_target_pump_thermostat2
rest:
- resource: http://172.18.0.8:3000/optimize
method: GET
scan_interval: 120
timeout: 120
sensor:
- name: optimized_target_pump_thermostat2
unique_id: 'c8f646df-9792-43e2-aab4-a0b5360af52a'
unit_of_measurement: "°C"
device_class: "temperature"
value_template: >
{{ value_json.plan[0].pump.target_temp }}
json_attributes:
- ok
- cost
- plan
- params
- result
- name: optimized_pump_production2
unit_of_measurement: "W"
device_class: "power"
value_template: >
{{ value_json.plan[0].pump.production }}
- name: optimized_target_flow_temp2
unit_of_measurement: "°C"
device_class: "temperature"
value_template: >
{{ value_json.plan[0].rad_flow_temp }}
- name: optimized_heat_consumption2
unit_of_measurement: "W"
device_class: "power"
value_template: >
{{ -value_json.plan[0].consumption.total }}
- name: optimized_predicted_24h_cost2
unique_id: '32154361-7a3f-43de-9ddd-a50e240af4cf'
unit_of_measurement: "SEK"
device_class: "monetary"
value_template: >
{{ value_json.cost | round(2) }}