Siddhant Kumar
Project 047 Β· Environment

Rainwater Harvesting Monitor.

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.

Intermediate 10–16 hours 34 min read WaterSensorsApp
Jump to source Bill of materials
Rainwater Harvesting Monitor β€” reference build illustration MCU VCC Β· GND Β· SIG Β· NC
Difficulty
Intermediate
Build time
10–16 hours
Indicative cost
β‚Ή4,500 – β‚Ή6,500
Platform
ESP32 DevKit V1 (ESP-WROOM-32)
Category
Environment
Last updated
28 July 2026
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.

An HC-SR04 ultrasonic distance sensor with its two transducers
A non-contact ultrasonic sensor reads the tank level to track stored rainwater volume. Photograph sourced from Wikimedia Commons β€” HC-SR04.jpg. Reused under the licence stated on that page; please check it before republishing.

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

SettingHow it is used
Domestic rainwater harvestingHomes using stored rainwater for gardens, toilets and washing, automating diversion and mains changeover for maximum free-water use.
Institutional / campus systemsSchools, offices and apartments managing larger tanks with quality logging and savings reporting.
Agricultural and horticultural storageFarms and nurseries tracking irrigation-water reserves and quality across a dry season.
Water-scarce and off-grid settingsMaximising 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

AttributeValue
Difficulty levelIntermediate
Estimated completion time10–16 hours
Indicative build costβ‚Ή4,500 – β‚Ή6,500
Primary disciplineEnvironment
Reference platformESP32 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.

ComponentKey specificationQtyApprox. 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 DAC1β‚Ή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Β° beam1β‚Ή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 measurement1β‚Ή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 probe1β‚Ή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 min1β‚Ή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 MPa1β‚Ή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 channel1β‚Ή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 frame1β‚Ή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 protection1β‚Ή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 discharge1β‚Ή450
First-flush diverter + actuated valve
The core water-quality mechanism
Motorised/solenoid valve to divert initial runoff to waste1β‚Ή800
Source changeover valve(s)Valves/pump to select tank vs mains supply1β‚Ή700
Rain/flow inlet sensorDetects runoff starting and measures inflow for first-flush volume1β‚Ή250
Weatherproof enclosureIP65 for the electronics near the tank1β‚Ή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

PartSpecificationSupplyInterfaceReference
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 DAC3.3 V logic / 5 V USBUART, SPI, IΒ²C, IΒ²S, CAN, PWMDatasheet
JSN-SR04T waterproof ultrasonic sensor25–450 cm, Β±1 cm, IP67 sealed transducer, 45Β° beam5 VTrigger/Echo or UARTDatasheet
Turbidity sensor (TSD-10 style)0–3000 NTU, analogue 0–4.5 V, IR transmission measurement5 VAnalogue + digitalDatasheet
Analogue TDS / conductivity probe0–1000 ppm, Β±10 % F.S., 0–2.3 V analogue, waterproof probe3.3–5.5 VAnalogueDatasheet
Analogue pH sensor kit (E-201-C probe + BNC board)pH 0–14, Β±0.1 pH at 25 Β°C, 5–60 Β°C, response < 1 min5 VAnalogue (offset trimmer)Datasheet
YF-S201 hall-effect water flow sensor1–30 L/min, Β±10 %, 450 pulses per litre, Β½β€³ BSP thread, ≀ 1.75 MPa5–18 VOpen-collector pulseDatasheet
4-channel opto-isolated relay board4 Γ— SPDT, 10 A @ 250 VAC, active-low inputs, LED per channel5 V coil4Γ— digitalDatasheet
20 W 12 V polycrystalline solar panelVmp 17.5 V, Imp 1.14 A, Voc 21.6 V, 350 Γ— 290 mm, aluminium frame12 V nominalMC4 / screw terminalsDatasheet
TP4056 Li-ion charger + DW01 protection1 A programmable CC/CV charge to 4.2 V Β±1 %, over-discharge and short protection4.5–5.5 V inmicro-USB / padsDatasheet
18650 Li-ion cell 3400 mAh + holder3.7 V nominal, 4.2 V full, 3400 mAh, ~12.6 Wh, 2 C discharge3.0–4.2 VHolder / spot-welded tabsDatasheet

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.

