Smart Home Fix

Detect a pump running dry before it fails

A pump can be electrically on while moving little or no water. Home Assistant can detect that impossible relationship by comparing real power, measured flow, source level and how long the condition lasts.

The fault this project detects

The target fault is a commanded or powered pump that is not producing the expected flow. Depending on the pump, dry-running power may rise, fall or look deceptively normal, so a universal watt threshold is unsafe.

The worked system is a rainwater or bore transfer pump with a circuit power sensor, a flow sensor, a source tank-level sensor and Home Assistant. It observes only; the pump's manufacturer protection remains the primary safeguard.

Pump dry-run evidence flow Multiple measurements are combined into advisory, warning and critical fault states. Pump powerWater flowSource tank levelSustained runtime Evidence model direction + context agreement + duration Advisory dashboard only Warning one push Critical repeat + audible
Power proves electrical activity; flow proves hydraulic work. Tank level and duration add the missing context.

The evidence model

A useful alarm does not promote one noisy reading straight to an emergency. It combines measurements that fail in different ways:

  1. Running state. Measured power establishes that the motor is drawing meaningful power rather than trusting a relay command.
  2. Hydraulic result. Flow below the pump's measured normal minimum shows that the expected work is not happening.
  3. Source condition. A low source tank or bore header strengthens the dry-run interpretation.
  4. Power signature and time. Power outside the healthy running band or a sustained no-flow period filters brief priming and valve transitions.

The alarm describes the contradiction - powered pump, inadequate flow - not an unproven mechanical diagnosis. A blocked line, closed valve, lost prime, sensor fault or dry source can produce similar evidence.

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 several healthy starts, steady runs, stops and any manufacturer-approved priming period. Record power and flow on the same timestamps. Do not deliberately run a pump dry to create training data.

MeasurementWhy it mattersRequired value
sensor.bore_pump_powerStandby, start and stable running powerRead this from your own History: the W it settles at during standby, at start-up, and while running steadily.
sensor.bore_flow_ratePriming flow and healthy steady flowRead this from your own History: the L/min during priming and once flow is steady.
sensor.source_tank_levelLowest normal operating levelCheck this sensor's own History for the lowest level it reaches under normal use, in whichever unit it reports.
run durationLongest healthy prime and no-flow transitionTime it from your own History graph: how long your longest healthy prime and no-flow transition actually take.
fault protectionExisting controller, float and thermal protectionLook up your own controller's make, model and built-in protection behaviour from its manual or nameplate.

The experiment that makes this article defensible

  1. Capture multiple normal starts from command through stable flow, with power and flow aligned by timestamp.
  2. Close only a normal downstream outlet so the pump stops through its intended pressure control; do not dead-head it beyond manufacturer operation.
  3. Record a low-source event only if it occurs naturally or can be simulated through the controller's approved test function.
  4. Replay thresholds with monitoring disabled and confirm normal priming remains Advisory or clear.

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 entityPurposeSetting
input_number.pump_running_wattsSeparates meaningful motor power from standbyEnter the running-power figure, in W, that you measured for this pump above.
input_number.pump_minimum_flow_l_minLowest healthy flow after primingEnter the lowest healthy post-priming flow, in L/min, that you measured above.
input_number.pump_normal_power_low_wattsLower edge of healthy running bandEnter the lower W edge of your own pump's healthy running band, from your measurements above.
input_number.pump_normal_power_high_wattsUpper edge of healthy running bandEnter the upper W edge of your own pump's healthy running band, from your measurements above.
input_number.pump_low_source_levelSource level that corroborates dry-runningEnter your tank's own low-level threshold, in % or litres to match your sensor, from its History.
input_number.pump_dry_run_hold_minutesNo-flow persistence before WarningEnter the no-flow duration, in minutes, you measured as normal priming above, plus a margin.
input_number.pump_dry_run_critical_hold_minutesPersistence before CriticalEnter a longer minutes value than the Warning hold, based on how much further you're willing to let the fault run before escalating.
input_boolean.pump_dry_run_monitoring_enabledMaster notification gateLeave this switched off until you've tested the automation and are ready to receive real alerts.
input_number.pump_dry_run_critical_repeat_minutesCritical repeat spacingEnter 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.

Compare electrical input with hydraulic output

The power band is deliberately two-sided because a fault can change motor load in either direction. All limits come from helpers populated with your measured pump data.

template:
  - binary_sensor:
      - name: "Bore pump running"
        unique_id: bore_pump_running
        default_entity_id: binary_sensor.bore_pump_running
        device_class: running
        availability: >
          {{ has_value('sensor.bore_pump_power')
             and has_value('input_number.pump_running_watts') }}
        state: >
          {{ (states('sensor.bore_pump_power') | float)
             >= (states('input_number.pump_running_watts') | float) }}

      - name: "Bore pump power abnormal"
        unique_id: bore_pump_power_abnormal
        default_entity_id: binary_sensor.bore_pump_power_abnormal
        device_class: problem
        availability: >
          {{ has_value('sensor.bore_pump_power')
             and has_value('input_number.pump_normal_power_low_watts')
             and has_value('input_number.pump_normal_power_high_watts') }}
        state: >
          {% set watts = states('sensor.bore_pump_power') | float %}
          {% set low = states('input_number.pump_normal_power_low_watts') | float %}
          {% set high = states('input_number.pump_normal_power_high_watts') | float %}
          {{ is_state('binary_sensor.bore_pump_running', 'on')
             and (watts < low or watts > high) }}

      - name: "Pump dry-run warning"
        unique_id: pump_dry_run_warning
        default_entity_id: binary_sensor.pump_dry_run_warning
        device_class: problem
        delay_on:
          minutes: >
            {{ states('input_number.pump_dry_run_hold_minutes') | int(0) }}
        availability: >
          {{ has_value('sensor.bore_flow_rate')
             and has_value('input_number.pump_minimum_flow_l_min') }}
        state: >
          {{ is_state('binary_sensor.bore_pump_running', 'on')
             and (states('sensor.bore_flow_rate') | float)
                 <= (states('input_number.pump_minimum_flow_l_min') | float) }}
        attributes:
          reason: >-
            Pump power is {{ states('sensor.bore_pump_power') }} W
            but measured flow is {{ states('sensor.bore_flow_rate') }} L/min.

      - name: "Pump dry-run critical"
        unique_id: pump_dry_run_critical
        default_entity_id: binary_sensor.pump_dry_run_critical
        device_class: problem
        delay_on:
          minutes: >
            {{ states('input_number.pump_dry_run_critical_hold_minutes') | int(0) }}
        state: >
          {{ is_state('binary_sensor.pump_dry_run_warning', 'on')
             and (is_state('binary_sensor.bore_pump_power_abnormal', 'on')
                  or ((states('sensor.source_tank_level') | float(999999))
                      <= (states('input_number.pump_low_source_level') | 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_dry_run_warning_push
    alias: "Pump dry-run - Warning push"
    mode: single
    triggers:
      - trigger: state
        entity_id: binary_sensor.pump_dry_run_warning
        to: "on"
    conditions:
      - condition: state
        entity_id: input_boolean.pump_dry_run_monitoring_enabled
        state: "on"
    actions:
      - action: notify.mobile_app_your_phone
        data:
          title: "Pump dry-run warning"
          message: >-
            {{ state_attr('binary_sensor.pump_dry_run_warning', 'reason')
                or 'The warning condition is active. Check Home Assistant for evidence.' }}

  - id: pump_dry_run_critical_repeat
    alias: "Pump dry-run - Critical repeat"
    mode: restart
    triggers:
      - trigger: state
        entity_id: binary_sensor.pump_dry_run_critical
        to: "on"
    conditions:
      - condition: state
        entity_id: input_boolean.pump_dry_run_monitoring_enabled
        state: "on"
    actions:
      - action: notify.mobile_app_your_phone
        data:
          title: "Pump dry-run 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_dry_run_critical
              state: "on"
            - condition: template
              value_template: >-
                {{ states('input_number.pump_dry_run_critical_repeat_minutes') | int(0) > 0 }}
          sequence:
            - delay:
                minutes: >-
                  {{ states('input_number.pump_dry_run_critical_repeat_minutes') | int(0) }}
            - condition: state
              entity_id: binary_sensor.pump_dry_run_critical
              state: "on"
            - action: notify.mobile_app_your_phone
              data:
                title: "Pump dry-run 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

Power says the motor is doing something; flow says whether the system is moving water. Source level and the healthy power band provide independent corroboration. The delay allows normal priming but must remain shorter than the equipment's safe no-flow period, which comes from the manufacturer or a pump technician - not from this page.

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

TierMeaningAction
AdvisoryPump power is present while flow is still establishing.Dashboard only
WarningPowered operation continues without the measured minimum flow.One push notification
CriticalNo-flow operation persists and a low source or abnormal motor-load signature agrees.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 alert fires during priming
Measure the longest healthy prime and set persistence from that evidence, within the pump's safe operating limit.
Flow reads zero at low demand
Confirm the flow sensor's starting threshold and orientation; some meters cannot resolve small flows.
Power looks normal during a fault
That is why flow is primary evidence. Do not force a power-signature rule that the pump does not exhibit.
Variable-speed power moves constantly
Compare power against commanded speed or pressure setpoint, or use a model-specific operating envelope.
The tank sensor is unavailable
Keep the no-flow Warning available, but do not claim source-level corroboration until the sensor is valid.

Safety and limits

Do not use a smart plug as motor protection unless the device and installation are specifically engineered for the load. Fixed wiring, current clamps and pump isolation are licensed-electrical work. Keep thermal, pressure, dry-run and float protection supplied by the manufacturer.

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

FAQ

Can watts alone detect a pump running dry?

Not reliably across different pumps. Dry-running can raise, lower or barely change power. Flow, source level, runtime and the pump's own measured healthy power band make the conclusion stronger.

How long should no flow be allowed?

Use the longest measured healthy priming period while remaining inside the manufacturer's safe no-flow limit. If that limit is unknown, this is where a pump technician should set the protection.

What if the flow sensor fails?

Treat an unavailable or stale flow sensor as a monitoring fault. Do not convert missing flow data into proof of dry-running without independent evidence.

Can Home Assistant replace a dry-run controller?

No. Home Assistant is a useful monitoring and escalation layer, but dedicated pump protection operates closer to the equipment and should remain in place.

Should the automation turn the pump off?

Only after a site-specific electrical and hydraulic safety review. The article intentionally alerts without controlling the pump because a false stop can also cause damage or interrupt essential water.