Using excess solar power to heat swimming pool

This is a simple project to heat a swimming pool using excess solar power.

It’s my first attempt with Home Assistant and it was surprisingly easy to do once I’d figured out the basics of YAML syntax. It’s also my first forum post so apologies for any rookie mistakes.

I have rooftop PV solar and a swimming pool heated by a heat pump. A single water pump is used to both filter the pool and push water through the heat pump when it’s heating.

Here in Australia the power companies buy excess power from you at a fraction of the cost for which they sell it to you. So it’s costly to heat the pool unless you actually use the electricity at the time you’re producing it. Obviously a home battery would solve the problem but that’s beyond my budget.

I jotted down some basic requirements for my project:

  1. The heat pump should only be turned on when there’s enough excess power being generated. I want to measure excess, not actual, production because if I’m already using power for the house there’s none spare for the pool.

  2. The heat pump won’t work if there’s no water flow. So the water pump must be turned on whenever the heater is on.

  3. However the water pump must run for a minimum 4 hours per day to filter the pool. The heating logic must not affect that. The pump must always run for 4 hours to avoid the pool becoming a hot green soup!

  4. The water pump has no wifi control or timer. It’s just a dumb on-off switch from the mains so needs a wifi-enabled power switch.

  5. I don’t want the pumps turning on and off every 2 minutes as the sun peeks out from behind a cloud. That’ll destroy the motors pretty quickly. I need to use some kind of rolling average to make a decision every X minutes, for example every 60 minutes.

Step 1 - Check my equipment will work with Home Assistant

The rooftop solar uses Enphase Envoy which is integrated with Home Assistant. So no set up required, Home Assistant detected it immediately. It reports real-time power production and consumption from which I can calculate excess power.

The heat pump is wifi enabled. Reading this post it’s apparent most heat pumps are made by one company and use Tuya for wifi control. That was indeed the case. Adding the heat pump to Home Assistant was simply a case of following the integration instructions. It’s a bit of a faff to get the necessary keys but not hard. Just follow the instructions closely.

The water pump has no built-in control but given it’s just on/off a wifi power switch is all that’s needed. Because the heat pump uses Tuya I figured I’d keep it the same and go with a Tuya-based switch too. This is perfect for the job and is waterproofed. Ideal for a pool area and saves messing about installing waterproof housings etc. Having already configured Tuya for the heat pump, Home Assistant detected the switch immediately, no problem.

Step 2 - Calculate excess power and smooth it into a 60 minute rolling average

Calculating excess power is simple. Just a template sensor to calculate production minus consumption:

template:
  - sensor:
      name: "Excess Power"
      unit_of_measurement: "W"
      state: "{{ (states('sensor.envoy_XXXXXXXXX_current_power_production')|float) - (states('sensor.envoy_XXXXXXXXX_current_power_consumption')|float) }}"

That’s an instantaneous reading, which I smoothed into a 60 minute rolling average using a statistics sensor:

sensor:
  - platform: statistics
    name: "Excess Power 60 minute average"
    entity_id: sensor.excess_power
    state_characteristic: mean
    max_age:
      minutes: 60

Step 3 - Trigger pump and heater based on average excess power

This is all done in automations.yaml, the full version of which is below.

As mentioned before I don’t want the pumps switching on/off too quickly. I decided a minimum one hour interval between switching was appropriate. So the automation begins with an hourly timer:

#01. AVERAGE POWER TRIGGER FOR POOL HEATING
- id: average_power_trigger_pool_heating
  alias: average power trigger pool heating
  trigger:
    platform: time_pattern
    minutes: "/59"
  action:

    - choose:

When the timer event fires, one of three situations needs to be handled:

  1. The system is off but there’s enough excess power (>1kw) to turn it on. Turn the water pump on and set the heater to heat:
    #System is OFF. Generating sufficient excess power (>1kw) to turn it on.
      - conditions:
            alias: "Generating excess power and system currently off"
            and:
                - condition: numeric_state
                  entity_id: sensor.excess_power_60_minute_average
                  above: 1000
                - condition: state
                  entity_id: switch.pool_pump_socket_1
                  state: "off"                  
        sequence:
          - service: switch.turn_on
            target:
              entity_id: switch.pool_pump_socket_1
          - service: climate.set_hvac_mode
            target:
              entity_id: climate.pool_heater
            data:
              hvac_mode: 'heat'
  1. The system is already on and there’s still enough excess power to keep it on. This is no different to #1 except the excess power check is lower because the heater is active and using 1kw. Excess power just needs to be positive to tell me I’ve got enough to keep running the heater. I set it to 100w just to give a margin of error.

There will be a better way to combine this with the code for #1 but I this is simple enough and easy to follow:

    #System is ON. Generating sufficient excess power (>100w) to keep it turned on
      - conditions:
            alias: "Generating excess power and system currently on"      
            and:
                - condition: numeric_state
                  entity_id: sensor.excess_power_60_minute_average
                  above: 100
                - condition: state
                  entity_id: switch.pool_pump_socket_1
                  state: "on"                  
        sequence:
          - service: switch.turn_on
            target:
              entity_id: switch.pool_pump_socket_1
          - service: climate.set_hvac_mode
            target:
              entity_id: climate.pool_heater
            data:
              hvac_mode: 'heat'              
  1. The system is on but there’s not enough power to keep it on. Turn it off.
    #System is ON. NOT generating sufficient excess power (<100w). Turn it off.
      - conditions:
            alias: "Not generating excess power. Turn system off."
            and:
                - condition: numeric_state
                  entity_id: sensor.excess_power_60_minute_average
                  below: 100
                - condition: state
                  entity_id: switch.pool_pump_socket_1
                  state: "on"                  
        sequence:
          - service_template: >
                {%- if is_state("input_boolean.pool_pump_onoff_timer", "on") -%}
                switch.turn_on
                {%- else -%}
                switch.turn_off
                {% endif %}
            target:
              entity_id: switch.pool_pump_socket_1
          - service: climate.set_hvac_mode
            target:
              entity_id: climate.pool_heater
            data:
              hvac_mode: 'off'              

The one complication here is the 4 hour requirement for the water pump. The heater can always be turned off but, if the pump is on as part of the 4 hour filtration cycle, it needs to stay on.

My solution was to implement the 4 hour timer using an input_boolean.

input_boolean:
  pool_pump_onoff_timer:
    name: "Pool Pump OnOff Timer"
    initial: "off"

A separate automation timer sets the boolean to true at 8am and false at 12pm and the water pump switch is linked to the boolean. Whenever the boolean changes the pump turns on/off accordingly.

That’s why #3 uses a service_template to control pool_pump_socket_1. The pool pump is not just blindly turned off. It’s turned on or off as appropriate for the 4 hour timer.

Performance

I’m sure there are more elegant ways to do this but it works perfectly. It’s added 15 degrees C to the pool in just over a week with no increase in power drawn from the grid.

Obviously you can tinker with the settings to make it more aggressive or conservative and it took a bit of time watching power readings to figure out appropriate excess power limits for my particular equipment.

But really the hardest part was figuring out the YAML syntax for what I wanted to do and I’ve implemented, with a $100 Raspberry Pi, something that even $1000 dedicated pool controllers cannot do.

I hope this is of use to others in keeping their pool costs down.

Full configuration.yaml


# Loads default set of integrations. Do not remove.
default_config:

# Text to speech
tts:
  - platform: google_translate

automation: !include automations.yaml
script: !include scripts.yaml
scene: !include scenes.yaml

# Example configuration.yaml entry
logger:
  default: info


template:
  - sensor:
      name: "Excess Power"
      unit_of_measurement: "W"
      state: "{{ (states('sensor.envoy_122122028187_current_power_production')|float) - (states('sensor.envoy_122122028187_current_power_consumption')|float) }}"

sensor:
  - platform: statistics
    name: "Excess Power 60 minute average"
    entity_id: sensor.excess_power
    state_characteristic: mean
    max_age:
      minutes: 60

  - platform: statistics
    name: "Excess Power 30 minute average"
    entity_id: sensor.excess_power
    state_characteristic: mean
    max_age:
      minutes: 30
      
  - platform: statistics
    name: "Excess Power 02 minute average"
    entity_id: sensor.excess_power
    state_characteristic: mean
    max_age:
      minutes: 2

input_boolean:
  pool_pump_onoff_timer:
    name: "Pool Pump OnOff Timer"
    initial: "off"

Full automations.yaml

#01. AVERAGE POWER TRIGGER FOR POOL HEATING
- id: average_power_trigger_pool_heating
  alias: average power trigger pool heating
  trigger:
    platform: time_pattern
    minutes: "/59"
  action:

    - choose:

    #System is OFF. Generating sufficient excess power (>1kw) to turn it on.
      - conditions:
            alias: "Generating excess power and system currently off"
            and:
                - condition: numeric_state
                  entity_id: sensor.excess_power_60_minute_average
                  above: 1000
                - condition: state
                  entity_id: switch.pool_pump_socket_1
                  state: "off"                  
        sequence:
          - service: switch.turn_on
            target:
              entity_id: switch.pool_pump_socket_1
          - service: climate.set_hvac_mode
            target:
              entity_id: climate.pool_heater
            data:
              hvac_mode: 'heat'

              

    #System is ON. Generating sufficient excess power (>100w) to keep it turned on
      - conditions:
            alias: "Generating excess power and system currently on"      
            and:
                - condition: numeric_state
                  entity_id: sensor.excess_power_60_minute_average
                  above: 100
                - condition: state
                  entity_id: switch.pool_pump_socket_1
                  state: "on"                  
        sequence:
          - service: switch.turn_on
            target:
              entity_id: switch.pool_pump_socket_1
          - service: climate.set_hvac_mode
            target:
              entity_id: climate.pool_heater
            data:
              hvac_mode: 'heat'              



    #System is ON. NOT generating sufficient excess power (<100w). Turn it off.
      - conditions:
            alias: "Not generating excess power. Turn system off."
            and:
                - condition: numeric_state
                  entity_id: sensor.excess_power_60_minute_average
                  below: 100
                - condition: state
                  entity_id: switch.pool_pump_socket_1
                  state: "on"                  
        sequence:
          - service_template: >
                {%- if is_state("input_boolean.pool_pump_onoff_timer", "on") -%}
                switch.turn_on
                {%- else -%}
                switch.turn_off
                {% endif %}
            target:
              entity_id: switch.pool_pump_socket_1
          - service: climate.set_hvac_mode
            target:
              entity_id: climate.pool_heater
            data:
              hvac_mode: 'off'              



#02. POOL PUMP DAILY ON/OFF TIMER 
- id: pool_pump_timer_on
  alias: pool pump timer on
  trigger:
    platform: time
    at: "08:30:00"
  action:
    service: input_boolean.turn_on
    target:
      entity_id: input_boolean.pool_pump_onoff_timer
      
- id: pool_pump_timer_off
  alias: pool pump timer off
  trigger:
    platform: time
    at: "10:30:00"
  action:
    service: input_boolean.turn_off
    target:
      entity_id: input_boolean.pool_pump_onoff_timer
      
      
      
#03. NIGHTLY HA RESTART
#- id: restart_ha
#  alias: Restart HA
#  trigger: 
#    platform: time
#    at: "02:00:00"  
#  action:
#    - service: homeassistant.restart
- id: reboot_pi
  initial_state: true
  alias: 'Reboot pi daily'
  trigger:
  - platform: time
    at: '02:00:00'
  action:
  - service: hassio.host_reboot
8 Likes

Great first post! Thanks for writing this up - it’s exactly what I am looking to do with my excess solar. Still waiting for the pool heat pump to be delivered though! But I will work through your solution when it arrives.

This is brilliant! I’m in the same situation, very unhappy with the amount I get back from the energy company and been wondering for weeks what I can do to self-consume my excess solar power. Looks like I will be getting a heat pump for the pool.

Thanks for sharing

