"""
Motion Detection Security System
Raspberry Pi Pico with PIR Sensor
Wokwi Simulator Compatible
Components:
- Raspberry Pi Pico
- PIR Motion Sensor (HC-SR501)
- LCD Display (I2C 16x2)
- Buzzer (Alarm)
- Red LED (Alert indicator)
- Green LED (System armed indicator)
- Push Button (Arm/Disarm system)
"""
from machine import Pin, I2C
import time
# ============ PIN CONFIGURATION ============
PIR_PIN = 15 # PIR sensor output
BUZZER_PIN = 14 # Buzzer/Alarm
LED_ALERT = 16 # Red LED - Motion detected
LED_ARMED = 17 # Green LED - System armed
BUTTON_PIN = 18 # Push button to arm/disarm
LED_STATUS = 25 # Built-in LED - Status indicator
# Alarm settings
ALARM_DURATION = 5 # Alarm sounds for 5 seconds
COOLDOWN_TIME = 3 # 3 seconds cooldown between detections
# ============ HARDWARE INITIALIZATION ============
# Initialize PIR sensor
pir = Pin(PIR_PIN, Pin.IN)
# Initialize outputs
buzzer = Pin(BUZZER_PIN, Pin.OUT)
led_alert = Pin(LED_ALERT, Pin.OUT)
led_armed = Pin(LED_ARMED, Pin.OUT)
led_status = Pin(LED_STATUS, Pin.OUT)
# Initialize 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 ============
system_armed = False
motion_detected = False
detection_count = 0
last_detection_time = 0
# ============ HELPER FUNCTIONS ============
def startup_sequence():
"""Display startup message and test components"""
print("\n" + "="*50)
print(" MOTION DETECTION SECURITY SYSTEM")
print(" Raspberry Pi Pico + PIR Sensor")
print("="*50)
print("\nInitializing components...")
if lcd_available:
lcd.clear()
lcd.print("Security System", 0, 0)
lcd.print("Initializing...", 1, 0)
# Test LEDs
print("Testing LEDs...")
led_alert.on()
led_armed.on()
time.sleep(0.5)
led_alert.off()
led_armed.off()
# Test buzzer
print("Testing buzzer...")
buzzer.on()
time.sleep(0.2)
buzzer.off()
time.sleep(1)
if lcd_available:
lcd.clear()
lcd.print("Press Button", 0, 0)
lcd.print("to ARM system", 1, 0)
print("\nSystem ready!")
print("Press button to ARM/DISARM")
print("="*50 + "\n")
def toggle_system():
"""Toggle system armed/disarmed state"""
global system_armed, detection_count
system_armed = not system_armed
if system_armed:
led_armed.on()
detection_count = 0
print("\n🔒 SYSTEM ARMED - Monitoring for motion...")
if lcd_available:
lcd.clear()
lcd.print("SYSTEM ARMED", 0, 2)
lcd.print("Monitoring...", 1, 1)
# Beep to confirm
buzzer.on()
time.sleep(0.1)
buzzer.off()
else:
led_armed.off()
led_alert.off()
buzzer.off()
print("\n🔓 SYSTEM DISARMED - Standing by...")
if lcd_available:
lcd.clear()
lcd.print("DISARMED", 0, 3)
lcd.print("Press to ARM", 1, 1)
# Double beep to confirm
for _ in range(2):
buzzer.on()
time.sleep(0.1)
buzzer.off()
time.sleep(0.1)
def check_motion():
"""Check PIR sensor for motion"""
return pir.value() == 1
def trigger_alarm():
"""Trigger alarm when motion is detected"""
global detection_count, last_detection_time
detection_count += 1
current_time = time.time()
print("\n" + "!"*50)
print("⚠️ MOTION DETECTED! ⚠️")
print(f"Detection #{detection_count}")
print(f"Time: {current_time}")
print("!"*50)
if lcd_available:
lcd.clear()
lcd.print("!! ALERT !!", 0, 2)
lcd.print(f"Motion #{detection_count}", 1, 2)
# Activate alarm
led_alert.on()
# Sound alarm with pattern
alarm_start = time.time()
while time.time() - alarm_start < ALARM_DURATION:
buzzer.on()
led_status.on()
time.sleep(0.2)
buzzer.off()
led_status.off()
time.sleep(0.2)
led_alert.off()
last_detection_time = time.time()
print(f"Alarm stopped. Total detections: {detection_count}")
if lcd_available:
lcd.clear()
lcd.print("SYSTEM ARMED", 0, 2)
lcd.print(f"Detects: {detection_count}", 1, 2)
def display_status():
"""Display current system status"""
if lcd_available:
if system_armed:
lcd.clear()
lcd.print("ARMED - Ready", 0, 1)
lcd.print(f"Alerts: {detection_count}", 1, 2)
else:
lcd.clear()
lcd.print("DISARMED", 0, 3)
lcd.print("Press to ARM", 1, 1)
def print_status():
"""Print status to serial monitor"""
status = "ARMED" if system_armed else "DISARMED"
motion = "DETECTED" if check_motion() else "None"
print(f"\rStatus: {status} | Motion: {motion} | Detections: {detection_count}", end="")
# ============ MAIN PROGRAM ============
def main():
global motion_detected, last_detection_time
# Startup
startup_sequence()
# Turn off all outputs initially
led_alert.off()
led_armed.off()
buzzer.off()
led_status.off()
last_button_state = 0
last_button_time = 0
status_update_time = 0
print("System running. Monitoring for button press and motion...\n")
while True:
try:
current_time = time.time()
# Button debouncing and handling
button_state = button.value()
if button_state == 1 and last_button_state == 0:
if current_time - last_button_time > 0.3: # 300ms debounce
toggle_system()
last_button_time = current_time
last_button_state = button_state
# Check for motion only if system is armed
if system_armed:
if check_motion():
# Check cooldown period
if current_time - last_detection_time > COOLDOWN_TIME:
trigger_alarm()
time.sleep(COOLDOWN_TIME) # Wait before next detection
# Blink status LED slowly when armed
if int(current_time * 2) % 2 == 0:
led_status.on()
else:
led_status.off()
else:
led_status.off()
# Update status display every 2 seconds
if current_time - status_update_time > 2:
print_status()
status_update_time = current_time
time.sleep(0.1) # Small delay to reduce CPU usage
except KeyboardInterrupt:
print("\n\nSystem stopped by user")
# Turn off all outputs
led_alert.off()
led_armed.off()
buzzer.off()
led_status.off()
if lcd_available:
lcd.clear()
lcd.print("System Stopped", 0, 0)
break
except Exception as e:
print(f"\nError: {e}")
time.sleep(1)
# Run the program
if __name__ == "__main__":
main()