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.
Key Features
Components Required
⚠️ In Wokwi, a potentiometer simulates the soil moisture sensor — turn the dial to change the moisture reading. The DC motor simulates the water pump.
Circuit Connections
Moisture States & Thresholds
< 30% moisture
30–70% moisture
> 70% moisture
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)
diagram.json
Paste this into Wokwi's diagram.json tab. Turn the potentiometer to simulate different soil moisture levels.
{
"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": {}
}
MicroPython Code (main.py)
Save as main.py on your Pico, or paste directly into Wokwi's editor.
""" 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()
What You'll Learn
Real-World Applications
Run the Simulation
Copy into Wokwi's diagram tab to place all components.
Copy the full code into Wokwi's code editor.
The startup sequence runs, LEDs flash, relay clicks.
Rotate to simulate dry/wet soil. Watch LEDs and LCD update.
When DRY — relay activates, motor spins, blue LED lights.
Click the green MANUAL button to trigger watering instantly.
Comments
Post a Comment