Smart Plant Watering System using Raspberry pi pico using Wokwi Simulator

Smart Plant Watering System | Raspberry Pi Pico + Soil Moisture Sensor – Wokwi
🌱 IoT Automation 🌾 Agriculture 🍓 Raspberry Pi Pico 🟢 Wokwi

Smart Plant Watering System
with Soil Moisture Sensor

Build an intelligent irrigation system using a Raspberry Pi Pico, analog soil moisture sensor, relay, 16×2 LCD, and LED indicators. The system monitors moisture continuously and automatically waters plants when the soil gets too dry — with manual override and cooldown protection.

~20 min read 12 Components MicroPython Auto + Manual Mode
💧 Live Soil Moisture Simulation
35%
🔴 DRY — Pump Activating...
✨ Overview

Key Features

📡
Real-time MonitoringAnalog ADC reads moisture every 2 seconds
💧
Auto WateringPump triggers when soil drops below threshold
🔘
Manual OverridePush button for on-demand irrigation
📟
LCD Status Display16×2 I2C shows moisture % and status
🚥
LED IndicatorsRed (Dry), Green (Wet), Blue (Pump On)
Relay Pump Control5V relay switches DC water pump power
🔢
Watering CounterTracks total irrigation cycles run
⏱️
Cooldown Protection10-second lockout prevents over-watering
5-Second Pump LimitAuto shutoff after each watering cycle
📊
Serial LoggingDetailed console output for debugging
🎛️
Adjustable ThresholdsCustomize for different plant types
💡
Built-in LED HeartbeatGP25 blinks to confirm system running
LED Status Summary
Red LED — GP15Soil is dry — watering needed
Green LED — GP16Soil is adequately wet
Blue LED — GP17Water pump is running
0Requirements

Components Required

🍓
Raspberry Pi Pico
RP2040, MicroPython
🌱
Soil Moisture Sensor
Capacitive or Resistive, Analog
💧
DC Water Pump
3–12V, simulated as DC motor
Relay Module
5V, switches pump power
📟
16×2 LCD (I2C)
Address 0x27, VBUS power
🔴
Red LED
Dry soil indicator
🟢
Green LED
Wet soil indicator
🔵
Blue LED
Pump running indicator
🔘
Push Button
Manual watering trigger
〰️
3× 220Ω Resistors
LED current limiting
🔌
Breadboard + Wires
Prototyping setup
🐍
MicroPython Firmware
Flashed on Pico

⚠️ In Wokwi, a potentiometer simulates the soil moisture sensor — turn the dial to change the moisture reading. The DC motor simulates the water pump.

1Wiring

Circuit Connections

📟 LCD 16×2 (I2C)
SDAGP0
SCLGP1
VCCVBUS (5V)
GNDGND.8
🌱 Soil Moisture Sensor (ADC)
VCC3.3V (Pin 36)
GNDGND.3
AOUTGP26 (ADC0)
Wokwi: use potentiometer
⚡ Relay Module
VCCVBUS (5V)
GNDGND.4
IN (Signal)GP14
COMMotor A+
NOVBUS (5V)
💧 Water Pump (DC Motor)
Positive (+)Relay COM
Negative (−)GND.5
Relay switches pump power on/off
🚥 LED Indicators (220Ω each)
Red (Dry)GP15 → R1 → A
Green (Wet)GP16 → R2 → A
Blue (Pump)GP17 → R3 → A
All cathodesGND.1
🔘 Manual Button
Terminal 1GP18
Terminal 23.3V
PULL_DOWN — active HIGH when pressed
💡 Logic

Moisture States & Thresholds

🔴
DRY
ADC > 45000
< 30% moisture
🚰 Auto-water triggered after cooldown
🟡
MODERATE
30000–45000
30–70% moisture
⚪ Monitor only, no LEDs lit
🟢
WET
ADC < 30000
> 70% moisture
✅ Adequate moisture, no action needed

The ADC reads a 16-bit value (0–65535). Since lower ADC = wetter soil (capacitive sensor), the code inverts this to a percentage: moisture% = 100 - (raw / 65535 × 100)

