import machine
import time
# --- تعريف العتاد (GPIO) ---
buzzer = machine.Pin(19, machine.Pin.OUT)
buzzer.value(0)
# محرك السيرفو
servo_pwm = machine.PWM(machine.Pin(18), freq=50)
def set_door(open_door):
# 75 تقريباً تعادل زاوية 90 درجة، و25 تعادل زاوية 0
servo_pwm.duty(75 if open_door else 25)
set_door(False) # القفل مغلق افتراضياً
# I2C (مشترك للشاشة والـ RTC على D21 و D22)
i2c = machine.I2C(0, scl=machine.Pin(22), sda=machine.Pin(21))
# --- قراءة ساعة RTC DS1307 ---
DS1307_ADDR = 0x68
def bcd2dec(val):
return (val // 16 * 10) + (val % 16)
def get_time():
try:
data = i2c.readfrom_mem(DS1307_ADDR, 0x00, 7)
hour = bcd2dec(data[2] & 0x3F)
minute = bcd2dec(data[1])
return hour, minute
except:
return 10, 0 # وقت افتراضي صباحي في حال تأخر الحساس
# --- أوامر شاشة LCD 16x2 I2C ---
LCD_ADDR = 0x27
def lcd_cmd(cmd):
try:
i2c.writeto(LCD_ADDR, bytes([(cmd & 0xF0) | 0x0C, (cmd & 0xF0) | 0x08]))
i2c.writeto(LCD_ADDR, bytes([((cmd << 4) & 0xF0) | 0x0C, ((cmd << 4) & 0xF0) | 0x08]))
except:
pass
def lcd_char(val):
try:
i2c.writeto(LCD_ADDR, bytes([(val & 0xF0) | 0x0D, (val & 0xF0) | 0x09]))
i2c.writeto(LCD_ADDR, bytes([((val << 4) & 0xF0) | 0x0D, ((val << 4) & 0xF0) | 0x09]))
except:
pass
def lcd_init():
for c in [0x33, 0x32, 0x28, 0x0C, 0x06, 0x01]:
lcd_cmd(c)
def lcd_clear():
lcd_cmd(0x01)
def lcd_print(line, text):
lcd_cmd(0x80 if line == 0 else 0xC0)
for ch in text.ljust(16)[:16]:
lcd_char(ord(ch))
# --- لوحة المفاتيح 4x4 Keypad ---
ROW_PINS = [13, 12, 14, 27]
COL_PINS = [26, 25, 33, 32]
KEYS = [
['1', '2', '3', 'A'],
['4', '5', '6', 'B'],
['7', '8', '9', 'C'],
['*', '0', '#', 'D']
]
rows = [machine.Pin(p, machine.Pin.OUT) for p in ROW_PINS]
cols = [machine.Pin(p, machine.Pin.IN, machine.Pin.PULL_DOWN) for p in COL_PINS]
def scan_keys():
for r_idx, r_pin in enumerate(rows):
r_pin.value(1)
for c_idx, c_pin in enumerate(cols):
if c_pin.value() == 1:
while c_pin.value() == 1:
pass # انتظار رفع الإصبع
r_pin.value(0)
return KEYS[r_idx][c_idx]
r_pin.value(0)
return None
def buzz(duration_ms):
buzzer.value(1)
time.sleep_ms(duration_ms)
buzzer.value(0)
# --- قواعد النظام وحالات الدخول ---
MASTER_PIN = "1234"
WORK_START = 8 # من 8 صباحاً
WORK_END = 17 # إلى 5 مساءً
state = "IDLE"
pin_entered = ""
failed_attempts = 0
state_timer = 0
key_timer = 0
def show_idle():
global state, pin_entered
state = "IDLE"
pin_entered = ""
h, m = get_time()
lcd_clear()
lcd_print(0, "DOOR LOCKED")
lcd_print(1, "Time: {:02d}:{:02d}".format(h, m))
# تشغيل النظام وتهيئة الشاشة
lcd_init()
show_idle()
# --- حلقة العمل اللحظية (Non-blocking Engine) ---
while True:
now = time.ticks_ms()
key = scan_keys()
if state == "IDLE":
if key and key.isdigit():
pin_entered = key
buzz(40)
state = "TYPING"
key_timer = now
lcd_clear()
lcd_print(0, "ENTER PIN (#:OK)")
lcd_print(1, "*")
elif state == "TYPING":
# مهلة 10 ثوانٍ للإلغاء التلقائي
if time.ticks_diff(now, key_timer) >= 10000:
buzz(200)
show_idle()
elif key:
key_timer = now
if key == '#': # تأكيد
h, m = get_time()
is_time_ok = (WORK_START <= h < WORK_END)
is_pin_ok = (pin_entered == MASTER_PIN)
if is_pin_ok and is_time_ok:
failed_attempts = 0
state = "GRANTED"
state_timer = now
set_door(True) # فتح القفل
buzz(80)
lcd_clear()
lcd_print(0, "ACCESS GRANTED")
lcd_print(1, "Door Open (5s)")
print("[AUDIT LOG] PIN:", pin_entered, "Status: GRANTED at {:02d}:{:02d}".format(h, m))
else:
failed_attempts += 1
reason = "WRONG PIN" if not is_pin_ok else "OUT OF HOURS"
print("[AUDIT LOG] PIN:", pin_entered, "Status: DENIED -", reason)
if failed_attempts >= 3:
state = "LOCKOUT"
state_timer = now
lcd_clear()
lcd_print(0, "SYSTEM LOCKOUT!")
buzz(500)
else:
state = "DENIED"
state_timer = now
lcd_clear()
lcd_print(0, reason)
lcd_print(1, "Attempts left: {}".format(3 - failed_attempts))
buzz(250)
elif key == '*': # إلغاء
buzz(40)
show_idle()
elif len(pin_entered) < 6 and key.isdigit():
pin_entered += key
buzz(40)
lcd_print(1, "*" * len(pin_entered))
elif state == "GRANTED":
if time.ticks_diff(now, state_timer) >= 5000: # 5 ثوانٍ
set_door(False)
show_idle()
elif state == "DENIED":
if time.ticks_diff(now, state_timer) >= 2000:
show_idle()
elif state == "LOCKOUT":
elapsed = time.ticks_diff(now, state_timer)
if elapsed >= 60000: # دقيقة كاملة
failed_attempts = 0
show_idle()
else:
sec_left = (60000 - elapsed) // 1000
lcd_print(1, "Wait: {:02d}s".format(sec_left))
time.sleep_ms(300)
time.sleep_ms(20)