Water damage is one of the most common problems in homes, and it often happens when nobody is there: a hose on the washing machine bursts, or a pipe under the sink starts to drip. Real "smart" leak protection uses a sensor on the floor and a motorised valve on the water pipe. When the sensor gets wet, the valve closes and the water stops. In this lesson you build a working model of that system. The water sensor lies on the "floor", a servo turns a model valve, and the UNO keeps watch. You will also learn three tricks that real products use: powering the sensor only when it measures, remembering the valve position after a power cut, and moving the valve once a week so it doesn't get stuck.
What you'll learn
- How a water sensor measures with bare copper tracks, and why you should only power it while measuring
- How to move a servo slowly
- How to save a setting in EEPROM so it survives a power cut
- How a button can do two things: a short press and a long press
What you need
- UNO R3 board and USB cable (UNO R3 Starter Kit)
- Water level sensor (45-in-1 Sensor Kit)
- Servo motor SG90 with horns and screws (UNO R3 Starter Kit)
- Active buzzer (UNO R3 Starter Kit)
- Green LED, red LED and 2 resistors, 220 Ω (UNO R3 Starter Kit)
- 1 push button (UNO R3 Starter Kit)
- Cardboard and a straw or wooden skewer for the model valve, and a small dish of water for testing
- Breadboard and jumper wires (UNO R3 Starter Kit)
Important: this is a learning project, not a certified safety device. A servo can't turn a real water valve, and real shut-off valves must be fitted by a plumber. Keep the water away from the UNO, the breadboard and the USB cable: only the sensor's tip may get wet.
Wiring

