diff --git a/init.py b/init.py index d2587d9..25eeed1 100644 --- a/init.py +++ b/init.py @@ -1,8 +1,9 @@ from time import time from astral import LocationInfo from astral.sun import sun -from datetime import date, datetime +from datetime import date, datetime, timedelta from zoneinfo import ZoneInfo +from typing import Callable import requests from grove.gpio import GPIO @@ -48,8 +49,20 @@ def isSunUp() -> bool: now = datetime.now(city.tzinfo) return s["sunrise"] <= now <= s["sunset"] +class WakeUpAction: + offsetSeconds: int # how many seconds before alarm time to trigger + action: Callable[[], None] + triggered: bool = False + + def __init__(self, offsetSeconds: int, action: Callable[[], None]): + self.offsetSeconds = offsetSeconds + self.action = action + self.triggered = False + + # sunset and sunrise class AlarmClock: + wakeupActions: list[WakeUpAction] = [] ledGpio: GPIO buttonGpio: GPIO relayGpio: GPIO @@ -61,15 +74,43 @@ class AlarmClock: longPressThreshold: float = 1.0 # 1 second for long press buttonPressStartTime: float = 0.0 configMode: str = "normal" # modes: normal, set_hour, set_minute - alarmHour: int = 7 - alarmMinute: int = 0 + alarmTime: datetime = datetime.now(ZoneInfo("Europe/Helsinki")).replace(hour=9, minute=13, second=0, microsecond=0) def __init__(self, pins: dict = {}): self.ledGpio = GPIO(pins.get("led", 5), GPIO.OUT) self.buttonGpio = GPIO(pins.get("button", 6), GPIO.IN) self.relayGpio = GPIO(pins.get("relay", 16), GPIO.OUT) self.lcd = JHD1802() + self.wakeupActions = [] self.setLcdText("AlarmClock Init") + def addWakeUpAction(self, offsetSeconds: int, action: Callable[[], None]): + """Add a wakeup action to be triggered offsetSeconds before alarm time""" + self.wakeupActions.append(WakeUpAction(offsetSeconds, action)) + + def resetWakeUpActions(self): + """Reset all wakeup actions so they can trigger again tomorrow""" + for action in self.wakeupActions: + action.triggered = False + + def checkWakeUpActions(self): + """Check and trigger any wakeup actions that are due""" + now = datetime.now(ZoneInfo(city.timezone)) + today_alarm = now.replace( + hour=self.alarmTime.hour, + minute=self.alarmTime.minute, + second=0, + microsecond=0 + ) + + for wakeupAction in self.wakeupActions: + if wakeupAction.triggered: + continue + trigger_time = today_alarm - timedelta(seconds=wakeupAction.offsetSeconds) + if now >= trigger_time and now < today_alarm + timedelta(minutes=1): + print(f"Triggering wakeup action (offset: {wakeupAction.offsetSeconds}s)") + wakeupAction.action() + wakeupAction.triggered = True + def start(self): print("AlarmClock started") @@ -88,7 +129,10 @@ class AlarmClock: currentHour = datetime.now(ZoneInfo(city.timezone)).hour currrentSecond = datetime.now(ZoneInfo(city.timezone)).second - self.setLcdText(f"Time {currentHour:02d}:{currentMinute:02d}:{currrentSecond:02d}\nAlarm {self.alarmHour:02d}:{self.alarmMinute:02d}") + self.setLcdText(f"Time {currentHour:02d}:{currentMinute:02d}:{currrentSecond:02d}\nAlarm {self.alarmTime.hour:02d}:{self.alarmTime.minute:02d}") + + # Check and trigger wakeup actions + self.checkWakeUpActions() # Only process button state change if debounce delay has passed if currentButtonState != self.lastKnownButtonState: @@ -128,27 +172,27 @@ class AlarmClock: setShellyPlugState(shelly_deviceId, self.relayGpio.read() == 0) elif self.configMode == "set_hour": # Increment hour - self.alarmHour = (self.alarmHour + 1) % 24 - self.setLcdText(f"Set Hour: {self.alarmHour:02d}") + self.alarmTime = self.alarmTime.replace(hour=(self.alarmTime.hour + 1) % 24) + self.setLcdText(f"Set Hour: {self.alarmTime.hour:02d}") elif self.configMode == "set_minute": # Increment minute - self.alarmMinute = (self.alarmMinute + 1) % 60 - self.setLcdText(f"Set Min: {self.alarmMinute:02d}") + self.alarmTime = self.alarmTime.replace(minute=(self.alarmTime.minute + 1) % 60) + self.setLcdText(f"Set Min: {self.alarmTime.minute:02d}") def onLongPress(self): """Handle long button press - cycle through configuration modes""" if self.configMode == "normal": self.configMode = "set_hour" - self.setLcdText(f"Set Hour: {self.alarmHour:02d}") + self.setLcdText(f"Set Hour: {self.alarmTime.hour:02d}") print(f"Entering hour setting mode") elif self.configMode == "set_hour": self.configMode = "set_minute" - self.setLcdText(f"Set Min: {self.alarmMinute:02d}") + self.setLcdText(f"Set Min: {self.alarmTime.minute:02d}") print(f"Switching to minute setting mode") elif self.configMode == "set_minute": self.configMode = "normal" - self.setLcdText(f"Saved: {self.alarmHour:02d}:{self.alarmMinute:02d}") - print(f"Alarm time saved: {self.alarmHour:02d}:{self.alarmMinute:02d}") + self.setLcdText(f"Saved: {self.alarmTime.hour:02d}:{self.alarmTime.minute:02d}") + print(f"Alarm time saved: {self.alarmTime.hour:02d}:{self.alarmTime.minute:02d}") lastLcdText: str = "" def setLcdText(self, text: str): @@ -176,6 +220,13 @@ pins = { "buzzer": 12, } alarm_clock = AlarmClock(pins) + +# Add wakeup actions (offsetSeconds = seconds before alarm to trigger) +alarm_clock.addWakeUpAction(120, lambda: print("2 minutes to alarm!")) # 2 minutes before alarm +alarm_clock.addWakeUpAction(90, lambda: setShellyPlugState(shelly_deviceId, True)) # activate plug 90s before alarm +alarm_clock.addWakeUpAction(30, lambda: print("30 seconds to alarm!")) # 30 seconds before alarm +alarm_clock.addWakeUpAction(0, lambda: print("Alarm triggered!")) # At alarm time + alarm_clock.start()