ROS 2 workspace for multi-robot swarm exploration. Four TurtleBot3 Waffle robots autonomously explore a complex maze using decentralized frontier-based navigation. Gazebo sim runs headless and Foxglove provides the visualisation UI.
Written in Python with ROS 2 Jazzy, the codebase includes SLAM, Nav2, a custom frontier detector, and a per-robot FSM. Robots coordinate via geodesic Voronoi partitioning: a simultaneous multi-source BFS divides the merged map so each robot owns the region it can reach fastest, then selects the nearest frontier within its partition. This eliminates duplicate work without any central coordinator. See ARCHITECTURE.md for a full description.
| Step | Tool | Purpose |
|---|---|---|
| Write & edit code | Dev Container | Full ROS 2 + tooling environment in VS Code |
| Run the simulator | docker compose |
Builds the sim image and launches the full stack |
The dev container forwards the host Docker socket, so docker compose commands work from inside it.
- Docker (Desktop or Engine)
- VS Code with the Dev Containers extension (
ms-vscode-remote.remote-containers)
- Clone this repo and open the folder in VS Code.
- When prompted, click Reopen in Container (or
F1→ Dev Containers: Reopen in Container). - VS Code builds the image from
.devcontainer/Dockerfile.devand mounts the workspace at/ws. pre-commit installruns automatically on first open.
The dev container includes ROS 2 Jazzy, ruff, mypy, pre-commit, and recommended VS Code extensions (Ruff, Pylance, ROS, XML, TOML, Claude Code).
From the workspace root — either on the host or from a terminal inside the dev container:
make build # Build the sim image (only needed after Dockerfile or dependency changes)
make up # Start the full 4-robot stack
make down # Stop and remove containers
make rebuild # Build then start in one step
make logs # Follow logs from all running servicesThis starts seven services:
| Service | What it runs |
|---|---|
sim |
Gazebo Harmonic headless + clock bridge |
robot_0 |
TurtleBot3 Waffle at (0.6, 0.6) — SLAM + Nav2 + FSM |
robot_1 |
TurtleBot3 Waffle at (1.8, 0.6) — SLAM + Nav2 + FSM |
robot_2 |
TurtleBot3 Waffle at (0.6, 1.8) — SLAM + Nav2 + FSM |
robot_3 |
TurtleBot3 Waffle at (1.8, 1.8) — SLAM + Nav2 + FSM |
global |
Map merge node + frontier detector |
foxglove |
Foxglove bridge on ws://localhost:8765 |
Stop with Ctrl-C or make down.
Fast (headless) — validates launch file syntax and node imports in seconds, no display needed:
make test # docker build --target test .Live single-robot — starts Gazebo with one robot to verify the full exploration stack end-to-end:
make single # docker compose --profile single upRun headless at maximum simulation speed and record a bag (auto-stops when all robots reach DONE):
make record # headless only
make record FOXGLOVE=1 # also start the Foxglove bridge
make record EXPLORE=60 # stop at 60% exploration
make record TIMEOUT=300 # stop after 300 secondsBags are written to ./bags/.
Open Foxglove (Desktop or web):
- New connection → Foxglove WebSocket →
ws://localhost:8765 - Set fixed frame to
world.
Useful topics:
| Panel | Topic |
|---|---|
| Map (merged) | /merged_map |
| LaserScan (robot 0) | /robot_0/scan |
| LaserScan (robot 1) | /robot_1/scan |
| LaserScan (robot 2) | /robot_2/scan |
| Frontier markers | /frontier_markers |
| Goal markers | /robot_N/goal_markers |
| Coverage plot | /robot_N/status → map_cells_known |
A pre-built dashboard layout is in config/foxglove/swarm_ws.json — import it via File → Import layout.
Hooks run automatically before every commit. To run manually:
pre-commit run --all-files| Hook | What it does |
|---|---|
check-json |
Validates JSON syntax |
check-yaml |
Validates YAML syntax |
check-toml |
Validates TOML syntax |
ruff |
Python linting (auto-fix) |
ruff-format |
Python formatting |
trailing-whitespace |
Strips trailing spaces |
end-of-file-fixer |
Ensures files end with a newline |
mixed-line-ending |
Normalises line endings to LF |
check-merge-conflict |
Blocks leftover merge-conflict markers |
check-added-large-files |
Blocks files > 500 KB |
Update hook versions:
pre-commit autoupdate- Port 8765 in use: stop any process on that port and rerun
docker compose up. - No map updates: SLAM and the global map merge start with timed delays — wait ~25 s after the stack is up before expecting
/merged_mapor/frontiers. - Robots not moving: the FSM enters EXPLORING only after frontiers appear on
/frontiers; allow time for SLAM to build initial maps. - Missing ROS packages in sim image: rebuild with
docker compose build. - DDS participant index errors: CycloneDDS is configured with
MaxAutoParticipantIndex: 200to handle 30+ nodes; if you see index collisions check theCYCLONEDDS_URIenv var incompose.yaml.