Skip to content

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:

sudo usermod -a -G dialout $USER   # log out/in once for the group change

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

make sim

This single command does everything:

  1. Brings up PX4 SITL + ROS2 containers in Docker
  2. Launches QGroundControl on the host (auto-downloaded on first run, cached in ~/.local/share/bennu/)
  3. Kicks off the nominal_survey mission in the background — the drone takes off, flies a lawnmower grid, takes photos, and returns home
  4. Drops you into an interactive ros2-dev shell

Watch the drone fly in QGroundControl, or tail the mission log from the shell:

tail -f /tmp/mission.log

To run a different scenario, set the SCENARIO variable:

make sim SCENARIO=my_scenario.yaml

When done, exit the shell and run make clean to stop containers.

Headless Mode

make dev

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

make qgc

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:

make dev-watch

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:

sudo bash sim/setup_nvidia_docker.sh

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:

  1. X11 access --- run xhost +local:docker on the host
  2. NVIDIA drivers --- run nvidia-smi on the host to confirm drivers are loaded
  3. Container GPU access --- run docker exec bennu-px4-sitl-debug nvidia-smi to verify the GPU is visible inside the container
  4. CDI spec --- run nvidia-ctk cdi generate --output=/etc/cdi/nvidia.yaml if 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

  1. Click the Takeoff button in the bottom-left action bar (Fly view).
  2. Confirm the takeoff altitude (default ~2.5 m) and slide to confirm.
  3. 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

  1. Switch to the Plan tab (top toolbar).
  2. Click on the map to add waypoints. Each waypoint gets a default altitude you can adjust in the sidebar.
  3. Click Upload (top-right) to send the mission to PX4.
  4. 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:=true switches uxrce_dds_agent from serial (/dev/ttyAMA0, real Pi 4 UART) to UDP on port 8888, where PX4 SITL listens. It also flips the camera node to the placeholder backend, since libcamera isn't available in the dev image.
  • output_dir:=/tmp/captures overrides 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:

docker exec -it bennu-ros2-dev bash
ros2 topic list | grep fmu

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:

docker exec -it bennu-px4-sitl-dev bash -c '/opt/px4/build/px4_sitl_default/bin/px4-mavlink-shell'

(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

make clean

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