ESP32 S3 UNO - Water Sensor

This ESP32 S3 Uno water sensor tutorial shows you how to wire a water level sensor to the ESP32 S3 Uno form board and read it with Arduino code. With this simple water detector you can catch a water leak, sense rainfall, warn about a tank overflow, or measure how high the water is.

In this tutorial, you will:

  1. Wire a water level sensor to the ESP32 S3 Uno
  2. Read the sensor value with analogRead() in Arduino IDE
  3. Detect water leakage, rain or tank overflow with a threshold
  4. Measure the water level in several steps
  5. Calibrate the water sensor for your own water
ESP32 S3 Uno water sensor

Hardware Preparation

1×ESP32 S3 Uno-form Board
1×USB Cable Type-A to Type-C (for USB-A PC)
1×USB Cable Type-C to Type-C (for USB-C PC)
1×Water level sensor
1×Jumper Wires

Or you can buy the following kits:

1×DIYables Sensor Kit (18 sensors/displays)
Disclosure: Some of the links provided in this section are Amazon affiliate links. We may receive a commission for any purchases made through these links at no additional cost to you.
Additionally, some of these links are for products from our own brand, DIYables .

Overview of Water Level Sensor

A water level sensor is a cheap and simple way to find out if water is present, and roughly how deep it is. It has no moving parts. It only uses copper traces and the fact that normal water conducts electricity.

Water Level Sensor Pinout

The sensor has only three pins, so the wiring is quick.

Pin What It Does
S (Signal) Analog output. Connect it to an analog input pin of the ESP32 S3 Uno
+ (VCC) Power input, 3.3V to 5V. With the ESP32 S3 Uno, power it with 3.3V (from a GPIO pin, see below)
* (GND) Ground (0V)
water sensor pinout

※ NOTE THAT:

The voltage on the signal pin depends on the voltage you give to the VCC pin. If you change the supply voltage, the readings change too.

How Water Level Sensor Works

The idea is simple: the deeper the sensor goes into water, the higher the voltage on the signal pin. The details below explain why.

This section includes advanced information that may be overwhelming. If you are unsure about the content, feel free to skip it and move on to the next sections.

Copper traces

Look at the sensor and you will see ten copper traces. Five are power traces and five are sense traces. They sit side by side, and each sense trace lies between two power traces. In dry air the traces do not touch. When the sensor goes into water, the water connects them.

A variable resistor made of water

Together, the traces act like a variable resistor, a bit like a potentiometer. Its resistance depends on how much of the sensor is under water. When more of the sensor is in water, current flows more easily and the resistance goes down. When less of the sensor is in water, the resistance goes up.

From resistance to voltage

The sensor turns this resistance into an output voltage. So when you measure the voltage on the signal pin, you know how far the water has climbed up the sensor.

ESP32 S3 Uno Pinout

The image below shows the pinout of the ESP32 S3 Uno form board. Use it to find the Uno header pins (D5, A0…) and their GPIO numbers when you wire the sensor.

ESP32 S3 Uno pinout diagram

Wiring Diagram

A water sensor that has power all the time in a wet place corrodes fast and does not last long. To avoid this, the diagram powers the sensor from a digital pin, not from a power pin. The code sets this pin HIGH just before it reads the sensor, and sets it LOW right after. A GPIO pin gives 3.3V, which is the right voltage for the ESP32 S3 Uno.

The wiring diagram between ESP32 S3 Uno Water Sensor

This image is created using Fritzing. Click to enlarge image

Water Sensor Pin ESP32 S3 Uno Pin
+ (VCC) D5 (GPIO20)
* (GND) GND
S (Signal) A0 (GPIO2)

WARNING

ESP32 S3 Uno pins are NOT 5V tolerant. Do not power the sensor from the 5V pin. With 5V on VCC, the signal pin can go above 3.3V and damage the analog input. Keep the sensor powered from D5 (GPIO20) or from the 3.3V pin.

