Some hardware links on this page are affiliate links. As an Amazon Associate I earn from qualifying purchases. We may earn a commission if you buy through them, at no extra cost to you. See the affiliate disclosure.
A mechanical circulator timer that runs fifteen minutes, eight times a day, keeps hot water “mostly” ready and spends the rest of its time stirring a loop that is already hot. A few minutes is usually enough for a domestic return loop, and it is too long if the return pipe is already at temperature. This pack turns a smart plug on from a morning schedule window or from bathroom motion, then turns it off when a return-pipe sensor reaches a target or when a short max runtime elapses, whichever comes first. A cooldown stops motion from starting another cycle while someone is still in the room.
Entity ids are blueprint inputs. Do not publish which room the motion sensor sits in beyond “a bathroom,” and do not publish a live plug name.
This is not relay auto-off timer. That pack starts a fixed duration whenever the switch turns on, with no temperature and no schedule window. This pack owns when the pump may start, and it can end the run early. Do not put both on the same plug.
What problem it solves
You want hot water at the far end of the loop during the hours people are up, plus an on-demand run when someone walks into a bathroom outside that rhythm, without leaving the pump on for a quarter-hour block or letting a motion sensor chatter it on and off.
How it works
- Two starts — a time pattern inside the morning window (pack default every 30 minutes between two clock times), or a motion binary going
on. Both are optional inputs; leave motion empty for a schedule-only house. - Cooldown — an
input_datetimestores the last start. A new start is ignored until that timestamp is older than the cooldown (pack default 20 minutes). Motion that stays on does not queue another run. The schedule tick is subject to the same cooldown, so a window tick that lands during a run does nothing. - Plug on —
switch.turn_onthe smart plug, then writenowinto the datetime. The pump’s own timer dial should be off or bypassed; Home Assistant owns the outlet. - Stop early or on time —
wait_for_triggeruntil the return-pipe temperature is at or above the target (pack default 38 °C / 100 °F — set it to the return temperature you actually measure when the far tap runs hot), withtimeoutequal to the max runtime (pack default 3 minutes). Thenswitch.turn_off. - Sensor missing — if you did not add a probe, the wait is just the timeout. If the probe is configured and becomes
unavailablemid-run, the timeout still fires. The pump cannot stay on because a sensor failed. - Outside the window — motion still works if you enable it (that is the off-hours tap). The schedule does not. An optional “someone home” condition is in the pack; leave it empty to skip it. No names go in the blueprint.
A Home Assistant restart during a run can drop a wait_for_trigger. The pack also turns the plug off if it has been on for longer than the max runtime (a state trigger with for:), so a reboot cannot leave the circulator running until the next tick.
Hardware notes
- Plug versus pump. This is for a plug-in circulator. Read the pump nameplate. The linked plug is 15 A at 120 V; a small domestic recirc is usually far under that, and a well pump or a hard-wired boiler circulator is a different job. Do not put a hard-wired pump on a smart plug by cutting into its mains.
- Motion. Any
binary_sensorwith device class motion or occupancy. A PIR you already have is enough. The HC-SR501 is the “I do not have one” option: it can share the probe’s ESP32 or any other board. Set its onboard delay pot short and let Home Assistant own the cooldown — the module’s pot and this blueprint will fight if both try to be the timer. - Probe. Optional. A waterproof DS18B20 strapped to the return pipe (the line coming back to the heater, under the insulation, with the metal tip against copper) tells you the loop is hot. Supply water at the heater is the wrong pipe: it is hot even when the far bathroom is cold. The DIYables-style adapter includes the 4.7 kΩ pull-up; a bare probe needs that resistor between data and VCC.
- Board. ESP32-C6 Super Mini if you are adding the probe. Leave GPIO8, GPIO9, GPIO12, GPIO13, and GPIO15 alone (LED, boot, USB, straps). The YAML uses GPIO4 for the probe and GPIO5 for an optional PIR.
Wi-Fi credentials stay in secrets.yaml. Never paste SSIDs, PSKs, or API keys into a published YAML.
Wiring
| DS18B20 | ESP32-C6 Super Mini |
|---|---|
| Red (VCC) | 3V3 |
| Black (GND) | GND |
| Yellow or white (data) | GPIO4 |
| 4.7 kΩ (if your board does not include it) | Between data and 3V3 |
| HC-SR501 (optional) | ESP32-C6 Super Mini |
|---|---|
| VCC | 5V |
| GND | GND |
| OUT | GPIO5 |
The PIR’s output is a 3.3 V high on most modules and is fine on a 3.3 V GPIO. Power it from 5V, not from 3V3.
Snippet
Blueprint shape. Inputs replace real entity ids:
trigger:
- platform: time_pattern
minutes: !input window_minutes
id: schedule
- platform: state
entity_id: !input motion_sensor
to: "on"
id: motion
- platform: state
entity_id: !input pump_plug
to: "on"
for: !input max_runtime
id: stuck_on
action:
- choose:
- conditions:
- condition: trigger
id: stuck_on
sequence:
- service: switch.turn_off
target:
entity_id: !input pump_plug
- conditions:
- condition: template
value_template: "{{ cooldown_elapsed }}"
- condition: or
conditions:
- condition: trigger
id: motion
- condition: and
conditions:
- condition: trigger
id: schedule
- condition: time
after: !input window_start
before: !input window_end
sequence:
- service: switch.turn_on
target:
entity_id: !input pump_plug
- service: input_datetime.set_datetime
target:
entity_id: !input last_start
data:
datetime: "{{ now() }}"
- wait_for_trigger:
- platform: numeric_state
entity_id: !input return_temp
above: !input target_temp
timeout: !input max_runtime
continue_on_timeout: true
- service: switch.turn_off
target:
entity_id: !input pump_plug
Return-pipe node (substitutions, not a live house). One probe, so the address can be discovered in the logs instead of pasted:
substitutions:
friendly_name: Return pipe
data_pin: GPIO4
motion_pin: GPIO5
one_wire:
- platform: gpio
pin: ${data_pin}
id: pipe_bus
sensor:
- platform: dallas_temp
one_wire_id: pipe_bus
name: ${friendly_name}
unit_of_measurement: "°C"
update_interval: 10s
binary_sensor:
- platform: gpio
pin:
number: ${motion_pin}
mode:
input: true
pullup: true
name: Bathroom motion
device_class: motion
filters:
- delayed_off: 3s
Skip the binary_sensor block if motion already exists somewhere else. Credentials stay in secrets.yaml.
Setup outline
- Confirm the pump is plug-in and the nameplate current is within the plug’s rating. Plug it in, adopt the plug in Home Assistant, and toggle it once while you are next to the pump so you know which outlet is live.
- Optional: flash the ESPHome YAML, strap the probe to the return pipe, and read a temperature that rises when you run the pump from the dashboard.
- Create the last-start
input_datetime. Import the blueprint. Fill the plug, window, motion (or leave it blank), max runtime, cooldown, and the optional temperature target. - Test schedule: set the window to include now and the pattern to every minute, run one cycle, confirm off at the timeout. Put the real 30-minute pattern back.
- Test motion: walk in, confirm one run, wave again inside the cooldown and confirm the plug stays off. If the probe is fitted, run a cycle with the target set just above the idle pipe temperature and confirm the plug drops before the timeout.
Troubleshooting
- Pump never starts in the window. The time condition does not wrap, the cooldown datetime is still recent from a test, or the plug entity is a button rather than a switch. Window start must be earlier than window end for a same-day morning.
- Pump never stops. A second automation or the plug’s own app timer turned it back on, or the stuck-on trigger is missing and a restart ate the wait. Keep a single owner, and keep the
for:off trigger. - Stops in seconds. The target is below the idle return temperature, so the numeric trigger is already true. Measure the pipe cold, measure it after a real hot-tap run, and set the target between them.
- Motion retriggers constantly. Cooldown is 0, or the PIR pot is retriggering and each new
onedge bypasses your condition. The condition has to apply to the motion trigger, not only to the schedule. - Probe stays unknown. Data and VCC swapped, missing pull-up, or the pin is a strap pin. GPIO4 is the pack default. Power the probe from 3V3.
- Both this pack and a duration timer fight. Remove relay auto-off timer from this plug.
Credits
Based on the community thread Scheduling a smart plug for a hot water circulation pump. This write-up is an original on-demand pack: a morning window or bathroom motion, a short max runtime, an earlier stop from a return-pipe sensor, and a cooldown.