from machine import Pin, PWM
import time
# ------------------- Configuration -------------------
SECRET_CODE = "21906200" # Your 8-digit student ID
MAX_FAILED_ATTEMPTS = 3 # Maximum consecutive failed attempts
LOCKDOWN_DURATION = 10 # Lockdown duration in seconds
# Servo calibration parameters (critical for proper movement)
SERVO_LOCKED_DUTY = 1000 # Pulse width for locked state (0°) in microseconds
SERVO_UNLOCKED_DUTY = 3000 # Pulse width for unlocked state (90°) in microseconds
# -----------------------------------------------------
# Global state variables
is_locked = False
inbuffer = "--------" # Stores last 8 key presses
failed_attempts = 0
is_lockdown = False
lockdown_end_time = 0
# ------------------- Hardware Pin Definition -------------------
# 4x4 Keypad: Rows (output), Columns (input with pull-up resistors)
ROW_PINS = [Pin(26, Pin.OUT), Pin(22, Pin.OUT), Pin(21, Pin.OUT), Pin(20, Pin.OUT)]
COL_PINS = [Pin(19, Pin.IN, Pin.PULL_UP), Pin(18, Pin.IN, Pin.PULL_UP),
Pin(17, Pin.IN, Pin.PULL_UP), Pin(16, Pin.IN, Pin.PULL_UP)]
# Keypad key mapping (matches row/column pins)
KEYS = [['1','2','3','A'], ['4','5','6','B'], ['7','8','9','C'], ['*','0','#','D']]
# Servo Motor (GPIO15, 50Hz PWM required for standard servos)
servo = PWM(Pin(15))
servo.freq(50) # Standard servo operating frequency
# LEDs (active HIGH: 1 = ON, 0 = OFF)
green_led = Pin(14, Pin.OUT, value=0) # Indicates successful unlock
red_led = Pin(13, Pin.OUT, value=0) # Indicates wrong password
yellow_led = Pin(12, Pin.OUT, value=0) # Indicates active lockdown
# ---------------------------------------------------------------
def set_servo_locked():
"""Set servo to LOCKED state (forces locked pulse width)"""
servo.duty_u16(us_to_duty(SERVO_LOCKED_DUTY))
time.sleep(1) # Extended delay to ensure motor reaches target position
print(f"📌 Servo locked (Pulse width: {SERVO_LOCKED_DUTY}us)")
def set_servo_unlocked():
"""Set servo to UNLOCKED state (forces unlocked pulse width)"""
servo.duty_u16(us_to_duty(SERVO_UNLOCKED_DUTY))
time.sleep(1) # Extended delay to ensure motor reaches target position
print(f"📌 Servo unlocked (Pulse width: {SERVO_UNLOCKED_DUTY}us)")
def us_to_duty(us):
"""Convert microsecond (us) pulse width to Pico W's duty_u16 value (65536 = full scale)"""
# Formula derivation: duty = (us * 65536) / (1000000 / 50) → Simplified to us * 3.2768
return int(us * 3.2768)
def reset_leds():
"""Turn off all LEDs"""
green_led.value(0)
red_led.value(0)
yellow_led.value(0)
def scan_keypad():
"""Scan 4x4 keypad and return pressed key (debounce optimized)"""
# Initialize all row pins to HIGH
for row in ROW_PINS:
row.value(1)
pressed_key = None
for row_idx, row in enumerate(ROW_PINS):
# Pull current row LOW to detect column presses
row.value(0)
time.sleep_us(20) # Extended debounce delay
for col_idx, col in enumerate(COL_PINS):
if col.value() == 0: # Key press detected
pressed_key = KEYS[row_idx][col_idx]
# Wait for key release (debounce protection)
while col.value() == 0:
time.sleep_us(100)
row.value(1) # Restore row to HIGH
return pressed_key
row.value(1) # Restore row to HIGH
return pressed_key
def handle_key(key_value):
"""Handle key input logic (ensures servo response on state change)"""
global is_locked, inbuffer, failed_attempts, is_lockdown, lockdown_end_time
# Check if system is in lockdown mode
current_time = time.time()
if is_lockdown:
if current_time < lockdown_end_time:
yellow_led.value(1)
print(f"🔒 Lockdown active! {int(lockdown_end_time - current_time)}s remaining")
return
else:
# Reset state after lockdown expires
is_lockdown = False
failed_attempts = 0
reset_leds()
print("✅ Lockdown lifted. You may try again.")
# Logic for LOCKED state
if is_locked:
if key_value == '#':
# Submit password for verification
print(f"\nSubmitted code: {inbuffer}")
if inbuffer == SECRET_CODE:
# Correct password → Unlock system
is_locked = False
failed_attempts = 0
reset_leds()
green_led.value(1)
print("✅ Correct password! Unlocking...")
set_servo_unlocked() # Force servo to unlocked position
print("🔓 System state = UNLOCKED")
time.sleep(1)
green_led.value(0)
else:
# Wrong password → Increment failed attempt counter
failed_attempts += 1
reset_leds()
red_led.value(1)
print(f"❌ Wrong password! Failed attempts: {failed_attempts}/{MAX_FAILED_ATTEMPTS}")
time.sleep(1)
red_led.value(0)
inbuffer = "--------" # Reset input buffer
# Trigger lockdown after maximum failed attempts
if failed_attempts >= MAX_FAILED_ATTEMPTS:
is_lockdown = True
lockdown_end_time = current_time + LOCKDOWN_DURATION
yellow_led.value(1)
print(f"⚠️ Too many failed attempts! Locking for {LOCKDOWN_DURATION}s")
else:
# Update input buffer with numeric keys only
if key_value.isdigit():
inbuffer = inbuffer[1:] + key_value
print(f"Buffer: {inbuffer}", end="\r")
# Logic for UNLOCKED state
else:
if key_value == '*':
# Press * to lock the system
is_locked = True
inbuffer = "--------" # Reset input buffer
reset_leds()
print("🔐 Locking system...")
set_servo_locked() # Force servo to locked position
print("📌 System state = LOCKED")
# ------------------- Initialization (Critical: Force initial state) -------------------
print("==================================")
print("🔧 Initializing combination lock...")
reset_leds()
# Force servo to unlocked state on startup
set_servo_unlocked()
is_locked = False
print(f"🔓 Initial state = UNLOCKED")
print(f"📌 Secret code = {SECRET_CODE}")
print("Instructions:")
print("- Press * to LOCK the system")
print("- Enter 8-digit code + # to UNLOCK")
print(f"- {MAX_FAILED_ATTEMPTS} consecutive failures trigger {LOCKDOWN_DURATION}s lockdown")
print("==================================")
# ------------------- Main Loop -------------------
while True:
key = scan_keypad()
if key:
print(f"\nPressed key: {key}")
handle_key(key)
time.sleep_ms(50) # Reduce CPU usage