MISSION LOG

MISSION LOG

RECORD OPEN

ATILIM UAV · GROUND CONTROL STATION

MISSION LOG / DETAIL

Archive

lıVE · READ MODE

Software

SITL + Gazebo Simulation: Testing Before You Fly

SITL + Gazebo Simulation: Testing Before You Fly

FIG. 01

RECORD DATA

Category

Software

TARİH

TÜM KAYITLAR

Here is how a field test day goes: you are up at 7 AM, you drive two hours, and if you remembered to charge the battery packs, setup takes 40 minutes. Then, on the very first AUTO mission, instead of flying to the second waypoint the aircraft switches to RTL (Return To Launch). The reason? The altitude reference in the mission file is wrong. Working that out costs you 20 minutes; fixing it and trying again costs another battery.

What all of these failures have in common is that none of them needed a flight to find. Mission planning logic, mode transitions, failsafe thresholds, parameter sets — every one of them can be caught at a desk. Clearing them out beforehand leaves the field day for the things that genuinely have to be flown.

In this post we cover ArduPilot SITL, how we tie it together with Gazebo, and which tests we run at the desk. At the end comes the part that matters just as much: what simulation cannot test.

What is SITL, and how is it different from HITL?

SITL (Software In The Loop) is the ArduPilot flight software built for a PC with an ordinary C++ compiler — a native executable that lets you test how the code behaves with no hardware at all. Sensor data comes from a flight dynamics model instead of real sensors; everything else in the code is exactly what runs in a real flight.

HITL (Hardware In The Loop) uses the real flight controller: the normal firmware runs on the Pixhawk and sensor data is fed in from the simulator over a cable. More realistic in theory, but on the ArduPilot side this path is no longer the recommended one in practice.



SITL

HITL

What runs

ArduPilot compiled for a PC

Firmware on the actual FC

Hardware required

No

Yes (FC + cable)

Time scaling

Can be sped up with SIM_SPEEDUP

Real time only

Debugger, static analysis

Available

Difficult

ArduPilot support

Active, the primary path

Via X-Plane and FlightGear, Plane only; the documentation marks it as archived and no longer supported

When to pick it

Almost always

When you suspect an FC-specific driver or hardware timing problem — and even for that, ArduPilot now points you at the "Simulation on Hardware" path instead

Short version: start with SITL. That is what we did.

Installing and running SITL

Setup is the same as setting up a normal ArduPilot development environment. You clone the repository with its submodules and run the prerequisites script:

git clone --recurse-submodules https://github.com/ArduPilot/ardupilot
cd ardupilot
Tools/environment_install/install-prereqs-ubuntu.sh -y

git clone --recurse-submodules https://github.com/ArduPilot/ardupilot
cd ardupilot
Tools/environment_install/install-prereqs-ubuntu.sh -y

Note: this script does not work on Ubuntu releases that are past end of standard support (20.04 LTS, for example). Use a current LTS.

Building and running:

./waf configure --board sitl
./waf copter

# Tools/autotest/sim_vehicle.py must be on your PATH
sim_vehicle.py -v ArduCopter -f quad --console --map -w
./waf configure --board sitl
./waf copter

# Tools/autotest/sim_vehicle.py must be on your PATH
sim_vehicle.py -v ArduCopter -f quad --console --map -w

The flags here matter:

Flag

What it does

-v ArduCopter

Vehicle type

-f quad

Frame type

--console

MAVProxy status console

--map

Map window

-w

Wipes the virtual EEPROM and starts from default parameters

-L <name>

Start location, taken from Tools/autotest/locations.txt

--no-mavproxy

Starts without MAVProxy (for when you are attaching your own client)

Use the -w flag only on the first run, or when you actually want to reset parameters; pass it every time and everything you set up is gone.

Bringing Gazebo into the loop

SITL's own built-in physics model is enough for most mission logic testing. We add Gazebo when we want to see the environment visually and stream video off a camera.

