Build a Wi-Fi rain gauge with a tipping bucket

Print a funnel and a tipping bucket like the ones in real weather stations, count every tip with the photo-interrupter and chart the rainfall in millimetres on ThingSpeak.
  • Level:Advanced
  • Duration: 2 h 30 min
  • Ages 12+
  • Platform: ESP32

Tools for this lesson

Helpful for this build. Open a tool to see tips and more projects that use it.

Build a Wi-Fi rain gauge with a tipping bucket – EEMENTOR STEM™ lesson

Most automatic weather stations measure rain with a tipping bucket: the rain runs down a funnel into a tiny seesaw with a cup on each side. When one cup is full it tips over, empties itself and brings the other cup under the funnel. Each tip means the same small amount of rain, so counting tips measures the rainfall. In this capstone you print the whole mechanism, count the tips with the photo-interrupter, work out millimetres of rain and send them to ThingSpeak, where you can follow every shower on a chart.

What you'll learn

  • How a tipping-bucket rain gauge turns rain into pulses
  • How to calibrate a sensor with a measured amount of water
  • How to count events with an interrupt and send totals to the cloud
  • How to reset a daily total at midnight with internet time

What you need

  • ESP32 board, expansion board and USB cable (ESP32 IoT Starter Kit)
  • Photo-interrupter module (45-in-1 Sensor Kit)
  • 3 long female-to-male jumper wires (or extend them with female-to-female and male-to-male wires)
  • A 3 mm rod, 65 mm long: a bamboo skewer or an M3 screw with a nut
  • A syringe or a kitchen measuring jug, to calibrate
  • A free ThingSpeak account
  • A 2.4 GHz Wi-Fi network
  • A plastic food box to keep the ESP32 dry
  • The printed parts from the 3D printing box below (frame, bucket and funnel), or the cardboard version described there
  • Breadboard and jumper wires (ESP32 IoT Starter Kit)

Wiring

Breadboard wiring diagram: Build a Wi-Fi rain gauge with a tipping bucket
Breadboard view. Check each connection against the wiring table below.
From To
Photo-interrupter S ESP32 GPIO 27
Photo-interrupter + ESP32 3V3
Photo-interrupter − GND

Sensor modules from different makers can label and order their pins differently. Always follow the labels printed on your module.

Set up ThingSpeak

  1. Sign in at thingspeak.com (it uses a MathWorks account; if you are under the age required by their terms, ask a parent or teacher to create it).
  2. Create a new channel called Rain gauge with two fields: Rain last 15 min (mm) and Rain today (mm).
  3. Copy the Write API Key from the API Keys tab.

Calibrate the bucket

First you find out how much water one tip holds. Upload the sketch with ML_PER_TIP left at 2.0, open the Serial Monitor at 115200 baud, and then:

  1. Fill a syringe or measuring jug with exactly 50 ml of water.
  2. Pour it into the funnel very slowly, a few drops at a time, over about five minutes. Real rain is slow, and pouring fast makes the bucket overflow before it tips.
  3. Read the number of tips in the Serial Monitor. Work out ML_PER_TIP = 50 ÷ tips. For example, 50 ml and 22 tips gives 2.27.
  4. Put your value into the sketch and upload it again. Repeat once to check.

The funnel opening is 100 mm across, so its area is π × 50 × 50 = 7854 mm². One millimetre of rain over that area is 7.85 ml of water, which is why the sketch divides the volume by 7.85.

The sketch

Replace YOUR_WIFI_NAME and YOUR_WIFI_PASSWORD with your own network details (2.4 GHz only).

Put your Write API Key into WRITE_API_KEY, your calibrated value into ML_PER_TIP, and your time zone into TIME_ZONE (the one in the sketch is for Estonia and Finland; search for "POSIX time zone" plus your city to find yours).

#include <WiFi.h>
#include <HTTPClient.h>
#include <time.h>

const char* WIFI_NAME = "YOUR_WIFI_NAME";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
const char* WRITE_API_KEY = "YOUR_WRITE_API_KEY";
const char* TIME_ZONE = "EET-2EEST,M3.5.0/3,M10.5.0/4";

const int SENSOR_PIN = 27;
const float ML_PER_TIP = 2.0;            // set this when you calibrate
const float ML_PER_MM = 7.85;            // 1 mm of rain in a 100 mm funnel
const unsigned long EVERY = 15UL * 60 * 1000;   // send every 15 minutes

