Smart Plant Watering System using Raspberry pi pico using Wokwi Simulator

Build an intelligent automatic plant watering system using Raspberry Pi Pico and soil moisture sensor. The system monitors soil moisture levels continuously and automatically waters plants when the soil becomes too dry. Perfect for learning IoT automation and agricultural technology without buying physical components - test everything in Wokwi simulator first!



Key Features

  • Real-time soil moisture monitoring with analog sensor
  • Automatic watering when soil moisture drops below threshold
  • Manual watering button for on-demand irrigation
  • LCD display showing moisture levels and system status
  • Visual LED indicators: Red (Dry), Green (Wet), Blue (Pump Active)
  • Water pump control via relay module
  • Watering counter tracks irrigation cycles
  • Cooldown period prevents over-watering (10-second reset)
  • Adjustable thresholds for different plant types
  • Moisture percentage display (0-100%)
  • Serial monitoring with detailed logging
  • Automatic pump shutoff after 5 seconds

Components Required

  • Raspberry Pi Pico (simulated in Wokwi)
  • Soil Moisture Sensor (Capacitive or Resistive)
  • DC Water Pump (3-12V)
  • Relay Module (5V)
  • 16x2 LCD Display (I2C connection)
  • Red LED (Dry soil indicator)
  • Green LED (Wet soil indicator)
  • Blue LED (Pump running indicator)
  • Push Button (Manual watering)
  • 3x 220Ω Resistors (for LEDs)
  • Breadboard and jumper wires
  • MicroPython firmware on Pico

Circuit Connections

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": {}
}

Soil Moisture Sensor (Analog):

  • VCC → 3.3V (Pin 36)
  • GND → GND
  • AOUT (Analog Output) → GPIO26 (ADC0)
  • Note: In Wokwi, use potentiometer to simulate sensor

Relay Module:

  • VCC → 5V (VBUS Pin 40)
  • GND → GND
  • IN (Signal) → GPIO14
  • COM → Motor positive (+)
  • NO (Normally Open) → 5V (VBUS)

Water Pump (DC Motor):

  • Positive (+) → Relay COM
  • Negative (-) → GND
  • Note: Relay switches power to pump

LCD Display (I2C):

  • SDA → GPIO0 (I2C0 SDA)
  • SCL → GPIO1 (I2C0 SCL)
  • VCC → 5V (VBUS)
  • GND → GND

Red LED (Dry Soil Indicator):

  • Anode (+) → GPIO15 (through 220Ω resistor)
  • Cathode (-) → GND

Green LED (Wet Soil Indicator):

  • Anode (+) → GPIO16 (through 220Ω resistor)
  • Cathode (-) → GND

Blue LED (Pump Running Indicator):

  • Anode (+) → GPIO17 (through 220Ω resistor)
  • Cathode (-) → GND

Push Button (Manual Watering):

  • One terminal → GPIO18
  • Other terminal → 3.3V
  • Internal pull-down resistor enabled in code
Code:
"""
Smart Plant Watering System
Raspberry Pi Pico with Soil Moisture Sensor
Wokwi Simulator Compatible

Components:
- Raspberry Pi Pico
- Soil Moisture Sensor (Analog)
- Water Pump (DC Motor)
- Relay Module
- LCD Display (I2C 16x2)
- LED Indicators (Dry, Wet, Pump)
- Manual Override Button
"""

from machine import Pin, ADC, I2C
import time

# ============ PIN CONFIGURATION ============
MOISTURE_PIN = 26         # ADC0 - Soil moisture sensor (analog)
RELAY_PIN = 14            # Relay control for water pump
LED_DRY = 15              # Red LED - Soil is dry
LED_WET = 16              # Green LED - Soil is wet
LED_PUMP = 17             # Blue LED - Pump running
BUTTON_PIN = 18           # Manual watering button
LED_STATUS = 25           # Built-in LED - Status indicator

# Moisture thresholds (0-65535 for 16-bit ADC)
# Lower value = more moisture
DRY_THRESHOLD = 30000     # Below this = soil is dry
WET_THRESHOLD = 45000     # Above this = soil is wet

# Watering settings
PUMP_DURATION = 5         # Pump runs for 5 seconds
CHECK_INTERVAL = 2        # Check moisture every 2 seconds
COOLDOWN_TIME = 10        # Wait 10 seconds after watering

# ============ HARDWARE INITIALIZATION ============
# Initialize soil moisture sensor (ADC)
moisture_sensor = ADC(Pin(MOISTURE_PIN))