2Wokwi Config

diagram.json

Paste this into Wokwi's diagram.json tab. Turn the potentiometer to simulate different soil moisture levels.

diagram.json
{
  "version": 1,
  "author": "Smart Plant Watering System",
  "editor": "wokwi",
  "parts": [
    {
      "type": "wokwi-pi-pico", "id": "pico",
      "top": 0, "left": 0, "attrs": {}
    },
    {
      "type": "wokwi-potentiometer", "id": "pot1",
      "top": -80, "left": 300,
      "attrs": { "label": "Soil Moisture" }
    },
    {
      "type": "wokwi-relay-module", "id": "relay1",
      "top": 120, "left": 350, "attrs": {}
    },
    {
      "type": "wokwi-lcd1602", "id": "lcd1",
      "top": 200, "left": 300,
      "attrs": { "pins": "i2c" }
    },
    {
      "type": "wokwi-dc-motor", "id": "motor1",
      "top": 100, "left": 550,
      "attrs": { "label": "Water Pump" }
    },
    {
      "type": "wokwi-led", "id": "led1",
      "top": -120, "left": -150,
      "attrs": { "color": "red", "label": "DRY SOIL" }
    },
    {
      "type": "wokwi-led", "id": "led2",
      "top": -70, "left": -150,
      "attrs": { "color": "green", "label": "WET SOIL" }
    },
    {
      "type": "wokwi-led", "id": "led3",
      "top": -20, "left": -150,
      "attrs": { "color": "blue", "label": "PUMP ON" }
    },
    {
      "type": "wokwi-pushbutton", "id": "btn1",
      "top": 50, "left": -150,
      "attrs": { "color": "green", "label": "MANUAL" }
    },
    {
      "type": "wokwi-resistor", "id": "r1",
      "top": -110, "left": -230,
      "attrs": { "value": "220" }
    },
    {
      "type": "wokwi-resistor", "id": "r2",
      "top": -60, "left": -230,
      "attrs": { "value": "220" }
    },
    {
      "type": "wokwi-resistor", "id": "r3",
      "top": -10, "left": -230,
      "attrs": { "value": "220" }
    }
  ],
  "connections": [
    [ "pico:GP0",    "lcd1:SDA",      "green",   [ "v0" ] ],
    [ "pico:GP1",    "lcd1:SCL",      "blue",    [ "v0" ] ],
    [ "lcd1:VCC",    "pico:VBUS",     "red",     [ "v0" ] ],
    [ "lcd1:GND",    "pico:GND.8",    "black",   [ "v0" ] ],
    [ "pot1:VCC",    "pico:3V3",      "red",     [ "v0" ] ],
    [ "pot1:GND",    "pico:GND.3",    "black",   [ "v0" ] ],
    [ "pot1:SIG",    "pico:GP26",     "orange",  [ "v0" ] ],
    [ "relay1:VCC",  "pico:VBUS",     "red",     [ "v0" ] ],
    [ "relay1:GND",  "pico:GND.4",    "black",   [ "v0" ] ],
    [ "relay1:IN",   "pico:GP14",     "purple",  [ "v0" ] ],
    [ "relay1:COM",  "motor1:A+",     "brown",   [ "v0" ] ],
    [ "relay1:NO",   "pico:VBUS",     "red",     [ "v0" ] ],
    [ "motor1:A-",   "pico:GND.5",    "black",   [ "v0" ] ],
    [ "r1:1",        "pico:GP15",     "red",     [ "v0" ] ],
    [ "r1:2",        "led1:A",        "",        [ "v0" ] ],
    [ "led1:C",      "pico:GND.1",    "black",   [ "v0" ] ],
    [ "r2:1",        "pico:GP16",     "green",   [ "v0" ] ],
    [ "r2:2",        "led2:A",        "",        [ "v0" ] ],
    [ "led2:C",      "pico:GND.1",    "black",   [ "v0" ] ],
    [ "r3:1",        "pico:GP17",     "blue",    [ "v0" ] ],
    [ "r3:2",        "led3:A",        "",        [ "v0" ] ],
    [ "led3:C",      "pico:GND.1",    "black",   [ "v0" ] ],
    [ "btn1:1.l",    "pico:GP18",     "green",   [ "v0" ] ],
    [ "btn1:1.r",    "pico:3V3",      "red",     [ "v0" ] ]
  ],
  "dependencies": {}
}
3Code

