2026-07-23 11:13:09 +04:00
2026-07-23 11:13:09 +04:00
2026-07-23 10:55:34 +04:00
2026-07-23 10:55:34 +04:00
2026-07-23 10:55:34 +04:00
2026-07-23 11:13:09 +04:00
2026-07-23 10:55:34 +04:00
2026-07-23 10:55:34 +04:00
2026-07-23 11:13:09 +04:00

Pudu Map GUI — fleet map dashboard

Convert maps scanned by the Pudu commercial robots (CC2 / BellaBot / …) into ROS2 Nav2 maps, then run the whole map lifecycle for the Unitree fleet from one browser dashboard: convert → preview → edit → stage → deploy → install the robot's nav stack → RViz → send goals.

cd Project/Other/pudu_map_gui
./start.sh               # ← the ONLY command: starts it + opens the browser
./start.sh logs       follow the server log
./start.sh stop       stop it   (same as the dashboard's ⏻ Shutdown button)
./start.sh restart    stop + start
./start.sh build      rebuild the image, then start
./start.sh fg         run attached in this terminal (debugging only)

One bash script, fully docker

start.sh is the single entry point (run_docker.sh still works — it forwards there). First run builds the pudu-map-gui image (ros:jazzy + rviz2 + converter deps + ssh/rsync + zenity/tk picker + roslibpy); every run after that starts in seconds. The host needs docker and an X server only — no ROS, no pip, no conda on the host.

It runs detached and only reports success once the URL actually answers. That is deliberate: the dashboard is a service, and docker run -it can hang forever on a host whose docker attach stream is wedged (observed on this workstation — create/start/stop fine, every attached run hung). Use ./start.sh fg only when you want the raw attached output.

⏻ Shutdown (dashboard header) stops everything this GUI started on the workstation — RViz, the rosbridge relay, the local map publisher — and then the server itself, so the container exits. Robots are never touched: their nav stacks keep running. Start again with ./start.sh.

The container runs with --net=host, X11 passthrough, and the host paths mounted verbatim (~/Robotics_workspace, ~/Downloads, ~/.ssh read-only), so every absolute path behaves identically in and out of docker. A host-side fallback (python3 pudu_gui.py) exists but is never required.

The robot sides are docker too:

robot stack runtime localization for the shared map
R1 r1-nav2 + r1-vslam containers (Project/R1/nav) fully docker on the backpack stereo VSLAM (RTAB-Map) + map_align static TF onto the Pudu frame
G1 sanad_nav3~/nav_g1 (Project/G1/Nav2_Projects/sanad_nav3) ⚠ native on the Jetson (see below) MID-360 LiDAR SLAM / STATIC_MAP=1 map_server + AMCL
Go2 go2-nav2 container (Project/GO2/nav/nav2) fully docker on the EDU computer L1 LiDAR synthetic /scan → slam_toolbox (mapping) / AMCL (saved map)

All three are Nav2 Foxy 0.4.7. The workstation never joins their DDS — every live view goes through the robot's rosbridge websocket into local RViz (Go2/R1 on :9090, the G1's own stack on :9091:9090 on that Jetson belongs to an unrelated pre-existing service and is never used).

G1 exception, stated honestly: the standalone G1 stack interoperates with the Jetson's existing host processes (livox SDK bridge, LIO workspace, shared ZMQ ports) and the robot is under a standing deploy hold — so its install is a file sync onto the Jetson's existing ROS runtime, not a container.

Program flow — inside each robot

One naming scheme everywhere — the nav stack installs to ~/nav_<type> on every robot:

robot install folder ROS 2 SLAM runs as
Go2 /home/unitree/nav_go2 Foxy (docker) slam_toolbox on the L1 LiDAR (synthetic /scan); AMCL for saved/Pudu maps go2-nav2 container
G1 /home/unitree/nav_g1 Foxy on the Jetson RTAB-Map on the MID-360 LiDAR; STATIC_MAP=1 = map_server + AMCL on the Pudu map host scripts (own rosbridge on :9091)
R1 /home/unitree/nav_r1 + /home/unitree/nav_r1_vslam Foxy (docker) RTAB-Map stereo VSLAM (no LiDAR) + map_align static TF onto the Pudu frame r1-nav2 + r1-vslam containers (both halves installed together)

