"""Alarm clock module.""" # Модуль будильника. # Отвечает за хранение будильников (в JSON файле), их проверку и воспроизведение звука. import json import subprocess import re from datetime import datetime from pathlib import Path from ..core.config import BASE_DIR from ..audio.stt import listen # Файл базы данных будильников ALARM_FILE = BASE_DIR / "data" / "alarms.json" # Звуковой файл сигнала ALARM_SOUND = BASE_DIR / "assets" / "sounds" / "Apex-1.mp3" class AlarmClock: def __init__(self): self.alarms = [] self.load_alarms() def load_alarms(self): """Загрузка списка будильников из JSON файла.""" if ALARM_FILE.exists(): try: with open(ALARM_FILE, "r", encoding="utf-8") as f: self.alarms = json.load(f) except Exception as e: print(f"❌ Ошибка загрузки будильников: {e}") self.alarms = [] def save_alarms(self): """Сохранение списка будильников в JSON файл.""" try: with open(ALARM_FILE, "w", encoding="utf-8") as f: json.dump(self.alarms, f, indent=4) except Exception as e: print(f"❌ Ошибка сохранения будильников: {e}") def add_alarm(self, hour: int, minute: int): """Добавление нового будильника (или обновление существующего).""" for alarm in self.alarms: if alarm["hour"] == hour and alarm["minute"] == minute: alarm["active"] = True self.save_alarms() return self.alarms.append({"hour": hour, "minute": minute, "active": True}) self.save_alarms() print(f"⏰ Будильник установлен на {hour:02d}:{minute:02d}") def cancel_all_alarms(self): """Выключение (деактивация) всех будильников.""" for alarm in self.alarms: alarm["active"] = False self.save_alarms() print("🔕 Все будильники отменены.") def check_alarms(self): """ Проверка: не пора ли звенеть? Вызывается в главном цикле. Возвращает True, если будильник сработал. """ now = datetime.now() triggered = False for alarm in self.alarms: if alarm["active"]: if alarm["hour"] == now.hour and alarm["minute"] == now.minute: print( f"⏰ ВРЕМЯ БУДИЛЬНИКА: {alarm['hour']:02d}:{alarm['minute']:02d}" ) alarm["active"] = ( False # Одноразовый будильник, выключаем после срабатывания ) triggered = True self.trigger_alarm() # Запуск звука и ожидание стоп-слова break # Звоним только один за раз if triggered: self.save_alarms() return True return False def trigger_alarm(self): """ Логика срабатывания будильника. Запускает воспроизведение MP3 через mpg123 и слушает команду "Стоп". Использует облачное распознавание речи для остановки. """ print("🔔 БУДИЛЬНИК ЗВОНИТ! (Скажите 'Стоп' или 'Александр стоп')") # Запуск плеера mpg123 в бесконечном цикле (--loop -1) cmd = ["mpg123", "-q", "--loop", "-1", str(ALARM_SOUND)] try: process = subprocess.Popen(cmd) except FileNotFoundError: print( "❌ Ошибка: mpg123 не найден. Установите его: sudo apt install mpg123" ) return try: stop_words = [ "стоп", "хватит", "тихо", "замолчи", "отмена", "александр стоп", ] # Цикл ожидания стоп-команды while True: text = listen(timeout_seconds=3.0, detection_timeout=3.0) if text: text_lower = text.lower() if any(word in text_lower for word in stop_words): print(f"🛑 Будильник остановлен по команде: '{text}'") break except Exception as e: print(f"❌ Ошибка во время будильника: {e}") finally: # Обязательно убиваем процесс плеера process.terminate() try: process.wait(timeout=1) except subprocess.TimeoutExpired: process.kill() print("🔕 Будильник выключен.") def parse_command(self, text: str) -> str | None: """ Парсинг команды установки будильника из текста. Примеры: "разбуди в 7:30", "будильник на 8 утра". """ text = text.lower() if "будильник" not in text and "разбуди" not in text: return None if "отмени" in text: self.cancel_all_alarms() return "Хорошо, я отменил все будильники." # Поиск формата "7:30", "7.30" match = re.search(r"\b(\d{1,2})[:.-](\d{2})\b", text) if match: h, m = int(match.group(1)), int(match.group(2)) if 0 <= h <= 23 and 0 <= m <= 59: self.add_alarm(h, m) return f"Я установил будильник на {h} часов {m} минут." # Поиск формата словами "на 7 часов 15 минут" match_time = re.search( r"на\s+(\d{1,2})(?:\s*(?:часов|часа|час))?(?:\s+(\d{1,2})(?:\s*(?:минут|минуты|минута))?)?", text, ) if match_time: h = int(match_time.group(1)) m = int(match_time.group(2)) if match_time.group(2) else 0 # Умная коррекция времени (если говорят "в 8", а сейчас 9, то это скорее 8 вечера или 8 утра завтра) # Здесь простая логика AM/PM if "вечера" in text and h < 12: h += 12 elif "утра" in text and h == 12: h = 0 if 0 <= h <= 23 and 0 <= m <= 59: self.add_alarm(h, m) return f"Хорошо, разбужу вас в {h}:{m:02d}." return "Я не понял время для будильника. Пожалуйста, скажите точное время, например 'семь тридцать'." # Глобальный экземпляр _alarm_clock = None def get_alarm_clock(): global _alarm_clock if _alarm_clock is None: _alarm_clock = AlarmClock() return _alarm_clock