ESP32 S3 Uno Code - Reading Value from Water Sensor

This first sketch powers the sensor for a short moment, reads the signal pin, and turns the power off again. It prints the raw value to the Serial Monitor once per second. On the ESP32 S3 Uno the value goes from 0 to 4095, because the ADC is 12-bit.

/* * This ESP32 S3 Uno code was developed by newbiely.com * * This ESP32 S3 Uno code is made available for public use without any restriction * * For comprehensive instructions and wiring diagrams, please visit: * https://newbiely.com/tutorials/esp32-s3-uno/esp32-s3-uno-water-sensor */ #define POWER_PIN 20 // The ESP32 S3 Uno pin D5 (GPIO20) connected to the VCC pin of water sensor to control the power #define SIGNAL_PIN 2 // The ESP32 S3 Uno pin A0 (GPIO2) connected to the signal pin of water sensor int value = 0; // Variable for storing the value read from the sensor void setup() { Serial.begin(9600); // Start serial communication at 9600 baud // set the ADC attenuation to 11 dB (up to ~3.3V input) analogSetAttenuation(ADC_11db); pinMode(POWER_PIN, OUTPUT); // Set POWER_PIN as an output digitalWrite(POWER_PIN, LOW); // Initially set the POWER_PIN LOW to turn the sensor off } void loop() { digitalWrite(POWER_PIN, HIGH); // Set POWER_PIN HIGH to power the sensor delay(10); // Allow sensor time to stabilize (10ms delay) value = analogRead(SIGNAL_PIN); // Read the value from the sensor (0-4095) digitalWrite(POWER_PIN, LOW); // Set POWER_PIN LOW to turn off the sensor Serial.print("Sensor value: "); // Print the sensor value to Serial Monitor Serial.println(value); // Display the sensor value delay(1000); // Wait for 1 second before reading again }

The sketch calls analogSetAttenuation(ADC_11db) in setup(). By default, the ESP32 S3 Uno ADC can only measure a small voltage. The 11 dB setting lets the analog pin read the full range, from 0V up to about 3.3V. Without it, the reading hits 4095 too early and the water values look wrong.

Detailed Instructions

Do these steps in order:

  1. New to the ESP32 S3 Uno? Follow ESP32 S3 Uno - Getting Started first.
  2. Wire it up as shown in the diagram.
  3. Connect the board to your computer with a USB Type-C cable.
  4. Open Arduino IDE, choose the ESP32S3 Dev Module board and the correct COM port.
  5. Copy the code above and paste it into Arduino IDE.
  6. Upload the code by clicking the Upload button.
  7. Lower the sensor slowly into a glass of water.
  8. Open the Serial Monitor at 9600 baud and watch the values. The value is 0 while the sensor touches nothing.
  9. Tip: Write down the values you see at different depths. You will need them for the leak threshold and the level steps below.
∞
Newbiely | Arduino IDE 2.3.8
──
☐
✕
File
Edit
Sketch
Tools
Help
ESP32S3 Dev Module
Newbiely.ino
···
8 Serial.println("Hello World!");
Output
Serial Monitor
Message (Enter to send message to 'ESP32S3 Dev Module' on 'COM15')
New Line
9600 baud
Sensor value: 0 Sensor value: 0 Sensor value: 0 Sensor value: 100 Sensor value: 388 Sensor value: 1136 Sensor value: 1712 Sensor value: 1740 Sensor value: 1764 Sensor value: 1820 Sensor value: 1868 Sensor value: 2084 Sensor value: 2112 Sensor value: 2212
Ln 11, Col 1
ESP32S3 Dev Module on COM15
2

※ NOTE THAT:

Never put the whole sensor under water. Only the bare copper traces on the board may touch the water. Mount the sensor with care so the electronics and the pins stay dry.

How To Detect Water Leakage

A leak, rain or a tank overflow all mean the same thing for the sensor: water has reached it. So the code only needs to compare the reading with a threshold. You find the right threshold in the calibration section of this tutorial.