MicroPython Code (main.py)

Save as main.py on your Pico, or paste directly into Wokwi's editor.

main.py — MicroPython
"""
Smart Plant Watering System
Raspberry Pi Pico + Soil Moisture Sensor
Wokwi Simulator Compatible
"""

from machine import Pin, ADC, I2C
import time

# ── PIN CONFIGURATION ─────────────────────────────────────────
MOISTURE_PIN  = 26   # ADC0 - Analog soil moisture (potentiometer in Wokwi)
RELAY_PIN     = 14   # Relay signal → water pump
LED_DRY       = 15   # Red LED
LED_WET       = 16   # Green LED
LED_PUMP      = 17   # Blue LED
BUTTON_PIN    = 18   # Manual watering button
LED_STATUS    = 25   # Built-in Pico LED (heartbeat)

# ── THRESHOLDS (16-bit ADC: 0–65535, lower = wetter) ──────────
DRY_THRESHOLD  = 30000  # Above WET_THRESHOLD = DRY
WET_THRESHOLD  = 45000  # Below DRY_THRESHOLD = WET
PUMP_DURATION  = 5      # Pump runs 5 seconds per cycle
CHECK_INTERVAL = 2      # Read sensor every 2 seconds
COOLDOWN_TIME  = 10     # No re-watering within 10 seconds

# ── HARDWARE INIT ─────────────────────────────────────────────
moisture_sensor = ADC(Pin(MOISTURE_PIN))
relay      = Pin(RELAY_PIN, Pin.OUT);  relay.off()
led_dry    = Pin(LED_DRY,  Pin.OUT)
led_wet    = Pin(LED_WET,  Pin.OUT)
led_pump   = Pin(LED_PUMP, Pin.OUT)
led_status = Pin(LED_STATUS, Pin.OUT)
button     = Pin(BUTTON_PIN, Pin.IN, Pin.PULL_DOWN)
i2c        = I2C(0, scl=Pin(1), sda=Pin(0), freq=400000)

# ── LCD I2C CLASS (16×2, PCF8574 backpack) ───────────────────
class LCD_I2C:
    def __init__(self, i2c, addr=0x27, rows=2, cols=16):
        self.i2c = i2c;  self.addr = addr
        self.LCD_CLEARDISPLAY  = 0x01;  self.LCD_RETURNHOME    = 0x02
        self.LCD_ENTRYMODESET  = 0x04;  self.LCD_DISPLAYCONTROL= 0x08
        self.LCD_FUNCTIONSET   = 0x20;  self.LCD_SETDDRAMADDR  = 0x80
        self.LCD_DISPLAYON     = 0x04;  self.LCD_CURSOROFF     = 0x00
        self.LCD_BLINKOFF      = 0x00;  self.LCD_ENTRYLEFT     = 0x02
        self.LCD_4BITMODE      = 0x00;  self.LCD_2LINE         = 0x08
        self.LCD_5x8DOTS       = 0x00
        self.LCD_BACKLIGHT     = 0x08;  self.LCD_NOBACKLIGHT   = 0x00
        self.backlight_state   = self.LCD_BACKLIGHT
        self.init_display()

    def write_byte(self, byte, mode):
        hi = mode | (byte & 0xF0)       | self.backlight_state
        lo = mode | ((byte << 4) & 0xF0) | self.backlight_state
        self.i2c.writeto(self.addr, bytearray([hi]));  self.toggle_enable(hi)
        self.i2c.writeto(self.addr, bytearray([lo]));  self.toggle_enable(lo)

    def toggle_enable(self, byte):
        time.sleep_us(1)
        self.i2c.writeto(self.addr, bytearray([byte | 0x04]))
        time.sleep_us(1)
        self.i2c.writeto(self.addr, bytearray([byte & ~0x04]))
        time.sleep_us(50)

    def init_display(self):
        time.sleep_ms(50)
        for _ in range(3): self.write_byte(0x03, 0);  time.sleep_ms(5)
        self.write_byte(0x02, 0)
        self.write_byte(self.LCD_FUNCTIONSET | self.LCD_4BITMODE |
                        self.LCD_2LINE | self.LCD_5x8DOTS, 0)
        self.write_byte(self.LCD_DISPLAYCONTROL | self.LCD_DISPLAYON |
                        self.LCD_CURSOROFF | self.LCD_BLINKOFF, 0)
        self.clear()
        self.write_byte(self.LCD_ENTRYMODESET | self.LCD_ENTRYLEFT, 0)

    def clear(self):
        self.write_byte(self.LCD_CLEARDISPLAY, 0);  time.sleep_ms(2)

    def set_cursor(self, row, col):
        self.write_byte(self.LCD_SETDDRAMADDR | (col + [0x00, 0x40][row]), 0)

    def print(self, text, row=0, col=0):
        self.set_cursor(row, col)
        for c in str(text): self.write_byte(ord(c), 1)

