Overhead polytanks are everywhere in Ghana, and knowing how much water is left saves a lot of climbing. In this project an ESP32 reads a waterproof HC-SR04 ultrasonic sensor mounted in the tank lid, converts the distance into a percentage and shows it on a small OLED screen.
How it works
The sensor sends a short ultrasonic pulse down towards the water and times the echo. Sound travels at about 343 m/s (0.0343 cm per microsecond), so the distance is echo time × 0.0343 ÷ 2. Subtract that from the tank height and you have the water level.
Wiring
- HC-SR04 VCC → 5V (the ESP32 DevKit's VIN/5V pin when powered over USB) and GND → GND.
- Trig → GPIO 5.
- Echo → GPIO 18 through a voltage divider (1 kΩ from Echo to GPIO 18, 2 kΩ from GPIO 18 to GND). When powered from 5 V the echo pulse is 5 V, and ESP32 pins are 3.3 V only.
- OLED SDA → GPIO 21, SCL → GPIO 22, VCC → 3.3V, GND → GND.
Mount the probe facing straight down, at least 25 cm above the highest water level — the HC-SR04 cannot measure closer than that.
The code
Install Adafruit SSD1306 and Adafruit GFX from the Arduino Library Manager, set TANK_HEIGHT_CM and SENSOR_GAP_CM for your tank, and upload:
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
const int TRIG = 5, ECHO = 18;
const float TANK_HEIGHT_CM = 120; // inside height of the tank
const float SENSOR_GAP_CM = 30; // distance from sensor to the "full" line
Adafruit_SSD1306 oled(128, 64, &Wire, -1);
float readDistanceCm() {
digitalWrite(TRIG, LOW); delayMicroseconds(5);
digitalWrite(TRIG, HIGH); delayMicroseconds(20);
digitalWrite(TRIG, LOW);
unsigned long us = pulseIn(ECHO, HIGH, 30000); // 30 ms timeout
return us ? us * 0.0343 / 2.0 : -1;
}
void setup() {
Serial.begin(115200);
pinMode(TRIG, OUTPUT); pinMode(ECHO, INPUT);
oled.begin(SSD1306_SWITCHCAPVCC, 0x3C);
}
void loop() {
float d = readDistanceCm();
if (d > 0) {
float water = TANK_HEIGHT_CM + SENSOR_GAP_CM - d;
int pct = constrain((int)(water / TANK_HEIGHT_CM * 100), 0, 100);
Serial.printf("distance %.1f cm, level %d%%\n", d, pct);
oled.clearDisplay(); oled.setTextColor(SSD1306_WHITE);
oled.setTextSize(1); oled.setCursor(0, 0); oled.print("Tank level");
oled.setTextSize(3); oled.setCursor(0, 24); oled.printf("%d%%", pct);
oled.display();
}
delay(2000);
}
Tips for a reliable reading
- Take 5 readings and use the median — ripples and condensation cause the odd wild value.
- Keep the electronics outside the tank in a sealed box; only the probe goes in the lid.
- For a remote tank, add a SIM800L module (see our SMS alerts tutorial) or use the ESP32's Wi-Fi to send the level to a dashboard over MQTT.



