Smart Home Fix

Detect a hot-water heating fault before the shower

A cylinder can still be warm when an element, contactor or control path stops doing useful work. Check whether temperature rises when measured power says heating is active, whether two probes agree, and whether the expected heating window has elapsed.

The fault this project detects

This project detects electrical input without the expected tank response, an overdue heating cycle and contradictory top and lower tank probes. It does not diagnose the element or thermostat directly; it identifies evidence that heating performance is abnormal.

The example assumes a storage electric hot-water system with non-invasive temperature probes, whole-circuit power monitoring and a Home Assistant Schedule helper representing when heating is normally available. Controlled-load supply may not follow your own schedule, so model the real installation.

Hot-water fault evidence flow Multiple measurements are combined into advisory, warning and critical fault states. Tank temperature rateElement circuit powerSecond tank probeHeating-window context Evidence model direction + context agreement + duration Advisory dashboard only Warning one push Critical repeat + audible
Power without a matching temperature rise is an early contradiction; schedule and second-probe context reduce nuisance alarms.

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. Value: tank temperature remains the comfort and safety backstop.
  2. Direction: the temperature derivative shows whether stored water is actually heating.
  3. Agreement: top and lower probes expose stratification, probe movement and broader tank response.
  4. Context and time: circuit power and the expected heating window determine when a rise should occur, with persistence for thermal lag.

The strongest early clue is a contradiction: sustained element-circuit power but no calibrated positive temperature response. That can appear before the upper tank probe reaches a cold threshold.

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 normal week and timing the longest gap between real updates. 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: pick two points from that entity's History, subtract the earlier value from the later one for your measured change, divide by the number of seconds between their timestamps for the elapsed seconds, and that division gives you the site-specific gradient. A Derivative helper can instead display a friendlier per-hour unit when configured that way.

Collect this data before choosing a threshold

Record complete healthy heating cycles with normal household draw, including power start and stop, both temperature curves and recovery after showers or overnight use.

MeasurementWhy it mattersRequired value
sensor.hot_water_top_temperatureStored-water resultWatch this sensor through a full normal heating cycle and note the minimum and maximum °C it reaches, with timestamps, so you know what healthy looks like.
sensor.hot_water_lower_temperatureSecond thermal viewCompare this sensor's curve against the top sensor over the same cycle and note the normal temperature difference, or stratification, between them.
sensor.hot_water_temperature_rateHeating responseWatch this derivative sensor for a few minutes after heating power switches on and note the °C/h rise rate you would call healthy.
sensor.hot_water_circuit_powerElectrical input proxyCheck this sensor's History and note the typical wattage range when the circuit is idle, or standby, versus actively heating.
schedule.hot_water_heating_windowExpected availabilityEnter the actual heating window from your own tariff or controlled-load schedule.

The experiment that makes this article defensible

  1. Capture at least three ordinary healthy heating cycles, including one after meaningful hot-water use.
  2. Align circuit power, top and lower temperatures, derivative and schedule on one timeline.
  3. Measure the normal lag between power starting and a reliable positive temperature rate.
  4. Use a genuine fault or technician-supervised service event for failure evidence; never disconnect, bypass or expose the element to stage one.

Keep your own record as you go: timestamped readings, your Home Assistant version, the exact entity IDs you used, and one or two History or dashboard screenshots. 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
sensor.hot_water_temperature_rateDerivative helper for the selected tank probe in °C/hTry a time window against your own History and pick the one that reliably shows the healthy rise rate you measured earlier.
schedule.hot_water_heating_windowExpected heating availabilityEnter the actual heating periods from your own tariff or timer schedule.
input_number.hot_water_heating_power_wSeparates active heating from standbySet this wattage threshold between the standby and active ranges you measured on the circuit-power sensor.
input_number.hot_water_min_rise_c_per_hourMinimum healthy positive responseSet this a little below the healthy rise rate you measured after power switches on.
input_number.hot_water_fault_hold_minutesThermal-lag allowance before WarningSet a hold time that allows for the normal thermal lag you observed before a real fault should be reported.
input_number.hot_water_critical_temperature_cSite-specific last-line low temperatureSet this to a low temperature that would genuinely concern you for your own household's hot-water needs, based on the healthy range you measured.
input_boolean.hot_water_monitoring_enabledMaster notification gateLeave this switched off until you have tested the automation end to end, then turn it on.
input_number.hot_water_critical_repeat_minutesCritical repeat spacingSet a repeat interval greater than zero that matches how often you actually want to be reminded of a critical fault.

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.

Ask whether electrical input produces useful heat

The power threshold must come from the measured circuit, and the derivative threshold must come from complete healthy cycles. Do not use the template to control a contactor.