# Init LCD (graceful fallback if not found)
try:
    lcd = LCD_I2C(i2c);  lcd_available = True
    print("LCD initialized")
except:
    lcd_available = False
    print("LCD not found — serial monitor only")

# ── GLOBAL STATE ──────────────────────────────────────────────
watering_count    = 0
last_watering_time = 0

# ── SENSOR & LOGIC FUNCTIONS ──────────────────────────────────
def read_moisture():
    raw = moisture_sensor.read_u16()
    pct = 100 - int((raw / 65535) * 100)  # Invert: low ADC = wet
    return raw, pct

def get_soil_status(raw):
    if   raw < DRY_THRESHOLD:  return "WET"
    elif raw > WET_THRESHOLD:  return "DRY"
    else:                      return "MODERATE"

def update_leds(status):
    led_dry.value(status == "DRY")
    led_wet.value(status == "WET")

def water_plants(manual=False):
    global watering_count, last_watering_time
    watering_count += 1
    trigger = "MANUAL" if manual else "AUTO"

    print(f"\n{'='*40}\n🌱 WATERING ({trigger}) — Cycle #{watering_count}\n{'='*40}")

    if lcd_available:
        lcd.clear()
        lcd.print("  WATERING!  ", 0, 0)
        lcd.print(f"Cycle #{watering_count}", 1, 2)

    relay.on();  led_pump.on()

    for remaining in range(PUMP_DURATION, 0, -1):
        print(f"Watering... {remaining}s remaining")
        led_status.on();  time.sleep(0.5)
        led_status.off(); time.sleep(0.5)

    relay.off();  led_pump.off()
    last_watering_time = time.time()
    print(f"✓ Done! Total waterings: {watering_count}\n")

def display_lcd(pct, status):
    if not lcd_available: return
    lcd.clear()
    lcd.print(f"Moisture: {pct}%", 0, 0)
    lcd.print(f"Status: {status}", 1, 0)

def startup_sequence():
    print("\n" + "="*50)
    print("  SMART PLANT WATERING SYSTEM")
    print("  Raspberry Pi Pico + Moisture Sensor")
    print("="*50)
    print(f"\n  Dry threshold:   {DRY_THRESHOLD}")
    print(f"  Wet threshold:   {WET_THRESHOLD}")
    print(f"  Pump duration:   {PUMP_DURATION}s")
    print(f"  Check interval:  {CHECK_INTERVAL}s")
    print(f"  Cooldown:        {COOLDOWN_TIME}s\n")

    if lcd_available:
        lcd.clear();  lcd.print("Plant Watering", 0, 1)
        lcd.print("Starting...", 1, 2)

    # Flash all LEDs to confirm hardware
    for pin in [led_dry, led_wet, led_pump]:
        pin.on()
    time.sleep(0.5)
    for pin in [led_dry, led_wet, led_pump]:
        pin.off()

    relay.on();  time.sleep(0.2);  relay.off()  # Brief pump test
    time.sleep(0.5)
    print("System ready! Monitoring soil moisture...\n" + "="*50 + "\n")

    if lcd_available:
        lcd.clear();  lcd.print("System Ready!", 0, 2)
        lcd.print("Monitoring...", 1, 1)

