другая структура проекта + beads + александр повтори + комментарии везде + readme
This commit is contained in:
0
app/features/__init__.py
Normal file
0
app/features/__init__.py
Normal file
190
app/features/alarm.py
Normal file
190
app/features/alarm.py
Normal file
@@ -0,0 +1,190 @@
|
||||
"""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.local_stt import listen_for_keywords
|
||||
|
||||
# Файл базы данных будильников
|
||||
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 и слушает команду "Стоп".
|
||||
Использует локальное распознавание (Vosk), чтобы не зависеть от интернета.
|
||||
"""
|
||||
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_for_keywords(stop_words, timeout=3.0)
|
||||
if text:
|
||||
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
|
||||
Reference in New Issue
Block a user