volatile unsigned long tips = 0;
volatile unsigned long lastTipMs = 0;
unsigned long tipsToday = 0;
unsigned long lastSend = 0;
int today = -1;

void IRAM_ATTR onTip() {
  unsigned long now = millis();
  if (now - lastTipMs > 300) {           // one tip, not the bucket bouncing
    tips++;
    lastTipMs = now;
  }
}

int dayOfMonth() {
  struct tm t;
  if (!getLocalTime(&t, 10)) return -1;
  return t.tm_mday;
}

void send(float mmPeriod, float mmToday) {
  HTTPClient http;
  String url = String("http://api.thingspeak.com/update?api_key=") + WRITE_API_KEY +
               "&field1=" + String(mmPeriod, 2) + "&field2=" + String(mmToday, 2);
  http.begin(url);
  int code = http.GET();
  http.end();
  Serial.printf("Sent %.2f mm (today %.2f mm), ThingSpeak answered %d\n", mmPeriod, mmToday, code);
}

void setup() {
  Serial.begin(115200);
  pinMode(SENSOR_PIN, INPUT);
  attachInterrupt(digitalPinToInterrupt(SENSOR_PIN), onTip, CHANGE);   // both ways count
  WiFi.begin(WIFI_NAME, WIFI_PASSWORD);
  Serial.print("Connecting to Wi-Fi");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println(" connected");
  configTzTime(TIME_ZONE, "pool.ntp.org");
}

void loop() {
  static unsigned long shown = 0;
  if (tips != shown) {                   // show every tip while you calibrate
    shown = tips;
    Serial.printf("Tips: %lu\n", tipsToday + shown);
  }

  if (millis() - lastSend >= EVERY) {
    lastSend = millis();
    noInterrupts();
    unsigned long period = tips;
    tips = 0;
    interrupts();
    shown = 0;

    int day = dayOfMonth();
    if (day != -1 && today != -1 && day != today) tipsToday = 0;   // a new day
    if (day != -1) today = day;
    tipsToday += period;

    float mmPeriod = period * ML_PER_TIP / ML_PER_MM;
    float mmToday = tipsToday * ML_PER_TIP / ML_PER_MM;
    if (WiFi.status() == WL_CONNECTED) send(mmPeriod, mmToday);
    else WiFi.reconnect();
  }
}

Put it outside

  1. Place the gauge on a flat, open spot, away from walls, trees and roofs that shelter it or drip onto it. Check it is level, or the bucket tips unevenly.
  2. Put the ESP32 in a plastic food box and run only the sensor wires out to the gauge, or keep the ESP32 indoors and use long wires through a window gap.
  3. Weigh the frame down with a stone, or screw it to a post, so the wind can't blow it over.
  4. Open your ThingSpeak channel. After the next shower you see the rain in 15-minute steps and the total for the day.

How it works

The flag under the bucket is in the sensor's slot when the bucket leans one way and out of it when it leans the other way. So every tip changes the sensor's output, from LOW to HIGH or from HIGH to LOW. That is why the interrupt uses CHANGE: each change is one tip. The 300 ms check stops one tip from counting twice when the bucket bounces.

The interrupt only counts. Every 15 minutes loop() takes the count, resets it, adds it to today's total and converts both to millimetres. The internet time from configTzTime() tells the sketch when a new day starts, so the daily total goes back to zero at midnight, just like the rainfall figures in weather reports.

Try this next

Troubleshooting

  • The tips don't count: watch the module's LED while you rock the bucket by hand. It must change every time; if not, move the module so the flag goes deeper into the slot.
  • Each tip counts twice: make the 300 in onTip() bigger.
  • The bucket overflows instead of tipping: it sticks on its rod. Make the holes in the bucket slightly bigger with a 3.5 mm drill, and check the frame is level.
  • "ThingSpeak answered 0": the free plan accepts one update every 15 seconds; check the Write API Key.
  • It never connects to Wi-Fi: check the network name and password (both are case-sensitive) and make sure the network is 2.4 GHz.
  • A printed part is too tight or too loose: printers differ by a few tenths of a millimetre. Sand or file a tight hole, and fix a loose part with a drop of hot glue or a strip of tape.

Build it yourself

Everything used in this lesson comes in one kit, with parts tested by our engineers.

ESP32 IoT Starter Kit | Arduino IDE Compatible | Free Tutorials $57.00

Shop the kit