Update 2026-07-23 10:55:34
This commit is contained in:
commit
1d354ad2a3
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
__pycache__/
|
||||
*.pyc
|
||||
Logs/
|
||||
*.log
|
||||
29
Dockerfile
Normal file
29
Dockerfile
Normal file
@ -0,0 +1,29 @@
|
||||
# Pudu Map GUI — fully dockerized. ONE image carries everything the GUI does:
|
||||
# web server + converter/editor backend (pillow/numpy/pyyaml)
|
||||
# goal sending (roslibpy -> robot rosbridge)
|
||||
# local /map publisher + RViz preview (ROS Jazzy + rviz2, X11 passthrough)
|
||||
# robot deploys (openssh-client/scp), ping, native file picker (zenity/tk)
|
||||
# Run with host network + X11 + the host paths mounted VERBATIM (see run_docker.sh)
|
||||
# so all absolute paths inside the code resolve identically in and out of docker.
|
||||
FROM ros:jazzy
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
ros-jazzy-rviz2 \
|
||||
python3-pil python3-numpy python3-yaml python3-pip \
|
||||
python3-tk zenity \
|
||||
openssh-client rsync iputils-ping psmisc \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# roslibpy is not packaged in apt; 24.04 needs the PEP-668 override. cryptography
|
||||
# is PINNED to the deb version — pip can't uninstall debian packages (no RECORD)
|
||||
# and the TLS extra chain would otherwise try to upgrade it.
|
||||
RUN pip3 install --no-cache-dir --break-system-packages "cryptography==41.0.7" roslibpy
|
||||
|
||||
# The GUI itself is volume-mounted (workstation-canonical, live-editable) — the
|
||||
# image only guarantees the runtime. Fallback copy so the image also runs alone.
|
||||
COPY pudu_gui.py index.html local_map_pub.py /opt/pudu_map_gui/
|
||||
|
||||
# rviz/relay launches source /opt/ros/jazzy/setup.bash themselves (ENV_CLEAN)
|
||||
ENTRYPOINT ["/bin/bash", "-lc"]
|
||||
CMD ["source /opt/ros/jazzy/setup.bash && python3 ${PUDU_GUI_DIR:-/opt/pudu_map_gui}/pudu_gui.py"]
|
||||
222
README.md
Normal file
222
README.md
Normal file
@ -0,0 +1,222 @@
|
||||
# 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.
|
||||
2364
index.html
Normal file
2364
index.html
Normal file
File diff suppressed because it is too large
Load Diff
82
local_map_pub.py
Normal file
82
local_map_pub.py
Normal file
@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env python3
|
||||
"""local_map_pub — publish a converted map on /map so RViz can preview it LOCALLY.
|
||||
|
||||
Used by the Pudu Map GUI's "RViz: preview converted map" button: no robot, no
|
||||
rosbridge, no map_server needed — just the workstation's ROS (Jazzy) + the
|
||||
converted map.yaml. Republishes every 2 s (works with RViz's default volatile
|
||||
Map subscription regardless of durability settings).
|
||||
|
||||
python3 local_map_pub.py /path/to/map.yaml
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
import rclpy
|
||||
import yaml
|
||||
from nav_msgs.msg import OccupancyGrid
|
||||
from PIL import Image
|
||||
from rclpy.node import Node
|
||||
from rclpy.qos import DurabilityPolicy, QoSProfile
|
||||
|
||||
|
||||
def load_grid(yaml_path):
|
||||
with open(yaml_path) as f:
|
||||
meta = yaml.safe_load(f)
|
||||
img_path = meta["image"]
|
||||
if not os.path.isabs(img_path):
|
||||
img_path = os.path.join(os.path.dirname(os.path.abspath(yaml_path)), img_path)
|
||||
gray = np.array(Image.open(img_path).convert("L")).astype(np.float32)
|
||||
free_t = float(meta.get("free_thresh", 0.196))
|
||||
occ_t = float(meta.get("occupied_thresh", 0.65))
|
||||
if int(meta.get("negate", 0)):
|
||||
p = gray / 255.0
|
||||
else:
|
||||
p = (255.0 - gray) / 255.0
|
||||
occ = np.full(gray.shape, -1, dtype=np.int8)
|
||||
occ[p <= free_t] = 0
|
||||
occ[p >= occ_t] = 100
|
||||
# pgm row 0 = top; OccupancyGrid row 0 = bottom -> flip vertically
|
||||
occ = occ[::-1, :]
|
||||
return meta, occ
|
||||
|
||||
|
||||
class LocalMapPub(Node):
|
||||
def __init__(self, yaml_path):
|
||||
super().__init__("pudu_gui_local_map_pub")
|
||||
meta, occ = load_grid(yaml_path)
|
||||
self._msg = OccupancyGrid()
|
||||
self._msg.header.frame_id = "map"
|
||||
self._msg.info.resolution = float(meta["resolution"])
|
||||
self._msg.info.height, self._msg.info.width = occ.shape
|
||||
ox, oy = float(meta["origin"][0]), float(meta["origin"][1])
|
||||
self._msg.info.origin.position.x = ox
|
||||
self._msg.info.origin.position.y = oy
|
||||
self._msg.info.origin.orientation.w = 1.0
|
||||
self._msg.data = occ.reshape(-1).tolist()
|
||||
qos = QoSProfile(depth=1, durability=DurabilityPolicy.TRANSIENT_LOCAL)
|
||||
self._pub = self.create_publisher(OccupancyGrid, "/map", qos)
|
||||
self.create_timer(2.0, self._tick)
|
||||
self._tick()
|
||||
self.get_logger().info(
|
||||
f"publishing {self._msg.info.width}x{self._msg.info.height} @ "
|
||||
f"{self._msg.info.resolution} m/px, origin ({ox}, {oy}) from {yaml_path}")
|
||||
|
||||
def _tick(self):
|
||||
self._msg.header.stamp = self.get_clock().now().to_msg()
|
||||
self._pub.publish(self._msg)
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
sys.exit("usage: local_map_pub.py /path/to/map.yaml")
|
||||
rclpy.init()
|
||||
node = LocalMapPub(sys.argv[1])
|
||||
try:
|
||||
rclpy.spin(node)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
1
maps/.current.json
Normal file
1
maps/.current.json
Normal file
@ -0,0 +1 @@
|
||||
{"yaml": "/home/zedx/Robotics_workspace/yslootahtech/Project/G1/Nav2_Projects/sanad_nav3/maps/pudu_modified/map.yaml"}
|
||||
1
maps/.last_pose.json
Normal file
1
maps/.last_pose.json
Normal file
@ -0,0 +1 @@
|
||||
{"g1": {"x": -3.252046312175514, "y": -6.437751531970616, "yaw": 0.0, "at": "2026-07-21 08:22"}, "go2": {"x": -3.0064908722109527, "y": -6.3947261663286, "yaw": 0.0, "at": "2026-07-21 13:00"}}
|
||||
11
maps/yslootahtech_pudu.bak_20260720_082358/annotations.yaml
Normal file
11
maps/yslootahtech_pudu.bak_20260720_082358/annotations.yaml
Normal file
@ -0,0 +1,11 @@
|
||||
home_points:
|
||||
- name: W
|
||||
x: 0
|
||||
y: 0.01
|
||||
yaw: 0.00999633
|
||||
- name: N
|
||||
x: 1.27
|
||||
y: -4.19
|
||||
yaw: -0.38
|
||||
charge_stations: []
|
||||
areas: []
|
||||
BIN
maps/yslootahtech_pudu.bak_20260720_082358/map.pgm
Normal file
BIN
maps/yslootahtech_pudu.bak_20260720_082358/map.pgm
Normal file
Binary file not shown.
BIN
maps/yslootahtech_pudu.bak_20260720_082358/map.pgm.orig
Normal file
BIN
maps/yslootahtech_pudu.bak_20260720_082358/map.pgm.orig
Normal file
Binary file not shown.
7
maps/yslootahtech_pudu.bak_20260720_082358/map.yaml
Normal file
7
maps/yslootahtech_pudu.bak_20260720_082358/map.yaml
Normal file
@ -0,0 +1,7 @@
|
||||
image: map.pgm
|
||||
mode: trinary
|
||||
resolution: 0.05
|
||||
origin: [-5.85, -11.8, 0.0]
|
||||
negate: 0
|
||||
occupied_thresh: 0.65
|
||||
free_thresh: 0.196
|
||||
BIN
maps/yslootahtech_pudu.bak_20260720_082358/map_keepout_baked.pgm
Normal file
BIN
maps/yslootahtech_pudu.bak_20260720_082358/map_keepout_baked.pgm
Normal file
Binary file not shown.
@ -0,0 +1,7 @@
|
||||
image: map_keepout_baked.pgm
|
||||
mode: trinary
|
||||
resolution: 0.05
|
||||
origin: [-5.85, -11.8, 0.0]
|
||||
negate: 0
|
||||
occupied_thresh: 0.65
|
||||
free_thresh: 0.196
|
||||
11
maps/yslootahtech_pudu.bak_20260720_125341/annotations.yaml
Normal file
11
maps/yslootahtech_pudu.bak_20260720_125341/annotations.yaml
Normal file
@ -0,0 +1,11 @@
|
||||
home_points:
|
||||
- name: W
|
||||
x: 0
|
||||
y: 0.01
|
||||
yaw: 0.00999633
|
||||
- name: N
|
||||
x: 1.27
|
||||
y: -4.19
|
||||
yaw: -0.38
|
||||
charge_stations: []
|
||||
areas: []
|
||||
BIN
maps/yslootahtech_pudu.bak_20260720_125341/map.pgm
Normal file
BIN
maps/yslootahtech_pudu.bak_20260720_125341/map.pgm
Normal file
Binary file not shown.
BIN
maps/yslootahtech_pudu.bak_20260720_125341/map.pgm.orig
Normal file
BIN
maps/yslootahtech_pudu.bak_20260720_125341/map.pgm.orig
Normal file
Binary file not shown.
7
maps/yslootahtech_pudu.bak_20260720_125341/map.yaml
Normal file
7
maps/yslootahtech_pudu.bak_20260720_125341/map.yaml
Normal file
@ -0,0 +1,7 @@
|
||||
image: map.pgm
|
||||
mode: trinary
|
||||
resolution: 0.05
|
||||
origin: [-5.85, -11.8, 0.0]
|
||||
negate: 0
|
||||
occupied_thresh: 0.65
|
||||
free_thresh: 0.196
|
||||
BIN
maps/yslootahtech_pudu.bak_20260720_125341/map_keepout_baked.pgm
Normal file
BIN
maps/yslootahtech_pudu.bak_20260720_125341/map_keepout_baked.pgm
Normal file
Binary file not shown.
@ -0,0 +1,7 @@
|
||||
image: map_keepout_baked.pgm
|
||||
mode: trinary
|
||||
resolution: 0.05
|
||||
origin: [-5.85, -11.8, 0.0]
|
||||
negate: 0
|
||||
occupied_thresh: 0.65
|
||||
free_thresh: 0.196
|
||||
2449
pudu_gui.py
Normal file
2449
pudu_gui.py
Normal file
File diff suppressed because it is too large
Load Diff
4
run_docker.sh
Executable file
4
run_docker.sh
Executable file
@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
# Compatibility shim — the launcher is now ONE script: start.sh
|
||||
# (./run_docker.sh [logs|stop|restart|build|fg] still works and forwards there.)
|
||||
exec "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/start.sh" "$@"
|
||||
99
start.sh
Executable file
99
start.sh
Executable file
@ -0,0 +1,99 @@
|
||||
#!/usr/bin/env bash
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Pudu Map GUI — THE one script.
|
||||
#
|
||||
# ./start.sh start it + open the browser
|
||||
# ./start.sh logs follow the server log
|
||||
# ./start.sh stop stop it (same as the dashboard's ⏻ Shutdown)
|
||||
# ./start.sh restart stop + start
|
||||
# ./start.sh build rebuild the docker image, then start
|
||||
# ./start.sh fg run attached in this terminal (debugging only)
|
||||
#
|
||||
# Everything runs in docker; the host needs only docker + an X server.
|
||||
# Host paths are mounted VERBATIM so every absolute path behaves the same in
|
||||
# and out of the container:
|
||||
# ~/Robotics_workspace stage dirs, map editor, relay, rviz configs
|
||||
# ~/Downloads Pudu exports
|
||||
# ~/.ssh (read-only) robot deploys (known_hosts redirected to /tmp)
|
||||
# X11 + DISPLAY RViz + the native file picker
|
||||
# --net=host web GUI on :8777, DDS for the local /map, rosbridge
|
||||
#
|
||||
# DETACHED BY DEFAULT: the dashboard is a service. Also, `docker run -it` can
|
||||
# hang forever on a host whose docker attach stream is wedged (seen live on this
|
||||
# workstation: create/start/stop fine, every attached run hung) — detached
|
||||
# sidesteps that. Use `fg` only when you need the raw attached output.
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
set -euo pipefail
|
||||
CMD="${1:-start}"
|
||||
# not in the docker group in THIS shell? re-run the whole script under sg
|
||||
docker ps >/dev/null 2>&1 || exec sg docker -c "$0 $CMD"
|
||||
|
||||
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
IMG=pudu-map-gui
|
||||
NAME=pudu-map-gui
|
||||
PORT=8777
|
||||
URL="http://127.0.0.1:$PORT"
|
||||
|
||||
_stop() {
|
||||
# the GUI's own viewer processes live INSIDE the container, so removing it
|
||||
# takes them with it. Robots are never touched by this script.
|
||||
if docker rm -f "$NAME" >/dev/null 2>&1; then echo "stopped $NAME"
|
||||
else echo "$NAME was not running"; fi
|
||||
}
|
||||
|
||||
case "$CMD" in
|
||||
stop) _stop; exit 0 ;;
|
||||
logs) exec docker logs -f "$NAME" ;;
|
||||
restart) _stop ;;
|
||||
esac
|
||||
|
||||
if [ "$CMD" = build ] || ! docker image inspect $IMG >/dev/null 2>&1; then
|
||||
echo "== building $IMG (first run takes a few minutes) =="
|
||||
docker build -t $IMG "$HERE"
|
||||
fi
|
||||
|
||||
# let the container's root talk to the host X server (RViz / zenity picker)
|
||||
xhost +local: >/dev/null 2>&1 || true
|
||||
# a stray host-side instance would hold the port — hand over cleanly
|
||||
fuser -k "$PORT/tcp" >/dev/null 2>&1 || true
|
||||
docker rm -f "$NAME" >/dev/null 2>&1 || true
|
||||
|
||||
RUN=(docker run --rm --net=host --name "$NAME"
|
||||
-e DISPLAY="${DISPLAY:-:0}"
|
||||
-e HOME="$HOME"
|
||||
-e PUDU_GUI_DIR="$HERE"
|
||||
-v /tmp/.X11-unix:/tmp/.X11-unix
|
||||
-v "$HOME/Robotics_workspace:$HOME/Robotics_workspace"
|
||||
-v "$HOME/Downloads:$HOME/Downloads"
|
||||
-v "$HOME/.ssh:$HOME/.ssh:ro")
|
||||
|
||||
if [ "$CMD" = fg ]; then # attached, for debugging
|
||||
TT=(-it); [ -t 0 ] || TT=()
|
||||
exec "${RUN[@]}" "${TT[@]}" $IMG
|
||||
fi
|
||||
|
||||
"${RUN[@]}" -d $IMG >/dev/null
|
||||
|
||||
# never claim success blindly — wait until the server actually answers
|
||||
for _ in $(seq 40); do
|
||||
if curl -fsS -m 2 -o /dev/null "$URL/" 2>/dev/null; then
|
||||
[ "${NOBROWSER:-0}" = 1 ] || (xdg-open "$URL" >/dev/null 2>&1 &) || true
|
||||
cat <<EOT
|
||||
|
||||
Pudu Map GUI is up -> $URL
|
||||
(opened in your browser; if the page looks old, reload with $URL/?v=1)
|
||||
|
||||
logs: $0 logs stop: $0 stop restart: $0 restart
|
||||
or press ⏻ Shutdown in the dashboard header.
|
||||
EOT
|
||||
exit 0
|
||||
fi
|
||||
docker ps --format '{{.Names}}' | grep -qx "$NAME" || {
|
||||
echo "container exited during startup — last log lines:" >&2
|
||||
docker logs "$NAME" 2>&1 | tail -20 >&2 || true
|
||||
exit 1
|
||||
}
|
||||
sleep 1
|
||||
done
|
||||
echo "started, but $URL did not answer within 40s — check: $0 logs" >&2
|
||||
exit 1
|
||||
Loading…
x
Reference in New Issue
Block a user