ESP32 S3 Uno Code - Detecting Water Leakage

/* * This ESP32 S3 Uno code was developed by newbiely.com * * This ESP32 S3 Uno code is made available for public use without any restriction * * For comprehensive instructions and wiring diagrams, please visit: * https://newbiely.com/tutorials/esp32-s3-uno/esp32-s3-uno-water-sensor */ #define POWER_PIN 20 // The ESP32 S3 Uno pin D5 (GPIO20) connected to the VCC pin of water sensor to control the power #define SIGNAL_PIN 2 // The ESP32 S3 Uno pin A0 (GPIO2) connected to the signal pin of water sensor #define THRESHOLD 1200 // Threshold for the 12-bit ADC (0-4095) int value = 0; // Initialize variable to store the value read from the sensor void setup() { Serial.begin(9600); // set the ADC attenuation to 11 dB (up to ~3.3V input) analogSetAttenuation(ADC_11db); pinMode(POWER_PIN, OUTPUT); // Set the power pin as an output digitalWrite(POWER_PIN, LOW); // Initially turn off the power } void loop() { digitalWrite(POWER_PIN, HIGH); // Enable power to the sensor delay(10); // Allow some time for the sensor to stabilize value = analogRead(SIGNAL_PIN); // Read the sensor output digitalWrite(POWER_PIN, LOW); // Disable power to the sensor if (value > THRESHOLD) { // Check if the sensor output exceeds the threshold Serial.print("The water is detected"); // Send a message to the serial monitor } }

The threshold of 1200 is only a starting point for the 12-bit ADC. Replace it with the value you measure in your own water.

How To Measure The Water Level

Sometimes "wet or dry" is not enough, and you want to know how full a tank is. The sketch below splits the full height of the sensor into 4 steps and prints the current step. The highest level it can measure is the height of the sensor itself.

/* * This ESP32 S3 Uno code was developed by newbiely.com * * This ESP32 S3 Uno code is made available for public use without any restriction * * For comprehensive instructions and wiring diagrams, please visit: * https://newbiely.com/tutorials/esp32-s3-uno/esp32-s3-uno-water-sensor */ #define POWER_PIN 20 // The ESP32 S3 Uno pin D5 (GPIO20) connected to the VCC pin of water sensor to control the power #define SIGNAL_PIN 2 // The ESP32 S3 Uno pin A0 (GPIO2) connected to the signal pin of water sensor #define SENSOR_MIN 0 // Minimum value for sensor #define SENSOR_MAX 2084 // Maximum value for sensor (12-bit ADC) int value = 0; // Holds the current sensor value int level = 0; // Holds the water level indicator void setup() { Serial.begin(9600); // Initialize serial communication // set the ADC attenuation to 11 dB (up to ~3.3V input) analogSetAttenuation(ADC_11db); pinMode(POWER_PIN, OUTPUT); // Set the power pin as output digitalWrite(POWER_PIN, LOW); // Initially turn off the sensor } void loop() { digitalWrite(POWER_PIN, HIGH); // Activate the sensor delay(10); // Wait for the sensor to stabilize value = analogRead(SIGNAL_PIN); // Read the sensor output digitalWrite(POWER_PIN, LOW); // Deactivate the sensor level = map(value, SENSOR_MIN, SENSOR_MAX, 0, 4); // Map sensor values to 0-4 scale Serial.print("Water level: "); // Print the label Serial.println(level); // Print the mapped water level delay(1000); // Wait one second before repeating }

※ NOTE THAT:

  • You find SENSOR_MIN and SENSOR_MAX during calibration.
  • The map() method is simple and not very precise, but it is good enough for many projects. For better results, measure a separate threshold for each level, as the calibration section explains.

Water Level Sensor Calibration