RTAB-Map is the SLAM library on the G1 (LiDAR mode) and the R1 (stereo visual mode) — it builds the 2D occupancy grids Nav2 consumes. The Go2 uses slam_toolbox instead (its L1 data arrives as a laser scan, where slam_toolbox is the lighter, proven fit on Foxy).

Go2 — what runs inside nav_go2:

L1 LiDAR ─ rt/utlidar/cloud + robot_odom ─► SDK reader (DDS domain 0)
   └─ ZMQ ─► /scan + /odom + TF (domain 42, in-container)
/scan ─► slam_toolbox (slam mode)   OR   map_server + AMCL (nav mode, your map)
      ─► Nav2 Foxy ─► /cmd_vel ─ ZMQ ─► SportClient consumer ─► robot walks
rosbridge :9090 ─► dashboard / workstation RViz
folder: run.sh  build.sh  Dockerfile  entrypoint.sh  bridge/  params/  launch/  maps/  sdk/

G1 — what runs from nav_g1 (standalone flavor):

MID-360 ─► livox bridge ─► pointcloud→laserscan ─► /scan
/scan + leg odom TF ─► RTAB-Map (SLAM)   OR   map_server + AMCL (STATIC_MAP=1)
      ─► Nav2 ─► /cmd_vel ─► loco bridge (ZMQ) ─► gait
folder: scripts/ (bringup.sh …)  launch/  config/  maps/  web/  rviz/

R1 — what runs inside nav_r1 (+ the vslam containers):

stereo head cams ─► rectify ─► RTAB-Map VSLAM (r1-vslam-map) ─► odom + grid
grid (+ Pudu map via map_align TF in --pudu mode) ─► Nav2 (r1-nav2) ─► /cmd_vel
      ─► IPC bridge ─► LocoClient — moves ONLY after YOU arm FSM 811
folder: run.sh  build.sh  bridge/  params/  launch/  maps/  sdk/

Robots installed under the old folder names (go2_nav2_docker, r1_nav2_docker, marcus_nav2_test) are detected as legacy — press Install once to migrate; Uninstall cleans both old and new names.