# Initialize relay for pump
relay = Pin(RELAY_PIN, Pin.OUT)
relay.off()  # Pump off initially

# Initialize LED indicators
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)

# Initialize manual button with pull-down
button = Pin(BUTTON_PIN, Pin.IN, Pin.PULL_DOWN)

# Initialize I2C for LCD (GP0=SDA, GP1=SCL)
i2c = I2C(0, scl=Pin(1), sda=Pin(0), freq=400000)

# ============ LCD 16x2 I2C CLASS ============
class LCD_I2C:
    def __init__(self, i2c, addr=0x27, rows=2, cols=16):
        self.i2c = i2c
        self.addr = addr
        self.rows = rows
        self.cols = cols
       
        # LCD Commands
        self.LCD_CLEARDISPLAY = 0x01
        self.LCD_RETURNHOME = 0x02
        self.LCD_ENTRYMODESET = 0x04
        self.LCD_DISPLAYCONTROL = 0x08
        self.LCD_FUNCTIONSET = 0x20
        self.LCD_SETDDRAMADDR = 0x80
       
        # Flags
        self.LCD_DISPLAYON = 0x04
        self.LCD_CURSOROFF = 0x00
        self.LCD_BLINKOFF = 0x00
        self.LCD_ENTRYLEFT = 0x02
        self.LCD_ENTRYSHIFTDECREMENT = 0x00
        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
       
        # Initialize display
        self.init_display()
   
    def write_byte(self, byte, mode):
        """Write a byte to the LCD"""
        high_bits = mode | (byte & 0xF0) | self.backlight_state
        low_bits = mode | ((byte << 4) & 0xF0) | self.backlight_state
       
        # Write high nibble
        self.i2c.writeto(self.addr, bytearray([high_bits]))
        self.toggle_enable(high_bits)
       
        # Write low nibble
        self.i2c.writeto(self.addr, bytearray([low_bits]))
        self.toggle_enable(low_bits)
   
    def toggle_enable(self, byte):
        """Toggle enable bit"""
        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):
        """Initialize LCD display"""
        time.sleep_ms(50)
       
        # Put LCD into 4-bit mode
        self.write_byte(0x03, 0)
        time.sleep_ms(5)
        self.write_byte(0x03, 0)
        time.sleep_us(150)
        self.write_byte(0x03, 0)
        self.write_byte(0x02, 0)
       
        # Function set
        self.write_byte(self.LCD_FUNCTIONSET | self.LCD_4BITMODE |
                       self.LCD_2LINE | self.LCD_5x8DOTS, 0)
       
        # Display control
        self.write_byte(self.LCD_DISPLAYCONTROL | self.LCD_DISPLAYON |
                       self.LCD_CURSOROFF | self.LCD_BLINKOFF, 0)
       
        # Clear display
        self.clear()
       
        # Entry mode
        self.write_byte(self.LCD_ENTRYMODESET | self.LCD_ENTRYLEFT |
                       self.LCD_ENTRYSHIFTDECREMENT, 0)
   
    def clear(self):
        """Clear the display"""
        self.write_byte(self.LCD_CLEARDISPLAY, 0)
        time.sleep_ms(2)
   
    def set_cursor(self, row, col):
        """Set cursor position"""
        row_offsets = [0x00, 0x40]
        self.write_byte(self.LCD_SETDDRAMADDR | (col + row_offsets[row]), 0)
   
    def print(self, text, row=0, col=0):
        """Print text at specified position"""
        self.set_cursor(row, col)
        for char in str(text):
            self.write_byte(ord(char), 1)
   
    def backlight(self, state):
        """Turn backlight on/off"""
        if state:
            self.backlight_state = self.LCD_BACKLIGHT
        else:
            self.backlight_state = self.LCD_NOBACKLIGHT
        self.i2c.writeto(self.addr, bytearray([self.backlight_state]))

# Initialize LCD
try:
    lcd = LCD_I2C(i2c)
    lcd_available = True
    print("LCD initialized successfully")
except:
    lcd_available = False
    print("LCD not found - using Serial Monitor only")

# ============ GLOBAL VARIABLES ============
watering_count = 0
last_watering_time = 0
system_enabled = True

