Contents β 26 sections
Project Overview
Tracks how much rainwater a system has stored and whether it is clean enough to use, automating the first-flush diversion and telling you when to draw from the tank versus the mains.
Rainwater harvesting is wonderfully simple in principle β catch roof runoff, store it, use it β and quietly fiddly in practice. How full is the tank, really? Is the water in it clean enough for what you want to use it for, or has it gone stagnant? Did the "first flush" of dirty water that washes the roof's accumulated dust, leaves and bird droppings actually get diverted away before the clean water started filling the tank? And should the garden or the toilets be drawing from the tank right now, or has it run low enough that they should switch to mains? This monitor answers all of those continuously, turning a passive tank into a managed water resource.
It measures tank level non-contact with an ultrasonic sensor so you always know your stored volume and can see it rise with each rain and fall with each use. It watches basic water quality β turbidity for cloudiness, TDS/EC for dissolved solids, and optionally pH β so you know whether the stored water is fit for irrigation, washing or (with proper treatment) more. It automates the first-flush diverter, ensuring the initial dirty runoff is sent to waste and only the cleaner water that follows is admitted to the tank, which is the single biggest determinant of stored-water quality. And it manages the changeover between tank and mains, drawing from the tank while it is adequate and quality is acceptable, and switching to mains when it is not β maximising the free rainwater used without ever leaving a tap dry.
Everything is logged so you can see how much water the system has saved, how quality varies through a storage season, and whether the catchment is performing. It reports to a phone or dashboard, can run on solar where there is no convenient power, and alerts on the things that matter: tank nearly empty, quality degraded, first-flush or overflow events, or a sensor fault. It is honest that it monitors and manages rather than treats β it will tell you the water is turbid and switch you to mains, but making rainwater potable needs proper filtration and disinfection beyond its scope β yet as the brain of a harvesting system it captures far more usable water, more safely, than a manual tank ever will.
What this project does
- Measures tank level non-contact and reports stored volume and its trend
- Monitors turbidity, TDS/EC and optionally pH of the stored water
- Automates the first-flush diverter to keep dirty initial runoff out of the tank
- Manages changeover between tank and mains by level and quality
- Logs water saved, quality over the season and catchment performance
- Alerts on low tank, poor quality, first-flush/overflow and sensor faults
- Runs on mains or solar and reports to a phone/dashboard
Real-World Applications
| Setting | How it is used |
|---|---|
| Domestic rainwater harvesting | Homes using stored rainwater for gardens, toilets and washing, automating diversion and mains changeover for maximum free-water use. |
| Institutional / campus systems | Schools, offices and apartments managing larger tanks with quality logging and savings reporting. |
| Agricultural and horticultural storage | Farms and nurseries tracking irrigation-water reserves and quality across a dry season. |
| Water-scarce and off-grid settings | Maximising every litre of captured rain where mains is limited, unreliable or expensive. |
Deployment contexts where a build of this kind earns its keep.
Features & Capabilities
- Stored-volume tracking with rain-response and usage visibility
- Basic water-quality monitoring for fit-for-purpose decisions
- Automated first-flush diversion β the key to stored-water quality
- Smart tank/mains changeover to maximise free-water use
- Season-long logging of savings and quality
- Solar-capable, phone-connected operation
- Honest scope: manages and monitors, does not make water potable
Difficulty, Time & Required Skills
| Attribute | Value |
|---|---|
| Difficulty level | Intermediate |
| Estimated completion time | 10β16 hours |
| Indicative build cost | βΉ4,500 β βΉ6,500 |
| Primary discipline | Environment |
| Reference platform | ESP32 DevKit V1 (ESP-WROOM-32) |
Skills you should have (or will pick up)
- Non-contact tank-level measurement and volume calculation
- Basic water-quality sensing (turbidity, TDS, pH) and interpretation
- Automating a first-flush diverter and valve/pump control
- Changeover logic between sources with hysteresis
- Logging, alerting and solar/phone connectivity
Bill of Materials
Every part below is commonly available from Indian and international hobby-electronics suppliers. Prices are indicative 2026 retail figures in Indian rupees and will drift β treat them as a budgeting guide, not a quotation.
| Component | Key specification | Qty | Approx. cost |
|---|---|---|---|
| ESP32 DevKit V1 (ESP-WROOM-32) Wi-Fi transmit bursts peak near 500 mA β size the regulator accordingly. | Dual-core Xtensa LX6 @ 240 MHz, 520 KB SRAM, 4 MB flash, Wi-Fi 802.11 b/g/n + BLE 4.2, 34 GPIO, 18Γ 12-bit ADC, 2Γ 8-bit DAC | 1 | βΉ450 |
| JSN-SR04T waterproof ultrasonic sensor The 25 cm blind zone matters β mount it above the maximum expected water level. | 25β450 cm, Β±1 cm, IP67 sealed transducer, 45Β° beam | 1 | βΉ450 |
| Turbidity sensor (TSD-10 style) Optical window fouls quickly β plan a wiper or weekly clean. | 0β3000 NTU, analogue 0β4.5 V, IR transmission measurement | 1 | βΉ900 |
| Analogue TDS / conductivity probe Conductivity rises about 2 % per Β°C β always temperature-compensate the reading. | 0β1000 ppm, Β±10 % F.S., 0β2.3 V analogue, waterproof probe | 1 | βΉ780 |
| Analogue pH sensor kit (E-201-C probe + BNC board) Two-point calibrate with pH 4.00 and pH 6.86 buffers; store the probe wet. | pH 0β14, Β±0.1 pH at 25 Β°C, 5β60 Β°C, response < 1 min | 1 | βΉ2,400 |
| YF-S201 hall-effect water flow sensor Count pulses on a hardware interrupt; the K-factor changes with pipe orientation. | 1β30 L/min, Β±10 %, 450 pulses per litre, Β½β³ BSP thread, β€ 1.75 MPa | 1 | βΉ350 |
| 4-channel opto-isolated relay board All four coils energised draw ~280 mA β do not power from the MCU 5 V pin. | 4 Γ SPDT, 10 A @ 250 VAC, active-low inputs, LED per channel | 1 | βΉ280 |
| 20 W 12 V polycrystalline solar panel Rated watts assume 1000 W/mΒ² β plan for 60β70 % of nameplate in real installs. | Vmp 17.5 V, Imp 1.14 A, Voc 21.6 V, 350 Γ 290 mm, aluminium frame | 1 | βΉ1,200 |
| TP4056 Li-ion charger + DW01 protection Buy the version *with* protection ICs β the bare charger will over-discharge your cell. | 1 A programmable CC/CV charge to 4.2 V Β±1 %, over-discharge and short protection | 1 | βΉ45 |
| 18650 Li-ion cell 3400 mAh + holder Never charge below 0 Β°C; always use a protected cell or a BMS. | 3.7 V nominal, 4.2 V full, 3400 mAh, ~12.6 Wh, 2 C discharge | 1 | βΉ450 |
| First-flush diverter + actuated valve The core water-quality mechanism | Motorised/solenoid valve to divert initial runoff to waste | 1 | βΉ800 |
| Source changeover valve(s) | Valves/pump to select tank vs mains supply | 1 | βΉ700 |
| Rain/flow inlet sensor | Detects runoff starting and measures inflow for first-flush volume | 1 | βΉ250 |
| Weatherproof enclosure | IP65 for the electronics near the tank | 1 | βΉ400 |
Estimated total: βΉ9,455, excluding tools, shipping and consumables.
Tools and consumables
- Soldering iron (temperature controlled, 350 Β°C) with 0.8 mm 60/40 or lead-free solder
- Digital multimeter β continuity, DC volts and current ranges
- Wire strippers, flush cutters and a small set of precision screwdrivers
- Heat-shrink tubing and a heat gun (or a lighter, carefully)
- A laptop with a USB port and the toolchain listed above
Hardware Specifications
| Part | Specification | Supply | Interface | Reference |
|---|---|---|---|---|
| ESP32 DevKit V1 (ESP-WROOM-32) | Dual-core Xtensa LX6 @ 240 MHz, 520 KB SRAM, 4 MB flash, Wi-Fi 802.11 b/g/n + BLE 4.2, 34 GPIO, 18Γ 12-bit ADC, 2Γ 8-bit DAC | 3.3 V logic / 5 V USB | UART, SPI, IΒ²C, IΒ²S, CAN, PWM | Datasheet |
| JSN-SR04T waterproof ultrasonic sensor | 25β450 cm, Β±1 cm, IP67 sealed transducer, 45Β° beam | 5 V | Trigger/Echo or UART | Datasheet |
| Turbidity sensor (TSD-10 style) | 0β3000 NTU, analogue 0β4.5 V, IR transmission measurement | 5 V | Analogue + digital | Datasheet |
| Analogue TDS / conductivity probe | 0β1000 ppm, Β±10 % F.S., 0β2.3 V analogue, waterproof probe | 3.3β5.5 V | Analogue | Datasheet |
| Analogue pH sensor kit (E-201-C probe + BNC board) | pH 0β14, Β±0.1 pH at 25 Β°C, 5β60 Β°C, response < 1 min | 5 V | Analogue (offset trimmer) | Datasheet |
| YF-S201 hall-effect water flow sensor | 1β30 L/min, Β±10 %, 450 pulses per litre, Β½β³ BSP thread, β€ 1.75 MPa | 5β18 V | Open-collector pulse | Datasheet |
| 4-channel opto-isolated relay board | 4 Γ SPDT, 10 A @ 250 VAC, active-low inputs, LED per channel | 5 V coil | 4Γ digital | Datasheet |
| 20 W 12 V polycrystalline solar panel | Vmp 17.5 V, Imp 1.14 A, Voc 21.6 V, 350 Γ 290 mm, aluminium frame | 12 V nominal | MC4 / screw terminals | Datasheet |
| TP4056 Li-ion charger + DW01 protection | 1 A programmable CC/CV charge to 4.2 V Β±1 %, over-discharge and short protection | 4.5β5.5 V in | micro-USB / pads | Datasheet |
| 18650 Li-ion cell 3400 mAh + holder | 3.7 V nominal, 4.2 V full, 3400 mAh, ~12.6 Wh, 2 C discharge | 3.0β4.2 V | Holder / spot-welded tabs | Datasheet |
Consolidated electrical and interface specifications for every active part in the build.
Power Budget & Supply Sizing
Add up the typical active current of every part, then size the supply with at least 50 % headroom so transmit bursts and motor inrush never brown out the controller.
| Load | Supply rail | Typical current (mA) | Notes |
|---|---|---|---|
| ESP32 DevKit V1 (ESP-WROOM-32) | 3.3 V logic / 5 V USB | 160 | Wi-Fi transmit bursts peak near 500 mA β size the regulator accordingly. |
| JSN-SR04T waterproof ultrasonic sensor | 5 V | 30 | The 25 cm blind zone matters β mount it above the maximum expected water level. |
| Turbidity sensor (TSD-10 style) | 5 V | 30 | Optical window fouls quickly β plan a wiper or weekly clean. |
| Analogue TDS / conductivity probe | 3.3β5.5 V | 4 | Conductivity rises about 2 % per Β°C β always temperature-compensate the reading. |
| Analogue pH sensor kit (E-201-C probe + BNC board) | 5 V | 8 | Two-point calibrate with pH 4.00 and pH 6.86 buffers; store the probe wet. |
| YF-S201 hall-effect water flow sensor | 5β18 V | 15 | Count pulses on a hardware interrupt; the K-factor changes with pipe orientation. |
| 4-channel opto-isolated relay board | 5 V coil | 280 | All four coils energised draw ~280 mA β do not power from the MCU 5 V pin. |
| 20 W 12 V polycrystalline solar panel | 12 V nominal | 1140 | Rated watts assume 1000 W/mΒ² β plan for 60β70 % of nameplate in real installs. |
| TP4056 Li-ion charger + DW01 protection | 4.5β5.5 V in | 1000 | Buy the version *with* protection ICs β the bare charger will over-discharge your cell. |
Summed typical draw is 2667 mA. With a 1.5Γ design margin the supply should deliver at least 4100 mA continuously at the stated rail voltage.
Software Requirements & Development Environment
Reference toolchain: Arduino IDE 2.3.x with the ESP32 board package 3.x (or PlatformIO on VS Code). Anything newer normally works; anything older may lack the board definitions used here.
- Install the Arduino IDE 2.3.x (or PlatformIO if you prefer a real editor and dependency locking).
- Add
https://espressif.github.io/arduino-esp32/package_esp32_index.jsonunder File β Preferences β Additional Board Manager URLs, then install esp32 from the Boards Manager. - Set the correct port under Tools β Port. On Linux add yourself to the
dialoutgroup:sudo usermod -aG dialout $USERand log out and back in. - Open the Serial Monitor at 115200 baud β every sketch here logs its state there.
- Keep File β Preferences β Show verbose output during: compilation switched on while you are debugging build errors.
Required libraries
| Library | Why it is needed | Install |
|---|---|---|
| WiFi (ESP32 core) bundled | Station/AP connection management for the ESP32. | Bundled with the ESP32 Arduino core |
| PubSubClient 2.8 | Lightweight MQTT 3.1.1 client for constrained devices. | Library Manager β "PubSubClient" by Nick O'Leary |
| ArduinoJson 7.x | Zero-allocation JSON serialisation and parsing. | Library Manager β "ArduinoJson" by Benoit Blanchon |
| Preferences (NVS) bundled | Wear-levelled key/value storage in ESP32 flash for settings. | Bundled with the ESP32 core |
| NTPClient / configTime bundled | Wall-clock time from an NTP server for timestamping. | Bundled (`configTime()` on ESP32) |
Block Diagram
The block diagram shows the functional decomposition of the system β what senses, what decides, what acts, and where the data ends up.
Circuit Diagram & Wiring
Every signal line in the build is shown below, followed by a pin-by-pin connection table you can work through with a multimeter in hand.
| Peripheral | Peripheral pin | Controller pin | Signal |
|---|---|---|---|
| JSN-SR04T | TRIG/ECHO | GPIO 26/25 | Tank level (non-contact) |
| Turbidity | AOUT | GPIO 34 (ADC) | Water cloudiness |
| TDS/EC | AOUT | GPIO 35 (ADC) | Dissolved solids |
| Flow sensor | PULSE | GPIO 27 | Inflow (first-flush volume) |
| First-flush valve | IN | GPIO 14 | Divert dirty runoff to waste |
| Changeover valve | IN | GPIO 12 | Tank vs mains select |
| pH (opt) | AOUT | GPIO 32 (ADC) | pH via amp |
| Solar + TP4056 | OUT | 3V3 reg | Charged supply |
Wire one row at a time and tick it off β most "it does not work" reports trace back to a single swapped pair.
Wiring explanation
- Mount the ultrasonic level sensor at the top of the tank, aimed down at the water, above the maximum (overflow) level so it is never submerged.
- Place the turbidity and TDS probes where they see representative stored water, not stagnant corners or the inlet stream.
- Put the flow/rain sensor at the inlet so the controller knows when runoff starts and can measure the first-flush volume.
- Drive the first-flush and changeover valves via correctly-rated relays with a manual override, so a fault never traps you without water.
- Keep analogue probe grounds quiet and separate from the valve/pump switching current.
System Architecture
Read the stack from the bottom up: physical hardware, the firmware that drives it, the transport that moves data off the device, and the software a human actually looks at.
Working Principle
A rainwater system's usefulness comes down to two questions the monitor answers continuously: how much water do I have, and how good is it. Level answers the first: an ultrasonic sensor looking down at the water gives the stored depth, which the known tank geometry turns into a volume, and watching that volume over time shows the system breathing β jumping up with each rain, ebbing with each use, and revealing whether the catchment is keeping pace with demand. Because the sensor is non-contact and mounted above the water, nothing sits in the tank to foul or fail, and the reading survives the algae and sediment that would defeat a float.
Quality answers the second question, and its most important determinant is the first flush. When rain starts, the initial runoff sweeps the roof's accumulated dust, leaves, droppings and grime into the downpipe β a concentrated slug of contamination. If that slug enters the tank it fouls the whole store; if it is diverted to waste and only the cleaner water that follows is admitted, the stored water is far better. Automating this is exactly what the monitor does: it detects runoff beginning (via the inlet flow sensor), diverts a set first-flush volume to waste, and only then opens the path to the tank. Sizing that volume to the roof area (a few litres per square metre of catchment is a common guide) is the single most effective water-quality intervention in the whole system, and doing it automatically means it happens every time, not just when someone remembers.
The in-tank quality sensors β turbidity for cloudiness, TDS/EC for dissolved solids, optionally pH β then tell you whether the stored water is fit for its intended use. This is deliberately framed as fit-for-purpose, not potability: turbid water is fine for flushing toilets, questionable for washing, and unsuitable for drinking without proper treatment, and the monitor's job is to report the quality and let the changeover logic act on it, not to certify the water safe. Rising turbidity over a stagnant month, for instance, is a signal to use the water down and let fresh rain refresh it, or to service the filtration.
Those two answers combine in the changeover decision, which is where the system pays for itself. The controller draws from the tank whenever the level is adequate and the quality is acceptable for the use, falling back to mains only when the tank is too low or the water too poor β with hysteresis so it does not flicker between sources around a threshold. This maximises the free rainwater actually used (the whole economic point) while guaranteeing supply never fails. Everything is logged, so over a season you can see litres saved, how quality tracked rainfall and stagnation, and whether the first-flush and catchment sizing are right β turning a set-and-forget tank into a managed resource you can actually optimise. It manages and monitors within honest limits: it will divert, switch and alert intelligently, but making rainwater potable remains a job for proper filtration and disinfection beyond the sensor's reach.
The maths behind it
Stored volume from level
Ultrasonic distance d to the water, sensor at height H:
water_depth = H β d
volume = water_depth Γ tank_cross_section
(or integrate the tank's area profile if non-uniform)
Start draw-from-tank above V_low; alert below V_min.
First-flush volume
Divert the initial runoff that washes the roof:
V_firstflush β f Β· A_roof (f ~ 0.5β2 L per mΒ²)
Measure inflow with the flow sensor and divert until the
cumulative inflow since runoff-start exceeds V_firstflush,
then admit to the tank. Sized to roof area and dirtiness.
Source changeover with hysteresis
use_tank if volume > V_on AND quality_ok
switch_mains if volume < V_off OR NOT quality_ok
V_on > V_off gives hysteresis (no flicker at the threshold).
quality_ok = turbidity < T_lim AND TDS < D_lim (and pH band)
for the intended use (irrigation vs washing differ).
Program Flowchart
The firmware is a single cooperative loop. Nothing blocks for long, so networking, sensing and the user interface all stay responsive.
Assembly Instructions
Build on a breadboard first and only commit to solder once the whole system has run for an hour without a fault.
Fit level and quality sensing
Mount the ultrasonic sensor above the tank's maximum level, aimed down. Place the turbidity and TDS (and optional pH) probes in representative stored water, accessible for cleaning.
Record the tank geometry so level converts to volume correctly.
Install the first-flush and changeover valves
Fit the actuated first-flush diverter at the downpipe with the inlet flow sensor upstream of it, and the source-changeover valve(s)/pump between tank, mains and the point of use.
Wire both valves through correctly-rated relays with a manual override.
Set up power, control and reporting
Power from mains or solar, house the electronics in an IP65 box near the tank, and configure Wi-Fi/MQTT reporting to a phone/dashboard.
Step-by-Step Implementation Guide
Work through these in order. Each step ends in something you can observe, so a failure is always localised to the step you just finished.
Automate the first flush
On detecting runoff (inflow starting), divert to waste and integrate the inflow until the sized first-flush volume has passed, then admit water to the tank.
cppfirst-flush.inofloat V_FIRSTFLUSH_L; // sized to roof area (f * A_roof) volatile uint32_t flowPulses = 0; float pulsesPerLitre = 450.0f; // flow-sensor constant void IRAM_ATTR onFlow() { flowPulses++; } enum Inlet { IDLE, FLUSHING, ADMIT }; Inlet inlet = IDLE; uint32_t flushStartPulses = 0; void inletTick(bool runoff) { switch (inlet) { case IDLE: if (runoff) { // rain just started flushStartPulses = flowPulses; setDiverter(true); // send dirty first-flush to waste inlet = FLUSHING; } break; case FLUSHING: { float litres = (flowPulses - flushStartPulses) / pulsesPerLitre; if (litres >= V_FIRSTFLUSH_L) { setDiverter(false); // clean water now setTankInlet(true); // admit to tank inlet = ADMIT; } break; } case ADMIT: if (!runoff) { // rain stopped setTankInlet(false); inlet = IDLE; } break; } }if (runoff) { // rain just startedWhen the inlet flow sensor sees runoff begin, the diverter opens to waste β the dirty first flush is sent away before any of it can reach the tank.float litres = (flowPulses - flushStartPulses) / pulsesPerLitreThe first flush is measured by volume, not time, by counting flow-sensor pulses since runoff started β so the right amount is diverted regardless of rain intensity.if (litres >= V_FIRSTFLUSH_L)Once the sized first-flush volume has passed, the diverter closes and the tank inlet opens, admitting the cleaner water that follows.case ADMIT:Clean water fills the tank until the rain stops, when the inlet closes and the system resets for the next storm.Decide source by level and quality
Convert level to volume, evaluate quality against the intended use, and select tank or mains with hysteresis; log the reading and any events, and alert on low level, poor quality or faults.
Complete Source Code
The listing below is complete and compiles as written β there are no elided sections. Read the annotations under each block before you upload it.
/* βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Rainwater Harvesting Monitor β ESP32
Tracks tank volume and water quality, automates the first-flush
diverter, and manages tank/mains changeover by level and quality.
Logs savings and quality; reports to a dashboard. Manages and
monitors β it does not make rainwater potable.
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */
#include <WiFi.h>
#include <PubSubClient.h>
#include <Preferences.h>
#define PIN_TRIG 26
#define PIN_ECHO 25
#define PIN_TURB 34
#define PIN_TDS 35
#define PIN_FLOW 27
#define PIN_DIVERT 14 // first-flush diverter valve
#define PIN_SOURCE 12 // tank(HIGH) / mains(LOW) changeover
#define TANK_H_CM 200.0f
#define TANK_AREA_M2 2.0f
#define V_ON_L 300.0f // draw from tank above this
#define V_OFF_L 120.0f // switch to mains below this (hysteresis)
#define TURB_LIM 100.0f // NTU limit for the intended use
#define TDS_LIM 600.0f // ppm limit
Preferences prefs;
WiFiClient net; PubSubClient mqtt(net);
float V_FIRSTFLUSH_L, TURB_CAL, TDS_CAL, pulsesPerLitre=450.0f;
volatile uint32_t flowPulses=0;
bool useTank=false;
void IRAM_ATTR onFlow(){ flowPulses++; }
float tankVolumeL() {
digitalWrite(PIN_TRIG,LOW); delayMicroseconds(2);
digitalWrite(PIN_TRIG,HIGH); delayMicroseconds(10); digitalWrite(PIN_TRIG,LOW);
long us=pulseIn(PIN_ECHO,HIGH,30000);
if(!us) return -1;
float dist=us/58.0f; // cm
float depth=constrain(TANK_H_CM-dist,0.0f,TANK_H_CM);
return depth/100.0f * TANK_AREA_M2 * 1000.0f; // litres
}
float readTurb(){ long s=0; for(int i=0;i<64;i++) s+=analogRead(PIN_TURB);
return (s/64.0f)/4095.0f*TURB_CAL; }
float readTDS(){ long s=0; for(int i=0;i<64;i++) s+=analogRead(PIN_TDS);
return (s/64.0f)/4095.0f*TDS_CAL; }
void setSource(bool tank){
useTank=tank; digitalWrite(PIN_SOURCE, tank?HIGH:LOW);
}
void report(float vol,float turb,float tds,bool tank,bool qOk){
char b[200];
snprintf(b,sizeof b,
"{\"vol_l\":%.0f,\"turb\":%.0f,\"tds\":%.0f,\"source\":\"%s\","
"\"quality_ok\":%d}", vol,turb,tds, tank?"tank":"mains", qOk?1:0);
mqtt.publish("rainwater/1/state", b);
}
void setup(){
Serial.begin(115200);
pinMode(PIN_TRIG,OUTPUT); pinMode(PIN_ECHO,INPUT);
pinMode(PIN_DIVERT,OUTPUT); pinMode(PIN_SOURCE,OUTPUT);
pinMode(PIN_FLOW,INPUT_PULLUP);
attachInterrupt(PIN_FLOW,onFlow,FALLING);
analogSetPinAttenuation(PIN_TURB,ADC_11db);
analogSetPinAttenuation(PIN_TDS,ADC_11db);
prefs.begin("rain",true);
V_FIRSTFLUSH_L=prefs.getFloat("ff",20.0f);
TURB_CAL=prefs.getFloat("turb",1000.0f);
TDS_CAL=prefs.getFloat("tds",1000.0f);
prefs.end();
WiFi.begin(WIFI_SSID,WIFI_PASS); mqtt.setServer(MQTT_HOST,1883);
setSource(false); // default mains until proven
}
void loop(){
if(!mqtt.connected()&&WiFi.status()==WL_CONNECTED) mqtt.connect("rain-1");
mqtt.loop();
float vol=tankVolumeL();
float turb=readTurb();
float tds=readTDS();
bool qualityOk = (turb < TURB_LIM) && (tds < TDS_LIM);
// changeover with hysteresis
if (!useTank && vol > V_ON_L && qualityOk) setSource(true);
if ( useTank && (vol < V_OFF_L || !qualityOk)) setSource(false);
if (vol >= 0 && vol < V_OFF_L)
mqtt.publish("rainwater/1/alert","tank low");
if (!qualityOk)
mqtt.publish("rainwater/1/alert","quality below limit");
report(vol,turb,tds,useTank,qualityOk);
delay(5000); // 0.2 Hz management loop
}
Configuration & Calibration
Configuration steps
- Set the tank geometry (height, cross-section) so volume is accurate; set V_on/V_off and the quality limits for each intended use.
- Size V_FIRSTFLUSH_L to your roof area and typical dirtiness (a few litres per mΒ²).
- Calibrate the turbidity and TDS scales and the flow-sensor pulses-per-litre.
- Configure reporting, alerts, and the manual valve override behaviour.
Calibration procedure
An uncalibrated sensor produces confident, precise, wrong numbers. Do this once per physical unit and record the constants.
Tank volume
Verify computed volume against known added volumes at a couple of levels; correct the geometry constants.
Quality probes
Calibrate turbidity against a standard and TDS against a known solution; set fit-for-purpose limits for your uses.
Flow and first-flush
Calibrate the flow sensor's pulses-per-litre and confirm the diverter passes exactly the sized first-flush volume before admitting to the tank.
Network Architecture & Connectivity
Communication protocol
The controller publishes volume, quality and the active source on a slow cadence, with immediate publishes on changeover, first-flush/overflow events and alerts. Control is local; the dashboard is for visibility and history.
| Topic / endpoint | Direction | Payload |
|---|---|---|
rainwater/1/state | node β broker | volume, turbidity, TDS, source, quality-ok |
rainwater/1/event | node β broker | first-flush, overflow, changeover |
rainwater/1/alert | node β broker | tank low, quality below limit, sensor fault |
Message contract between the device and the broker.
Cloud platform configuration
A broker feeds a dashboard that trends stored volume against rainfall and use, logs quality across the season, and totals the mains water saved β the number that justifies the system.
Dashboard setup
Volume history with rain/use overlay, live quality gauges, active-source indicator, cumulative savings, and event markers for first-flush and overflow.
Mobile app integration
Alerts for a low tank, quality dropping below the use limit, overflow, or a sensor/valve fault.
Security considerations
- Authenticate any remote valve/source commands; keep the manual override and safe-default-to-mains independent of the network.
- Protect the dashboard so household water data stays private.
- Alert on controller silence so a failed node is noticed before a tap runs dry.
Testing Procedure & Expected Output
Test from the bottom up. Confirm power, then each sensor in isolation, then the integrated loop β the first failing step tells you exactly where to look.
| Test | What you should see |
|---|---|
| Add known water volumes | Reported volume matches; changeover engages above V_on |
| Simulate rain onset with inflow | Diverter sends first-flush to waste, then admits to tank by volume |
| Raise turbidity above the limit | Quality flagged; source switches to mains |
| Draw the tank below V_off | Switches to mains; low-tank alert; no flicker (hysteresis) |
| Trigger the manual override | Valves respond manually regardless of controller state |
| Run a solar day/night cycle | Battery covers the management loop and any valve actuation |
Bench-test checklist. If a row fails, stop and fix it before moving on.
Expected output
The dashboard shows tank volume with rain/use history, current turbidity/TDS/pH, the active source (tank/mains), cumulative water saved, and any alerts or first-flush/overflow events.
{
"vol_l": 410,
"turb": 34,
"tds": 210,
"source": "tank",
"quality_ok": 1
}
Here the tank holds 410 L of acceptable-quality water (turbidity 34 NTU, TDS 210 ppm), so the system is drawing from the tank rather than mains β free water used, supply assured.
Troubleshooting: Common Errors & Fixes
Performance Optimisation
- A slow management loop (seconds) suffices β tank and quality change gradually; reserve fast response for the first-flush flow counting.
- Median-filter ultrasonic pings and average quality ADC samples for stable readings.
- On solar, the valves are the main momentary draw; size the battery for actuation plus standby.
- Log locally/aggregate to keep network traffic light while preserving season-long history.
- Replace every
delay()with amillis()comparison β blocking delays are the single most common cause of dropped readings. - Sample sensors on a fixed cadence and publish on a slower one; you almost never need to transmit at the sampling rate.
- Move networking into its own FreeRTOS task so a slow DNS lookup cannot stall the control loop.
- Use
uint8_t/uint16_twhere the range allows; on an 8-bit AVR a 32-bit add costs four times as much. - Batch several samples into one MQTT publish. Radio time, not CPU time, dominates the energy budget.
- Set the MQTT keep-alive to a value that matches your reporting interval so the broker does not churn reconnections.
- For battery builds use deep sleep between samples: an ESP32 drops from ~160 mA awake to about 10 Β΅A asleep, which is the difference between days and months of runtime.
Safety Precautions
- This monitors and manages water; it does not make rainwater potable β treat quality readings as fit-for-purpose guidance, and use proper filtration/disinfection for drinking water.
- Provide a manual override and default to mains on any fault so supply is never lost.
- Keep any cross-connection between rainwater and mains compliant with local plumbing codes (backflow prevention) to protect the mains supply.
- Use correctly-rated valves/relays and protect the electronics from the wet tank environment.
- Mains voltage kills. Anything on the load side of the relay is at 230 V. Do not work on a powered circuit, and never leave exposed mains wiring on a bench where someone could touch it.
- Keep at least 6 mm of creepage between the mains and low-voltage sides of any board you make, and never route mains tracks under the microcontroller.
- Have a qualified electrician do the final installation into a consumer unit or wall fitting. In most jurisdictions this is a legal requirement, not a suggestion.
- Fit an RCD/RCBO upstream and fuse the load appropriately for its rating.
- Lithium cells vent and burn when abused. Only use protected cells or a proper BMS, never charge below 0 Β°C, and never leave a charging pack unattended on a wooden desk.
- Water and electronics: mount all boards above the maximum possible water line, use drip loops on every cable, and pressure-test plumbing before wiring anything up.
- Wear eye protection when soldering or cutting, and solder in a ventilated space β rosin flux fumes are a respiratory irritant.
- Power the circuit through a bench supply with a current limit while you are testing. A 300 mA limit turns a wiring mistake into a beep instead of a dead board.
- Disconnect power before changing any wiring. Hot-plugging a sensor onto a live bus is the fastest way to lose a controller.
Maintenance
- Clean turbidity/TDS probes and recalibrate periodically; fouling biases quality.
- Check the first-flush diverter actuates and the flow sensor counts before the wet season.
- Inspect the tank and overflow, and verify the level sensor's line of sight is clear.
- Test the manual override and mains-default behaviour regularly.
- Re-check every screw terminal and header after the first week β thermal cycling loosens connections that felt tight on day one.
- Clean the sensing element on a schedule. Optical and electrochemical sensors foul, and a fouled sensor reports plausible nonsense rather than failing outright.
- Log pack voltage. When resting voltage after a full charge drops below about 4.0 V, the cell is near end of life β replace it.
- Wash the panel every few weeks in dusty conditions; a visible dust film costs 15β25 % of the harvest.
- Keep the broker and dashboard containers patched, and rotate device credentials at least once a year.
- Recalibrate at the interval given in the calibration section, and keep the constants in a text file next to the firmware β not only in flash.
- Keep a short logbook of firmware versions and what changed. Six months later you will not remember why that constant is 1.083.
Future Improvements & Upgrades
A working v1 is a platform, not a finish line. These are the upgrades that add the most capability for the least rework.
- Add inline filtration/UV with monitoring to extend usable quality toward potable (with proper validation).
- Forecast supply from a weather feed to pre-empt low-tank changeovers.
- Add per-outlet metering to attribute savings by use (garden vs toilets).
- Integrate with home automation for demand-aware use of stored water.
- Design a proper PCB. Once the breadboard version has run for a month, moving to a two-layer board removes the intermittent-contact failures that dominate prototype faults.
- Add over-the-air firmware updates so you never have to physically reach a deployed node again.
- Add persistent local storage (microSD or the on-chip flash) so a network outage does not create a hole in your data.
- Move configuration out of the source: a captive-portal setup page or a JSON config file makes the build reusable without a recompile.
- Add a battery and solar option so the unit survives a power cut and can be sited away from a socket.
- Write a small test harness that feeds synthetic sensor values through the decision logic, so you can validate thresholds without physically triggering the event.
Frequently Asked Questions
References & Learning Resources
These are the primary sources worth reading in full. Manufacturer datasheets always outrank forum posts when the two disagree.