# Intel RealSense D435 — API Server Server FastAPI per la depth camera Intel RealSense D4xx (testato su D435) con accesso remoto via REST/WebSocket. Esecuzione tramite Docker. Mantiene la stessa shape API del servizio Nuwa HP60C (envelope `ApiResponse`, endpoint `/api/...`, header `X-Api-Key`, WebSocket `/ws`) così che i client e il frontend React esistenti funzionano senza modifiche. ## Caratteristiche - **RGB**: configurabile (default 640x480) BGR8, allineato al depth - **Depth**: 640x480 uint16 in millimetri (allineato al color via `rs.align`) - **Intrinsics**: parametri intrinseci color + depth e estrinseci depth→color - **Discovery**: enumerazione devices via `pyrealsense2` - **API Server**: REST + WebSocket con envelope `ApiResponse` standard - **Sicurezza**: API Key opzionale (`X-Api-Key`) su tutti gli endpoint REST - **NATS**: messaging asincrono opzionale (profilo `nats`) - **Frontend React**: viewer RGB, depth e 3D point cloud (riusato dal Nuwa) - **Swagger**: documentazione interattiva API a `/apidocs` ## Hardware | Parametro | Valore | |-----------|--------| | Camera | Intel RealSense D435 | | Serial number (questo robot) | `933322070696` | | Firmware | `05.16.00.01` | | Range depth | 0.1m – 10m | | Interfaccia | USB 3.0 | | SDK | librealsense 2.55.1 + pyrealsense2 (built from source) | | Host | Jetson Xavier NX, Ubuntu 20.04 (JetPack 5.1.x) | ## Configurazione Crea `.env` (vedi `.env.example`): ```env API_PORT=8878 API_KEY=cambia-questa-chiave-segreta CAMERA_WIDTH=640 CAMERA_HEIGHT=480 CAMERA_FPS=30 CAMERA_AUTO_OPEN=1 # apre la camera al boot, niente /api/open manuale CORS_ORIGINS=* # LAN-only OK ``` ## Accesso dalla LAN Il container gira sul robot e binda la porta `8878` su tutte le interfacce (`0.0.0.0`). Dal vostro PC sulla stessa LAN raggiungete il servizio via: ``` http://:8878/api/health # health check http://:8878/apidocs # Swagger UI http://:8878/gui # frontend React ws://:8878/ws # stream WebSocket ``` Il robot ha Avahi attivo, quindi `ubuntu.local:8878` funziona se mDNS risolve sul vostro PC (`avahi-resolve -n ubuntu.local`). Altrimenti usate l'IP diretto (`ip a` sul robot per scoprirlo). > Lo streaming **non richiede internet** — solo la build (~25 min al primo > avvio) deve raggiungere registry Docker/apt/PyPI/npm via il tinyproxy > locale del robot. ## Build & avvio ```bash # Backend (porta 8878) docker compose up --build -d # Backend + NATS docker compose --profile nats up --build -d ``` > Il primo build compila i bindings Python di `pyrealsense2` da source — > ~20–30 min su Jetson. I build successivi sono cached. Il container: - non fa unbind UVC (a differenza del Nuwa) — RealSense usa direttamente uvcvideo - monta `/dev/bus/usb` in privileged mode per accesso diretto USB - riparte automaticamente (`restart: unless-stopped`) ## Endpoints Identici al Nuwa per drop-in compatibility: | Endpoint | Metodo | Descrizione | |----------|--------|-------------| | `/api/health` | GET | Health check (no auth) | | `/api/status` | GET | Stato SDK/camera e calibrazione | | `/api/discover` | GET | Rileva camere RealSense su USB | | `/api/open` | POST | Apre camera per indice (`device_index`) | | `/api/close` | POST | Chiude la camera | | `/api/capture` | GET | Frame RGB+depth (JSON, base64) | | `/api/rgb` | GET | Frame RGB come JPEG | | `/api/depth` | GET | Frame depth come PNG 16-bit (mm) | | `/api/depth/color` | GET | Depth colormap JET come JPEG | | `/api/intrinsics` | GET | Calibrazione completa | | `/api/pointcloud` | GET | Point cloud (subsampling `step`) | | `/api/pointcloud/roi` | GET | Point cloud su ROI pixel-space | | `/api/measure` | GET | Backprojection 3D di un pixel | | `/api/snapshot` | POST | Salva snapshot RGB | | `/api/recording/{start,stop,status}` | POST/GET | Registrazione continua | | `/ws` | WS | Streaming WebSocket (text msgs: `start`, `stop`, `close`) | #### Payload calibrazione `/api/intrinsics` ritorna i campi `ir` e `rgb` per compatibilità con il frontend Nuwa, ma in questo servizio: - `ir` = intrinseci del **sensore depth** (post-rectification dello stereo IR) - `rgb` = intrinseci del **sensore color** - `extrinsics_ir_to_rgb` = trasformazione **depth → color** ```json { "ir": { "fx": ..., "fy": ..., "cx": ..., "cy": ..., "distortion": { "k1":..., "k2":..., "k3":..., "p1":..., "p2":... } }, "rgb": { "fx": ..., "fy": ..., "cx": ..., "cy": ..., "distortion": { ... } }, "extrinsics_ir_to_rgb": { "rotation": [r00, r01, r02, r10, r11, r12, r20, r21, r22], "translation": [tx, ty, tz] } } ``` - Rotation row-major 3x3 - Translation in **millimetri** (la D4xx ritorna `extrinsics.translation` in metri; convertito qui per allinearsi alla convenzione Nuwa) - D4xx pubblica tipicamente coefficienti di distorsione tutti zero (immagini già rettificate dall'ASIC) ### Esempi curl ```bash # Discover curl -H "X-Api-Key: $API_KEY" http://localhost:8878/api/discover # Apri (D435 in genere index 0) curl -X POST -H "X-Api-Key: $API_KEY" \ "http://localhost:8878/api/open?device_index=0" # Frame RGB curl -H "X-Api-Key: $API_KEY" http://localhost:8878/api/rgb -o rgb.jpg # Frame depth (PNG 16-bit, valori in mm) curl -H "X-Api-Key: $API_KEY" http://localhost:8878/api/depth -o depth.png # Intrinsics curl -H "X-Api-Key: $API_KEY" http://localhost:8878/api/intrinsics | jq # Close curl -X POST -H "X-Api-Key: $API_KEY" http://localhost:8878/api/close ``` ## Note - `librealsense2` runtime nel container è installato dal repo apt ufficiale Intel (pinned a `2.55.1-0~realsense.3337`). I bindings Python sono buildati da `IntelRealSense/librealsense@v2.55.1`. - Il frontend React originale (`/gui`) referenzia `ir` e `rgb` come label di display — su questo servizio quei nomi mappano depth/color (vedi sopra). - L'autostart è gestito da Docker (`restart: unless-stopped`). Per partire automaticamente al boot del robot serve `systemctl enable docker`. - Per disabilitare l'auth API setta `API_KEY=` (vuoto). Sconsigliato in produzione.