Preview build — page text is a draft awaiting human review. Code badges come from real compiles. The flasher is real but not yet hardware-tested; the Playground is still a preview.
Water Tank Alarm: the finished build
ESP32India utility230 V

Water tank level alarm with pump cut-off (ESP32)

Know when your tank is full or empty — and cut the pump automatically.

Difficulty
Medium
Build time
2 h
Parts cost
₹1,285 · $15
Verified — core esp32 3.3.12, LiquidCrystal I2C 1.1.2, 3 Nov 2026Compile-only

Community draft — report a mistake

Mains voltage (230 V) — can kill. The low-voltage side (board, sensors, display, relay module) is safe to build yourself. The mains side must be wired by a licensed electrician, and tested with the circuit switched off at the MCB.

  • Never switch a motor with a relay module. Motors pull several times their running current at start-up; hobby relay contacts weld or overheat. The relay only drives a contactor’s coil (or your pump starter’s coil input).
  • Contactor rated for motors (AC-3) at or above the load’s nameplate current.
  • Protect and earth: MCB on live, RCCB/ELCB on the circuit, metal bodies earthed. Switch live only — never neutral or earth.
  • Keep the two worlds apart: mains and low-voltage wiring in separate compartments of a closed, non-metallic enclosure; no bare 230 V terminals.
  • Fail safe: the code switches the load off when a sensor stops answering. Still fit a manual override at the starter.

Parts list

PartQtyApprox.Buy
ESP32 DevKit V1 (30-pin)1₹380Buy ESP32 DevKit V1 (30-pin)
HC-SR04 ultrasonic sensor (or JSN-SR04T waterproof)1₹90Buy HC-SR04 ultrasonic sensor (or JSN-SR04T waterproof)
16×2 LCD with I2C backpack (PCF8574, 0x27)1₹180Buy 16×2 LCD with I2C backpack (PCF8574, 0x27)
4-channel I2C level shifter (BSS138)1₹40Buy 4-channel I2C level shifter (BSS138)
1-channel relay module, 5 V coil, 3.3 V-trigger (active-LOW)1₹70Buy 1-channel relay module, 5 V coil, 3.3 V-trigger (active-LOW)
Active buzzer module, high-level trigger1₹25Buy Active buzzer module, high-level trigger
Resistors 1 kΩ + 2 kΩ (echo divider)1₹5Buy Resistors 1 kΩ + 2 kΩ (echo divider)
Contactor, 230 V AC coil, AC-3 rated ≥ pump current (e.g. 9 A)1₹450Buy Contactor, 230 V AC coil, AC-3 rated ≥ pump current (e.g. 9 A)
Jumper wires + 5 V 1 A adapter1₹45Buy Jumper wires + 5 V 1 A adapter
Total₹1,285

Affiliate disclosure: “Buy” links may earn FlashReady a small commission at no extra cost to you. Prices are indicative.

Wiring

Wiring diagram: water tank alarm on ESP32 DevKit V1D18 to HC-SR04 TRIG. HC-SR04 ECHO through a 1 kΩ resistor to D19, with a 2 kΩ resistor from D19 to ground. D21 and D22 through a BSS138 level shifter to the LCD backpack SDA and SCL. D25 to the buzzer module. D26 to the relay module IN. A 5 V adapter feeds VIN and the 5 V parts; the board's 3V3 feeds the shifter's low side and the buzzer. Mains live goes through an MCB to the contactor's L1 and to relay COM. Relay NO switches the contactor coil A1, coil A2 goes to neutral, contactor T1 feeds the pump, and neutral and earth go straight to the pump.⚠ 230 V AC — electrician only1 kΩ2 kΩMCBESP32-WROOM-32USBENVPVND34D35D32D33D25D26D27D14D12D13GNDVIND23D22TX0RX0D21D19D18D5TX2RX2D4D2D15GND3V3ESP32 DevKit V1 (30-pin)Level shifterBSS138 · 3.3 V ⇄ 5 VLVL 62%PUMP OFF16×2 LCD + I2C (0x27)HC-SR04 (5 V)Buzzer moduleRELAYRelay module (active-LOW)5 V 1 AadapterCONTACTOR230 V coilL1A1T1A2PumpLV1LV2LVGNDHV1HV2HVGNDGNDVCCSDASCLVCCTRIGECHOGNDI/OVCCGNDDC+DC−INNCCOMNO−+LNE3V35V5V5V3V35V3V3
  • 5V
  • 3V3
  • GND
  • Flags with the same label are the same connection. Arcs mark crossings that do not connect.
