Smart Irrigation System with Soil Moisture Sensor using ESP32 DevKit V1 in Wokwi Simulator

Smart Irrigation System with ESP32 & Soil Moisture Sensor — Wokwi | MakeMindz
IoT Project · ESP32 · Intermediate · Wokwi

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.

ESP32 DevKit V1 Soil Moisture Sensor Relay Module + Pump 3 LED Indicators millis() Non-Blocking ~45 min · Intermediate
// 00 — Project Overview

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.


// 01 — Why Smart Irrigation?

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.

💧Saves up to 50% water vs fixed schedules
Reduces electricity usage from pump over-running
🌿Improves plant growth with optimal moisture
🌊Prevents root rot from over-watering
📡Enables remote monitoring via WiFi / cloud
🌾Supports sustainable agricultural practices

// 02 — Parts List

Components Required

All available as virtual parts in Wokwi. In Wokwi, the soil sensor is simulated using a potentiometer.

01
ESP32 DevKit V1Main controller — 12-bit ADC, built-in WiFi, 240 MHz dual-core processor.
02
Soil Moisture SensorAnalog type — outputs voltage proportional to water content in soil.
03
Relay ModuleElectrically isolates high-voltage pump from the ESP32. Controlled by GPIO 25.
04
DC Motor / Water PumpSimulated in Wokwi as a DC motor labeled "Water Pump".
05
3× LEDs (Red, Green, Blue)Status indicators: DRY / WET / PUMP RUNNING.
06
3× 220Ω ResistorsCurrent limiters — one per LED.

// 03 — System Logic

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 ValueMoisture %Condition
80019%DRY
200048%MODERATE
360088%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.

Moisture percentage — 0% to 100%
DRY MODERATE WET
0%40%70%100%
🔴 DRY → Pump ONvalue < 40%
🟡 Monitor40% – 70%
🟢 WET → Pump OFFvalue > 70%

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


// 04 — Circuit Setup

Wiring the Circuit

Paste the diagram.json for instant setup — the full circuit builds automatically in Wokwi.

  1. 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
    diagram.json — paste into Wokwi
    {
      "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.

  2. Pin Connection Reference

    ComponentESP32 PinPurpose
    Soil sensor (pot SIG)GPIO 34Analog input (ADC)
    Relay module INGPIO 25Pump switch control
    Red LED (DRY)GPIO 26via 220Ω resistor
    Green LED (WET)GPIO 27via 220Ω resistor
    Blue LED (PUMP)GPIO 33via 220Ω resistor
    Sensor & Relay VCC3V3Power supply
    Motor (pump) via relayVIN → relay COM/NOHigh 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.


// 05 — The Code

ESP32 Code — Fully Explained

Paste this into smart_irrigation.ino in the Wokwi editor. Every section is commented clearly.

smart_irrigation.ino — full code
/*
 * 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:

Smart Irrigation System Starting...
System Ready! Monitoring soil moisture...
-------------------------------
Soil Moisture: 32% (Raw: 1310) Status: DRY - Needs watering!
>>> PUMP STARTED - Watering plants...
Soil Moisture: 48% (Raw: 1966) Status: MODERATE - Monitoring
Soil Moisture: 78% (Raw: 3195) Status: WET - No watering needed
>>> PUMP STOPPED - Watering complete
-------------------------------

ℹ️ The ESP32 Serial Monitor baud rate is 115200 — make sure you select this in the Wokwi Serial Monitor tab, otherwise output appears garbled.


// 06 — Running the Simulation

Testing in Wokwi

Adjust the soil moisture potentiometer and watch all outputs respond in real time.

  1. Paste Code and Play

    Paste smart_irrigation.ino into the code editor and paste the diagram.json into the diagram tab. Click ▶ Play — the circuit auto-builds and starts immediately.

  2. 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.

  3. 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.

  4. Try Adjusting the Thresholds

    In the code, change #define DRY_THRESHOLD 40 to 60 — now the pump activates much sooner. Lower it to 20 and 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.


// 07 — Why ESP32?

ESP32 vs Arduino UNO

ESP32 isn't just faster — it fundamentally enables IoT features that make this project cloud-ready from day one.

WiFi
✅ Built-in
❌ External
ADC Resolution
12-bit
10-bit
Clock Speed
240 MHz
16 MHz
Bluetooth
✅ Yes
❌ No
Analog Pins
18 ADC
6 ADC
SRAM
520 KB
2 KB

💡 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.


// 08 — Next Level

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
// 09 — Frequently Asked Questions

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.


// 10 — Knowledge Check

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%?


// 11 — More Projects

More ESP32 Projects

Continue building with more ESP32 IoT projects from MakeMindz.

Smart Irrigation System · ESP32 + Soil Moisture Sensor + Wokwi

Simulate free at wokwi.com · No hardware needed · MakeMindz

Comments

try for free