
Pond Water Level Monitor for Aquaculture
Uses an HC-SR04 ultrasonic sensor on an ESP32 to measure aquaculture pond water level in real time, with a local buzzer alert when the level is too low or too high.
In this project, you'll build a pond water level monitoring system for aquaculture using an HC-SR04 ultrasonic sensor mounted on an ESP32 board. The system measures the pond's water level in real time and sounds a buzzer alert when the level is too low or too high. It's a fun project that puts IoT concepts to practical use in aquaculture.
What You'll Learn
How to use an HC-SR04 ultrasonic sensor to measure distance.
How to configure and program an ESP32 to connect to and process sensor data.
How to detect and handle alert conditions when the water level is out of range.
How to integrate a buzzer for audible alerts.
How to apply IoT concepts in a real-world project.
Key Features
Fast, accurate water level measurement with the HC-SR04.
Instant local buzzer alert when the water level crosses a set threshold.
Simple, easy-to-follow design suitable for beginners and experienced makers alike.
Easy to extend with data logging or network reporting features.
Why This Project Is Worth Building
This project doesn't just sharpen your programming and IoT skills — it delivers real practical value for aquaculture. Water level is a critical factor for the survival of farmed fish and shrimp, so monitoring and controlling it matters. This system lets farmers stay ahead of water-level problems and protect their stock, ultimately improving yield and product quality.
Detailed guide
Uses an HC-SR04 ultrasonic sensor on an ESP32 to measure aquaculture pond water level in real time, with a local buzzer alert when the level is too low or too high.
1. Introduction
In aquaculture (shrimp, fish farming), pond/tank water level directly affects animal health — too little water causes heat stress and low dissolved oxygen, while overflow causes water loss and pollutes the surrounding area. This project mounts an HC-SR04 ultrasonic sensor above the pond to measure the distance down to the water surface in real time, converts it to a water-level percentage, and sounds a local buzzer alert when the level drops too low (needs a top-up) or rises too high (risk of overflow/flooding).
This is a non-contact measurement approach well suited to pond environments, since the sensor never touches the water — reducing the risk of corrosion or fouling compared to float-type or electrode-based water-level sensors.
2. Components Needed
Component | Qty | Reference Price |
|---|---|---|
ESP32 DevKit V1 (30-pin) | 1 | ~90,000 - 120,000₫ |
HC-SR04 — Ultrasonic Distance Sensor | 1 | ~15,000 - 25,000₫ |
Active Buzzer — local alarm | 1 | ~5,000 - 10,000₫ |
1kΩ + 2kΩ resistors (voltage divider for the ECHO pin) | 1 set | ~5,000₫ |
Waterproof enclosure (IP65+) for mounting the sensor at the pond's edge | 1 | Varies by material |
Jumper wires, a sensor mount, 5V power supply | 1 set | ~20,000 - 30,000₫ |
3. Wiring Diagram
HC-SR04 | ESP32 |
|---|---|
VCC | VIN (5V) |
GND | GND |
TRIG | GPIO26 |
ECHO | GPIO27 (through a voltage divider — see the note below) |
Active Buzzer | ESP32 |
|---|---|
VCC | 3V3 |
GND | GND |
IO | GPIO25 |
Important hardware safety note:
The HC-SR04 outputs its ECHO pulse at 5V, but the ESP32's GPIO only tolerates up to about 3.3V (~3.6V absolute maximum). When deploying on real hardware,
you must use a resistor voltage divider (e.g. 1kΩ in series to GND through a 2kΩ, tapping the midpoint into GPIO27) or a level-shifter module on the ECHO line before connecting it to the ESP32 — otherwise you risk damaging the GPIO pin/chip over time.
The sensor also needs to be mounted in a watertight enclosure when placed outdoors or over the pond, to prevent damage from rain/humidity.
4. Example #1 — Basic Distance Measurement with the HC-SR04
#define TRIG_PIN 26
#define ECHO_PIN 27
void setup() {
Serial.begin(115200);
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
}
void loop() {
digitalWrite(TRIG_PIN, LOW);
delayMicroseconds(2);
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
long duration = pulseIn(ECHO_PIN, HIGH, 25000);
float distanceCm = duration * 0.0343 / 2.0;
if (duration == 0) {
Serial.println("Khong doc duoc cam bien (timeout)");
} else {
Serial.print("Khoang cach: ");
Serial.print(distanceCm);
Serial.println(" cm");
}
delay(1000);
}
5. Example #2 — Buzzer Alert When Water Level Crosses a Threshold
#define BUZZER_PIN 25
const float DISTANCE_LOW_WATER_CM = 130.0; // qua xa cam bien = nuoc thap
void setup() {
pinMode(BUZZER_PIN, OUTPUT);
digitalWrite(BUZZER_PIN, LOW);
}
void loop() {
float distanceCm = 140.0; // thay bang gia tri do tu HC-SR04 thuc te
if (distanceCm > DISTANCE_LOW_WATER_CM) {
digitalWrite(BUZZER_PIN, HIGH); // canh bao nuoc thap
} else {
digitalWrite(BUZZER_PIN, LOW);
}
delay(1000);
}
6. Example #3 — The Complete Pond Water-Level Monitoring Application
/*
Theo doi muc nuoc ao nuoi thuy san
Board: ESP32 DevKit V1 (30 pins)
Cam bien: HC-SR04 (sieu am do khoang cach tu mep ao xuong mat nuoc)
Canh bao tai cho: Active Buzzer
Wiring (khop voi diagram tren IoTLabs Maker):
HC-SR04 TRIG -> GPIO26 ECHO -> GPIO27
VCC -> VIN(5V) GND -> GND
Buzzer IO -> GPIO25 VCC -> 3V3 GND -> GND
LUU Y QUAN TRONG (an toan phan cung):
HC-SR04 xuat xung ECHO muc 5V nhung GPIO ESP32 chi chiu duoc toi da 3.3V (~3.6V).
Trien khai thuc te BAT BUOC dung cau phan ap tro (vi du 1kOhm + 2kOhm) tren chan ECHO
truoc khi noi vao GPIO27, neu khong se co nguy co hong chan GPIO/chip ve lau dai.
Ngoai ra can op ky watertight cho dau do khi lap ngoai troi/tren mat ao.
Nguyen ly do muc nuoc:
distance_cm = khoang cach tu cam bien (gan mieng ao) xuong mat nuoc.
muc nuoc CAO <-> distance_cm NHO (nuoc gan cam bien)
muc nuoc THAP <-> distance_cm LON (nuoc xa cam bien)
Canh bao khi muc nuoc qua thap (can bom bo sung) hoac qua cao (nguy co tran/ngap).
*/
#define TRIG_PIN 26
#define ECHO_PIN 27
#define BUZZER_PIN 25
// Hieu chinh theo kich thuoc ao thuc te (khoang cach tu cam bien den day/mep ao)
const float SENSOR_TO_EMPTY_CM = 150.0; // khoang cach khi ao can (muc nuoc = 0%)
const float SENSOR_TO_FULL_CM = 20.0; // khoang cach khi ao day (muc nuoc = 100%)
const float LOW_LEVEL_ALERT_PCT = 20.0; // canh bao muc nuoc thap
const float HIGH_LEVEL_ALERT_PCT = 95.0; // canh bao muc nuoc qua cao / sap tran
const unsigned long MEASURE_INTERVAL_MS = 3000;
const unsigned long PULSE_TIMEOUT_US = 25000; // ~4.3m, du cho khoang cach thuc te trong ao
unsigned long lastMeasure = 0;
bool sensorTimeoutSeen = false;
float measureDistanceCm() {
digitalWrite(TRIG_PIN, LOW);
delayMicroseconds(2);
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
unsigned long duration = pulseIn(ECHO_PIN, HIGH, PULSE_TIMEOUT_US);
if (duration == 0) {
return -1.0; // timeout / khong doc duoc - cam bien mat ket noi hoac ngoai tam
}
return duration * 0.0343 / 2.0; // toc do am thanh ~343 m/s
}
void setBuzzer(bool on) {
digitalWrite(BUZZER_PIN, on ? HIGH : LOW);
}
void setup() {
Serial.begin(115200);
delay(300);
Serial.println();
Serial.println(F("=== Theo doi muc nuoc ao nuoi IoTLabs - Boot OK ==="));
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
pinMode(BUZZER_PIN, OUTPUT);
digitalWrite(TRIG_PIN, LOW);
setBuzzer(false);
Serial.println(F("HC-SR04 + Buzzer san sang. Bat dau do muc nuoc dinh ky."));
}
void loop() {
unsigned long now = millis();
if (now - lastMeasure < MEASURE_INTERVAL_MS) {
return;
}
lastMeasure = now;
float distanceCm = measureDistanceCm();
if (distanceCm < 0) {
sensorTimeoutSeen = true;
setBuzzer(false); // khong doan canh bao khi khong co du lieu tin cay
Serial.println(F("{\"distance_cm\":null,\"level_pct\":null,\"alert\":null,\"status\":\"sensor_timeout\"}"));
return;
}
sensorTimeoutSeen = false;
// Quy doi khoang cach -> % muc nuoc, gioi han 0-100
float levelPct = (SENSOR_TO_EMPTY_CM - distanceCm) /
(SENSOR_TO_EMPTY_CM - SENSOR_TO_FULL_CM) * 100.0;
if (levelPct < 0) levelPct = 0;
if (levelPct > 100) levelPct = 100;
const char* alert = "normal";
bool buzzerOn = false;
if (levelPct <= LOW_LEVEL_ALERT_PCT) {
alert = "low_water";
buzzerOn = true;
} else if (levelPct >= HIGH_LEVEL_ALERT_PCT) {
alert = "high_water";
buzzerOn = true;
}
setBuzzer(buzzerOn);
Serial.print(F("{\"distance_cm\":"));
Serial.print(distanceCm, 1);
Serial.print(F(",\"level_pct\":"));
Serial.print(levelPct, 1);
Serial.print(F(",\"alert\":\""));
Serial.print(alert);
Serial.println(F("\",\"status\":\"ok\"}"));
}
7. Common Issues
Issue | Cause | Fix |
|---|---|---|
pulseIn() always returns 0 (timeout) | The sensor isn't aimed straight down at the flat water surface, or the distance exceeds the HC-SR04's 400cm range | Make sure the sensor is perpendicular to the water surface, and mounted within the 2-400cm measuring range |
Readings jump around erratically when there's wind/waves | Ripples on the water surface make the sound reflection unstable | Apply a moving average filter across several readings, or increase the measurement interval to a few seconds |
The ESP32's GPIO fails after long-term outdoor use | No voltage divider on the ECHO pin — the GPIO is continuously exposed to a 5V pulse beyond its tolerance | Always use a resistor voltage divider (1kΩ+2kΩ) or a level-shifter on ECHO before connecting it to GPIO27 |
The buzzer sounds continuously even at a normal water level | The alert thresholds are set incorrectly (LOW/HIGH swapped), or SENSOR_TO_EMPTY_CM/SENSOR_TO_FULL_CM haven't been calibrated for the real pond | Measure the actual distance when the pond is empty and when it's full to calibrate these two constants |
8. Summary
This pond water-level monitoring system catches a too-low or too-high water level early, without a sensor that touches the water directly — well suited to the harsh conditions of an aquaculture pond.
The local buzzer alert lets operators know right away, even with no network connection.
Possible extensions: send water-level data over MQTT to a dashboard monitoring multiple ponds at once, or integrate automatic pump control via a relay when the level drops below a safe threshold.