175 lines
6.6 KiB
Python
175 lines
6.6 KiB
Python
"""Timer module."""
|
||
|
||
# Модуль таймера.
|
||
# Отвечает за установку таймеров (в оперативной памяти), их проверку и воспроизведение звука.
|
||
|
||
import subprocess
|
||
import re
|
||
from datetime import datetime, timedelta
|
||
from pathlib import Path
|
||
from ..core.config import BASE_DIR
|
||
from ..audio.stt import listen
|
||
|
||
# Звуковой файл сигнала (используем тот же, что и для будильника)
|
||
ALARM_SOUND = BASE_DIR / "assets" / "sounds" / "Apex-1.mp3"
|
||
|
||
|
||
class TimerManager:
|
||
def __init__(self):
|
||
# Список активных таймеров: {"end_time": datetime, "label": str}
|
||
self.timers = []
|
||
|
||
def add_timer(self, seconds: int, label: str):
|
||
"""Добавление нового таймера."""
|
||
end_time = datetime.now() + timedelta(seconds=seconds)
|
||
self.timers.append({"end_time": end_time, "label": label})
|
||
# Сортируем, чтобы ближайший был первым
|
||
self.timers.sort(key=lambda x: x["end_time"])
|
||
print(f"⏳ Таймер установлен на {label} (до {end_time.strftime('%H:%M:%S')})")
|
||
|
||
def cancel_all_timers(self):
|
||
"""Отмена всех таймеров."""
|
||
count = len(self.timers)
|
||
self.timers = []
|
||
print(f"🔕 Все таймеры ({count}) отменены.")
|
||
|
||
def check_timers(self):
|
||
"""
|
||
Проверка: не истек ли какой-то таймер?
|
||
Вызывается в главном цикле.
|
||
Возвращает True, если таймер сработал (и был обработан).
|
||
"""
|
||
if not self.timers:
|
||
return False
|
||
|
||
now = datetime.now()
|
||
# Смотрим первый (самый ранний) таймер
|
||
# Используем индекс 0, так как список отсортирован
|
||
first_timer = self.timers[0]
|
||
|
||
if now >= first_timer["end_time"]:
|
||
# Таймер сработал!
|
||
# Удаляем его из списка
|
||
label = first_timer["label"]
|
||
self.timers.pop(0)
|
||
|
||
print(f"⌛ ТАЙМЕР ИСТЕК: {label}")
|
||
self.trigger_timer(label)
|
||
return True
|
||
|
||
return False
|
||
|
||
def trigger_timer(self, label: str):
|
||
"""
|
||
Логика срабатывания таймера.
|
||
Запускает воспроизведение MP3 и слушает команду "Стоп".
|
||
"""
|
||
print(f"🔔 ТАЙМЕР НА {label} СРАБОТАЛ! (Скажите 'Стоп')")
|
||
|
||
# Запуск плеера mpg123 в бесконечном цикле
|
||
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:
|
||
"""
|
||
Парсинг команды установки таймера.
|
||
Примеры: "таймер на 5 минут", "засеки 10 секунд".
|
||
"""
|
||
text = text.lower()
|
||
|
||
# Ключевые слова для таймера
|
||
if not any(word in text for word in ["таймер", "засеки", "поставь таймер"]):
|
||
return None
|
||
|
||
if "отмени" in text or "удали" in text:
|
||
self.cancel_all_timers()
|
||
return "Хорошо, все таймеры отменены."
|
||
|
||
# Поиск времени
|
||
# Ищем комбинации: число + (час/мин/сек)
|
||
# Пример: "1 час 30 минут", "5 минут", "30 секунд"
|
||
|
||
total_seconds = 0
|
||
found_time = False
|
||
parts = []
|
||
|
||
# Часы
|
||
match_hours = re.search(r"(\d+)\s*(?:час|часа|часов)", text)
|
||
if match_hours:
|
||
h = int(match_hours.group(1))
|
||
total_seconds += h * 3600
|
||
parts.append(f"{h} ч")
|
||
found_time = True
|
||
|
||
# Минуты
|
||
match_minutes = re.search(r"(\d+)\s*(?:мин|минуту|минуты|минут)", text)
|
||
if match_minutes:
|
||
m = int(match_minutes.group(1))
|
||
total_seconds += m * 60
|
||
parts.append(f"{m} мин")
|
||
found_time = True
|
||
|
||
# Секунды
|
||
match_seconds = re.search(r"(\d+)\s*(?:сек|секунду|секунды|секунд)", text)
|
||
if match_seconds:
|
||
s = int(match_seconds.group(1))
|
||
total_seconds += s
|
||
parts.append(f"{s} сек")
|
||
found_time = True
|
||
|
||
if found_time and total_seconds > 0:
|
||
label = " ".join(parts)
|
||
self.add_timer(total_seconds, label)
|
||
return f"Засек {label}."
|
||
|
||
# Если сказали "таймер", но не нашли время
|
||
return "Я не понял, на сколько поставить таймер. Скажите, например, 'таймер на 5 минут'."
|
||
|
||
|
||
# Глобальный экземпляр
|
||
_timer_manager = None
|
||
|
||
|
||
def get_timer_manager():
|
||
global _timer_manager
|
||
if _timer_manager is None:
|
||
_timer_manager = TimerManager()
|
||
return _timer_manager |