Sanadv3/navigation/README.md

4.3 KiB

SanadV3 Navigation

Thin HTTP client to the external web_nav3 Nav2 stack. This module owns no ROS2/Nav2 code — it lets SanadV3 (dashboard + voice) drive autonomous navigation over plain HTTP. If web_nav3 is down, nav features degrade gracefully and the rest of SanadV3 keeps running.

What this module does

  • web_nav3_client.pyWebNav3Client, a loose-coupled requests client. By contract no method ever raises into the caller: each returns {"ok": bool, "error": str|None, ...} or a NavStatus. If web_nav3 is unreachable, callers get a clean failure result instead of an exception.
  • NavStatus — health snapshot from GET /api/status (bringup_alive, rosbridge_alive, reachable, log_tail).

Architecture

SanadV3 dashboard (:8000)  ─┐
  Navigation tab            │   HTTP        ┌── Nav2 ──┐
                            ├──────────────▶│ web_nav3 │──▶ cmd_vel_loco_bridge ──▶ LocoClient (G1 legs)
SanadV3 voice (Gemini)    ──┘  (:8765)      └──────────┘
  movement_dispatch.py                       rosbridge :9090 (live map / TF)
  • SanadV3 plane = Python/asyncio, non-ROS. Dashboard on :8000.
  • web_nav3 = standalone FastAPI on :8765 wrapping ROS2 Nav2 + rosbridge on :9090. It owns SLAM, Nav2, and the cmd_vel_loco_bridge that drives the G1 legs via LocoClient.

Configure

Connection is resolved with precedence env var → dashboard config → default:

  • WEB_NAV3_URL (default http://127.0.0.1:8765) — the web_nav3 FastAPI base.
  • ROSBRIDGE_URL (default ws://127.0.0.1:9090) — live map / TF stream.
  • SANAD_ROBOT_NAME (default sanad) — sent as the X-Robot-Name header.

config.py exposes WEB_NAV3_URL. main.py builds the shared nav_client singleton; dashboard/routes/navigation.py builds its own module-level client (both use the same resolution). A broken nav package never blocks the dashboard.

Dashboard Navigation tab

Backend proxy lives under /api/nav/* (prefix applied in dashboard/app.py). The "Navigation" SPA tab lists saved places and missions, sends goto / cancel, saves the current pose, and embeds the live web_nav3 map iframe from the robot at :8765. When the client is unavailable, status returns {"available": false} and action endpoints return 503.

API endpoints (/api/nav/*)

Method Path Action
GET /status health; {available:false} if degraded
GET /config web_nav3 / rosbridge URLs + robot name
GET /places list saved places
POST /goto navigate to a saved place by name
POST /cancel best-effort cancel (stops bringup)
POST /save_here save current pose as a named place
GET /maps list maps
GET /missions list missions
POST /missions/run run a saved mission by id

NEXT STEPS

  1. Voice bridge (not yet wired). voice/movement_dispatch.py currently drives discrete loco_controller steps only. Add a path so destination phrases ("go to the lobby" / "اذهب إلى الردهة") map to nav_client.goto() instead of stepping. Keep it gated on the existing recognition_state.movement_enabled toggle.

  2. CRITICAL — LocoClient arbitration (prerequisite, do before #1). web_nav3's cmd_vel_loco_bridge and SanadV3's loco_controller must never drive LocoClient simultaneously — two velocity sources to the G1 legs at once is unsafe. Only ONE may hold the legs. Before enabling voice-driven autonomous nav, build a hand-off: when a Nav2 goto is active, loco_controller must release / be disarmed, and vice versa (fail-closed). No goto() voice wiring lands until this interlock exists.

  3. Single DDS participant ordering. SanadV3 and web_nav3 share one Unitree DDS domain on the G1. Initialize the DDS channel factory exactly once, before any consumer. Decide startup order (whoever owns LocoClient inits first) and ensure the other side never re-inits the participant.