Hey folks, hoping someone can help me. If I should start a separate thread please let me know. I am essentially trying to do exactly what you have posted here, but have a slightly different situation:

  1. Right now I have a Gas Heater heating the pool/jacuzzi (400k btu) and NO electric heater. This is the legacy setup from when I bought the house.

  2. I installed solar panels and often have excess solar (particularly in the winter) and would like to dump this excess into heating the pool (as selling it back to power company doesn’t provide much value.

  3. Every time I talk to a pool guy about it they want to replace the gas heater with a giant electric heater. My thinking is that they are thinking I want something that can heat the pool/jacuzzi as fast as the gas heater would, which is not the case. I am looking for something that can heat the pool even if it only adds a degree or two a day.

  4. I am in the phoenix area, so unfortunately in the winter I could easily lose 5 degrees over night, so essentially I would need to heat at 15,000 gallons of water 4-6 degrees. This makes me think I need around 75k btu electric heater?

  5. This is all complicated by the fact that the electrical back to the pool equipment area is a long run, and fairly minimal at the moment. I will likely need to upgrade the sub panel and conduit.

Any thoughts would be GREATLY appreciated.

Hi Robert,

I’m the author of the original post and what you want to do sounds perfectly normal to me. It’s exactly what I’m doing and is a classic use of a pool HEAT PUMP as opposed to a straight HEATER.

Are your pool guys definitely talking about a heat pump?

My pool is much smaller than yours (~8000 gallons) but it operates exactly as you suggest. I only power the heat pump using solar (i.e. during the day) and the most it can add is about 6 degrees a day. It then loses about 3-5 degrees overnight depending upon the weather.

So I can slowly get it to a temperature and keep it there for days on end when the weather is good. Melbourne gets pretty cold in the winter so I shut it down. But it sounds like Phoenix climate would allow you to run it year round with the right sized pump.

The crucial thing is what size heat pump to get. I’m in Australia so all my research used metric units of litres and kilowatts. This is an Australian website for one of the major pool suppliers here but has a useful calculation that came out about right for me ( pool size litres / 2500 ). It suggests 14kw for me and I got 15kw which works well.

Maybe you could use this to get a very rough idea of size required, in KW, convert to BTUs and see if that’s anywhere near what your pool guys are suggesting.

The other tip is to get a good thermal pool cover if you don’t have one already and cover the pool every night when needed. It made a massive difference. Probably halved the losses overnight.

You’re right about the electrical supply. I had to have a qualified electrician install it and add a dedicated sub panel etc.

Hope it goes well.

Amazing post! I’m in the same situation. I was using simple automation - if production is over 600W turn on pool heating which is dead simple and not effective. Thanks mate!

I’m looking for the same solution to use the excess solar power and not deliver it back to the grid as much as possible.

The only problem I can not overcome (in my thinking process) is the following.

I can measure the output of the inverter, which is always a positive number.
When I’m producing power, my power meter is a returning a negative number (YouLess).
Will this return a unrealistic value
Power production: 3500watt
Youless power usage: -1500watt

Then from the calculations 3500 - -1500 = 5000
Or am I just calculationg it wrong in my head? :smiley:

I am able to implement the sensors and template suggested and of course will measure it a couple of days.

At this time I’m only using grid power, as it night time over here and there is no sun. :smiley:

afbeelding

If I were you I would try something like:

template:
 - sensor:
      - name: Whatever you want
        unique_id: "whatever_you_want"
        unit_of_measurement: "W"
        device_class: power
        state: "{{ states('sensor.[SENSOR WITH MINUS AND PLUS VALUES]') | int(0) | abs }}"

This will display positive values as positive and negative values as positive.

Thanks for your suggestion, But it seems my helpers are not helping me and really fighting with it.
This all due to my, still little, knowledge.

The sensor worked for one day.

I have defined:

{{ (states('sensor.verbruiktotal_custom')|float) - (states('sensor.total_teruglevering')|float) }}"

This gives me a error on the developer tab;

ValueError: Template error: float got invalid input ‘unknown’ when rendering template ‘{{ (states(‘sensor.verbruiktotal_custom’)|float) - (states(‘sensor.total_teruglevering’)|float) }}"’ but no default was specified

This is likely due to the helper sensor is in a unknow state.
In the screenshot I can see the helpers I’ve created for my high and low data points (midnight tariff and day tariff),
Those are calculating fine.
I want to combine them into one again (sensor.verbruiktotal.custom)

Feeling I’m going down my own rabbit hole.
Maybe I have “overengineered” some things and would appreciate some help.

Basic Values I have from my devices:
Youless:

  • energy high (sensor.energy_high)
  • energy low (sensor.energy_low)
  • delivery high (sensor.energy_delivery_high)
  • delivery low (sensor.energy_delivery_low)

Inverter:

  • DC output power (sensor.zonnepanelen_tcp_dc_power).

Goal is to have:

  • energy high + energy low = usage ( reset on a daily basis so I only have the values for today)
  • delivery high + delivery low = delivery (reset on a daily basis so I only have the values for today)

This is something I can do in a script or automation later on.Iif delivery = > 5kWh and DC output = > 1kWh do something.

Hi @jamesianjordan, thanks for starting this thread and for staying with it now for a couple of years.

I’m in Canberra and thinking about a similar build. Similar weather to Melbourne, except our winters are colder! Currently, in the middle of the day I’m often wasting 15-30kWh of power because the grid just doesn’t want it. So the solar panels power down to what the house needs. So I figure, I may as well dump that as heat into the pool.

I don’t have a pool heat pump yet. You said most heat pumps here are Tuya under the hood. Which brand did you get?