Run the Gazebo SITL Simulation¶
Launch the full Bennu software stack in simulation using Docker. The simulation runs PX4 SITL with Gazebo Harmonic for physics and rendering, while a second container provides the ROS2 Jazzy environment with the uXRCE-DDS agent.
Prerequisites
- Docker and Docker Compose installed
- QGroundControl installed on the host (see below)
- NVIDIA GPU with Container Toolkit installed (for GUI mode)
- X11 display server running (for GUI mode)
Install QGroundControl¶
make sim and make qgc auto-download QGroundControl into ~/.local/share/bennu/
the first time they run, so no manual install is needed for the sim flow.
For real-hardware use over USB (radio telemetry), add yourself to dialout:
If you'd rather use a system-managed install, see the QGC install guide for AppImage, Flatpak, and other distros.
Architecture¶
The simulation uses two Docker containers communicating over UDP:
graph LR
subgraph "px4-sitl container"
PX4["PX4 SITL<br/>Gazebo Harmonic"]
end
subgraph "ros2-dev container"
XRCE["uXRCE-DDS Agent<br/>ROS2 Nodes"]
end
subgraph Host
QGC[QGroundControl]
end
PX4 -->|"MAVLink<br/>UDP 14550"| QGC
PX4 <-->|"UDP 8888"| XRCE
Compose Environments¶
The simulation stack is split into three Docker Compose files for different
use cases. A Makefile in sim/ wraps all Docker Compose commands.
| Environment | Compose File | Make Target | Use Case |
|---|---|---|---|
| Dev | docker-compose.dev.yml |
make dev |
Interactive development, pytest-watch |
| SIL | docker-compose.sil.yml |
make test-smoke / make test-sitl |
Headless CI, automated mission tests |
| Debug | docker-compose.debug.yml |
make dev-debug |
Gazebo 3D GUI with GPU passthrough |
Start the Simulation¶
One Command (recommended)¶
This single command does everything:
- Brings up PX4 SITL + ROS2 containers in Docker
- Launches QGroundControl on the host (auto-downloaded on first run, cached
in
~/.local/share/bennu/) - Kicks off the
nominal_surveymission in the background — the drone takes off, flies a lawnmower grid, takes photos, and returns home - Drops you into an interactive
ros2-devshell
Watch the drone fly in QGroundControl, or tail the mission log from the shell:
To run a different scenario, set the SCENARIO variable:
When done, exit the shell and run make clean to stop containers.
Headless Mode¶
PX4 SITL starts with Gazebo physics running in the background, no QGC. Use this on CI runners, over SSH, or when you don't need a ground station UI.
QGroundControl Only¶
Useful if you already have make dev running, or if you want to connect QGC
to a real Pixhawk over USB.
To start dev mode with pytest-watch auto-rerunning tests on file changes:
GUI Mode (with Gazebo 3D window)¶
To see the drone in the Gazebo 3D viewport, use the debug profile which adds NVIDIA GPU passthrough and X11 forwarding:
# Allow Docker containers to access your display (run once per login session)
xhost +local:docker
# Launch with Gazebo GUI enabled
make dev-debug
Gazebo opens in a new window showing the x500 quadcopter model.
Connect QGroundControl¶
Open QGroundControl --- it auto-connects to PX4 SITL via MAVLink on
localhost:14550. Both containers use host networking, so no port mapping is
needed.
NVIDIA GPU Setup (required for GUI mode)¶
GUI mode requires an NVIDIA GPU with the Container Toolkit installed:
This installs the NVIDIA Container Toolkit, generates a CDI specification, and configures Docker to pass through the GPU. After running the script, restart Docker and the simulation.
Gazebo GUI crashes with OpenGL error
If you see Failed to create OpenGL context in the logs, the GPU is not
accessible inside the container. Check:
- X11 access --- run
xhost +local:dockeron the host - NVIDIA drivers --- run
nvidia-smion the host to confirm drivers are loaded - Container GPU access --- run
docker exec bennu-px4-sitl-debug nvidia-smito verify the GPU is visible inside the container - CDI spec --- run
nvidia-ctk cdi generate --output=/etc/cdi/nvidia.yamlif the CDI spec is missing
Run Tests¶
# Unit, contract, and integration tests (no PX4 needed)
make test
# Full mission SIL test (headless, requires PX4 SITL)
make test-smoke
# Run all scenario tests
make test-sitl
# Run all tests
make test-all
For detailed SIL testing guidance — scenarios, timeouts, debugging — see Run SIL Tests.
Fly the Drone¶
Once the simulation is running and QGroundControl shows a green Ready to Fly status bar, you can fly the simulated drone. No ROS2 nodes are needed for this --- QGroundControl talks directly to PX4 over MAVLink.
Takeoff¶
- Click the Takeoff button in the bottom-left action bar (Fly view).
- Confirm the takeoff altitude (default ~2.5 m) and slide to confirm.
- The drone arms, spins up motors, and climbs to the target altitude.
Fly to a Point¶
Click anywhere on the map and choose Go to location. The drone flies to that point at its current altitude.
Plan a Waypoint Mission¶
- Switch to the Plan tab (top toolbar).
- Click on the map to add waypoints. Each waypoint gets a default altitude you can adjust in the sidebar.
- Click Upload (top-right) to send the mission to PX4.
- Switch back to the Fly tab and click Start Mission.
The drone takes off, flies the waypoints in order, and returns to launch.
Land¶
- Land --- click the Land button to land at the current position.
- RTL (Return to Launch) --- click RTL to fly back to the takeoff point and land automatically. This is the same behavior as the real drone's failsafe.
Test ROS2 Nodes¶
Open a shell in the ROS2 container and launch the Bennu nodes:
docker exec -it bennu-ros2-dev bash
source /ros2_ws/install/setup.bash
ros2 launch bennu_bringup drone.launch.py use_sim:=true output_dir:=/tmp/captures
The two arg overrides matter for the dev container:
use_sim:=trueswitchesuxrce_dds_agentfrom serial (/dev/ttyAMA0, real Pi 4 UART) to UDP on port 8888, where PX4 SITL listens. It also flips the camera node to theplaceholderbackend, sincelibcameraisn't available in the dev image.output_dir:=/tmp/capturesoverrides the default/home/pi/captures, which doesn't exist in the dev container.
A successful start emits UDP/IPv6 server initialized from the agent and
create_client | session_id: 0x81 shortly after, when PX4 SITL connects.
To inspect available topics:
You should see /fmu/out/vehicle_odometry, /fmu/out/vehicle_status, and others.
If the list is empty, the bridge hasn't connected yet — give it ~10s after launch.
Access the PX4 Console¶
To interact with the PX4 shell (pxh>), open a separate terminal and exec
into the running container:
(Use bennu-px4-sitl-debug instead if you started the GUI/debug profile.)
Alternatively, use QGroundControl's built-in MAVLink console (Analyze > MAVLink Console).
Stop the Simulation¶
All Make Targets¶
Run make help from the repo root to see all available commands:
sim Containers + QGC + auto-fly $(SCENARIO) + ros2-dev shell
dev Start dev environment (headless PX4 + ros2-dev shell)
dev-watch Start dev + pytest-watch auto-rerun
dev-debug Start debug environment with Gazebo GUI (requires GPU + xhost)
qgc Launch QGroundControl (auto-downloads on first run)
test Run unit + contract + integration tests (no PX4)
test-smoke Run single SIL smoke test (nominal_survey scenario)
test-sitl Run all SIL scenario tests
test-all Run all tests (unit + all SIL scenarios)
clean Stop all containers, remove volumes
help Show available commands