🔗Goal

Build a system that monitors soil moisture and alerts you when your plant needs watering. When the soil gets too dry, an LED lights up and a buzzer beeps to remind you to water.
Here is how the system works at a high level:
graph LR
A[Soil Moisture Sensor] -->|Analog signal| B[ESP32]
B -->|GPIO| C[LED]
B -->|GPIO| D[Buzzer]
B -->|Serial| E[Monitor]The capacitive soil moisture sensor outputs an analog voltage that decreases as the soil dries out. The ESP32 reads this value, compares it to a threshold, and activates the LED and buzzer when watering is needed.
The chart below shows how soil moisture drops over time. Once the reading falls below the threshold, the alert triggers:

🔗Prerequisites

You will need the following components:
| Component | Qty | Notes | Buy |
|---|---|---|---|
| ESP32 dev board | 1 | Any ESP32 board with ADC pins | AliExpress | Amazon.de .co.uk .com |
| Capacitive soil moisture sensor v1.2 | 1 | Prefer capacitive over resistive (lasts longer) | AliExpress | Amazon.de .co.uk .com |
| LED (red) | 1 | Any standard 5mm LED | AliExpress | Amazon.de .co.uk .com |
| 220 ohm resistor | 1 | Current-limiting resistor for the LED | AliExpress | Amazon.de .co.uk .com |
| Active buzzer | 1 | 3.3V compatible | AliExpress | Amazon.de .co.uk .com |
| Breadboard | 1 | AliExpress | Amazon.de .co.uk .com | |
| Jumper wires | ~8 | Male-to-male and male-to-female | AliExpress | Amazon.de .co.uk .com |
Links marked Amazon/AliExpress are affiliate links. We may earn a small commission at no extra cost to you.
The current through the LED is limited by a resistor. For a red LED with a typical forward voltage of $V_f = 2.0\,\text{V}$, the resistor value is calculated using Ohm's law:
$$R = \frac{V_{supply} - V_f}{I} = \frac{3.3 - 2.0}{0.005} = 260\,\Omega$$
A 220 ohm resistor works fine here — the LED will draw about 6 mA, which is bright enough and well within the ESP32 GPIO limit of 12 mA.
🔗Tutorial

🔗Step 1: Wiring
Connect everything to the ESP32 as shown in the table below:
| Component | Pin | ESP32 Pin |
|---|---|---|
| Moisture sensor | VCC | 3.3V |
| Moisture sensor | GND | GND |
| Moisture sensor | AOUT | GPIO 34 |
| LED (anode, long leg) | — | GPIO 25 (through 220 ohm resistor) |
| LED (cathode, short leg) | — | GND |
| Buzzer (+) | — | GPIO 26 |
| Buzzer (−) | — | GND |
GPIO 34 is an input-only pin with ADC support, which makes it a good choice for reading analog sensors.
🔗Step 2: Determine your threshold
The sensor outputs a value between 0 and 4095 (ESP32 has a 12-bit ADC). The mapping depends on your specific sensor, but as a general guide:
| Sensor reading | Soil condition |
|---|---|
| 3000–4095 | Dry (needs water) |
| 1500–3000 | Moist |
| 0–1500 | Wet / submerged |
These values vary between sensors. Use the serial monitor (Step 4) to find the right threshold for your sensor and plant.
The decision logic follows this flow:
graph TD
A[Read sensor] --> B{Reading > threshold?}
B -->|Yes: soil is dry| C[Turn on LED]
C --> D[Beep buzzer]
D --> E[Wait 1 second]
B -->|No: soil is moist| F[Turn off LED]
F --> G[Silence buzzer]
G --> E
E --> A🔗Step 3: Upload the code
#define SENSOR_PIN 34
#define LED_PIN 25
#define BUZZER_PIN 26
// Threshold: readings above this mean the soil is dry.
// Adjust based on your sensor calibration.
#define DRY_THRESHOLD 3000
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
pinMode(BUZZER_PIN, OUTPUT);
}
void loop() {
int moisture = analogRead(SENSOR_PIN);
Serial.print("Soil moisture: ");
Serial.println(moisture);
if (moisture > DRY_THRESHOLD) {
// Soil is dry — alert!
digitalWrite(LED_PIN, HIGH);
digitalWrite(BUZZER_PIN, HIGH);
delay(500);
digitalWrite(BUZZER_PIN, LOW);
delay(500);
} else {
// Soil is moist — all good
digitalWrite(LED_PIN, LOW);
digitalWrite(BUZZER_PIN, LOW);
delay(1000);
}
}🔗Step 4: Calibrate
- Open the Serial Monitor (115200 baud)
- Read the value with the sensor in dry air — note this number
- Read the value with the sensor in a glass of water — note this number
- Place the sensor in your plant's soil and read the value when the soil feels dry to the touch
- Set
DRY_THRESHOLDto that value
The analog reading maps to a voltage via the ESP32's 12-bit ADC:
$$V_{sensor} = \frac{\text{ADC reading}}{4095} \times 3.3\,\text{V}$$
For example, a reading of 3000 corresponds to $V = \frac{3000}{4095} \times 3.3 \approx 2.42\,\text{V}$.
🔗Common Issues and Solutions
| Problem | Cause | Fix |
|---|---|---|
| Reading is always 0 | Sensor not connected to an ADC-capable pin | Use GPIO 32–39 (ADC1). Avoid ADC2 pins (GPIO 0, 2, 4, 12–15, 25–27) when using WiFi |
| Reading fluctuates wildly | Electrical noise or loose connection | Add a 100nF capacitor between sensor VCC and GND. Check wiring |
| Reading doesn't change | Sensor not deep enough in soil | Push sensor in until the line marking on the PCB |
| Buzzer won't stop | Threshold too low | Increase DRY_THRESHOLD based on calibration |
| LED is very dim | Resistor value too high | Use a 220 ohm resistor instead of 1K+ |
| Sensor corrodes quickly | Using a resistive sensor | Switch to a capacitive sensor (no exposed metal contacts) |
Photo credits: Photo by feey on Unsplash. Photo by Bmonster Lab on Pexels. Soil moisture sensor image from Wikimedia Commons (CC0).