Anyone with an overhead water tank knows the feeling — you turn on the tap, expect water, and get a sputter. This project sits in the lid of the tank, looks down at the water surface with an ultrasonic ping, and reports the level over WiFi. When the level drops below a threshold, it pings your phone via a webhook. The hardware costs $20, the install takes thirty minutes, and the device runs for months on AA batteries.
How it works
every 15 min] Wake --> Ping[JSN-SR04T pings
distance to water] Ping --> Calc{Calculate
level %} Calc -->|level > threshold| Sleep[Deep sleep
15 min] Calc -->|level < threshold| WiFi[Connect WiFi
POST to webhook] WiFi --> Sleep Sleep --> Wake
Flow: the ESP32 sleeps 99% of the time. It wakes, pings, decides whether to alert, sleeps again. Battery life is dominated by the sleep current of the regulator.
Bill of materials
- ESP32 dev board (ideally low-power: TTGO T7, FireBeetle) — $9
- JSN-SR04T waterproof ultrasonic sensor — $5
- 3 × AA holder + 3 AA cells — $4
- Active piezo buzzer (optional, local audible alert) — $1
- Project box (sealed, drillable) — $5
Total around $20. The JSN-SR04T (note: the "T" suffix is critical — the regular HC-SR04 is not waterproof) has the transducer on a 2.5m cable so the electronics stay in a dry box on top of the tank.
Wiring
JSN-SR04T ESP32
+5V --- 5V (Vin)
GND --- GND
TRIG --- GPIO 12
ECHO --- GPIO 14
Buzzer + GPIO 13 (optional)
Buzzer - GNDThe JSN-SR04T speaks the same TTL protocol as the HC-SR04 — fire TRIG high for 10 µs, measure the time ECHO stays high. The library NewPing handles this and converts to centimeters automatically.
Firmware design
Almost everything happens in setup(). The ESP32 wakes from deep sleep, pings the sensor, computes the level percentage based on a calibrated tank-empty and tank-full distance (you measure these once during install), and decides whether to alert. If alerting, it connects to WiFi and POSTs JSON to your webhook (IFTTT, Telegram bot, Home Assistant — whichever you prefer). Then it sleeps for 15 minutes. The loop() function is never called; deep sleep wakes back into setup.
For battery life, the entire wake cycle takes about 8 seconds. At 80 mA active and 10 µA sleep, that averages around 0.7 mA — three AAs (3000 mAh) last roughly 4-5 months.
Calibration
Once installed, you need to teach the firmware what "empty" and "full" mean. Record the sensor distance reading when the tank is full (typically 10-30 cm — the sensor needs to clear the tank lid) and when it is empty (typically 200+ cm depending on tank height). Set FULL_CM and EMPTY_CM in config.h. The level percent is computed as map(distance, EMPTY_CM, FULL_CM, 0, 100).
Going further
- Add LoRa or NB-IoT instead of WiFi for tanks far from the house. WiFi rarely reaches a rooftop tank reliably.
- Track usage patterns over time. Two weeks of data tells you the household consumption rate, and the firmware can predict empty time more accurately than threshold alerts.
- Add a second sensor for greywater / rainwater tanks; same firmware, different topic.
Key code: setup
This is the heart of the firmware, taken from the working sketch. The complete file (with config template, library list, and the rest of the helpers) is around 61 lines and is included in the downloadable project package — request it via the form below.
void setup() {
Serial.begin(115200);
pinMode(BUZZER_PIN, OUTPUT);
wakeCount++;
int pct = readLevelPct();
Serial.printf("[level] %d%% (wake #%d)\n", pct, wakeCount);
if (pct >= 0 && pct < THRESHOLD_PCT) {
// Local beep
for (int i = 0; i < 3; i++) {
digitalWrite(BUZZER_PIN, HIGH); delay(150);
digitalWrite(BUZZER_PIN, LOW); delay(150);
}
sendAlert(pct);
}
Serial.println("[sleep] zzz...");
esp_sleep_enable_timer_wakeup((uint64_t)WAKE_INTERVAL_MIN * 60ULL * 1000000ULL);
esp_deep_sleep_start();
}Get the complete project package
The article above shows the core firmware and the principles behind it. The complete project package — assembled, tested, and ready to flash — is available by email request. We send it manually, and we read every request.
- Complete Arduino sketch (.ino) with full error handling
- List of required libraries with version numbers
- Printable wiring diagram (PDF)
- Bill of materials with current part numbers
- Build guide and troubleshooting tips
- Configuration template (WiFi, MQTT, etc.)
Share your thoughts
Worked with this in production and have a story to share, or disagree with a tradeoff? Email us at support@mybytenest.com — we read everything.