# ── MAIN LOOP ─────────────────────────────────────────────────
def main():
    global last_watering_time
    startup_sequence()

    for pin in [relay, led_dry, led_wet, led_pump, led_status]:
        pin.off()

    last_check_time  = 0
    last_button_state = 0
    last_button_time  = 0

    while True:
        try:
            now = time.time()

            # Manual button (debounced, active HIGH)
            btn = button.value()
            if btn == 1 and last_button_state == 0:
                if now - last_button_time > 0.5:
                    print("\n🔘 MANUAL WATERING TRIGGERED")
                    water_plants(manual=True)
                    last_button_time = now
                    time.sleep(COOLDOWN_TIME)
            last_button_state = btn

            # Periodic moisture check
            if now - last_check_time >= CHECK_INTERVAL:
                raw, pct = read_moisture()
                status = get_soil_status(raw)

                update_leds(status)
                display_lcd(pct, status)

                print(f"──── Moisture: {pct}% | Raw: {raw} | Status: {status}")
                if status == "DRY":
                    print("  ⚠️  Soil too dry!")
                    if now - last_watering_time > COOLDOWN_TIME:
                        water_plants(manual=False)
                        time.sleep(COOLDOWN_TIME)
                elif status == "WET":
                    print("  ✓  Soil moisture is good")
                else:
                    print("  ℹ️  Moderate moisture")
                print(f"  Total waterings: {watering_count}")

                last_check_time = now

            # Heartbeat blink on built-in LED
            led_status.value(int(now * 2) % 2 == 0)
            time.sleep(0.1)

        except KeyboardInterrupt:
            print("\n\nSystem stopped.")
            for p in [relay, led_dry, led_wet, led_pump, led_status]:
                p.off()
            if lcd_available:
                lcd.clear();  lcd.print("System Stopped", 0, 0)
            break
        except Exception as e:
            print(f"Error: {e}");  relay.off();  time.sleep(1)

if __name__ == "__main__":
    main()
📚 Skills

What You'll Learn

ADC analog sensor reading
Sensor calibration & thresholds
Relay control for high-power loads
I2C LCD communication
Event-driven programming
State machine (Dry/Mod/Wet)
Button debouncing (PULL_DOWN)
Timer & cooldown management
ADC to percentage conversion
Automatic control systems
Threshold-based decision logic
Agricultural IoT principles
MicroPython error handling
Wokwi simulation workflow
🌍 Use Cases

Real-World Applications

🏠 Home Garden Automation
🌿 Greenhouse Management
🏙️ Vertical Farming
🏢 Office Plant Care
🌾 Small-Scale Farm Irrigation
✈️ Vacation Plant Care
🧪 Research Lab Plant Studies
🌱 Nursery Seedling Care
💦 Smart Hydroponics
🏫 STEM School Projects
🌵 Drought-Efficient Irrigation
🪴 Balcony Garden Automation
4Run

Run the Simulation

Paste diagram.json

Copy into Wokwi's diagram tab to place all components.

Paste main.py

Copy the full code into Wokwi's code editor.

Click ▶ Play

The startup sequence runs, LEDs flash, relay clicks.

Turn Potentiometer

Rotate to simulate dry/wet soil. Watch LEDs and LCD update.

Watch Auto-Water

When DRY — relay activates, motor spins, blue LED lights.

Test Manual Button

Click the green MANUAL button to trigger watering instantly.

More Raspberry Pi Pico Projects

🟢 Beginner — Foundation Projects
🔵 Intermediate — Build Your Skills
🔴 Advanced — Master Level

© 2026 MakeMindz · Raspberry Pi Pico & IoT Projects for Students, Makers & STEM Learners

Comments

try for free