Dashboard features

  1. Convert — path, .zip, or native file/folder picker. Emits map.pgm/.yaml, annotations.yaml (homes/chargers as goal poses), keepout_mask.*, and always map_keepout_baked.* (Foxy has no KeepoutFilter — keepouts are baked as lethal; the robot stacks serve that exact filename).
  2. Map library — every converted AND every edited map is saved in the project: Project/Other/pudu_map_gui/maps/<name>/, one folder per set (a 📁 chip shows the exact location after each convert). Editing a set that lives elsewhere (a stage dir, ~/Downloads, a robot save) relocates it into the library first, so staged/deployed copies are never mutated in place. Reconverting an export overwrites its set unless it was hand-edited — then the edit is kept as a .bak_<timestamp> folder. The list also scans the robot stage dirs and ~/Downloads; each row has Load and 🗑 delete (removes only that set's files, never a maps folder itself, and refuses anything outside the known map roots).
  3. Preview — pan/zoom occupancy + keepout + route graph + poses with world-meter readout; 🎯 goal mode click-drags a heading and publishes /goal_pose via rosbridge.
  4. Edit — the full Office Map Editor (served un-forked from the R1 tree) with auto-load of the current map, Save to Pudu GUI (rebuilds the baked pair), and a Ctrl+C transform panel (move / rotate the whole map).
  5. Stage — copies into the robot's workstation-canonical maps dir (old set backed up, stale files cleared).
  6. Deploy — scp the map set to the robot over the IP you pick (key auth, BatchMode; docker-proof ssh with explicit identities).
  7. Nav stack install / uninstall / scan (step 4):
    • 🔍 Check — one ssh round-trip: files present? docker image present? stack running right now? (R1 also reports whether vslam is up.)
    • ⬇ Install — runs that robot's one-shot installer (install.sh <ip>): rsync code+maps+SDK, then docker build on the robot. The R1 install ships BOTH halves — nav_r1 (r1-nav2 image) and nav_r1_vslam (r1-vslam image, RTAB-Map stereo VSLAM). The Go2 installer ends with a ~35 s sensors-only smoke run (starts the stack briefly, no motion, then removes it); the R1/G1 installers start nothing. Output streams live into the Logs tab. Refuses — unconditionally — while the stack (or the R1's vslam) is running.
    • 🗑 Uninstall — removes what install ships: the docker image(s), the stack's runtime containers (Go2: go2-nav2 + cli helpers; R1: r1-nav2, r1-imu, r1-vslam-map, r1-rosbridge, r1-cam), and the install dir(s), legacy names included. Exact-name matching only — nothing else on the robot is ever touched. Refuses while anything is running.
  8. RViz (three views): preview converted map (no robot), live robot (robot's own /map via relay), loaded map + live robot (your chosen map locally + live TF/odom; 2D Pose Estimate + 2D Goal Pose forwarded to the robot).
  9. 📡 Lidar tab — the connected robot's live sensor view, to confirm localization. G1/Go2: the live /scan streamed straight from the robot's rosbridge into the browser (plain WebSocket, no external library) and drawn on the loaded map at the robot's AMCL/odom pose — if the red points sit on the walls, the robot is localized correctly; without a pose it falls back to a robot-centric polar view with range rings. R1: it has no LiDAR, so the tab shows its camera instead (MJPEG from http://<ip>:8088/stream).
  10. Logs — every action in the Logs tab (copy all / export / pop-out), plus toasts for every success and failure.

Typical loops

Go2 (verified live 2026-07-16):

Install (dashboard)  →  run.sh slam  →  walk the robot  →  run.sh save-map office
→  run.sh use-map office  →  run.sh nav  →  RViz 2D Pose Estimate  →  goals

R1 (Pudu map): Install (dashboard) → deploy the map → start_r1_nav.sh --nav --pudu from the workstation → fit map_align.yaml once (R1/nav/tools/fit_map_alignment.py) → goals. The robot moves only after the user arms FSM 811.

G1 (held): stage + deploy from the dashboard when the hold lifts → STATIC_MAP=1 ~/nav_g1/scripts/bringup.sh.

Requirements & safety

  • Workstation: docker + X11. Robot: docker + internet (first build only).
  • ssh key auth must exist once per robot: ssh-copy-id unitree@<ip> (the GUI never types passwords).
  • The GUI never arms or moves a robot: install/uninstall touch software only (the Go2 install's smoke run brings up sensors briefly, never motion), and both refuse while a stack is live, with no override. Goals: the R1 walks only after the user arms FSM 811; the Go2 boots in sport mode, so a goal makes it walk as soon as its nav stack runs (velocity-capped, with a deadman) — treat every goal click as a movement command.
  • G1 standing hold: the dashboard warns before any G1 deploy/install.

Files

run_docker.sh     ← the one entry script (build-on-first-run + run)
Dockerfile        image: ros:jazzy + rviz2 + deps (self-contained runtime)
pudu_gui.py       server: convert/stage/deploy/install/rviz/goal endpoints
index.html        the dashboard
local_map_pub.py  local /map publisher for the RViz previews
maps/             ← the map library: one folder per converted/edited set

Robot installers the dashboard drives: Project/GO2/nav/nav2/docker/install.sh, Project/R1/nav/nav2/docker/install.sh, Project/G1/Nav2_Projects/sanad_nav3/install.sh.

Troubleshooting

  • Install button says "stack is RUNNING" — that's the guard: stop the robot's session first, then install. Go2: ~/nav_go2/run.sh estop (robot sits) then docker rm -f go2-nav2 on the robot. R1: on the robot docker exec r1-nav2 /entrypoint.sh estop; docker rm -f r1-nav2 (there is no --stop flag on start_r1_nav.sh — running it bare STARTS the stack).
  • "no key auth yet" — run ssh-copy-id unitree@<ip> in a terminal once.
  • Combo RViz poses don't line up — the robot must actually run the same map in nav mode (use-map + run.sh nav), then give a 2D Pose Estimate.
  • G1/R1 share 192.168.123.164 — whichever robot is on the wire answers; check the status line says the stack you expect.
Description
No description provided
Readme
Languages
Python 50.7%
HTML 46.8%
Shell 1.9%
Dockerfile 0.6%