RisalDash RisalDash

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.

ProjectWaterJSN-SR04TPredictionESP32
ESP32 water tank project: level, time-to-empty prediction, leak detection and auto pump

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:

Ultrasonic sensor wired to an ESP32 with a voltage divider on ECHO
Same wiring for HC-SR04 and JSN-SR04T. Built in BoardLab.

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

PartRole~Cost
ESP32 / ESP8266brain + Wi-Fi + dashboard$3–5
JSN-SR04Twaterproof level sensing$3–4
Relay modulepump control$1–2
5 V PSUpower$2–3

Tune the thresholds tonight — the tank can wait for the weekend.