Every connection
FromToWireWhy
ESP32 D18HC-SR04 TRIGPurple3.3 V trigger is enough for the HC-SR04
HC-SR04 ECHO1 kΩ → D19, 2 kΩ → GNDGreenDivider drops the 5 V echo to 3.3 V
ESP32 D21 (SDA)Level shifter LV2 → HV2 → LCD SDABlueBackpack pull-ups sit at 5 V; shifter protects the ESP32
ESP32 D22 (SCL)Level shifter LV1 → HV1 → LCD SCLYellow
ESP32 D25Buzzer module I/OOrangeHigh-level-trigger module (has its own transistor)
ESP32 D26Relay module INTealActive-LOW; module must accept a 3.3 V trigger
5 V adapter +ESP32 VIN, LCD VCC, HC-SR04 VCC, shifter HV, relay DC+Red5 V net
ESP32 3V3Shifter LV, buzzer VCCAmber3.3 V net (from the board’s regulator)
5 V adapter −Every GNDBlackOne common ground
Mains L (via MCB)Contactor L1 and relay COMBrown230 V. MCB sized for the pump (C-curve)
Relay NOContactor coil A1BrownThe relay only switches the coil (tens of mA), never the motor
Contactor A2Mains NBlueCoil return
Contactor T1Pump LBrownContactor rated ≥ pump current, AC-3 (motor duty)
Mains N / EPump N / pump body (earth)Blue / green-yellowNever switch neutral or earth; earth the pump body

Board quirks: Don't move TRIG to GPIO 5 or 15 — both output PWM at boot and the sensor will fire at random. Keep the level shifter. Most LCD backpacks pull SDA/SCL up to their 5 V supply, and ESP32 pins are 3.3 V only.

Code

water-tank-alarm.inoArduino C++Open in PlaygroundOpen in PlaygroundFlash
// Water tank level alarm with pump cut-off — ESP32 DevKit V1
// HC-SR04 (ECHO through a 1k/2k divider), 16x2 I2C LCD via a BSS138 level shifter,
// active buzzer module, 3.3 V-trigger relay module. Wiring: see the diagram above.
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

constexpr uint8_t TRIG_PIN   = 18;
constexpr uint8_t ECHO_PIN   = 19;   // 5 V echo -> divider -> 3.3 V
constexpr uint8_t BUZZER_PIN = 25;   // buzzer module I/O (high = on)
constexpr uint8_t RELAY_PIN  = 26;
constexpr bool RELAY_ACTIVE_LOW = true;  // most blue relay modules

constexpr float EMPTY_CM = 120.0;  // sensor face to tank bottom
constexpr float FULL_CM  = 20.0;   // sensor face to full water line
constexpr int PUMP_ON_BELOW  = 25; // percent
constexpr int PUMP_OFF_ABOVE = 95; // percent

LiquidCrystal_I2C lcd(0x27, 16, 2);
bool pumpOn = false;

float readDistanceCm() {
  digitalWrite(TRIG_PIN, LOW);
  delayMicroseconds(2);
  digitalWrite(TRIG_PIN, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIG_PIN, LOW);
  unsigned long us = pulseIn(ECHO_PIN, HIGH, 30000UL);  // ~5 m max
  if (us == 0) return -1;                               // no echo
  return us * 0.0343f / 2.0f;
}

