chore: sync local changes
This commit is contained in:
@@ -1,8 +1,5 @@
|
||||
"""Timer module."""
|
||||
|
||||
# Модуль таймера.
|
||||
# Отвечает за установку таймеров (в оперативной памяти), их проверку и воспроизведение звука.
|
||||
|
||||
import subprocess
|
||||
import re
|
||||
import json
|
||||
@@ -25,7 +22,7 @@ ALARM_SOUND = BASE_DIR / "assets" / "sounds" / "Apex-1.mp3"
|
||||
TIMER_FILE = BASE_DIR / "data" / "timers.json"
|
||||
ASK_TIMER_TIME_PROMPT = "На какое время мне поставить таймер?"
|
||||
|
||||
# --- Number words parsing helpers (ru) ---
|
||||
# Числа словами
|
||||
_NUMBER_UNITS = {
|
||||
"ноль": 0,
|
||||
"один": 1,
|
||||
@@ -103,13 +100,19 @@ _UNIT_LEMMAS = {
|
||||
"мин": "minutes",
|
||||
"сек": "seconds",
|
||||
}
|
||||
_UNIT_LEMMAS = {
|
||||
"час": "hours",
|
||||
"минута": "minutes",
|
||||
"секунда": "seconds",
|
||||
"мин": "minutes",
|
||||
"сек": "seconds",
|
||||
}
|
||||
_UNIT_FORMS = {
|
||||
"hours": ("час", "часа", "часов"),
|
||||
"minutes": ("минуту", "минуты", "минут"),
|
||||
"seconds": ("секунду", "секунды", "секунд"),
|
||||
}
|
||||
|
||||
# Optional ordinal formatting for list numbering.
|
||||
try:
|
||||
from num2words import num2words
|
||||
except Exception:
|
||||
@@ -251,12 +254,11 @@ def _format_ordinal_index(index: int) -> str:
|
||||
|
||||
class TimerManager:
|
||||
def __init__(self):
|
||||
# Список активных таймеров: {"end_time": datetime, "label": str}
|
||||
self.timers = []
|
||||
self.load_timers()
|
||||
|
||||
def load_timers(self):
|
||||
"""Загрузка списка таймеров из JSON файла."""
|
||||
"""Загрузка из файла."""
|
||||
if TIMER_FILE.exists():
|
||||
try:
|
||||
with open(TIMER_FILE, "r", encoding="utf-8") as f:
|
||||
@@ -277,7 +279,7 @@ class TimerManager:
|
||||
self.timers = sorted(timers, key=lambda x: x["end_time"])
|
||||
|
||||
def save_timers(self):
|
||||
"""Сохранение списка таймеров в JSON файл."""
|
||||
"""Сохранение в файл."""
|
||||
payload = [
|
||||
{"end_time": t["end_time"].isoformat(), "label": t.get("label", "")}
|
||||
for t in self.timers
|
||||
@@ -289,7 +291,7 @@ class TimerManager:
|
||||
print(f"❌ Ошибка сохранения таймеров: {e}")
|
||||
|
||||
def describe_timers(self) -> str:
|
||||
"""Возвращает текстовое описание активных таймеров."""
|
||||
"""Описание активных таймеров."""
|
||||
if not self.timers:
|
||||
return "Активных таймеров нет."
|
||||
|
||||
@@ -312,38 +314,29 @@ class TimerManager:
|
||||
return "Активные таймеры: " + "; ".join(items) + "."
|
||||
|
||||
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"])
|
||||
self.save_timers()
|
||||
print(f"⏳ Таймер установлен на {label} (до {end_time.strftime('%H:%M:%S')})")
|
||||
print(f"⏳ Таймер: {label} (до {end_time.strftime('%H:%M:%S')})")
|
||||
|
||||
def cancel_all_timers(self):
|
||||
"""Отмена всех таймеров."""
|
||||
"""Отменить все таймеры."""
|
||||
count = len(self.timers)
|
||||
self.timers = []
|
||||
self.save_timers()
|
||||
print(f"🔕 Все таймеры ({count}) отменены.")
|
||||
print(f"🔕 Таймеры отменены: {count}")
|
||||
|
||||
def check_timers(self):
|
||||
"""
|
||||
Проверка: не истек ли какой-то таймер?
|
||||
Вызывается в главном цикле.
|
||||
Возвращает True, если таймер сработал (и был обработан).
|
||||
"""
|
||||
"""Проверка таймеров. Возвращает 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)
|
||||
self.save_timers()
|
||||
@@ -355,36 +348,30 @@ class TimerManager:
|
||||
return False
|
||||
|
||||
def trigger_timer(self, label: str):
|
||||
"""
|
||||
Логика срабатывания таймера.
|
||||
Запускает воспроизведение MP3 и слушает команду "Стоп".
|
||||
"""
|
||||
print(f"🔔 ТАЙМЕР НА {label} СРАБОТАЛ! (Скажите 'Стоп')")
|
||||
"""Срабатывание таймера."""
|
||||
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"
|
||||
)
|
||||
print("❌ mpg123 не найден. Установите: sudo apt install mpg123")
|
||||
return
|
||||
|
||||
try:
|
||||
# Цикл ожидания стоп-команды
|
||||
while True:
|
||||
text = listen(timeout_seconds=3.0, detection_timeout=3.0, fast_stop=True)
|
||||
text = listen(
|
||||
timeout_seconds=3.0, detection_timeout=3.0, fast_stop=True
|
||||
)
|
||||
if text:
|
||||
if is_stop_command(text, mode="lenient"):
|
||||
print(f"🛑 Таймер остановлен по команде: '{text}'")
|
||||
print(f"🛑 Остановлен: '{text}'")
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Ошибка во время таймера: {e}")
|
||||
print(f"❌ Ошибка: {e}")
|
||||
finally:
|
||||
# Обязательно убиваем процесс плеера
|
||||
process.terminate()
|
||||
try:
|
||||
process.wait(timeout=1)
|
||||
@@ -393,12 +380,9 @@ class TimerManager:
|
||||
print("🔕 Таймер выключен.")
|
||||
|
||||
def parse_command(self, text: str) -> str | None:
|
||||
"""
|
||||
Парсинг команды установки таймера.
|
||||
Примеры: "таймер на 5 минут", "засеки 10 секунд".
|
||||
"""
|
||||
"""Парсинг команды таймера."""
|
||||
text = _normalize_timer_text(text.lower())
|
||||
|
||||
|
||||
# Ключевые слова для таймера
|
||||
if not any(word in text for word in ["таймер", "засеки", "поставь таймер"]):
|
||||
return None
|
||||
@@ -413,9 +397,6 @@ class TimerManager:
|
||||
return "Хорошо, все таймеры отменены."
|
||||
|
||||
# Поиск времени
|
||||
# Ищем комбинации: число + (час/мин/сек)
|
||||
# Пример: "1 час 30 минут", "5 минут", "30 секунд"
|
||||
|
||||
total_seconds = 0
|
||||
parts = []
|
||||
hours = None
|
||||
@@ -438,7 +419,7 @@ class TimerManager:
|
||||
if match_seconds:
|
||||
seconds = int(match_seconds.group(1))
|
||||
|
||||
# Дополняем числительные словами (например, "одну минуту")
|
||||
# Числа словами
|
||||
word_values = _extract_word_time_values(text)
|
||||
if hours is None and word_values["hours"] is not None:
|
||||
hours = word_values["hours"]
|
||||
@@ -456,9 +437,7 @@ class TimerManager:
|
||||
|
||||
found_time = any(value is not None for value in [hours, minutes, seconds])
|
||||
if found_time:
|
||||
total_seconds = (
|
||||
(hours or 0) * 3600 + (minutes or 0) * 60 + (seconds or 0)
|
||||
)
|
||||
total_seconds = (hours or 0) * 3600 + (minutes or 0) * 60 + (seconds or 0)
|
||||
if has_fractional:
|
||||
total_seconds = int(round(total_seconds))
|
||||
h = total_seconds // 3600
|
||||
@@ -480,16 +459,17 @@ class TimerManager:
|
||||
label = " ".join(parts)
|
||||
self.add_timer(total_seconds, label)
|
||||
return f"Поставил таймер на {label}."
|
||||
|
||||
# Если попросили поставить таймер, но не назвали время — задаем уточняющий вопрос.
|
||||
if re.search(r"(постав|установ|запусти|включи|засеки)", text) or text.strip() in {
|
||||
|
||||
# Если время не названо — спрашиваем
|
||||
if re.search(
|
||||
r"(постав|установ|запусти|включи|засеки)", text
|
||||
) or text.strip() in {
|
||||
"таймер",
|
||||
"поставь таймер",
|
||||
}:
|
||||
return ASK_TIMER_TIME_PROMPT
|
||||
|
||||
# Если сказали "таймер", но не нашли время.
|
||||
return "Я не понял, на сколько поставить таймер. Скажите, например, 'таймер на 5 минут'."
|
||||
return "Я не понял, на сколько поставить таймер."
|
||||
|
||||
|
||||
# Глобальный экземпляр
|
||||
|
||||
Reference in New Issue
Block a user