"""
main.py
Semaforo vehicular y peatonal conectado por WiFi y MQTT.
Trabajo en clase N°5 - Python para IoT - UTP
Estudiante: Sarai Miranda
"""
from machine import Pin, PWM
import time
import json # NUEVO: mensajes en formato JSON
import network # NUEVO: control del WiFi
from umqtt.simple import MQTTClient # NUEVO: cliente MQTT
import tm1637
# ---------------------------------------------------------------
# SECCION 1. Configuracion de hardware y tiempos
# ---------------------------------------------------------------
PIN_VERDE_VEH = 25
PIN_AMARILLO_VEH = 26
PIN_ROJO_VEH = 27
PIN_VERDE_PEAT = 12
PIN_ROJO_PEAT = 14
PIN_BOTON = 33
PIN_BUZZER = 13
PIN_CLK = 22
PIN_DIO = 21
T_AMARILLO = 3000
T_TODO_ROJO = 1000
T_CRUCE = 20000
T_DESPEJE = 3000
DUR_PITIDO = 100
# ---------------------------------------------------------------
# SECCION 1B. Red y MQTT (NUEVA)
# ---------------------------------------------------------------
ID_ESTUDIANTE = "saraimiranda" # Identificador personal
WIFI_SSID = "Wokwi-GUEST" # Red WiFi del simulador
WIFI_CLAVE = "" # Sin clave
BROKER = "broker.hivemq.com" # Broker MQTT publico
PUERTO = 1883
CLIENTE_ID = "semaforo-" + ID_ESTUDIANTE
BASE = "utp/ds8/" + ID_ESTUDIANTE + "/"
TOPICO_ESTADO = (BASE + "estado").encode() # Publica estado
TOPICO_CMD = (BASE + "cmd").encode() # Escucha comandos
TOPICO_CONEXION = (BASE + "conexion").encode() # online / offline
T_WIFI = 15000 # ms maximos para WiFi
T_PING = 30000 # ms entre pings al broker
T_REINTENTO = 10000 # ms entre reintentos
# ---------------------------------------------------------------
# SECCION 2. Definicion de estados y variables globales
# ---------------------------------------------------------------
VEH_VERDE = "VEHICULOS EN VERDE"
VEH_AMARILLO = "VEHICULOS EN AMARILLO"
TODO_ROJO = "TODO EN ROJO"
CRUCE = "CRUCE PEATONAL"
DESPEJE = "DESPEJE PEATONAL"
estado = VEH_VERDE
solicitud = False
inicio_estado = time.ticks_ms()
# ---------------------------------------------------------------
# SECCION 3. Inicializacion de periféricos
# ---------------------------------------------------------------
led_verde_veh = Pin(PIN_VERDE_VEH, Pin.OUT)
led_amarillo_veh = Pin(PIN_AMARILLO_VEH, Pin.OUT)
led_rojo_veh = Pin(PIN_ROJO_VEH, Pin.OUT)
led_verde_peat = Pin(PIN_VERDE_PEAT, Pin.OUT)
led_rojo_peat = Pin(PIN_ROJO_PEAT, Pin.OUT)
buzzer = PWM(Pin(PIN_BUZZER), freq=1000, duty=0)
pantalla = tm1637.TM1637(clk=Pin(PIN_CLK), dio=Pin(PIN_DIO))
boton = Pin(PIN_BOTON, Pin.IN, Pin.PULL_UP)
# ---------------------------------------------------------------
# SECCION 4. Funciones auxiliares (Hardware y Sonido)
# ---------------------------------------------------------------
def sonar(estado_sonido):
if estado_sonido:
buzzer.duty(512)
else:
buzzer.duty(0)
def intervalo_pitido(tiempo_restante_ms):
if tiempo_restante_ms > 10000:
return 1000
elif tiempo_restante_ms > 5000:
return 500
else:
return 250
def al_presionar(pin):
global solicitud
solicitud = True
boton.irq(trigger=Pin.IRQ_FALLING, handler=al_presionar)
# ---------------------------------------------------------------
# SECCION 5. Control de Luces y Transición de Estados
# ---------------------------------------------------------------
def cambiar_estado(nuevo):
global estado, inicio_estado
estado = nuevo
inicio_estado = time.ticks_ms()
print("Estado:", estado)
if nuevo == VEH_VERDE:
led_verde_veh.value(1)
led_amarillo_veh.value(0)
led_rojo_veh.value(0)
led_verde_peat.value(0)
led_rojo_peat.value(1)
pantalla.numero(0)
sonar(False)
elif nuevo == VEH_AMARILLO:
led_verde_veh.value(0)
led_amarillo_veh.value(1)
led_rojo_veh.value(0)
led_verde_peat.value(0)
led_rojo_peat.value(1)
elif nuevo == TODO_ROJO:
led_verde_veh.value(0)
led_amarillo_veh.value(0)
led_rojo_veh.value(1)
led_verde_peat.value(0)
led_rojo_peat.value(1)
elif nuevo == CRUCE:
led_verde_veh.value(0)
led_amarillo_veh.value(0)
led_rojo_veh.value(1)
led_verde_peat.value(1)
led_rojo_peat.value(0)
elif nuevo == DESPEJE:
led_verde_veh.value(0)
led_amarillo_veh.value(0)
led_rojo_veh.value(1)
led_verde_peat.value(0)
led_rojo_peat.value(1)
pantalla.numero(0)
sonar(False)
# NUEVO: avisar el cambio por MQTT (el CRUCE se publica cada segundo)
if nuevo != CRUCE:
publicar_estado()
# ---------------------------------------------------------------
# SECCION 5B. Conexion WiFi (NUEVA)
# ---------------------------------------------------------------
wlan = network.WLAN(network.STA_IF)
def conectar_wifi():
wlan.active(True)
if wlan.isconnected():
return True
print("Conectando a WiFi", WIFI_SSID, end="")
wlan.connect(WIFI_SSID, WIFI_CLAVE)
inicio = time.ticks_ms()
while not wlan.isconnected():
if time.ticks_diff(time.ticks_ms(), inicio) > T_WIFI:
print(" sin respuesta")
return False
print(".", end="")
time.sleep_ms(250)
print(" conectado. IP:", wlan.ifconfig()[0])
return True
# ---------------------------------------------------------------
# SECCION 5C. MQTT: publicar, recibir y reconectar (NUEVA)
# ---------------------------------------------------------------
cliente = None
mqtt_ok = False # True mientras haya conexion con el broker
ultimo_ping = 0
ultimo_intento = 0
def publicar(topico, texto, retener=False):
global mqtt_ok
if not mqtt_ok:
return # sin conexion: no se intenta
try:
cliente.publish(topico, texto.encode(), retener)
except OSError as e:
print("Error al publicar:", e)
mqtt_ok = False
def publicar_estado():
restante = ultimo_segundo if estado == CRUCE else 0
datos = json.dumps({"estado": estado, "restante": restante})
publicar(TOPICO_ESTADO, datos)
def al_mensaje(topico, msg): # llega un comando por MQTT
global solicitud
orden = msg.decode().strip().upper()
print("MQTT recibido:", orden)
if orden == "CRUZAR":
solicitud = True # la misma bandera que el boton
elif orden == "ESTADO":
publicar_estado() # responde con el estado actual
else:
print("Comando desconocido")
def conectar_mqtt():
global cliente, mqtt_ok, ultimo_ping
try:
cliente = MQTTClient(CLIENTE_ID, BROKER, port=PUERTO, keepalive=60)
cliente.set_callback(al_mensaje)
cliente.set_last_will(TOPICO_CONEXION, b"offline", retain=True)
cliente.connect()
cliente.subscribe(TOPICO_CMD)
mqtt_ok = True
ultimo_ping = time.ticks_ms()
publicar(TOPICO_CONEXION, "online", True)
publicar_estado()
print("MQTT conectado a", BROKER)
print("Comandos en:", TOPICO_CMD.decode())
except OSError as e:
print("No se pudo conectar al broker:", e)
mqtt_ok = False
def atender_red():
global mqtt_ok, ultimo_ping, ultimo_intento
ahora = time.ticks_ms()
if mqtt_ok:
try:
cliente.check_msg() # revisa comandos sin bloquear
if time.ticks_diff(ahora, ultimo_ping) >= T_PING:
cliente.ping() # "sigo vivo" para el broker
ultimo_ping = ahora
except OSError as e:
print("Conexion MQTT perdida:", e)
mqtt_ok = False
ultimo_intento = ahora
elif estado == VEH_VERDE: # reconectar solo en reposo
if time.ticks_diff(ahora, ultimo_intento) >= T_REINTENTO:
ultimo_intento = ahora
if wlan.isconnected():
print("Reintentando MQTT...")
conectar_mqtt()
else:
print("Sin WiFi: se reintenta en 10 s")
try:
wlan.connect(WIFI_SSID, WIFI_CLAVE) # no espera
except OSError:
pass # ya estaba intentando
# ---------------------------------------------------------------
# SECCION 6. Bucle principal (no bloqueante)
# ---------------------------------------------------------------
ultimo_pitido = 0
ultimo_segundo = -1
cambiar_estado(VEH_VERDE)
if conectar_wifi(): # NUEVO: conectar antes del bucle
conectar_mqtt()
else:
print("Sin red: el semaforo funciona solo con el boton")
while True:
ahora = time.ticks_ms()
transcurrido = time.ticks_diff(ahora, inicio_estado)
if estado == VEH_VERDE:
if solicitud:
solicitud = False
print("Solicitud de cruce aceptada")
cambiar_estado(VEH_AMARILLO)
elif estado == VEH_AMARILLO:
if transcurrido >= T_AMARILLO:
cambiar_estado(TODO_ROJO)
elif estado == TODO_ROJO:
if transcurrido >= T_TODO_ROJO:
cambiar_estado(CRUCE)
ultimo_segundo = -1
ultimo_pitido = time.ticks_ms()
sonar(True) # primer pitido al iniciar el cruce
elif estado == CRUCE:
restante = T_CRUCE - transcurrido
if restante <= 0:
cambiar_estado(DESPEJE)
else:
segundos = (restante + 999) // 1000 # redondeo hacia arriba
if segundos != ultimo_segundo:
ultimo_segundo = segundos
pantalla.numero(segundos)
print("Tiempo para cruzar:", segundos, "s")
publicar_estado() # NUEVO: cuenta por MQTT
desde_pitido = time.ticks_diff(ahora, ultimo_pitido)
if desde_pitido >= intervalo_pitido(restante):
ultimo_pitido = ahora
sonar(True)
elif desde_pitido >= DUR_PITIDO:
sonar(False)
elif estado == DESPEJE:
if transcurrido >= T_DESPEJE:
solicitud = False # se ignoran los toques durante el ciclo
cambiar_estado(VEH_VERDE)
atender_red() # NUEVO: comandos, ping y reconexion
time.sleep_ms(10)