Community project

Automatic Water Dispenser Monitor

ESP32
Photo of Automatic Water Dispenser Monitor
Generated with AI

reaseyvirakbot28

Published October 29, 2026

This project builds an automatic water dispenser monitor that tracks tank level in real time and dispenses water on demand. The ESP32 microcontroller reads distance measurements from an ultrasonic sensor to calculate water depth, displays the fill percentage on an OLED screen, and controls a 12V submersible pump via a MOSFET switch when the user presses the button.

The guide provides a complete wiring diagram, parts list, and step-by-step assembly instructions for mounting the sensor in a tank, connecting the display and pump control circuit, and calibrating the level thresholds. Firmware is included and ready to upload, with configuration constants for different tank dimensions.

Wiring diagram

Loading diagram…

Gather all the parts

QtyComponent
1

JSN-SR04T Ultrasonic Sensor

JSN-SR04T waterproof ultrasonic distance sensor. Sealed transducer on a cable feeds a small driver board with HC-SR04-compatible trigger/echo timing. 25cm to 4m typical, 2m optimal. Common for water tank level, sump monitoring, and aquaponics reservoirs because the transducer survives splashes and condensation that kill an HC-SR04.

1

SSD1306 OLED

0.96-inch 128 x 64 white monochrome OLED display module driven by an SSD1306 controller over I2C (address 0x3C). It runs from 3 V to 5 V and connects through a 4-pin 2.54 mm header (GND, VCC, SCL, SDA); the self-emitting pixels need no backlight.

1

Push Button

Momentary push button switch

1

12V DC Submersible Mini Water Pump

Two-wire brushed DC submersible water pump for small irrigation or circulation projects. It is an external load and must be driven through a relay, MOSFET, or motor driver; do not connect directly to an MCU GPIO.

1

AO3400A

30V 5.7A N-channel enhancement-mode logic-level MOSFET in SOT-23-3L. Vgs(th) typically ~0.7V (max 1.45V at Id=250 uA), fully enhanced at Vgs=2.5V-4.5V. RDS(on) ~26 mOhm at Vgs=4.5V, ~38 mOhm at Vgs=2.5V. Driven directly by 3.3V or 5V microcontroller GPIO/PWM as a low-side switch for loads up to its Id and VDS limits. No firmware library required.

1

1N4001 Flyback Diode

General-purpose silicon rectifier diode used as a flyback / freewheeling diode to suppress back-EMF voltage spikes from inductive loads (DC motors, relays). Rated 50 V reverse voltage and 1 A forward current. Connected in parallel across the motor terminals with cathode (striped band) to the motor positive (+) / 5 V supply side and anode to the motor negative (−) / transistor collector side. Requires no firmware or library — purely passive protection component. Higher-voltage 1N400x rectifier variants are compatible substitutes when the current and package fit the design.

1

12V Barrel-Jack Adapter

12 V / 2 A wall adapter with a 5.5 mm / 2.1 mm barrel jack. Used to power motor drivers, LED strips, or boards that need a higher rail.

1

Resistor

1 kΩ

Through-hole resistor (current-limiting in series with an LED)

1

Resistor

2 kΩ

Through-hole resistor (current-limiting in series with an LED)

1

Resistor

220 Ω

Through-hole resistor (current-limiting in series with an LED)

1

Resistor

10 kΩ

Through-hole resistor (current-limiting in series with an LED)

Assemble it in 5 steps

1. Prepare a suitable tank

Fit the waterproof sensor head above the water, pointing straight down in a clear wide area. Keep its small circuit board dry. Use a tall tank: the sensor must be at least 30 cm above the water at full and 80 cm at the usable empty level with the default settings. Adjust FULL_CM and EMPTY_CM in the firmware to measured distances for your tank; full must be at least 25 cm. Test readings with your actual tank.

  • The sensor is not a certified overflow or dry-run safety device. Do not fill above its blind zone or leave the system unattended.

2. Connect the screen and level sensor

With power unplugged, wire screen VCC to 3V3, GND to GND, SDA to GPIO21 (data) and SCL to GPIO22 (clock). Wire the sensor VCC to 5V, GND to GND, and TRIG to GPIO26 (trigger). Put the 1 kΩ resistor between ECHO and GPIO27, and the 2 kΩ resistor between GPIO27 and GND. They reduce the sensor signal to a safe voltage.

  • Check the screen's printed pin labels; their order varies. Never connect ECHO directly to an ESP32 pin.

3. Connect the dispense button

Connect the button SIGNAL to GPIO32 and GND to GND. Hold it to run the pump and release to stop.

4. Wire the pump switch

Solder the small AO3400A MOSFET onto a suitable SOT-23 breakout. Connect its source to GND, drain to pump negative, and gate to GPIO25 through the 220 Ω resistor. Connect the 10 kΩ resistor between gate and GND. Connect pump positive to the 12 V adapter positive. Connect the adapter negative to ESP32 GND. Put the diode across the pump: banded end to pump positive, unbanded end to pump negative. Use a matching barrel-jack connector to access the adapter output.

  • Never connect 12 V to the ESP32 power or GPIO pins. Choose a 12 V pump with startup and running current below the adapter's 2 A rating and compatible with the 1 A diode; verify MOSFET heating during tests. Keep all electronics, the adapter and mains power dry.

5. Test before using

Connect tubing and prime the pump as its instructions require. Power the board by USB and the pump from its separate adapter. Check the screen level against measured water heights. Briefly hold the button with a container under the outlet; release to stop. Check low-water and sensor-error stopping. The pump stops after 10 seconds until the button is released.

  • For drinking water, use only a pump, tubing, tank and sensor materials explicitly rated food-safe, and clean and sanitize them. A generic irrigation pump is not automatically drink-safe.

Review all connections

1. Connections between "level" and "ESP32"

FunctionlevelESP32
powerVCC5V
groundGNDGND
dataTRIGGPIO 26
dataECHO → Resistor P1EXT

2. Connections between "echo_top" and "ESP32"

Functionecho_topESP32
digitalP2GPIO 27

3. Connections between "echo_bottom" and "ESP32"

Functionecho_bottomESP32
digitalP1 → Resistor P2EXT
groundP2GND

4. Connections between "screen" and "ESP32"

FunctionscreenESP32
powerVCC3V3
groundGNDGND
i2cSDAGPIO 21
i2cSCLGPIO 22

5. Connections between "button" and "ESP32"

FunctionbuttonESP32
digitalSIGNALGPIO 32
groundGNDGND

6. Connections between "gate_resistor" and "ESP32"

Functiongate_resistorESP32
digitalP1GPIO 25
digitalP2 → AO3400A Gate (G)EXT

7. Connections between "gate_pulldown" and "ESP32"

Functiongate_pulldownESP32
digitalP1 → AO3400A Gate (G)EXT
groundP2GND

8. Connections between "switch" and "ESP32"

FunctionswitchESP32
groundSource (S)GND
powerDrain (D) → 12V DC Submersible Mini Water Pump PUMP-EXT

9. Connections between "pump" and "ESP32"

FunctionpumpESP32
dataPUMP+ → 12V Barrel-Jack Adapter +12VEXT

10. Connections between "diode" and "ESP32"

FunctiondiodeESP32
passiveCathode → 12V DC Submersible Mini Water Pump PUMP+EXT
passiveAnode → 12V DC Submersible Mini Water Pump PUMP-EXT

11. Connections between "supply" and "ESP32"

FunctionsupplyESP32
groundGNDGND

Deploy the firmware

#include <Arduino.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

constexpr int TRIG = 26, ECHO = 27, PUMP = 25, BUTTON = 32;
// Measure vertically from the sensor face to the water. Adjust for your tank.
constexpr int FULL_CM = 30;   // Must be at least 25 cm from sensor.
constexpr int EMPTY_CM = 80;  // Water surface at the usable empty level.
constexpr unsigned long SAMPLE_MS = 600, MAX_RUN_MS = 10000;
Adafruit_SSD1306 screen(128, 64, &Wire, -1);
bool displayOK = false, sensorOK = false, pumping = false, locked = false;
int percent = 0, lastPercent = -2, lastState = -1;
unsigned long lastSample = 0, pumpStart = 0;

void sampleLevel() {
  digitalWrite(TRIG, LOW); delayMicroseconds(2);
  digitalWrite(TRIG, HIGH); delayMicroseconds(25);
  digitalWrite(TRIG, LOW);
  unsigned long us = pulseIn(ECHO, HIGH, 30000);
  float cm = us * 0.0343f / 2.0f;
  // Reject readings in the sensor's blind zone and well beyond the tank.
  sensorOK = us != 0 && cm >= 25.0f && cm <= EMPTY_CM + 10;
  if (sensorOK) percent = constrain((int)((EMPTY_CM - cm) * 100.0f / (EMPTY_CM - FULL_CM)), 0, 100);
}

void setup() {
  pinMode(PUMP, OUTPUT); digitalWrite(PUMP, LOW);
  pinMode(TRIG, OUTPUT); digitalWrite(TRIG, LOW);
  pinMode(ECHO, INPUT); pinMode(BUTTON, INPUT_PULLUP);
  Wire.begin(21, 22);
  displayOK = screen.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  sampleLevel(); lastSample = millis();
}

void loop() {
  unsigned long now = millis();
  if (now - lastSample >= SAMPLE_MS) { lastSample = now; sampleLevel(); now = millis(); }
  bool pressed = digitalRead(BUTTON) == LOW;
  if (!pressed) locked = false;
  bool canPump = sensorOK && percent > 10 && !locked;
  if (pumping && now - pumpStart >= MAX_RUN_MS) { locked = true; canPump = false; }
  bool next = pressed && canPump;
  if (next && !pumping) pumpStart = now;
  pumping = next;
  digitalWrite(PUMP, pumping ? HIGH : LOW);
  // Redraw only when the displayed level or state changes.
  int state = !sensorOK ? 0 : percent <= 10 ? 1 : locked ? 2 : pumping ? 3 : 4;
  int shown = sensorOK ? percent : -1;
  if (displayOK && (state != lastState || shown != lastPercent)) {
    lastState = state; lastPercent = shown;
    screen.clearDisplay(); screen.setTextColor(SSD1306_WHITE);
    screen.setTextSize(1); screen.setCursor(0, 0); screen.print("WATER LEVEL");
    screen.setTextSize(2); screen.setCursor(0, 17);
    if (sensorOK) { screen.print(percent); screen.print('%'); } else screen.print("--");
    screen.setTextSize(1); screen.setCursor(0, 48);
    if (state == 0) screen.print("SENSOR ERROR");
    else if (state == 1) screen.print("LOW - REFILL");
    else if (state == 2) screen.print("RELEASE BUTTON");
    else if (state == 3) screen.print("DISPENSING");
    else screen.print("HOLD TO DISPENSE");
    screen.display();
  }
  delay(10);
}

Remix this project

Make it yours in one click

Open a full copy of this project in your own Schematik workspace, diagram, code, parts, and assembly steps included. Swap the sensor, add features, or redesign the whole thing with AI. The author's original stays untouched.

Open in Schematik