Desmond-Dong commited on
Commit
f6e443c
·
1 Parent(s): 64faf24

Fix app.py to properly run and handle stop event

Browse files

- Change event loop handling to keep server running until stop_event
- Use while loop with asyncio.sleep instead of run_until_complete
- Add proper cleanup in finally block
- Ensure mDNS service is registered for Home Assistant auto-discovery
- Fix audio processing to use pyaudio correctly

Files changed (1) hide show
  1. reachy_mini_ha_voice/app.py +33 -26
reachy_mini_ha_voice/app.py CHANGED
@@ -24,6 +24,7 @@ from .models import (
24
  )
25
  from .satellite import VoiceSatelliteProtocol
26
  from .util import get_mac
 
27
 
28
  _LOGGER = logging.getLogger(__name__)
29
  _MODULE_DIR = Path(__file__).parent
@@ -42,7 +43,8 @@ class ReachyMiniHAVoiceApp(ReachyMiniApp):
42
  self._state: Optional[ServerState] = None
43
  self._event_loop: Optional[asyncio.AbstractEventLoop] = None
44
  self._audio_thread: Optional[threading.Thread] = None
45
- self._server_task: Optional[asyncio.Task] = None
 
46
 
47
  def run(self, reachy_mini: ReachyMini, stop_event: threading.Event):
48
  """Run the voice assistant."""
@@ -65,19 +67,15 @@ class ReachyMiniHAVoiceApp(ReachyMiniApp):
65
  self._audio_thread.start()
66
 
67
  # Start ESPHome server
68
- self._server_task = self._event_loop.create_task(
69
- self._run_server(self._state)
70
- )
71
- self._event_loop.run_until_complete(self._server_task)
72
 
73
  except Exception as e:
74
  _LOGGER.error("Error running voice assistant: %s", e)
 
 
75
  finally:
76
  _LOGGER.info("Shutting down voice assistant")
77
- if self._audio_thread:
78
- self._audio_thread.join(timeout=5)
79
- if self._event_loop:
80
- self._event_loop.close()
81
 
82
  def _init_state(self, reachy_mini: ReachyMini) -> ServerState:
83
  """Initialize server state."""
@@ -144,9 +142,7 @@ class ReachyMiniHAVoiceApp(ReachyMiniApp):
144
 
145
  with open(model_config_path, "r", encoding="utf-8") as f:
146
  model_config = json.load(f)
147
- model_type = WakeWordType(
148
- model_config.get("type", "microWakeWord")
149
- )
150
  if model_type == WakeWordType.OPEN_WAKE_WORD:
151
  wake_word_path = model_config_path.parent / model_config["model"]
152
  else:
@@ -160,9 +156,7 @@ class ReachyMiniHAVoiceApp(ReachyMiniApp):
160
  wake_word_path=wake_word_path,
161
  )
162
  except Exception as e:
163
- _LOGGER.error(
164
- "Error loading wake word config %s: %s", model_config_path, e
165
- )
166
 
167
  return available_wake_words
168
 
@@ -178,25 +172,32 @@ class ReachyMiniHAVoiceApp(ReachyMiniApp):
178
  _LOGGER.error("Failed to load stop model: %s", e)
179
  return None
180
 
181
- async def _run_server(self, state: ServerState) -> None:
182
  """Run ESPHome server."""
183
  # Start ESPHome server
184
  loop = asyncio.get_running_loop()
185
- server = await loop.create_server(
186
  lambda: VoiceSatelliteProtocol(state), host="0.0.0.0", port=6053
187
  )
188
 
189
  # Auto discovery (zeroconf, mDNS) - required for Home Assistant auto-discovery
190
- discovery = HomeAssistantZeroconf(port=6053, name="ReachyMini")
191
- await discovery.register_server()
192
 
193
  try:
194
- async with server:
195
  _LOGGER.info("ESPHome server started on port 6053")
196
  _LOGGER.info("mDNS service registered for auto-discovery")
197
- await server.serve_forever()
 
 
 
 
 
 
198
  finally:
199
- await discovery.unregister_server()
 
200
  _LOGGER.info("ESPHome server stopped")
201
 
202
  def _process_audio(self, state: ServerState) -> None:
@@ -246,10 +247,9 @@ class ReachyMiniHAVoiceApp(ReachyMiniApp):
246
 
247
  while True:
248
  try:
 
249
  data = stream.read(CHUNK, exception_on_overflow=False)
250
- audio_array = (
251
- np.frombuffer(data, dtype=np.int16).astype(np.float32) / 32768.0
252
- )
253
 
254
  # Send to satellite if connected
255
  if state.satellite is not None:
@@ -328,4 +328,11 @@ class ReachyMiniHAVoiceApp(ReachyMiniApp):
328
  stream.stop_stream()
329
  stream.close()
330
  p.terminate()
331
- _LOGGER.info("Audio processing stopped")
 
 
 
 
 
 
 
 
24
  )
25
  from .satellite import VoiceSatelliteProtocol
26
  from .util import get_mac
27
+ from .zeroconf import HomeAssistantZeroconf
28
 
29
  _LOGGER = logging.getLogger(__name__)
30
  _MODULE_DIR = Path(__file__).parent
 
43
  self._state: Optional[ServerState] = None
44
  self._event_loop: Optional[asyncio.AbstractEventLoop] = None
45
  self._audio_thread: Optional[threading.Thread] = None
46
+ self._server: Optional[asyncio.Server] = None
47
+ self._discovery: Optional[HomeAssistantZeroconf] = None
48
 
49
  def run(self, reachy_mini: ReachyMini, stop_event: threading.Event):
50
  """Run the voice assistant."""
 
67
  self._audio_thread.start()
68
 
69
  # Start ESPHome server
70
+ self._event_loop.run_until_complete(self._run_server(self._state, stop_event))
 
 
 
71
 
72
  except Exception as e:
73
  _LOGGER.error("Error running voice assistant: %s", e)
74
+ import traceback
75
+ traceback.print_exc()
76
  finally:
77
  _LOGGER.info("Shutting down voice assistant")
78
+ self._cleanup()
 
 
 
79
 
80
  def _init_state(self, reachy_mini: ReachyMini) -> ServerState:
81
  """Initialize server state."""
 
142
 
143
  with open(model_config_path, "r", encoding="utf-8") as f:
144
  model_config = json.load(f)
145
+ model_type = WakeWordType(model_config.get("type", "microWakeWord"))
 
 
146
  if model_type == WakeWordType.OPEN_WAKE_WORD:
147
  wake_word_path = model_config_path.parent / model_config["model"]
148
  else:
 
156
  wake_word_path=wake_word_path,
157
  )
158
  except Exception as e:
159
+ _LOGGER.error("Error loading wake word config %s: %s", model_config_path, e)
 
 
160
 
161
  return available_wake_words
162
 
 
172
  _LOGGER.error("Failed to load stop model: %s", e)
173
  return None
174
 
175
+ async def _run_server(self, state: ServerState, stop_event: threading.Event) -> None:
176
  """Run ESPHome server."""
177
  # Start ESPHome server
178
  loop = asyncio.get_running_loop()
179
+ self._server = await loop.create_server(
180
  lambda: VoiceSatelliteProtocol(state), host="0.0.0.0", port=6053
181
  )
182
 
183
  # Auto discovery (zeroconf, mDNS) - required for Home Assistant auto-discovery
184
+ self._discovery = HomeAssistantZeroconf(port=6053, name="ReachyMini")
185
+ await self._discovery.register_server()
186
 
187
  try:
188
+ async with self._server:
189
  _LOGGER.info("ESPHome server started on port 6053")
190
  _LOGGER.info("mDNS service registered for auto-discovery")
191
+
192
+ # Run until stop_event is set
193
+ while not stop_event.is_set():
194
+ await asyncio.sleep(0.1)
195
+
196
+ _LOGGER.info("Stop event received, shutting down...")
197
+
198
  finally:
199
+ if self._discovery:
200
+ await self._discovery.unregister_server()
201
  _LOGGER.info("ESPHome server stopped")
202
 
203
  def _process_audio(self, state: ServerState) -> None:
 
247
 
248
  while True:
249
  try:
250
+ # Read audio chunk
251
  data = stream.read(CHUNK, exception_on_overflow=False)
252
+ audio_array = np.frombuffer(data, dtype=np.int16).astype(np.float32) / 32768.0
 
 
253
 
254
  # Send to satellite if connected
255
  if state.satellite is not None:
 
328
  stream.stop_stream()
329
  stream.close()
330
  p.terminate()
331
+ _LOGGER.info("Audio processing stopped")
332
+
333
+ def _cleanup(self) -> None:
334
+ """Clean up resources."""
335
+ if self._audio_thread:
336
+ self._audio_thread.join(timeout=5)
337
+ if self._event_loop and not self._event_loop.is_closed():
338
+ self._event_loop.close()