Add support for the new gRPC stream StreamFloorTopology(FloorTopologyRequest) -> stream FloorTopologyUpdate end-to-end:
- Nitro server subscribes to gRPC and exposes
GET /api/floor-topology+GET /api/floor-topology/stream(SSE). - Browser consumes JSON/SSE only (no direct gRPC) and renders the topology in the 3D viewer in a style similar to the current LiDAR overlay.
- Multiplicity: proto now sends a snapshot list:
FloorTopologyUpdate.polylines[](each is aFloorPolyline). - Frames: topology polylines are robot-relative;
FloorPolyline.frame_idis the coordinate frame the points are expressed in (typicallybase_footprintfor/floor/topologyfromrovi_floor). - Default render mode: UI parents/attaches polylines to the robot (no TF lookups needed).
- Geometry: honor
points[].z(3D), and line-only visualization. - Performance: point count “does not matter” (so we can keep implementation simple; still avoid obvious per-frame waste).
- Contract enforcement: hard fail if
FloorPolyline.frame_idis notbase_footprint(to avoid silently wrong transforms).
- Snapshot:
GET /api/floor-topology→{ topology: FloorTopology | null } - SSE:
GET /api/floor-topology/streamevent: topologywith JSON payload (FloorTopology)event: clearwhen stale/disconnected (client resets tonull/ shows nothing)- keep-alive
: pingframes every ~15s
(If you prefer the SSE event name to be polyline instead of topology, I’ll match that—just confirm.)
Add src/lib/floorTopology.ts:
FloorPolyline = { ns: string; id: number; frameId: string; points: {x:number;y:number;z:number}[]; closed: boolean }FloorTopology = { timestampUnixMs: number; seq: string; polylines: FloorPolyline[] }
- Hub: create
src/server/floorTopologyHub.tsmodeled aftersrc/server/lidarHub.ts:- load proto via
@grpc/proto-loader, connect toUiBridgeatUI_GATEWAY_GRPC_ADDR - start stream
client.StreamFloorTopology({}) - normalize/validate incoming
FloorTopologyUpdateintoFloorTopology - store
latestTopologyand notify subscribers - staleness timer using
BRIDGE_STALE_MS(or a dedicatedBRIDGE_STALE_TOPOLOGY_MSif you want) - reconnect loop using
UI_GATEWAY_GRPC_RECONNECT_MS - optional logging behind
DEBUG_TOPOLOGY(name TBD)
- load proto via
- Routes:
src/routes/api.floor-topology.ts→ returns snapshot JSON (no-store)src/routes/api.floor-topology.stream.ts→ SSE stream (retry hint viaUI_GATEWAY_SSE_RETRY_MS)
- Add
src/lib/floorTopologyClient.tsxmodeled aftersrc/lib/lidarClient.tsx:- fetch initial snapshot
- open
EventSource('/api/floor-topology/stream') - handle
topology(JSON parse) andclear/error - apply an extra client-side stale timeout (like LiDAR) for belt-and-suspenders
- Wire provider into the model route tree:
- wrap
ModelViewerCanvasinFloorTopologyProviderinsidesrc/components/ModelViewerHost.tsx
- wrap
- Extend
src/components/ModelViewerCanvas.tsx:const { topology } = useFloorTopology()- Convert
topology.polylines[]into arrays for<Line points={...} />.
- Default transform strategy (robot-attached):
- Render polylines inside the same robot
<group position={modelPos} rotation={modelRot}>so they move with the robot pose. - No pose-history sync needed in this mode.
- Hard fail if
polyline.frameIdis notbase_footprint(since we’re not doing TF lookups between robot frames).
- Render polylines inside the same robot
- Primitives:
- Use
<Line points={...} />only (no point cloud rendering).
- Use
- Closed loops:
- If
closed, add a final segment from last → first (or pass a points array with repeated first point). - Optional: visually distinguish closed vs open (only if you want; otherwise same style).
- If
Pick one (based on preference):
- A) Always-on overlay in the 3D viewer, or
- B) Add a small toggle button in the viewer overlay (near Fullscreen) to show/hide “Topology”.
- With bridge running: topology appears and updates in
/model. - On disconnect/staleness: server emits
clear, UI clears overlay (no stale carry-forward). - No browser-side gRPC; only
fetch+EventSource. - Rendering matches coordinate expectations (frame issues obvious via quick log-on-change like LiDAR’s frame log).
src/server/floorTopologyHub.ts(new)src/routes/api.floor-topology.ts(new)src/routes/api.floor-topology.stream.ts(new)src/lib/floorTopology.ts(new)src/lib/floorTopologyClient.tsx(new)src/components/ModelViewerHost.tsx(wrap provider)src/components/ModelViewerCanvas.tsx(render overlay)
- If
FloorPolyline.frame_idis notbase_footprint(or whatever you consider the canonical robot overlay frame), we either need static extrinsics (URDF-like, similar to LiDAR) or true TF lookup support; initial plan assumes no TF lookups.