Smart Irrigation System
with ESP32
Monitor real soil moisture and automatically activate a water pump when the soil becomes dry — a complete IoT farming system simulated free in Wokwi. No hardware needed to start.
What You'll Build
A fully automatic plant watering system — reads soil moisture every 2 seconds and triggers a relay-controlled water pump when soil dries below your threshold.
Moisture Detection
ESP32's 12-bit ADC reads analog sensor voltage — far more precise than Arduino's 10-bit.
Relay Control
A relay module acts as the high-current switch that safely controls the water pump.
Auto Watering
Pump activates for a fixed time (5 seconds) then stops — preventing overwatering automatically.
LED Indicators
Three LEDs show dry/wet/pump status at a glance — no screen needed for quick checks.
Non-Blocking Code
Uses millis() for all timing — sensor reads and pump control run simultaneously.
IoT-Ready
ESP32's built-in WiFi means this project is one step away from cloud monitoring and alerts.
The Problem with Traditional Irrigation
Fixed-schedule watering ignores real conditions. Soil moisture changes with temperature, sunlight, rainfall, and plant type — a smart system waters only when needed.
Components Required
All available as virtual parts in Wokwi. In Wokwi, the soil sensor is simulated using a potentiometer.
How the Irrigation System Works
Five stages transform a soil reading into an automated watering decision — every 2 seconds, without blocking any other operation.
Soil Moisture Detection
The soil moisture sensor outputs an analog voltage — higher moisture means higher voltage. ESP32 reads this on GPIO 34 using its 12-bit ADC, returning a value from 0 to 4095.
Converting ADC Value → Percentage
The raw ADC reading is mapped to a 0–100% scale using map(soilValue, 0, 4095, 0, 100) — making thresholds intuitive and easy to adjust.
| ADC Value | Moisture % | Condition |
|---|---|---|
| 800 | 19% | DRY |
| 2000 | 48% | MODERATE |
| 3600 | 88% | WET |
Threshold-Based Decision Logic
Two thresholds define three moisture zones. This hysteresis band prevents the pump from rapidly switching ON/OFF near a single threshold value.
Relay & Pump Control
When soil moisture drops below 40%, the code calls startPump() — setting GPIO 25 HIGH to energize the relay, which closes the circuit and powers the water pump for 5 seconds before automatically stopping.
💡 The pump runs for exactly 5 seconds (PUMP_ON_TIME 5000) then stops automatically using millis() — no delay() is used, so the system stays fully responsive throughout.
LED Status Indicators
Red LED
GPIO 26
Soil is DRY
Moisture < 40%
Green LED
GPIO 27
Soil is WET
Moisture > 70%
Blue LED
GPIO 33
Pump is RUNNING
Active for 5 sec
Wiring the Circuit
Paste the diagram.json for instant setup — the full circuit builds automatically in Wokwi.
-
Quick Setup: Paste the diagram.json
- Click the diagram.json tab in the Wokwi editor
- Select all (Ctrl+A) and delete existing content
- Paste the full JSON below, then press Ctrl+S
{ "version": 1, "author": "Smart Irrigation System", "editor": "wokwi", "parts": [ { "type": "wokwi-esp32-devkit-v1", "id": "esp", "top": 0, "left": 0, "attrs": {} }, { "type": "wokwi-potentiometer", "id": "pot1", "top": -60, "left": 250, "attrs": { "label": "Soil Moisture" } }, { "type": "wokwi-relay-module", "id": "relay1", "top": 100, "left": 300, "attrs": {} }, { "type": "wokwi-led", "id": "led1", "top": -80, "left": -100, "attrs": { "color": "red", "label": "DRY" } }, { "type": "wokwi-led", "id": "led2", "top": -40, "left": -100, "attrs": { "color": "green", "label": "WET" } }, { "type": "wokwi-led", "id": "led3", "top": 0, "left": -100, "attrs": { "color": "blue", "label": "PUMP" } }, { "type": "wokwi-resistor", "id": "r1", "top": -70, "left": -160, "attrs": { "value": "220" } }, { "type": "wokwi-resistor", "id": "r2", "top": -30, "left": -160, "attrs": { "value": "220" } }, { "type": "wokwi-resistor", "id": "r3", "top": 10, "left": -160, "attrs": { "value": "220" } }, { "type": "wokwi-dc-motor", "id": "motor1", "top": 200, "left": 450, "attrs": { "label": "Water Pump" } } ], "connections": [ [ "esp:TX0", "$serialMonitor:RX", "", [] ], [ "esp:RX0", "$serialMonitor:TX", "", [] ], [ "pot1:VCC", "esp:3V3", "red", ["v0"] ], [ "pot1:GND", "esp:GND.1", "black", ["v0"] ], [ "pot1:SIG", "esp:34", "green", ["v0"] ], [ "relay1:VCC", "esp:3V3", "red", ["v0"] ], [ "relay1:GND", "esp:GND.2", "black", ["v0"] ], [ "relay1:IN", "esp:25", "orange", ["v0"] ], [ "r1:1", "esp:26", "red", ["v0"] ], [ "r1:2", "led1:A", "", ["v0"] ], [ "led1:C", "esp:GND.1", "black", ["v0"] ], [ "r2:1", "esp:27", "green", ["v0"] ], [ "r2:2", "led2:A", "", ["v0"] ], [ "led2:C", "esp:GND.1", "black", ["v0"] ], [ "r3:1", "esp:33", "blue", ["v0"] ], [ "r3:2", "led3:A", "", ["v0"] ], [ "led3:C", "esp:GND.1", "black", ["v0"] ], [ "relay1:COM", "motor1:A+", "purple", ["v0"] ], [ "relay1:NO", "esp:VIN", "red", ["v0"] ], [ "motor1:A-", "esp:GND.2", "black", ["v0"] ] ], "dependencies": {} }⚠️ Copy the entire JSON including the outer { } braces, or the circuit won't load correctly.
-
Pin Connection Reference
Component ESP32 Pin Purpose Soil sensor (pot SIG) GPIO 34 Analog input (ADC) Relay module IN GPIO 25 Pump switch control Red LED (DRY) GPIO 26 via 220Ω resistor Green LED (WET) GPIO 27 via 220Ω resistor Blue LED (PUMP) GPIO 33 via 220Ω resistor Sensor & Relay VCC 3V3 Power supply Motor (pump) via relay VIN → relay COM/NO High current path 💡 GPIO 34 is input-only on the ESP32 — it has no internal pull-up/down and cannot be used as OUTPUT. It's perfect for reading analog sensors.
ESP32 Code — Fully Explained
Paste this into smart_irrigation.ino in the Wokwi editor. Every section is commented clearly.
/* * Smart Irrigation System with Soil Moisture Sensor * ESP32-based automatic plant watering system * Features: Non-blocking millis() timing, threshold hysteresis, * 3-LED status display, relay pump control */ // ── Pin Definitions ────────────────────────────────────── #define SOIL_MOISTURE_PIN 34 // Analog input (ADC) — input-only pin #define RELAY_PIN 25 // Controls the relay module (water pump) #define LED_DRY 26 // Red LED — soil is dry #define LED_WET 27 // Green LED — soil is wet #define LED_PUMP 33 // Blue LED — pump is active // ── Threshold values (0–100 scale after mapping) ───────── #define DRY_THRESHOLD 40 // Below this → start watering #define WET_THRESHOLD 70 // Above this → soil is wet enough // ── Timing constants ────────────────────────────────────── #define PUMP_ON_TIME 5000 // Pump runs for 5 seconds per cycle #define READ_INTERVAL 2000 // Read sensor every 2 seconds // ── State variables ─────────────────────────────────────── int soilMoistureValue = 0; int soilMoisturePercent = 0; bool pumpActive = false; unsigned long lastReadTime = 0; unsigned long pumpStartTime = 0; void setup() { Serial.begin(115200); Serial.println("Smart Irrigation System Starting..."); pinMode(RELAY_PIN, OUTPUT); pinMode(LED_DRY, OUTPUT); pinMode(LED_WET, OUTPUT); pinMode(LED_PUMP, OUTPUT); // Ensure everything starts off digitalWrite(RELAY_PIN, LOW); digitalWrite(LED_DRY, LOW); digitalWrite(LED_WET, LOW); digitalWrite(LED_PUMP, LOW); Serial.println("System Ready! Monitoring soil moisture..."); Serial.println("-------------------------------"); } void loop() { unsigned long currentTime = millis(); // ── Non-blocking sensor read every 2 seconds ────────── if (currentTime - lastReadTime >= READ_INTERVAL) { readSoilMoisture(); lastReadTime = currentTime; } // ── Start pump if soil is dry and pump not already on ── if (!pumpActive && soilMoisturePercent < DRY_THRESHOLD) { startPump(); } // ── Auto-stop pump after PUMP_ON_TIME milliseconds ───── if (pumpActive && (currentTime - pumpStartTime >= PUMP_ON_TIME)) { stopPump(); } // ── Update LED status indicators ─────────────────────── updateLEDIndicators(); } void readSoilMoisture() { soilMoistureValue = analogRead(SOIL_MOISTURE_PIN); soilMoisturePercent = map(soilMoistureValue, 0, 4095, 0, 100); Serial.print("Soil Moisture: "); Serial.print(soilMoisturePercent); Serial.print("% (Raw: "); Serial.print(soilMoistureValue); Serial.print(") Status: "); if (soilMoisturePercent < DRY_THRESHOLD) Serial.println("DRY - Needs watering!"); else if (soilMoisturePercent > WET_THRESHOLD) Serial.println("WET - No watering needed"); else Serial.println("MODERATE - Monitoring"); } void startPump() { if (!pumpActive) { pumpActive = true; pumpStartTime = millis(); digitalWrite(RELAY_PIN, HIGH); digitalWrite(LED_PUMP, HIGH); Serial.println(">>> PUMP STARTED - Watering plants..."); } } void stopPump() { if (pumpActive) { pumpActive = false; digitalWrite(RELAY_PIN, LOW); digitalWrite(LED_PUMP, LOW); Serial.println(">>> PUMP STOPPED - Watering complete"); Serial.println("-------------------------------"); } } void updateLEDIndicators() { if (soilMoisturePercent < DRY_THRESHOLD) { digitalWrite(LED_DRY, HIGH); digitalWrite(LED_WET, LOW); } else if (soilMoisturePercent > WET_THRESHOLD) { digitalWrite(LED_DRY, LOW); digitalWrite(LED_WET, HIGH); } else { digitalWrite(LED_DRY, LOW); // Moderate — both off digitalWrite(LED_WET, LOW); } }
Serial Monitor Output
Open the Serial Monitor in Wokwi (115200 baud) to see live readings:
ℹ️ The ESP32 Serial Monitor baud rate is 115200 — make sure you select this in the Wokwi Serial Monitor tab, otherwise output appears garbled.
Testing in Wokwi
Adjust the soil moisture potentiometer and watch all outputs respond in real time.
-
Paste Code and Play
Paste
smart_irrigation.inointo the code editor and paste thediagram.jsoninto the diagram tab. Click ▶ Play — the circuit auto-builds and starts immediately. -
Adjust the Soil Moisture Potentiometer
Click on the "Soil Moisture" potentiometer in Wokwi — a dial appears. Try these scenarios:
- Dial all the way left → very dry (≈10%), Red LED ON, pump starts after 2 sec
- Dial to middle → moderate (≈50%), all LEDs off, pump silent
- Dial all the way right → very wet (≈90%), Green LED ON, pump stays off
💡 Watch the Blue (PUMP) LED turn on exactly when soil drops below 40% — and turn off automatically after 5 seconds, even while the loop keeps reading the sensor.
-
Monitor the DC Motor (Water Pump)
Find the "Water Pump" DC motor component in Wokwi. When the relay activates, the motor spins — simulating a real pump pushing water to your plants. You'll see it start and stop exactly on schedule.
-
Try Adjusting the Thresholds
In the code, change
#define DRY_THRESHOLD 40to60— now the pump activates much sooner. Lower it to20and only very dry soil triggers watering. This threshold tuning is how you calibrate the system for different plants.⚠️ Avoid setting DRY_THRESHOLD equal to WET_THRESHOLD — this removes the hysteresis band and causes rapid pump on/off cycling.
ESP32 vs Arduino UNO
ESP32 isn't just faster — it fundamentally enables IoT features that make this project cloud-ready from day one.
💡 The 12-bit ADC (0–4095) vs Arduino's 10-bit (0–1023) gives 4× the precision for reading moisture levels — critical for accurate threshold detection in real farming conditions.
Extension Challenges
Mastered the base project? Extend it into a full IoT farming system.
- Add WiFi — post soil data to a ThingSpeak or Firebase dashboard every minute
- Send Telegram bot alerts when soil drops below threshold or pump activates
- Add a DHT22 sensor to factor temperature & humidity into watering decisions
- Add an OLED display showing moisture %, pump status, and last watered time
- Track total pump runtime to estimate total water consumed per day
- Use deep sleep between readings to run the system on solar + battery power
- Add a rain sensor — skip watering if it has rained in the last 6 hours
- Control multiple zones with multiple relays for different plant types
Common Questions
Quick answers to the most frequently asked questions about this project.
Can ESP32 automatically water plants without any manual input?
Yes — that's exactly what this project does. By reading the soil moisture sensor and controlling the relay, ESP32 fully automates the irrigation cycle. Once programmed, no human intervention is needed during normal operation.
What is the ideal moisture threshold for most plants?
Most common houseplants and vegetables prefer 40–70% soil moisture. Succulents and cacti prefer 20–40%. Tropical plants may need 60–80%. The DRY_THRESHOLD and WET_THRESHOLD constants in the code make it easy to calibrate for your specific plants.
Can I power this system with solar energy?
Yes — the ESP32 supports deep sleep mode, which can reduce power consumption to a few µA between readings. Combined with a small solar panel (5–10W) and a LiPo battery, you can run this system completely off-grid — ideal for garden or field deployment.
Does Wokwi support pump (motor) simulation?
Yes. Wokwi includes a DC motor component that accurately simulates the start/stop behavior of a water pump when the relay switches. You'll see the motor spin when the pump is active.
Why use millis() instead of delay()?
Using delay() completely freezes the microcontroller — no sensor reads, no LED updates, no Serial output during that time. millis() lets the loop run continuously, checking elapsed time non-blockingly. This means the pump timer and sensor timer work simultaneously without interfering with each other.
Quick Quiz
Test your understanding before moving to the next project.
Q1. What range of values does the ESP32's ADC return when reading an analog sensor?
Q2. Why are two thresholds (DRY 40% and WET 70%) used instead of just one?
Q3. Why does this code use millis() instead of delay() for the pump timer?
Q4. What does the relay module do in this circuit?
Q5. Which LED lights up when the soil moisture is above 70%?
More ESP32 Projects
Continue building with more ESP32 IoT projects from MakeMindz.
Comments
Post a Comment