Build a rain alarm with a web page

Use the water level sensor as a rain detector: the ESP32 beeps when the first drops land, and a web page shows whether it's raining and when it started.
  • Level:Intermediate
  • Duration: 45 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 rain alarm with a web page – EEMENTOR STEM™ lesson

Washing on the line, windows open, bike outside: the first drops of rain are worth knowing about. The water level sensor from the Sensor Kit makes a good rain detector, because a drop of water across its metal lines is enough to change its reading. In this lesson the ESP32 checks it every second, sounds a buzzer when rain starts, and serves a web page that shows the status and the time the rain began.

What you'll learn

  • How to use the water level sensor as a rain detector
  • How to start an alarm only when the state changes
  • How to add a mute button to a web page
  • How to show the real time with the internet clock

What you need

  • ESP32 board, expansion board and USB cable (ESP32 IoT Starter Kit)
  • Water level sensor (45-in-1 Sensor Kit)
  • Active buzzer module (45-in-1 Sensor Kit)
  • A 2.4 GHz Wi-Fi network
  • Breadboard and jumper wires (ESP32 IoT Starter Kit)

Make it real: tape the sensor to a piece of card leaning at about 30 degrees, lines facing up, so rain hits it and runs off. Keep the ESP32 and the wires indoors or under cover.

Wiring

Breadboard wiring diagram: Build a rain alarm with a web page
Breadboard view. Check each connection against the wiring table below.
From To
Water level sensor S ESP32 GPIO 35
Water level sensor + ESP32 GPIO 32 (powers the sensor only while measuring)
Water level sensor − GND
Active buzzer module S ESP32 GPIO 25
Active buzzer module + and − 3V3 and GND

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

Only the sensor may get wet. The ESP32 must stay dry.

The sketch

Replace YOUR_WIFI_NAME and YOUR_WIFI_PASSWORD with your own network details (2.4 GHz only). Open the Serial Monitor at 115200 baud to see the address, then open it in a browser on a phone or computer on the same Wi-Fi.

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

const char* WIFI_NAME = "YOUR_WIFI_NAME";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
const char* TIME_ZONE = "EET-2EEST,M3.5.0/3,M10.5.0/4";  // Estonia; change for your country

const int RAIN_PIN = 35;
const int RAIN_POWER = 32;
const int BUZZER_PIN = 25;
const int WET_ABOVE = 600;     // rain when the reading is above this
const int DRY_BELOW = 300;     // dry again below this

WebServer server(80);
bool raining = false;
bool muted = false;
int reading = 0;
String rainSince = "-";
unsigned long lastCheck = 0;

const char PAGE[] = R"rawliteral(
<!DOCTYPE html><html><head><meta charset='utf-8'>
<meta name='viewport' content='width=device-width, initial-scale=1'><title>Rain alarm</title>
<style>body{font-family:sans-serif;padding:16px;text-align:center}#s{font-size:64px;margin:10px}button{font-size:18px;padding:8px 16px}</style></head><body>
<h1>Rain alarm</h1><div id='s'>-</div><p id='t'></p>
<button onclick="fetch('/mute')">Mute alarm</button>
<script>
async function update() {
  const d = await (await fetch('/data')).json();
  document.getElementById('s').textContent = d.raining ? '🌧️ Raining' : '☀️ Dry';
  document.getElementById('t').textContent = d.raining ? 'Since ' + d.since + (d.muted ? ' (muted)' : '') : '';
}
setInterval(update, 3000);
update();
</script></body></html>
)rawliteral";

String timeNow() {
  struct tm t;
  if (!getLocalTime(&t, 10)) return "unknown time";
  char text[20];
  strftime(text, sizeof(text), "%d.%m %H:%M", &t);
  return String(text);
}

void checkRain() {
  digitalWrite(RAIN_POWER, HIGH);
  delay(20);
  reading = analogRead(RAIN_PIN);
  digitalWrite(RAIN_POWER, LOW);

  if (!raining && reading > WET_ABOVE) {       // rain has just started
    raining = true;
    muted = false;
    rainSince = timeNow();
    Serial.println("Rain started at " + rainSince);
  }
  if (raining && reading < DRY_BELOW) {        // dry again
    raining = false;
    Serial.println("Dry again");
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(RAIN_POWER, OUTPUT);
  pinMode(BUZZER_PIN, OUTPUT);

  WiFi.begin(WIFI_NAME, WIFI_PASSWORD);
  while (WiFi.status() != WL_CONNECTED) delay(500);
  configTzTime(TIME_ZONE, "pool.ntp.org");
  Serial.print("Open http://");
  Serial.println(WiFi.localIP());

  server.on("/", []() { server.send(200, "text/html", PAGE); });
  server.on("/data", []() {
    String json = "{\"raining\":" + String(raining ? "true" : "false") +
                  ",\"muted\":" + String(muted ? "true" : "false") +
                  ",\"since\":\"" + rainSince + "\",\"reading\":" + String(reading) + "}";
    server.send(200, "application/json", json);
  });
  server.on("/mute", []() {
    muted = true;
    server.send(200, "text/plain", "ok");
  });
  server.begin();
}

void loop() {
  server.handleClient();
  if (millis() - lastCheck >= 1000) {
    lastCheck = millis();
    checkRain();
  }
  // two beeps every 3 seconds while it rains, unless muted
  bool beep = raining && !muted && (millis() % 3000 < 400) && (millis() % 200 < 100);
  digitalWrite(BUZZER_PIN, beep);
}

How it works

Rain water conducts electricity, so drops that bridge the sensor's metal lines raise its reading. The ESP32 checks once per second, powering the sensor only for 20 ms each time so that the lines corrode as slowly as possible.

The alarm reacts to changes: the moment the state goes from dry to raining, it notes the time, clears the mute and starts beeping. Two limits stop it flapping between states while the plate is just a little wet: it needs a reading above 600 to call it rain, and below 300 to call it dry again. The beep pattern comes from the clock: the buzzer is on when both millis() % 3000 < 400 and millis() % 200 < 100 are true, which gives two short beeps every three seconds without any delay().

Try this next

  • Show the raw reading on the page to see how wet the plate is.
  • Count how many times it rained today.
  • Wipe the plate dry automatically: tilt it more steeply so drops run off faster after the rain.

Troubleshooting

  • 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.
  • The page doesn't open: the phone or computer must be on the same Wi-Fi as the ESP32, not on mobile data.
  • It never detects rain: drip water on the sensor and check the reading in the /data address; lower WET_ABOVE if needed.
  • It stays "raining" long after the rain: water is left on the plate. Tilt it more, or raise DRY_BELOW.
  • The buzzer is always on: some buzzer modules switch on with LOW. Write !beep instead of beep in the last line.

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