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:

  1. Hardware for Home Assistant? Variants: HAOS vs. Docker
  2. Home Assistant + DIY microcontroller + ESP Home (Docker)
  3. Control heating: PV surplus > ESP32 & Home Assistant
  4. 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
5-person household, heating and hot water

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.

positive Bewertung({{pro_count}})
Rate Post:
{{percentage}} % positive
negative Bewertung({{con_count}})

THANK YOU for your review!

Questions / Comments


(sorted by rating / date) [all comments(newest first)]

✍Magnus
2024-03-16 12:08
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.

By continuing to browse the site, you agree to our use of cookies. More Details