Maximum PV self-consumption heat pump (2/2)
My heat pump was anything but smart until I married it to an ESP32 microcontroller. Since the marriage, it has been monitored by Home Assistant and controlled via automation. Combined with the performance data of the PV system, Home Assistant is able to use the electricity produced as directly as possible for heating and thus increase self-consumption.
The automation in action: in green: PV production; in yellow: the electricity consumption of the heating system.
If possible, the heating is only activated at times when the PV system is producing.
In detail, the behavior could ideally look like this: If the PV yield and the PV forecast allow it, the heat pump starts early with hot water preparation and later with heating mode. Additional energy can be consumed simultaneously by the heating element to further increase the hot water temperature. By consuming the energy directly at an early stage, the battery is charged more slowly, which maintains self-consumption for as long as possible.
Screed as buffer storage
A small buffer cylinder with 300 liters is installed in my heating system for hydraulic decoupling. If I run the circulation pumps for the underfloor heating on the lowest setting, the cylinder is discharged in about an hour. Thanks to the very slow underfloor heating, the heating can still be deactivated for a few hours without affecting the room temperature. The reason for this is that the screed has an enormous storage potential. Ideally, the heating is no longer active after sunset, which means that the PV system battery can be used purely for household electricity in the evening and at night.
Implementation – Availability
The hardware required for the solution presented here is inexpensive and works in principle with almost any heating system. In addition to the previous article "Controlling heating: PV surplus > ESP32 & Home Assistant", this article covers my implementation in detail.
As additional components create additional dependencies, I paid particular attention during implementation to ensuring that the heating continues to function normally in the event of a possible failure of the added components. If Home Assistant, the WLAN or the ESP fail, the actual heating system takes over again.
I am aware that my implementation is tailored to my heating system and therefore probably cannot be copied one-to-one or only partially for other projects, but I hope that one or the other can get a few ideas.
Goal: Control heat pump for heating and hot water as well as heating rod, depending on the PV surplus
As mentioned in the previous article, my heating system is a heat pump with a buffer tank and boiler. To (over)control my heating, I use Home Assistant and an ESP32 with a 4-channel relay board and several temperature sensors.
The heating is overridden depending on the room temperature: when the PV system is producing enough electricity and there is sufficient capacity in the battery storage system.
Although the ESP is very stable, I have installed the relays in such a way that if the ESP fails, the relays drop and the temperature sensors of the actual heating system are activated: The heater works as before in the event of a fault. The ESP is also able to trigger certain actions independently, even if the Wi-Fi or Home Assistant is not available.
In addition to the heat pump, a Zigbee socket makes it possible to activate a heating element in the boiler and thus achieve higher hot water temperatures. In order not to overload the socket, I connected the heating rod with only 2kW: functionally, at least for hot water preparation, a low-cost replacement for the Fronius Ohmpilot.
My setup combines the information from the following articles:
- Hardware for Home Assistant? Variants: HAOS vs. Docker
- Home Assistant + DIY microcontroller + ESP Home (Docker)
- Control heating: PV surplus > ESP32 & Home Assistant
- PV surplus, hot water: heating element
The heart of the ESP-Home project
The ESP32 microcontroller does not integrate directly into the heating control system, nor does it replace it. Instead, the ESP32 microcontroller was built around the existing control system: with its own sensors and its own relays for manipulating the existing heating control system. Dozens of cables lead from the ESP32 for the heating (over)control system to several temperature sensors: installed in the most diverse places in the heating system and the house. I still don't have the courage to post a picture of the wiring, as it currently looks more like a test setup, so I've shown it schematically:
Here is a rough overview of the main components:
- DS18B20 temperature sensors for the heating and outside temperature, see: DS18B20 - Temperature sensors in ESP-Home
DS18B20 temperature sensors installed:
- Directly in the heat pump in the heat exchanger
- Boiler
- Buffer tank
- Flow temperature
- Return temperature
- Outdoor temperature
- ...
- DHT11 temperature sensors for certain living areas: ESPHome: Temperature and humidity sensors DHT11/22
- Relay board for controlling the heating: Relay Board ESP32 - ESPHome
- Relay1: simulate hot water temperature: without Relay2: hot water
- Relay2: Fake hot water cold
- Relay3: Fake buffer tank temperature: none Relay4: Hot buffer tank
- Relay4: Fake cold buffer tank
The complete ESP Home project for the ESP looks like this:
esphome:
name: heating
esp32:
board: esp32dev
framework:
type: arduino
# Enable logging
logger:
# Enable Home Assistant API
api:
encryption:
key: "???"
ota:
password: "???"
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Heating Fallback Hotspot"
password: "???"
on_disconnect:
if:
condition:
switch.is_on: relay4
then:
- switch.turn_off: relay3
- switch.turn_off: relay4
captive_portal:
dallas:
- pin: GPIO13
update_interval: 22s
# Relais
switch:
- platform: gpio
pin: GPIO32
name: Relay-heating-water-fake
id: relay1
inverted: true
- platform: gpio
pin: GPIO33
name: Relay-heating-water-kalt
id: relay2
inverted: true
- platform: gpio
pin: GPIO25
name: Relay-heating-buffer-fake
id: relay3
inverted: true
- platform: gpio
pin: GPIO26
name: Relay-heating-buffer-cold
id: relay4
inverted: true
# Individual sensors
sensor:
- platform: uptime
name: heating.uptime
- platform: dht
pin: GPIO23
model: DHT11
temperature:
name: "Kitchen.Temperature"
id: kitchen_temperature
humidity:
name: "Kitchen.Humidity"
- platform: dht
pin: GPIO1
model: DHT11
temperature:
name: "Livingroom.Temperature"
id: livingroom_temperature
humidity:
name: "Livingroom.Humidity"
- platform: dht
pin: GPIO21
model: DHT11
temperature:
name: "Office.Temperature"
id: office_temperature
humidity:
name: "Office.Humidity"
- platform: dht
pin: GPIO19
model: DHT11
temperature:
name: "Fronthouse.Temperature"
id: fronthouse_temperature
humidity:
name: "fronthouse.Humidity"
- platform: dht
pin: GPIO3
model: DHT11
temperature:
name: "Toiletgroundfloor.Temperature"
id: toiletgroundfloor_temperature
humidity:
name: "Toiletgroundfloor.Humidity"
- platform: dallas
address: 0x??
name: "heating.Vorlauf"
- platform: dallas
address: 0x??
name: "heating.Ruecklauf"
- platform: dallas
address: 0x??
name: "heating.heatexchanger"
on_value_range:
- above: 60.0
then:
- switch.turn_off: relay1
- switch.turn_off: relay2
- switch.turn_off: relay3
- switch.turn_off: relay4
- platform: dallas
address: 0x??
name: "heating.Hotwater"
on_value_range:
- below: 40.0
then:
- switch.turn_off: relay1
- switch.turn_off: relay2
- above: 48
then:
- switch.turn_on: relay1
- switch.turn_off: relay2
- platform: dallas
address: 0x??
name: "heating.bufferstorage"
- platform: dallas
address: 0x??
name: "outside.temperature"
- platform: combination
type: median
name: "groundfloor.Temperature"
sources:
- source: kitchen_temperature
- source: livingroom_temperature
- source: office_temperature
- source: toiletgroundfloor_temperature
- source: fronthouse_temperature
As can be seen from the project, I have implemented certain actions directly via triggers on the ESP32. This enables the ESP32 to transfer control to the actual heating control system if the WLAN fails or if Home Assistant is not available. For example, all relays are deactivated when the heat exchanger reaches 60 °C, the hot water reaches its target temperature or falls below a certain value.
A temperature of over 60 °C in the heat exchanger does not occur in my system in normal operation and if this is reached, the ESP32 switches and the heating system takes over control with its own temperature sensors. The same applies to the hot water: The system cannot exceed 48 °C for the hot water and once this temperature is reached, the relays are deactivated and the heat pump is no longer fooled with incorrect temperature values. In addition, the ESP32 returns control to the heating system if the water falls below 40 °C: a kind of emergency so that the water does not get completely cold.
If the heating was activated via the simulated temperature value of the relay for the buffer tank and the WLAN connection is lost, the ESP also drops the heating relays.
The temperature values of the living rooms are combined into one value via a "combination sensor" of the median type and serve as the basis for the heating override implemented in Home Assistant. (In contrast to the mean value, the median is better able to deal with individual outliers)
Home Assistant automation
I have created my own template sensor helper so that the HA automation can react better to a PV surplus:
{% set remaining_load = (states("sensor.byd_battery_box_premium_hv_maximale_kapazitat") | default(0) | float *
(1-((states("sensor.byd_battery_box_premium_hv_ladezustand") | default(0) |float))/100))/1000 %}
{% set before_onehourbeforesunset= as_timestamp(now()) | timestamp_custom('%H%M') | float < (states.sun.sun.attributes.next_setting |as_timestamp - (60*60))| timestamp_custom("%H%M", True) | float %}
{% set pvsumkw = states("sensor.pv_sum_kw") | default(0) | float %}
{% set bat_gt_2 = states("sensor.byd_available_capacity") | default(0) | float > 2 %}
{{ states("sensor.pv_remaining_today") | default(0) | float > (remaining_load+3)
and before_onehourbeforesunset and pvsumkw > 1 and bat_gt_2 }}
The template sensor switches to on when more than 1kW is produced by the PV system, the fill level of the PV battery is at least 2 kWh and the PV forecast for the remaining day is greater than the remaining capacity of the battery. Ok, I can see that this sentence is difficult to digest, perhaps an example will help: Assuming the 10 kWh battery is 50% charged, there are still 5 kWh left until it is full. If the PV forecast for the remaining day is greater than 5 kWh, the template triggers: There is a potential surplus and the target temperature of the rooms is raised via the following automation. This means that the heating can be activated even before the battery is 100% charged. And if the PV forecast is correct, the battery will still be fully charged later that day. Since the PV forecast of the Home Assistant Integration: "Forecast.Solar" is only updated every hour, the template adds 3 kWh (remaining_load+3) at this point to be on the safe side.
Based on this template, the scripts for the relays and the already implemented logic of the ESP, the HA automation can be simplified somewhat, here is the complete YAML code:
description: ""
mode: parallel
trigger:
- platform: time_pattern
minutes: /30
- platform: numeric_state
entity_id:
- sensor.pv_sum
for:
hours: 0
minutes: 10
seconds: 0
below: 3500
- platform: numeric_state
entity_id:
- sensor.byd_battery_box_premium_hv_chargingstatus
above: 70
- platform: state
entity_id:
- sensor.pv
- platform: numeric_state
entity_id:
- sensor.grid_power_kw
for:
hours: 0
minutes: 1
seconds: 0
above: 0
- platform: numeric_state
entity_id:
- sensor.grid_power_kw
for:
hours: 0
minutes: 2
seconds: 0
below: -2
- platform: numeric_state
entity_id:
- sensor.heating_hotwater
above: 60
- platform: numeric_state
entity_id:
- sensor.heating_hotwater
below: 43
condition: []
action:
- alias: Hot water automatically
if:
- condition: time
after: "13:00:00"
- condition: time
before: "23:00:00"
- condition: numeric_state
entity_id: sensor.heating_hotwater
below: 43
then:
- service: script.hotwater_automatic
data: {}
- alias: Hot water on
if:
- condition: state
entity_id: sensor.pv
state: "True"
- condition: state
entity_id: switch.relay_heating_water_cold
state: "off"
- condition: numeric_state
entity_id: sensor.heating_hotwater
below: 44
then:
- service: script.hotwater_on
data: {}
- alias: Hot water heating element
if:
- alias: grid_power_kw < -2
condition: template
value_template: >-
{{( states("sensor.grid_power_kw") | float -
(states("sensor.hotwater_active_power") | default(0) | float / 1000))
< - 2 }}
- condition: numeric_state
entity_id: sensor.byd_battery_box_premium_hv_chargingstatus
above: 98
- condition: numeric_state
entity_id: sensor.heating_hotwater
above: 44
below: 60
- condition: state
entity_id: switch.relay_heating_water_cold
state: "off"
then:
- device_id: ""
domain: ""
entity_id: ""
else:
- device_id: ""
domain: ""
entity_id: ""
- alias: Puffer
if:
- condition: or
conditions:
- condition: numeric_state
entity_id: sensor.outside_temperature
above: 22
- alias: vor 2:30 -> 21°C
condition: and
conditions:
- condition: or
conditions:
- condition: time
after: "21:00:00"
enabled: true
- condition: time
before: "02:30:00"
- condition: numeric_state
entity_id: sensor.groundfloor_temperature
above: 20.99
- alias: vor 3:30 -> 21.3°C
condition: and
conditions:
- condition: time
before: "03:30:00"
- condition: numeric_state
entity_id: sensor.eg_temperatur
above: 21.3
- alias: 5:00 to 2h after und PV Forecast > 25kWh -> 21.3°C
condition: and
conditions:
- condition: numeric_state
entity_id: sensor.pv_remaining_today
above: 25
- condition: numeric_state
entity_id: sensor.eg_temperatur
above: 21.3
- condition: time
after: "05:00:00"
- condition: sun
before: sunrise
before_offset: "2:00:00"
- condition: state
entity_id: sensor.pv
state: "False"
- condition: state
entity_id: input_boolean.pv_snow
state: "off"
- condition: and
conditions:
- condition: state
entity_id: sensor.pv
state: "False"
- condition: numeric_state
entity_id: sensor.groundfloor_temperature
above: 21.99
alias: PV- 21.99°C
- condition: numeric_state
entity_id: sensor.groundfloor_temperature
above: 22.99
then:
- if:
- alias: >-
Heating recently activated? wait at least 30 minutes before doing
this
Turn off
condition: template
value_template: >-
{% set timeSinceStateChanged = ((as_timestamp(now()) -
as_timestamp(states.sensor.heating.last_changed)) / 60) | int %}
{{ false if (states("sensor.heating") == "Heating" and
timeSinceStateChanged < 30) else true }}
then:
- service: script.buffer_off
data: {}
else:
- if:
- condition: numeric_state
entity_id: sensor.pv_sum
above: 3500
- device_id: ""
domain: ""
entity_id: ""
condition: device
- alias: Wait at least 1 hour before switching it back on
condition: template
value_template: >-
{% set timeSinceStateChanged = ((as_timestamp(now()) -
as_timestamp(states.binary_sensor.heating_active.last_changed)) /
60) | int %} {{
false if (
states("sensor.heating") != "Heizen" and
timeSinceStateChanged < 60
) else true
}}
then:
- service: script.bufferstorage_heating
data: {}
else:
- service: script.bufferstorage_automatic
data: {}
trace:
stored_traces: 100
max: 10
The automation is started every 30 minutes and additionally when certain events occur for a faster reaction. The actions are composed as follows:
Action: Hot water automatically
Between 13:00 and 23:00 and when the hot water temperature is below 43 °C, the heat pump should take over control:"Automatic hot water script". The threshold values in the actual heating control system become active below 43 °C, so hot water preparation usually starts when the script is triggered.
Action: Hot water on
If there is a PV surplus(PV+ template sensor triggers) and the hot water temperature is below 44 °C, the water should be activated by simulating a low boiler temperature:"Script: Hot water on".
The ESP and its automation take over switching off when the target temperature above 48 °C is reached.
Action: Hot water heating element
If the water temperature is above 44 °C, the battery is fully charged and there is an additional PV surplus ("sensor.grid_power_kw"), the immersion heater is activated. Of course, the heating element is not as efficient as the heat pump, but it enables the water to be heated to temperatures above the range of the heat pump. As the feed-in tariff for excess electricity is currently very low, raising the hot water temperature increases the chance that the heat pump will no longer need to be activated in the evening.
Action: Heating
The action for the heating initially uses conditions under which the heating should be deactivated:
- If the outside temperature is above 22 °C, the heating is deactivated.
- Depending on the time and the room temperature, the heating is deactivated when one of the following events occurs:
- Before 2:30 a.m. at night, if the room temperature is above 21 °C,
- before 3:30 a.m. if the room temperature is above 21.3 °C and
- depending on the PV forecast above 22 °C.
- With a PV surplus: Template PV+: the heating is only deactivated at a room temperature above 23 °C.
- Depending on the charge level of the battery, the heating is forced from 70 %. Below 70 % charge level, both heating relays are deactivated and the actual heating control decides whether the heating should be activated.
In practice – PV self-sufficiency
Here is an example of automation at the beginning of March:
The automation only really works when the sun is shining: on days when sufficient PV electricity is generated (in green), the heating (in yellow) can be started during the day. If the PV forecast is low, the automation allows a target temperature of up to 22 °C before sunrise, which means that the heating is activated earlier. Here is another example of a few days with 100% self-consumption including battery status:
The energy distribution of our house for the beginning of 2024:
month | Self-sufficiency |
Electricity consumption HA Energy Dashboard: |
---|---|---|
January |
In January, the control system still had relatively little effect, as the PV electricity produced was nowhere near enough. |
|
february |
The relatively mild February allowed the control system to consume the majority of the PV electricity directly with the heat pump on many days. |
|
März |
In March, the control developed its full potential and was already able to cover 89% of electricity consumption with PV power. |
|
April |
In April, almost the entire electricity requirement was covered by PV power. |
|
Mai |
||
Juni |
Conclusion
Admittedly, my setup is far from plug and play, and the automation only worked after some tuning. But if you like to work with automatic processes, you can really let off steam with ESP-Home and Home Assistant and save a lot of electricity.
{{percentage}} % positive
THANK YOU for your review!
Questions / Comments
(sorted by rating / date) [all comments (best rated first)]
Thank you for sharing your work! I also have an old heating pump that I want to make a bit smarter. But I don't have PV, batteries or any buffer to the heating system. I think I will start by reading temperatures and power consumption to see how it actually operates with its own automation. Then I might add room temperature sensors and output a fake median value to the heat pump.