// Median of 5 readings rejects ripples and stray echoes.
float medianDistanceCm() {
  float r[5];
  for (int i = 0; i < 5; i++) { r[i] = readDistanceCm(); delay(60); }
  for (int i = 1; i < 5; i++) {
    float k = r[i]; int j = i - 1;
    while (j >= 0 && r[j] > k) { r[j + 1] = r[j]; j--; }
    r[j + 1] = k;
  }
  return r[2];
}

void setPump(bool on) {
  pumpOn = on;
  digitalWrite(RELAY_PIN, (on != RELAY_ACTIVE_LOW) ? HIGH : LOW);
}

void setup() {
  Serial.begin(115200);
  pinMode(TRIG_PIN, OUTPUT);
  pinMode(ECHO_PIN, INPUT);
  pinMode(BUZZER_PIN, OUTPUT);
  pinMode(RELAY_PIN, OUTPUT);
  setPump(false);

  Wire.begin(21, 22);  // SDA, SCL
  lcd.init();
  lcd.backlight();
  lcd.print("Tank monitor");
}

void loop() {
  float d = medianDistanceCm();
  lcd.clear();

  if (d < 0) {                    // sensor unplugged or wet
    setPump(false);               // fail safe: never run blind
    digitalWrite(BUZZER_PIN, !digitalRead(BUZZER_PIN));
    lcd.print("Sensor error!");
    Serial.println("ERR no echo");
    delay(500);
    return;
  }

  int level = constrain((int)((EMPTY_CM - d) * 100 / (EMPTY_CM - FULL_CM)), 0, 100);
  if (!pumpOn && level < PUMP_ON_BELOW) setPump(true);
  if (pumpOn && level > PUMP_OFF_ABOVE) setPump(false);
  digitalWrite(BUZZER_PIN, level >= 98 ? HIGH : LOW);  // overflow warning

  lcd.printf("LVL %3d%%", level);
  lcd.setCursor(0, 1);
  lcd.print(pumpOn ? "PUMP ON " : "PUMP OFF");
  Serial.printf("dist=%.1fcm level=%d%% pump=%d\n", d, level, pumpOn);
  delay(500);
}

Compiled for esp32 on esp32 3.3.12 with LiquidCrystal I2C 1.1.2 · 3 Nov 2026

How it works

The HC-SR04 measures how long an ultrasonic ping takes to bounce off the water. Half that time multiplied by the speed of sound (0.0343 cm/µs) is the gap to the surface, which the sketch maps onto your tank’s empty and full marks.

Two thresholds — pump on below 25 %, off above 95 % — stop the relay from chattering when the level sits on a line. If the sensor stops answering, the pump turns off: running a motor blind is how tanks overflow.

The ESP32 never touches mains. Its relay module only switches the coil of a contactor, and the contactor switches the pump.

Troubleshooting

Level jumps around or reads 0%

Mount the sensor at least 20 cm above the full line and away from the inlet pipe. Splashing water causes stray echoes; the median filter handles small ripples only.

LCD lights up but shows blocks or nothing

Turn the contrast trimmer on the I2C backpack. Still blank? Run the I2C scanner — some backpacks answer at 0x3F instead of 0x27.

Relay clicks on at boot

Your module is active-LOW and GPIO26 floats before setup() runs. Keep RELAY_ACTIVE_LOW = true; for a hard guarantee add a 10 kΩ pull-up from IN to the module's VCC.

ESP32 resets when the pump starts

Contactor switching noise is coupling into the 5 V side. Fit an RC snubber across the contactor coil, keep mains and low-voltage wiring apart, and power the ESP32 from its own adapter.

Upgrades

  • Send a Telegram alert when the tank is full or the sensor fails.
  • Add a dry-run guard — a float switch in the sump so the pump never runs without water.
  • Log levels to a web dashboard on your phone over Wi-Fi.

↑↓ to move · Enter to open