From 815fc7bcde1c465c2628cc6e60bbac37c56d94f6 Mon Sep 17 00:00:00 2001 From: Twirre Meulenbelt <43213592+TwirreM@users.noreply.github.com> Date: Thu, 29 Jan 2026 17:18:39 +0100 Subject: [PATCH] feat: publish face detection instead of req/res ref: N25B-395 --- src/robot_interface/core/config.py | 4 + .../endpoints/face_detector.py | 73 +++++-------------- 2 files changed, 21 insertions(+), 56 deletions(-) diff --git a/src/robot_interface/core/config.py b/src/robot_interface/core/config.py index 3caec21..13d0027 100644 --- a/src/robot_interface/core/config.py +++ b/src/robot_interface/core/config.py @@ -19,6 +19,8 @@ class AgentSettings(object): :vartype audio_sender_port: int :ivar face_detection_port: Port used for sending face detection events, defaults to 5559. :vartype face_detection_port: int + :ivar face_detection_interval: Time between face detection events, defaults to 1000 ms. + :vartype face_detection_interval: int """ def __init__( self, @@ -28,6 +30,7 @@ class AgentSettings(object): video_sender_port=None, audio_sender_port=None, face_detection_port=None, + face_detection_interval=None, ): self.control_backend_host = get_config(control_backend_host, "AGENT__CONTROL_BACKEND_HOST", "localhost") self.actuation_receiver_port = get_config(actuation_receiver_port, "AGENT__ACTUATION_RECEIVER_PORT", 5557, int) @@ -35,6 +38,7 @@ class AgentSettings(object): self.video_sender_port = get_config(video_sender_port, "AGENT__VIDEO_SENDER_PORT", 5556, int) self.audio_sender_port = get_config(audio_sender_port, "AGENT__AUDIO_SENDER_PORT", 5558, int) self.face_detection_port = get_config(face_detection_port, "AGENT__FACE_DETECTION_PORT", 5559, int) + self.face_detection_interval = get_config(face_detection_interval, "AGENT__FACE_DETECTION_INTERVAL", 1000, int) class VideoConfig(object): diff --git a/src/robot_interface/endpoints/face_detector.py b/src/robot_interface/endpoints/face_detector.py index e33e83d..e686fd9 100644 --- a/src/robot_interface/endpoints/face_detector.py +++ b/src/robot_interface/endpoints/face_detector.py @@ -13,61 +13,47 @@ from robot_interface.core.config import settings class FaceDetectionSender(SocketBase): """ - Face detection REP endpoint. + Face detection endpoint. - - Polls ALMemory["FaceDetected"] - - Maintains a rolling boolean state - - Responds to REQ polling over ZMQ REP + Subscribes to and polls ALMemory["FaceDetected"], sends events to CB. """ def __init__(self, zmq_context, port=settings.agent_settings.face_detection_port): super(FaceDetectionSender, self).__init__("face") - if port: - self.create_socket(zmq_context, zmq.REP, port) + self.create_socket(zmq_context, zmq.PUB, port) self._face_service = None self._memory_service = None - self.face_detected = False - self._last_seen_face_time = 0 - self._face_thread = None - self._rep_thread = None - - def endpoint_description(self): - return "face" - - # ------------------------------------------------------------------ - # Face detection logic (runs independently) - # ------------------------------------------------------------------ def start_face_detection(self): if not state.qi_session: logging.warning("No Qi session available. Face detection not started.") return - import qi - self._face_service = state.qi_session.service("ALFaceDetection") self._memory_service = state.qi_session.service("ALMemory") self._face_service.setTrackingEnabled(False) self._face_service.setRecognitionEnabled(False) - self._face_service.subscribe("FaceDetectionSender", 500, 0.0) + self._face_service.subscribe( + "FaceDetectionSender", + settings.agent_settings.face_detection_interval, + 0.0, + ) self._face_thread = threading.Thread(target=self._face_loop) self._face_thread.start() - self._rep_thread = threading.Thread(target=self._rep_loop) - self._rep_thread.start() - - logging.info("Face detection + REP endpoint started.") + logging.info("Face detection started.") def _face_loop(self): """ - Continuously updates `self.face_detected`. + Continuously send face detected to the CB, at the interval set in the + ``start_face_detection`` method. """ while not state.exit_event.is_set(): try: @@ -81,40 +67,11 @@ class FaceDetectionSender(SocketBase): and len(value[1][0]) > 0 ) - now = time.time() - if face_present: - self._last_seen_face_time = now - - # Face considered lost after 3 seconds - self.face_detected = (now - self._last_seen_face_time) < 3 - + self.socket.send(json.dumps({"face_detected": face_present}).encode("utf-8")) except Exception: logging.exception("Error reading FaceDetected") - time.sleep(0.1) - - - def _rep_loop(self): - """ - Handles ZMQ REQ/REP polling from the Control Backend. - """ - while not state.exit_event.is_set(): - try: - # Receive request (content ignored) - _ = self.socket.recv() - - # Respond with current face state - response = { - "endpoint": "face", - "data": self.face_detected, - } - - self.socket.send_json(response) - - except Exception: - logging.exception("Error in face detection REP loop") - time.sleep(0.1) - + time.sleep(settings.agent_settings.face_detection_interval / 1000.0) def stop_face_detection(self): try: @@ -124,3 +81,7 @@ class FaceDetectionSender(SocketBase): logging.info("Face detection stopped.") except Exception: logging.warning("Error during face detection cleanup.") + + def close(self): + super(FaceDetectionSender, self).close() + self.stop_face_detection()