Files
smart-speaker/main.py

137 lines
4.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Smart Speaker - Main Application
Голосовой ассистент с wake word detection, STT, AI и TTS.
Flow:
1. Wait for wake word ("Alexandr")
2. Listen to user speech (STT)
3. Send query to AI (Perplexity)
4. Clean response from markdown
5. Speak response (TTS)
6. Loop back to step 1
"""
import signal
import sys
from wakeword import wait_for_wakeword, cleanup as cleanup_wakeword, check_wakeword_once
from stt import listen, cleanup as cleanup_stt, get_recognizer
from ai import ask_ai
from cleaner import clean_response
from tts import speak, initialize as init_tts
from sound_level import set_volume, parse_volume_text
def signal_handler(sig, frame):
"""Handle Ctrl+C gracefully."""
print("\n\n👋 Завершение работы...")
cleanup_wakeword()
cleanup_stt()
sys.exit(0)
def main():
"""Main application loop."""
print("=" * 50)
print("🔊 УМНАЯ КОЛОНКА")
print("=" * 50)
print("Скажите 'Alexandr' для активации")
print("Нажмите Ctrl+C для выхода")
print("=" * 50)
print()
# Setup signal handler for graceful exit
signal.signal(signal.SIGINT, signal_handler)
# Pre-initialize models (takes a few seconds)
print("⏳ Инициализация моделей...")
get_recognizer().initialize() # Initialize STT model first
init_tts() # Then initialize TTS model
print()
# Main loop
skip_wakeword = False
while True:
try:
# Step 1: Wait for wake word or Follow-up listen
if not skip_wakeword:
wait_for_wakeword()
# Standard listen after activation
user_text = listen(timeout_seconds=7.0)
else:
# Follow-up listen (wait 2.0s for start, then listen long)
print("👂 Слушаю продолжение диалога...")
user_text = listen(timeout_seconds=20.0, detection_timeout=2.0)
if not user_text:
# User didn't continue conversation, go back to sleep
skip_wakeword = False
continue
# Reset flag for now (will be set to True if we speak successfully)
skip_wakeword = False
# Step 2: Check if speech was recognized
if not user_text:
speak("Извините, я вас не расслышал. Попробуйте ещё раз.")
continue
# Check for volume command
if user_text.lower().startswith("громкость"):
try:
# Remove "громкость" prefix and strip whitespace
vol_str = user_text.lower().replace("громкость", "", 1).strip()
# Try to parse the number
level = parse_volume_text(vol_str)
if level is not None:
if set_volume(level):
speak(f"Громкость установлена на {level}")
else:
speak("Не удалось установить громкость.")
else:
speak(
"Я не понял число громкости. Скажите число от одного до десяти."
)
continue
except Exception as e:
print(f"❌ Ошибка громкости: {e}")
speak("Не удалось изменить громкость.")
continue
# Step 3: Send to AI
ai_response = ask_ai(user_text)
# Step 4: Clean response
clean_text = clean_response(ai_response)
# Step 5: Speak response (with wake word interrupt support)
completed = speak(clean_text, check_interrupt=check_wakeword_once)
# Enable follow-up mode for next iteration
skip_wakeword = True
# If interrupted by wake word, we still want to skip_wakeword (which is set above)
# but we can print a message
if not completed:
print("⏹️ Ответ прерван - слушаю следующий вопрос")
continue
print()
print("-" * 30)
print()
# Step 6: Loop continues with skip_wakeword=True
except KeyboardInterrupt:
signal_handler(None, None)
except Exception as e:
print(f"❌ Ошибка: {e}")
speak("Произошла ошибка. Попробуйте ещё раз.")
if __name__ == "__main__":
main()