# main.py - Código unificado de sistema embebido con arquitectura FQS
import time
import math
import random
from machine import Pin, I2C, ADC, Timer, PWM
from ssd1306 import SSD1306_I2C
import framebuf
from machine import time_pulse_us
import csv
import dht
print("Iniciando script...")
# --- Constantes y Pines ---
WIDTH, HEIGHT = 128, 64
# Pines I2C para la pantalla OLED
SCL_PIN = 5
SDA_PIN = 4
# Pines para el Joystick Analógico (Zoom en Calculadora, Cursor)
JOY_X_PIN = 26
JOY_Y_PIN = 27
# Pines para los Botones (4 Direccionales + 1 Seleccionar + 1 Atrás)
BTN_UP_PIN = 10
BTN_DOWN_PIN = 11
BTN_RIGHT_PIN = 12
BTN_LEFT_PIN = 13
BTN_ENTER_PIN = 15
BTN_BACK_PIN = 16
# Pin para el Buzzer
BUZZER_PIN = 14
# Pin para el sensor DHT22
DHT22_PIN = 19
# Pines para HC-SR04 -- Mantenemos COMENTADO para evitar el error anterior
# TRIGGER_PIN = 20
# ECHO_PIN = 21
# DISTANCE_THRESHOLD = 5
# --- Definición de Modos del Sistema ---
MODE_PRESENTATION = -1
MODE_MENU = 0
MODE_CLOCK = 1
MODE_CHRONOMETER = 2
MODE_ANALOG_CLOCK = 3
MODE_TEMPERATURE = 6
MODE_CALENDAR = 7
# --- Inicialización de Hardware ---
oled_initialized = False
try:
print("Inicializando I2C y OLED...")
i2c = I2C(0, scl=Pin(SCL_PIN), sda=Pin(SDA_PIN), freq=400000)
oled = SSD1306_I2C(WIDTH, HEIGHT, i2c)
oled_initialized = True
print("OLED inicializada exitosamente.")
except Exception as e:
print(f"ERROR: Fallo al inicializar OLED en pines SCL={SCL_PIN}, SDA={SDA_PIN}. Mensaje: {e}")
buzzer = PWM(Pin(BUZZER_PIN))
buzzer.freq(1000)
buzzer.duty_u16(0)
print("Buzzer inicializado.")
# Inicialización de pines de botones con pull-up interno
btn_up = Pin(BTN_UP_PIN, Pin.IN, Pin.PULL_UP)
btn_down = Pin(BTN_DOWN_PIN, Pin.IN, Pin.PULL_UP)
btn_right = Pin(BTN_RIGHT_PIN, Pin.IN, Pin.PULL_UP)
btn_left = Pin(BTN_LEFT_PIN, Pin.IN, Pin.PULL_UP)
btn_enter = Pin(BTN_ENTER_PIN, Pin.IN, Pin.PULL_UP)
btn_back = Pin(BTN_BACK_PIN, Pin.IN, Pin.PULL_UP)
print("Botones inicializados.")
# Inicialización de ADCs para el joystick
joy_x = ADC(JOY_X_PIN)
joy_y = ADC(JOY_Y_PIN)
print("Joystick ADCs inicializados.")
# Inicialización del sensor DHT22
dht_sensor = dht.DHT22(Pin(DHT22_PIN))
print("Sensor DHT22 inicializado (o intento).")
# Inicialización del sensor HC-SR04 -- Mantenemos COMENTADO
# trigger_pin = Pin(TRIGGER_PIN, Pin.OUT)
# echo_pin = Pin(ECHO_PIN, Pin.IN)
# trigger_pin.low()
# print("HC-SR04 inicializado.")
# --- Variables Globales del Sistema ---
current_mode = MODE_PRESENTATION
# Variables para el reloj digital global (se actualiza cada segundo)
global_h = 0
global_m = 0
global_s = 0
# Timer para el reloj global (actualiza variables globales, no la UI)
def tick_global_reloj(t):
global global_h, global_m, global_s
global_s += 1
if global_s >= 60:
global_s = 0
global_m += 1
if global_m >= 60:
global_m = 0
global_h += 1
if global_h >= 24:
global_h = 0
try:
print("Inicializando Timer global para el reloj...")
timer_global_reloj = Timer()
timer_global_reloj.init(freq=1, mode=Timer.PERIODIC, callback=tick_global_reloj)
print("Timer global inicializado.")
except Exception as e:
print(f"ERROR: Fallo al inicializar el Timer global. Mensaje: {e}")
# Variables para el cronómetro
ch = cm = cs = cc = 0
cronometro_activo = False
# Variables para el calendario
global_day = 1
global_month = 1
global_year = 2025
# Variables de estado para los sub-modos de ajuste
clock_setting_active = False
time_setting_part = 0
current_setting_h = 0
current_setting_m = 0
current_setting_s = 0
calendar_setting_active = False
date_setting_part = 0
current_setting_day = 1
current_setting_month = 1
current_setting_year = 2025
# Variable para rastrear el estado de la OLED (power)
oled_powered = True
# --- Funciones Auxiliares del Sistema ---
# Control del Buzzer para sonido de clic
def play_click_sound():
if buzzer is not None:
buzzer.duty_u16(5000)
buzzer.freq(2000)
time.sleep_ms(50)
buzzer.duty_u16(0)
# Diccionario para almacenar los estados de los botones para debounce.
button_last_states = {
'up': [True], 'down': [True], 'right': [True],
'left': [True], 'enter': [True], 'back': [True]
}
# Función para debouncing de botones. Retorna True si el botón fue presionado (transición de alto a bajo)
def check_button_press(button_pin, last_state_var_list):
current_state = button_pin.value()
if not current_state and last_state_var_list[0]:
last_state_var_list[0] = False
play_click_sound()
return True
elif current_state and not last_state_var_list[0]:
last_state_var_list[0] = True
return False
# Muestra el reloj digital global en una esquina de la OLED
def display_global_clock():
if oled_initialized:
oled.text("{:02}:{:02}".format(global_h, global_m), 80, 0)
# --- Clases base para la arquitectura FQS ---
class Task:
"""Clase base para todas las tareas que serán encoladas y ejecutadas."""
def __init__(self, name="GenericTask", priority=0, requires_oled=False):
self.name = name
self.priority = priority
self.requires_oled = requires_oled
def execute(self):
"""Método que debe ser implementado por las subclases para realizar la acción de la tarea."""
raise NotImplementedError("El método execute() debe ser implementado por la subclase.")
def __lt__(self, other):
"""Define el comportamiento 'menor que' para comparación de prioridades (para PriorityQueue)."""
return self.priority < other.priority
def __repr__(self):
return f"<Task: {self.name}, Prio: {self.priority}, OLED: {self.requires_oled}>"
class UITask(Task):
"""Tarea para actualizar la interfaz de usuario de un modo específico."""
def __init__(self, mode, name="UITask", priority=5):
super().__init__(name, priority, requires_oled=True)
self.mode = mode
def execute(self):
pass
class ButtonPressTask(Task):
"""Tarea que representa una pulsación de botón con su acción asociada."""
def __init__(self, button_type, current_mode, extra_params=None, priority=10, name="ButtonTask"):
super().__init__(name, priority)
self.button_type = button_type
self.current_mode = current_mode
self.extra_params = extra_params if extra_params is not None else {}
def execute(self):
pass
class ClockTickTask(Task):
"""Tarea para actualizar la lógica de tiempo del cronómetro o reloj. Alta prioridad para precisión."""
def __init__(self, target_clock, priority=8, name="ClockTickTask"):
super().__init__(name, priority)
self.target_clock = target_clock
def execute(self):
if self.target_clock == 'global_clock':
tick_global_reloj(None)
elif self.target_clock == 'chronometer':
global ch, cm, cs, cc, cronometro_activo
if cronometro_activo:
cc += 1
if cc >= 100:
cc = 0
cs += 1
if cs >= 60:
cs = 0
cm += 1
if cm >= 60:
cm = 0
ch += 1
class SensorReadTask(Task):
"""Tarea para leer un sensor (DHT22 o HC-SR04)."""
def __init__(self, sensor_type, priority=7, name="SensorReadTask"):
super().__init__(name, priority)
self.sensor_type = sensor_type
def execute(self):
global oled_powered
if self.sensor_type == 'dht22':
try:
dht_sensor.measure()
except OSError as e:
print(f"Error reading DHT22: {e}")
# Lógica del HC-SR04 -- Mantenemos COMENTADA
# elif self.sensor_type == 'hcsr04':
# distance = read_distance_hcsr04()
# if oled_initialized:
# if 0 < distance <= DISTANCE_THRESHOLD and oled_powered:
# oled.poweroff()
# oled_powered = False
# oled.fill(0)
# oled.show()
# elif (distance > DISTANCE_THRESHOLD or distance == -1) and not oled_powered:
# oled.poweron()
# oled_powered = True
# oled.fill(0)
# oled.show()
class BuzzerTask(Task):
"""Tarea para controlar el buzzer."""
def __init__(self, freq, duty_u16, duration_ms, priority=9, name="BuzzerTask"):
super().__init__(name, priority)
self.freq = freq
self.duty_u16 = duty_u16
self.duration_ms = duration_ms
def execute(self):
if buzzer is not None:
buzzer.duty_u16(self.duty_u16)
buzzer.freq(self.freq)
time.sleep_ms(self.duration_ms)
buzzer.duty_u16(0)
def enqueue_click_sound():
task_queue.add_task(BuzzerTask(freq=2000, duty_u16=5000, duration_ms=50, name="ClickSound"))
# --- Funciones de Modos (solo dibujan) ---
def run_presentation_mode():
global current_mode
if not oled_initialized:
print("OLED no inicializada. Saltando modo presentación.")
current_mode = MODE_MENU
return
oled.fill(0)
for _ in range(2):
buzzer.freq(500)
buzzer.duty_u16(30000)
time.sleep_ms(200)
buzzer.duty_u16(0)
time.sleep_ms(50)
buzzer.freq(1000)
buzzer.duty_u16(30000)
time.sleep_ms(200)
buzzer.duty_u16(0)
time.sleep_ms(50)
buzzer.duty_u16(0)
agumon_bytes = bytearray([
0xff, 0xc0, 0x03, 0xff, 0xff, 0xc0, 0x03, 0xff,
0xff, 0x3f, 0xfc, 0xff, 0xff, 0x3f, 0xfc, 0xff,
0xc0, 0xff, 0x0f, 0x3f, 0xc0, 0xff, 0x0f, 0x3f,
0x3f, 0xff, 0xc3, 0x3f, 0x3f, 0xff, 0xc3, 0x3f,
0x3f, 0xff, 0x03, 0x3f, 0x3f, 0xff, 0x03, 0x3f,
0x3f, 0xc3, 0xff, 0x3f, 0x3f, 0xc3, 0xff, 0x3f,
0xc0, 0x3f, 0xff, 0x3f, 0xc0, 0x3f, 0xff, 0x3f,
0xcf, 0xff, 0x0c, 0xff, 0xcf, 0xff, 0x0c, 0xff,
0xf0, 0x00, 0xfc, 0xff, 0xf0, 0x00, 0xfc, 0xff,
0xfc, 0x3f, 0x0f, 0x3f, 0xfc, 0x3f, 0x0f, 0x3f,
0xf3, 0x3c, 0xfc, 0x3f, 0xf3, 0x3c, 0xfc, 0x3f,
0xf0, 0x3c, 0x0f, 0xc0, 0xf0, 0x3c, 0x0f, 0xc0,
0xff, 0x0f, 0xcc, 0x0c, 0xff, 0x0f, 0xcc, 0x0c,
0xf0, 0xf0, 0x0f, 0xc3, 0xf0, 0xf0, 0x0f, 0xc3,
0xcc, 0xf3, 0x33, 0x33, 0xcc, 0xf3, 0x33, 0x33,
0xc0, 0x03, 0x00, 0x03, 0xc0, 0x03, 0x00, 0x03
])
agumon_fb = framebuf.FrameBuffer(agumon_bytes, 32, 32, framebuf.MONO_HLSB)
oled.blit(agumon_fb, (WIDTH // 2) - 16, 0)
oled.text("Edwin Luis", 20, 40)
oled.text("Garavito Omoya", 5, 50)
oled.show()
time.sleep(3)
oled.fill(0)
oled.show()
current_mode = MODE_MENU
# --- MODO: Menú Principal con Iconos ---
icon_clock_bytes = bytearray([0xff, 0xff, 0xff, 0xff, 0x85, 0xb7, 0xbd, 0xb7, 0xbd, 0xb7, 0xbd, 0xb4, 0x85, 0xb4, 0xf5, 0xb7, 0xf5, 0xb7, 0xf5, 0xb4, 0xf5, 0xb4, 0xf5, 0xb7, 0x85, 0xb7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff])
icon_clock_fb = framebuf.FrameBuffer(icon_clock_bytes, 16, 16, framebuf.MONO_HLSB)
icon_chrono_bytes = bytearray([0xff, 0xff, 0x7f, 0xfc, 0xff, 0xfe, 0x1f, 0xf0, 0xef, 0xef, 0xf7, 0xdf, 0xfb, 0xbf, 0x1b, 0xb1, 0x5b, 0xb5, 0x5b, 0xb5, 0x1b, 0xb1, 0xfb, 0xbf, 0xf7, 0xdf, 0xef, 0xef, 0x1f, 0xf0, 0xff, 0xff])
icon_chrono_fb = framebuf.FrameBuffer(icon_chrono_bytes, 16, 16, framebuf.MONO_HLSB)
icon_analog_bytes = bytearray([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf7, 0xff, 0xfb, 0xff, 0xfd, 0x7f, 0xfe, 0x7f, 0xfe, 0xbf, 0xff, 0xdf, 0xff, 0xef, 0xff, 0xf7, 0xff, 0xfb, 0xff, 0xfd, 0xff, 0xff, 0xff])
icon_analog_fb = framebuf.FrameBuffer(icon_analog_bytes, 16, 16, framebuf.MONO_HLSB)
icon_temp_bytes = bytearray([0xff, 0xff, 0xfe, 0x7f, 0xfd, 0xbf, 0xfd, 0x3f, 0xfd, 0xbf, 0xfd, 0x3f, 0xfd, 0xbf, 0xfd, 0x3f, 0xfd, 0xbf, 0xfd, 0x3f, 0xfb, 0xdf, 0xf7, 0xef, 0xf7, 0xef, 0xf7, 0xef, 0xf8, 0x1f, 0xff, 0xff])
icon_temp_fb = framebuf.FrameBuffer(icon_temp_bytes, 16, 16, framebuf.MONO_HLSB)
icon_calendar_bytes = bytearray([0x12, 0x48, 0x12, 0x48, 0x12, 0x48, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xf7, 0x0f, 0xf7, 0xef, 0xf7, 0xef, 0xf7, 0x0f, 0xf7, 0x7f, 0xf7, 0x7f, 0xf7, 0x7f, 0xf7, 0x0f, 0xff, 0xff, 0xff, 0xff])
icon_calendar_fb = framebuf.FrameBuffer(icon_calendar_bytes, 16, 16, framebuf.MONO_HLSB)
menu_items = [
("Reloj Digital", icon_clock_fb, MODE_CLOCK),
("Cronometro", icon_chrono_fb, MODE_CHRONOMETER),
("Reloj Analogico", icon_analog_fb, MODE_ANALOG_CLOCK),
("Temperatura", icon_temp_fb, MODE_TEMPERATURE),
("Calendario", icon_calendar_fb, MODE_CALENDAR)
]
current_menu_selection = 0
def draw_menu_mode():
global current_menu_selection
if not oled_initialized: return
oled.fill(0)
display_offset = 0
if len(menu_items) > 3:
if current_menu_selection >= 2:
display_offset = current_menu_selection - 2
if display_offset > len(menu_items) - 3:
display_offset = len(menu_items) - 3
if display_offset < 0:
display_offset = 0
for i in range(display_offset, min(len(menu_items), display_offset + 3)):
menu_text, menu_icon_fb, _ = menu_items[i]
icon_x = 0
text_x = 20
y_pos = 5 + (i - display_offset) * 20
if i == current_menu_selection:
oled.fill_rect(icon_x, y_pos, WIDTH, 18, 1)
oled.blit(menu_icon_fb, icon_x + 2, y_pos + 1, 0)
oled.text(menu_text, text_x, y_pos + 5, 0)
else:
oled.blit(menu_icon_fb, icon_x + 2, y_pos + 1, 1)
oled.text(menu_text, text_x, y_pos + 5, 1)
oled.show()
# --- MODO: Reloj Digital (solo dibujo) ---
def draw_clock_mode():
global global_h, global_m, global_s, time_setting_part
global current_setting_h, current_setting_m, current_setting_s, clock_setting_active
if not oled_initialized: return
oled.fill(0)
display_global_clock()
if not clock_setting_active:
oled.text("Reloj Digital", 0, 10)
full_time_display = "{:02}:{:02}:{:02}".format(global_h, global_m, global_s)
oled.text(full_time_display, 20, 30)
oled.text("ENTER: Ajustar", 0, 50)
oled.text("BACK: Menu", 0, 40)
else:
oled.text("Ajuste de Hora", 0, 10)
hora_display = "{:02}:{:02}:{:02}".format(current_setting_h, current_setting_m, current_setting_s)
oled.text(hora_display, 20, 30)
if time_setting_part == 0:
oled.rect(20-1, 30+8+1, 2*8+2, 2, 1)
elif time_setting_part == 1:
oled.rect(20+3*8-1, 30+8+1, 2*8+2, 2, 1)
elif time_setting_part == 2:
oled.rect(20+6*8-1, 30+8+1, 2*8+2, 2, 1)
oled.text("UP/DOWN: Ajustar", 0, 40)
oled.text("RIGHT: Siguiente", 0, 50)
oled.show()
# --- MODO: Cronómetro (solo dibujo) ---
def draw_chronometer_mode():
global ch, cm, cs, cc, cronometro_activo
if not oled_initialized: return
oled.fill(0)
display_global_clock()
oled.text("Cronometro", 0, 10)
t = "{:02}:{:02}:{:02}.{:02}".format(ch, cm, cs, cc)
oled.text(t[0:5], 10, 30)
oled.text(t[6:], 10, 45)
oled.text("{}".format("ON" if cronometro_activo else "PAUSA"), 85, 20)
oled.text("LEFT: Reset", 0, 56)
oled.show()
# --- MODO: Reloj Analógico (solo dibujo) ---
def draw_analog_clock_mode():
global global_h, global_m, global_s
if not oled_initialized: return
oled.fill(0)
cx, cy, r = 64, 32, 28
for a in range(0, 360, 30):
x = int(cx + math.cos(math.radians(a)) * r)
y = int(cy + math.sin(math.radians(a)) * r)
oled.pixel(x, y, 1)
ang_s = (global_s / 60) * 360 - 90
ang_m = (global_m / 60) * 360 - 90
ang_h = ((global_h % 12 + global_m / 60) / 12) * 360 - 90
oled.line(cx, cy, int(cx + math.cos(math.radians(ang_s)) * r), int(cy + math.sin(math.radians(ang_s)) * r), 1)
oled.line(cx, cy, int(cx + math.cos(math.radians(ang_m)) * (r - 6)), int(cy + math.sin(math.radians(ang_m)) * (r - 6)), 1)
oled.line(cx, cy, int(cx + math.cos(math.radians(ang_h)) * (r - 12)), int(cy + math.sin(math.radians(ang_h)) * (r - 12)), 1)
oled.text("Analogico", 30, 0)
oled.show()
# --- MODO: Temperatura (solo dibujo) ---
def draw_temperature_mode():
global current_mode
if not oled_initialized: return
oled.fill(0)
display_global_clock()
oled.text("Modo: Temperatura", 0, 10)
try:
temperatura = dht_sensor.temperature()
humedad = dht_sensor.humidity()
oled.text("Temp: {:.1f}C".format(temperatura), 0, 30)
oled.text("Hum: {:.1f}%".format(humedad), 0, 45)
except OSError as e:
oled.text("Error DHT22", 0, 30)
oled.text(str(e)[:15], 0, 45)
oled.show()
# --- MODO: Calendario (solo dibujo y funciones auxiliares) ---
def get_days_in_month(month, year):
if month == 2:
if (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0):
return 29
else:
return 28
elif month in [4, 6, 9, 11]:
return 30
else:
return 31
def draw_calendar_mode():
global global_day, global_month, global_year
global current_setting_day, current_setting_month, current_setting_year
global date_setting_part, calendar_setting_active
if not oled_initialized: return
oled.fill(0)
display_global_clock()
if not calendar_setting_active:
oled.text("Calendario", 0, 10)
full_date_display = "{:02}/{:02}/{}".format(global_day, global_month, global_year)
oled.text(full_date_display, 20, 30)
oled.text("ENTER: Ajustar", 0, 50)
oled.text("BACK: Menu", 0, 40)
else:
oled.text("Ajuste de Fecha", 0, 10)
date_display = "{:02}/{:02}/{}".format(current_setting_day, current_setting_month, current_setting_year)
oled.text(date_display, 20, 30)
if date_setting_part == 0:
oled.rect(20-1, 30+8+1, 2*8+2, 2, 1)
elif date_setting_part == 1:
oled.rect(20+3*8-1, 30+8+1, 2*8+2, 2, 1)
elif date_setting_part == 2:
oled.rect(20+6*8-1, 30+8+1, 4*8+2, 2, 1)
oled.text("UP/DOWN: Ajustar", 0, 40)
oled.text("RIGHT: Siguiente", 0, 50)
oled.show()
# --- Funciones de Manejo de Lógica (llamadas por ButtonPressTask o el Scheduler) ---
def handle_button_menu_mode(button_type):
global current_mode, current_menu_selection
if button_type == 'up':
current_menu_selection = (current_menu_selection - 1 + len(menu_items)) % len(menu_items)
elif button_type == 'down':
current_menu_selection = (current_menu_selection + 1) % len(menu_items)
elif button_type == 'enter':
_ , _ , target_mode = menu_items[current_menu_selection]
current_mode = target_mode
def handle_button_clock_mode(button_type):
global current_mode, clock_setting_active, time_setting_part
global global_h, global_m, global_s, current_setting_h, current_setting_m, current_setting_s
if not clock_setting_active:
if button_type == 'enter':
clock_setting_active = True
current_setting_h = global_h
current_setting_m = global_m
current_setting_s = global_s
time_setting_part = 0
elif button_type == 'back':
current_mode = MODE_MENU
else:
if button_type == 'up':
if time_setting_part == 0:
current_setting_h = (current_setting_h + 1) % 24
elif time_setting_part == 1:
current_setting_m = (current_setting_m + 1) % 60
elif time_setting_part == 2:
current_setting_s = (current_setting_s + 1) % 60
elif button_type == 'down':
if time_setting_part == 0:
current_setting_h = (current_setting_h - 1 + 24) % 24
elif time_setting_part == 1:
current_setting_m = (current_setting_m - 1 + 60) % 60
elif time_setting_part == 2:
current_setting_s = (current_setting_s - 1 + 60) % 60
elif button_type == 'right':
time_setting_part = (time_setting_part + 1) % 3
elif button_type == 'enter':
global_h = current_setting_h
global_m = current_setting_m
global_s = current_setting_s
clock_setting_active = False
elif button_type == 'back':
clock_setting_active = False
def handle_button_chronometer_mode(button_type):
global cronometro_activo, ch, cm, cs, cc, current_mode
if button_type == 'enter':
cronometro_activo = not cronometro_activo
elif button_type == 'left':
ch = cm = cs = cc = 0
cronometro_activo = False
elif button_type == 'back':
current_mode = MODE_MENU
def handle_button_analog_clock_mode(button_type):
global current_mode
if button_type == 'back':
current_mode = MODE_MENU
def handle_button_temperature_mode(button_type):
global current_mode
if button_type == 'back':
current_mode = MODE_MENU
def handle_button_calendar_mode(button_type):
global current_mode, calendar_setting_active, date_setting_part
global global_day, global_month, global_year
global current_setting_day, current_setting_month, current_setting_year
if not calendar_setting_active:
if button_type == 'enter':
calendar_setting_active = True
current_setting_day = global_day
current_setting_month = global_month
current_setting_year = global_year
date_setting_part = 0
elif button_type == 'back':
current_mode = MODE_MENU
else:
if button_type == 'up':
if date_setting_part == 0:
max_days = get_days_in_month(current_setting_month, current_setting_year)
current_setting_day = (current_setting_day % max_days) + 1
elif date_setting_part == 1:
current_setting_month = (current_setting_month % 12) + 1
max_days = get_days_in_month(current_setting_month, current_setting_year)
if current_setting_day > max_days:
current_setting_day = max_days
elif date_setting_part == 2:
current_setting_year = (current_setting_year % 2100) + 1 if current_setting_year < 2099 else 2000
if current_setting_month == 2:
max_days = get_days_in_month(current_setting_month, current_setting_year)
if current_setting_day > max_days:
current_setting_day = max_days
elif button_type == 'down':
if date_setting_part == 0:
max_days = get_days_in_month(current_setting_month, current_setting_year)
current_setting_day = (current_setting_day - 2 + max_days) % max_days + 1
elif date_setting_part == 1:
current_setting_month = (current_setting_month - 2 + 12) % 12 + 1
max_days = get_days_in_month(current_setting_month, current_setting_year)
if current_setting_day > max_days:
current_setting_day = max_days
elif date_setting_part == 2:
current_setting_year = (current_setting_year - 2001 + 100) % 100 + 2000 if current_setting_year > 2000 else 2099
if current_setting_month == 2:
max_days = get_days_in_month(current_setting_month, current_setting_year)
if current_setting_day > max_days:
current_setting_day = max_days
elif button_type == 'right':
date_setting_part = (date_setting_part + 1) % 3
elif button_type == 'enter':
global_day = current_setting_day
global_month = current_setting_month
global_year = current_setting_year
calendar_setting_active = False
elif button_type == 'back':
calendar_setting_active = False
# --- Sensor HC-SR04 -- Mantenemos COMENTADO
# def read_distance_hcsr04():
# """Mide distancia en cm con HC-SR04"""
# trigger_pin.low()
# time.sleep_us(2)
# trigger_pin.high()
# time.sleep_us(10)
# trigger_pin.low()
# pulse_time = time_pulse_us(echo_pin, 1, 30000)
# distance_cm = (pulse_time * 0.0343) / 2
# return distance_cm if pulse_time > 0 else -1
# --- Mapeo de Funciones de Dibujo y Manejo de Botones ---
mode_draw_functions = {
MODE_MENU: draw_menu_mode,
MODE_CLOCK: draw_clock_mode,
MODE_CHRONOMETER: draw_chronometer_mode,
MODE_ANALOG_CLOCK: draw_analog_clock_mode,
MODE_TEMPERATURE: draw_temperature_mode,
MODE_CALENDAR: draw_calendar_mode
}
button_action_handlers = {
MODE_MENU: handle_button_menu_mode,
MODE_CLOCK: handle_button_clock_mode,
MODE_CHRONOMETER: handle_button_chronometer_mode,
MODE_ANALOG_CLOCK: handle_button_analog_clock_mode,
MODE_TEMPERATURE: handle_button_temperature_mode,
MODE_CALENDAR: handle_button_calendar_mode
}
# --- Implementación de Colas (Queues) ---
class BaseQueue:
def __init__(self):
self._tasks = []
def add_task(self, task):
raise NotImplementedError
def get_task(self):
raise NotImplementedError
def is_empty(self):
return len(self._tasks) == 0
def size(self):
return len(self._tasks)
class FIFOQueue(BaseQueue):
def __init__(self):
super().__init__()
print("FIFOQueue initialized.")
def add_task(self, task):
self._tasks.append(task)
def get_task(self):
if not self.is_empty():
return self._tasks.pop(0)
return None
class LIFOQueue(BaseQueue):
def __init__(self):
super().__init__()
print("LIFOQueue initialized.")
def add_task(self, task):
self._tasks.append(task)
def get_task(self):
if not self.is_empty():
return self._tasks.pop()
return None
class PriorityQueue(BaseQueue):
def __init__(self):
super().__init__()
print("PriorityQueue initialized.")
def add_task(self, task):
self._tasks.append(task)
self._tasks.sort(key=lambda x: x.priority, reverse=True)
def get_task(self):
if not self.is_empty():
return self._tasks.pop()
return None
# --- Implementación de Schedulers ---
class Scheduler:
def __init__(self, task_queue, name="GenericScheduler"):
self.task_queue = task_queue
self.name = name
self.last_ui_update_time = time.ticks_ms()
self.ui_update_interval_ms = 100
self.last_sensor_read_time = time.ticks_ms()
self.sensor_read_interval_ms = 1000
def enqueue_periodic_tasks(self):
# Tarea de actualización de UI
if time.ticks_diff(time.ticks_ms(), self.last_ui_update_time) >= self.ui_update_interval_ms:
self.task_queue.add_task(UITask(current_mode, name=f"DrawMode_{current_mode}"))
self.last_ui_update_time = time.ticks_ms()
# Tarea de lectura de sensores (DHT22)
if time.ticks_diff(time.ticks_ms(), self.last_sensor_read_time) >= self.sensor_read_interval_ms:
self.task_queue.add_task(SensorReadTask('dht22', name="ReadDHT22"))
# self.task_queue.add_task(SensorReadTask('hcsr04', name="ReadHCSR04"))
self.last_sensor_read_time = time.ticks_ms()
# Tarea de tick del cronómetro
if current_mode == MODE_CHRONOMETER and cronometro_activo:
self.task_queue.add_task(ClockTickTask('chronometer', priority=12, name="ChronoTick"))
def check_buttons_and_enqueue_tasks(self):
button_pins_by_type = {
'up': btn_up,
'down': btn_down,
'right': btn_right,
'left': btn_left,
'enter': btn_enter,
'back': btn_back
}
for button_type, pin_obj in button_pins_by_type.items():
if check_button_press(pin_obj, button_last_states[button_type]):
self.task_queue.add_task(ButtonPressTask(button_type, current_mode, name=f"Btn_{button_type}"))
def run(self):
raise NotImplementedError
class RoundRobinScheduler(Scheduler):
def __init__(self, task_queue):
super().__init__(task_queue, name="RoundRobinScheduler")
print("Scheduler: Round Robin")
def run(self):
self.enqueue_periodic_tasks()
self.check_buttons_and_enqueue_tasks()
task = self.task_queue.get_task()
if task:
global oled_powered
if task.requires_oled and not oled_powered:
oled.poweron()
oled_powered = True
oled.fill(0)
oled.show()
if isinstance(task, UITask):
if oled_powered and task.mode in mode_draw_functions:
mode_draw_functions[task.mode]()
elif isinstance(task, ButtonPressTask):
if task.current_mode in button_action_handlers:
button_action_handlers[task.current_mode](task.button_type)
self.task_queue.add_task(UITask(current_mode, name=f"DrawMode_{current_mode}"))
else:
task.execute()
time.sleep_ms(10)
class PriorityBasedScheduler(Scheduler):
def __init__(self, task_queue):
if not isinstance(task_queue, PriorityQueue):
raise TypeError("PriorityBasedScheduler requiere una PriorityQueue.")
super().__init__(task_queue, name="PriorityBasedScheduler")
print("Scheduler: Priority Based")
def run(self):
self.enqueue_periodic_tasks()
self.check_buttons_and_enqueue_tasks()
task = self.task_queue.get_task()
if task:
global oled_powered
if task.requires_oled and not oled_powered:
oled.poweron()
oled_powered = True
oled.fill(0)
oled.show()
if isinstance(task, UITask):
if oled_powered and task.mode in mode_draw_functions:
mode_draw_functions[task.mode]()
elif isinstance(task, ButtonPressTask):
if task.current_mode in button_action_handlers:
button_action_handlers[task.current_mode](task.button_type)
self.task_queue.add_task(UITask(current_mode, name=f"DrawMode_{current_mode}", priority=task.priority + 1))
else:
task.execute()
time.sleep_ms(10)
class EventDrivenLIFOScheduler(Scheduler):
def __init__(self, task_queue):
if not isinstance(task_queue, LIFOQueue):
raise TypeError("EventDrivenLIFOScheduler requiere una LIFOQueue.")
super().__init__(task_queue, name="EventDrivenLIFOScheduler")
self.ui_update_interval_ms = 200
self.sensor_read_interval_ms = 2000
print("Scheduler: Event Driven (LIFO)")
def run(self):
self.check_buttons_and_enqueue_tasks()
self.enqueue_periodic_tasks()
task = self.task_queue.get_task()
if task:
global oled_powered
if task.requires_oled and not oled_powered:
oled.poweron()
oled_powered = True
oled.fill(0)
oled.show()
if isinstance(task, UITask):
if oled_powered and task.mode in mode_draw_functions:
mode_draw_functions[task.mode]()
elif isinstance(task, ButtonPressTask):
if task.current_mode in button_action_handlers:
button_action_handlers[task.current_mode](task.button_type)
self.task_queue.add_task(UITask(current_mode, name=f"DrawMode_{current_mode}"))
else:
task.execute()
time.sleep_ms(10)
# --- Bucle Principal del Sistema (refactorizado para FQS) ---
def main_loop():
global current_mode, oled_powered
if oled_initialized:
oled.fill(0)
oled.show()
else:
print("OLED no inicializada. Modo sin pantalla")
run_presentation_mode()
current_mode = MODE_MENU
# --- Configuración de Schedulers y Colas ---
# Opción 1: FIFO Queue + Round Robin Scheduler (DEFAULT)
# Descomenta la siguiente sección si quieres usar Round Robin
# global task_queue, active_scheduler
# task_queue = FIFOQueue()
# active_scheduler = RoundRobinScheduler(task_queue)
# print("\n--- USANDO FIFO QUEUE + ROUND ROBIN SCHEDULER (DEFAULT) ---")
# Opción 2: Priority Queue + Priority-Based Scheduler
# Comenta la Opción 1 y la Opción 3, y DESCOMENTA esta sección para usarla
global task_queue, active_scheduler
task_queue = PriorityQueue()
active_scheduler = PriorityBasedScheduler(task_queue)
print("\n--- USANDO PRIORITY QUEUE + PRIORITY-BASED SCHEDULER ---")
# Opción 3: LIFO Queue + Event-Driven LIFO Scheduler
# Comenta la Opción 1 y la Opción 2, y DESCOMENTA esta sección para usarla
# global task_queue, active_scheduler
# task_queue = LIFOQueue()
# active_scheduler = EventDrivenLIFOScheduler(task_queue)
# print("\n--- USANDO LIFO QUEUE + EVENT-DRIVEN LIFO SCHEDULER ---")
# Bucle principal del sistema, ahora impulsado por el scheduler
while True:
try:
active_scheduler.run()
except Exception as e:
print(f"ERROR FATAL en main_loop del scheduler: {e}")
if oled_initialized:
try:
oled.poweron()
oled.fill(0)
oled.text("Error! Reinic", 0, 0)
oled.show()
except:
pass
time.sleep(2)
current_mode = MODE_MENU
# --- Ejecución del Programa ---
if __name__ == "__main__":
main_loop()
# En nuestro sistema embebido, hemos implementado y configurado tres combinaciones
# diferentes de colas (Queues) y planificadores (Schedulers) para la gestión de tareas.
# Cada combinación ofrece un enfoque distinto para decidir qué tarea se ejecuta y cuándo,
# impactando directamente la 'sensación' y el rendimiento del sistema.
#
# Un sistema embebido como el nuestro (basado en MicroPython en una Pico W) es
# mono-hilo, lo que significa que solo puede ejecutar una cosa a la vez. Las colas y
# los schedulers nos ayudan a simular concurrencia y a gestionar múltiples tareas
# de manera eficiente, decidiendo el orden en que estas tareas "compiten" por el tiempo
# de procesamiento de la CPU.
#
# -----------------------------------------------------------------------------------
#
# 1. FIFO Queue + Round Robin Scheduler (Fairness y Simpleza)
#
# - FIFO (First-In, First-Out) Queue: Como una fila en un banco o un tubo.
# La primera tarea que se añade a la cola es la primera en ser procesada.
# Es el método más directo y predecible para la gestión de datos.
#
# - Round Robin Scheduler: Este planificador toma la tarea más antigua de la cola,
# la ejecuta (en nuestro caso, la ejecuta por completo), y luego pasa a la siguiente.
# Todas las tareas reciben una oportunidad equitativa de ejecución en un ciclo
# repetitivo. No hay prioridad; el orden es estrictamente por llegada.
#
# Ventajas:
# - Simpleza y Facilidad de Implementación: Es muy fácil de entender y codificar.
# - Equidad (Fairness): Todas las tareas, sin importar su importancia,
# eventualmente se ejecutarán. Ninguna tarea se queda sin ser procesada
# indefinidamente ('starvation').
# - Predecibilidad: El orden de ejecución es claro y secuencial.
#
# Desventajas:
# - Baja Latencia de Respuesta a Eventos Críticos: Si una pulsación de botón
# (una tarea que requiere respuesta inmediata) se encola detrás de muchas
# tareas de baja prioridad, el usuario podría percibir un retraso.
# - Ineficiencia para Tareas de Alta Prioridad: Una tarea urgente
# (como una actualización crítica de la pantalla o un tick de cronómetro)
# debe esperar su turno como cualquier otra.
#
# -----------------------------------------------------------------------------------
#
# 2. Priority Queue + Priority-Based Scheduler (Responsividad y Priorización)
#
# Esta combinación introduce el concepto de importancia relativa de las tareas.
#
# - Priority Queue: Las tareas se almacenan y se recuperan según su nivel de prioridad.
# En nuestro código, hemos asignado prioridades a las tareas (ej: pulsaciones de
# botones tienen prioridad alta, actualizaciones de UI prioridad media, etc.).
# Cuando se añade una tarea, la cola se reordena (o la tarea se inserta en el lugar
# correcto) para que las tareas de mayor prioridad (número más bajo) estén siempre
# al frente, listas para ser seleccionadas.
#
# - Priority-Based Scheduler: El planificador siempre selecciona y ejecuta la tarea
# con la **mayor prioridad** (el número de prioridad más bajo) que esté disponible
# en la cola.
#
# Ventajas:
# - Excelente Responsividad para Tareas Críticas: Tareas importantes como
# pulsaciones de botones o ticks de cronómetro se ejecutarán casi de inmediato,
# sin importar cuántas tareas de menor prioridad estén esperando. Esto mejora
# significativamente la experiencia del usuario para interacciones críticas.
# - Uso Eficiente de Recursos: El sistema puede concentrar sus esfuerzos en lo más
# importante en un momento dado.
#
# Desventajas:
# - Riesgo de "Hambruna" (Starvation): Si hay un flujo constante de tareas de
# alta prioridad, las tareas de baja prioridad podrían nunca ejecutarse,
# o ejecutarse con muy poca frecuencia. Aunque en nuestro diseño se mitiga un poco.
# - Mayor Complejidad: Requiere una asignación cuidadosa y un entendimiento
# claro de las prioridades de cada tipo de tarea.
#
# -----------------------------------------------------------------------------------
#
# 3. LIFO Queue + Event-Driven LIFO Scheduler (Inmediatez del Evento Reciente)
#
# Esta combinación se enfoca en la inmediatez de los eventos más recientes.
#
# - LIFO (Last-In, First-Out) Queue: Como una pila de platos. El último elemento
# que se añade a la pila es el primero en ser retirado. Las tareas más
# recientemente encoladas son las primeras en ser ejecutadas.
#
# - Event-Driven LIFO Scheduler: Este planificador es ideal para sistemas donde
# la "frescura" del evento es clave. Las tareas generadas por los eventos más
# recientes (como las interacciones del usuario) se priorizan sobre las tareas
# antiguas o periódicas. El sistema se siente muy reactivo a las últimas acciones.
#
# Ventajas:
# - Buena Respuesta a Eventos Recientes: Si el usuario presiona un botón
# varias veces seguidas, la última pulsación (y su efecto en la UI) se procesará
# muy rápidamente, lo que puede dar una sensación de gran inmediatez.
# - Sentido de Inmediatez: La interfaz de usuario reacciona rápidamente a la
# interacción más reciente del usuario.
#
# Desventajas:
# - Menos Predecibilidad para Tareas Antiguas/Periódicas: Si hay muchos eventos
# o tareas nuevas que se añaden constantemente, las tareas periódicas (como la
# actualización constante de la hora o la lectura de sensores) podrían retrasarse
# o "saltarse" ciclos, ya que las tareas más recientes tienen preferencia.
# - Posible "Hambruna" de Tareas Antiguas: Similar al scheduler de prioridad,
# pero en este caso por ser desplazado por nuevas entradas en la pila.
#
# -----------------------------------------------------------------------------------
#
# En resumen, al experimentar con estas tres configuraciones, puedes observar y comprender
# de primera mano cómo las decisiones de diseño en la gestión de tareas impactan
# directamente la **experiencia de usuario** y el **rendimiento general** de tu sistema
# embebido. Cada una tiene su lugar y es adecuada para diferentes tipos de aplicaciones.
#
# # Para cambiar entre los schedulers:
# # 1. Ve a la función 'main_loop()' al final del archivo.
# # 2. Comenta los dos bloques de código de 'global task_queue, active_scheduler' que NO quieres usar.
# # 3. Descomenta el bloque que SÍ quieres usar.
# # 4. Guarda y reinicia tu dispositivo o simulación.
#