from machine import Pin, PWM
import time
import network
from umqtt.simple import MQTTClient
# Configuración de Wi-Fi
WIFI_SSID = "Wokwi-GUEST" # Nombre de la red Wi-Fi para Wokwi
WIFI_PASSWORD = "" # Sin contraseña para Wokwi-GUEST
# Configuración de MQTT (Adafruit IO)
MQTT_BROKER = "io.adafruit.com"
MQTT_PORT = 1883 # Puerto estándar de MQTT (para SSL sería 8833, pero es más complejo)
MQTT_CLIENT_ID = b"Nahuel_lorelli" # ID único de cliente MQTT. Usando username como ID.
MQTT_USERNAME = "Nahuel_lorelli" # USERNAME DE ADAFRUIT IO!
MQTT_PASSWORD = "aio_jOMD29XbjfjxfoRdk6Lfadb0MFXq" # AIO KEY DE ADAFRUIT IO
# Tópicos MQTT para la cerradura (basados en Feeds de Adafruit IO)
# El formato es "USERNAME/feeds/NOMBRE_DEL_FEED"
TOPIC_STATUS = b"Nahuel_lorelli/feeds/cerradura-estado" #
TOPIC_DOOR_SENSOR = b"Nahuel_lorelli/feeds/puerta-estado" #
TOPIC_COMMAND = b"Nahuel_lorelli/feeds/cerradura-comando" #
TOPIC_EVENTS = b"Nahuel_lorelli/feeds/cerradura-eventos" # Nuevo tópico para eventos
# --- Pines GPIO del ESP32 (según tu diagrama de Wokwi) ---
# Keypad
ROWS_PINS = [Pin(21, Pin.IN, Pin.PULL_UP), Pin(19, Pin.IN, Pin.PULL_UP), Pin(18, Pin.IN, Pin.PULL_UP), Pin(5, Pin.IN, Pin.PULL_UP)]
COLS_PINS = [Pin(17, Pin.OUT), Pin(16, Pin.OUT), Pin(4, Pin.OUT), Pin(2, Pin.OUT)]
# Servo
SERVO_PIN = Pin(13)
servo = PWM(SERVO_PIN, freq=50)
# Slide Switch (Sensor de Contacto de Puerta)
DOOR_SENSOR_PIN = Pin(23, Pin.IN, Pin.PULL_UP)
# RGB LED (Configurado para Cátodo Común: Pin común a GND)
LED_R_PIN = Pin(25, Pin.OUT)
LED_G_PIN = Pin(26, Pin.OUT)
LED_B_PIN = Pin(27, Pin.OUT)
# Teclas del Keypad //
KEYPAD_KEYS = [
['1', '2', '3', 'A'],
['4', '5', '6', 'B'],
['7', '8', '9', 'C'],
['*', '0', '#', 'D']
]
# --- Variables de la Cerradura ---
CORRECT_PASSWORD = "1234"
current_password_input = ""
MAX_PASSWORD_LENGTH = 4
is_door_locked = True
# --- Cliente MQTT global ---
client = None
last_ping_time = 0
PING_INTERVAL_MS = 30000 # Enviar un ping cada 30 segundos para mantener la conexión
# --- Funciones de Conectividad ---
def connect_wifi(ssid, password):
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
if not wlan.isconnected():
print(f"Conectando a red Wi-Fi: {ssid}...")
wlan.connect(ssid, password)
max_attempts = 10
while not wlan.isconnected() and max_attempts > 0:
print(".", end="")
time.sleep(1)
max_attempts -= 1
if wlan.isconnected():
print("\n¡Conectado a Wi-Fi!")
print("Dirección IP:", wlan.ifconfig()[0])
return True
else:
print("\n¡ERROR! No se pudo conectar a Wi-Fi.")
return False
return True
def mqtt_callback(topic, msg):
print(f"Mensaje MQTT recibido en tópico '{topic.decode()}': '{msg.decode()}'")
if topic.decode() == TOPIC_COMMAND.decode():
command = msg.decode().strip().upper()
if command == "LOCK":
if not is_door_locked:
lock_door()
publish_event("Comando MQTT: Cerradura BLOQUEADA") ### NUEVO: Eventos de LOG
elif command == "UNLOCK":
if is_door_locked:
unlock_door()
publish_event("Comando MQTT: Cerradura DESBLOQUEADA") ### NUEVO: Eventos de LOG
else:
print(f"Comando desconocido: {command}")
publish_event(f"Comando MQTT desconocido: {command}") ### NUEVO: Eventos de LOG
def connect_mqtt():
global client, last_ping_time
try:
# Asegúrate de que MQTT_CLIENT_ID sea un byte string
client = MQTTClient(MQTT_CLIENT_ID, MQTT_BROKER, port=MQTT_PORT, user=MQTT_USERNAME, password=MQTT_PASSWORD, keepalive=60)
client.set_callback(mqtt_callback)
client.connect()
print(f"¡Conectado al broker MQTT: {MQTT_BROKER}!")
client.subscribe(TOPIC_COMMAND)
print(f"Suscrito a tópico de comandos: {TOPIC_COMMAND.decode()}")
last_ping_time = time.ticks_ms() # Reinicia el tiempo del último ping al conectar
return True
except Exception as e:
print(f"¡ERROR! No se pudo conectar al broker MQTT: {e}")
return False
# Funciones de publicación
def publish_status():
global client
status_msg = "BLOQUEADA" if is_door_locked else "DESBLOQUEADA"
try:
if client:
client.publish(TOPIC_STATUS, status_msg.encode())
print(f"MQTT: Publicado estado '{status_msg}' en '{TOPIC_STATUS.decode()}'")
except Exception as e:
print(f"ERROR al publicar estado MQTT: {e}")
# Si falla al publicar, intenta reconectar
reconnect_mqtt_safe()
def publish_door_state(state):
global client
# --- MODIFICACIÓN: Lógica corregida para el sensor de puerta ---
# Si el pin es 0 (LOW con PULL_UP), significa que el contacto está CERRADO.
# Si el pin es 1 (HIGH con PULL_UP), significa que el contacto está ABIERTO.
door_state_msg = "CERRADA" if state == 0 else "ABIERTA"
try:
if client:
client.publish(TOPIC_DOOR_SENSOR, door_state_msg.encode())
print(f"MQTT: Publicado estado puerta '{door_state_msg}' en '{TOPIC_DOOR_SENSOR.decode()}'")
publish_event(f"Puerta: {door_state_msg}") # Nuevo: Publica evento de cambio de estado de puerta
except Exception as e:
print(f"ERROR al publicar estado puerta MQTT: {e}")
# Si falla al publicar, intenta reconectar
reconnect_mqtt_safe()
def publish_event(event_message): ### NUEVA FUNCIÓN PARA EVENTOS
global client
try:
if client:
client.publish(TOPIC_EVENTS, event_message.encode())
print(f"MQTT: Publicado evento '{event_message}' en '{TOPIC_EVENTS.decode()}'")
except Exception as e:
print(f"ERROR al publicar evento MQTT: {e}")
reconnect_mqtt_safe()
def reconnect_mqtt_safe():
global client
print("Intentando reconectar MQTT de forma segura...")
try:
client.disconnect() # Desconecta limpiamente si estaba conectado
except:
pass # Ignora si ya estaba desconectado o hubo un error
if not connect_mqtt(): # Intenta reconectar
print("Reconexión MQTT fallida.")
# --- Funciones de Control ---
def set_rgb_color(r, g, b):
LED_R_PIN.value(r)
LED_G_PIN.value(g)
LED_B_PIN.value(b)
def turn_off_rgb():
set_rgb_color(0, 0, 0)
def set_servo_angle(angle):
duty = int(25 + (angle / 180.0) * 100)
servo.duty(duty)
time.sleep_ms(500)
def lock_door():
global is_door_locked
set_servo_angle(0)
is_door_locked = True
print("Cerradura: BLOQUEADA")
set_rgb_color(1, 0, 0) # Enciende LED Rojo
time.sleep(10) # <--- DURACIÓN DE LUZ ROJA: 10 segundos
turn_off_rgb()
publish_status()
publish_event("Cerradura: BLOQUEADA") # Nuevo: Publica evento de bloqueo
def unlock_door():
global is_door_locked
set_servo_angle(90)
is_door_locked = False
print("Cerradura: DESBLOQUEADA")
set_rgb_color(0, 1, 0) # Enciende LED Verde
time.sleep(5) # <--- DURACIÓN DE LUZ VERDE: 5 segundos
turn_off_rgb()
publish_status()
publish_event("Cerradura: DESBLOQUEADA") # Nuevo: Publica evento de desbloqueo
# --- Lógica de Inicio ---
print("Cargando sistema de cerradura...")
turn_off_rgb()
time.sleep_ms(1000)
# 1. Conectar a Wi-Fi
if not connect_wifi(WIFI_SSID, WIFI_PASSWORD):
print("No se pudo conectar a Wi-Fi, abortando.")
while True:
set_rgb_color(1,0,0)
time.sleep_ms(500)
turn_off_rgb()
time.sleep_ms(500)
# 2. Conectar a MQTT (AHORA HABILITADO para Adafruit IO)
if not connect_mqtt():
print("No se pudo conectar a MQTT, abortando.")
while True:
set_rgb_color(1,1,0) # Amarillo intermitente si no conecta MQTT
time.sleep_ms(500)
turn_off_rgb()
time.sleep_ms(500)
lock_door()
time.sleep_ms(500)
last_door_state = DOOR_SENSOR_PIN.value()
publish_door_state(last_door_state)
# --- Bucle Principal del Programa ---
print("Iniciando bucle principal. Intenta presionar teclas del keypad.")
while True:
# --- Manejar mensajes MQTT entrantes y mantener conexión ---
try:
if client:
client.check_msg() # Procesa mensajes MQTT que llegan al ESP32
# Envía un ping periódico para mantener la conexión viva
if time.ticks_diff(time.ticks_ms(), last_ping_time) > PING_INTERVAL_MS:
client.ping()
last_ping_time = time.ticks_ms()
print("MQTT: Enviando PING para mantener conexión.")
except Exception as e:
print(f"Error al verificar o mantener mensajes MQTT: {e}")
reconnect_mqtt_safe()
# --- Lectura del Keypad ---
key_pressed = None
for col_idx, col_pin in enumerate(COLS_PINS):
col_pin.value(0) # Pone la columna actual en LOW
for row_idx, row_pin in enumerate(ROWS_PINS):
if not row_pin.value(): # Si la fila es LOW (significa que una tecla está siendo presionada)
print(f"DEBUG Keypad: Tecla detectada en Fila {row_idx} y Columna {col_idx}.")
key_pressed = KEYPAD_KEYS[row_idx][col_idx]
while not row_pin.value(): # Espera a que la tecla sea liberada (debounce)
time.sleep_ms(20)
time.sleep_ms(200) # <--- AJUSTE PARA KEYPAD: Retardo después de la liberación de tecla para mejor lectura
break # Sale del bucle de filas
col_pin.value(1) # Pone la columna actual en HIGH de nuevo
if key_pressed: # Si ya encontró una tecla, sale del bucle de columnas
break
if key_pressed: # Si se detectó una tecla presionada
print(f"Tecla presionada: {key_pressed}")
if key_pressed == '*':
current_password_input = ""
print("Entrada de contraseña reiniciada.")
set_rgb_color(0, 0, 1) # Azul para reinicio
time.sleep(5) # <--- DURACIÓN DE LUZ AZUL: 5 segundos
turn_off_rgb()
publish_event("Keypad: Entrada de contraseña reiniciada") # Nuevo: Publica evento
elif key_pressed == '#':
print(f"Contraseña ingresada: {current_password_input}")
if current_password_input == CORRECT_PASSWORD:
print("Contraseña CORRECTA.")
set_rgb_color(0, 1, 0) # Verde
time.sleep_ms(1000) # LED verde para confirmación de contraseña (1 segundo)
unlock_door() # Esta función tiene su propio delay de 5 segundos para el LED verde
time.sleep_ms(5000) # Espera 5 segundos después de desbloquear, antes de intentar bloquear
lock_door() # Esta función tiene su propio delay de 10 segundos para el LED rojo
turn_off_rgb()
publish_event("Keypad: Acceso Concedido") # Nuevo: Publica evento
else:
print("Contraseña INCORRECTA.")
set_rgb_color(1, 0, 0) # Rojo
time.sleep_ms(10000) # LED rojo para contraseña incorrecta (10 segundos)
turn_off_rgb()
publish_event(f"Keypad: Acceso Denegado (Contraseña: {current_password_input})") # Nuevo: Publica evento con contraseña
current_password_input = "" # Reinicia la entrada después de la verificación
else: # Si es un número o letra para la contraseña
if len(current_password_input) < MAX_PASSWORD_LENGTH:
current_password_input += key_pressed
print(f"Entrada actual: {current_password_input}")
else:
print(f"Límite de caracteres alcanzado ({MAX_PASSWORD_LENGTH}). Presiona '*' para reiniciar o '#' para verificar.")
# Sensor de Puerta (1 o 0) / Cerrada o abierta
current_door_physical_state = DOOR_SENSOR_PIN.value()
if current_door_physical_state != last_door_state:
print(f"Cambio en el estado de la puerta detectado: {current_door_physical_state}")
publish_door_state(current_door_physical_state)
last_door_state = current_door_physical_state
# --- MODIFICACIÓN: Lógica de Alerta corregida ---
# Si la cerradura está BLOQUEADA y la puerta está ABIERTA (current_door_physical_state == 1)
if is_door_locked and current_door_physical_state == 1:
print("¡ALERTA! Puerta abierta mientras la cerradura está BLOQUEADA.")
set_rgb_color(1, 0, 0) # Rojo para alerta
time.sleep_ms(1000) # Mantén la luz de alerta por un segundo
turn_off_rgb()
publish_event("ALERTA: Puerta abierta estando BLOQUEADA") # Nuevo: Publica evento de alerta
# elif not is_door_locked and current_door_physical_state == 1: # Esta condición ya no es necesaria aquí, o podría ser para otra lógica específica.
# print("Puerta CERRADA mientras la cerradura está DESBLOQUEADA.") # Comentario incorrecto, 1 es ABIERTA
# pass
time.sleep_ms(200) # Retardo principal del bucle, crucial para MQTT y otros eventos