другая структура проекта + beads + александр повтори + комментарии везде + readme

This commit is contained in:
2026-01-09 04:14:50 +03:00
parent 242ead5355
commit ce28fede74
31 changed files with 1654 additions and 1333 deletions

190
app/features/alarm.py Normal file
View 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