ESPHomeESP32Home Assistant

ESP32-C6 water meter

An ESP32-C6 Super Mini counts pulses from a normally-open reed on an analog water meter, publishes liters per minute and a total, and flags a flow that never stops.

Difficulty · IntermediateSetup · 45–90 minutesHA · current stable (native API)ESPHome · current stable
Get the pack · from $9Have us set it up

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.

An analog water meter already has the measurement. A small magnet on the liter dial, or a pair of pulse wires the utility left on the register, closes a contact once per unit of water. This pack puts an ESP32-C6 Super Mini on that contact, turns each closure into liters per minute and a running total, and pages you when flow keeps going long after every tap should be shut.

Wi-Fi credentials stay in secrets.yaml. The liters-per-pulse number is a substitution you fill after a bucket test. Do not publish a live meter’s total or a photo of the register.

A puddle under one fixture is a different sensor. Pair this with IKEA Badring leak alert for a probe on the floor. A circulator pump on a timer is hot water recirculation on demand.

What problem it solves

You want today’s water use, and a warning when a toilet flapper or a hose bib runs for the better part of an hour, from the meter you already have.

How it works

  1. One pulse, one GPIO — a normally-open reed sits between the pulse pin and ground. The pin’s pull-up holds it high. Each time the meter magnet passes, the reed closes, the pin falls, and pulse_meter counts the edge. A meter that already has a dry-contact pulse cable lands on the same two wires.
  2. Rate — pulse_meter reports pulses per minute. A multiply filter scales that by liters_per_pulse, so the entity is liters per minute. timeout (pack default 2 minutes) returns the rate to 0 after the magnet stops. Without that timeout the last rate sits on the dashboard forever.
  3. Total — the component’s total block counts pulses and applies the same liters-per-pulse scale. device_class: water and state_class: total_increasing let Home Assistant graph it. A reboot can restart the on-device counter at 0. total_increasing tells Home Assistant that drop is a meter reset, and a Utility Meter helper keeps the daily and monthly sums.
  4. Debounce — a glass reed bounces. internal_filter: 200ms with internal_filter_mode: PULSE ignores edges closer together than that. If a single dial revolution shows up as two or three pulses, raise the filter before you touch liters_per_pulse.
  5. Continuous flow — a blueprint watches the flow sensor. Above a trickle you set, for longer than a real shower, it sends one notification. The duration has to be longer than the longest legitimate draw, or a bath becomes an alarm.

Leave the meter sealed. The reed goes on the outside of the glass, over the dial that carries the magnet, or on the pulse terminals the manufacturer brought out. It does not go inside the register.

Hardware notes

  • Board. ESP32-C6 Super Mini, mains-powered from the USB-C supply. Leave GPIO8 (LED and strap), GPIO9 (BOOT and strap), GPIO12 and GPIO13 (USB), and GPIO15 (strap) alone. The pack uses GPIO6 for the pulse, with the internal pull-up.
  • Reed. A normally-open glass reed closes only while a magnet is close. The linked set is 2×14 mm normally-open contacts plus small magnets. A normally-closed door contact is the wrong part: it sits closed all day and the counter runs wild. If the meter already has a pulse cable, you may not need a separate reed at all.
  • Placement. Tape the reed to the register glass where a fridge magnet on the dial makes it click once per revolution. Confirm with a continuity beep before you close the box. Keep the leads short, and keep them off the I²C pins if you add the optional sensor.
  • Optional climate. An AHT20 on GPIO0 (SDA) and GPIO1 (SCL) is the same bus as the ESP32-C6 climate sensor. Skip it when you only want water.

Wiring

Reed or meter pulse cable ESP32-C6 Super Mini
Contact A GPIO6
Contact B GND

The internal pull-up is enabled in YAML. An external resistor is optional.

AHT20 (optional) ESP32-C6 Super Mini
VCC 3V3
GND GND
SDA GPIO0
SCL GPIO1

Snippet

Pins and the calibration factor are substitutions. Measure liters_per_pulse before you trust the total.

substitutions:
  pulse_pin: GPIO6
  liters_per_pulse: "1.0"

sensor:
  - platform: pulse_meter
    pin:
      number: ${pulse_pin}
      mode:
        input: true
        pullup: true
    name: "Water flow"
    unit_of_measurement: "L/min"
    device_class: volume_flow_rate
    state_class: measurement
    accuracy_decimals: 2
    timeout: 2min
    internal_filter: 200ms
    internal_filter_mode: PULSE
    filters:
      - multiply: ${liters_per_pulse}
    total:
      name: "Water total"
      unit_of_measurement: "L"
      device_class: water
      state_class: total_increasing
      accuracy_decimals: 3
      filters:
        - multiply: ${liters_per_pulse}

The continuous-flow blueprint takes the flow entity, a threshold in L/min, a duration, and a notify target (!input). A Utility Meter helper on sensor.water_total, cycled daily, is the number to put on a dashboard.

Setup outline

  1. Flash the YAML. Confirm the flow entity sits at 0 with every tap off.
  2. Fill a container you can measure (a 10 L bucket is enough). Count the pulses on the total while it fills. liters_per_pulse is volume divided by pulses. Put that number in the substitution and reflash.
  3. Run the same volume again. The total should move by about that volume. A factor-of-two error is almost always a bounce (raise internal_filter) or a dial that pulses twice per liter.
  4. Add a Utility Meter on the total, cycle daily or monthly.
  5. Import the continuous-flow blueprint. Set the threshold just above what you read with the house quiet, and the duration longer than a shower. Trigger it once with a low threshold so you see the notification, then put the real numbers back.

Troubleshooting

  • Total climbs with the water off. The reed is normally closed, the wires are shorted, or the magnet sits on the reed at rest. Use a normally-open contact, and move it until it clicks only as the dial magnet passes.
  • One revolution counts two or three. Contact bounce. Raise internal_filter (try 500 ms) and repeat the bucket test. Change liters_per_pulse only after one revolution is one pulse.
  • Flow stays at the last number after you close the tap. timeout is missing or very long. The pack default is 2 minutes, which is the gap that means “stopped.”
  • Total returns to 0 after a power cut. The on-device counter restarted. Home Assistant statistics and the Utility Meter keep history when state_class is total_increasing. The ESP number is “since boot”; the helper is the one you read.
  • No pulses at all. The magnet never reaches the reed, or the pulse cable is a powered output rather than a dry contact. A dry contact clicks on a continuity meter. A powered output needs a different interface. Do not tie a meter wire that is above 3.3 V straight to the GPIO.
  • Board will not boot. The pulse wire landed on a strap pin (GPIO8, GPIO9, or GPIO15) and holds it during reset. Move it to GPIO6.

Credits

Pulse counting on an ESP32-C6 for a Home Assistant water total follows the SmartHomeShop WaterMeterKit V3 (firmware). This write-up is an original Super Mini recipe: a normally-open reed or the meter’s own pulse contact, a liters-per-pulse calibration, and a continuous-flow alert. It does not use that kit’s board, cloud firmware, or onboard humidity sensor.

Changelog

v1.0 · Oct 25, 2026: First publish.