"""
MICROPYTHON ESTACIÓN METEOROLÓGICA IoT
Autor: Estudiante Anónimo - Versión: 3.3
DESCRIPCIÓN GENERAL:
Sistema IoT integrado que combina lectura ambiental, actuación física y transmisión de datos en tiempo real.
Funcionalidades principales:
1. Monitoreo continuo de temperatura/humedad con sensor DHT22
2. Control proporcional de servomotor basado en umbrales térmicos con histéresis
3. Comunicación bidireccional mediante protocolo MQTT
4. Gestión automática de conexiones WiFi/MQTT con reconexión inteligente
5. Optimización de energía mediante publicación selectiva de datos
6. Movimiento suave del servo con velocidades configurables
7. Protección contra valores inválidos y manejo de errores robusto
"""
# ============ IMPORTACIÓN DE LIBRERÍAS ============
from machine import Pin, PWM # Control de pines GPIO y PWM (GPIO15 para servo)
import dht # Driver para sensor DHT22 (±0.5°C de precisión)
import network # Gestión de conexión WiFi (modo estación)
from umqtt.simple import MQTTClient # Cliente MQTT (QoS 0 para IoT)
import time # Temporización precisa (ms y us)
import ujson # Serialización JSON optimizada (menor RAM)
# ============ CONFIGURACIÓN SERVOMOTOR ============
SERVO_PIN = 15 # GPIO15 para señal PWM del servo (5V lógicos)
PWM_FREQ = 50 # Frecuencia estándar servos (periodo 20ms)
PWM_BITS = 16 # Resolución PWM 16-bit (0-65535 pasos)
MAX_DUTY = 65535 # Valor máximo duty cycle (100% ancho de pulso)
MIN_PULSE_US = 500 # Ancho pulso mínimo (0.5ms = posición 0°)
MAX_PULSE_US = 2400 # Ancho pulso máximo (2.4ms = posición 180°)
PWM_PERIOD_US = 20000 # Periodo completo PWM (20,000μs = 20ms)
SPEED_UP = 4 # Tiempo por paso al subir (4ms/° → 720ms/180°)
SPEED_DOWN = 1 # Tiempo por paso al bajar (1ms/° → 180ms/180°)
HYSTERESIS = 2.0 # Margen histéresis (±1°C para evitar oscilaciones)
servo = PWM(Pin(SERVO_PIN), freq=PWM_FREQ, duty_u16=0) # Inicializar PWM
# ============ CONFIGURACIÓN MQTT ============
MQTT_CLIENT_ID = "micropython-weather-demo" # ID único dispositivo IoT
MQTT_BROKER = "broker.mqttdashboard.com" # Broker público sin autenticación
MQTT_TOPIC = "wokwi-weather" # Tópico MQTT para publicación
# ============ SENSOR Y VARIABLES ============
sensor = dht.DHT22(Pin(16)) # Sensor en GPIO16 (protocolo 1-wire)
TEMPERATURE_THRESHOLD = 25.0 # Umbral térmico base (25°C)
servo_active = False # Estado del servo (True/False)
def connect_mqtt():
"""Gestión robusta de conexión MQTT con reconexión automática"""
try:
client = MQTTClient(MQTT_CLIENT_ID, MQTT_BROKER) # Nueva instancia
client.connect() # Handshake TCP/MQTT (timeout 15s)
print("MQTT: Conectado") # Confirmación conexión exitosa
return client
except Exception as e:
print(f"MQTT Error: {e} - Reintentando...") # Log de error detallado
time.sleep(5) # Espera exponencial antes de reintentar
return connect_mqtt() # Llamada recursiva para reconexión
def set_angle(angle):
"""Control preciso de posición angular con validación de parámetros"""
if not isinstance(angle, (int, float)): # Validación tipo numérico
raise TypeError("Ángulo debe ser numérico")
angle = max(0, min(180, angle)) # Limitar rango físico (0-180°)
pulse_us = MIN_PULSE_US + (angle * (MAX_PULSE_US - MIN_PULSE_US) / 180) # Mapeo lineal
duty = int(pulse_us * MAX_DUTY / PWM_PERIOD_US) # Cálculo valor PWM
servo.duty_u16(duty) # Escritura directa en registros
def move_servo_intermittent():
"""Movimiento completo del servo sin interrupciones"""
for ang in range(0, 181, 1): # Barrido ascendente 0°→180° (paso 1°)
set_angle(ang) # Actualizar posición
time.sleep_ms(SPEED_UP) # Mantener posición (4ms/°)
for ang in range(180, -1, -1): # Barrido descendente 180°→0° (paso -1°)
set_angle(ang) # Actualizar posición
time.sleep_ms(SPEED_DOWN) # Mantener posición (1ms/°)
# ============ CONEXIÓN WIFI ============
print("Conectando WiFi...", end="") # UI: Inicio proceso conexión
wifi = network.WLAN(network.STA_IF) # Configurar interfaz como cliente
wifi.active(True) # Activar controlador WiFi
wifi.connect('Wokwi-GUEST', '') # Conectar a red abierta
while not wifi.isconnected(): # Esperar conexión exitosa
print(".", end="") # Indicador progreso visual
time.sleep(0.1) # Pausa no bloqueante (100ms)
print(" WiFi: Conectado") # Confirmación conexión + IP
# ============ BUCLE PRINCIPAL ============
client = connect_mqtt() # Conexión inicial MQTT
last_data = "" # Registro último payload enviado
while True: # Bucle infinito de monitoreo
try:
sensor.measure() # Lectura síncrona del sensor (≈2ms)
temp = sensor.temperature() # Temperatura actual (float 1 decimal)
hum = sensor.humidity() # Humedad actual (float 1 decimal)
# Cálculo de umbrales dinámicos con histéresis
upper = TEMPERATURE_THRESHOLD + HYSTERESIS/2 # Límite superior
lower = TEMPERATURE_THRESHOLD - HYSTERESIS/2 # Límite inferior
# Lógica de control de estado del servo
if temp > upper and not servo_active: # Condición activación
print(f"ACTIVAR: {temp} > {upper}") # Log térmico
servo_active = True # Cambiar estado
elif temp < lower and servo_active: # Condición desactivación
print(f"DESACTIVAR: {temp} < {lower}") # Log térmico
servo_active = False # Cambiar estado
if servo_active: # Ejecutar movimiento si está activo
move_servo_intermittent() # Rutina de barrido completo
data = ujson.dumps({ # Serialización JSON eficiente
"temp": temp, # Temperatura actual
"humidity": hum, # Humedad relativa
"servo_active": servo_active, # Estado actuador
"hysteresis": HYSTERESIS # Margen aplicado
})
if data != last_data: # Publicar solo si hay cambios
print(f"PUBLICANDO: {data}") # Log de transmisión
client.publish(MQTT_TOPIC, data) # Envío MQTT
last_data = data # Actualizar registro
else:
print("SIN CAMBIOS - No se publica") # Optimización ancho de banda
except OSError as e: # Manejo de excepciones
print(f"ERROR: {e} - Reconectando...") # Log detallado
client = connect_mqtt() # Reconexión automática MQTT
time.sleep(1) # Intervalo entre ciclos (1 segundo)