Two things change the sensor reading: the water level and how well the water conducts electricity. Pure water hardly conducts at all. Tap water, rain water and dirty water hold minerals, so they conduct much better. The better the water conducts, the more sensitive the sensor gets. The supply voltage on the VCC pin also changes the values.

Because of this, you get the best results when you calibrate the sensor with the same water you plan to watch. Do a short test before you pick any threshold.

How to do the test

  1. Upload the first sketch that prints the raw sensor value.
  2. Put the sensor into the water up to the level you care about.
  3. Read the value in the Serial Monitor.
  4. Use this value as the threshold that starts your action.

You may need a few tries to get a stable number.

What the test tells you

With the same test you can find the lowest value (sensor out of the water), the highest value (all traces under water), the threshold for leak detection, and the threshold for each step of your level scale.

Video Tutorial

Watch the video below to see this ESP32 S3 Uno project step by step.

The demo in the video uses the code below. It reads the water sensor with a simple filter (EMA), turns the level into a 0 to 4 scale, shows it on an LCD 20x4 I2C, lights a green, yellow or red LED, and beeps a buzzer at the top level. The calibrated range is set for the 12-bit ADC (0 to 4095).

/* * This ESP32 S3 Uno code was developed by newbiely.com * * This ESP32 S3 Uno code is made available for public use without any restriction * * For comprehensive instructions and wiring diagrams, please visit: * https://newbiely.com/tutorials/esp32-s3-uno/esp32-s3-uno-water-sensor */ #include <Wire.h> #include <LiquidCrystal_I2C.h> LiquidCrystal_I2C lcd(0x27, 20, 4); // Water sensor pins (ESP32 S3 Uno) #define SENSOR_POWER_PIN 20 // D5 #define SENSOR_SIGNAL_PIN 2 // A0 // Status indicator pins (ESP32 S3 Uno) #define GREEN_LED_PIN 3 // D6 - boot strapping pin #define YELLOW_LED_PIN 14 // D7 #define RED_LED_PIN 21 // D8 #define BUZZER_PIN 46 // D9 - boot strapping pin // Calibrated sensor range (12-bit ADC, 0-4095) #define SENSOR_MIN 120 #define SENSOR_MAX 1880 // Exponential moving average filter float filteredSensor = 0.0; const float FILTER_ALPHA = 0.75; // Sensor sampling interval unsigned long lastSensorRead = 0; const unsigned long SENSOR_INTERVAL = 100; // LCD refresh interval unsigned long lastLCDUpdate = 0; const unsigned long LCD_INTERVAL = 250; // Buzzer timing unsigned long lastBuzzerTime = 0; const unsigned long BUZZER_INTERVAL = 300; bool buzzerState = false; /** * Read the water sensor while powering it only during measurement. * This helps reduce sensor corrosion during long-term operation. */ int readWaterSensor() { digitalWrite(SENSOR_POWER_PIN, HIGH); delayMicroseconds(500); int val = analogRead(SENSOR_SIGNAL_PIN); digitalWrite(SENSOR_POWER_PIN, LOW); return val; } void setup() { Serial.begin(115200); // set the ADC attenuation to 11 dB (up to ~3.3V input) analogSetAttenuation(ADC_11db); // Configure sensor power and signal pins. pinMode(SENSOR_POWER_PIN, OUTPUT); digitalWrite(SENSOR_POWER_PIN, LOW); // Configure LEDs and buzzer. pinMode(GREEN_LED_PIN, OUTPUT); pinMode(YELLOW_LED_PIN, OUTPUT); pinMode(RED_LED_PIN, OUTPUT); pinMode(BUZZER_PIN, OUTPUT); // Ensure all indicators are OFF at startup. digitalWrite(GREEN_LED_PIN, LOW); digitalWrite(YELLOW_LED_PIN, LOW); digitalWrite(RED_LED_PIN, LOW); digitalWrite(BUZZER_PIN, LOW); // Initialize LCD. lcd.init(); lcd.backlight(); lcd.clear(); lcd.setCursor(4, 0); lcd.print("WATER LEVEL"); lcd.setCursor(6, 1); lcd.print("MONITOR"); lcd.setCursor(5, 3); lcd.print("Starting..."); delay(1500); lcd.clear(); // Initialize the filter with the first sensor reading. filteredSensor = readWaterSensor(); } void loop() { // Read and filter the sensor periodically. if (millis() - lastSensorRead >= SENSOR_INTERVAL) { lastSensorRead = millis(); int rawSensor = readWaterSensor(); // Apply exponential moving average to reduce sensor noise. filteredSensor = FILTER_ALPHA * rawSensor + (1.0 - FILTER_ALPHA) * filteredSensor; // Limit the sensor value to the calibrated range. int clampedSensor = constrain( (int)filteredSensor, SENSOR_MIN, SENSOR_MAX ); // Convert the sensor value into a water level from 0 to 4. int level = map( clampedSensor, SENSOR_MIN, SENSOR_MAX, 0, 4 ); // Update the LED indicator according to the water level. if (level == 0) { digitalWrite(GREEN_LED_PIN, LOW); digitalWrite(YELLOW_LED_PIN, LOW); digitalWrite(RED_LED_PIN, LOW); } else if (level <= 2) { digitalWrite(GREEN_LED_PIN, HIGH); digitalWrite(YELLOW_LED_PIN, LOW); digitalWrite(RED_LED_PIN, LOW); } else if (level == 3) { digitalWrite(GREEN_LED_PIN, LOW); digitalWrite(YELLOW_LED_PIN, HIGH); digitalWrite(RED_LED_PIN, LOW); } else { digitalWrite(GREEN_LED_PIN, LOW); digitalWrite(YELLOW_LED_PIN, LOW); digitalWrite(RED_LED_PIN, HIGH); } } // Convert the filtered sensor value into the current water level. int currentLevel = map( constrain( (int)filteredSensor, SENSOR_MIN, SENSOR_MAX ), SENSOR_MIN, SENSOR_MAX, 0, 4 ); // Activate the buzzer when the water level reaches the maximum. if (currentLevel == 4) { if (millis() - lastBuzzerTime >= BUZZER_INTERVAL) { lastBuzzerTime = millis(); buzzerState = !buzzerState; digitalWrite(BUZZER_PIN, buzzerState ? HIGH : LOW); } } else { // Immediately stop the buzzer when the level drops below maximum. digitalWrite(BUZZER_PIN, LOW); buzzerState = false; } // Refresh the LCD periodically to avoid unnecessary screen updates. if (millis() - lastLCDUpdate >= LCD_INTERVAL) { lastLCDUpdate = millis(); lcd.setCursor(0, 0); lcd.print("Water Level "); lcd.setCursor(0, 1); lcd.print("Level: "); lcd.print(currentLevel); lcd.print(" / 4 "); // Output sensor data for debugging and calibration. Serial.print("ADC: "); Serial.print((int)filteredSensor); Serial.print(" | Level: "); Serial.print(currentLevel); Serial.println(" / 4"); } }
Demo Part ESP32 S3 Uno Pin
Water sensor + (VCC) D5 (GPIO20)
Water sensor S (Signal) A0 (GPIO2)
Green LED D6 (GPIO3)
Yellow LED D7 (GPIO14)
Red LED D8 (GPIO21)
Buzzer D9 (GPIO46)
LCD SDA SDA (GPIO8)
LCD SCL SCL (GPIO9)