LoadSupply railTypical current (mA)Notes
ESP32 DevKit V1 (ESP-WROOM-32)3.3 V logic / 5 V USB160Wi-Fi transmit bursts peak near 500 mA β€” size the regulator accordingly.
JSN-SR04T waterproof ultrasonic sensor5 V30The 25 cm blind zone matters β€” mount it above the maximum expected water level.
Turbidity sensor (TSD-10 style)5 V30Optical window fouls quickly β€” plan a wiper or weekly clean.
Analogue TDS / conductivity probe3.3–5.5 V4Conductivity rises about 2 % per Β°C β€” always temperature-compensate the reading.
Analogue pH sensor kit (E-201-C probe + BNC board)5 V8Two-point calibrate with pH 4.00 and pH 6.86 buffers; store the probe wet.
YF-S201 hall-effect water flow sensor5–18 V15Count pulses on a hardware interrupt; the K-factor changes with pipe orientation.
4-channel opto-isolated relay board5 V coil280All four coils energised draw ~280 mA β€” do not power from the MCU 5 V pin.
20 W 12 V polycrystalline solar panel12 V nominal1140Rated watts assume 1000 W/mΒ² β€” plan for 60–70 % of nameplate in real installs.
TP4056 Li-ion charger + DW01 protection4.5–5.5 V in1000Buy 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.json under 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 dialout group: sudo usermod -aG dialout $USER and 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

LibraryWhy it is neededInstall
WiFi (ESP32 core) bundledStation/AP connection management for the ESP32.Bundled with the ESP32 Arduino core
PubSubClient 2.8Lightweight MQTT 3.1.1 client for constrained devices.Library Manager β†’ "PubSubClient" by Nick O'Leary
ArduinoJson 7.xZero-allocation JSON serialisation and parsing.Library Manager β†’ "ArduinoJson" by Benoit Blanchon
Preferences (NVS) bundledWear-levelled key/value storage in ESP32 flash for settings.Bundled with the ESP32 core
NTPClient / configTime bundledWall-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.

Rainwater Harvesting Monitor β€” system block diagramFunctional block diagram of the Rainwater Harvesting Monitor system. MeasureTank levelultrasonicQualityturbidity/TDS/pHInflowflow sensorDecideESP32first-flush + changeoverLogiclevel Γ— qualityActFirst-flush valvedivert dirty runoffChangeovertank ↔ mainsReportDashboardvolume + savingsAlertlow/qualityrightrightnone
Rainwater Harvesting Monitor β€” system block diagram

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.

Rainwater Harvesting Monitor β€” wiring schematicConnection schematic showing which controller pin drives each peripheral. Sensors / InputsControllerActuators / OutputsESP32 DevKit V1(ESP-WROOM-32)3.3 V logic / 5 V USBJSN-SR04TGPIO 26/25Tank level(non-contact)TurbidityGPIO 34 (ADC)Water cloudinessTDS/ECGPIO 35 (ADC)Dissolved solidsFlow sensorGPIO 27Inflow (first-flushvolume)First-flush valveGPIO 14Divert dirty runoffto wasteChangeover valveGPIO 12Tank vs mains selectpH (opt)GPIO 32 (ADC)pH via ampSolar + TP40563V3 regCharged supply
Rainwater Harvesting Monitor β€” wiring schematic
PeripheralPeripheral pinController pinSignal
JSN-SR04TTRIG/ECHOGPIO 26/25Tank level (non-contact)
TurbidityAOUTGPIO 34 (ADC)Water cloudiness
TDS/ECAOUTGPIO 35 (ADC)Dissolved solids
Flow sensorPULSEGPIO 27Inflow (first-flush volume)
First-flush valveINGPIO 14Divert dirty runoff to waste
Changeover valveINGPIO 12Tank vs mains select
pH (opt)AOUTGPIO 32 (ADC)pH via amp
Solar + TP4056OUT3V3 regCharged 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.
An ESP32 development board with the ESP-WROOM-32 module and USB connector
ESP32 module automating the first-flush diverter and the tank/mains changeover. Photograph sourced from Wikimedia Commons β€” ESP32 Espressif ESP-WROOM-32 Dev Board.jpg. Reused under the licence stated on that page; please check it before republishing.

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.

