What this is: A debugging story about an indoor simulated drone that would spin up its motors, wait, and then quietly refuse to arm — and how we tracked the failure down to one disabled GPS switch.
Why it’s here: “It won’t arm” is one of the most common and most confusing problems in drone simulation. This entry walks through the full reasoning chain so you can recognize the same symptoms and avoid the dead ends we explored.
Date: 2026-06-14 Ticket: indoor SITL GPS-arm fix + re-takeoff
Glossary
If you are new to the vocabulary, read this first. The rest of the article assumes these terms.
- Arming. Telling the flight controller “you are allowed to spin the propellers for real.” Before arming, the drone is inert. A drone that “won’t arm” is like a car that turns the key but refuses to start because a safety interlock is unhappy.
- Prearm check. A safety checklist the flight controller runs before it agrees to arm. If any item fails, arming is blocked and the controller usually tells you which item failed. Think of it as the pre-flight walkaround a pilot does — except the software refuses to budge until every box is ticked.
- GPS-denied / indoor flying. GPS signals barely penetrate buildings, so an indoor drone often has no reliable satellite fix. Our test world is an indoor stand, so GPS is a recurring headache.
- GPS fix. A usable position estimate from satellites. “No fix” means the receiver cannot tell where it is. A “fix” with enough satellites means it can.
- Re-takeoff. Taking off, landing, and then taking off again in the same session — without restarting the whole stack. Sounds trivial, but a latch left in the wrong state can silently block the second takeoff.
- EKF (Extended Kalman Filter). The math engine inside the flight controller that fuses GPS, accelerometers, and gyros into one consistent estimate of “where am I and which way am I pointing.” When its inputs disagree badly, it complains.
- SITL (Software In The Loop). Running the real flight-controller firmware as a program on a PC instead of on a physical board. The same code, no hardware.
- EEPROM. A small non-volatile memory where the flight controller stores its parameters. In simulation it is just a file (
eeprom.bin) — and, as you will see, it can quietly override the parameter file you thought was authoritative.
1. The symptom: motors arm-attempt, then give up
Manual takeoff in the indoor world base_stand_12x12 simply did not work. This was first reported through the interface, and then reproduced on a clean reboot of the D2 machine, so it was not a one-off glitch.
The sequence looked like this:
- The manual-fly node sends a takeoff command.
- The mode switches to
GUIDED(the mode where the flight controller accepts position/altitude commands from our software). - The EKF is given 8 seconds to settle.
- Arming fails — the drone reports
armed=False. - The node retries three times, then exits with an error.
Throughout, the drone just sat there at z=0.21 — propellers idle, never leaving the stand.
2. Root cause analysis
The first useful clue came from the flight controller’s own status text:
PreArm: GPS 1: Bad fix
That is the prearm check telling us exactly why it refused: it does not believe the GPS is healthy.
Confirming it from the data side:
/mavros/global_position/raw/fixreportedstatus=-1(NO_FIX).- ArduPilot requires a usable GPS fix to arm in
GUIDEDmode, so the prearm check blocked us.
So the real question became: why is there no GPS fix at all in a simulated world?
2.1 The disabled switch
The simulated GPS backend was switched off at the moment the stack started up — the parameter SIM_GPS1_ENABLE was 0. No simulated satellites means no fix means no arming.
There was also a naming trap. On the newer ArduPilot 4.8-dev build, several older parameter names were renamed, and the old names are now silently ignored. We verified this directly: trying to set the old-style arming-check parameter at runtime returned a “no such parameter” failure, while setting SIM_GPS1_ENABLE succeeded. So any configuration that still referenced the old names was a no-op — it looked applied but did nothing.
2.2 Why the indoor parameter file didn’t help
The indoor parameter file (indoor.parm) did contain the right setting. So why was the GPS still off?
Because the SITL launcher started the simulation without the “wipe” flag. Without that flag, the previous parameter values persist in eeprom.bin across restarts — and that stale SIM_GPS1_ENABLE=0 from a prior run overrode the value in the parameter file. The file said “GPS on,” the saved memory said “GPS off,” and the saved memory won.
This is a classic simulation footgun: you edit the config, restart, nothing changes, and you slowly lose your mind. The parameter file is only the source of truth if the saved memory is wiped first.
2.3 Two dead ends we ruled out
Honesty about what didn’t work is part of the story:
- Skipping the arming check. Setting the “skip arming checks” parameter (both in the file and at runtime) does not remove the GPS requirement here. It looked like a tempting shortcut, but it is a dead end — the GPS gate stayed up regardless.
- Turning GPS on at runtime instead of at boot. Enabling
SIM_GPS1_ENABLE=1after startup did restore the fix (status=0, fix type 6, 10 satellites). But it then produced a new arming refusal: the GPS and the attitude estimate disagreed by a wildly large distance. The reason is timing — the EKF had already chosen its reference origin without GPS, and then GPS suddenly appeared at a different location. The two estimates were now hundreds of kilometers apart on paper.
The lesson from that second dead end is the key insight: GPS must be enabled at boot, not bolted on afterward, so the EKF builds its origin with GPS present from the very first moment.
3. The fix
Four small changes, each closing one part of the trap:
config/ardupilot/indoor.parm— setSIM_GPS1_ENABLE 1so the simulated GPS is on from boot.help_scripts/launch.sh— start the SITL launcher with the wipe flag. This clears the saved memory on every start, making the parameter file the genuine source of truth and removing the eeprom-override footgun for good.policy_bridge/manual_fly_node.py— after landing, reset the internal “takeoff done” latch back toFalse. This lets the node accept a fresh takeoff command, enabling takeoff/land cycles N times in a row instead of just once.policy_bridge/sitl_comm.py— updated a stale comment in the landing routine. Re-takeoff after landing does work as long as the GPS fix stays alive.
A note on point 3: the landing call already flips the “airborne” flag back to false, so the next episode start triggers a clean, full takeoff. The only missing piece was clearing the one-shot latch that had been blocking the second command.
4. Verification on the stand
All of the following were checked on the D2 machine, in the base_stand_12x12 world, with GPS status=0 from boot.
| Case | Result |
|---|---|
| Takeoff to 1.0 m (arm) | armed=true at ~6 s, climb to 1.0 m, stable hover |
| Altitude change 1.0 → 1.8 → 0.8 m | holds each target precisely |
| Land → disarm | descends to 0.21, disarms |
| Re-takeoff (raw mavros) | re-arms and runs NAV_TAKEOFF to 1.0 m |
| Two full cycles takeoff/altitude/land (manual_fly) | both cycles completed end to end |
Video track of the first cycle is archived in the simulation media store as sim_20260614_230916.mp4.
5. Closing notes
- An older note recorded that re-takeoff after landing failed — but that observation was made under a GPS NO_FIX condition. With a healthy GPS fix in place, repeated takeoff is now stable. Same code path, different outcome, because the underlying GPS state was different.
- The “skip arming checks” parameter is still present in the indoor parameter file. It is harmless, but to be clear: it was not what solved this. The real fix was enabling the simulated GPS at boot and wiping stale saved parameters.
The broader takeaway for anyone debugging simulated arming: when the controller says “bad GPS fix,” resist the urge to disable the check. Instead, ask why the GPS is unhealthy — and remember that saved parameter memory can quietly outrank the config file you just edited.