WARNING

The green LED is on D6 (GPIO3) and the buzzer is on D9 (GPIO46). Both are boot strapping pins on the ESP32 S3 Uno. If a part pulls one of these pins HIGH while the board starts, the board may not boot or may fail to upload code. Connect the LED and the buzzer to GND through their resistor or module (not to 3.3V). If you have boot or upload problems, unplug these wires and try again. The LCD also runs on 5V, and many LCD I2C backpacks pull SDA and SCL up to 5V, so use a 5V to 3.3V level converter on these two lines.

FAQ

Can I power the water sensor from 5V on the ESP32 S3 Uno?

It is not a good idea. With 5V on VCC, the signal pin can go above 3.3V, and ESP32 S3 Uno pins are not 5V tolerant. Power the sensor from a GPIO pin (3.3V) or the 3.3V pin, so the signal stays in a safe range.

Why do I get values up to 4095 instead of 1023?

The ESP32 S3 Uno has a 12-bit ADC, so analogRead() returns 0 to 4095. The UNO R4 uses 10-bit by default and returns 0 to 1023. If you copy thresholds from an UNO R4 project, multiply them by about 4, then check them again with your own water.

Why does the code call analogSetAttenuation(ADC_11db)?

It sets the ESP32 S3 Uno ADC input range to about 0V to 3.3V, so the full water sensor signal maps to 0 to 4095. Without it the range is smaller, and the readings saturate early. The UNO R4 does not need this line, because it is ESP32-only. Keep the signal voltage at 3.3V or less.

