Project · Water · Monitoring
A water tank that predicts its own empty
Knowing the tank is at 62 % is nice. Knowing it will be empty in 14 hours — and that the sudden extra drain looks like a burst pipe — is what actually saves you. One waterproof ultrasonic sensor plus an ESP32 gives you level, volume, a time-to-empty prediction, leak alerts and automatic pump control, on a dashboard the chip serves itself.
The sensing — one ping from the lid
A JSN-SR04T (the waterproof HC-SR04) mounts inside the tank lid and pings the water
surface. The echo gives the air gap; the level follows from the tank height:
levelPct = 100 · (1 − gapCm / TANK_CM), and litres from the capacity. Wiring is the
standard ultrasonic hookup — 5 V power, a divider on ECHO for the 3.3 V pin:
The prediction — an EMA, not machine learning
Time-to-empty doesn't need a model in the cloud. Smooth the drain rate with an exponential moving average (so one flushed toilet doesn't panic the forecast), then divide the remaining level by it. Leak detection is the same number with a threshold: if water disappears far faster than your household norm while the pump is off — something is leaking.
// --- the predictor: smooth the drain rate, project time-to-empty ---
float dPct = (prevPct - levelPct) * 240.0f; // %/min at 250 ms per tick
prevPct = levelPct;
if (!pump) ratePctMin += 0.05f * (dPct - ratePctMin); // EMA — calm, not jumpy
hoursLeft = ratePctMin > 0.01f ? levelPct / ratePctMin / 60.0f : 99;
// --- leak detection: dropping much faster than the household norm, pump off ---
bool leakNow = !pump && ratePctMin > 0.9f;
if (leakNow && !leak && evlog) evlog->print("LEAK suspected - close the valve");
leak = leakNow;
// --- auto-fill: pump on below 25 %, off above 90 % ---
if (levelPct < 25) pump = true;
else if (levelPct > 90) pump = false; The dashboard
Level bar, volume, the “Empty in” stat, a trend chart, the raw sensor page, a leak LED and a pump toggle — declared in a dozen lines; the ESP serves the whole UI over its own access point:
dash.layout("Tank", RICON_WATER);
dash.gauge("Level", &levelPct, 0, 100, "%").variant("bar");
dash.stat("Volume", &liters, "L");
dash.stat("Empty in", &hoursLeft, "h"); // the prediction, live
dash.chart("Trend", &levelPct, "%");
dash.layout("Sensor", RICON_SIGNAL);
dash.metric("Air gap", &distCm, "cm"); // what the ultrasonic actually reads
dash.metric("Drain rate", &ratePctMin, "%/min");
dash.led("Leak suspected", &leak);
dash.toggle("Pump", &pump);
dash.beginAP("RisalDash-Tank", "12345678"); The shipped example (examples/04_Templates/TankLevel) runs with no
hardware: the tank drains, refills, and occasionally springs a simulated leak so you can
watch the alert fire. Swap one line for the real pingCm() when the sensor arrives —
the fake-sensor workflow.
Bill of materials
| Part | Role | ~Cost |
|---|---|---|
| ESP32 / ESP8266 | brain + Wi-Fi + dashboard | $3–5 |
| JSN-SR04T | waterproof level sensing | $3–4 |
| Relay module | pump control | $1–2 |
| 5 V PSU | power | $2–3 |
Tune the thresholds tonight — the tank can wait for the weekend.