The ardupilot_gazebo plugin is what bridges the two. As of 2026 the plugin supports Gazebo Garden, Harmonic (LTS), Ionic and Jetty (LTS); the repository recommends Harmonic.

git clone https://github.com/ArduPilot/ardupilot_gazebo
cd ardupilot_gazebo
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=RelWithDebInfo
make -j4
git clone https://github.com/ArduPilot/ardupilot_gazebo
cd ardupilot_gazebo
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=RelWithDebInfo
make -j4

Then two environment variables:

export GZ_SIM_SYSTEM_PLUGIN_PATH=$HOME/ardupilot_gazebo/build:$GZ_SIM_SYSTEM_PLUGIN_PATH
export GZ_SIM_RESOURCE_PATH=$HOME/ardupilot_gazebo/models:$HOME/ardupilot_gazebo/worlds:$GZ_SIM_RESOURCE_PATH
export GZ_SIM_SYSTEM_PLUGIN_PATH=$HOME/ardupilot_gazebo/build:$GZ_SIM_SYSTEM_PLUGIN_PATH
export GZ_SIM_RESOURCE_PATH=$HOME/ardupilot_gazebo/models:$HOME/ardupilot_gazebo/worlds:$GZ_SIM_RESOURCE_PATH

And then, in two terminals:

# Terminal 1
gz sim -v4 -r iris_runway.sdf

# Terminal 2
sim_vehicle.py -v ArduCopter -f gazebo-iris --model JSON --map --console
# Terminal 1
gz sim -v4 -r iris_runway.sdf

# Terminal 2
sim_vehicle.py -v ArduCopter -f gazebo-iris --model JSON --map --console

The critical detail is --model JSON: data exchange between SITL and Gazebo runs over the JSON interface, and the frame name has to start with gazebo-. The plugin's gimbal camera streams video over GStreamer to UDP 127.0.0.1:5600 by default — that became our stand-in test source for the RTSP stream we get from the SIYI A8 mini.

The connection layer: which port goes where

SITL maps its serial ports onto TCP ports: serial 0–3 become 5760–5763 respectively. The default for a ground control station connection is 5760. When sim_vehicle.py also launches MAVProxy, MAVProxy takes that connection and fans it out to local UDP ports.

Client

Connection

Notes

Mission Planner

TCP 127.0.0.1:5760

If MAVProxy is not running, connect straight to this port

Mission Planner (with MAVProxy running)

UDP 14550

One of MAVProxy's default local outputs

pymavlink script

UDP 14551

The second local output; does not collide with the GCS

Remote machine

output add <ip>:14550 in MAVProxy

A second station over the network

A typical takeoff-and-mission sequence from the MAVProxy console:

mode guided
arm throttle
takeoff 40
wp load Tools/autotest/Generic_Missions/CMAC-circuit.txt
mode auto
wp set 2

mode guided
arm throttle
takeoff 40
wp load Tools/autotest/Generic_Missions/CMAC-circuit.txt
mode auto
wp set 2

On the pymavlink side, the connection pattern is plain:

from pymavlink import mavutil

conn = mavutil.mavlink_connection('udpin:localhost:14551')
conn.wait_heartbeat()
print("Heartbeat: system %u, component %u" %
      (conn.target_system, conn.target_component))

msg = conn.recv_match(type='GLOBAL_POSITION_INT', blocking=True)
from pymavlink import mavutil

conn = mavutil.mavlink_connection('udpin:localhost:14551')
conn.wait_heartbeat()
print("Heartbeat: system %u, component %u" %
      (conn.target_system, conn.target_component))

msg = conn.recv_match(type='GLOBAL_POSITION_INT', blocking=True)

On our side, this script is the same code that feeds FastMosaic's telemetry input. In other words, the only difference between the code that talks to SITL and the code that talks to the real aircraft is the connection string.

What we test

Scenario

