Files
raspi-iot-ryhmatyo/init.py
Aaro Varis 26da910850 testing
2026-02-05 09:09:23 +02:00

236 lines
8.8 KiB
Python

from time import time
from astral import LocationInfo
from astral.sun import sun
from datetime import date, datetime, timedelta
from zoneinfo import ZoneInfo
from typing import Callable
import requests
from grove.gpio import GPIO
from grove.display.jhd1802 import JHD1802
shelly_server = "https://shelly-237-eu.shelly.cloud"
shelly_token = "M2M5YTYxdWlkEFD82A2301693D62637E530B4CDDC0DC6E2CDD8F01BE5E5102000B92638A2DA74019E6A81E6D17E0"
shelly_deviceId = "8cbfea9fd6d0"
def setShellyPlugState(deviceId: str, state: bool) -> dict:
"""
Set the state of a Shelly plug/switch device.
Args:
deviceId: The Shelly device id
state: True to turn on, False to turn off
Returns:
Response from the API as a dictionary
"""
url = f"{shelly_server}/v2/devices/api/set/switch"
params = {"auth_key": shelly_token}
payload = {
"id": deviceId,
"on": state
}
response = requests.post(url, params=params, json=payload)
if response.status_code == 200:
return {"success": True}
else:
return response.json()
city = LocationInfo("Seinäjoki", "Finland", "Europe/Helsinki", 62.7900, 22.8400)
s = sun(city.observer, date=date.today())
print(f"City: {city.name}, {city.region}")
print(f"Timezone: {city.timezone}")
print(f"Latitude: {city.latitude:.6f}; Longitude: {city.longitude:.6f}")
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
lastKnownButtonState: bool = False
lcd: JHD1802
lastButtonPressTime: float = 0.0
lastButtonStateChangeTime: float = 0.0
debounceDelay: float = 0.05 # 50ms debounce delay
longPressThreshold: float = 1.0 # 1 second for long press
buttonPressStartTime: float = 0.0
configMode: str = "normal" # modes: normal, set_hour, set_minute
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")
while True:
self.loop()
def setRelayState(self, state: bool):
self.relayGpio.write(1 if state else 0)
def loop(self):
currentButtonState = not self.buttonGpio.read()
currentTime = time()
if (self.configMode == "normal"):
currentMinute = datetime.now(ZoneInfo(city.timezone)).minute
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.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:
if (currentTime - self.lastButtonStateChangeTime) >= self.debounceDelay:
self.lastKnownButtonState = currentButtonState
self.lastButtonStateChangeTime = currentTime
self.onButtonPress("button", currentButtonState)
# Check for long press while button is held
if currentButtonState and self.buttonPressStartTime > 0:
pressDuration = currentTime - self.buttonPressStartTime
if pressDuration >= self.longPressThreshold:
self.onLongPress()
self.buttonPressStartTime = 0.0 # Reset to prevent repeated triggers
def onButtonPress(self, button: str, state: bool):
print(f"Button '{button}' pressed state: {state}")
currentTime = time()
if button == "button":
if state: # Button pressed down
self.buttonPressStartTime = currentTime
else: # Button released
if self.buttonPressStartTime > 0:
pressDuration = currentTime - self.buttonPressStartTime
self.buttonPressStartTime = 0.0
# Only handle as short press if it wasn't a long press
if pressDuration < self.longPressThreshold:
self.onShortPress()
def onShortPress(self):
"""Handle short button press based on current mode"""
if self.configMode == "normal":
# Normal mode: toggle relay
self.setRelayState(not self.relayGpio.read())
setShellyPlugState(shelly_deviceId, self.relayGpio.read() == 0)
elif self.configMode == "set_hour":
# Increment hour
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.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.alarmTime.hour:02d}")
print(f"Entering hour setting mode")
elif self.configMode == "set_hour":
self.configMode = "set_minute"
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.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):
if text == self.lastLcdText:
return
self.lastLcdText = text
self.lcd.clear()
rows = text.split("\n")
for i, row in enumerate(rows):
if i == 0:
self.lcd.setCursor(0, 0)
self.lcd.write(f"{(" "+row):<17}")
elif i == 1:
self.lcd.setCursor(1, 0)
self.lcd.write(row)
#print(f"LCD: {text}")
pins = {
"button": 6,
"led": 5,
"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()