Улучшенная работа погоды + ускорение работы + фикс неработоспособности после пары часов

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
2026-02-02 21:06:14 +03:00
parent 2d40bc0f9b
commit 845ef7c531
7 changed files with 661 additions and 72 deletions

View File

@@ -11,6 +11,7 @@ import asyncio
import time
import pyaudio
import logging
from datetime import datetime, timedelta
from ..core.config import DEEPGRAM_API_KEY, SAMPLE_RATE
from deepgram import (
DeepgramClient,
@@ -52,6 +53,7 @@ class SpeechRecognizer:
self.pa = None
self.stream = None
self.transcript = ""
self.last_successful_operation = datetime.now()
def initialize(self):
"""Инициализация клиента Deepgram и PyAudio."""
@@ -62,10 +64,34 @@ class SpeechRecognizer:
config = DeepgramClientOptions(
verbose=logging.WARNING,
)
self.dg_client = DeepgramClient(DEEPGRAM_API_KEY, config)
try:
self.dg_client = DeepgramClient(DEEPGRAM_API_KEY, config)
except Exception as e:
print(f"❌ Ошибка при создании клиента Deepgram: {e}")
raise
self.pa = get_audio_manager().get_pyaudio()
print("✅ Deepgram клиент готов")
# Обновляем время последней успешной операции
self.last_successful_operation = datetime.now()
def check_connection_health(self):
"""Проверяет здоровье соединения и при необходимости пересоздает клиента."""
# Проверяем, прошло ли больше 15 минут с последней успешной операции
if datetime.now() - self.last_successful_operation > timedelta(minutes=15):
print("🔄 Обновление соединения Deepgram для предотвращения таймаута...")
try:
# Очищаем старый клиент
if self.stream:
self.stream.stop_stream()
self.stream.close()
self.stream = None
# Создаем новый клиент
self.dg_client = None
self.initialize()
print("✅ Соединение Deepgram обновлено")
except Exception as e:
print(f"⚠️ Ошибка при обновлении соединения: {e}")
def _get_stream(self):
"""Открывает аудиопоток PyAudio, если он еще не открыт."""
@@ -111,15 +137,27 @@ class SpeechRecognizer:
def on_speech_started(unused_self, speech_started, **kwargs):
"""Вызывается, когда VAD (Voice Activity Detection) слышит голос."""
loop.call_soon_threadsafe(speech_started_event.set)
try:
loop.call_soon_threadsafe(speech_started_event.set)
except RuntimeError:
# Event loop might be closed, ignore
pass
def on_utterance_end(unused_self, utterance_end, **kwargs):
"""Вызывается, когда Deepgram решает, что фраза закончилась (пауза)."""
loop.call_soon_threadsafe(stop_event.set)
try:
loop.call_soon_threadsafe(stop_event.set)
except RuntimeError:
# Event loop might be closed, ignore
pass
def on_error(unused_self, error, **kwargs):
print(f"Error: {error}")
loop.call_soon_threadsafe(stop_event.set)
print(f"Deepgram Error: {error}")
try:
loop.call_soon_threadsafe(stop_event.set)
except RuntimeError:
# Event loop might be closed, ignore
pass
# Подписываемся на события
dg_connection.on(LiveTranscriptionEvents.Transcript, on_transcript)
@@ -138,6 +176,8 @@ class SpeechRecognizer:
interim_results=True,
utterance_end_ms=1000, # Пауза 1.0с считается концом фразы (было 1.2)
vad_events=True,
# Добавляем параметры таймаута для долгой работы
endpointing=300, # Таймаут в миллисекундах для автоматического завершения
)
# --- Задача отправки аудио с буферизацией ---
@@ -159,11 +199,19 @@ class SpeechRecognizer:
)
# Пока подключаемся, копим данные
while not connect_future.done():
timeout_count = 0
max_timeout = 5000 # Максимальное количество итераций ожидания (около 2.5 секунд при 0.0005 задержке)
while not connect_future.done() and timeout_count < max_timeout:
if stream.is_active():
data = stream.read(4096, exception_on_overflow=False)
audio_buffer.append(data)
await asyncio.sleep(0.001)
await asyncio.sleep(0.0005) # Уменьшаем задержку для более быстрой обработки
timeout_count += 1
if timeout_count >= max_timeout:
print("⏰ Timeout connecting to Deepgram")
return
# Проверяем результат подключения
if connect_future.result() is False:
@@ -180,14 +228,18 @@ class SpeechRecognizer:
audio_buffer = None # Освобождаем память
# 4. Продолжаем стримить в реальном времени
while not stop_event.is_set():
stream_timeout = 0
max_stream_timeout = int(timeout_seconds / 0.002) # Примерный таймаут в зависимости от timeout_seconds
while not stop_event.is_set() and stream_timeout < max_stream_timeout:
if stream.is_active():
data = stream.read(4096, exception_on_overflow=False)
dg_connection.send(data)
chunks_sent += 1
if chunks_sent % 50 == 0:
print(".", end="", flush=True)
await asyncio.sleep(0.005)
await asyncio.sleep(0.002) # Уменьшаем задержку для более быстрого реагирования
stream_timeout += 1
except Exception as e:
print(f"Audio send error: {e}")
@@ -209,7 +261,7 @@ class SpeechRecognizer:
speech_started_event.wait(), timeout=detection_timeout
)
except asyncio.TimeoutError:
# Если за detection_timeout (5 сек) никто не начал говорить, выходим
# Если за detection_timeout никто не начал говорить, выходим
stop_event.set()
# 2. Если речь началась (или таймаута нет), ждем завершения (stop_event)
@@ -219,11 +271,20 @@ class SpeechRecognizer:
except asyncio.TimeoutError:
pass # Общий таймаут вышел
except Exception as e:
print(f"Error in waiting for events: {e}")
stop_event.set()
await sender_task
try:
await sender_task
except Exception as e:
print(f"Error waiting for sender task: {e}")
# Завершаем соединение и ждем последние результаты
dg_connection.finish()
try:
dg_connection.finish()
except Exception as e:
print(f"Error finishing connection: {e}")
return self.transcript
@@ -244,17 +305,21 @@ class SpeechRecognizer:
if not self.dg_client:
self.initialize()
# Проверяем здоровье соединения перед началом прослушивания
self.check_connection_health()
self.current_lang = lang
print(f"🎙️ Слушаю ({lang})...")
last_error = None
# Делаем 2 попытки на случай сбоя сети
for attempt in range(2):
# Создаем новое live подключение для каждой сессии
dg_connection = self.dg_client.listen.live.v("1")
# Делаем 3 попытки на случай сбоя сети
for attempt in range(3):
dg_connection = None
try:
# Создаем новое live подключение для каждой сессии
dg_connection = self.dg_client.listen.live.v("1")
# Запускаем асинхронный процесс обработки
transcript = asyncio.run(
self._process_audio(
@@ -264,20 +329,32 @@ class SpeechRecognizer:
final_text = transcript.strip() if transcript else ""
if final_text:
print(f"📝 Распознано: {final_text}")
# Обновляем время последней успешной операции
self.last_successful_operation = datetime.now()
return final_text
else:
# Если вернулась пустая строка (тишина), считаем это штатным завершением.
# Не нужно повторять попытку, как при ошибке сети.
# Все равно обновляем время последней успешной операции
self.last_successful_operation = datetime.now()
return ""
except Exception as e:
last_error = e
print(f"Attempt {attempt + 1} failed: {e}")
if attempt == 0:
print("⚠️ Не удалось подключиться к Deepgram, повторяю...")
time.sleep(1)
# Закрываем соединение, если оно было создано
if dg_connection:
try:
dg_connection.finish()
except:
pass # Игнорируем ошибки при завершении
if attempt < 2: # Не ждем после последней попытки
print(f"⚠️ Не удалось подключиться к Deepgram, попытка {attempt + 1}/3, повторяю...")
time.sleep(1) # Уменьшаем задержку между попытками
if last_error:
print(f"❌ Ошибка STT: {last_error}")
print(f"❌ Ошибка STT после всех попыток: {last_error}")
else:
print("⚠️ Речь не распознана")
return ""
@@ -285,10 +362,19 @@ class SpeechRecognizer:
def cleanup(self):
"""Очистка ресурсов."""
if self.stream:
self.stream.stop_stream()
self.stream.close()
try:
if self.stream.is_active():
self.stream.stop_stream()
except Exception as e:
print(f"Ошибка при остановке потока: {e}")
try:
self.stream.close()
except Exception as e:
print(f"Ошибка при закрытии потока: {e}")
self.stream = None
# self.pa.terminate() - Используем общий менеджер
# Сбросим клиента для принудительного переподключения
self.dg_client = None
# Глобальный экземпляр
@@ -313,5 +399,8 @@ def cleanup():
"""Внешняя функция очистки."""
global _recognizer
if _recognizer:
_recognizer.cleanup()
try:
_recognizer.cleanup()
except Exception as e:
print(f"Ошибка при очистке STT: {e}")
_recognizer = None