How to trigger it

What it tells us

Waypoint order and altitude reference

wp load ... + mode auto

Whether the mission file is right, and whether the aircraft follows the expected sequence

RC loss failsafe

param set SIM_RC_FAIL 1 (total loss), 2 (neutral channels)

Whether the failsafe action is correct, and how long it takes to fire

GPS loss

param set SIM_GPS_DISABLE 1 — the parameter name can change between releases, so check with param show SIM_GPS* first

EKF behavior, mode fallback

Wind

param set SIM_WIND_SPD 10 and SIM_WIND_DIR 180

Position hold error, mission duration

Sensor noise

param set SIM_ACC_RND 3 (m/s²)

Filter behavior on a vibration-prone aircraft

PID tuning

Change the gain parameters and repeat the same mission

A rough sense of which way stability is trending

Time acceleration

param set SIM_SPEEDUP 5

5x real time; for burning through long missions quickly

One caveat on SIM_SPEEDUP: the documentation notes that at high speedup values MAVProxy may not keep up with the network traffic, and that you can see errors like "Set RC override timeout" or a GCS failsafe. The fix is simple — lower the multiplier.

What simulation CANNOT test

Do not skip this section; over-trusting SITL costs you at least as much as never using it.

  • Detection performance. The Gazebo camera produces synthetic imagery. Our mAP50 of 0.950 on the aerial-only validation set came from real flight frames; a score measured on synthetic imagery means nothing in the field. Model validation is done with real data.

  • Companion computer load. The 15-20 FPS inference rate on the Jetson Orin NX, thermal throttling and RTSP latency cannot be measured on a desktop.

  • Vibration, EMI, real sensor imperfections. SIM_ACC_RND adds noise, but it knows nothing about your airframe's resonant frequency.

  • Mechanics. The release servo, the gimbal mount, cable strain — none of it exists in simulation. All that simulation reproduces of the autonomous airdrop test we ran from ~20 m in the field is the question "did the servo command go out at the right moment".

  • Radio range and real link quality. There is no packet loss on localhost.

Common pitfalls

  • Forgetting to carry the parameters over to the field. The good failsafe setting you found in SITL does not migrate to the real aircraft on its own. Keep the parameter file under version control.

  • -w on every single run. The virtual EEPROM gets wiped and everything you set up in the previous session is gone.

  • The wrong frame name. With Gazebo, the frame name has to start with gazebo- and you have to pass --model JSON; otherwise SITL flies with its own internal model, the vehicle in Gazebo never moves, and you spend a long time hunting for why nothing works.

  • Not making the environment variables persistent. GZ_SIM_SYSTEM_PLUGIN_PATH and GZ_SIM_RESOURCE_PATH disappear in a new terminal. Write them into .bashrc.

  • Port collisions. Trying to connect both Mission Planner and your own script to 14550 on the same machine. Move the second one to 14551.

  • Counting a test that passed in simulation as "verified". Passing in SITL does not mean you are ready to fly; it means you are ready to go out to the field.

The practical summary

  1. Plain SITL first, Gazebo second. Most mission logic, mode transition and failsafe testing does not need Gazebo; do not take on the setup overhead for nothing.

  2. Change nothing but the connection string. If your ground software connects to SITL with udpin:localhost:14551, it should connect to the real aircraft with that same code. Two code paths that drift apart will produce surprises in the field.

  3. Write the failsafe scenarios down as a checklist. Repeat the SIM_RC_FAIL, GPS-disabled and wind tests after every firmware update — it is a five-minute job.

  4. Manage the parameter set from a single source. The values you tune in SITL and then fly in the field should come out of the same file.

  5. Keep the limits of simulation in writing. The "this test cannot be done in SITL" list is the document that actually decides where your field day time goes.

©2026 ATILIM UAV TEAM

©2026 ATILIM UAV TEAM

Create a free website with Framer, the website builder loved by startups, designers and agencies.