# ============ HELPER FUNCTIONS ============
def startup_sequence():
    """Display startup message and test components"""
    print("\n" + "="*50)
    print("     SMART PLANT WATERING SYSTEM")
    print("     Raspberry Pi Pico + Moisture Sensor")
    print("="*50)
    print("\nInitializing components...")
   
    if lcd_available:
        lcd.clear()
        lcd.print("Plant Watering", 0, 1)
        lcd.print("Starting...", 1, 2)
   
    # Test LEDs
    print("Testing indicators...")
    led_dry.on()
    led_wet.on()
    led_pump.on()
    time.sleep(0.5)
    led_dry.off()
    led_wet.off()
    led_pump.off()
   
    # Test pump relay (very brief)
    print("Testing pump relay...")
    relay.on()
    time.sleep(0.2)
    relay.off()
   
    time.sleep(1)
   
    print("\nConfiguration:")
    print(f"  Dry Threshold: {DRY_THRESHOLD}")
    print(f"  Wet Threshold: {WET_THRESHOLD}")
    print(f"  Pump Duration: {PUMP_DURATION}s")
    print(f"  Check Interval: {CHECK_INTERVAL}s")
   
    if lcd_available:
        lcd.clear()
        lcd.print("System Ready!", 0, 2)
        lcd.print("Monitoring...", 1, 1)
   
    print("\nSystem ready!")
    print("Monitoring soil moisture...")
    print("="*50 + "\n")

def read_moisture():
    """Read soil moisture sensor (ADC)"""
    # Read ADC value (0-65535 for 16-bit)
    raw_value = moisture_sensor.read_u16()
   
    # Convert to percentage (inverted - lower reading = more moisture)
    # 0 = very wet (max moisture)
    # 65535 = very dry (no moisture)
    moisture_percent = 100 - int((raw_value / 65535) * 100)
   
    return raw_value, moisture_percent

def get_soil_status(raw_value):
    """Determine soil moisture status"""
    if raw_value < DRY_THRESHOLD:
        return "WET"
    elif raw_value > WET_THRESHOLD:
        return "DRY"
    else:
        return "MODERATE"

def update_leds(status):
    """Update LED indicators based on soil status"""
    if status == "DRY":
        led_dry.on()
        led_wet.off()
    elif status == "WET":
        led_dry.off()
        led_wet.on()
    else:  # MODERATE
        led_dry.off()
        led_wet.off()

def water_plants(manual=False):
    """Activate water pump to water plants"""
    global watering_count, last_watering_time
   
    watering_count += 1
    trigger = "MANUAL" if manual else "AUTO"
   
    print("\n" + "💧"*25)
    print(f"🌱 WATERING PLANTS! ({trigger})")
    print(f"Watering cycle #{watering_count}")
    print("💧"*25)
   
    if lcd_available:
        lcd.clear()
        lcd.print("💧 WATERING! 💧", 0, 0)
        lcd.print(f"Cycle #{watering_count}", 1, 2)
   
    # Activate pump
    relay.on()
    led_pump.on()
   
    # Run pump for specified duration with countdown
    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)
   
    # Turn off pump
    relay.off()
    led_pump.off()
    last_watering_time = time.time()
   
    print("✓ Watering complete!")
    print(f"Total waterings: {watering_count}")
    print("💧"*25 + "\n")

def display_status(raw_value, moisture_percent, status):
    """Display current status on LCD"""
    if lcd_available:
        lcd.clear()
        lcd.print(f"Moisture: {moisture_percent}%", 0, 0)
        lcd.print(f"Status: {status}", 1, 0)

def print_readings(raw_value, moisture_percent, status):
    """Print readings to serial monitor"""
    print("\n" + "-"*50)
    print("📊 SOIL MOISTURE READING")
    print("-"*50)
    print(f"Raw Value:    {raw_value}")
    print(f"Moisture:     {moisture_percent}%")
    print(f"Status:       {status}")
   
    if status == "DRY":
        print("⚠️  WARNING: Soil is too dry!")
    elif status == "WET":
        print("✓ Good: Soil has adequate moisture")
    else:
        print("ℹ️  Moderate: Soil moisture is acceptable")
   
    print(f"Waterings:    {watering_count}")
    print("-"*50)

def check_manual_button():
    """Check if manual watering button is pressed"""
    return button.value() == 1

