Desmond-Dong commited on
Commit
cea3a9b
·
1 Parent(s): d77ff8d

Add extensive logging and error handling

Browse files

- Add module-level logging configuration
- Add print statements for visibility
- Add detailed logging at each step
- Add exception handling with traceback
- Log when run() method is called
- Log when settings_app is available or not
- Log each initialization step
This will help diagnose why the app appears to run but doesn't work

Files changed (1) hide show
  1. reachy_mini_ha_voice/app.py +66 -10
reachy_mini_ha_voice/app.py CHANGED
@@ -2,8 +2,10 @@
2
 
3
  import asyncio
4
  import logging
 
5
  import threading
6
  import time
 
7
  from pathlib import Path
8
  from queue import Queue
9
  from typing import Dict, List, Optional, Set, Union
@@ -36,12 +38,24 @@ except Exception:
36
  JSONResponse = object
37
  StaticFiles = object
38
 
 
 
 
 
 
 
 
39
  _LOGGER = logging.getLogger(__name__)
40
  _MODULE_DIR = Path(__file__).parent
41
  _REPO_DIR = _MODULE_DIR.parent
42
  _WAKEWORDS_DIR = _REPO_DIR / "wakewords"
43
  _SOUNDS_DIR = _REPO_DIR / "sounds"
44
 
 
 
 
 
 
45
 
46
  class ReachyMiniHAVoiceApp(ReachyMiniApp):
47
  """Home Assistant Voice Assistant for Reachy Mini."""
@@ -50,6 +64,7 @@ class ReachyMiniHAVoiceApp(ReachyMiniApp):
50
 
51
  def __init__(self):
52
  """Initialize the app."""
 
53
  self._state: Optional[ServerState] = None
54
  self._event_loop: Optional[asyncio.AbstractEventLoop] = None
55
  self._audio_thread: Optional[threading.Thread] = None
@@ -61,33 +76,51 @@ class ReachyMiniHAVoiceApp(ReachyMiniApp):
61
 
62
  def run(self, reachy_mini: ReachyMini, stop_event: threading.Event):
63
  """Run the voice assistant."""
 
 
 
 
64
  _LOGGER.info("Starting Reachy Mini Home Assistant Voice Assistant")
 
65
 
66
  self._robot = reachy_mini
67
  self._stop_event = stop_event
68
 
69
  # Setup settings API
70
- self._init_settings_ui_if_needed()
 
 
 
 
 
71
 
72
  try:
73
  # Create event loop
 
74
  self._event_loop = asyncio.new_event_loop()
75
  asyncio.set_event_loop(self._event_loop)
 
76
 
77
  # Initialize server state
 
78
  self._state = self._init_state(reachy_mini)
 
79
 
80
  # Start media recording and playing
 
81
  reachy_mini.media.start_recording()
82
  reachy_mini.media.start_playing()
83
  time.sleep(1) # Give time for pipelines to start
 
84
 
85
  # Start audio processing loop
 
86
  self._event_loop.run_until_complete(self._run_audio_loop(self._state, stop_event))
87
 
88
  except Exception as e:
89
- _LOGGER.error("Error running voice assistant: %s", e)
90
- import traceback
 
91
  traceback.print_exc()
92
  finally:
93
  _LOGGER.info("Shutting down voice assistant")
@@ -98,6 +131,7 @@ class ReachyMiniHAVoiceApp(ReachyMiniApp):
98
  if self._settings_initialized:
99
  return
100
  if not hasattr(self, 'settings_app') or self.settings_app is None:
 
101
  return
102
 
103
  static_dir = _MODULE_DIR / "static"
@@ -106,7 +140,9 @@ class ReachyMiniHAVoiceApp(ReachyMiniApp):
106
  if hasattr(self.settings_app, "mount"):
107
  try:
108
  self.settings_app.mount("/static", StaticFiles(directory=str(static_dir)), name="static")
109
- except Exception:
 
 
110
  pass
111
 
112
  class AppStatus(BaseModel):
@@ -129,11 +165,14 @@ class ReachyMiniHAVoiceApp(ReachyMiniApp):
129
  })
130
 
131
  self._settings_initialized = True
 
132
 
133
  def _init_state(self, reachy_mini: ReachyMini) -> ServerState:
134
  """Initialize server state."""
135
  # Load wake words
 
136
  available_wake_words = self._load_wake_words()
 
137
 
138
  # Load active wake words
139
  active_wake_words = set()
@@ -151,7 +190,10 @@ class ReachyMiniHAVoiceApp(ReachyMiniApp):
151
  _LOGGER.error("Failed to load wake word %s: %s", default_wake_word, e)
152
 
153
  # Load stop model
 
154
  stop_model = self._load_stop_model()
 
 
155
 
