If you have ever tried to build a water level detector using a standard $1 PCB rain sensor module, you already know the painful truth: exposed copper traces and water do not mix. The moment you apply a DC voltage across exposed traces in a wet environment, electrolysis begins. Within 48 hours, the anode trace will corrode into a green, flaky mess, and your sensor will fail. For a reliable water level detector Arduino project, you must abandon resistive probes and move to non-contact or isolated sensing.
This guide walks through a bench-tested, non-contact capacitive build designed for sealed plastic tanks. We will cover the exact hardware selection, provide a fail-safe code implementation with dry-run protection, and detail the specific debugging steps when the sensor misreads.
The Sensor Selection Decision Tree
Before wiring a single jumper, you must choose the right sensing mechanism for your specific tank environment. Use this decision matrix to terminate your part selection.
| Sensor Type | Mechanism | Lifespan in Water | Verdict |
|---|---|---|---|
| PCB Rain Module | Resistive (Exposed) | < 1 week | Reject: Electrolysis destroys traces rapidly. |
| HC-SR04 Ultrasonic | Acoustic Time-of-Flight | N/A (Fails on condensation) | Reject: Water droplets on the transducer cause false echoes. |
| Stainless Float Switch | Magnetic Reed Relay | 5+ years | Conditional: Excellent for open sumps, but requires tank penetration. |
| XKC-Y25-V (5V) | Capacitive (Non-Contact) | Indefinite | DEFAULT PICK: Mounts outside plastic tanks. Zero corrosion risk. |
The Concrete Pick: For sealed, non-metallic tanks (like IBC totes, RO water reservoirs, or PVC setups), use the XKC-Y25-V (5V version). It senses the dielectric change of water through up to 13mm of plastic wall thickness without ever touching the liquid.
Parts List and Wiring Spec Sheet
This build targets the Arduino Nano (ATmega328P, 5V/16MHz variant). We use the 5V Nano specifically to match the 5V logic output of the XKC-Y25-V sensor without requiring a logic level shifter or voltage divider.
Bill of Materials (BOM)
- Microcontroller: Arduino Nano (ATmega328P, 5V logic) - ~$6 (clone) / $22 (genuine)
- Sensor: XKC-Y25-V Non-Contact Liquid Level Sensor (5V version) - ~$8. Warning: Do not buy the XKC-Y25-T12V or 24V variants; they require higher supply voltages and output 12V/24V logic that will fry the Nano's GPIO.
- Actuator: 5V Relay Module with Optocoupler Isolation (Songle SRD-05VDC-SL-C) - ~$3
- Power: 5V 2A USB power supply (to handle relay coil inrush)
- Consumables: High-bond double-sided tape (e.g., 3M VHB) or thermal paste for sensor coupling.
Pin Mapping Table
| Component | Wire / Pin | Arduino Nano Pin | Notes |
|---|---|---|---|
| XKC-Y25-V | Red (VCC) | 5V | Must be stable 5V. Do not use 3V3. |
| XKC-Y25-V | Black (GND) | GND | Share common ground with Nano and Relay. |
| XKC-Y25-V | Brown (OUT) | D2 | Digital HIGH when water is present. |
| Relay Module | VCC | 5V | Powers the optocoupler LED and coil. |
| Relay Module | GND | GND | Common ground. |
| Relay Module | IN | D8 | Active LOW on most optocoupler modules. |
Capacitive sensors require a tight physical bond to the tank wall. Any air gap will act as a dielectric barrier and cause false negatives. Use 3M VHB tape or a thin smear of thermal paste between the sensor face and the plastic tank to eliminate air pockets.
Step-by-Step Build and Compilable Code
- Prep the Tank: Clean the outside of the plastic tank with isopropyl alcohol at the desired trigger height. Let it dry completely.
- Mount the Sensor: Apply 3M VHB tape to the XKC-Y25-V face and press it firmly against the tank. Route the wires away from any high-voltage pump lines to avoid EMI.
- Wire the Nano: Connect the sensor and relay to the Nano according to the pin mapping table above. Ensure the relay module's jumper is set to 'VCC' (not 'JD-VCC') if powering directly from the Nano's 5V rail.
- Upload the Firmware: Flash the code below. This implementation includes a critical safety feature: a dry-run timeout. If the pump runs for more than 5 minutes without the sensor dropping LOW, the system halts to prevent the pump motor from burning out.
#include <Arduino.h>
// --- PIN DEFINITIONS ---
#define SENSOR_PIN 2
#define RELAY_PIN 8
// --- TIMING CONSTANTS ---
#define DEBOUNCE_MS 200
#define MAX_RUN_MS 300000 // 5 minute dry-run protection limit
// --- STATE VARIABLES ---
bool pumpState = false;
unsigned long pumpStartTime = 0;
unsigned long lastDebounce = 0;
bool faultTriggered = false;
void setup() {
Serial.begin(9600);
pinMode(SENSOR_PIN, INPUT);
pinMode(RELAY_PIN, OUTPUT);
// Ensure relay starts OFF (Active LOW relay modules require HIGH to be off)
digitalWrite(RELAY_PIN, HIGH);
Serial.println(F("Water Level Controller Initialized"));
}
void loop() {
// Halt execution if a critical fault was detected
if (faultTriggered) {
return;
}
int reading = digitalRead(SENSOR_PIN);
unsigned long currentTime = millis();
// Debounce logic to prevent relay chatter from sensor jitter
if ((currentTime - lastDebounce) > DEBOUNCE_MS) {
lastDebounce = currentTime;
// Water detected, turn pump ON
if (reading == HIGH && !pumpState) {
pumpState = true;
pumpStartTime = currentTime;
digitalWrite(RELAY_PIN, LOW); // Active LOW relay
Serial.println(F("Pump ON - Water Detected"));
}
// Water cleared, turn pump OFF
else if (reading == LOW && pumpState) {
pumpState = false;
digitalWrite(RELAY_PIN, HIGH); // Active LOW relay
Serial.println(F("Pump OFF - Tank Cleared"));
}
}
// Safety Check: Dry-run timeout protection
if (pumpState && (currentTime - pumpStartTime > MAX_RUN_MS)) {
digitalWrite(RELAY_PIN, HIGH); // Force pump OFF
faultTriggered = true;
Serial.println(F("[FAULT] PUMP DRY-RUN TIMEOUT - SYSTEM HALTED"));
}
}
Debugging: Exact Errors and Failure Modes
When a non-contact capacitive sensor fails in the field, it rarely fails silently. Here is how to diagnose the most common issues based on serial output and physical behavior.
First Three Things to Check When It Fails
- Wall Thickness and Material: The XKC-Y25-V maxes out at roughly 13mm of non-metallic material. If you are using a thick-walled PVC pipe or a stainless steel tank, the capacitive field cannot penetrate. Fix: Switch to a stainless steel float switch for metal tanks.
- Voltage Variant Mismatch: Check the sticker on the sensor. If it says 12V or 24V, it will not output a clean 5V logic HIGH to the Nano, leading to floating pin states. Fix: Replace with the exact 5V XKC-Y25-V model.
- EMI from Pump Motors: Inductive kickback from the pump motor can cause voltage sags on the 5V rail, resetting the Arduino Nano or causing the sensor to output phantom HIGH signals. Fix: Power the relay coil and pump from a separate 5V/12V supply, sharing only the GND with the Nano.
Exact Error String: [FAULT] PUMP DRY-RUN TIMEOUT - SYSTEM HALTED
If your serial monitor prints this exact string, the microcontroller intentionally shut down the pump because it ran for 5 continuous minutes without the sensor reporting an empty tank. This is a protective measure to save your pump impeller from melting.
Ranked Causes:
- Cause 1 (Most Likely): The tank has a leak or the inlet valve is blocked, and the tank is genuinely not filling. Verify physical water flow.
- Cause 2: The sensor has peeled away from the tank wall, creating an air gap. The sensor no longer 'sees' the water dielectric. Re-tape with fresh VHB.
- Cause 3: Mineral buildup on the inside of the tank wall is masking the water level change. Drain and scrub the tank interior.
For a deeper understanding of how capacitive sensing handles environmental noise and dielectric variations, refer to this Analog Devices capacitive sensor primer. Additionally, review the official Arduino debounce documentation to understand why the 200ms delay in our code is critical for preventing relay contact welding.
Extending the Build: Relays, Wi-Fi, and Simplification
Depending on your deployment environment, you may need to scale this project up for smart-home integration, or scale it down to remove the microcontroller entirely.
How to Simplify (No Code Required)
If you do not need serial logging, dry-run timeouts, or Wi-Fi alerts, drop the Arduino entirely. You can wire a 12V stainless steel float switch directly to the coil of a 12V automotive relay. When the water rises, the float closes the reed switch, energizing the relay coil, and switching the high-power pump. This reduces your part count to three components, eliminates software bugs, and drops the BOM cost to under $10.
How to Extend (IoT and Telemetry)
If you want to receive a push notification when the [FAULT] triggers, swap the Arduino Nano for an ESP32-WROOM-32 DevKit v1.
Migration Steps:
- Change the sensor power to the ESP32's 5V
VINpin (the XKC-Y25-V requires 5V, not 3.3V). - Move the sensor output to a GPIO that doesn't conflict with the ESP32's boot strapping pins (use GPIO 4 or 5).
- Add the
PubSubClientlibrary to publish the fault state to an MQTT broker (like Mosquitto) for Home Assistant integration. - Implement a watchdog timer (WDT) in the ESP32 code to automatically reboot the system if the Wi-Fi stack hangs, ensuring the pump controller remains online.
By selecting the correct non-contact sensor and implementing software-based safety limits, your water level detector will survive years of continuous operation without a single corroded trace.