Rainwater Harvesting Monitor β€” architecture stackLayered architecture from hardware to user interface. Hardware layerESP32 DevKit V1 (ESP-WROOM-32) Β· JSN-SR04T waterproof ultrasonic sensor Β·Turbidity sensor (TSD-10 style) Β· Analogue TDS / conductivity probeDriver layerwifi Β· pubsub Β· arduinojson Β· preferencesApplication logicsampling loop Β· filtering Β· thresholds Β· state machineTransport layerWi-Fi + MQTT β†’ home water dashboard Β· TLS Β· retry and backoffPresentation layerdashboard Β· mobile notifications Β· historical charts
Rainwater Harvesting Monitor β€” architecture stack

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

plainStored 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

plainFirst-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

plainSource 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.

Rainwater Harvesting Monitor β€” firmware flowchartControl flow through the main program loop. Runoff detected (inflow)?Divert first-flush volume to wasteIdle / serve demandDivert first-flush volume towasteFirst-flush volume passed?Admit clean water to tankDivert first-flush volume to wasteAdmit clean water to tankIdle / serve demandTank adequate AND qualityOK?Draw from tankSwitch to mainsDraw from tankSwitch to mains
Rainwater Harvesting Monitor β€” firmware flowchart

Assembly Instructions

Build on a breadboard first and only commit to solder once the whole system has run for an hour without a fault.

  1. 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.

  2. 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.

  3. 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.

  1. 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.ino
    float 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.
  2. 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.

