Skip to content

Instantly share code, notes, and snippets.

@mirisbowring
Last active April 27, 2026 19:14
Show Gist options
  • Select an option

  • Save mirisbowring/cb3e7c60a549d11866171287f97b4133 to your computer and use it in GitHub Desktop.

Select an option

Save mirisbowring/cb3e7c60a549d11866171287f97b4133 to your computer and use it in GitHub Desktop.
Schedules Pool Pump activity based on min activity per day, blocked times (like night) and has an option to steer the pump with solar overproduction
blueprint:
name: Pool Controller V6 (Auto-Cycle Calculation & PV)
description: "Advanced pool pump controller using a grid sensor for PV surplus. Features anti-cycling delays, a daily minimum runtime guarantee, and an optional 'Force Cycles' toggle. The minimum cycle duration is automatically calculated based on pool volume and pump flow rate (1 full turnover). Requires an Input Select (Mode), Input Boolean (Force Cycle), and a History Stats sensor."
domain: automation
input:
pool_pump_switch:
name: Pool Pump Switch
description: "The smart switch controlling your pool pump (e.g., Shelly)."
selector:
entity:
domain: switch
mode_select_entity:
name: Mode Selector (Input Select)
description: "Helper entity. Required options: 'Auto', 'Manual On', 'Manual Off', '24/7'."
selector:
entity:
domain: input_select
force_cycle_boolean:
name: "Force Cycles Toggle (Input Boolean)"
description: "If active, an ongoing pump cycle will not be interrupted by clouds until 1 full water turnover is completed."
selector:
entity:
domain: input_boolean
pool_volume:
name: Pool Volume (m³)
description: "Total volume of your pool in cubic meters."
default: 6.0
selector:
number:
min: 1.0
max: 200.0
step: 0.5
pump_flow_rate:
name: Pump Flow Rate (m³/h)
description: "How much water your pump moves per hour."
default: 6.0
selector:
number:
min: 0.5
max: 100.0
step: 0.5
runtime_sensor_entity:
name: Runtime Sensor (History Stats)
description: "Sensor tracking today's total pump runtime."
selector:
entity:
domain: sensor
grid_sensor_entity:
name: Grid Power Sensor
description: "Sensor reading your main meter. Negative values = Export/Surplus, Positive values = Import."
selector:
entity:
domain: sensor
start_time_input:
name: Start Time (Window Open)
description: "Earliest time the pump is allowed to run in Auto mode."
default: "08:00:00"
selector:
time:
end_time_input:
name: End Time (Window Close)
description: "Latest time the pump must stop in Auto mode."
default: "20:00:00"
selector:
time:
min_hours_input:
name: Minimum Daily Runtime (Hours)
description: "Target total runtime per day. Will force the pump on if time is running out."
default: 10
selector:
number:
min: 1
max: 24
step: 1
pv_on_threshold_input:
name: PV Turn-On Threshold (Watts)
description: "Positive number. Start pump when exporting more than X watts."
default: 600
selector:
number:
min: 0
max: 10000
pv_on_delay:
name: Turn-On Delay (Seconds)
description: "Surplus must be stable for this long before starting."
default: 60
selector:
number:
min: 0
max: 3600
unit_of_measurement: s
pv_off_threshold_input:
name: PV Turn-Off Threshold (Watts)
description: "Positive number. Stop pump when exporting drops below X watts."
default: 200
selector:
number:
min: 0
max: 10000
pv_off_delay:
name: Turn-Off Delay (Seconds)
description: "Time to wait before stopping. Bridges short clouds to prevent short-cycling."
default: 180
selector:
number:
min: 0
max: 3600
unit_of_measurement: s
mode: restart
trigger:
- platform: state
entity_id: !input mode_select_entity
- platform: template
value_template: "{{ (states(grid_sensor_entity) | float(0)) <= ((pv_on_threshold_input) * -1) }}"
for:
seconds: !input pv_on_delay
- platform: template
value_template: "{{ (states(grid_sensor_entity) | float(0)) > ((pv_off_threshold_input) * -1) }}"
for:
seconds: !input pv_off_delay
- platform: time_pattern
minutes: "/5"
action:
- variables:
switch_entity: !input pool_pump_switch
mode_entity: !input mode_select_entity
force_entity: !input force_cycle_boolean
pool_vol: !input pool_volume
pump_flow: !input pump_flow_rate
# Calculate duration for 1 full turnover in minutes
cycle_min: "{{ ((pool_vol | float(1)) / (pump_flow | float(1))) * 60 }}"
runtime_sensor: !input runtime_sensor_entity
grid_sensor: !input grid_sensor_entity
pv_on_threshold: !input pv_on_threshold_input
pv_off_threshold: !input pv_off_threshold_input
start_time: !input start_time_input
end_time: !input end_time_input
min_hours: !input min_hours_input
current_mode: "{{ states(mode_entity) }}"
force_active: "{{ is_state(force_entity, 'on') }}"
# FIX: Calculate current continuous runtime in ONE step to avoid string conversion errors
current_run_duration_min: "{{ ((now() - states[switch_entity].last_changed).total_seconds() / 60) if is_state(switch_entity, 'on') else 0 }}"
# Logic: Cycle Protection
is_cycle_finished: "{{ current_run_duration_min | float(0) >= cycle_min | float(0) }}"
may_stop_by_pv: "{{ not force_active or is_cycle_finished }}"
# Logic: Grid & PV calculations
grid_val: "{{ states(grid_sensor) | float(0) }}"
has_pv_surplus: "{{ grid_val <= (pv_on_threshold * -1) }}"
is_below_stop_limit: "{{ grid_val > (pv_off_threshold * -1) }}"
# Logic: Daily window and forced runs
current_runtime_total: "{{ states(runtime_sensor) | float(0) }}"
start_hhmm: "{{ start_time[:5] }}"
end_hhmm: "{{ end_time[:5] }}"
is_within_window: "{{ now().strftime('%H:%M') >= start_hhmm and now().strftime('%H:%M') < end_hhmm }}"
hours_left_in_window: "{{ (today_at(end_hhmm) - now()).total_seconds() / 3600 if is_within_window else 0 }}"
hours_left_today: "{{ (today_at('23:59') - now()).total_seconds() / 3600 }}"
needs_forced_run_auto: "{{ is_within_window and (min_hours - current_runtime_total) >= hours_left_in_window }}"
needs_forced_run_24_7: "{{ (min_hours - current_runtime_total) >= hours_left_today }}"
- choose:
# 1. MANUAL OFF
- conditions: "{{ current_mode == 'Manual Off' }}"
sequence:
- service: switch.turn_off
target:
entity_id: "{{ switch_entity }}"
# 2. MANUAL ON
- conditions: "{{ current_mode == 'Manual On' }}"
sequence:
- service: switch.turn_on
target:
entity_id: "{{ switch_entity }}"
# 3. AUTO MODE
- conditions: "{{ current_mode == 'Auto' }}"
sequence:
- choose:
# Fallback: Hard stop if outside window
- conditions: "{{ not is_within_window }}"
sequence:
- service: switch.turn_off
target:
entity_id: "{{ switch_entity }}"
# Turn ON: Forced run needed OR PV surplus is stable
- conditions: "{{ needs_forced_run_auto or has_pv_surplus }}"
sequence:
- service: switch.turn_on
target:
entity_id: "{{ switch_entity }}"
# Turn OFF: PV drops below limit AND no forced run needed AND cycle duration reached (if forced)
- conditions: "{{ is_below_stop_limit and not needs_forced_run_auto and may_stop_by_pv }}"
sequence:
- service: switch.turn_off
target:
entity_id: "{{ switch_entity }}"
# 4. 24/7 MODE
- conditions: "{{ current_mode == '24/7' }}"
sequence:
- choose:
# Turn ON: Forced run needed before midnight OR PV surplus is stable
- conditions: "{{ needs_forced_run_24_7 or has_pv_surplus }}"
sequence:
- service: switch.turn_on
target:
entity_id: "{{ switch_entity }}"
# Turn OFF: PV drops below limit AND no forced run needed AND cycle duration reached (if forced)
- conditions: "{{ is_below_stop_limit and not needs_forced_run_24_7 and may_stop_by_pv }}"
sequence:
- service: switch.turn_off
target:
entity_id: "{{ switch_entity }}"
@mirisbowring

