From 22e13435b1534abad213716ee1cfb81eb4b3d03f Mon Sep 17 00:00:00 2001 From: Adriano Dal Pastro Date: Wed, 20 May 2026 12:16:33 +0200 Subject: [PATCH] feat(right_arm): endpoint /snapshot/{front,wrist} --- src/presa_mano/api/right_arm_routes.py | 19 +++++++++++++++ tests/test_right_arm_routes.py | 33 ++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/src/presa_mano/api/right_arm_routes.py b/src/presa_mano/api/right_arm_routes.py index 2aac8b8..5920132 100644 --- a/src/presa_mano/api/right_arm_routes.py +++ b/src/presa_mano/api/right_arm_routes.py @@ -4,6 +4,7 @@ from __future__ import annotations from typing import Any from fastapi import APIRouter, HTTPException +from fastapi.responses import Response from ..clients import G1Unavailable from ..config import update_config @@ -54,3 +55,21 @@ def post_activate() -> dict[str, Any]: return _state_payload(sw) except G1Unavailable as e: raise HTTPException(status_code=502, detail=str(e)) + + +@router.get("/snapshot/front") +def snapshot_front() -> Response: + try: + jpeg = _deps.front_camera().rgb_jpeg() + except Exception as e: + raise HTTPException(status_code=502, detail=f"front_camera unreachable: {e}") + return Response(content=jpeg, media_type="image/jpeg") + + +@router.get("/snapshot/wrist") +def snapshot_wrist() -> Response: + try: + jpeg = _deps.nuwa().rgb_jpeg() + except Exception as e: + raise HTTPException(status_code=502, detail=f"wrist nuwa unreachable: {e}") + return Response(content=jpeg, media_type="image/jpeg") diff --git a/tests/test_right_arm_routes.py b/tests/test_right_arm_routes.py index 61c94f9..9230b70 100644 --- a/tests/test_right_arm_routes.py +++ b/tests/test_right_arm_routes.py @@ -60,3 +60,36 @@ def test_activate_sets_robot_active_g1_and_side_right(): body = r.json() assert body["robot_active"] == "g1" assert body["side"] == "right" + + +def test_snapshot_front_returns_jpeg(): + fake_jpeg = b"\xff\xd8\xff" + b"\x00" * 100 # magic JPEG bytes + with patch("presa_mano.api.right_arm_routes._deps.front_camera") as factory: + cam = MagicMock() + cam.rgb_jpeg.return_value = fake_jpeg + factory.return_value = cam + r = client.get("/api/right_arm/snapshot/front") + assert r.status_code == 200 + assert r.headers["content-type"].startswith("image/jpeg") + assert r.content == fake_jpeg + + +def test_snapshot_wrist_returns_jpeg(): + fake_jpeg = b"\xff\xd8\xff" + b"\x00" * 50 + with patch("presa_mano.api.right_arm_routes._deps.nuwa") as factory: + cam = MagicMock() + cam.rgb_jpeg.return_value = fake_jpeg + factory.return_value = cam + r = client.get("/api/right_arm/snapshot/wrist") + assert r.status_code == 200 + assert r.headers["content-type"].startswith("image/jpeg") + assert r.content == fake_jpeg + + +def test_snapshot_front_502_on_failure(): + with patch("presa_mano.api.right_arm_routes._deps.front_camera") as factory: + cam = MagicMock() + cam.rgb_jpeg.side_effect = RuntimeError("connection refused") + factory.return_value = cam + r = client.get("/api/right_arm/snapshot/front") + assert r.status_code == 502