# ============ MAIN PROGRAM ============
def main():
    global last_watering_time
   
    # Startup
    startup_sequence()
   
    # Turn off all outputs initially
    relay.off()
    led_dry.off()
    led_wet.off()
    led_pump.off()
    led_status.off()
   
    last_check_time = 0
    last_button_state = 0
    last_button_time = 0
   
    print("System running. Monitoring soil moisture and button...\n")
   
    while True:
        try:
            current_time = time.time()
           
            # Check manual button (with debouncing)
            button_state = button.value()
            if button_state == 1 and last_button_state == 0:
                if current_time - last_button_time > 0.5:  # 500ms debounce
                    print("\n🔘 MANUAL WATERING TRIGGERED")
                    water_plants(manual=True)
                    last_button_time = current_time
                    time.sleep(COOLDOWN_TIME)
            last_button_state = button_state
           
            # Check moisture at regular intervals
            if current_time - last_check_time >= CHECK_INTERVAL:
                # Read sensor
                raw_value, moisture_percent = read_moisture()
                status = get_soil_status(raw_value)
               
                # Update indicators
                update_leds(status)
               
                # Display on LCD
                display_status(raw_value, moisture_percent, status)
               
                # Print to serial
                print_readings(raw_value, moisture_percent, status)
               
                # Check if watering is needed
                if status == "DRY":
                    # Check cooldown period
                    if current_time - last_watering_time > COOLDOWN_TIME:
                        water_plants(manual=False)
                        time.sleep(COOLDOWN_TIME)  # Wait after watering
               
                last_check_time = current_time
           
            # Blink status LED slowly
            if int(current_time * 2) % 2 == 0:
                led_status.on()
            else:
                led_status.off()
           
            time.sleep(0.1)  # Small delay to reduce CPU usage
           
        except KeyboardInterrupt:
            print("\n\nSystem stopped by user")
            # Turn off all outputs
            relay.off()
            led_dry.off()
            led_wet.off()
            led_pump.off()
            led_status.off()
            if lcd_available:
                lcd.clear()
                lcd.print("System Stopped", 0, 0)
            break
        except Exception as e:
            print(f"\nError: {e}")
            relay.off()  # Ensure pump is off on error
            time.sleep(1)

# Run the program
if __name__ == "__main__":
    main()

Built-in LED (Status):

  • Automatically connected to GPIO25 (blinks during operation)

Applications

  • Home Garden Automation: Automatically water indoor plants when away
  • Greenhouse Management: Maintain optimal soil moisture for crops
  • Vertical Farming: Automate irrigation in vertical garden systems
  • Balcony Gardens: Perfect for apartment gardening
  • Office Plants: Keep office plants healthy without daily attention
  • Agricultural Monitoring: Small-scale farm irrigation automation
  • School Projects: Educational tool for STEM learning
  • Research Labs: Controlled environment plant studies
  • Nurseries: Seedling care automation
  • Drought Management: Efficient water usage for water conservation
  • Vacation Plant Care: Keep plants alive while traveling
  • Smart Hydroponics: Water level management in hydroponic systems

What You'll Learn

  • Analog sensor reading using ADC (Analog-to-Digital Converter)
  • Sensor calibration and threshold setting
  • Relay control for high-power devices
  • PWM concepts for motor control (optional)
  • I2C communication for LCD displays
  • Event-driven programming (sensor-triggered actions)
  • State management (dry/moderate/wet states)
  • Button debouncing for reliable input
  • Automatic control systems design
  • Threshold-based decision making
  • Timer management for cooldown periods
  • Data conversion (raw ADC to percentage)
  • Agricultural IoT principles
  • Using Wokwi simulator for agricultural automation

Code Structure

Your MicroPython code will include:

  1. Import necessary libraries (machine.Pin, machine.ADC, time, LCD)
  2. Initialize soil moisture sensor on GPIO26 (ADC0)
  3. Initialize relay on GPIO14 for pump control
  4. Initialize LCD on I2C pins (GPIO0, GPIO1)
  5. Initialize LED indicators (GPIO15, 16, 17)
  6. Initialize manual button on GPIO18
  7. Define moisture thresholds:
    • DRY: < 30% moisture
    • WET: > 70% moisture
    • MODERATE: 30-70% moisture
  8. Create read_moisture() function:
    • Read ADC value (0-65535)
    • Convert to percentage (0-100%)
    • Return both raw and percentage values
  9. Create water_plants() function:
    • Activate relay (pump ON)
    • Turn on blue LED
    • Run for 5 seconds with countdown
    • Turn off relay (pump OFF)
    • Update watering counter
  10. Create update_leds() function:
    • Red LED ON if dry
    • Green LED ON if wet
    • Both OFF if moderate
  11. Main loop:
    • Read moisture every 2 seconds
    • Check manual button
    • If dry, trigger automatic watering
    • Update LCD display
    • Respect cooldown period
    • Log events to serial monitor

Comments