diff --git a/__pycache__/timer_control.cpython-310.pyc b/__pycache__/timer_control.cpython-310.pyc index bd6f71c..35954e5 100644 Binary files a/__pycache__/timer_control.cpython-310.pyc and b/__pycache__/timer_control.cpython-310.pyc differ diff --git a/__pycache__/wecker_control.cpython-310.pyc b/__pycache__/wecker_control.cpython-310.pyc new file mode 100644 index 0000000..a6820b1 Binary files /dev/null and b/__pycache__/wecker_control.cpython-310.pyc differ diff --git a/main.py b/main.py index 48e7c9c..7bbaeca 100644 --- a/main.py +++ b/main.py @@ -9,6 +9,7 @@ import re import asyncio from weather_jetzt import get_weather_for_location from timer_control import parse_time, start_timer, stop_timer, timer_status_info, format_duration +from wecker_control import parse_time_wecker, minutes_until, start_wecker, stop_wecker, wecker_status_info, calculate_target_datetime #test @@ -93,7 +94,7 @@ INTENTS = { # "duration": r"(\w+)\s*(sekunden|sekunde|minuten|minute|stunden|stunde)" "duration": r"((?:\w+)\s*(?:sekunden|sekunde|minuten|minute|stunden|stunde))" - } + }, }, "stop": { "keywords": ["stopp", "stoppe", "beende"], @@ -104,6 +105,25 @@ INTENTS = { "required_slots": {} } } + }, + "wecker": { + "keywords": ["wecker", "timer"], + "actions": { + "start": { + "keywords": ["erstelle", "stelle"], + "required_slots": { + "timeSet": r"\b([\w]+)\s*uhr(?:\s*([\w]+))?\b" + }, + }, + "stop": { + "keywords": ["stopp", "stoppe", "entferne"], + "required_slots": {} + }, + "status": { + "keywords": ["status", "läuft", "noch"], + "required_slots": {} + } + } } } @@ -122,7 +142,7 @@ def detect_intent(text): # ========================= - +## WEATHER def weather_skill(slots): location = slots["location"] result = asyncio.run(get_weather_for_location(location)) @@ -133,6 +153,7 @@ def weather_skill(slots): return f"Keine Wetterdaten verfügbar" #return f"Das Wetter in {location} ist sonnig bei 20 Grad." +## TIMER def start_timer_skill(slots): duration = slots["duration"] seconds = parse_time(duration) @@ -162,7 +183,40 @@ def status_timer_skill(slots): else: return f"Es läuft kein Timer" +#ALARM +def start_wecker_skill(slots): + parsed = parse_time_wecker(slots["timeSet"]) + + if not parsed: + return "Die Uhrzeit konnte nicht erkannt werden." + + hour, minute = parsed + target = calculate_target_datetime(hour, minute) + start_wecker(target) + minutes = minutes_until(target) + + return f"Wecker für {hour:02d}:{minute:02d} wurde gestellt er klingelt in {minutes} minuten" + +def stopp_wecker_skill(slots): + stop_wecker() + print("Wecker wurde gestoppt") + return f"Wecker wurde gestoppt" + +def status_wecker_skill(slots): + + info = wecker_status_info() + + if info["status"] == "running": + target_time = info["target_time"] + return f"Ein Wecker wurde bereits auf {wecker_target_time} gestellt" + elif info["status"] == "finished": + return f"Der Wecker ist abgelaufen" + elif info["status"] == "stopped": + return f"Der Wecker wurde gestoppt, es ist kein Wecker gestellt" + else: + return f"Es ist kein Wecker gestellt" + SKILLS = { @@ -170,7 +224,12 @@ SKILLS = { "timer": { "start": start_timer_skill, "stop": stopp_timer_skill, - "status": status_timer_skill + "status": status_timer_skill, + }, + "wecker": { + "start": start_wecker_skill, + "stop": stopp_wecker_skill, + "status": status_wecker_skill } } @@ -246,7 +305,7 @@ def check_required(text): if slot not in context["slots"]: match = re.search(pattern, text) if match: - context["slots"][slot] = match.group(1) # schau an + context["slots"][slot] = match.group(0) #alles slots else: context["pending_slot"] = slot ask_for_slot(slot) @@ -268,7 +327,8 @@ def check_required(text): def ask_for_slot(slot): questions = { "location": "Für welchen Ort?", - "duration": "Wie lange soll der Timer laufen?" + "duration": "Wie lange soll der Timer laufen?", + "timeSet": "Zu welcher Uhrzeit soll der Wecker klingeln?" } speak(questions.get(slot, "Bitte spezifizieren.")) diff --git a/wecker_control.py b/wecker_control.py new file mode 100644 index 0000000..4fb864f --- /dev/null +++ b/wecker_control.py @@ -0,0 +1,147 @@ +import time +import threading +from text2numde import text2num, is_number, sentence2num +from playsound3 import playsound +from datetime import datetime, timedelta +import re +import math + +wecker_thread = None +wecker_stop = threading.Event() +wecker_status = "idle" +wecker_target_time: datetime | None = None + + +def parse_time_wecker(text): + + text = text.lower().strip() + + match = re.search(r"\b([\w]+)\s*uhr(?:\s*([\w]+))?\b", text) + if not match: + return None + + hour_text = match.group(1) + minute_text= match.group(2) + + + try: + hour = text2num(hour_text) + minute = text2num(minute_text) if minute_text else 0 + except ValueError: + print("Konnte die Zahl nicht erkennen.") + return None + + if hour < 0 or hour > 23: + return None + if minute < 0 or minute > 59: + return None + + print(f"{hour:02d}:{minute:02d}") + return hour, minute + +def calculate_target_datetime(hour: int, minute: int) -> datetime: + now = datetime.now() + + target = now.replace(hour=hour, minute=minute, second=0, microsecond=0) + + if target <= now: + target += timedelta(days=1) + + return target + + +def minutes_until(target: datetime) -> int: + #return int(((target - now)).total_seconds() // 60) hatte das davor aber das hat abgerundet + seconds = (target - datetime.now()).total_seconds() + return math.ceil(seconds / 60) + + +def start_wecker(target_time: datetime): + global wecker_thread, wecker_status, wecker_target_time + + if wecker_status == "running": + return False # wecker läuft bereits + + wecker_stop.clear() + wecker_target_time = target_time + wecker_status = "running" + + def run(): + global wecker_status + seconds = (wecker_target_time - datetime.now()).total_seconds() + if seconds < 0: + seconds = 0 + + if not wecker_stop.wait(seconds): + wecker_status = "finished" + print("wecker abgelaufen") # """ TTS """ + + wecker_thread = threading.Thread(target=run, daemon=True) + wecker_thread.start() + return True +""" + print(f"wecker startet für {seconds} Sekunden...") + time.sleep(seconds) + print("wecker abgelaufen!") + playsound("/home/tino/Desktop/Abschlussprojekt/test assistant/cloneAssistantAllInOne/RasPi_Voice_Assistant--WIP/clock-alarm-8761.mp3") + sound.stop() + """ + +""" # Beispielnutzung +spoken_input = "eine sekunde" # This could come from a voice assistant +seconds = parse_time(spoken_input) +if seconds: + start_wecker(seconds) """ + +def stop_wecker(): + global wecker_status + if wecker_status != "running": + print("Kein wecker gestellt") + return False + + wecker_stop.set() + wecker_status = "stopped" + return True + +def wecker_status_info(): + if wecker_status != "running": + return {"status": wecker_status, "target_time": 0} + + + return { + "status": "running", + "target_time": wecker_target_time + } + +def format_duration(seconds): + if seconds < 60: + return f"{seconds} {second_text(seconds)}" + + minutes = seconds // 60 + secs = seconds % 60 + + if minutes < 10: + if secs == 0: + return f"{minutes} {minute_text(minutes)}" + return f"{minutes} {minute_text(minutes)} und {secs} {second_text(secs)}" + + if minutes < 60: + return f"{minutes} {minute_text(minutes)}" + + hours = minutes // 60 + mins = minutes % 60 + + if mins == 0: + return f"{hours} {hour_text(hours)}" + return f"{hours} {hour_text(hours)} und {mins} {minute_text(mins)}" + + + +def minute_text(n): + return "Minute" if n == 1 else "Minuten" + +def second_text(n): + return "Sekunde" if n == 1 else "Sekunden" + +def hour_text(n): + return "Stunde" if n == 1 else "Stunden"