Desmond-Dong commited on
Commit
51ad4f0
·
1 Parent(s): a946134

fix: enable face tracking by default and improve tap detection logging

Browse files

- Face tracking now enabled by default (was disabled)
- Face tracking stays enabled during idle (was being disabled)
- Improved tap detector logging with IMU data verification
- Improved camera server logging with FPS stats
- Better error messages for missing head tracker dependencies

reachy_mini_ha_voice/camera_server.py CHANGED
@@ -76,7 +76,7 @@ class MJPEGCameraServer:
76
 
77
  # Face tracking state
78
  self._head_tracker = None
79
- self._face_tracking_enabled = False # Disabled by default, enabled on wake word
80
  self._face_tracking_offsets: List[float] = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
81
  self._face_tracking_lock = threading.Lock()
82
 
@@ -103,10 +103,16 @@ class MJPEGCameraServer:
103
  try:
104
  from .head_tracker import HeadTracker
105
  self._head_tracker = HeadTracker()
106
- _LOGGER.info("Face tracking enabled")
 
 
 
 
107
  except Exception as e:
108
  _LOGGER.warning("Failed to initialize head tracker: %s", e)
109
  self._head_tracker = None
 
 
110
 
111
  # Start frame capture thread
112
  self._capture_thread = threading.Thread(
@@ -144,7 +150,10 @@ class MJPEGCameraServer:
144
 
145
  def _capture_frames(self) -> None:
146
  """Background thread to capture frames from Reachy Mini and do face tracking."""
147
- _LOGGER.info("Starting camera capture thread")
 
 
 
148
 
149
  while self._running:
150
  try:
@@ -152,6 +161,8 @@ class MJPEGCameraServer:
152
  frame = self._get_camera_frame()
153
 
154
  if frame is not None:
 
 
155
  # Encode frame as JPEG for streaming
156
  encode_params = [cv2.IMWRITE_JPEG_QUALITY, self.quality]
157
  success, jpeg_data = cv2.imencode('.jpg', frame, encode_params)
@@ -167,6 +178,14 @@ class MJPEGCameraServer:
167
 
168
  # Handle smooth interpolation when face lost
169
  self._process_face_lost_interpolation(current_time)
 
 
 
 
 
 
 
 
170
 
171
  # Sleep to maintain target FPS
172
  time.sleep(self._frame_interval)
 
76
 
77
  # Face tracking state
78
  self._head_tracker = None
79
+ self._face_tracking_enabled = True # Enabled by default for always-on face tracking
80
  self._face_tracking_offsets: List[float] = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
81
  self._face_tracking_lock = threading.Lock()
82
 
 
103
  try:
104
  from .head_tracker import HeadTracker
105
  self._head_tracker = HeadTracker()
106
+ _LOGGER.info("Face tracking enabled with YOLO head tracker")
107
+ except ImportError as e:
108
+ _LOGGER.warning("Failed to import head tracker (missing dependencies): %s", e)
109
+ _LOGGER.warning("Install with: pip install ultralytics supervision huggingface_hub")
110
+ self._head_tracker = None
111
  except Exception as e:
112
  _LOGGER.warning("Failed to initialize head tracker: %s", e)
113
  self._head_tracker = None
114
+ else:
115
+ _LOGGER.info("Face tracking disabled by configuration")
116
 
117
  # Start frame capture thread
118
  self._capture_thread = threading.Thread(
 
150
 
151
  def _capture_frames(self) -> None:
152
  """Background thread to capture frames from Reachy Mini and do face tracking."""
153
+ _LOGGER.info("Starting camera capture thread (face_tracking=%s)", self._face_tracking_enabled)
154
+
155
+ frame_count = 0
156
+ last_log_time = time.time()
157
 
158
  while self._running:
159
  try:
 
161
  frame = self._get_camera_frame()
162
 
163
  if frame is not None:
164
+ frame_count += 1
165
+
166
  # Encode frame as JPEG for streaming
167
  encode_params = [cv2.IMWRITE_JPEG_QUALITY, self.quality]
168
  success, jpeg_data = cv2.imencode('.jpg', frame, encode_params)
 
178
 
179
  # Handle smooth interpolation when face lost
180
  self._process_face_lost_interpolation(current_time)
181
+
182
+ # Log stats every 10 seconds
183
+ if current_time - last_log_time >= 10.0:
184
+ fps = frame_count / (current_time - last_log_time)
185
+ _LOGGER.debug("Camera: %.1f fps, face_tracking=%s, head_tracker=%s",
186
+ fps, self._face_tracking_enabled, self._head_tracker is not None)
187
+ frame_count = 0
188
+ last_log_time = current_time
189
 
190
  # Sleep to maintain target FPS
191
  time.sleep(self._frame_interval)
reachy_mini_ha_voice/motion.py CHANGED
@@ -85,17 +85,14 @@ class ReachyMiniMotion:
85
  """Called when wake word is detected.
86
 
87
  Non-blocking: command sent to MovementManager.
88
- Enables face tracking to look at the user.
89
  """
90
  _LOGGER.debug("on_wakeup called")
91
  if self._movement_manager is None:
92
  _LOGGER.warning("on_wakeup: movement_manager is None, skipping motion")
93
  return
94
 
95
- # Enable face tracking when wake word detected
96
- if self._camera_server is not None:
97
- self._camera_server.set_face_tracking_enabled(True)
98
- _LOGGER.info("Face tracking enabled on wake word")
99
 
100
  # Set listening state - face tracking will handle looking at user
101
  self._movement_manager.set_state(RobotState.LISTENING)
@@ -188,7 +185,7 @@ class ReachyMiniMotion:
188
  """Called when returning to idle state.
189
 
190
  Non-blocking: command sent to MovementManager.
191
- Disables face tracking to save resources.
192
  """
193
  if self._movement_manager is None:
194
  return
@@ -197,10 +194,8 @@ class ReachyMiniMotion:
197
  self._movement_manager.set_state(RobotState.IDLE)
198
  self._movement_manager.reset_to_neutral(duration=0.5)
199
 
200
- # Disable face tracking when returning to idle (save resources)
201
- if self._camera_server is not None:
202
- self._camera_server.set_face_tracking_enabled(False)
203
- _LOGGER.info("Face tracking disabled on idle")
204
 
205
  _LOGGER.debug("Reachy Mini: Idle pose")
206
 
 
85
  """Called when wake word is detected.
86
 
87
  Non-blocking: command sent to MovementManager.
88
+ Face tracking is always enabled, so robot will look at user automatically.
89
  """
90
  _LOGGER.debug("on_wakeup called")
91
  if self._movement_manager is None:
92
  _LOGGER.warning("on_wakeup: movement_manager is None, skipping motion")
93
  return
94
 
95
+ # Face tracking is always enabled, no need to enable it here
 
 
 
96
 
97
  # Set listening state - face tracking will handle looking at user
98
  self._movement_manager.set_state(RobotState.LISTENING)
 
185
  """Called when returning to idle state.
186
 
187
  Non-blocking: command sent to MovementManager.
188
+ Face tracking remains enabled for continuous tracking.
189
  """
190
  if self._movement_manager is None:
191
  return
 
194
  self._movement_manager.set_state(RobotState.IDLE)
195
  self._movement_manager.reset_to_neutral(duration=0.5)
196
 
197
+ # Note: Face tracking remains enabled for continuous tracking
198
+ # This allows the robot to always look at the user when they approach
 
 
199
 
200
  _LOGGER.debug("Reachy Mini: Idle pose")
201
 
reachy_mini_ha_voice/tap_detector.py CHANGED
@@ -103,6 +103,16 @@ class TapDetector:
103
  "(only available on Wireless version)"
104
  )
105
  return
 
 
 
 
 
 
 
 
 
 
106
  except Exception as e:
107
  _LOGGER.warning("Failed to check IMU availability: %s", e)
108
  return
@@ -114,7 +124,8 @@ class TapDetector:
114
  name="tap-detector"
115
  )
116
  self._thread.start()
117
- _LOGGER.info("TapDetector started (threshold=%.1fg)", self._threshold_ms2 / 9.81)
 
118
 
119
  def stop(self) -> None:
120
  """Stop tap detection thread."""
 
103
  "(only available on Wireless version)"
104
  )
105
  return
106
+
107
+ # Verify accelerometer data is present
108
+ if "accelerometer" not in imu_data:
109
+ _LOGGER.warning(
110
+ "IMU accelerometer data not available - tap detection disabled"
111
+ )
112
+ return
113
+
114
+ _LOGGER.info("IMU available, accelerometer data: %s", imu_data.get("accelerometer"))
115
+
116
  except Exception as e:
117
  _LOGGER.warning("Failed to check IMU availability: %s", e)
118
  return
 
124
  name="tap-detector"
125
  )
126
  self._thread.start()
127
+ _LOGGER.info("TapDetector started (threshold=%.1fg, cooldown=%.1fs)",
128
+ self._threshold_g, self._cooldown_seconds)
129
 
130
  def stop(self) -> None:
131
  """Stop tap detection thread."""