# IMPORT NECESSARY LIBRARY
import machine
from machine import Pin, I2C, PWM, Timer, SPI
from time import sleep, ticks_us
from ssd1306 import SSD1306_I2C
from ili9341 import Display, color565
from xglcd_font import XglcdFont
# PIN CONFIGURATION
PIR = Pin(39, Pin.IN) # PIR Motion Sensor
PB = Pin(36, Pin.IN, Pin.PULL_UP) # Pushbutton for Exit
red_led = Pin(19, Pin.OUT) # Red LED (Access Denied)
blue_led = Pin(18, Pin.OUT) # Blue LED (Access Granted)
Buzzer = PWM(Pin(13)) # Buzzer
Buzzer.freq(500)
Buzzer.duty(0) # Ensure buzzer is off at the start
servo = PWM(Pin(15), freq=50) # Servo Motor for Door Lock
# I2C SETUP for OLED
sda = Pin(21)
scl = Pin(22)
i2c = I2C(0, scl=scl, sda=sda, freq=400000)
oled = SSD1306_I2C(128, 64, i2c)
# SPI SETUP for TFT (ILI9341)
sck = Pin(4)
mosi = Pin(16)
miso = Pin(0)
spi = SPI(1, baudrate=32000000, sck=sck, mosi=mosi, miso=miso)
rst = Pin(5)
dc = Pin(17)
cs = Pin(23)
display = Display(spi, dc=dc, cs=cs, rst=rst)
arcadepix = XglcdFont('ArcadePix9x11.c', 9, 11)
# KEYPAD SETUP
ROWS = [32, 33, 25, 26]
COLS = [27, 14, 12]
key_map = [
['1', '2', '3', 'A'],
['4', '5', '6', 'B'],
['7', '8', '9', 'C'],
['*', '0', '#', 'D']
]
row_pins = [Pin(pin, Pin.OUT) for pin in ROWS]
col_pins = [Pin(pin, Pin.IN, Pin.PULL_DOWN) for pin in COLS]
# CORRECT PASSCODE
correct_passcode = "1505"
# SYSTEM STATES
inside = False # Whether the user is inside (door opened)
waiting_for_motion = True # Whether to wait for motion detection after actions
# Ultrasonic Sensor Pin Configuration (only one sensor now)
Trig = Pin(2, Pin.OUT) # Trig pin (Output)
Echo = Pin(34, Pin.IN) # Echo pin (Input)
# Function to measure distance using the ultrasonic sensor
def measure_distance():
Trig.value(0) # Set Trig pin low
sleep(0.000002) # Small delay to ensure Trig pin is low
Trig.value(1) # Set Trig pin high to send pulse
sleep(0.00001) # Trigger pulse duration
Trig.value(0) # Set Trig pin low again
while Echo.value() == 0:
pulse_start = ticks_us()
while Echo.value() == 1:
pulse_end = ticks_us()
pulse_duration = pulse_end - pulse_start
distance = (pulse_duration * 0.0343) / 2 # Calculate distance in cm
return distance
# SERVO CONTROL FUNCTION
def set_servo(angle):
duty = int((angle / 180) * 102 + 26)
servo.duty(duty)
# FUNCTION TO DISPLAY MESSAGE ON TFT
def show_tft_message(message):
display.clear()
display.draw_text(
x=50, y=120, text=message,
font=arcadepix, color=color565(255, 0, 0),
background=color565(255, 255, 255)
)
# FUNCTION TO DRAW ON TFT WHEN MOTION DETECTED
def ili9341_spi():
display.clear()
display.draw_circle(120, 260, 40, color565(0, 255, 0)) # Head
display.fill_circle(100, 270, 3, color565(255, 0, 0)) # Left eye
display.fill_circle(140, 270, 3, color565(255, 0, 0)) # Right eye
display.draw_hline(100, 250, 40, color565(255, 0, 0)) # Mouth
display.draw_vline(120, 60, 160, color565(0, 255, 0)) # Body
display.draw_line(120, 190, 80, 140, color565(0, 0, 255)) # Right arm
display.draw_line(120, 190, 160, 140, color565(0, 0, 255)) # Left arm
display.draw_line(120, 60, 80, 10, color565(0, 0, 255)) # Right leg
display.draw_line(120, 60, 150, 10, color565(0, 0, 255)) # Left leg
# FUNCTION TO SCAN KEYPAD
def scan_keypad():
for row_idx, row_pin in enumerate(row_pins):
row_pin.on()
for col_idx, col_pin in enumerate(col_pins):
if col_pin.value() == 1:
row_pin.off()
return key_map[row_idx][col_idx]
row_pin.off()
return None
# ACCESS GRANTED FUNCTION (Unlocks Door)
def access_granted():
global waiting_for_motion # Ensure global access to the variable
# Inform the user that access is granted
oled.fill(0)
oled.text("ACCESS GRANTED", 10, 20)
oled.show()
show_tft_message("ACCESS GRANTED")
sleep(2)
# Unlock door
oled.fill(0)
oled.text("Door Unlocking", 10, 30)
oled.show()
set_servo(0) # Unlock door
blue_led.on()
red_led.off()
sleep(5) # Keep unlocked for 5 seconds
# Lock door after 5 seconds
set_servo(90) # Lock door again
oled.fill(0)
oled.text("Door Locked", 10, 30)
oled.show()
show_tft_message("DOOR LOCKED")
# Reset the motion detection flag and re-enable motion detection
waiting_for_motion = True # Now we are ready to detect motion again
# Debugging print to ensure we are re-enabling PIR interrupt
print("Re-enabling PIR interrupt...")
# Re-enable the PIR sensor interrupt to detect motion again
PIR.irq(trigger=Pin.IRQ_RISING, handler=interrupt_PIR)
# Debugging: Check PIR value to see if it's ready to detect motion
if PIR.value() == 1:
print("Motion detected by PIR immediately after access.")
else:
print("PIR is not detecting motion, waiting for motion.")
# ACCESS DENIED FUNCTION
def access_denied():
oled.fill(0)
oled.text("YOU'RE RESTRICTED!", 10, 20)
oled.show()
set_servo(90) # Ensure door remains locked
Bz_on() # Activate buzzer for warning
for _ in range(3): # Blink red LED three times
red_led.on()
sleep(0.5)
red_led.off()
sleep(0.5)
waiting_for_motion = True # Reset system to wait for motion detection again
# FUNCTION TO TURN OFF BUZZER
def Bz_on():
Buzzer.duty(1023)
sleep(1.5)
Buzzer.duty(0)
sleep(1)
# INTERRUPT FOR PIR SENSOR (ENTRY DETECTION)
def interrupt_PIR(pin):
global waiting_for_motion, inside
if PIR.value() == 1 and waiting_for_motion:
print("PIR interrupt triggered: Motion Detected!")
oled.fill(0)
oled.text("Motion Detected!", 10, 20)
oled.show()
red_led.on()
blue_led.off()
ili9341_spi() # Draw figure on TFT
sleep(1)
enter_passcode() # Wait for passcode entry
# PASSCODE ENTRY FUNCTION
def enter_passcode():
global inside, waiting_for_motion
oled.fill(0)
set_servo(90) # Ensure door starts locked
oled.text("Enter Passcode:", 10, 10)
oled.show()
show_tft_message("ENTER PASSCODE")
user_input = ""
while len(user_input) < 4:
key = scan_keypad()
if key:
oled.text("*", 10 + len(user_input) * 10, 30)
oled.show()
user_input += key
sleep(0.3)
if user_input == correct_passcode:
access_granted()
inside = True # User is inside, door opened
waiting_for_motion = False # Stop motion detection during door open process
else:
access_denied()
# Global variable to track door state and last press time
door_opened = False
last_press_time = 0 # Initialize last press time to 0 (start of system)
# Interrupt handler for the Pushbutton (Exit Button)
def interrupt_PB(pin):
global last_press_time, door_opened
current_time = ticks_us() # Get current time in microseconds
# Debounce logic: only process the press if 2 seconds have passed
if current_time - last_press_time > 2000: # 2-second debounce
last_press_time = current_time # Update the last press time
# If the door is already open, do nothing
if door_opened:
print("Door is already open before, NO MORE!")
return
# Disable the interrupt completely to prevent multiple triggers
PB.irq(handler=None)
# Perform the action of opening the door
oled.fill(0)
oled.text("EXIT DOOR OPEN", 10, 20)
oled.show()
show_tft_message("EXIT OPEN") # Display on TFT
# Unlock the door (servo motor)
set_servo(0) # Unlock door
blue_led.off()
red_led.off()
# Set the door opened flag to prevent multiple openings
door_opened = False
# Wait for 5 seconds with the door open
sleep(5)
# Lock the door again
set_servo(90)
oled.fill(0)
oled.text("Door Locked", 10, 30)
oled.show()
show_tft_message("DOOR LOCKED") # Update TFT
# Reset door opened flag
door_opened = True
# Re-enable the interrupt for the next press (after the door is locked)
PB.irq(trigger=Pin.IRQ_FALLING, handler=interrupt_PB)
# After the exit, reset the system to wait for motion again
waiting_for_motion = True # Reset to motion detection state
# Setting up Interrupts
PIR.irq(trigger=Pin.IRQ_RISING, handler=interrupt_PIR)
PB.irq(trigger=Pin.IRQ_FALLING, handler=interrupt_PB)
# MAIN LOOP
while True:
# Detect distance continuously with the ultrasonic sensor
distance = measure_distance()
# Display the distance on the OLED regardless of motion detection
if distance < 100:
oled.fill(0) # Clear the OLED when the distance is less than 100 cm
oled.text("Motion detected!", 10, 10) # Display that motion is detected
oled.text(f"Dist: {distance:.2f} cm", 10, 30) # Show the current distance
oled.show() # Update the OLED display
else:
oled.fill(0) # Clear the OLED when no motion is detected
oled.text("All clear", 10, 10) # Show "All clear" when the distance is more than 100 cm
oled.show() # Update the OLED display
# If motion is detected, handle it with PIR and other logic
if waiting_for_motion: # Wait for motion detection and password entry
if distance < 100: # If someone is near (using ultrasonic)
oled.fill(0)
oled.text("Motion detected!", 10, 10)
oled.text(f"Dist: {distance:.2f} cm", 10, 30)
oled.show()
sleep(1) # Delay to display message
else:
oled.fill(0)
oled.text("All clear", 10, 10)
oled.show()
# Small delay between checks
sleep(0.5) # Main loop delay for motion detection
else:
# After access granted and door unlocked
print("Waiting for motion after access granted...") # Debug message
# Check for motion (PIR)
if PIR.value() == 1: # PIR sensor triggered
print("Motion detected by PIR!")
oled.fill(0)
oled.text("Motion Detected!", 10, 20)
oled.show()
red_led.on()
blue_led.off()
ili9341_spi() # Draw figure on TFT for motion detected
sleep(1) # Allow display to show
enter_passcode() # Allow entry of passcode
else:
print("No motion detected, waiting...") # Debug message
sleep(0.1) # Small delay to reduce load when no motion is detected