Why is the sensor powered from a digital pin?

A water sensor that has power all the time slowly corrodes in a wet place. Powering it from D5 (GPIO20) only during a reading keeps the copper traces in good shape much longer. The sensor draws very little current, so a GPIO pin can power it.

Can the water sensor measure the exact water depth?

Not very well. The reading depends on the water's minerals, the temperature and the supply voltage, and it is not linear. It is great for leak, rain and overflow alarms, and for a rough level in a few steps. For exact depth, use an ultrasonic or pressure sensor.

What if my project needs more pins than the Uno headers give?

The ESP32 S3 Uno has two extra rows of holes with more GPIOs: GPIO15 and GPIO16 (I/O, PWM, analog), GPIO45 and GPIO35 to GPIO42 (I/O, PWM), and GPIO47/GPIO48 (output only, PWM). Solder pin headers to use them and write the GPIO number in your code. For a second water sensor, use GPIO15 or GPIO16 for the signal pin and GPIO35 to GPIO42 for its power pin. Do not use GPIO47/GPIO48 as sensor inputs. Two warnings: (1) GPIO47/GPIO48 may run at 1.8V instead of 3.3V on some modules (especially ones with "V" in the name, like R8V or R16V), so 3.3V devices can be damaged or act strangely. (2) GPIO0, GPIO3 (D6), GPIO45 and GPIO46 (D9) are boot strapping pins, and a wrong connection can stop the board from booting or uploading code. Use the other extra pins first.

Troubleshooting

Problem Possible Cause Solution
No COM port shows up in Arduino IDE USB driver missing or board not in upload mode Install the CP210x or CH340 driver, use a data USB cable, or hold BOOT while you press RESET
Value is always 0 even in water No power on the sensor or loose wire Check that + goes to D5 (GPIO20), S goes to A0 (GPIO2) and GND is connected
Value stays at 4095 Attenuation not set, or sensor powered from 5V Keep analogSetAttenuation(ADC_11db) in setup() and power the sensor from D5 or 3.3V
Values are much lower than expected Very pure or distilled water conducts poorly Calibrate with the real water you will watch, and lower the threshold
Leak message never appears Threshold too high for your water Run the first sketch, read the wet value and set THRESHOLD below it
Readings drift over weeks Copper traces corrode Power the sensor only when reading, and replace the sensor when the traces look dark
Board does not boot or upload with the demo wired LED on D6 (GPIO3) or buzzer on D9 (GPIO46) pulls a boot strapping pin Wire them to GND, or unplug them while uploading
Serial Monitor shows strange characters Baud rate does not match the code Use 9600 baud for the first three sketches and 115200 baud for the demo

※ OUR MESSAGES

  • As freelancers, We are AVAILABLE for HIRE. See how to outsource your project to us
  • Please feel free to share the link of this tutorial. However, Please do not use our content on any other websites. We invested a lot of effort and time to create the content, please respect our work!