| From | To |
|---|---|
| Water sensor S | UNO A0 |
| Water sensor + | UNO pin 7 (not 5V, see below) |
| Water sensor − | GND |
| Servo signal (orange) | UNO pin 9 |
| Servo power (red) and GND (brown) | 5V and GND |
| UNO pin 5 | 220 Ω resistor, then the green LED's long leg |
| UNO pin 6 | 220 Ω resistor, then the red LED's long leg |
| Both LEDs' short legs | GND |
| Active buzzer + (longer leg) | UNO pin 8 |
| Active buzzer − | GND |
| Push button, one leg | UNO pin 2 |
| Push button, the diagonally opposite leg | GND |
The sensor gets its power from pin 7 instead of 5V. That way the sketch can switch it on for a few milliseconds per reading. When current flows through wet copper all the time, the tracks slowly corrode, and after a few weeks the sensor stops working. Powered only while measuring, it lasts for years.
Make the model valve
- Cut a cardboard "pipe" about 3 cm wide and 15 cm long, and draw a valve in the middle.
- Fix the long servo horn to the servo, and tape a straw or skewer to it as the valve handle.
- Tape the servo onto the pipe so the handle points along the pipe. A handle along the pipe means "open", across the pipe means "closed", just like a real ball valve.
- Lay the water sensor flat on a plate (your "floor"), tracks up, with the pins sticking out over the edge.
The sketch
Servo and EEPROM come with the Arduino IDE.
#include <Servo.h>
#include <EEPROM.h>
const int SENSOR_PIN = A0, SENSOR_POWER = 7;
const int SERVO_PIN = 9, GREEN_PIN = 5, RED_PIN = 6, BUZZER_PIN = 8, BUTTON_PIN = 2;
const int OPEN_ANGLE = 0, CLOSED_ANGLE = 90;
const int WET_LEVEL = 250; // readings above this mean "water"
const unsigned long EXERCISE_EVERY = 7UL * 24 * 3600 * 1000; // move the valve once a week
const int VALVE_ADDRESS = 0; // EEPROM byte that stores the valve state
Servo valve;
bool closed = false;
bool alarmOn = false;
int wetCount = 0;
unsigned long lastCheck = 0, lastExercise = 0, pressStart = 0;
bool lastButton = HIGH;
int readSensor() {
digitalWrite(SENSOR_POWER, HIGH); // power on only while measuring
delay(10);
int value = analogRead(SENSOR_PIN);
digitalWrite(SENSOR_POWER, LOW);
return value;
}
void moveValve(int from, int to) { // turn slowly, like a real valve motor
valve.attach(SERVO_PIN);
int step = to > from ? 1 : -1;
for (int a = from; a != to; a += step) {
valve.write(a);
delay(15);
}
valve.write(to);
delay(300);
valve.detach(); // no humming while it waits
}
void setValve(bool close) {
if (close == closed) return;
moveValve(close ? OPEN_ANGLE : CLOSED_ANGLE, close ? CLOSED_ANGLE : OPEN_ANGLE);
closed = close;
EEPROM.update(VALVE_ADDRESS, closed); // remember it after a power cut
Serial.println(closed ? "Valve CLOSED" : "Valve open");
}
void beeps(int n, int len) {
for (int i = 0; i < n; i++) {
digitalWrite(BUZZER_PIN, HIGH);
delay(len);
digitalWrite(BUZZER_PIN, LOW);
delay(150);
}
}
void setup() {
Serial.begin(9600);
pinMode(SENSOR_POWER, OUTPUT);
pinMode(GREEN_PIN, OUTPUT);
pinMode(RED_PIN, OUTPUT);
pinMode(BUZZER_PIN, OUTPUT);
pinMode(BUTTON_PIN, INPUT_PULLUP);
closed = EEPROM.read(VALVE_ADDRESS) == 1; // was it closed before the power cut?
moveValve(closed ? OPEN_ANGLE : CLOSED_ANGLE, closed ? CLOSED_ANGLE : OPEN_ANGLE);
Serial.println(closed ? "Started with the valve CLOSED. Hold the button 3 s to open it." : "Leak guard ready, valve open");
}
void loop() {
unsigned long now = millis();
// 1. Check the floor once a second
if (now - lastCheck >= 1000) {
lastCheck = now;
int level = readSensor();
Serial.print("Sensor: ");
Serial.println(level);
wetCount = level > WET_LEVEL ? wetCount + 1 : 0;
if (wetCount == 2 && !alarmOn) { // wet twice in a row: a real leak
Serial.println("LEAK! Closing the valve");
alarmOn = true;
setValve(true);
}
}
// 2. Button: short press = silence the alarm, hold 3 s = open the valve again
bool button = digitalRead(BUTTON_PIN);
if (lastButton == HIGH && button == LOW) pressStart = now;
if (lastButton == LOW && button == HIGH && now - pressStart < 1000 && alarmOn) {
alarmOn = false;
Serial.println("Alarm silenced. The valve stays closed.");
}
if (button == LOW && pressStart != 0 && now - pressStart > 3000) {
pressStart = 0;
if (readSensor() > WET_LEVEL) {
Serial.println("Still wet! Dry the floor and the sensor first.");
beeps(3, 300);
} else {
alarmOn = false;
setValve(false);
beeps(1, 100);
}
}
lastButton = button;
// 3. Once a week, close and open the valve so it can't get stuck
if (!closed && now - lastExercise > EXERCISE_EVERY) {
lastExercise = now;
Serial.println("Weekly valve exercise");
setValve(true);
setValve(false);
}
// 4. Lights and sound
digitalWrite(GREEN_PIN, !closed);
digitalWrite(RED_PIN, alarmOn ? (now / 200) % 2 : closed);
digitalWrite(BUZZER_PIN, alarmOn && (now / 500) % 2);
}
Try it
- Upload the sketch and open the Serial Monitor at 9600 baud. The green LED is on and the valve handle points along the pipe (open). A dry sensor reads close to 0.
- Dip the tip of the sensor in the dish of water, or drip a little water on it. Within two seconds the alarm sounds, the red LED flashes and the valve turns across the pipe: closed.
- Press the button briefly: the alarm stops, but the red LED stays on and the valve stays closed. That's on purpose: a closed valve is the safe state.
- Unplug the USB cable and plug it in again. The valve stays closed, because the UNO saved its state in EEPROM.
- Hold the button for 3 seconds while the sensor is still wet: three long beeps say "no". Dry the sensor with a tissue and hold the button again: one beep, and the valve opens.
How it works
The water sensor has long copper tracks next to each other. Every second track is connected to +, the others to S through a resistor. Dry, nothing connects them and S reads close to 0. Water conducts a little, so drops on the tracks let some current through and the reading rises. The sketch says "leak" when the reading is above 250 twice in a row, so a single splash doesn't close the valve.
The EEPROM is a small memory inside the UNO that keeps its contents without power. EEPROM.update() writes the valve state into byte 0, but only when it changes, because each EEPROM byte can only be written about 100,000 times. At start-up the sketch reads that byte back. Without this, a power cut during a leak would make the valve "forget" and the system would think it was open.
Real valves that never move can get stuck with limescale, and then they don't close when they are needed. Good leak protection systems therefore close and open the valve once in a while. EXERCISE_EVERY does that once a week. For testing, change it to 60000 to see it happen every minute.
Try this next
- Add a second water sensor in another room, on A1, and power it from pin 4.
- Show the state on the 1602 LCD: "All dry", "LEAK! Valve closed" and how long ago the leak started.
- Send the alarm to your phone by rebuilding the project on the ESP32 with ntfy.
- Use the relay module to switch a small 5 V pump, to pump the water away (low voltage only).
Troubleshooting
- The sensor reads high when it's dry: dry it completely, including the back. Old tracks with green spots are corroded; clean them gently with an eraser.
- The sensor always reads 0: check that + goes to pin 7 (not 5V), S to A0 and − to GND.
- The servo jitters or the UNO restarts: the servo draws bursts of current. Use a good USB port, and check that the servo's red wire is on 5V.
-
The valve turns the wrong way: swap the values of
OPEN_ANGLEandCLOSED_ANGLE, or refit the handle on the servo horn.