Commit ·
665fc27
1
Parent(s): cea3a9b
Simplify app.py to match official documentation
Browse files- Set custom_app_url to None (no custom settings UI)
- Remove settings_app related code (not in official docs)
- Add extensive logging for debugging
- Fix variable reference errors
- Follow official Reachy Mini app structure
- reachy_mini_ha_voice/app.py +2 -66
reachy_mini_ha_voice/app.py
CHANGED
|
@@ -9,7 +9,6 @@ import traceback
|
|
| 9 |
from pathlib import Path
|
| 10 |
from queue import Queue
|
| 11 |
from typing import Dict, List, Optional, Set, Union
|
| 12 |
-
from pydantic import BaseModel
|
| 13 |
|
| 14 |
import numpy as np
|
| 15 |
from pymicro_wakeword import MicroWakeWord, MicroWakeWordFeatures
|
|
@@ -28,16 +27,6 @@ from .satellite import VoiceSatelliteProtocol
|
|
| 28 |
from .util import get_mac
|
| 29 |
from .zeroconf import HomeAssistantZeroconf
|
| 30 |
|
| 31 |
-
try:
|
| 32 |
-
from fastapi import FastAPI, Response
|
| 33 |
-
from fastapi.responses import FileResponse, JSONResponse
|
| 34 |
-
from starlette.staticfiles import StaticFiles
|
| 35 |
-
except Exception:
|
| 36 |
-
FastAPI = object
|
| 37 |
-
FileResponse = object
|
| 38 |
-
JSONResponse = object
|
| 39 |
-
StaticFiles = object
|
| 40 |
-
|
| 41 |
# Configure root logger to ensure logs are visible
|
| 42 |
logging.basicConfig(
|
| 43 |
level=logging.INFO,
|
|
@@ -60,7 +49,7 @@ print("=" * 80)
|
|
| 60 |
class ReachyMiniHAVoiceApp(ReachyMiniApp):
|
| 61 |
"""Home Assistant Voice Assistant for Reachy Mini."""
|
| 62 |
|
| 63 |
-
custom_app_url: Optional[str] =
|
| 64 |
|
| 65 |
def __init__(self):
|
| 66 |
"""Initialize the app."""
|
|
@@ -72,7 +61,6 @@ class ReachyMiniHAVoiceApp(ReachyMiniApp):
|
|
| 72 |
self._discovery: Optional[HomeAssistantZeroconf] = None
|
| 73 |
self._robot: Optional[ReachyMini] = None
|
| 74 |
self._stop_event: Optional[threading.Event] = None
|
| 75 |
-
self._settings_initialized = False
|
| 76 |
|
| 77 |
def run(self, reachy_mini: ReachyMini, stop_event: threading.Event):
|
| 78 |
"""Run the voice assistant."""
|
|
@@ -86,14 +74,6 @@ class ReachyMiniHAVoiceApp(ReachyMiniApp):
|
|
| 86 |
self._robot = reachy_mini
|
| 87 |
self._stop_event = stop_event
|
| 88 |
|
| 89 |
-
# Setup settings API
|
| 90 |
-
try:
|
| 91 |
-
self._init_settings_ui_if_needed()
|
| 92 |
-
_LOGGER.info("Settings UI initialized")
|
| 93 |
-
except Exception as e:
|
| 94 |
-
_LOGGER.error(f"Failed to initialize settings UI: {e}")
|
| 95 |
-
traceback.print_exc()
|
| 96 |
-
|
| 97 |
try:
|
| 98 |
# Create event loop
|
| 99 |
_LOGGER.info("Creating event loop...")
|
|
@@ -126,47 +106,6 @@ class ReachyMiniHAVoiceApp(ReachyMiniApp):
|
|
| 126 |
_LOGGER.info("Shutting down voice assistant")
|
| 127 |
self._cleanup()
|
| 128 |
|
| 129 |
-
def _init_settings_ui_if_needed(self) -> None:
|
| 130 |
-
"""Attach settings UI to the settings app."""
|
| 131 |
-
if self._settings_initialized:
|
| 132 |
-
return
|
| 133 |
-
if not hasattr(self, 'settings_app') or self.settings_app is None:
|
| 134 |
-
_LOGGER.warning("settings_app not available, skipping settings UI")
|
| 135 |
-
return
|
| 136 |
-
|
| 137 |
-
static_dir = _MODULE_DIR / "static"
|
| 138 |
-
index_file = static_dir / "index.html"
|
| 139 |
-
|
| 140 |
-
if hasattr(self.settings_app, "mount"):
|
| 141 |
-
try:
|
| 142 |
-
self.settings_app.mount("/static", StaticFiles(directory=str(static_dir)), name="static")
|
| 143 |
-
_LOGGER.info(f"Mounted static files from {static_dir}")
|
| 144 |
-
except Exception as e:
|
| 145 |
-
_LOGGER.warning(f"Failed to mount static files: {e}")
|
| 146 |
-
pass
|
| 147 |
-
|
| 148 |
-
class AppStatus(BaseModel):
|
| 149 |
-
running: bool
|
| 150 |
-
connected: bool = False
|
| 151 |
-
|
| 152 |
-
@self.settings_app.get("/")
|
| 153 |
-
def _root() -> FileResponse:
|
| 154 |
-
return FileResponse(str(index_file))
|
| 155 |
-
|
| 156 |
-
@self.settings_app.get("/favicon.ico")
|
| 157 |
-
def _favicon() -> Response:
|
| 158 |
-
return Response(status_code=204)
|
| 159 |
-
|
| 160 |
-
@self.settings_app.get("/status")
|
| 161 |
-
def _status() -> JSONResponse:
|
| 162 |
-
return JSONResponse({
|
| 163 |
-
"running": self._state is not None,
|
| 164 |
-
"connected": self._state.satellite is not None if self._state else False
|
| 165 |
-
})
|
| 166 |
-
|
| 167 |
-
self._settings_initialized = True
|
| 168 |
-
_LOGGER.info("Settings UI routes registered")
|
| 169 |
-
|
| 170 |
def _init_state(self, reachy_mini: ReachyMini) -> ServerState:
|
| 171 |
"""Initialize server state."""
|
| 172 |
# Load wake words
|
|
@@ -377,7 +316,7 @@ class ReachyMiniHAVoiceApp(ReachyMiniApp):
|
|
| 377 |
if state.stop_word.process_streaming(micro_input):
|
| 378 |
stopped = True
|
| 379 |
|
| 380 |
-
if stopped and (
|
| 381 |
if state.satellite:
|
| 382 |
state.satellite.stop()
|
| 383 |
|
|
@@ -416,7 +355,6 @@ class ReachyMiniAudioPlayer:
|
|
| 416 |
|
| 417 |
def __init__(self, robot: ReachyMini):
|
| 418 |
self._robot = robot
|
| 419 |
-
self._playing = False
|
| 420 |
|
| 421 |
def play(self, audio_source, done_callback=None):
|
| 422 |
"""Play audio from file or URL."""
|
|
@@ -425,8 +363,6 @@ class ReachyMiniAudioPlayer:
|
|
| 425 |
# Check if it's a URL or file path
|
| 426 |
if audio_source.startswith(('http://', 'https://')):
|
| 427 |
_LOGGER.info(f"Playing audio from URL: {audio_source}")
|
| 428 |
-
# For URLs, we would need to download and play
|
| 429 |
-
# For now, just log
|
| 430 |
else:
|
| 431 |
# Load audio file
|
| 432 |
import soundfile as sf
|
|
|
|
| 9 |
from pathlib import Path
|
| 10 |
from queue import Queue
|
| 11 |
from typing import Dict, List, Optional, Set, Union
|
|
|
|
| 12 |
|
| 13 |
import numpy as np
|
| 14 |
from pymicro_wakeword import MicroWakeWord, MicroWakeWordFeatures
|
|
|
|
| 27 |
from .util import get_mac
|
| 28 |
from .zeroconf import HomeAssistantZeroconf
|
| 29 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
# Configure root logger to ensure logs are visible
|
| 31 |
logging.basicConfig(
|
| 32 |
level=logging.INFO,
|
|
|
|
| 49 |
class ReachyMiniHAVoiceApp(ReachyMiniApp):
|
| 50 |
"""Home Assistant Voice Assistant for Reachy Mini."""
|
| 51 |
|
| 52 |
+
custom_app_url: Optional[str] = None
|
| 53 |
|
| 54 |
def __init__(self):
|
| 55 |
"""Initialize the app."""
|
|
|
|
| 61 |
self._discovery: Optional[HomeAssistantZeroconf] = None
|
| 62 |
self._robot: Optional[ReachyMini] = None
|
| 63 |
self._stop_event: Optional[threading.Event] = None
|
|
|
|
| 64 |
|
| 65 |
def run(self, reachy_mini: ReachyMini, stop_event: threading.Event):
|
| 66 |
"""Run the voice assistant."""
|
|
|
|
| 74 |
self._robot = reachy_mini
|
| 75 |
self._stop_event = stop_event
|
| 76 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
try:
|
| 78 |
# Create event loop
|
| 79 |
_LOGGER.info("Creating event loop...")
|
|
|
|
| 106 |
_LOGGER.info("Shutting down voice assistant")
|
| 107 |
self._cleanup()
|
| 108 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
def _init_state(self, reachy_mini: ReachyMini) -> ServerState:
|
| 110 |
"""Initialize server state."""
|
| 111 |
# Load wake words
|
|
|
|
| 316 |
if state.stop_word.process_streaming(micro_input):
|
| 317 |
stopped = True
|
| 318 |
|
| 319 |
+
if stopped and (state.stop_word.id in state.active_wake_words):
|
| 320 |
if state.satellite:
|
| 321 |
state.satellite.stop()
|
| 322 |
|
|
|
|
| 355 |
|
| 356 |
def __init__(self, robot: ReachyMini):
|
| 357 |
self._robot = robot
|
|
|
|
| 358 |
|
| 359 |
def play(self, audio_source, done_callback=None):
|
| 360 |
"""Play audio from file or URL."""
|
|
|
|
| 363 |
# Check if it's a URL or file path
|
| 364 |
if audio_source.startswith(('http://', 'https://')):
|
| 365 |
_LOGGER.info(f"Playing audio from URL: {audio_source}")
|
|
|
|
|
|
|
| 366 |
else:
|
| 367 |
# Load audio file
|
| 368 |
import soundfile as sf
|