Copy link
Copy Markdown
Author

Smart PV Pool Pump Controller

A Home Assistant blueprint designed to control a pool pump based on solar (PV) surplus, while ensuring your pool water stays clean through a guaranteed daily minimum runtime and anti cycling protection.

Key Features

  • Solar Optimization: Automatically turns the pump on when exporting excess solar power to the grid.
  • Guaranteed Daily Runtime: Set a target (e.g. 10 hours). If solar surplus wasn't enough throughout the day, the automation forces the pump on before your "quiet time" window closes to meet the daily target.
  • Night Quiet Time: Defines an operating window (e.g., 08:00 - 20:00). Outside this window, the pump is strictly turned off (Auto mode).
  • Smart Cycle Calculation: Enter your Pool Volume and Pump Flow Rate. The blueprint automatically calculates the exact time needed for 1 full water turnover.
  • Anti Cycling & Force Cycles: Turn on/off delays prevent rapid switching during patchy clouds. Enable "Force Cycles" to prevent the pump from stopping mid-turnover if a cloud blocks the sun.
  • 4 Operating Modes: Easily switch between Auto, Manual On (Ignores PV and schedule), Manual Off (perfect for swimming), and 24/7 (ignores the night window).

Prerequisites

Before importing this blueprint, you must create the following helper entities in the Home Assistant UI (Settings -> Devices & Services -> Helpers):

1. Mode Selector (Input Select)

Create an Dropdown helper with exactly these four options:

  • Auto
  • Manual On
  • Manual Off
  • 24/7

2. Force Cycles Toggle (Input Boolean)

Create a Switch helper to enable or disable the "Force Cycle" protection.

3. Grid Power Sensor

You need a sensor reading your main house meter's active power in Watts.

  • Important: The logic expects Negative values for Export/Surplus and Positive values for Import/Consumption.

4. Daily Runtime Sensor (History Stats)

Create a History Stats helper via the UI to track today's runtime.

  • Config: Set the "State" to on, the "Start time" to 00:00:00 of the current day, and the "End time" to now().

How "Force Cycles" Works

When the Force Cycles toggle is active, the blueprint calculates the time needed for one full water turnover (e.g., 60 minutes). If the pump starts due to solar surplus, but a cloud covers the sun 20 minutes later, the pump will not turn off. It will continue running until the 60-minute turnover is completed.
Note: The Night Quiet Time boundary overrides this and will always perform a hard stop to prevent nighttime noise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment