Desmond-Dong commited on
Commit
0c6d129
·
1 Parent(s): 312c52f

v0.2.7: Add DOA caching to prevent ReSpeaker query overload

Browse files

- Add _get_cached_doa() method with 500ms TTL
- Use thread-safe ReSpeaker access with _respeaker_lock
- Prevents daemon crash from excessive DOA queries when HA subscribes to sensors

pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
 
5
  [project]
6
  name = "reachy_mini_ha_voice"
7
- version = "0.2.6"
8
  description = "Home Assistant Voice Assistant for Reachy Mini"
9
  readme = "README.md"
10
  requires-python = ">=3.10"
 
4
 
5
  [project]
6
  name = "reachy_mini_ha_voice"
7
+ version = "0.2.7"
8
  description = "Home Assistant Voice Assistant for Reachy Mini"
9
  readme = "README.md"
10
  requires-python = ">=3.10"
reachy_mini_ha_voice/__init__.py CHANGED
@@ -11,7 +11,7 @@ Key features:
11
  - Reachy Mini motion control integration
12
  """
13
 
14
- __version__ = "0.2.6"
15
  __author__ = "Desmond Dong"
16
 
17
  # Don't import main module here to avoid runpy warning
 
11
  - Reachy Mini motion control integration
12
  """
13
 
14
+ __version__ = "0.2.7"
15
  __author__ = "Desmond Dong"
16
 
17
  # Don't import main module here to avoid runpy warning
reachy_mini_ha_voice/reachy_controller.py CHANGED
@@ -64,6 +64,13 @@ class ReachyController:
64
 
65
  # Thread lock for ReSpeaker USB access to prevent conflicts with GStreamer audio pipeline
66
  self._respeaker_lock = __import__('threading').Lock()
 
 
 
 
 
 
 
67
 
68
  @property
69
  def is_available(self) -> bool:
@@ -713,34 +720,37 @@ class ReachyController:
713
 
714
  # ========== Phase 5: Audio Sensors ==========
715
 
716
- def get_doa_angle(self) -> float:
717
- """Get direction of arrival angle in degrees."""
 
 
 
 
718
  if not self.is_available:
719
- return 0.0
 
720
  try:
721
- # Access DOA through media_manager
722
- doa_result = self.reachy.media.get_DoA()
 
 
723
  if doa_result is not None:
724
- # Convert radians to degrees
725
- return math.degrees(doa_result[0])
726
- return 0.0
727
  except Exception as e:
728
- logger.error(f"Error getting DOA angle: {e}")
729
- return 0.0
 
 
 
 
 
730
 
731
  def get_speech_detected(self) -> bool:
732
- """Check if speech is detected."""
733
- if not self.is_available:
734
- return False
735
- try:
736
- # Access speech detection through media_manager
737
- doa_result = self.reachy.media.get_DoA()
738
- if doa_result is not None:
739
- return doa_result[1]
740
- return False
741
- except Exception as e:
742
- logger.error(f"Error getting speech detection: {e}")
743
- return False
744
 
745
  # ========== Phase 6: Diagnostic Information ==========
746
 
 
64
 
65
  # Thread lock for ReSpeaker USB access to prevent conflicts with GStreamer audio pipeline
66
  self._respeaker_lock = __import__('threading').Lock()
67
+
68
+ # DOA caching to prevent excessive ReSpeaker queries
69
+ # DOA is only meaningful during wake word detection, not for continuous polling
70
+ self._last_doa_query = 0.0
71
+ self._doa_cache_ttl = 0.5 # 500ms cache TTL for DOA
72
+ self._cached_doa_angle = 0.0
73
+ self._cached_speech_detected = False
74
 
75
  @property
76
  def is_available(self) -> bool:
 
720
 
721
  # ========== Phase 5: Audio Sensors ==========
722
 
723
+ def _get_cached_doa(self) -> None:
724
+ """Update cached DOA values if cache expired."""
725
+ now = time.time()
726
+ if now - self._last_doa_query < self._doa_cache_ttl:
727
+ return # Use cached values
728
+
729
  if not self.is_available:
730
+ return
731
+
732
  try:
733
+ # Access DOA through media_manager with thread safety
734
+ with self._respeaker_lock:
735
+ doa_result = self.reachy.media.get_DoA()
736
+
737
  if doa_result is not None:
738
+ self._cached_doa_angle = math.degrees(doa_result[0])
739
+ self._cached_speech_detected = doa_result[1]
740
+ self._last_doa_query = now
741
  except Exception as e:
742
+ logger.debug(f"Error getting DOA: {e}")
743
+ # Keep using cached values on error
744
+
745
+ def get_doa_angle(self) -> float:
746
+ """Get direction of arrival angle in degrees (cached)."""
747
+ self._get_cached_doa()
748
+ return self._cached_doa_angle
749
 
750
  def get_speech_detected(self) -> bool:
751
+ """Check if speech is detected (cached)."""
752
+ self._get_cached_doa()
753
+ return self._cached_speech_detected
 
 
 
 
 
 
 
 
 
754
 
755
  # ========== Phase 6: Diagnostic Information ==========
756