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.
| Term | What it measures | Example |
|---|---|---|
| Rainfall total | Accumulated depth over a period | 12.4 mm today |
| Rain rate | Depth per hour at the current/averaged intensity | 8 mm/h |
| Raw tips | Accepted switch closures | 62 tips at 0.2 mm each |
| Rain detected | At least one recent valid tip | True 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
| Problem | Likely reason | First test |
|---|---|---|
| Rain measured while dry | Cable noise or bucket rocking | Watch raw contact while gently shaking housing |
| Two tips per manual movement | Reed-switch bounce | Adjust debounce only after measuring pulse width |
| No tips at all | Wrong GPIO, pull-up, broken cable | Short input briefly to GND and check logs |
| Count stops in heavy rain | Debounce too long / bucket overflow | Review shortest true pulse and drainage |
| Gauge differs from reference | Wrong mm/tip, placement, blocked funnel | Test with known volume |
| Daily total drops or spikes on restart | Counter changed or HA period helper misconfigured | Inspect 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
| Method | Measures | Limit |
|---|---|---|
| Tipping bucket | Accumulated rainfall depth from discrete tips | Misses fine drizzle until first tip; requires levelling/cleaning |
| Rain pad / optical sensor | Rain present now | Does not directly measure total rainfall depth |
| Weather API | Forecast or remote observations | May not reflect your actual garden |
| Capacitive soil sensor | Soil wetness near roots | Not 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/
- ESP32 Weather Station with ESPHome — combine rain with BME280 climate, wind and BH1750 light.
- ESP32 Smart Irrigation Controller — use rainfall as a condition for watering.
- Capacitive Soil Moisture with ESPHome — measure what the root zone actually retained.
- ESPHome pulse_meter vs pulse_counter — understand rate versus accumulated count.
- ESPHome Wi-Fi Disconnects — diagnose offline stations without losing sight of actual reboots.
Official Documentation
- ESPHome GPIO Binary Sensor — contact input, inversion and pull-ups.
- ESPHome Binary Sensor Filters — debounce and press actions.
- ESPHome Global Variables — restored counters.
- ESPHome Preferences — flash-write interval and persistence trade-offs.
- ESPHome Pulse Meter — optional rain-rate path and total count.
- Home Assistant Utility Meter — daily, weekly and monthly rainfall.
- Home Assistant Derivative — estimate rainfall intensity from increasing total.