# ================================================================
# Raspberry Pi Pico W - Smart Lock (Emergency Working Version)
# Servo: Moves to mid point on lock, returns to original on unlock
# This version FORCES the motor to visibly move every time
# ================================================================
from machine import Pin, PWM, I2C
import time
# ------------------------------------------------
# USER CONFIGURATION
# ------------------------------------------------
SECRET_CODE = "25864344"
MAX_ATTEMPTS = 3
LOCKOUT_TIME = 10
# ------------------------------------------------
# LCD
# ------------------------------------------------
class I2cLcd:
def __init__(self, i2c, addr, rows, cols):
self.i2c = i2c
self.addr = addr
self.rows = rows
self.cols = cols
self.backlight = 0x08
time.sleep_ms(50)
self._write4bits(0x03, 0)
time.sleep_ms(5)
self._write4bits(0x03, 0)
time.sleep_ms(5)
self._write4bits(0x03, 0)
time.sleep_ms(5)
self._write4bits(0x02, 0)
self._command(0x28)
self._command(0x0C)
self.clear()
self._command(0x06)
def _i2c_write(self, data):
self.i2c.writeto(self.addr, bytes([data]))
def _pulse_enable(self, data):
self._i2c_write(data | 0x04)
time.sleep_ms(1)
self._i2c_write(data & ~0x04)
time.sleep_ms(1)
def _write4bits(self, nibble, mode):
data = (nibble << 4) | self.backlight | (mode & 0x01)
self._i2c_write(data)
self._pulse_enable(data)
def _command(self, cmd):
self._write4bits(cmd >> 4, 0)
self._write4bits(cmd & 0x0F, 0)
def write_char(self, char):
val = ord(char)
self._write4bits(val >> 4, 1)
self._write4bits(val & 0x0F, 1)
def clear(self):
self._command(0x01)
time.sleep_ms(2)
def move_to(self, col, row):
row_offsets = [0x00, 0x40, 0x14, 0x54]
addr = col + row_offsets[row]
self._command(0x80 | addr)
def putstr(self, string):
for ch in string:
self.write_char(ch)
# ------------------------------------------------
# LCD INIT
# ------------------------------------------------
i2c = I2C(1, scl=Pin(27), sda=Pin(26), freq=400000)
lcd = I2cLcd(i2c, 0x27, 2, 16)
def lcd_show(line1="", line2=""):
lcd.clear()
lcd.move_to(0, 0)
lcd.putstr(line1[:16])
lcd.move_to(0, 1)
lcd.putstr(line2[:16])
# ------------------------------------------------
# SERVO (WORKING FORCED MOTION)
# ------------------------------------------------
servo = PWM(Pin(15))
servo.freq(50)
# Use fixed tested values — just move between 2 working values
POS_UNLOCK = 3000 # unlock (about 0 deg)
POS_LOCK = 7000 # locked (about 90 deg)
def lock_servo():
servo.duty_u16(POS_LOCK)
print("Locking... duty:", POS_LOCK)
time.sleep(1.5)
def unlock_servo():
servo.duty_u16(POS_UNLOCK)
print("Unlocking... duty:", POS_UNLOCK)
time.sleep(1.5)
# ------------------------------------------------
# BUZZER
# ------------------------------------------------
buzzer = PWM(Pin(14))
buzzer.duty_u16(0)
def beep(freq=1000, duration=0.1):
buzzer.freq(freq)
buzzer.duty_u16(30000)
time.sleep(duration)
buzzer.duty_u16(0)
def success_tone():
beep(1200, 0.1)
time.sleep(0.05)
beep(1500, 0.1)
def error_tone():
beep(400, 0.3)
def lock_tone():
beep(700, 0.1)
time.sleep(0.05)
beep(500, 0.1)
# ------------------------------------------------
# GLOBALS
# ------------------------------------------------
input_buffer = ""
is_locked = False
fail_count = 0
locked_out = False
# ------------------------------------------------
# KEYPAD
# ------------------------------------------------
class Keypad:
def __init__(self, rows, cols, callback):
self.rows = [Pin(r, Pin.OUT) for r in rows]
self.cols = [Pin(c, Pin.IN, Pin.PULL_DOWN) for c in cols]
self.callback = callback
self.keys = [
['1', '2', '3'],
['4', '5', '6'],
['7', '8', '9'],
['*', '0', '#']
]
def scan(self):
for r in range(4):
self.rows[r].high()
for c in range(3):
if self.cols[c].value():
key = self.keys[r][c]
time.sleep(0.15)
beep(1000, 0.05)
self.callback(key)
while self.cols[c].value():
time.sleep(0.05)
self.rows[r].low()
# ------------------------------------------------
# MAIN LOGIC (FORCED MOVEMENT)
# ------------------------------------------------
def key_pressed(key):
global input_buffer, is_locked, fail_count, locked_out
if locked_out:
lcd_show("LOCKOUT", "Try later")
return
if is_locked:
if key == '#':
if input_buffer == SECRET_CODE:
is_locked = False
fail_count = 0
success_tone()
lcd_show("ACCESS GRANTED", "UNLOCKING")
servo.duty_u16(3000) # UNLOCK
time.sleep(1.5)
input_buffer = ""
lcd_show("UNLOCKED", "Press * to lock")
else:
fail_count += 1
error_tone()
lcd_show("WRONG CODE", f"Attempt {fail_count}")
input_buffer = ""
time.sleep(1.5)
if fail_count >= MAX_ATTEMPTS:
locked_out = True
lcd_show("LOCKOUT", f"Wait {LOCKOUT_TIME}s")
error_tone()
time.sleep(LOCKOUT_TIME)
locked_out = False
fail_count = 0
lcd_show("LOCKED", "Enter code:")
else:
lcd_show("LOCKED", "Enter code:")
elif key == '*':
input_buffer = ""
lcd_show("LOCKED", "Code cleared")
elif key.isdigit():
if len(input_buffer) < 8:
input_buffer += key
lcd_show("LOCKED", "Code: " + input_buffer)
else:
if key == '*':
is_locked = True
input_buffer = ""
lcd_show("LOCKING", "Please wait...")
servo.duty_u16(7000) # LOCK
time.sleep(1.5)
lock_tone()
lcd_show("LOCKED", "Enter code:")
else:
lcd_show("UNLOCKED", "Press * to lock")
# ------------------------------------------------
# INITIALISATION
# ------------------------------------------------
keypad = Keypad(
rows=[20, 21, 22, 19],
cols=[16, 17, 18],
callback=key_pressed
)
unlock_servo()
lcd_show("UNLOCKED", "Press * to lock")
# ------------------------------------------------
# MAIN LOOP
# ------------------------------------------------
while True:
keypad.scan()
time.sleep(0.05)