

PX4 SITL in Gazebo, flown over MAVROS
Four terminal panes, one short flight, the offboard gotcha that blocks arming until setpoints flow, and why the code survives the move to hardware
Overview#
This is step one of a longer project I am working on with my teammates for our bachelor thesis: build a drone, starting in simulation, then moving to human-in-the-loop flying, and finally to the real thing. Before you can crash a drone you built yourself, you should crash it in software first. Software in the loop (SITL) lets you do exactly that, for free, as many times as you want.
The video below is the whole stack running on my machine: the PX4 flight stack simulated in Gazebo, flown in offboard mode through MAVROS. It is a short flight, and the interesting part is how many moving pieces it takes to get a fake drone to do that.
Offboard control of a simulated iris quadcopter: arm, take off, fly, land, all from ROS.
The stack#
Four pieces are running at once, each with its own terminal pane in the video:
| Piece | What it is | Why it is here |
|---|---|---|
| Gazebo | 3D physics simulator | Renders the world and simulates physics for the iris quadcopter model that ships with PX4 |
| PX4 (SITL) | The flight stack, compiled for software in the loop | The real autopilot code, but fed by simulated sensors instead of a real IMU and GPS |
| ROS + MAVROS | The ROS middleware and its MAVLink bridge | Lets ROS nodes talk to the autopilot: read state, change modes, send setpoints |
| mavros_controllers | Offboard controller nodes | The setpoint generators that fly the drone |
I run my own forks of both big pieces: PX4-Autopilot ↗ (upstream is PX4/PX4-Autopilot ↗) and mavros_controllers ↗, forked from Jaeyoung-Lim’s version ↗. Same code for now, but the forks are where our own changes will land.
MAVLink and MAVROS in drones#
MAVLink is the little binary protocol that almost every autopilot speaks. It is a stream of numbered messages: heartbeats saying “I am alive”, status messages for armed/disarmed and flight mode, telemetry like position, attitude and battery, command messages like “arm” or “return to launch”, and setpoint messages that say “be at this position, with this velocity”.
MAVROS is the bridge between that world and ROS. It is a ROS node that turns each MAVLink message into a topic, and each command into a service. So /mavros/state shows armed and mode, /mavros/local_position/pose streams the estimated position, /mavros/setpoint_position/local accepts position targets, and /mavros/cmd/arming arms the motors. On a real drone, MAVROS runs on a companion computer riding along, wired to the flight controller over serial. In simulation the exact same node is used; the only difference is the link, which is a UDP port instead of a wire:
roslaunch mavros px4.launch fcu_url:="udp://:14540@localhost:14557"shMAVROS sends its link to port 14557, where the SITL instance listens for companion connections, and binds port 14540, the standard SDK port PX4 streams its telemetry back to. Because the protocol and the node are identical between simulation and hardware, everything written against MAVROS keeps working when the drone becomes real, which is the whole point of practicing on a UDP link first.
The walkthrough#
With all four terminals open, the demo goes like this.
First, bring up the simulated autopilot and its world:
make px4_sitl gazeboshPX4 boots inside the terminal, runs its startup scripts, starts the state estimator and opens Gazebo with the iris quadcopter sitting on the ground. Nothing is flying yet; this is a complete autopilot, just with imaginary sensors.
Second, connect MAVROS with the fcu_url above. The console fills with link and parameter traffic, and from here the autopilot is reachable as ordinary ROS topics.
Third, check the state and take over:
rostopic echo /mavros/state
rosrun mavros mavsys mode -c OFFBOARD
rosrun mavros mavsafety armshFrom that moment the flight stack stops waiting for a pilot. The offboard node publishes position targets, MAVROS translates them into MAVLink setpoints, PX4 turns them into motor commands, and Gazebo moves the model. The video shows the quad climb off the ground, hold and shift position under the controller’s targets, then land again with a land command.
Finally, rqt_graph draws the picture of who is talking to whom: the controller node feeding /mavros/setpoint_position/local, MAVROS in the middle, and everything flowing out through the single UDP link.
Team clips: hardware on the bench#
While the simulation side ran on laptops, the hardware side of the thesis was already waking up on a bench. We kept a couple of short clips, in Vietnamese, as the team’s memory of those first bench days.
The first one is the motor test: four Emax 2300 kV motors controlled through BLHeli 30A ESCs, powered through a Matek Mini PowerHub power distribution board. Before any of them meet a frame or a flight controller, each one gets spun on the bench to check it spins the right way, responds to throttle, and does not try to eat anything.
Motors on the bench: Emax 2300 kV, BLHeli 30A ESCs, Matek Mini PowerHub.
The second is less glamorous and more important: charging the 4S pack straight from a 220V wall socket with a charger, instead of whatever improvised arrangement we would have invented otherwise. A battery is the one part of a drone you do not want to improvise with.
Charging the 4S pack from the wall, the boring and correct way.
What is next#
This is the boring-looking first rung of the thesis ladder: prove the brain works before the body exists. Next up in the series is autonomous indoor mapping without GPS, where this same stack learns to see, and after that the human-in-the-loop flying and the design work for the drone that has to survive contact with the air.