Parts list
| Part | Qty | Approx. | Buy |
|---|---|---|---|
| Raspberry Pi Pico W (MicroPython installed) | 1 | ₹620 | Buy Raspberry Pi Pico W (MicroPython installed) |
| Water level / rain-drop sensor strip (S, +, −) | 1 | ₹40 | Buy Water level / rain-drop sensor strip (S, +, −) |
| Active buzzer module | 1 | ₹25 | Buy Active buzzer module |
| 3-core wire to the sensor (1–2 m) | 1 | ₹40 | Buy 3-core wire to the sensor (1–2 m) |
| 5 V USB adapter + micro-USB cable | 1 | ₹150 | Buy 5 V USB adapter + micro-USB cable |
| Total | ₹875 |
Affiliate disclosure: “Buy” links may earn FlashReady a small commission at no extra cost to you. Prices are indicative.
Wiring
| Raspberry Pi Pico W | Part · pin | Wire | Notes |
|---|---|---|---|
| GP22 | Water level sensor (exposed-trace board) · + | red | Powered from a GPIO only while reading — the traces corrode much slower |
| GP26 | Water level sensor (exposed-trace board) · S | green | ADC0, physical pin 31 |
| GND | Water level sensor (exposed-trace board) · - | black | |
| GP15 | Active buzzer module (high-level trigger) · I/O | orange | Physical pin 20 |
| 3V3 | Active buzzer module (high-level trigger) · VCC | red | |
| GND | Active buzzer module (high-level trigger) · GND | black |
Board quirks: Lay the strip flat on the floor at the lowest point under the appliance, traces facing down onto the floor (or on 1–2 mm spacers) so a thin film of water bridges them. Fix it with tape at the edges, not over the traces. The sensor is powered from GP22 only for 10 ms every 2 s. Bare copper traces with constant DC on them corrode within weeks when wet; switching the supply makes them last much longer. Keep the Pico W and its USB adapter <strong>above</strong> where water can reach — on the wall, not on the floor next to the sensor. Only the 3-core wire goes down. Phone alerts need Wi-Fi at that spot and the ntfy app subscribed to your topic. The buzzer works without Wi-Fi.
Code
# Water leak detector - Raspberry Pi Pico W (MicroPython).
# Water-level sensor strip on the floor under the RO purifier / washing machine / AC drain:
# + -> GP22 (powered only while reading, so the traces corrode far slower), S -> GP26 (ADC0), - -> GND.
# Buzzer module: I/O -> GP15, VCC -> 3V3, GND -> GND.
# Wet: the buzzer sounds and a high-priority ntfy alert goes to your phone (repeated every 15 min).
from machine import ADC, Pin
import time
import network
import json
WET_ABOVE = 12000 # raw ADC (0-65535); dry strip reads near 0 <- check with your sensor
CHECK_EVERY_S = 2
REPEAT_ALERT_S = 15 * 60
WIFI_SSID = "your-wifi"
WIFI_PASS = "your-password"
NTFY_TOPIC = "your-private-topic" # https://ntfy.sh/<topic> - pick something unguessable
# The FlashReady flasher saves your settings to /settings.json; they win over the lines above.
try:
with open("/settings.json") as f:
_s = json.load(f)
except (OSError, ValueError):
_s = {}
WIFI_SSID = _s.get("wifi_ssid") or WIFI_SSID
WIFI_PASS = _s.get("wifi_pass") or WIFI_PASS
NTFY_TOPIC = _s.get("ntfy_topic") or NTFY_TOPIC
ALERTS = WIFI_SSID != "your-wifi" and NTFY_TOPIC != "your-private-topic"
if not ALERTS:
print("Phone alerts off - no Wi-Fi name or ntfy topic set. Flash from flashready.in or type them into main.py.")
power = Pin(22, Pin.OUT, value=0)
sensor = ADC(26)
buzzer = Pin(15, Pin.OUT, value=0)
led = Pin("LED", Pin.OUT)
wlan = network.WLAN(network.STA_IF)
def wifi_up():
if not ALERTS:
return False
if not wlan.isconnected():
wlan.active(True)
wlan.connect(WIFI_SSID, WIFI_PASS)
for _ in range(20):
if wlan.isconnected():
break
time.sleep(0.5)
return wlan.isconnected()
def notify(message, priority="default"):
print(message)
if not wifi_up():
return
try:
import requests
except ImportError:
import urequests as requests
try:
requests.post("https://ntfy.sh/" + NTFY_TOPIC, data=message,
headers={"Title": "Water leak", "Priority": priority, "Tags": "droplet"}).close()
except OSError as e:
print("ntfy failed:", e)
def read_wet():
power.on()
time.sleep_ms(10) # let the strip settle
raw = sum(sensor.read_u16() for _ in range(4)) // 4
power.off()
return raw
wet = False
wet_count = 0
last_alert = 0
notify("Leak detector started", "low")
while True:
raw = read_wet()
# Two wet readings in a row before alarming (a single splash or noise is ignored).
wet_count = wet_count + 1 if raw > WET_ABOVE else 0
now_wet = wet_count >= 2
if now_wet and not wet:
wet = True
last_alert = time.time()
notify("WATER LEAK under the RO / machine! (sensor {})".format(raw), "high")
elif now_wet and time.time() - last_alert > REPEAT_ALERT_S:
last_alert = time.time()
notify("Still wet - water leak not fixed yet", "high")
elif not now_wet and wet and raw < WET_ABOVE // 2:
wet = False
notify("Dry again", "low")
print("raw={} wet={}".format(raw, int(wet)))
led.value(1 if wet else 0)
if wet:
# Pulsed alarm for the 2 s between readings: 0.25 s on, 0.25 s off.
for _ in range(CHECK_EVERY_S * 2):
buzzer.on()
time.sleep(0.25)
buzzer.off()
time.sleep(0.25)
else:
time.sleep(CHECK_EVERY_S)# Water leak detector - Raspberry Pi Pico W (MicroPython).
# Water-level sensor strip on the floor under the RO purifier / washing machine / AC drain:
# + -> GP22 (powered only while reading, so the traces corrode far slower), S -> GP26 (ADC0), - -> GND.
# Buzzer module: I/O -> GP15, VCC -> 3V3, GND -> GND.
# Wet: the buzzer sounds and a high-priority ntfy alert goes to your phone (repeated every 15 min).
from machine import ADC, Pin
import time
import network
import json
WET_ABOVE = 12000 # raw ADC (0-65535); dry strip reads near 0 <- check with your sensor
CHECK_EVERY_S = 2
REPEAT_ALERT_S = 15 * 60
WIFI_SSID = "your-wifi"
WIFI_PASS = "your-password"
NTFY_TOPIC = "your-private-topic" # https://ntfy.sh/<topic> - pick something unguessable
# The FlashReady flasher saves your settings to /settings.json; they win over the lines above.
try:
with open("/settings.json") as f:
_s = json.load(f)
except (OSError, ValueError):
_s = {}
WIFI_SSID = _s.get("wifi_ssid") or WIFI_SSID
WIFI_PASS = _s.get("wifi_pass") or WIFI_PASS
NTFY_TOPIC = _s.get("ntfy_topic") or NTFY_TOPIC
ALERTS = WIFI_SSID != "your-wifi" and NTFY_TOPIC != "your-private-topic"
if not ALERTS:
print("Phone alerts off - no Wi-Fi name or ntfy topic set. Flash from flashready.in or type them into main.py.")
power = Pin(22, Pin.OUT, value=0)
sensor = ADC(26)
buzzer = Pin(15, Pin.OUT, value=0)
led = Pin("LED", Pin.OUT)
wlan = network.WLAN(network.STA_IF)
def wifi_up():
if not ALERTS:
return False
if not wlan.isconnected():
wlan.active(True)
wlan.connect(WIFI_SSID, WIFI_PASS)
for _ in range(20):
if wlan.isconnected():
break
time.sleep(0.5)
return wlan.isconnected()
def notify(message, priority="default"):
print(message)
if not wifi_up():
return
try:
import requests
except ImportError:
import urequests as requests
try:
requests.post("https://ntfy.sh/" + NTFY_TOPIC, data=message,
headers={"Title": "Water leak", "Priority": priority, "Tags": "droplet"}).close()
except OSError as e:
print("ntfy failed:", e)
def read_wet():
power.on()
time.sleep_ms(10) # let the strip settle
raw = sum(sensor.read_u16() for _ in range(4)) // 4
power.off()
return raw
wet = False
wet_count = 0
last_alert = 0
notify("Leak detector started", "low")
while True:
raw = read_wet()
# Two wet readings in a row before alarming (a single splash or noise is ignored).
wet_count = wet_count + 1 if raw > WET_ABOVE else 0
now_wet = wet_count >= 2
if now_wet and not wet:
wet = True
last_alert = time.time()
notify("WATER LEAK under the RO / machine! (sensor {})".format(raw), "high")
elif now_wet and time.time() - last_alert > REPEAT_ALERT_S:
last_alert = time.time()
notify("Still wet - water leak not fixed yet", "high")
elif not now_wet and wet and raw < WET_ABOVE // 2:
wet = False
notify("Dry again", "low")
print("raw={} wet={}".format(raw, int(wet)))
led.value(1 if wet else 0)
if wet:
# Pulsed alarm for the 2 s between readings: 0.25 s on, 0.25 s off.
for _ in range(CHECK_EVERY_S * 2):
buzzer.on()
time.sleep(0.25)
buzzer.off()
time.sleep(0.25)
else:
time.sleep(CHECK_EVERY_S)Compiled for picow on micropython (mpy-cross v1.29.0) · 3 Nov 2026
How it works
RO purifiers, washing machines, geysers in a loft, and split-AC drain pipes all leak eventually — usually at night or while the family is away. A slow drip under a kitchen cabinet swells the plywood and seeps to the flat below before anyone notices. This detector sits on the floor and shouts the moment the floor gets wet.
How it works
The water-level sensor is a set of interleaved copper traces. Dry, the traces are open and the output S reads near 0. Water bridges the traces, current flows from + through the water and a resistor on the board, and S rises.
Every 2 seconds the Pico W switches GP22 on (which powers the strip), waits 10 ms, takes four ADC readings on GP26, and switches it off again. Two wet readings in a row (so a single splash or electrical noise doesn’t count) start the alarm:
- the buzzer pulses on GP15,
- the on-board LED lights,
- an ntfy.sh alert with high priority goes to your phone (it can override “do not disturb” in the ntfy app),
- while it stays wet, the alert repeats every 15 minutes.
When the strip reads clearly dry again (below half the threshold), it sends “Dry again” and stops beeping.
Why these parts
- A ₹40 sensor strip — it only needs to tell wet from dry, and it is cheap enough to replace when it corrodes.
- Pico W with MicroPython — Wi-Fi alerts in a short script you can read and change in Thonny.
- GPIO-powered sensor — the simplest fix for the strip’s biggest weakness, electrolysis.
Setting it up
Install MicroPython on the Pico W (the UF2 from micropython.org), then flash this project from the page: the flasher writes main.py and your Wi-Fi name, password and ntfy topic. Subscribe to the same topic in the ntfy app and pour a spoon of water on the strip to test.
India notes
Many flats in Indian cities store water in lofts and run RO purifiers 24×7, with an inlet that is under mains water pressure. A high-priority alert at 3 a.m. is better than a ceiling stain in the flat below. In the monsoon, the same detector works on a balcony drain or a basement floor.
Troubleshooting
It alarms while the floor is dry
Watch raw= in the REPL/Thonny console. A dry strip should read under a few thousand; humid monsoon air or a dusty strip reads higher. Clean the strip and raise WET_ABOVE above the dry reading.
It doesn’t notice water
Put a few drops across the traces: raw should jump well above WET_ABOVE. If not, check + goes to GP22 and S to GP26 (physical pin 31), not the other way round.
No phone alert
The console prints “Phone alerts off” when the Wi-Fi name or topic is missing. Otherwise look for “ntfy failed” — the Pico W can’t reach the internet from that spot.
Upgrades
- Close a 12 V solenoid valve on the RO inlet through a relay when a leak is detected.
- Use several strips (one per appliance) on GP26, GP27 and GP28 and name the place in the alert.
- Send a weekly “still alive” message so you know the detector is working.



