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:
- Import necessary libraries (machine.Pin, machine.ADC, time, LCD)
- Initialize soil moisture sensor on GPIO26 (ADC0)
- Initialize relay on GPIO14 for pump control
- Initialize LCD on I2C pins (GPIO0, GPIO1)
- Initialize LED indicators (GPIO15, 16, 17)
- Initialize manual button on GPIO18
- Define moisture thresholds:
- DRY: < 30% moisture
- WET: > 70% moisture
- MODERATE: 30-70% moisture
- Create read_moisture() function:
- Read ADC value (0-65535)
- Convert to percentage (0-100%)
- Return both raw and percentage values
- 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
- Create update_leds() function:
- Red LED ON if dry
- Green LED ON if wet
- Both OFF if moderate
- 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
Post a Comment