# ============================================================
# main.py - Reloj con:
# - CLOCK (HH:MM:SS) editable (BTN2 +1h, BTN3 +1min)
# - CLOCK-AMPM editable (BTN2 +1h, BTN3 +1min)
# - ALARM (hora/minuto en 12h AM/PM, pasos de 5 min)
# - STOPWATCH
# - TIMER (ajustable, cuenta regresiva)
# - Melodía no bloqueante (melody.py)
# - Ahorro energía:
# * Auto por inactividad (30s sin botones/comandos)
# * Auto por LDR (día/noche)
# * Control desde app: AUTO_IDLE ON/OFF, AUTO_LDR ON/OFF, BRIGHT n
# - BLE UART (ESP32-C3) para control desde app
# ============================================================
import time
from machine import Pin, SPI
import max7219
import inputs
import melody # usa start(), update(), stop()
from ldr_sensor import LDRSensor
from power_save import PowerSave
from ble_uart import BLEUART
# ------------------------------------------------------------
# DISPLAY MAX7219
# ------------------------------------------------------------
SCK_PIN = 4
MOSI_PIN = 6
CS_PIN = 7
NUM_MATRICES = 8
spi = SPI(
1,
baudrate=10_000_000,
polarity=1,
phase=0,
sck=Pin(SCK_PIN),
mosi=Pin(MOSI_PIN)
)
cs = Pin(CS_PIN, Pin.OUT)
display = max7219.Matrix8x8(spi, cs, NUM_MATRICES)
def show_text(text):
display.fill(0)
display.text(str(text), 0, 0, 1)
display.show()
def show_time(h, m, s):
display.fill(0)
msg = "{:02d}:{:02d}:{:02d}".format(h, m, s)
display.text(msg, 0, 0, 1)
display.show()
def _format_ampm(h, m):
sufijo = "AM"
if h == 0:
disp_h = 12
elif h < 12:
disp_h = h
elif h == 12:
disp_h = 12
sufijo = "PM"
else:
disp_h = h - 12
sufijo = "PM"
return "{:02d}:{:02d}{}".format(disp_h, m, sufijo)
def show_ampm(h, m):
display.fill(0)
display.text(_format_ampm(h, m), 0, 0, 1)
display.show()
def show_alarm_set(h, m):
display.fill(0)
display.text(_format_ampm(h, m), 0, 0, 1)
display.show()
def show_stopwatch(mm, ss):
display.fill(0)
display.text("{:02d}:{:02d}".format(mm, ss), 0, 0, 1)
display.show()
def show_timer(mm, ss):
display.fill(0)
display.text("{:02d}:{:02d}".format(mm, ss), 0, 0, 1)
display.show()
# ------------------------------------------------------------
# POWER SAVE (LDR + INACTIVIDAD)
# ------------------------------------------------------------
# LDR en GPIO5 (ADC). Si en Wokwi te da "invalid pin", cambia a un ADC válido del modelo simulado.
LDR_GPIO = 5
ldr = LDRSensor(LDR_GPIO)
ps = PowerSave(display.brightness)
ps.brillo_base = 8
ps.auto_idle = True
ps.auto_ldr = True
ps.idle_ms = 30_000
ps.idle_factor = 0.25
ps.ldr_threshold_night = 1500
# ------------------------------------------------------------
# MODOS
# ------------------------------------------------------------
MODO = "CLOCK"
MODOS = ["CLOCK", "AMPM", "ALARM", "STOPWATCH", "TIMER"]
# ------------------------------------------------------------
# CLOCK
# ------------------------------------------------------------
h = 12
m = 0
s = 0
ultimo_tick_clock = time.ticks_ms()
def actualizar_clock():
global h, m, s, ultimo_tick_clock
ahora = time.ticks_ms()
if time.ticks_diff(ahora, ultimo_tick_clock) >= 1000:
ultimo_tick_clock = time.ticks_add(ultimo_tick_clock, 1000)
s += 1
if s >= 60:
s = 0
m += 1
if m >= 60:
m = 0
h = (h + 1) % 24
# ------------------------------------------------------------
# STOPWATCH
# ------------------------------------------------------------
sw_mm = 0
sw_ss = 0
sw_corriendo = False
sw_tick = time.ticks_ms()
def actualizar_stopwatch():
global sw_mm, sw_ss, sw_corriendo, sw_tick
if not sw_corriendo:
sw_tick = time.ticks_ms()
return
ahora = time.ticks_ms()
if time.ticks_diff(ahora, sw_tick) >= 1000:
sw_tick = time.ticks_add(sw_tick, 1000)
sw_ss += 1
if sw_ss >= 60:
sw_ss = 0
sw_mm += 1
# ------------------------------------------------------------
# TIMER (ajustable)
# ------------------------------------------------------------
tm_mm = 0
tm_ss = 0
tm_corriendo = False
tm_tick = time.ticks_ms()
tm_finalizado = False
def reset_timer():
global tm_mm, tm_ss, tm_corriendo, tm_finalizado, tm_tick
tm_mm = 0
tm_ss = 0
tm_corriendo = False
tm_finalizado = False
tm_tick = time.ticks_ms()
def total_segundos_timer():
return tm_mm * 60 + tm_ss
def set_total_segundos_timer(total):
global tm_mm, tm_ss
if total < 0:
total = 0
if total > 99 * 60 + 59:
total = 99 * 60 + 59
tm_mm = total // 60
tm_ss = total % 60
def actualizar_timer():
global tm_corriendo, tm_tick, tm_finalizado, alarm_sonando
if not tm_corriendo or tm_finalizado:
tm_tick = time.ticks_ms()
return
ahora = time.ticks_ms()
if time.ticks_diff(ahora, tm_tick) >= 1000:
tm_tick = time.ticks_add(tm_tick, 1000)
total = total_segundos_timer()
if total > 0:
total -= 1
set_total_segundos_timer(total)
if total == 0:
tm_corriendo = False
tm_finalizado = True
if not alarm_sonando:
alarm_sonando = True
melody.start()
# ------------------------------------------------------------
# ALARMA (hora/minuto)
# ------------------------------------------------------------
alarm_h = 6
alarm_m = 0
alarm_activa = False
alarm_sonando = False # estado general de "hay alarma/timer sonando"
def check_alarm():
global alarm_sonando
if alarm_activa and h == alarm_h and m == alarm_m and s == 0 and not alarm_sonando:
alarm_sonando = True
melody.start()
# ------------------------------------------------------------
# BOTONES
# ------------------------------------------------------------
def procesar_comando(cmd):
global MODO
global sw_corriendo, sw_mm, sw_ss
global tm_corriendo, tm_finalizado, tm_tick, alarm_sonando
global alarm_h, alarm_m, alarm_activa
global h, m, s
ps.mark_activity()
if cmd == "MODE_NEXT":
idx = MODOS.index(MODO)
MODO = MODOS[(idx + 1) % len(MODOS)]
show_text(MODO)
time.sleep(0.5)
return
# Si hay sonido: BTN4 lo apaga
if alarm_sonando:
if cmd == "BTN4":
melody.stop()
alarm_sonando = False
show_text("OFF")
time.sleep(0.3)
return
if MODO == "CLOCK":
if cmd == "BTN2":
h = (h + 1) % 24
elif cmd == "BTN3":
m += 1
if m >= 60:
m = 0
h = (h + 1) % 24
return
if MODO == "AMPM":
if cmd == "BTN2":
h = (h + 1) % 24
elif cmd == "BTN3":
m += 1
if m >= 60:
m = 0
h = (h + 1) % 24
return
if MODO == "ALARM":
if cmd == "BTN2":
alarm_h = (alarm_h + 1) % 24
elif cmd == "BTN3":
alarm_m = (alarm_m + 5) % 60
elif cmd == "BTN4":
alarm_activa = True
show_text("OK")
time.sleep(0.3)
MODO = "CLOCK"
return
show_alarm_set(alarm_h, alarm_m)
return
if MODO == "STOPWATCH":
if cmd == "BTN2":
sw_corriendo = not sw_corriendo
elif cmd == "BTN3":
sw_mm = 0
sw_ss = 0
sw_corriendo = False
return
if MODO == "TIMER":
total = total_segundos_timer()
if not tm_corriendo and not tm_finalizado:
if cmd == "BTN2":
set_total_segundos_timer(total + 1)
elif cmd == "BTN3":
set_total_segundos_timer(total - 1)
elif cmd == "BTN4":
if total > 0:
tm_finalizado = False
tm_corriendo = True
tm_tick = time.ticks_ms()
return
if tm_corriendo:
if cmd == "BTN2":
tm_corriendo = False
elif cmd == "BTN3":
reset_timer()
return
if tm_finalizado:
if cmd in ("BTN2", "BTN3"):
reset_timer()
return
# ------------------------------------------------------------
# COMANDOS DE LA APP (BLE UART) - por líneas
# ------------------------------------------------------------
def procesar_cmd_texto(line):
"""
Comandos ejemplo:
AUTO_IDLE ON / OFF
AUTO_LDR ON / OFF
BRIGHT 10
SET_CLOCK 12 30 00
MODE CLOCK / AMPM / ALARM / STOPWATCH / TIMER
ALARM_SET 6 30
ALARM_ON / ALARM_OFF
TIMER_SET 90
TIMER_START / TIMER_PAUSE / TIMER_RESET
SW_START / SW_PAUSE / SW_RESET
STOP_ALARM
"""
global MODO, h, m, s
global alarm_h, alarm_m, alarm_activa, alarm_sonando
global sw_corriendo, sw_mm, sw_ss
global tm_corriendo, tm_finalizado, tm_tick
ps.mark_activity()
if not line:
return
up = line.strip().upper()
# ahorro
if up == "AUTO_IDLE ON":
ps.auto_idle = True; return
if up == "AUTO_IDLE OFF":
ps.auto_idle = False; return
if up == "AUTO_LDR ON":
ps.auto_ldr = True; return
if up == "AUTO_LDR OFF":
ps.auto_ldr = False; return
if up.startswith("BRIGHT "):
try:
v = int(up.split()[1])
ps.brillo_base = max(0, min(15, v))
except:
pass
return
# clock
if up.startswith("SET_CLOCK "):
try:
parts = up.split()
hh = int(parts[1]); mm = int(parts[2]); ss2 = int(parts[3])
h = hh % 24
m = mm % 60
s = ss2 % 60
except:
pass
return
if up.startswith("MODE "):
try:
modo = up.split()[1]
if modo in MODOS:
MODO = modo
except:
pass
return
# alarma
if up.startswith("ALARM_SET "):
try:
parts = up.split()
alarm_h = int(parts[1]) % 24
alarm_m = int(parts[2]) % 60
except:
pass
return
if up == "ALARM_ON":
alarm_activa = True; return
if up == "ALARM_OFF":
alarm_activa = False; return
# timer
if up.startswith("TIMER_SET "):
try:
total = int(up.split()[1])
tm_corriendo = False
tm_finalizado = False
set_total_segundos_timer(total)
except:
pass
return
if up == "TIMER_START":
if total_segundos_timer() > 0:
tm_finalizado = False
tm_corriendo = True
tm_tick = time.ticks_ms()
return
if up == "TIMER_PAUSE":
tm_corriendo = False; return
if up == "TIMER_RESET":
reset_timer(); return
# stopwatch
if up == "SW_START":
sw_corriendo = True; return
if up == "SW_PAUSE":
sw_corriendo = False; return
if up == "SW_RESET":
sw_mm = 0; sw_ss = 0; sw_corriendo = False
return
# apagar sonido
if up == "STOP_ALARM":
if alarm_sonando:
melody.stop()
alarm_sonando = False
return
# ------------------------------------------------------------
# BLE UART (IMPORTANTE: usar on_line, NO on_rx)
# ------------------------------------------------------------
ble = BLEUART(name="ESP32C3-RELOJ")
if ble.ok():
ble.on_line(procesar_cmd_texto) # <-- CORRECTO (antes tenías on_rx)
# show_text("BLE") # opcional
else:
# show_text("NO BLE") # opcional
pass
# ------------------------------------------------------------
# INICIO
# ------------------------------------------------------------
show_text("INIT")
time.sleep(0.8)
reset_timer()
ps.mark_activity()
ultimo_status = time.ticks_ms()
# ------------------------------------------------------------
# LOOP PRINCIPAL
# ------------------------------------------------------------
while True:
# 1) Botones
comandos = inputs.leer_comandos_botones()
for c in comandos:
procesar_comando(c)
# 2) Ahorro por LDR + inactividad
try:
lval = ldr.read()
ps.apply(lval)
except:
# por si Wokwi/firmware no soporta ADC en ese pin
ps.apply(None)
# 3) Lógicas
actualizar_clock()
actualizar_stopwatch()
actualizar_timer()
check_alarm()
melody.update()
# 4) Mostrar
if MODO == "CLOCK":
show_time(h, m, s)
elif MODO == "AMPM":
show_ampm(h, m)
elif MODO == "ALARM":
show_alarm_set(alarm_h, alarm_m)
elif MODO == "STOPWATCH":
show_stopwatch(sw_mm, sw_ss)
elif MODO == "TIMER":
show_timer(tm_mm, tm_ss)
# 5) STATUS a la app (opcional)
if ble.ok() and ble.connected():
now = time.ticks_ms()
if time.ticks_diff(now, ultimo_status) >= 1000:
ultimo_status = time.ticks_add(ultimo_status, 1000)
status = "Hora {:02d}:{:02d}:{:02d} | Modo {} | Br {} | Al {:02d}:{:02d} {}".format(
h, m, s,
MODO,
ps.brillo_base,
alarm_h, alarm_m,
"ON" if alarm_activa else "OFF"
)
ble.write(status + "\n")
time.sleep_ms(50)
Loading
esp32-c3-devkitm-1
esp32-c3-devkitm-1