from machine import Pin, I2C, ADC
from utime import sleep_ms, ticks_ms, ticks_diff
from ssd1306 import SSD1306_I2C
import dht
import math
# =============================================================================
# EcoPulse 9 - ESP32 + DHT22 + LDR + OLED SSD1306 + HUD
# Compatible con el diagram.json de Wokwi proporcionado.
# =============================================================================
# En Wokwi el diagram.json usa un DHT22.
MODE = "WOKWI" # "WOKWI" para DHT22 | "PHYSICAL" para DHT11
DHT_READ_INTERVAL_MS = 2000 # El DHT necesita tiempo entre lecturas
LDR_READ_INTERVAL_MS = 1500
FRAME_INTERVAL_MS = 90 # ~11 FPS para animaciones suaves
BUTTON_DEBOUNCE_MS = 180
LED_FEEDBACK_MS = 140
ADC_MAX_VALUE = 4095
LDR_INVERTED = False # Cambiar a True si luz/oscuridad queda invertida
LDR_SAMPLES = 8
# El montaje con LDR no es un luxometro calibrado.
# Este valor SOLO crea una escala visual compatible con oled_campus_hud.py.
# Pantalla 4 muestra el dato correcto como porcentaje relativo + ADC.
HUD_LUX_VISUAL_MAX = 500
STATION_ID = "EP9"
TOTAL_SCREENS = 5
# =============================================================================
# 2) MAPA DE PINES
# =============================================================================
DHT_GPIO = 15
LDR_ADC_GPIO = 34
SCL_PIN_GPIO = 18
SDA_PIN_GPIO = 19
# Boton rojo: pulldown, pulsado = 1
BUTTON_BACK_GPIO = 5
BUTTON_BACK_ACTIVE = 1
# Boton verde: pullup, pulsado = 0
BUTTON_NEXT_GPIO = 2
BUTTON_NEXT_ACTIVE = 0
# LEDs
RED_LED_GPIO = 22
GREEN_LED_GPIO = 21
# OLED según diagram.json
OLED_WIDTH = 128
OLED_HEIGHT = 64
OLED_ADDRESS = 0x3C
# Estado del sistema
temperatura = None
humedad = None
dht_error = None
ldr_raw = 0
ldr_pct = 0
ldr_error = None
estado_luz = "Sin datos"
# =============================================================================
# FUNCIONES OLED
# =============================================================================
def dibujar_rect_lleno(oled, x, y, ancho, alto, color):
"""Dibuja un rectángulo usando únicamente pixel(), compatible con el driver dado."""
for yy in range(y, y + alto):
for xx in range(x, x + ancho):
if 0 <= xx < OLED_WIDTH and 0 <= yy < OLED_HEIGHT:
oled.pixel(xx, yy, color)
def dibujar_linea_horizontal(oled, x, y, longitud, color):
"""Dibuja una línea horizontal usando pixel()."""
for xx in range(x, x + longitud):
if 0 <= xx < OLED_WIDTH and 0 <= y < OLED_HEIGHT:
oled.pixel(xx, y, color)
def dibujar_elipse(oled, cx, cy, rx, ry, color=1):
"""Dibuja una elipse sin depender de métodos extra del driver SSD1306."""
for grado in range(0, 360, 4):
angulo = math.radians(grado)
x = int(cx + rx * math.cos(angulo))
y = int(cy + ry * math.sin(angulo))
if 0 <= x < OLED_WIDTH and 0 <= y < OLED_HEIGHT:
oled.pixel(x, y, color)
def mostrar_mensaje(oled, linea1="", linea2="", linea3="", linea4=""):
"""Muestra cuatro líneas de texto."""
oled.fill(0)
oled.text(linea1, 0, 0, 1)
oled.text(linea2, 0, 16, 1)
oled.text(linea3, 0, 32, 1)
oled.text(linea4, 0, 48, 1)
oled.show()
def mostrar_hud(oled, temperatura, humedad, ldr_pct, estado_luz):
"""
HUD sencillo y compatible con el SSD1306 proporcionado.
No depende de oled_campus_hud.py.
"""
oled.fill(0)
oled.text("ECOPULSE 9", 0, 0, 1)
# Temperatura y humedad
if temperatura is None:
oled.text("T: --.- C", 0, 12, 1)
else:
oled.text("T: " + str(temperatura) + " C", 0, 12, 1)
if humedad is None:
oled.text("H: -- %", 68, 12, 1)
else:
oled.text("H: " + str(humedad) + " %", 68, 12, 1)
# LDR
oled.text("LUZ: " + str(ldr_pct) + "%", 0, 24, 1)
oled.text(estado_luz, 68, 24, 1)
# Barra de iluminación
dibujar_rect_lleno(oled, 0, 38, 100, 8, 1)
ancho_barra = int((ldr_pct / 100) * 96)
if ancho_barra > 0:
dibujar_rect_lleno(oled, 2, 40, ancho_barra, 4, 0)
# Separador y estado
dibujar_linea_horizontal(oled, 0, 51, 128, 1)
if dht_error is None and ldr_error is None:
oled.text("Sensores OK", 0, 54, 1)
elif dht_error is not None and ldr_error is not None:
oled.text("Error DHT + LDR", 0, 54, 1)
elif dht_error is not None:
oled.text("Error DHT", 0, 54, 1)
else:
oled.text("Error LDR", 0, 54, 1)
oled.show()
def animacion_tierra(oled, duracion_ms=4000):
"""Animación inicial compatible con el driver proporcionado."""
inicio = ticks_ms()
while ticks_diff(ticks_ms(), inicio) < duracion_ms:
t = ticks_ms() // 120
oled.fill(0)
cx = 64
cy = 30
radio = 18
# Tierra
for y in range(-radio, radio + 1):
xspan = int((radio * radio - y * y) ** 0.5)
dibujar_linea_horizontal(
oled,
cx - xspan,
cy + y,
xspan * 2 + 1,
1
)
# Continentes estilizados, usando rectángulos propios.
desplazamiento = t % 16
dibujar_rect_lleno(
oled,
cx - 12 + desplazamiento - 8,
cy - 8,
7,
4,
0
)
dibujar_rect_lleno(
oled,
cx - 7 + desplazamiento - 8,
cy - 1,
10,
4,
0
)
dibujar_rect_lleno(
oled,
cx + desplazamiento - 8,
cy - 10,
6,
5,
0
)
dibujar_rect_lleno(
oled,
cx + 3 + desplazamiento - 8,
cy + 3,
5,
4,
0
)
# Órbita
dibujar_elipse(oled, cx, cy, 27, 21, 1)
# Luna
angulo = math.radians((ticks_ms() // 40) % 360)
mx = int(cx + 27 * math.cos(angulo))
my = int(cy + 21 * math.sin(angulo))
dibujar_rect_lleno(oled, mx - 1, my - 1, 3, 3, 1)
# Estrellas
for i in range(14):
sx = (i * 19 + t * 2) % OLED_WIDTH
sy = (i * 11 + 7) % OLED_HEIGHT
oled.pixel(sx, sy, 1)
oled.text("ECOPULSE 9", 24, 2, 1)
oled.text("PLANETA TIERRA", 12, 54, 1)
oled.show()
sleep_ms(50)
# =============================================================================
# FUNCIONES DE SENSORES
# =============================================================================
def leer_dht(sensor):
"""Lee temperatura y humedad del sensor DHT."""
try:
sensor.measure()
return sensor.temperature(), sensor.humidity(), None
except OSError as error:
return None, None, error
except Exception as error:
return None, None, error
def leer_ldr(sensor):
"""
Obtiene un promedio de 8 muestras de la LDR.
Retorna:
raw ADC, porcentaje 0-100, error
"""
try:
total = 0
cantidad_muestras = 8
for _ in range(cantidad_muestras):
if hasattr(sensor, "read"):
total += sensor.read()
else:
total += sensor.read_u16() >> 4
sleep_ms(10)
valor_raw = total // cantidad_muestras
porcentaje = round(
(valor_raw / ADC_MAX_VALUE) * 100
)
if LDR_INVERTED:
porcentaje = 100 - porcentaje
porcentaje = max(0, min(100, porcentaje))
return valor_raw, porcentaje, None
except Exception as error:
return None, None, error
def clasificar_luz(porcentaje):
"""Clasifica el nivel relativo de iluminación."""
if porcentaje < 30:
return "Baja"
elif porcentaje < 70:
return "Media"
return "Alta"
def visual_lux_from_pct(porcentaje):
"""Convierte el porcentaje de LDR en una escala visual de 0 a HUD_LUX_VISUAL_MAX."""
porcentaje = clamp(porcentaje, 0, 100)
return int((porcentaje / 100) * HUD_LUX_VISUAL_MAX)
# =============================================================================
# INICIALIZACIÓN I2C / OLED
# =============================================================================
print("Iniciando EcoPulse 9...")
print("Configuracion:")
print("DHT GPIO:", DHT_GPIO)
print("LDR GPIO:", LDR_ADC_GPIO)
print("OLED SCL:", SCL_PIN_GPIO)
print("OLED SDA:", SDA_PIN_GPIO)
i2c = I2C(
0,
scl=Pin(SCL_PIN_GPIO),
sda=Pin(SDA_PIN_GPIO),
freq=400000
)
print("Escaneando bus I2C...")
dispositivos = i2c.scan()
print(
"Dispositivos encontrados:",
[hex(dispositivo) for dispositivo in dispositivos]
)
if OLED_ADDRESS not in dispositivos:
print(
"ADVERTENCIA: no se encontro OLED en",
hex(OLED_ADDRESS)
)
if dispositivos:
print("Se usara el primer dispositivo encontrado.")
oled_address = dispositivos[0]
else:
raise RuntimeError(
"No se detecto ningun dispositivo I2C. "
"Revisa VCC, GND, SCL=18 y SDA=19."
)
else:
oled_address = OLED_ADDRESS
print("Usando direccion OLED:", hex(oled_address))
oled = SSD1306_I2C(
OLED_WIDTH,
OLED_HEIGHT,
i2c,
addr=oled_address
)
# =============================================================================
# INICIALIZACIÓN DHT
# =============================================================================
if MODE == "WOKWI":
# El diagram.json proporcionado utiliza un DHT22.
sensor_dht = dht.DHT22(Pin(DHT_GPIO))
print("Sensor DHT configurado: DHT22")
else:
# Para montaje físico con DHT11.
sensor_dht = dht.DHT11(Pin(DHT_GPIO))
print("Sensor DHT configurado: DHT11")
# =============================================================================
# INICIALIZACIÓN LDR
# =============================================================================
ldr = ADC(Pin(LDR_ADC_GPIO))
try:
ldr.atten(ADC.ATTN_11DB)
except Exception as error:
print("Aviso: no se pudo configurar ATTN_11DB:", error)
try:
ldr.width(ADC.WIDTH_12BIT)
except Exception as error:
print("Aviso: no se pudo configurar WIDTH_12BIT:", error)
# === PRUEBA RÁPIDA ===
print("=== Prueba ADC GPIO4 ===")
for i in range(10):
raw = ldr.read()
print("Lectura", i, "→", raw)
sleep_ms(300)
print("========================")
# =============================================================================
# INICIALIZACIÓN BOTONES Y LEDS
# =============================================================================
button_back = Pin(BUTTON_BACK_GPIO, Pin.IN, Pin.PULL_DOWN)
button_next = Pin(BUTTON_NEXT_GPIO, Pin.IN, Pin.PULL_UP)
led_red = Pin(RED_LED_GPIO, Pin.OUT)
led_green = Pin(GREEN_LED_GPIO, Pin.OUT)
led_red.value(0)
led_green.value(0)
# =============================================================================
# HELPERS GRAFICOS
# =============================================================================
def clamp(value, low, high):
if value < low:
return low
if value > high:
return high
return value
def centered_text(fb, text, y, color=1):
text = str(text)
x = (OLED_WIDTH - len(text) * 8) // 2
if x < 0:
x = 0
fb.text(text, x, y, color)
def filled_circle(fb, cx, cy, r, color=1):
y = -r
while y <= r:
span = int((r * r - y * y) ** 0.5)
fb.hline(cx - span, cy + y, span * 2 + 1, color)
y += 1
def draw_degree(fb, x, y, color=1):
fb.pixel(x, y, color)
fb.pixel(x + 1, y, color)
fb.pixel(x, y + 1, color)
fb.pixel(x + 1, y + 1, color)
def draw_sun(fb, cx, cy, radius, now_ms):
filled_circle(fb, cx, cy, radius, 1)
pulse = (now_ms // 220) % 2
ray = radius + 3 + pulse
fb.hline(cx - ray - 3, cy, 3, 1)
fb.hline(cx + ray + 1, cy, 3, 1)
fb.vline(cx, cy - ray - 3, 3, 1)
fb.vline(cx, cy + ray + 1, 3, 1)
fb.pixel(cx - ray, cy - ray, 1)
fb.pixel(cx + ray, cy - ray, 1)
fb.pixel(cx - ray, cy + ray, 1)
fb.pixel(cx + ray, cy + ray, 1)
def draw_happy_face(fb, cx, cy, radius, now_ms):
"""
Cara feliz simple para OLED 128x64.
Sustituye al sol en la pantalla de temperatura.
"""
filled_circle(fb, cx, cy, radius, 1)
# Ojos
fb.fill_rect(cx - 3, cy - 2, 2, 2, 0)
fb.fill_rect(cx + 2, cy - 2, 2, 2, 0)
# Sonrisa animada
smile_shift = (now_ms // 300) % 2
fb.pixel(cx - 3, cy + 2 + smile_shift, 0)
fb.pixel(cx - 2, cy + 3 + smile_shift, 0)
fb.pixel(cx - 1, cy + 4 + smile_shift, 0)
fb.pixel(cx, cy + 4 + smile_shift, 0)
fb.pixel(cx + 1, cy + 4 + smile_shift, 0)
fb.pixel(cx + 2, cy + 3 + smile_shift, 0)
fb.pixel(cx + 3, cy + 2 + smile_shift, 0)
def draw_moon(fb, cx, cy, radius):
filled_circle(fb, cx, cy, radius, 1)
filled_circle(fb, cx + max(2, radius // 2), cy - 1, radius, 0)
def draw_cloud(fb, x, y):
filled_circle(fb, x + 7, y + 7, 5, 1)
filled_circle(fb, x + 16, y + 5, 7, 1)
filled_circle(fb, x + 27, y + 7, 5, 1)
fb.fill_rect(x + 6, y + 7, 23, 8, 1)
def draw_drop_outline(fb, cx, cy, size=9):
# Punta de la gota
fb.line(cx, cy - size, cx - size // 2, cy, 1)
fb.line(cx, cy - size, cx + size // 2, cy, 1)
# Parte redondeada inferior
filled_circle(fb, cx, cy + 3, size // 2, 1)
filled_circle(fb, cx, cy + 2, max(1, size // 2 - 2), 0)
def draw_screen_indicator(fb, current):
# Cinco puntos en la esquina inferior derecha.
x0 = 95
y = 61
for i in range(TOTAL_SCREENS):
x = x0 + i * 7
if i == current:
fb.fill_rect(x, y - 2, 4, 3, 1)
else:
fb.pixel(x + 1, y - 1, 1)
# =============================================================================
# PANTALLA 2 - TEMPERATURA
# =============================================================================
def render_temperature(oled, temp, error, now_ms):
fb = oled.framebuf
fb.fill(0)
fb.text("2/5 TEMP", 0, 0, 1)
if temp is None:
centered_text(fb, "DHT ERROR", 25)
fb.text("GPIO15", 36, 41, 1)
draw_screen_indicator(fb, 1)
oled.show()
return
temp_f = float(temp)
# Termometro
tx = 19
top = 14
height = 33
fb.rect(tx, top, 10, height, 1)
filled_circle(fb, tx + 5, 50, 7, 1)
filled_circle(fb, tx + 5, 50, 4, 0)
p = clamp((temp_f - 10.0) / 30.0, 0.0, 1.0)
mercury_h = int(28 * p)
mercury_y = top + 29 - mercury_h
fb.fill_rect(tx + 3, mercury_y, 4, mercury_h + 3, 1)
filled_circle(fb, tx + 5, 50, 3, 1)
for y in range(top + 4, top + 29, 6):
fb.hline(tx + 10, y, 4, 1)
# Cara feliz animada
radius = 8 + ((now_ms // 260) % 2)
draw_happy_face(fb, 92, 27, radius, now_ms)
# Ondas de calor
for i in range(3):
x = 54 + i * 13
y = 18 + ((now_ms // 130 + i * 4) % 18)
fb.pixel(x, y, 1)
fb.pixel(x + 1, y - 1, 1)
fb.pixel(x + 2, y, 1)
value = "{:.1f}".format(temp_f)
fb.text(value, 55, 45, 1)
draw_degree(
fb,
55 + len(value) * 8 + 1,
46,
1
)
fb.text(
"C",
55 + len(value) * 8 + 5,
45,
1
)
draw_screen_indicator(fb, 1)
oled.show()
# =============================================================================
# PANTALLA 3 - HUMEDAD
# =============================================================================
def render_humidity(oled, hum, error, now_ms):
fb = oled.framebuf
fb.fill(0)
fb.text("3/5 HUMEDAD", 0, 0, 1)
if hum is None:
centered_text(fb, "DHT ERROR", 25)
fb.text("GPIO15", 36, 41, 1)
draw_screen_indicator(fb, 2)
oled.show()
return
hum_f = clamp(float(hum), 0.0, 100.0)
# Nube con movimiento horizontal
phase = (now_ms // 120) % 48
if phase <= 24:
cloud_x = 7 + phase
else:
cloud_x = 7 + (48 - phase)
cloud_y = 13 + ((now_ms // 350) % 2)
draw_cloud(fb, cloud_x, cloud_y)
# HUMEDAD <= 40 -> lluvia
if hum_f <= 40:
drop_count = 1 + int(hum_f / 15)
if drop_count > 4:
drop_count = 4
fall = (now_ms // 90) % 19
for i in range(drop_count):
dx = cloud_x + 8 + i * 7
dy = cloud_y + 15 + ((fall + i * 5) % 20)
if dy < 53:
fb.pixel(dx, dy, 1)
if dy + 1 < 53:
fb.pixel(dx, dy + 1, 1)
# HUMEDAD > 40 -> truenos
else:
blink = (now_ms // 180) % 2
if blink:
bolt_x = cloud_x + 15
bolt_y = cloud_y + 14
fb.line(bolt_x, bolt_y, bolt_x - 3, bolt_y + 6, 1)
fb.line(bolt_x - 3, bolt_y + 6, bolt_x + 1, bolt_y + 6, 1)
fb.line(bolt_x + 1, bolt_y + 6, bolt_x - 2, bolt_y + 12, 1)
if hum_f > 70:
fb.line(
bolt_x + 8,
bolt_y + 1,
bolt_x + 5,
bolt_y + 7,
1
)
fb.line(
bolt_x + 5,
bolt_y + 7,
bolt_x + 9,
bolt_y + 7,
1
)
fb.line(
bolt_x + 9,
bolt_y + 7,
bolt_x + 6,
bolt_y + 13,
1
)
# Gota grande pulsante
drop_y = 28 + ((now_ms // 300) % 2)
draw_drop_outline(
fb,
100,
drop_y,
11
)
value = "{}%".format(int(hum_f))
fb.text(value, 78, 47, 1)
fb.rect(5, 55, 65, 7, 1)
fill_w = int(63 * hum_f / 100.0)
if fill_w > 0:
fb.fill_rect(
6,
56,
fill_w,
5,
1
)
draw_screen_indicator(fb, 2)
oled.show()
# =============================================================================
# PANTALLA 4 - LUZ / LUMINANCIA RELATIVA
# =============================================================================
def render_light(oled, pct, raw, error, now_ms):
fb = oled.framebuf
fb.fill(0)
fb.text("4/5 LUZ", 0, 0, 1)
if error is not None:
centered_text(fb, "LDR ERROR", 25)
fb.text("GPIO34", 36, 41, 1)
draw_screen_indicator(fb, 3)
oled.show()
return
pct = int(clamp(pct, 0, 100))
# Horizonte
fb.hline(0, 43, 128, 1)
if pct >= 55:
# A mayor iluminacion, el sol crece.
radius = 4 + int((pct - 55) * 5 / 45)
radius += (now_ms // 320) % 2
draw_sun(fb, 86, 25, radius, now_ms)
# Rayos ascendentes / energia
for i in range(4):
px = 13 + i * 12
py = 33 - ((now_ms // 110 + i * 3) % 12)
fb.pixel(px, py, 1)
fb.text("SOL", 4, 17, 1)
elif pct >= 35:
# Transicion: disco pequeno en el horizonte.
radius = 3 + ((now_ms // 350) % 2)
filled_circle(fb, 86, 38, radius, 1)
fb.text("TRANSICION", 4, 17, 1)
else:
# Poca luz: luna creciente + estrellas parpadeantes.
draw_moon(fb, 88, 25, 8)
twinkle = (now_ms // 260) % 2
stars = ((18, 17), (36, 28), (55, 15), (112, 20), (104, 34))
for i, pos in enumerate(stars):
if (i % 2) == twinkle:
fb.pixel(pos[0], pos[1], 1)
fb.text("LUNA", 4, 17, 1)
fb.text("LUZ:{}%".format(pct), 4, 47, 1)
fb.text(clasificar_luz(pct), 68, 47, 1)
# ADC en linea inferior; no se llama lux porque no hay calibracion fotometrica.
fb.text("ADC:{}".format(raw), 4, 56, 1)
draw_screen_indicator(fb, 3)
oled.show()
# =============================================================================
# PANTALLA 5 - AMDG EN LOS CUATRO PUNTOS DE UNA +
# =============================================================================
def render_amdg(oled, now_ms):
fb = oled.framebuf
fb.fill(0)
cx = 64
cy = 32
# Letras alrededor de la intersección
left_x = 51
right_x = 69
top_y = 20
bottom_y = 36
# Cruz compacta
h_x0 = 47
h_x1 = 81
v_y0 = 16
v_y1 = 48
# Línea horizontal
fb.fill_rect(
h_x0,
cy - 1,
h_x1 - h_x0 + 1,
2,
1
)
# Línea vertical
fb.fill_rect(
cx - 1,
v_y0,
2,
v_y1 - v_y0 + 1,
1
)
# Intersección
fb.fill_rect(
cx - 2,
cy - 2,
4,
4,
1
)
# Letras en los cuatro cuadrantes
fb.text("A", left_x, top_y, 1)
fb.text("M", right_x, top_y, 1)
fb.text("D", left_x, bottom_y, 1)
fb.text("G", right_x, bottom_y, 1)
# Indicador de pantalla, fuera del símbolo
fb.text("5/5", 101, 54, 1)
# Animación discreta que NO modifica AMDG
if (now_ms // 450) % 2 == 0:
fb.pixel(96, 57, 1)
fb.pixel(97, 57, 1)
oled.show()
# =============================================================================
# NAVEGACION / FEEDBACK
# =============================================================================
def short_transition(oled):
# Flash muy corto al cambiar de pantalla.
try:
oled.invert(1)
sleep_ms(28)
oled.invert(0)
except Exception:
pass
def start_feedback(red=False, green=False):
led_red.value(1 if red else 0)
led_green.value(1 if green else 0)
def stop_feedback():
led_red.value(0)
led_green.value(0)
# =============================================================================
# ARRANQUE
# =============================================================================
animacion_tierra(oled, 4000)
mostrar_mensaje(
oled,
"EcoPulse 9",
"Sistema listo",
"DHT: GPIO" + str(DHT_GPIO),
"LDR: GPIO" + str(LDR_ADC_GPIO)
)
sleep_ms(2000)
# Primera lectura inmediata.
ultimo_dht_ms = ticks_ms() - DHT_READ_INTERVAL_MS
ultimo_ldr_ms = ticks_ms() - LDR_READ_INTERVAL_MS
# Estado inicial de navegación
screen = 0
last_back_value = button_back.value()
last_next_value = button_next.value()
last_button_ms = ticks_ms() - BUTTON_DEBOUNCE_MS
feedback_started_ms = 0
feedback_active = False
last_frame_ms = ticks_ms() - FRAME_INTERVAL_MS
# =============================================================================
# CICLO PRINCIPAL
# =============================================================================
while True:
now_ms = ticks_ms()
actualizar_oled = False
# -------------------------------------------------------------------------
# DHT
# -------------------------------------------------------------------------
if ticks_diff(now_ms, ultimo_dht_ms) >= DHT_READ_INTERVAL_MS:
ultimo_dht_ms = now_ms
nueva_temperatura, nueva_humedad, nuevo_error = leer_dht(sensor_dht)
if nuevo_error is None:
temperatura = nueva_temperatura
humedad = nueva_humedad
dht_error = None
print(
"Temperatura:",
temperatura,
"C | Humedad:",
humedad,
"%"
)
else:
dht_error = nuevo_error
print("Error leyendo DHT:", dht_error)
actualizar_oled = True
# -------------------------------------------------------------------------
# LDR
# -------------------------------------------------------------------------
if ticks_diff(now_ms, ultimo_ldr_ms) >= LDR_READ_INTERVAL_MS:
ultimo_ldr_ms = now_ms
nuevo_raw, nuevo_pct, nuevo_error = leer_ldr(ldr)
if nuevo_error is None:
ldr_raw = nuevo_raw
ldr_pct = nuevo_pct
ldr_error = None
estado_luz = clasificar_luz(ldr_pct)
print(
"LDR ADC:",
ldr_raw,
"| Iluminacion:",
ldr_pct,
"% | Nivel:",
estado_luz
)
else:
ldr_error = nuevo_error
print("Error leyendo LDR:", ldr_error)
actualizar_oled = True
# -------------------------------------------------------------------------
# Botones con deteccion de flanco + debounce
# -------------------------------------------------------------------------
back_value = button_back.value()
next_value = button_next.value()
back_pressed = (
back_value == BUTTON_BACK_ACTIVE
and last_back_value != BUTTON_BACK_ACTIVE
)
next_pressed = (
next_value == BUTTON_NEXT_ACTIVE
and last_next_value != BUTTON_NEXT_ACTIVE
)
can_navigate = ticks_diff(now_ms, last_button_ms) >= BUTTON_DEBOUNCE_MS
if can_navigate and back_pressed:
last_button_ms = now_ms
screen = (screen - 1) % TOTAL_SCREENS
start_feedback(red=True)
feedback_started_ms = now_ms
feedback_active = True
short_transition(oled)
print("Pantalla:", screen + 1)
elif can_navigate and next_pressed:
last_button_ms = now_ms
screen = (screen + 1) % TOTAL_SCREENS
start_feedback(green=True)
feedback_started_ms = now_ms
feedback_active = True
short_transition(oled)
print("Pantalla:", screen + 1)
last_back_value = back_value
last_next_value = next_value
# Apaga LED de feedback sin detener las animaciones.
if feedback_active and ticks_diff(now_ms, feedback_started_ms) >= LED_FEEDBACK_MS:
stop_feedback()
feedback_active = False
# -------------------------------------------------------------------------
# Render de animaciones
# -------------------------------------------------------------------------
if ticks_diff(now_ms, last_frame_ms) >= FRAME_INTERVAL_MS:
last_frame_ms = now_ms
if screen == 0:
mostrar_hud(
oled,
temperatura,
humedad,
ldr_pct,
estado_luz
)
elif screen == 1:
render_temperature(
oled,
temperatura,
dht_error,
now_ms
)
elif screen == 2:
render_humidity(
oled,
humedad,
dht_error,
now_ms
)
elif screen == 3:
render_light(
oled,
ldr_pct,
ldr_raw,
ldr_error,
now_ms
)
else:
render_amdg(oled, now_ms)
sleep_ms(20)