Tipping-Bucket Rain Gauge with ESP32, ESPHome and Home Assistant

Build an ESP32 tipping-bucket rain gauge with ESPHome and Home Assistant: reed-switch wiring, debouncing, millimetres-per-tip calibration, daily rainfall, rain rate and reboot-safe totals.

An ESP32 tipping-bucket rain gauge measures how much rain actually falls at your property, rather than relying on a distant forecast or weather station. Every time a calibrated bucket tips, its reed switch generates a pulse. ESPHome counts those pulses, and Home Assistant turns the resulting rainfall total into daily, weekly and monthly records, alerts and irrigation conditions.

The wiring is simple. The difficult parts are the ones that matter over months of outdoor use: determining the real millimetres-per-tip constant, rejecting contact bounce, keeping accumulated rainfall across reboots, and avoiding an implausible “instant rain rate” after a single tip. This guide builds the reliable total first and treats intensity as a separate, derived measurement.

How a Tipping-Bucket Rain Gauge Works

Rain falls into funnel → water fills one bucket → bucket tips
→ magnet actuates reed switch → dry-contact pulse → ESP32 GPIO
→ accepted tip count × mm per tip = rainfall depth

A tip reports a depth of rainfall, not a volume consumed by a pipe. A gauge labelled 0.2 mm/tip means the collector geometry and bucket are designed so each tip represents 0.2 mm of rain over the funnel area. A different gauge may specify 0.1, 0.254 or another value. Do not copy the 0.2 mm example until you have checked your own gauge.

TermWhat it measuresExample
Rainfall totalAccumulated depth over a period12.4 mm today
Rain rateDepth per hour at the current/averaged intensity8 mm/h
Raw tipsAccepted switch closures62 tips at 0.2 mm each
Rain detectedAt least one recent valid tipTrue after recent rainfall

Parts and ESP32 Pin Choice

  • Classic ESP32 DevKit or ESP32-C3/S3 board with an unused digital input.
  • Passive two-wire tipping-bucket gauge with a reed switch, or equivalent documented dry-contact output.
  • Low-voltage power supply and a weather-resistant housing for the ESP32.
  • Two-conductor cable with outdoor-rated glands, strain relief and a drip loop.
  • Optional external 10 kΩ pull-up, surge/ESD protection and filtering for longer exposed cables.

The examples use GPIO27 on a classic ESP32. For a C3, C6 or S3, pick a suitable free pin from that particular board’s pinout; do not blindly use GPIO27 on every variant. Keep wiring away from mains and lightning-exposed structures. This is a hobby weather instrument, not a lightning-protected meteorological installation.

Wire a Passive Reed-Switch Gauge

ESP32 3.3 V ── internal pull-up ── GPIO27 ── reed switch ── GND

Reed open   → GPIO reads HIGH
Reed closed → GPIO reads LOW

For a simple passive contact, no 5 V connection is necessary. ESPHome enables the ESP32 internal pull-up; the reed switch merely shorts the signal to ground. Before using a prebuilt “rain sensor” with three or more wires, identify whether it contains an active circuit and measure its signal voltage. Never connect a possible 5 V output directly to a 3.3 V ESP32 GPIO.

On a long outdoor cable the internal pull-up may be too weak to reject interference reliably. An appropriately sized external pull-up at the controller, cable routing, hardware protection and an input filter can help. Do not put a large capacitor across the contact without checking the effect on valid pulse width.

Measure Your Gauge’s Pulse Before Choosing Debounce

Operate the bucket slowly by hand and watch the input. A physical tip might close the reed for tens or hundreds of milliseconds, and the contact may chatter several times before settling. The measured pulse width and minimum interval at the maximum expected rainfall determine safe filtering. A debounce interval that is longer than a real closure will cause missed tips.

ESPHome provides a useful alternative to a fixed-rate pulse counter for this slow instrument: use a GPIO binary sensor with a short delayed_on filter, then count each accepted press. This also makes it straightforward to store the count as a restored ESPHome global.

Recommended ESPHome Setup: Persistent Rainfall Total

The following is a complete starting configuration for a standard ESP32 DevKit and a passive rain gauge specified as 0.2 mm per tip. Replace the Wi-Fi and API secrets with your own values, and replace the calibration constant with the specification for your gauge.

esphome:
  name: garden-rain-gauge
  friendly_name: Garden Rain Gauge

esp32:
  board: esp32dev
  framework:
    type: esp-idf

logger:

api:
  encryption:
    key: !secret api_encryption_key

ota:
  - platform: esphome

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

# Avoid writing flash once per tip; flush accumulated state periodically.
preferences:
  flash_write_interval: 1min

globals:
  - id: accepted_rain_tips
    type: uint32_t
    restore_value: yes
    initial_value: '0'
    update_interval: 30s

binary_sensor:
  - platform: gpio
    id: rain_reed
    internal: true
    pin:
      number: GPIO27
      inverted: true
      mode:
        input: true
        pullup: true
    filters:
      - delayed_on: 30ms
    on_press:
      then:
        - lambda: |-
            id(accepted_rain_tips)++;
            id(rain_total).publish_state(id(accepted_rain_tips) * 0.2f);

sensor:
  - platform: template
    id: rain_total
    name: "Rainfall Total"
    lambda: |-
      return id(accepted_rain_tips) * 0.2f;
    update_interval: 30s
    unit_of_measurement: "mm"
    device_class: precipitation
    state_class: total_increasing
    accuracy_decimals: 1

  - platform: wifi_signal
    name: "Rain Gauge WiFi RSSI"
    update_interval: 60s
    entity_category: diagnostic

  - platform: uptime
    name: "Rain Gauge Uptime"
    update_interval: 60s
    entity_category: diagnostic

What this configuration does: a stable low input triggers one on_press, increments the restored integer count and publishes total millimetres immediately. The template sensor also republishes the restored total after startup and every 30 seconds. All rainfall totals are calculated from accepted tips, not from raw noisy edges.

The 30ms filter is a starting value to validate on your actual reed switch. It requires the input to stay active long enough before counting the press. If a legitimate closure lasts only 15 ms, that setting will miss it. A delayed_off filter can help keep brief reopenings from making multiple presses, but must also be chosen from observed pulse timing. Inspect actual log counts while tipping the bucket manually before installing the gauge.

A Note on the Reboot-Safe Total

ESPHome can restore globals and batches preference writes to reduce flash wear. A 1-minute flash write interval means the most recent tips may not yet have reached flash when power fails unexpectedly. This is a deliberate trade-off: writing on every tip increases flash wear. Publishing to Home Assistant immediately is helpful, but Home Assistant cannot recover rainfall that occurred while the ESP32 was unpowered. If no missed tip is acceptable, use an appropriate hardware pulse logger or a more durable persistence strategy.

Avoid deliberately resetting the source total at midnight. Let it rise throughout the device’s life and derive day/week/month periods in Home Assistant. Reflashing, changing the entity identity, restoring an older flash snapshot or replacing the board can still cause a count discontinuity; check Home Assistant history after major firmware changes.

Daily, Weekly and Monthly Rainfall in Home Assistant

Use Home Assistant’s Utility Meter helper three times with the Rainfall Total entity as the source, and choose Daily, Weekly and Monthly cycles. These helpers retain their own period values across Home Assistant restarts. The first partial period begins when the helper is created; do not mistake that first day for a complete daily observation.

# Home Assistant configuration.yaml
# Replace the source entity_id with the one actually created by ESPHome.
utility_meter:
  rain_today:
    source: sensor.garden_rain_gauge_rainfall_total
    cycle: daily
  rain_this_week:
    source: sensor.garden_rain_gauge_rainfall_total
    cycle: weekly
  rain_this_month:
    source: sensor.garden_rain_gauge_rainfall_total
    cycle: monthly

These are rainfall-depth sensors in mm, not litres of household water. Use a precipitation device class for the accumulating total, while a rain-rate sensor uses precipitation_intensity with mm/h. Avoid enabling Utility Meter “delta values” for this already accumulating source: it would add each total again instead of its increments.

Calculate Rain Rate from the Total

A single tip provides very coarse short-term rate information. At 0.2 mm per tip, one tip during one minute corresponds to 12 mm/h for that minute, but one tip after an hour of dry weather does not imply a continuous 12 mm/h shower. A five- or ten-minute average is easier to interpret than an instantaneous pulse-frequency estimate.

Home Assistant’s Derivative helper can calculate the rate of increase of the monotonically increasing rainfall total. Give it the rainfall-total sensor as input, time unit h, and a ten-minute smoothing window. It supports a total_increasing source that resets, provided the state class is set correctly.

# Optional Home Assistant configuration.yaml entry
sensor:
  - platform: derivative
    source: sensor.garden_rain_gauge_rainfall_total
    name: "Rain Intensity (10 min average)"
    unique_id: garden_rain_intensity_10m
    unit_time: h
    time_window: "00:10:00"
    max_sub_interval: "00:01:00"
    round: 1

The derivative result is in mm/h. In entity settings, use the precipitation-intensity device class where supported. The ten-minute value is an estimate over a recent window, not an official instantaneous meteorological intensity. Confirm that it returns toward zero after rain stops; if a derivative helper remains unexpectedly nonzero, inspect the source update cadence and missing-data periods.

Alternative: ESPHome Pulse Meter for Live Rain Rate

If you specifically want an ESPHome-side pulse-rate sensor, pulse_meter calculates frequency from the intervals between valid edges. Its default rate is pulses/min. For a 0.2 mm/tip gauge, convert it to mm/h with 0.2 × 60 = 12. This replaces the GPIO binary-sensor counter on that pin; do not run both input configurations simultaneously.

# Alternative to the GPIO binary-sensor configuration above.
# Choose this path if live ESPHome-side pulse rate is the priority.
sensor:
  - platform: pulse_meter
    id: rain_pulse_meter
    name: "Rain Intensity"
    pin:
      number: GPIO27
      inverted: true
      mode:
        input: true
        pullup: true
    internal_filter: 30ms
    internal_filter_mode: EDGE
    timeout: 5min
    filters:
      - multiply: 12.0  # pulses/min × 0.2 mm/tip × 60 min/h
    unit_of_measurement: "mm/h"
    device_class: precipitation_intensity
    state_class: measurement
    accuracy_decimals: 1
    total:
      name: "Rainfall Since Boot"
      unit_of_measurement: "mm"
      device_class: precipitation
      state_class: total_increasing
      accuracy_decimals: 1
      filters:
        - multiply: 0.2

