From 1932ac959bdbdcaf4d849de9c9368bf0815a5d5a Mon Sep 17 00:00:00 2001 From: JobvAlewijk Date: Mon, 12 Jan 2026 14:23:00 +0100 Subject: [PATCH] feat: connected with RI properly ref: N25B-397 --- .../agents/perception/__init__.py | 1 + .../agents/perception/face_rec_agent.py | 87 ++++++++----------- src/control_backend/core/config.py | 1 + src/control_backend/main.py | 7 ++ 4 files changed, 43 insertions(+), 53 deletions(-) diff --git a/src/control_backend/agents/perception/__init__.py b/src/control_backend/agents/perception/__init__.py index e18361a..91bfb7d 100644 --- a/src/control_backend/agents/perception/__init__.py +++ b/src/control_backend/agents/perception/__init__.py @@ -1,3 +1,4 @@ +from .face_rec_agent import FacePerceptionAgent as FacePerceptionAgent from .transcription_agent.transcription_agent import ( TranscriptionAgent as TranscriptionAgent, ) diff --git a/src/control_backend/agents/perception/face_rec_agent.py b/src/control_backend/agents/perception/face_rec_agent.py index 8201197..1369aa6 100644 --- a/src/control_backend/agents/perception/face_rec_agent.py +++ b/src/control_backend/agents/perception/face_rec_agent.py @@ -1,74 +1,55 @@ -import zmq -import zmq.asyncio as azmq +import asyncio from control_backend.agents import BaseAgent class FacePerceptionAgent(BaseAgent): """ - Receives and processes face detection / recognition events - coming from Pepper (via a NAOqi -> ZMQ bridge). + Receives face presence updates from the RICommunicationAgent + via the internal PUB/SUB bus. """ def __init__(self, name: str): super().__init__(name) - self._address = "tcp://127.0.0.1:5559" - self._socket: azmq.Socket | None = None + self._last_face_state: bool | None = None async def setup(self): self.logger.info("Starting FacePerceptionAgent") + self.add_behavior(self._poll_loop()) - ctx = azmq.Context.instance() - self._socket = ctx.socket(zmq.SUB) - self._socket.setsockopt_string(zmq.SUBSCRIBE, "") + async def _poll_loop(self): + poll_interval = 1.0 - self._socket.connect(self._address) - - self.add_behavior(self._listen_loop()) - - async def _listen_loop(self): while self._running: try: - msg = await self._socket.recv_json() - await self._process_face_data(msg) - except Exception: - self.logger.exception("Error receiving face data") + # Ask RICommunicationAgent (via main socket) + await self._req_socket.send_json({"endpoint": "face", "data": {}}) - async def _process_face_data(self, data: dict): - """ - Processes NAOqi FaceDetected-derived data. - """ - - faces = data.get("faces", []) - new_recognitions = data.get("new_recognitions", []) - - if not faces: - self.logger.debug("No faces detected") - return - - self.logger.debug("Detected %d face(s)", len(faces)) - - for face in faces: - face_id = face.get("face_id") - alpha = face.get("alpha") - beta = face.get("beta") - # size_x = face.get("size_x") - # size_y = face.get("size_y") - - recognized = face.get("recognized", False) - label = face.get("label") - score = face.get("score", 0.0) - - if recognized: - self.logger.info("Recognized %s (score=%.2f, id=%s)", label, score, face_id) - else: - self.logger.debug( - "Unrecognized face id=%s at (α=%.2f, β=%.2f)", face_id, alpha, beta + response = await asyncio.wait_for( + self._req_socket.recv_json(), timeout=poll_interval ) - # Temporal-filtered recognition (important!) - for name in new_recognitions: - self.logger.info("New person recognized: %s", name) + face_present = bool(response.get("data", False)) - # 🔮 Example belief posting hook - # await self.post_belief("person_present", name=name) + if self._last_face_state is None: + self._last_face_state = face_present + continue + + if face_present != self._last_face_state: + self._last_face_state = face_present + self.logger.info("👀 Face detected" if face_present else "🙈 Face lost") + # TODO: post belief to BDI here + + except Exception as e: + self.logger.warning("Face polling failed") + self.logger.warn(e) + + await asyncio.sleep(poll_interval) + + async def _handle_face_change(self, present: bool): + if present: + self.logger.info("👀 Face detected") + # await self.post_belief("face_present", value=True) + else: + self.logger.info("🙈 No face detected") + # await self.post_belief("face_present", value=False) diff --git a/src/control_backend/core/config.py b/src/control_backend/core/config.py index 927985b..f33a4c6 100644 --- a/src/control_backend/core/config.py +++ b/src/control_backend/core/config.py @@ -49,6 +49,7 @@ class AgentSettings(BaseModel): robot_speech_name: str = "robot_speech_agent" robot_gesture_name: str = "robot_gesture_agent" user_interrupt_name: str = "user_interrupt_agent" + face_agent_name: str = "face_detection_agent" class BehaviourSettings(BaseModel): diff --git a/src/control_backend/main.py b/src/control_backend/main.py index 3509cbc..8888298 100644 --- a/src/control_backend/main.py +++ b/src/control_backend/main.py @@ -38,6 +38,7 @@ from control_backend.agents.communication import RICommunicationAgent # Emotional Agents # LLM Agents from control_backend.agents.llm import LLMAgent +from control_backend.agents.perception.face_rec_agent import FacePerceptionAgent # User Interrupt Agent from control_backend.agents.user_interrupt.user_interrupt_agent import UserInterruptAgent @@ -147,6 +148,12 @@ async def lifespan(app: FastAPI): "name": settings.agent_settings.user_interrupt_name, }, ), + "FaceDetectionAgent": ( + FacePerceptionAgent, + { + "name": settings.agent_settings.face_agent_name, + }, + ), } agents = []