Initial commit: RealSense D435 API server
Drop-in replacement for the Nuwa-HP60C camera container, exposing RGB +
aligned depth + intrinsics via REST/WebSocket so the existing
pestello-pose-detector pipeline can consume frames without changes.
Key adaptations for D4xx on USB 2.0 (this robot has no USB 3 port exposed
externally — see STATUS.md):
- pyrealsense2 built from source; the librealsense v2.55.1 install rule
doesn't ship the main .so, so we copy it from the build dir and add an
__init__.py that re-exports the submodule.
- Color stream uses YUYV format on USB 2.x (BGR8/RGB8 time out on this
bus); converted to BGR via cv2.cvtColor in capture().
- rs.align is skipped on USB 2 (incompatible with YUYV); color is
cv2-resized to depth shape for approximate per-pixel alignment.
- Driver auto-detects usb_type_descriptor and falls back to the
full-resolution BGR8 + rs.align pipeline when on USB 3.
Tested: /api/{health,status,discover,rgb,depth,depth/color,capture,
intrinsics} all return 200 with valid payloads at 640x480.
This commit is contained in:
commit
07d95792e3
58 files changed
+6987
No files matched your search
@@ -0,0 +1,66 @@
|
||||
import base64
|
||||
import json
|
||||
import logging
|
||||
|
||||
from nats.aio.client import Client
|
||||
from src.backend.nats_client.subjects import Subjects
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_camera = None
|
||||
|
||||
|
||||
def set_camera(camera):
|
||||
global _camera
|
||||
_camera = camera
|
||||
|
||||
|
||||
def _nats_response(success: bool, data=None, error=None) -> bytes:
|
||||
return json.dumps({
|
||||
"success": success,
|
||||
"data": data,
|
||||
"error": error,
|
||||
}).encode()
|
||||
|
||||
|
||||
async def register_all(nc: Client):
|
||||
async def handle_snapshot(msg):
|
||||
try:
|
||||
if not _camera or not _camera.is_open():
|
||||
await msg.respond(_nats_response(
|
||||
False, error={"code": "NO_CAMERA", "message": "Camera not open"}
|
||||
))
|
||||
return
|
||||
|
||||
import cv2
|
||||
frame = _camera.capture()
|
||||
if frame is None:
|
||||
await msg.respond(_nats_response(
|
||||
False, error={"code": "TIMEOUT", "message": "Frame capture timeout"}
|
||||
))
|
||||
return
|
||||
|
||||
_, rgb_buf = cv2.imencode(".jpg", frame.rgb, [cv2.IMWRITE_JPEG_QUALITY, 85])
|
||||
_, depth_buf = cv2.imencode(".png", frame.depth)
|
||||
|
||||
await msg.respond(_nats_response(True, data={
|
||||
"rgb_jpeg_b64": base64.b64encode(rgb_buf.tobytes()).decode(),
|
||||
"depth_png_b64": base64.b64encode(depth_buf.tobytes()).decode(),
|
||||
"timestamp": frame.timestamp,
|
||||
"width": frame.rgb.shape[1],
|
||||
"height": frame.rgb.shape[0],
|
||||
}))
|
||||
except Exception as e:
|
||||
logger.error(f"Snapshot handler error: {e}")
|
||||
await msg.respond(_nats_response(
|
||||
False, error={"code": "PROCESSING_ERROR", "message": str(e)}
|
||||
))
|
||||
|
||||
async def handle_status(msg):
|
||||
await msg.respond(_nats_response(True, data={
|
||||
"camera_open": _camera.is_open() if _camera else False,
|
||||
"sdk_initialized": _camera.is_initialized() if _camera else False,
|
||||
}))
|
||||
|
||||
await nc.subscribe(Subjects.action("snapshot"), cb=handle_snapshot)
|
||||
await nc.subscribe(Subjects.action("status"), cb=handle_status)
|
||||
Reference in new issue
Block a user