145 lines
4.7 KiB
Python
145 lines
4.7 KiB
Python
import asyncio
|
|
import json
|
|
import logging
|
|
|
|
import zmq.asyncio
|
|
from fastapi import APIRouter, Request
|
|
from fastapi.responses import StreamingResponse
|
|
from zmq.asyncio import Context, Socket
|
|
|
|
from control_backend.core.config import settings
|
|
from control_backend.schemas.ri_message import GestureCommand, SpeechCommand
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.post("/command/speech", status_code=202)
|
|
async def receive_command_speech(command: SpeechCommand, request: Request):
|
|
"""
|
|
Send a direct speech command to the robot.
|
|
|
|
Publishes the command to the internal 'command' topic. The
|
|
:class:`~control_backend.agents.actuation.robot_speech_agent.RobotSpeechAgent`
|
|
will forward this to the robot.
|
|
|
|
:param command: The speech command payload.
|
|
:param request: The FastAPI request object.
|
|
"""
|
|
topic = b"command"
|
|
|
|
pub_socket: Socket = request.app.state.endpoints_pub_socket
|
|
await pub_socket.send_multipart([topic, command.model_dump_json().encode()])
|
|
|
|
return {"status": "Speech command received"}
|
|
|
|
|
|
@router.post("/command/gesture", status_code=202)
|
|
async def receive_command_gesture(command: GestureCommand, request: Request):
|
|
"""
|
|
Send a direct gesture command to the robot.
|
|
|
|
Publishes the command to the internal 'command' topic. The
|
|
:class:`~control_backend.agents.actuation.robot_speech_agent.RobotGestureAgent`
|
|
will forward this to the robot.
|
|
|
|
:param command: The speech command payload.
|
|
:param request: The FastAPI request object.
|
|
"""
|
|
topic = b"command"
|
|
|
|
pub_socket: Socket = request.app.state.endpoints_pub_socket
|
|
await pub_socket.send_multipart([topic, command.model_dump_json().encode()])
|
|
|
|
return {"status": "Gesture command received"}
|
|
|
|
|
|
@router.get("/ping_check")
|
|
async def ping(request: Request):
|
|
"""
|
|
Simple HTTP ping endpoint to check if the backend is reachable.
|
|
"""
|
|
pass
|
|
|
|
|
|
@router.get("/commands/gesture/tags")
|
|
async def get_available_gesture_tags(request: Request, count=0):
|
|
"""
|
|
Endpoint to retrieve the available gesture tags for the robot.
|
|
|
|
:param request: The FastAPI request object.
|
|
:return: A list of available gesture tags.
|
|
"""
|
|
req_socket = Context.instance().socket(zmq.REQ)
|
|
req_socket.connect(settings.zmq_settings.internal_gesture_rep_adress)
|
|
|
|
# Check to see if we've got any count given in the query parameter
|
|
amount = count or None
|
|
timeout = 5 # seconds
|
|
|
|
await req_socket.send(f"{amount}".encode() if amount else b"None")
|
|
try:
|
|
body = await asyncio.wait_for(req_socket.recv(), timeout=timeout)
|
|
except TimeoutError:
|
|
body = '{"tags": []}'
|
|
logger.debug("Got timeout error fetching gestures.")
|
|
|
|
# Handle empty response and JSON decode errors
|
|
available_tags = []
|
|
if body:
|
|
try:
|
|
available_tags = json.loads(body).get("tags", [])
|
|
except json.JSONDecodeError as e:
|
|
logger.error(f"Failed to parse gesture tags JSON: {e}, body: {body}")
|
|
# Return empty list on JSON error
|
|
available_tags = []
|
|
|
|
return {"available_gesture_tags": available_tags}
|
|
|
|
|
|
@router.get("/ping_stream")
|
|
async def ping_stream(request: Request):
|
|
"""
|
|
SSE endpoint for monitoring the Robot Interface connection status.
|
|
|
|
Subscribes to the internal 'ping' topic (published by the RI Communication Agent)
|
|
and yields status updates to the client.
|
|
|
|
:return: A StreamingResponse of connection status events.
|
|
"""
|
|
|
|
async def event_stream():
|
|
# Set up internal socket to receive ping updates
|
|
|
|
sub_socket = Context.instance().socket(zmq.SUB)
|
|
sub_socket.connect(settings.zmq_settings.internal_sub_address)
|
|
sub_socket.setsockopt(zmq.SUBSCRIBE, b"ping")
|
|
connected = False
|
|
|
|
ping_frequency = 2
|
|
|
|
# Even though its most likely the updates should alternate
|
|
# (So, True - False - True - False for connectivity),
|
|
# let's still check.
|
|
while True:
|
|
try:
|
|
topic, body = await asyncio.wait_for(
|
|
sub_socket.recv_multipart(), timeout=ping_frequency
|
|
)
|
|
connected = json.loads(body)
|
|
except TimeoutError:
|
|
logger.debug("got timeout error in ping loop in ping router")
|
|
connected = False
|
|
|
|
# Stop if client disconnected
|
|
if await request.is_disconnected():
|
|
logger.info("Client disconnected from SSE")
|
|
break
|
|
|
|
logger.debug(f"Yielded new connection event in robot ping router: {str(connected)}")
|
|
connectedJson = json.dumps(connected)
|
|
yield (f"data: {connectedJson}\n\n")
|
|
|
|
return StreamingResponse(event_stream(), media_type="text/event-stream")
|