Pets need fresh water all day, and a bowl can run dry quickly on a hot day. This lesson puts the water level sensor from the sensor kit into a printed clip on the bowl. The ESP32 checks the level every ten minutes and shows it in the Blynk app, and when it drops below a quarter it sends a notification: time to refill. To protect the sensor, it is only switched on for the moment of each measurement.
What you'll learn
- How a water level sensor measures water with exposed copper stripes
- Why powering a sensor only while measuring makes it last longer
- How to turn a raw reading into a percentage with two calibration points
- How to send one alert and reset it when the bowl is refilled
What you need
- ESP32 board, expansion board and USB cable (ESP32 IoT Starter Kit)
- Water level sensor (45-in-1 Sensor Kit)
- 3 long female-to-male jumper wires
- A free Blynk account and the Blynk IoT app on a phone
- A 2.4 GHz Wi-Fi network
- The printed parts from the 3D printing box below (bowl clip), or the cardboard version described there
- Breadboard and jumper wires (ESP32 IoT Starter Kit)
Wiring

| From | To |
|---|---|
| Water sensor S | ESP32 GPIO 35 |
| Water sensor + | ESP32 GPIO 32 (switched on only while measuring) |
| Water sensor − | GND |
Sensor modules from different makers can label and order their pins differently. Always follow the labels printed on your module.
Safety: the sensor only carries 3.3 V and is safe in the bowl, but keep the ESP32, the USB cable and the charger well away from water and from where your pet can chew them. Rinse the sensor now and then.
Set up Blynk
In the Blynk console, create a new template called Water Bowl (hardware ESP32, connection type Wi-Fi) and add these datastreams. Lesson 1 of this path shows each step in detail.
| Virtual pin | Name | Data type | Range | Phone widget |
|---|---|---|---|---|
| V0 | Water level | Integer | 0–100 | Gauge |
| V1 | Status | String | – | Label |
| V2 | Last refill | String | – | Label |
Then open the template's Events & Notifications tab, add an event with the code bowl_low (name: Water bowl low) and turn on push notifications for it. The event code must match the sketch exactly.
Save the template, create a new device from it and copy its three BLYNK_… lines. In the phone app, open the device, tap the wrench icon and add a Gauge (V0) and two Labels (V1, V2). Link each widget to its datastream.
Calibrate
Upload the sketch once and watch the Serial Monitor at 115200 baud: it prints the raw reading. Note the value with the sensor dry and with the bowl full, and put them into DRY and FULL.

The sketch
Replace the three BLYNK_… lines at the top with the ones from your device's Device info page, and add your Wi-Fi name and password (2.4 GHz only). The BLYNK_… lines must stay above #include <BlynkSimpleEsp32.h>.
#define BLYNK_TEMPLATE_ID "YOUR_TEMPLATE_ID"
#define BLYNK_TEMPLATE_NAME "Water Bowl"
#define BLYNK_AUTH_TOKEN "YOUR_AUTH_TOKEN"
#define BLYNK_PRINT Serial // show connection messages in the Serial Monitor
#include <WiFi.h>
#include <WiFiClient.h>
#include <BlynkSimpleEsp32.h>
#include <time.h>
char ssid[] = "YOUR_WIFI_NAME";
char pass[] = "YOUR_WIFI_PASSWORD";
const int SENSOR_PIN = 35;
const int POWER_PIN = 32;
const int DRY = 0; // raw reading with the sensor dry
const int FULL = 1900; // raw reading with the bowl full
const int LOW_PERCENT = 25;
bool lowSent = false;
int lastPercent = -1;
BlynkTimer timer;
int readRaw() {
digitalWrite(POWER_PIN, HIGH); // power the sensor only for a moment
delay(50);
long sum = 0;
for (int i = 0; i < 10; i++) sum += analogRead(SENSOR_PIN);
digitalWrite(POWER_PIN, LOW);
return sum / 10;
}
String timeNow() {
struct tm t;
if (!getLocalTime(&t, 10)) return "unknown time";
char text[20];
strftime(text, sizeof(text), "%d.%m %H:%M", &t);
return String(text);
}
void check() {
int raw = readRaw();
int percent = constrain(map(raw, DRY, FULL, 0, 100), 0, 100);
Serial.printf("raw %d -> %d %%\n", raw, percent);
Blynk.virtualWrite(V0, percent);
if (lastPercent >= 0 && percent > lastPercent + 20) { // big jump up: refilled
Blynk.virtualWrite(V2, timeNow());
}
lastPercent = percent;
if (percent < LOW_PERCENT) {
Blynk.virtualWrite(V1, "Refill soon!");
if (!lowSent) {
Blynk.logEvent("bowl_low", String("The water bowl is at ") + percent + " %.");
lowSent = true;
}
} else {
Blynk.virtualWrite(V1, "OK");
if (percent > LOW_PERCENT + 15) lowSent = false; // ready to warn again
}
}
void setup() {
Serial.begin(115200);
pinMode(POWER_PIN, OUTPUT);
digitalWrite(POWER_PIN, LOW);
Blynk.begin(BLYNK_AUTH_TOKEN, ssid, pass);
configTzTime("EET-2EEST,M3.5.0/3,M10.5.0/4", "pool.ntp.org"); // your time zone here
timer.setTimeout(3000L, check);
timer.setInterval(10L * 60 * 1000, check); // every 10 minutes
}
void loop() {
Blynk.run();
timer.run();
}
How it works
The sensor has long copper stripes, connected alternately to the power and to the signal. Water conducts a little electricity, so the more stripes are under water, the higher the reading. Two calibration values, dry and full, turn the raw number into a percentage with map().
With current flowing all the time, the copper slowly corrodes in water. That is why GPIO 32 powers the sensor for only 50 ms every ten minutes. The sensor is then switched on for less than a second a day, so it lasts much longer. The alert uses the same idea as in other Blynk lessons: lowSent makes sure you get one notification, and it resets only when the bowl has clearly been refilled.
Try this next
- Add a Superchart in Blynk to see how fast your pet drinks during the day.
- Measure every minute while you refill, to make the "last refill" time more exact.
- Use the same clip and sketch for a plant saucer or a rain barrel.
Troubleshooting
-
The device shows "Offline" in the app: check the Serial Monitor at 115200 baud. "Invalid auth token" means the
BLYNK_AUTH_TOKENline is wrong; if it never connects, check the Wi-Fi name and password, and that your network is 2.4 GHz. - The level is always 0: check the + wire is on GPIO 32 and S on GPIO 35, and calibrate again.
- The level jumps around: the water moves when your pet drinks; that is normal. Readings settle within a minute.
-
No notification: the event code must be exactly
bowl_low, with notifications turned on. - A printed part is too tight or too loose: printers differ by a few tenths of a millimetre. Sand or file a tight hole, and fix a loose part with a drop of hot glue or a strip of tape.