Skip to content

feat(perception): camera/depth/lidar/scene dashboard, occupancy base map, LAN fixes - #13

Merged
enkerewpo merged 13 commits into
syswonder:mainfrom
laojiahuo2003:feat/camera-display
Oct 9, 2026
Merged

enkerewpo merged 13 commits into
syswonder:mainfrom
laojiahuo2003:feat/camera-display

Conversation

@laojiahuo2003

Copy link
Copy Markdown
Contributor

Summary

  • New Perception page: camera RGB / depth / lidar / scene viewport tiles; each tile degrades per-resource, so partial deployments still render.
  • Occupancy base map served by the robot-local map service, aligned with scene overlays in the shared map frame.
  • Native MCP streamable-HTTP client (JSON-RPC over HTTP + SSE) with session caching, loopback rewriting, and Host-header pinning for LAN deployments.
  • Fixes: depth hover bounds (letterbox-accurate), map flickering, lidar heading (REP-103).
  • Multi-tile snapshot mode.

Test plan

  • python -m unittest discover -s tests -v → 41 passed
  • npm run build + committed vitals.bundle.js in sync
  • Playwright visual tests (desktop + mobile) → 16 passed
  • scripts/check_commit_authorship.py over the introduced commits → passed

enkerewpo and others added 8 commits September 12, 2026 18:13
- declarative per-resource availability so a partial deployment (no camera/depth, non-ROS transport) still shows what it has
- scene map with toggleable regions/objects/robot layers and partial tolerance when a scene RPC is missing
- occupancy-grid base map fetched from the robot map/rooms HTTP service (http://<robot-host>:50107/api/state), aligned via origin/resolution
- rewrite loopback-advertised MCP endpoints to the configured robot host so the page also works from another machine on the LAN
…ndpoints

The scene map rendered its occupancy grid but no objects, regions or robot,
while Atlas reported every scene contract reachable over transport=3. The scene
provider is the one MCP server here that keeps the SDK's DNS-rebinding
protection on, so it answers 421 to the Host urllib derives from the address we
dial; the camera and lidar providers leave it off, which is why only the scene
overlay went blank.

_rewrite_loopback already re-pointed a loopback-advertised endpoint at the
configured robot host, but only for dialling. It now also hands back the netloc
the provider advertised, and _post_json sends it as the Host header, so the
client dials the routable address while claiming the identity the server
expects.

Rewritten endpoints are dialled through an opener with no proxy. urllib picks
up the system proxy from the Windows registry, and a proxy routes by Host, so
the restored loopback Host was forwarded to the wrong upstream and came back
502. Providers that advertise a routable address are untouched and keep
whatever proxy behaviour they had.

Assisted-by: Claude:claude-fable-5 [Claude Code]
…rome

The scene tile gave no way to inspect the map: the view re-fitted to
whatever data arrived, so a new object or a driving robot rescaled the
plan under the operator, and labels turned to soup once a few boxes
overlapped.

The viewport is now the operator's. It fits once to the occupancy grid --
which, unlike the object cloud, has an extent that never moves -- and is
left alone from then on. Panning, zooming or checking "follow" only ever
moves it deliberately. Object labels are ordered largest-first, skipped
when their box is too small to carry text, and dropped when they would
collide, so a crowded plan stays readable.

The lidar tile drew its scan mirrored. LaserScan angles run
counter-clockwise in a y-up frame while a canvas counts y downwards, so
plotting cy + r*sin(a) put the robot's left where its right belongs: a
return to the robot's left landed below centre, while the scene map draws
the same direction above it. Negating the sine term puts the two tiles on
one set of axes, and a forward arrow plus the frame id now state which
way the scan faces and which frame the angles are measured in.

Polling moves off setInterval onto a self-rescheduling timer. Every tile
awaits its fetch and an MCP handshake alone is two round-trips, so a slow
robot let rounds stack without bound; the next round is now armed only
once the current one settles.

The page chrome loses most of its borders: hierarchy comes from spacing
and surface tone instead, numerics are tabular monospace, and the buttons
become quiet pills.

Assisted-by: Claude:claude-fable-5 [Claude Code]
Integrate i18n support into the camera perception feature branch:
- Added i18n.js and language switcher UI
- Applied data-i18n attributes to all UI text including new Perception page
- Merged i18n change handler with perception page lifecycle
- Both features now work together seamlessly
- fitCanvas falls back to parent dims when layout not ready
- cache lastLidarScan/lastScene and redraw canvases on window resize
- i18n: translate perception page strings (zh-CN)
…3/robonix-client into feat/camera-display

# Conflicts:
#	src/robonix_client/static/index.html
…kering, add multi-tile snapshots

- Fix depth hover bounds calculation to respect 4:3 letterbox/pillarbox aspect ratio
- Eliminate map flickering via double-buffered image decoding and single-pass canvas rendering
- Fix active tile placeholder visibility so standby hints vanish when live feed connects
- Add full multi-sensor snapshot export for Camera, Depth, LiDAR, and Scene Map
- Add cybernetic compass HUD to Scene Map and complete 100% i18n localization
- Bump static asset cache buster to v=20260919c
Copilot AI lite review requested due to automatic review settings September 21, 2026 02:54

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

…y bubbles

Agent bubbles appended a raw text node, so planner replies showed their
Markdown source (`**bold**`, numbered lists) literally. Render the subset the
model emits into DOM nodes built with createElement/textContent only (never
innerHTML), restricting links to http/https/mailto.

Also merge a FinalText into the turn's last agent bubble: an interleaved status
line cleared `activeAgentId`, which had been opening a second bubble carrying
text that was already streamed. Add the backend status/fallback strings and the
VERIFYING/PAUSED state labels.
…lemetry

Add a health API router and generated stubs plus a compute-node frontend
module, wired into the Vitals bundle, page markup, styles and i18n strings.
@enkerewpo enkerewpo added the enhancement New feature or request label Oct 8, 2026
@enkerewpo
enkerewpo self-requested a review October 8, 2026 09:51

@enkerewpo enkerewpo left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, the Perception page works well and the contract IDs match robonix dev. One request before merging: perception.py and health_api.py add about 650 lines without tests. A few small unit tests would help, for example _rewrite_loopback (loopback vs routable endpoints and the Host header), _sse_json parsing, and health_state_to_sample mapping the -1 sentinel to None.

Comment thread src/robonix_client/health_api.py Outdated
Comment thread src/robonix_client/health_api.py Outdated
Comment thread src/robonix_client/perception.py
The compute-node panel used to sit permanently at the bottom of the page and
never showed data, because the Vitals stream sends partial snapshots: a frame
without compute-node signals made the client give up and close the socket.
Merge the signals across frames in /ws/health, then surface the panel as a modal
launched from the compute-node row (Telemetry badge + expand icon) so the entry
point is discoverable without taking up page space. Also fix the perception
telemetry badge count, drop the duplicate perception eyebrow, and keep every
component-row status dot in one column.
Rebuild the top bar around read-only Atlas/operator badges that click through to
Settings, inline SVG icons, gradient action buttons and a two-line brand, with
the palette refreshed to match.

Alongside the redesign, address the open review threads: the compute-node alert
detail now goes through t() so the zh-CN table actually applies, the orphan and
duplicate translation keys are gone, the emergency-stop caveat about voice
recording is restored, streamed replies are persisted on a short debounce so a
dropped socket or mid-stream reload keeps them, the operator badge refreshes
after voice enrolment, and the dead command-bar CSS is removed.
@laojiahuo2003

Copy link
Copy Markdown
Contributor Author

On the tests request: added tests/test_perception_helpers.py and tests/test_health_api.py.

  • _rewrite_loopback — routable endpoint left untouched; loopback rewritten to the robot host while the advertised Host keeps the original value; loopback without a port.
  • _sse_json — parses the data lines and skips events, keepalives and malformed payloads.
  • -1 sentinel and non-numerics map to None; compute_node_sample projects the three signals and yields None for the absent ones. (The mapping helper is named compute_node_sample in the current code, not health_state_to_sample.)

@enkerewpo

Copy link
Copy Markdown
Member

LGTM

@enkerewpo
enkerewpo merged commit d368173 into syswonder:main Oct 9, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants