For most tank and reservoir applications, the definitive choice for a water level detection Arduino build is the JSN-SR04T waterproof ultrasonic sensor paired with an Arduino Nano v3. Unlike resistive probes that corrode within weeks or mechanical float switches that snag on debris, the JSN-SR04T measures distance acoustically from above the water line, keeping your electronics completely dry and maintenance-free.
This guide cuts through the guesswork. We will run a decision matrix to confirm this sensor is right for your specific tank, provide the exact pinout for the Nano v3, and supply production-ready C++ code featuring a median filter to eliminate acoustic ghost readings.
The Sensor Decision Matrix: Pick Your Water Level Detector
Not all water tanks are built the same. Before you order parts, run your scenario through this decision path to ensure you do not buy the wrong transducer.
| Sensor Type | Best For | Fatal Flaw | Approx. Cost |
|---|---|---|---|
| JSN-SR04T (Waterproof Ultrasonic) | Sealed tanks, outdoor cisterns, potable water | 20cm blind zone; wide beam angle | $8 - $12 |
| HC-SR04 (Standard Ultrasonic) | Indoor, open-top bins, hydroponics | Silver mesh corrodes rapidly in high humidity | $2 - $4 |
| Vertical Float Switch | Sump pumps, simple high/low alarms | Mechanical snagging; cannot measure continuous % | $3 - $6 |
| Resistive/Conductivity Probe | Short-term leak detection | Electrolysis destroys probes in weeks | $1 - $3 |
- If your tank is sealed, located outdoors, or holds drinking water where submerging electronics is a contamination risk → Pick the JSN-SR04T.
- If you only need to know if a sump pit is full or empty (binary state) → Pick a Vertical Float Switch.
- If you are monitoring an open-top indoor bin on a strict budget → Pick the HC-SR04.
Default Recommendation: Unless you are building a simple binary sump alarm, buy the JSN-SR04T. The $6 premium over standard sensors saves you from inevitable humidity-induced hardware failures.
Hardware Spec Sheet & Pin Mapping
This build targets the Arduino Nano v3 (ATmega328P). The Nano is chosen over the Uno for its compact footprint, making it easy to mount inside a small weatherproof junction box on the tank lid. The JSN-SR04T V2.0 draws roughly 20mA during a ping cycle, which is easily handled by the Nano's 5V rail.
Bill of Materials
- 1x Arduino Nano v3 (ATmega328P with CH340 USB-C or Mini-B)
- 1x JSN-SR04T Waterproof Ultrasonic Sensor (V2.0)
- 1x 4.7kΩ Resistor (for pull-down noise mitigation on Echo pin)
- 1x IP65 Waterproof Junction Box (for mounting the Nano)
- Silicone sealant (for cable gland penetrations)
Pin Mapping Table
| JSN-SR04T Pin | Arduino Nano v3 Pin | Notes |
|---|---|---|
| 5V (VCC) | 5V | Do not use 3.3V; sensor requires 5V to trigger reliably. |
| Trig | D9 | Digital Output |
| Echo | D10 | Digital Input (5V tolerant). Add 4.7kΩ to GND to prevent floating noise. |
| GND | GND | Common ground required. |
Step-by-Step Wiring & Assembly
Ultrasonic sensors have specific physical constraints. The JSN-SR04T has a blind zone of roughly 20cm to 25cm. If the water level rises within 20cm of the transducer face, the acoustic pulse will bounce back too fast for the microcontroller to process, resulting in erratic max-distance readings. Furthermore, the sensor has a ~60-degree beam angle; if your tank diameter is less than 30cm, the sound waves will reflect off the tank walls instead of the water surface.
- Mount the Transducer: Drill a hole in your tank lid matching the diameter of the JSN-SR04T transducer housing. Push it through and secure it with the provided locking nut. Ensure the mesh face points straight down.
- Verify Clearance: Measure from the transducer face to the maximum possible water level. This distance must be greater than 25cm. If it is not, raise the sensor using a PVC pipe extension.
- Wire the Control Board: Connect the Trig pin to Nano D9, and Echo to Nano D10. Solder the 4.7kΩ resistor between D10 and GND on the Nano side to bleed off static charge and prevent ghost triggers.
- Seal the Enclosure: Route the 4-wire cable from the tank lid to your IP65 junction box. Use a cable gland and apply silicone sealant to prevent moisture from creeping into the box and shorting the Nano.
Complete Arduino Code with Error Handling
The code below targets the Arduino Nano v3. It avoids external libraries like NewPing to ensure it compiles out-of-the-box on a fresh Arduino IDE install. It includes a median filter to discard acoustic anomalies (like a splash or a bat flying by) and calculates the actual water volume percentage based on your tank's physical dimensions.
/*
* Water Level Detection Arduino Project
* Target Board: Arduino Nano v3 (ATmega328P)
* Sensor: JSN-SR04T Waterproof Ultrasonic
*/
#define TRIG_PIN 9
#define ECHO_PIN 10
#define SPEED_OF_SOUND_CM_US 58.3 // Microseconds per cm (round trip)
// --- CONFIGURATION: EDIT THESE VALUES FOR YOUR TANK ---
#define TANK_TOTAL_DEPTH_CM 120.0 // Distance from sensor to tank floor
#define SENSOR_BLIND_ZONE_CM 25.0 // JSN-SR04T minimum read distance
#define MAX_VALID_DISTANCE_CM 450.0 // Sensor max range
const int sampleSize = 5;
long distanceSamples[sampleSize];
void setup() {
Serial.begin(115200);
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
digitalWrite(TRIG_PIN, LOW);
Serial.println("System Initialized. Calibrating...");
delay(1000);
}
void loop() {
float medianDistance = getMedianDistance();
// Error Handling: Filter out blind zone and timeout errors
if (medianDistance < SENSOR_BLIND_ZONE_CM) {
Serial.println("ERROR: Water level too high (Inside blind zone < 25cm)");
}
else if (medianDistance > MAX_VALID_DISTANCE_CM || medianDistance == 0) {
Serial.println("ERROR: Sensor timeout or disconnected. Check wiring.");
}
else {
float waterLevelCm = TANK_TOTAL_DEPTH_CM - medianDistance;
if (waterLevelCm < 0) waterLevelCm = 0;
float percentage = (waterLevelCm / TANK_TOTAL_DEPTH_CM) * 100.0;
Serial.print("Distance to water: ");
Serial.print(medianDistance, 1);
Serial.print(" cm | Water Level: ");
Serial.print(waterLevelCm, 1);
Serial.print(" cm (");
Serial.print(percentage, 1);
Serial.println("%)");
}
delay(1000); // Ping once per second
}
// --- MEDIAN FILTER FOR ACOUSTIC NOISE ---
float getMedianDistance() {
for (int i = 0; i < sampleSize; i++) {
distanceSamples[i] = readUltrasonic();
delay(20); // Short delay between pings to avoid echo overlap
}
// Simple bubble sort for small array
for (int i = 0; i < sampleSize - 1; i++) {
for (int j = 0; j < sampleSize - i - 1; j++) {
if (distanceSamples[j] > distanceSamples[j + 1]) {
long temp = distanceSamples[j];
distanceSamples[j] = distanceSamples[j + 1];
distanceSamples[j + 1] = temp;
}
}
}
return distanceSamples[sampleSize / 2];
}
long readUltrasonic() {
// Clear trigger pin
digitalWrite(TRIG_PIN, LOW);
delayMicroseconds(5);
// Send 10us pulse to trigger
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
// Read echo, timeout at 30000us (~500cm)
long duration = pulseIn(ECHO_PIN, HIGH, 30000);
if (duration == 0) return MAX_VALID_DISTANCE_CM + 1; // Timeout flag
return duration / SPEED_OF_SOUND_CM_US;
}
Troubleshooting: The First Three Things to Check
When your build fails, do not start rewriting code. Hardware and environment issues cause 95% of ultrasonic sensor failures. Check these three specific failure modes first.
1. Upload Fails with Sync Error
Exact Error String: avrdude: stk500_getsync() attempt 10 of 10: not in sync: resp=0x00
- Cause A: Missing CH340 USB-serial driver (common on clone Nanos). Install the latest CH34x driver for your OS.
- Cause B: Wrong bootloader selected. In the Arduino IDE, go to Tools → Processor and select "ATmega328P (Old Bootloader)". Most third-party Nanos use the old bootloader.
2. Serial Monitor Reads Constant '0 cm' or 'Timeout'
Symptom: The code compiles, but the serial output throws the timeout error or reads 0.
- Cause A: Trig and Echo pins are swapped. Verify D9 is Trig and D10 is Echo.
- Cause B: Insufficient power. The JSN-SR04T requires a solid 5V. If you are powering the Nano via a weak USB hub, the voltage may drop below 4.5V during the acoustic ping, causing the sensor logic to brownout. Measure the 5V pin with a multimeter during operation.
3. Erratic Readings Jumping Between 10cm and 300cm
Symptom: The water is still, but the serial monitor shows wild fluctuations.
- Cause A (Environmental): Condensation has formed on the transducer mesh. Water droplets scatter the acoustic pulse. Wipe the mesh dry and apply a very thin coating of hydrophobic spray (like Scotchguard) to the outside of the mesh.
- Cause B (Acoustic): The tank walls are reflecting the 60-degree beam. If your tank is narrow, line the upper inside walls of the tank with acoustic foam, or mount a PVC tube extending down from the sensor to isolate the beam path.
How to Extend or Simplify This Build
Depending on your end goal, you can scale this project down for a weekend science fair, or scale it up for a whole-home smart water system.
If you are just monitoring an open-top indoor hydroponics reservoir, swap the JSN-SR04T for the standard HC-SR04 ($2). Drop the IP65 enclosure and the pull-down resistor. The code remains 100% identical. Just keep it away from heavy misting.
To push water level data to Home Assistant via Wi-Fi, swap the Arduino Nano for an ESP32-WROOM-32 DevKit v1. The ESP32 operates on 3.3V logic, so you must add a voltage divider (e.g., 1kΩ and 2kΩ resistors) on the Echo pin to step the 5V signal down to 3.3V before it hits the ESP32 GPIO. Use the ESP-IDF MQTT libraries to publish the percentage payload to your local broker every 5 minutes.
By selecting the correct transducer for your environment and implementing a software median filter, your water level detection Arduino project will run for years without requiring physical maintenance. For more details on timing functions, refer to the official Arduino pulseIn() documentation.