Important limitations: this pulse-meter total starts from its initial in-memory count unless you explicitly implement a restore strategy. Do not describe it as a durable lifetime counter. The first event after a quiet period cannot establish a stable interval-based rate, and the timeout determines when rate falls to zero. For robust daily totals and low-rate rainfall, the restored-GPIO-count method above plus a Home Assistant derivative is usually easier to audit.

Calibrate the Actual Millimetres per Tip

Take the funnel’s effective collection area and a known water volume. In metric units, 1 mm of rainfall over 1 cm² corresponds to 0.1 mL of water, so the equivalent depth is:

Rain depth (mm) = 10 × poured water volume (mL) / funnel area (cm²)

Measured mm per tip = calculated depth (mm) / accepted tip count

For example, for a funnel with a measured effective area of 100 cm², slowly deliver 100 mL of water. That represents 10 mm of rain. If the ESP32 records 48 tips, the observed calibration is 10/48 = 0.2083 mm/tip. This is an illustrative measurement: use your collector’s actual area and actual counted tips.

Do not pour a litre straight into a tiny gauge. Deliver water slowly enough that the mechanism can empty normally; high flow can splash or bypass the bucket. Count manually or compare against a second reference gauge during real rainfall. Repeat at different flow rates if the manufacturer supplies an adjustment screw or correction curve.

Mechanical Installation Makes or Breaks Accuracy

  • Level the collecting rim in both axes: an uneven bucket tips at the wrong volume.
  • Mount where rain can enter freely, away from roof edges, trees, splash and irrigation sprinklers.
  • Keep the collecting rim accessible for clearing leaves, pollen and insects.
  • Allow the drain outlet to empty freely and keep cable entry below a drip loop.
  • Secure the mast so wind does not shake the tipping mechanism and generate false pulses.
  • Do not place the collector where falling debris or birds can regularly trigger the bucket.

A gauge under a tree may detect *drips after rain* but miss much of the actual rain event. A gauge next to a sprinkler records irrigation as rainfall. Mechanical placement must be checked before changing software calibration to “correct” implausible totals.

Avoiding False Tips and Missed Tips

ProblemLikely reasonFirst test
Rain measured while dryCable noise or bucket rockingWatch raw contact while gently shaking housing
Two tips per manual movementReed-switch bounceAdjust debounce only after measuring pulse width
No tips at allWrong GPIO, pull-up, broken cableShort input briefly to GND and check logs
Count stops in heavy rainDebounce too long / bucket overflowReview shortest true pulse and drainage
Gauge differs from referenceWrong mm/tip, placement, blocked funnelTest with known volume
Daily total drops or spikes on restartCounter changed or HA period helper misconfiguredInspect raw total before and after boot

If testing from the GPIO pin, remove the external gauge cable first and short the signal to GND briefly; this isolates the ESP32-side wiring. Reconnect the gauge only after one clean simulated press counts as one tip.

Can the ESP32 Deep-Sleep Between Rain Events?

Not if you intend to capture every tip with a GPIO that is inactive while deep sleeping. A sleeping processor may miss intermediate contacts and a wake event does not automatically recreate the complete tip history. Deep sleep can work with a suitable hardware counter or wake-and-latch design, but it needs explicit validation against the maximum tip rate and wake latency.

A mains-powered or solar-backed weather station that stays awake is simpler. If your weather station must retain rain counts through Wi-Fi outages, keep counting locally and reconnect later; Home Assistant visibility and physical pulse acquisition are separate requirements.

Useful Home Assistant Irrigation Automations

The Rain Today helper is valuable as a watering condition. A scheduled irrigation routine might skip watering when today’s rainfall exceeds a tested garden threshold, or when the past 24–48 hours have been wet. Keep threshold selection plant- and soil-specific rather than using one supposed universal “enough rain” number.

Do not use the rain gauge alone as a fail-safe for pumps or valves. Combine it with local ESPHome valve timing, soil moisture, a physical rain sensor where necessary, and a maximum irrigation duration. If the Wi-Fi connection fails or the rain gauge becomes blocked, a properly designed controller should not run the water indefinitely.

Rain Gauge vs Resistive Rain Pad vs Weather API

MethodMeasuresLimit
Tipping bucketAccumulated rainfall depth from discrete tipsMisses fine drizzle until first tip; requires levelling/cleaning
Rain pad / optical sensorRain present nowDoes not directly measure total rainfall depth
Weather APIForecast or remote observationsMay not reflect your actual garden
Capacitive soil sensorSoil wetness near rootsNot a rainfall measurement

These complement rather than replace each other. A pad can tell Home Assistant it has started raining before a bucket has accumulated enough water for its first tip; the bucket then provides actual local rainfall depth.

Related Guides on esp32.co.uk/

Official Documentation

Share your love