Detect pump short cycling from start count and runtime
A pump may still deliver water while starting far too often. Counting starts and comparing them with total runtime can expose a leaking line, failed pressure vessel, bad non-return valve or unstable control before the pump simply stops.
The fault this project detects
The target is an abnormal concentration of starts: many separate on periods within a measured window, usually with low total runtime. Continuous high demand is a different fault and should not be labelled short cycling.
The example monitors a pressure pump through a reliable running binary sensor. History Stats calculates how many on periods occurred and how many hours the pump ran inside the same rolling window.
The evidence model
A useful alarm does not promote one noisy reading straight to an emergency. It combines measurements that fail in different ways:
- Starts in window. History Stats type count measures how many on periods occur in the chosen interval.
- Runtime in window. A second History Stats entity distinguishes rapid short runs from a legitimate long draw.
- Expected use. Irrigation, filling or heavy household demand provides context for a busy pump.
- Persistence. The alert is based on a completed pattern, not one bounce or a Home Assistant restart.
Home Assistant identifies the duty pattern; it does not declare the failed component. The pressure vessel, check valve, pressure switch, pipework and water demand still need inspection.
Freshness is separate evidence
An entity can keep a plausible old value without becoming unavailable. Inspect last_changed on a source that should genuinely change, and work out your own longest healthy interval between meaningful state changes by scrolling back through that entity's History over a representative stretch of normal operation and timing the biggest gap between genuine changes. If the measured process can legitimately stay flat, add a changing source heartbeat or timestamp; do not treat an unchanged temperature, level or power value as proof that the transport is alive.
Rate maths: never copy a gradient
The Trend integration expresses min_gradient in units per second. The arithmetic example from the project brief is 4 °C/hour ÷ 3600 = 0.00111 °C/s. That is an example conversion, not a threshold for this equipment. Calculate the rate from your own timestamped readings: open History for the entity, pick two points recorded during normal operation, and divide the change in value between them by the number of seconds that elapsed to get the site-specific gradient to enter as min_gradient. A Derivative helper can instead display a friendlier per-hour unit when configured that way.
Collect this data before choosing a threshold
Capture quiet overnight periods, normal household demand and the busiest legitimate use. Verify that the running binary sensor has a clean state and does not chatter around standby power.
| Measurement | Why it matters | Required value |
|---|---|---|
binary_sensor.pressure_pump_running | Clean on periods and transition timestamps | Pull this entity's own History for a representative period and check its on/off transitions line up cleanly with real pump runs. |
sensor.pump_starts_in_window | Normal start count by use pattern | Read this sensor's own History over a normal-use period and note the start count that's typical for your property. |
sensor.pump_runtime_in_window | Normal total on-time in the same window | Read this sensor's own History over the same period and note your normal total on-time, in hours. |
water-use schedule | Known irrigation or transfer activity | List your own known irrigation, transfer or heavy-use periods, from a timer, controller log or memory, so they can be told apart from a fault. |
pump controller | Minimum run or anti-cycle behaviour | Check your own pump controller's manual or nameplate for any built-in minimum-run or anti-cycle setting it already applies. |
The experiment that makes this article defensible
- Record a quiet period with no deliberate water use and note spontaneous starts.
- Run one normal tap, irrigation zone or transfer cycle and capture start count plus total runtime.
- Confirm that one long run does not look like many short runs in History Stats.
- If a known short-cycling event already exists, preserve its trace before repairs; do not create pressure faults deliberately.
Keep your own record as you calibrate: timestamped History readings, your Home Assistant version, the exact entity IDs you used, and a screenshot or two of the History or dashboard views that show the pattern. Do not stage an unsafe electrical, refrigeration, pressure, battery or water fault merely to make a graph.
Create the helpers
Use Settings → Devices & services → Helpers. Create the named Number or Toggle helpers before loading the templates. Where this guide uses a Derivative, Statistics, History Stats, Integral, Schedule or Utility Meter helper, the current Home Assistant interface can create it; the source links below describe the current options.
| Helper or derived entity | Purpose | Setting |
|---|---|---|
input_number.pump_cycle_window_hours | Rolling observation window | Enter a window, in hours, long enough to smooth over one normal demand episode on your own property, based on what you saw in History. |
input_number.pump_short_cycle_warning_starts | Start count that deserves review | Enter a start count, within that window, clearly above your own quiet-baseline pattern from History. |
input_number.pump_short_cycle_critical_starts | Severe start count | Enter a higher start count than the Warning figure, reflecting a genuinely severe pattern for your own pump. |
input_number.pump_short_cycle_max_runtime_hours | Maximum total runtime still considered short cycling | Enter the maximum total runtime, in hours, within the window that's still consistent with short runs rather than legitimate demand on your own property. |
input_boolean.pump_short_cycle_monitoring_enabled | Master notification gate | Leave this switched off until you've tested the automation and are ready to receive real alerts. |
input_number.pump_short_cycle_critical_repeat_minutes | Critical repeat spacing | Enter a repeat interval, in minutes greater than zero, based on how often you want repeat critical alerts. |
Current Home Assistant YAML
Merge top-level keys with your existing configuration instead of duplicating them. Replace the example entity IDs consistently. If you keep automations in automations.yaml, paste only the automation list items there, without a top-level automation: wrapper.
Count on periods and measure their total time
History Stats requires exactly two of start, end and duration. These sensors use a templated rolling start and now as the end. Its count includes a pump already on at the start boundary, which is acceptable when the threshold is calibrated with the same method.
sensor:
- platform: history_stats
name: "Pump starts in window"
unique_id: pump_starts_in_window
entity_id: binary_sensor.pressure_pump_running
state: "on"
type: count
start: >-
{{ now() - timedelta(
hours=states('input_number.pump_cycle_window_hours') | int(0)) }}
end: "{{ now() }}"
- platform: history_stats
name: "Pump runtime in window"
unique_id: pump_runtime_in_window
entity_id: binary_sensor.pressure_pump_running
state: "on"
type: time
start: >-
{{ now() - timedelta(
hours=states('input_number.pump_cycle_window_hours') | int(0)) }}
end: "{{ now() }}"
template:
- binary_sensor:
- name: "Pump short-cycle warning"
unique_id: pump_short_cycle_warning
default_entity_id: binary_sensor.pump_short_cycle_warning
device_class: problem
availability: >
{{ has_value('sensor.pump_starts_in_window')
and has_value('input_number.pump_short_cycle_warning_starts') }}
state: >
{{ (states('sensor.pump_starts_in_window') | float)
>= (states('input_number.pump_short_cycle_warning_starts') | float) }}
attributes:
reason: >-
{{ states('sensor.pump_starts_in_window') }} starts and
{{ states('sensor.pump_runtime_in_window') }} running hours
inside the configured window.
- name: "Pump short-cycle critical"
unique_id: pump_short_cycle_critical
default_entity_id: binary_sensor.pump_short_cycle_critical
device_class: problem
state: >
{{ (states('sensor.pump_starts_in_window') | float(0))
>= (states('input_number.pump_short_cycle_critical_starts') | float(999999))
and (states('sensor.pump_runtime_in_window') | float(999999))
<= (states('input_number.pump_short_cycle_max_runtime_hours') | float(-999999)) }}Warning and Critical notification actions
Replace the mobile notification action and media-player entity with your own. Put critical-monitoring-alert.mp3 in /media, test it manually, and leave the monitoring Toggle off until calibration is complete.
automation:
- id: pump_short_cycle_warning_push
alias: "Pump cycling - Warning push"
mode: single
triggers:
- trigger: state
entity_id: binary_sensor.pump_short_cycle_warning
to: "on"
conditions:
- condition: state
entity_id: input_boolean.pump_short_cycle_monitoring_enabled
state: "on"
actions:
- action: notify.mobile_app_your_phone
data:
title: "Pump cycling warning"
message: >-
{{ state_attr('binary_sensor.pump_short_cycle_warning', 'reason')
or 'The warning condition is active. Check Home Assistant for evidence.' }}
- id: pump_short_cycle_critical_repeat
alias: "Pump cycling - Critical repeat"
mode: restart
triggers:
- trigger: state
entity_id: binary_sensor.pump_short_cycle_critical
to: "on"
conditions:
- condition: state
entity_id: input_boolean.pump_short_cycle_monitoring_enabled
state: "on"
actions:
- action: notify.mobile_app_your_phone
data:
title: "Pump cycling critical"
message: "The critical condition is active. Check the equipment and evidence now."
- action: media_player.play_media
target:
entity_id: media_player.alert_speaker
data:
media_content_id: "media-source://media_source/local/critical-monitoring-alert.mp3"
media_content_type: "audio/mpeg"
- repeat:
while:
- condition: state
entity_id: binary_sensor.pump_short_cycle_critical
state: "on"
- condition: template
value_template: >-
{{ states('input_number.pump_short_cycle_critical_repeat_minutes') | int(0) > 0 }}
sequence:
- delay:
minutes: >-
{{ states('input_number.pump_short_cycle_critical_repeat_minutes') | int(0) }}
- condition: state
entity_id: binary_sensor.pump_short_cycle_critical
state: "on"
- action: notify.mobile_app_your_phone
data:
title: "Pump cycling still critical"
message: "The critical condition remains active."
- action: media_player.play_media
target:
entity_id: media_player.alert_speaker
data:
media_content_id: "media-source://media_source/local/critical-monitoring-alert.mp3"
media_content_type: "audio/mpeg"
Why the logic works
Start count alone confuses one long draw with repeated starts. Total on-time inside the identical window separates the two patterns. The rolling window must be longer than a normal demand episode but short enough to expose the damaging cluster you care about.
The design is intentionally diagnostic rather than controlling. It reports an impossible or abnormal relationship; it does not bypass equipment protection or decide that a single suspected cause is proven.
Severity tiers without alert fatigue
| Tier | Meaning | Action |
|---|---|---|
| Advisory | Pump starts are accumulating faster than the quiet baseline. | Dashboard only |
| Warning | Start count reaches the measured abnormal level in the rolling window. | One push notification |
| Critical | The start count is severe while total runtime remains characteristic of short runs. | Push, repeat and audible alert |
A weak clue remains visible without interrupting you. A Warning requires persistence or corroboration. Critical is reserved for direct danger or several independent signals. That separation is what stops a useful monitoring system becoming notification wallpaper.
Troubleshooting
- The count is one higher than expected
- History Stats count can include an on state already active at the window start. Calibrate and interpret the sensor using that documented behaviour.
- The running sensor flickers
- Add hysteresis or delay to the power-derived running entity before counting it.
- Irrigation always triggers it
- Use a separate operating profile or suppress Warning during known irrigation while leaving raw counts visible.
- History is incomplete after a restart
- Confirm Recorder retention covers the full rolling window and allow History Stats to repopulate.
- A continuous leak does not short-cycle
- That is a different pattern: long runtime or continuous flow. Create a separate long-run fault instead of stretching this rule.
Safety and limits
Do not alter pressure-switch settings, accumulator pre-charge or pump controls without the correct procedure. Stored pressure and electrical supply can injure. Use a pump technician, plumber or licensed electrician as the equipment requires.
If a webhook is added later, treat its webhook_id like a password, keep local_only: true unless a reviewed design requires otherwise, and never use an unauthenticated webhook for locks, garage doors, power isolation or anything destructive.
Official sources
These are the primary documentation pages used for the design. Check them against the Home Assistant or ESPHome version you are actually running.
Related Smart Home Fix guides
- Fix unreliable entities before trusting alarms
- Separate smart-home devices without breaking discovery
- Reduce false alarms without hiding real faults
FAQ
What counts as pump short cycling?
It is a site-specific pattern of repeated short runs rather than one universal number of starts. Pump type, pressure vessel, demand and controller settings all matter.
Why measure total runtime as well as starts?
Many starts with low total runtime describes short cycling. A similar start count with long total runtime may reflect legitimate demand or a different fault.
Does History Stats count transitions?
Its count type counts periods in which the entity matched the requested state and may include an entity already in that state at the start boundary. Calibrate with that documented behaviour.
Can this diagnose a failed pressure tank?
It can reveal a duty pattern consistent with several faults, including a pressure-vessel problem. It cannot identify the component without physical testing.
Will the rolling count survive limited Recorder history?
Only if Recorder retains enough detailed history for the selected window. Verify retention and entity history before relying on the result.