template:
  - sensor:
      - name: "Hot water probe difference"
        unique_id: hot_water_probe_difference
        default_entity_id: sensor.hot_water_probe_difference
        device_class: temperature
        state_class: measurement
        unit_of_measurement: "°C"
        availability: >
          {{ has_value('sensor.hot_water_top_temperature')
             and has_value('sensor.hot_water_lower_temperature') }}
        state: >
          {{ ((states('sensor.hot_water_top_temperature') | float)
              - (states('sensor.hot_water_lower_temperature') | float)) | round(2) }}

  - binary_sensor:
      - name: "Hot water heating warning"
        unique_id: hot_water_heating_warning
        default_entity_id: binary_sensor.hot_water_heating_warning
        device_class: problem
        delay_on:
          minutes: >
            {{ states('input_number.hot_water_fault_hold_minutes') | int(0) }}
        availability: >
          {{ has_value('sensor.hot_water_circuit_power')
             and has_value('sensor.hot_water_temperature_rate') }}
        state: >
          {% set powered = (states('sensor.hot_water_circuit_power') | float)
             >= (states('input_number.hot_water_heating_power_w') | float) %}
          {% set slow = (states('sensor.hot_water_temperature_rate') | float)
             < (states('input_number.hot_water_min_rise_c_per_hour') | float) %}
          {{ powered and slow and is_state('schedule.hot_water_heating_window', 'on') }}
        attributes:
          reason: >-
            Circuit power is {{ states('sensor.hot_water_circuit_power') }} W,
            top temperature is {{ states('sensor.hot_water_top_temperature') }} °C,
            rate is {{ states('sensor.hot_water_temperature_rate') }} °C/h and
            top-lower difference is {{ states('sensor.hot_water_probe_difference') }} °C.

      - name: "Hot water heating critical"
        unique_id: hot_water_heating_critical
        default_entity_id: binary_sensor.hot_water_heating_critical
        device_class: problem
        state: >
          {{ is_state('binary_sensor.hot_water_heating_warning', 'on')
             and (states('sensor.hot_water_top_temperature') | float(999999))
                 <= (states('input_number.hot_water_critical_temperature_c') | 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: hot_water_warning_push
    alias: "Hot water - Warning push"
    mode: single
    triggers:
      - trigger: state
        entity_id: binary_sensor.hot_water_heating_warning
        to: "on"
    conditions:
      - condition: state
        entity_id: input_boolean.hot_water_monitoring_enabled
        state: "on"
    actions:
      - action: notify.mobile_app_your_phone
        data:
          title: "Hot water warning"
          message: >-
            {{ state_attr('binary_sensor.hot_water_heating_warning', 'reason')
                or 'The warning condition is active. Check Home Assistant for evidence.' }}

  - id: hot_water_critical_repeat
    alias: "Hot water - Critical repeat"
    mode: restart
    triggers:
      - trigger: state
        entity_id: binary_sensor.hot_water_heating_critical
        to: "on"
    conditions:
      - condition: state
        entity_id: input_boolean.hot_water_monitoring_enabled
        state: "on"
    actions:
      - action: notify.mobile_app_your_phone
        data:
          title: "Hot water 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.hot_water_heating_critical
              state: "on"
            - condition: template
              value_template: >-
                {{ states('input_number.hot_water_critical_repeat_minutes') | int(0) > 0 }}
          sequence:
            - delay:
                minutes: >-
                  {{ states('input_number.hot_water_critical_repeat_minutes') | int(0) }}
            - condition: state
              entity_id: binary_sensor.hot_water_heating_critical
              state: "on"
            - action: notify.mobile_app_your_phone
              data:
                title: "Hot water 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

Circuit power establishes expected heating, the derivative tests delivered thermal response, the second probe reveals stratification or probe disagreement, and the schedule stops the system demanding heat when none should be available.

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
AdvisoryHeating response or probe relationship has moved outside the recent healthy envelope.Dashboard only
WarningMeasured heating power persists during the expected window without the calibrated temperature rise.One push notification
CriticalThe corroborated performance fault remains active and the measured tank temperature reaches the site-specific low limit.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 derivative lags power
Measure normal thermal lag and select the helper time window and Warning persistence from that evidence.
Top and lower probes disagree
Stratification can be normal. Fix probe positions and calibrate the relationship across whole cycles.
Controlled load ignores the schedule
Model actual supply availability or a contactor-state input rather than a guessed clock window.
Power is present but the circuit is not heating
That contradiction is the alarm evidence, not proof of a specific failed component. A qualified technician must diagnose it.
Solar diversion changes the pattern
Include diverter state and calibrate separate operating profiles if power and heating occur in variable bursts.

Safety and limits

Stored hot water can scald and poorly managed temperatures can create health risks. Do not alter thermostats, tempering valves, elements or fixed wiring from Home Assistant. Electrical and plumbing work must be completed by appropriately licensed trades.

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 power monitoring prove the hot-water element works?

No. It proves electrical input at the measurement point. Temperature response is required to show that useful heat reached the tank.

Why use two temperature probes?

They reveal stratification, probe movement and whether a response is local or visible through more of the tank. They still need stable, safe placement.

What temperature should I set for hot water?

This guide does not prescribe one. Follow current Australian requirements and advice from a licensed plumber for storage, delivery and tempering.

Can Home Assistant control the element?

This diagnostic design deliberately does not. Fixed high-current switching, safety interlocks and water-temperature control require a compliant engineered installation.

What if the system is on controlled load?

Use actual supply or contactor evidence where available. A guessed schedule can misclassify a normal retailer-controlled period as a fault.