cpprainwater-monitor.ino
/* ═══════════════════════════════════════════════════════════════
   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
}
float tankVolumeL()Turns the non-contact ultrasonic distance into a stored volume using the tank geometry, giving the litres figure the changeover logic and dashboard need.
bool qualityOk = (turb < TURB_LIM) && (tds < TDS_LIM)Judges the stored water against fit-for-purpose limits for the intended use, not a potability standard β€” the honest scope of the monitor.
if (!useTank && vol > V_ON_L && qualityOk) setSource(true)Draws from the tank only when there is enough water and its quality is acceptable, maximising use of free rainwater.
if ( useTank && (vol < V_OFF_L || !qualityOk))Falls back to mains when the tank runs low or quality drops, with V_on above V_off giving hysteresis so the source does not flicker.
setSource(false); // default mains until provenOn start-up the system defaults to mains, so a fresh boot or a sensor fault never leaves a tap dry while it works out the tank's state.

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.

  1. Tank volume

    Verify computed volume against known added volumes at a couple of levels; correct the geometry constants.

  2. Quality probes

    Calibrate turbidity against a standard and TDS against a known solution; set fit-for-purpose limits for your uses.

  3. 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

Rainwater Harvesting Monitor β€” network topologyPath taken by telemetry from field node to end user. Edge nodesGatewayCloudClientsRainwater controllerESP32Tank sensorson the nodeWi-Fi 2.4 GHzRouterhome networkMQTT 1883Broker + dashboardvolume, quality, savingsDashboardvolume + sourcePhonelow/quality alerts
Rainwater Harvesting Monitor β€” network topology

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 / endpointDirectionPayload
rainwater/1/statenode β†’ brokervolume, turbidity, TDS, source, quality-ok
rainwater/1/eventnode β†’ brokerfirst-flush, overflow, changeover
rainwater/1/alertnode β†’ brokertank 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.

TestWhat you should see
Add known water volumesReported volume matches; changeover engages above V_on
Simulate rain onset with inflowDiverter sends first-flush to waste, then admits to tank by volume
Raise turbidity above the limitQuality flagged; source switches to mains
Draw the tank below V_offSwitches to mains; low-tank alert; no flicker (hysteresis)
Trigger the manual overrideValves respond manually regardless of controller state
Run a solar day/night cycleBattery 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.

jsonrainwater-state.json
{
  "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.

A Grafana time-series dashboard
A dashboard trends stored volume, water quality and the mains water saved over a season. Photograph sourced from Wikimedia Commons β€” Grafana dashboard.png. Reused under the licence stated on that page; please check it before republishing.

Troubleshooting: Common Errors & Fixes

Volume reading noisy/wrong

Likely cause. Ultrasonic echoes off inlet splash or ripples, or wrong geometry

Fix. Aim clear of splash; median-filter pings; verify tank dimensions

Tank fills with dirty water

Likely cause. First-flush volume too small or diverter not working

Fix. Increase V_FIRSTFLUSH_L for the roof; verify the diverter actuates and the flow sensor counts

Source flickers tank/mains

Likely cause. Insufficient hysteresis or noisy quality reads

Fix. Widen V_on/V_off gap; average quality readings; add a dwell time

Quality probes drift

Likely cause. Fouling or ageing

Fix. Clean probes; recalibrate; they need periodic maintenance

Taps run dry

Likely cause. Changeover or valve fault without override

Fix. Default to mains on fault; ensure the manual override works; alert on valve faults

The sketch will not upload β€” "Failed to connect" or "avrdude: stk500_recv()"

Likely cause. The bootloader is not being reached: wrong port, wrong board, a serial monitor holding the port open, or a USB cable that only carries power.

Fix. Close every serial monitor, confirm Tools β†’ Board and Port, and swap to a known data-capable USB cable. On an ESP32 hold BOOT while the IDE prints "Connecting…", then release. If a peripheral is wired to the UART pins (GPIO 1/3 on ESP32, D0/D1 on Uno) unplug it β€” it fights the programmer.

The board resets in a loop, or the serial monitor prints "Brownout detector was triggered"

Likely cause. The supply cannot deliver peak current. Wi-Fi transmit bursts, relay coils and servos all pull far more than their average draw.

Fix. Power peripherals from a separate regulated supply with a common ground rather than from the board 5 V pin. Add a 470–1000 Β΅F electrolytic capacitor across the supply near the load, and use a real power adapter rather than a laptop USB port.

Serial monitor shows garbage characters

Likely cause. Baud rate mismatch between Serial.begin() and the monitor, or a floating/shared UART line.

Fix. Set the monitor to 115200 to match the sketch. If it still garbles, the crystal or the USB bridge is being confused by noise β€” shorten the cable and keep motor wiring away from the USB lead.

Wi-Fi connects but MQTT never does (state -2)

Likely cause. Wrong broker address or port, a firewall in the way, or the broker requiring credentials the sketch is not sending.

Fix. Test from a laptop on the same network first: mosquitto_sub -h <broker> -t "#" -v. If that works, the problem is on the device β€” check the IP literal, port 1883 (or 8883 for TLS), and that client.setServer() runs before connect(). PubSubClient state codes are documented in its header.

Readings arrive for a while and then stop

Likely cause. The Wi-Fi or MQTT session dropped and the sketch never reconnects, or the broker dropped the client on keep-alive timeout.

Fix. Never assume the link stays up. Check WiFi.status() and client.connected() at the top of every loop and reconnect with exponential backoff. Add a watchdog so a wedged network stack reboots the device instead of going silent.

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 a millis() 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_t where 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

What is the first flush and why automate it?

The initial runoff washes the roof's accumulated dirt into the downpipe. Diverting that first slug to waste keeps the tank clean. Automating it means the right volume is diverted every storm, not just when someone remembers.

Does it make the rainwater safe to drink?

No. It monitors and manages quality and switches sources, but making rainwater potable needs proper filtration and disinfection. Treat its readings as fit-for-purpose guidance, not a safety certification.

How does it decide tank versus mains?

It draws from the tank when the level is adequate and the quality is acceptable for the use, and falls back to mains when either fails β€” with hysteresis so it does not flicker between sources.

What if a valve or sensor fails?

It defaults to mains and there is a manual override, so a fault never leaves you without water. Faults are also alerted.

How much water will it actually save?

As much as your catchment supplies and your uses draw β€” the monitor maximises it by using tank water whenever it is adequate and clean, and logs the running total so you can see the saving.

References & Learning Resources

These are the primary sources worth reading in full. Manufacturer datasheets always outrank forum posts when the two disagree.

  1. Rainwater harvesting β€” overviewReference
  2. First-flush diverters and water qualityReference
  3. Turbidity and TDS in water qualityReference
  4. Backflow prevention and cross-connection controlReference
  5. Domestic rainwater system design (guidance)Reference