156
  return ServerState(
157
  name="ReachyMini",
@@ -180,6 +222,7 @@ class ReachyMiniHAVoiceApp(ReachyMiniApp):
180
 
181
  for wake_word_dir in [_WAKEWORDS_DIR]:
182
  if not wake_word_dir.exists():
 
183
  continue
184
 
185
  for model_config_path in wake_word_dir.glob("*.json"):
@@ -214,6 +257,7 @@ class ReachyMiniHAVoiceApp(ReachyMiniApp):
214
  """Load stop word model."""
215
  stop_config_path = _WAKEWORDS_DIR / "stop.json"
216
  if not stop_config_path.exists():
 
217
  return None
218
 
219
  try:
@@ -224,20 +268,26 @@ class ReachyMiniHAVoiceApp(ReachyMiniApp):
224
 
225
  async def _run_audio_loop(self, state: ServerState, stop_event: threading.Event) -> None:
226
  """Run audio processing loop and ESPHome server."""
 
227
  # Start ESPHome server
228
  loop = asyncio.get_running_loop()
229
  self._server = await loop.create_server(
230
  lambda: VoiceSatelliteProtocol(state), host="0.0.0.0", port=6053
231
  )
 
232
 
233
  # Auto discovery (zeroconf, mDNS)
 
234
  self._discovery = HomeAssistantZeroconf(port=6053, name="ReachyMini")
235
  await self._discovery.register_server()
 
236
 
237
  try:
238
  async with self._server:
 
239
  _LOGGER.info("ESPHome server started on port 6053")
240
  _LOGGER.info("mDNS service registered for auto-discovery")
 
241
 
242
  # Audio processing loop
243
  input_sample_rate = self._robot.media.get_input_audio_samplerate()
@@ -253,7 +303,7 @@ class ReachyMiniHAVoiceApp(ReachyMiniApp):
253
 
254
  last_active: Optional[float] = None
255
 
256
- _LOGGER.info("Audio processing started")
257
 
258
  while not stop_event.is_set():
259
  # Get audio sample from Reachy Mini
@@ -327,7 +377,7 @@ class ReachyMiniHAVoiceApp(ReachyMiniApp):
327
  if state.stop_word.process_streaming(micro_input):
328
  stopped = True
329
 
330
- if stopped and (state.stop_word.id in state.active_wake_words):
331
  if state.satellite:
332
  state.satellite.stop()
333
 
@@ -337,22 +387,28 @@ class ReachyMiniHAVoiceApp(ReachyMiniApp):
337
 
338
  finally:
339
  if self._discovery:
 
340
  await self._discovery.unregister_server()
341
  _LOGGER.info("ESPHome server stopped")
342
 
343
  def _cleanup(self) -> None:
344
  """Clean up resources."""
 
345
  if self._robot:
346
  try:
347
  self._robot.media.stop_recording()
348
- except Exception:
349
- pass
 
350
  try:
351
  self._robot.media.stop_playing()
352
- except Exception:
353
- pass
 
354
  if self._event_loop and not self._event_loop.is_closed():
355
  self._event_loop.close()
 
 
356
 
357
 
358
  class ReachyMiniAudioPlayer:
 
2
 
3
  import asyncio
4
  import logging
5
+ import sys
6
  import threading
7
  import time
8
+ import traceback
9
  from pathlib import Path
10
  from queue import Queue
11
  from typing import Dict, List, Optional, Set, Union
 
38
  JSONResponse = object
39
  StaticFiles = object
40
 
41
+ # Configure root logger to ensure logs are visible
42
+ logging.basicConfig(
43
+ level=logging.INFO,
44
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
45
+ stream=sys.stdout
46
+ )
47
+
48
  _LOGGER = logging.getLogger(__name__)
49
  _MODULE_DIR = Path(__file__).parent
50
  _REPO_DIR = _MODULE_DIR.parent
51
  _WAKEWORDS_DIR = _REPO_DIR / "wakewords"
52
  _SOUNDS_DIR = _REPO_DIR / "sounds"
53
 
54
+ # Log when module is loaded
55
+ print("=" * 80)
56
+ print("Reachy Mini Home Assistant Voice Assistant module loaded")
57
+ print("=" * 80)
58
+
59
 
60
  class ReachyMiniHAVoiceApp(ReachyMiniApp):
61
  """Home Assistant Voice Assistant for Reachy Mini."""
 
64
 
65
  def __init__(self):
66
  """Initialize the app."""
67
+ print("ReachyMiniHAVoiceApp.__init__() called")
68
  self._state: Optional[ServerState] = None
69
  self._event_loop: Optional[asyncio.AbstractEventLoop] = None
70
  self._audio_thread: Optional[threading.Thread] = None
 
76
 
77
  def run(self, reachy_mini: ReachyMini, stop_event: threading.Event):
78
  """Run the voice assistant."""
79
+ print("=" * 80)
80
+ print("ReachyMiniHAVoiceApp.run() called")
81
+ print("=" * 80)
82
+ _LOGGER.info("=" * 80)
83
  _LOGGER.info("Starting Reachy Mini Home Assistant Voice Assistant")
84
+ _LOGGER.info("=" * 80)
85
 
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...")
100
  self._event_loop = asyncio.new_event_loop()
101
  asyncio.set_event_loop(self._event_loop)
102
+ _LOGGER.info("Event loop created")
103
 
104
  # Initialize server state
105
+ _LOGGER.info("Initializing server state...")
106
  self._state = self._init_state(reachy_mini)
107
+ _LOGGER.info("Server state initialized")
108
 
109
  # Start media recording and playing
110
+ _LOGGER.info("Starting media recording and playing...")
111
  reachy_mini.media.start_recording()
112
  reachy_mini.media.start_playing()
113
  time.sleep(1) # Give time for pipelines to start
114
+ _LOGGER.info("Media started")
115
 
116
  # Start audio processing loop
117
+ _LOGGER.info("Starting audio processing loop...")
118
  self._event_loop.run_until_complete(self._run_audio_loop(self._state, stop_event))
119
 
120
  except Exception as e:
121
+ _LOGGER.error("=" * 80)
122
+ _LOGGER.error(f"Error running voice assistant: {e}")
123
+ _LOGGER.error("=" * 80)
124
  traceback.print_exc()
125
  finally:
126
  _LOGGER.info("Shutting down voice assistant")
 
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"
 
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):
 
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
173
+ _LOGGER.info("Loading wake words...")
174
  available_wake_words = self._load_wake_words()
175
+ _LOGGER.info(f"Loaded {len(available_wake_words)} wake words")
176
 
177
  # Load active wake words
178
  active_wake_words = set()
 
190
  _LOGGER.error("Failed to load wake word %s: %s", default_wake_word, e)
191
 
192
  # Load stop model
193
+ _LOGGER.info("Loading stop model...")
194
  stop_model = self._load_stop_model()
195
+ if stop_model:
196
+ _LOGGER.info("Stop model loaded")
197
 
198
  return ServerState(
199
  name="ReachyMini",
 
222
 
223
  for wake_word_dir in [_WAKEWORDS_DIR]:
224
  if not wake_word_dir.exists():
225
+ _LOGGER.warning(f"Wake word directory not found: {wake_word_dir}")
226
  continue
227
 
228
  for model_config_path in wake_word_dir.glob("*.json"):
 
257
  """Load stop word model."""
258
  stop_config_path = _WAKEWORDS_DIR / "stop.json"
259
  if not stop_config_path.exists():
260
+ _LOGGER.warning(f"Stop model config not found: {stop_config_path}")
261
  return None
262
 
263
  try:
 
268
 
269
  async def _run_audio_loop(self, state: ServerState, stop_event: threading.Event) -> None:
270
  """Run audio processing loop and ESPHome server."""
271
+ _LOGGER.info("Starting ESPHome server...")
272
  # Start ESPHome server
273
  loop = asyncio.get_running_loop()
274
  self._server = await loop.create_server(
275
  lambda: VoiceSatelliteProtocol(state), host="0.0.0.0", port=6053
276
  )
277
+ _LOGGER.info("ESPHome server created")
278
 
279
  # Auto discovery (zeroconf, mDNS)
280
+ _LOGGER.info("Registering mDNS service...")
281
  self._discovery = HomeAssistantZeroconf(port=6053, name="ReachyMini")
282
  await self._discovery.register_server()
283
+ _LOGGER.info("mDNS service registered")
284
 
285
  try:
286
  async with self._server:
287
+ _LOGGER.info("=" * 80)
288
  _LOGGER.info("ESPHome server started on port 6053")
289
  _LOGGER.info("mDNS service registered for auto-discovery")
290
+ _LOGGER.info("=" * 80)
291
 
292
  # Audio processing loop
293
  input_sample_rate = self._robot.media.get_input_audio_samplerate()
 
303
 
304
  last_active: Optional[float] = None
305
 
306
+ _LOGGER.info("Audio processing loop started")
307
 
308
  while not stop_event.is_set():
309
  # Get audio sample from Reachy Mini
 
377
  if state.stop_word.process_streaming(micro_input):
378
  stopped = True
379
 
380
+ if stopped and (self._stop_word.id in state.active_wake_words):
381
  if state.satellite:
382
  state.satellite.stop()
383
 
 
387
 
388
  finally:
389
  if self._discovery:
390
+ _LOGGER.info("Unregistering mDNS service...")
391
  await self._discovery.unregister_server()
392
  _LOGGER.info("ESPHome server stopped")
393
 
394
  def _cleanup(self) -> None:
395
  """Clean up resources."""
396
+ _LOGGER.info("Cleaning up resources...")
397
  if self._robot:
398
  try:
399
  self._robot.media.stop_recording()
400
+ _LOGGER.info("Recording stopped")
401
+ except Exception as e:
402
+ _LOGGER.error(f"Error stopping recording: {e}")
403
  try:
404
  self._robot.media.stop_playing()
405
+ _LOGGER.info("Playing stopped")
406
+ except Exception as e:
407
+ _LOGGER.error(f"Error stopping playing: {e}")
408
  if self._event_loop and not self._event_loop.is_closed():
409
  self._event_loop.close()
410
+ _LOGGER.info("Event loop closed")
411
+ _LOGGER.info("Cleanup complete")
412
 
413
 
414
  class ReachyMiniAudioPlayer: