Robot dashboard UI built with TanStack Start (Nitro) and a server-side gRPC client.
The gRPC server is a ROS node running within this project workspace https://github.com/Roblibs/rovi_ros_ws
Dashboard Gauges use svg for metrics status. Robot real time rendering (Rviz like) uses Threejs and three/fiber for threejs in react integration.
Prereqs:
- Node
>= 22.12 pnpmUiBridgegRPC server listening on0.0.0.0:50051(client connects to127.0.0.1:50051by default)
Run:
pnpm install
pnpm devOpen http://localhost:3000.
The browser never connects to gRPC directly. Nitro (Node) subscribes to the gRPC stream and re-exposes it as:
GET /api/status— JSON snapshot (ornullif no fresh data)GET /api/status/stream— SSE stream (event: status/event: clear)
flowchart TD
GRPC[gRPC UiBridge<br/>0.0.0.0:50051] -->|GetStatus stream| HUB[Nitro server<br/>src/server/robotStatusHub.ts]
HUB -->|"GET /api/status (JSON)"| UI[React UI<br/>src/routes/index.tsx]
HUB -->|"GET /api/status/stream (SSE)"| UI
sequenceDiagram
participant UI as Browser UI
participant API as Nitro API routes
participant GRPC as UiBridge (gRPC)
API->>GRPC: GetStatus({})
GRPC-->>API: StatusUpdate (stream)
UI->>API: GET /api/status
API-->>UI: { status: RobotStatus | null }
UI->>API: GET /api/status/stream
API-->>UI: event: status (RobotStatus JSON)
API-->>UI: event: clear (no data)
Each data type (status, state, lidar) exposes two endpoints:
- GET
/api/<type>— Returns the cached latest value immediately. On first request (before gRPC delivers data), returnsnull. Useful for one-off checks or initial hydration. - GET
/api/<type>/stream— SSE stream that delivers the cached value on connect (if available), then forwards all subsequent updates in real-time.
The server maintains a single shared gRPC subscription per data type. The hub caches the latest value so GET requests are instant (no round-trip to gRPC), and fans out updates to all SSE subscribers.
When no data has arrived yet (or after a clear event), the API returns null. The UI handles this gracefully:
- Status/Lidar: Gauges and visualizations show
--placeholder - Robot State: The 3D model renders at origin (position
[0,0,0], no rotation) — this ensures the model is visible immediately without waiting for pose data
- Until the first
StatusUpdatearrives, the UI shows--. - Values are never carried forward if the stream stops updating: after a staleness timeout, the server emits
clearand the UI switches back to--. - If a field is missing in a snapshot (ex:
voltage_vnot present), the UI shows--for that field.
Environment variables (optional):
UI_GATEWAY_GRPC_ADDR(default:0.0.0.0:50051)UI_GATEWAY_GRPC_RECONNECT_MS(default:2000)BRIDGE_STALE_MS(default:7000)UI_GATEWAY_SSE_RETRY_MS(default:2000)
Debug logging (optional):
DEBUG_MODEL— enables model/meta/file route info logsDEBUG_STATUS— enables status stream info logsDEBUG_POSE— enables pose/state stream info logsDEBUG_LIDAR— enables lidar stream info logs
Viewer debug (optional):
VITE_THREE_AXES_DEBUG— shows world + robot axes helpers in the 3D view
Notes:
- Errors are always logged to stderr;
DEBUG_*flags only control info/verbose logs.
Proto:
proto/ui_gateway.proto(copied into this repo for now)
