Mission Planning¶
"Mission planning" in ArduPilot usually means clicking waypoints onto a map. This project does not do that: the target position is unknown in advance and there is no GPS indoors, so nothing map-based can work. Instead, the mission is a program — a state machine on the Raspberry Pi companion computer that flies the aircraft in GUIDED mode over MAVLink. This page explains both approaches and why we use the second.
The classic way: GCS waypoint missions — and why it does not fit¶
With Mission Planner or QGroundControl you place waypoints on a map, upload them to the flight controller, and switch to AUTO: the autopilot flies the list on its own, finishing with RTL or LAND. It is the right tool outdoors — but every element of it assumes GPS:
- Waypoints are lat/lon coordinates. Without a GPS fix the vehicle has no absolute position, so a lat/lon list is meaningless indoors.
- The target must be known when you plan. Our delivery pad's position is not known — finding it is part of the mission.
- RTL, the standard mission ending, needs GPS and first climbs to
RTL_ALT— indoors, into the ceiling.
A static waypoint list also cannot react: "search until the camera sees the pad, then centre over it" is a feedback loop, not a route.
Our way: a companion computer flying GUIDED¶
The Pi Zero 2 runs the Pi-Code state machine, connected to the FC on SERIAL4
(MAVLink2, 921600 baud; mavlink-router forwards the stream to UDP 127.0.0.1:14550).
The autopilot keeps doing what it is good at — stabilisation, position control, its
own failsafes — while the companion streams position targets and reacts to what the
camera sees:
stateDiagram-v2
[*] --> IDLE
IDLE --> TAKEOFF: GUIDED + armed
IDLE --> ABORT: pre-arm / arm fails
TAKEOFF --> SEARCH
SEARCH --> APPROACH: target detected
SEARCH --> ABORT: pattern exhausted
APPROACH --> DROP: centred over target
APPROACH --> SEARCH: target lost too long
DROP --> RECOVER
ABORT --> RECOVER: airborne
ABORT --> [*]: never armed / pilot took over
RECOVER --> [*]: landed + disarmed
| State | What happens |
|---|---|
IDLE |
verify the FC parameters read-only (they are owned by Mission Planner — see Flight Parameters), pre-arm sensor checks, GUIDED, arm |
TAKEOFF |
climb to search altitude (2 m) and verify the rangefinder tracks it |
SEARCH |
fly a pattern, polling the camera at each waypoint |
APPROACH |
visual servoing: nudge toward the detected pad until centred |
DROP |
open the payload servo (Pi GPIO), confirm |
RECOVER |
land in place and disarm — never RTL indoors |
A failsafe monitor (link loss, battery, phase timeouts, position plausibility) runs
before every state and can jump the machine to ABORT. If the pilot flips the mode
switch, the companion commands nothing further — the human always outranks it.
Navigating without GPS: local NED and a synthetic origin¶
Guided navigation indoors uses the local NED frame: waypoints are "metres north / east of the launch point", served by the optical-flow position estimate (see Position & Altitude Hold). Two pieces make it work:
SET_GPS_GLOBAL_ORIGIN— with no GPS, nothing sets the EKF's origin, and without an origin there is no local frame or home position. The companion sends the hall's real-world coordinate itself before arming (the real coordinate matters: the magnetic declination the EKF assumes comes from it).- Search patterns in local NED — an expanding spiral (default) or a
lawnmower sweep, generated as a waypoint list by
search.pyand flown withstop_and_lookcadence (pause at each point, poll the camera) orcontinuouspolling. The final approach uses body-frame offsets ("0.3 m forward, 0.2 m right") computed from the camera's pixel offsets.
Staged bring-up: milestones 1–6¶
The full mission has four unknowns at once — position hold, detector, search pattern, release. Each milestone adds exactly one, so a failure names its own cause:
| Milestone | Flies | Adds | Pass condition |
|---|---|---|---|
| 1 | arm, hold armed on the ground, disarm | arm/disarm verification | log line [ARM_TEST] PASS: automatic arm and disarm both confirmed |
| 2 | climb to 0.8 m, hold, land | position hold | drift of centimetres, not metres |
| 3 | hover over the pad, detector logging only | the detector | correct dx/dy sign |
| 4 | the search pattern, no detector | the pattern | pattern completed, ends TARGET_NOT_FOUND — that abort is the pass |
| 5 | search + detect + centre, no drop | the approach | centred, "releasing NOTHING" |
| 6 | the full delivery | the release | drop confirmed |
Milestone 2 can start from the pilot's hands (--takeover) instead of the ground —
see the on-ground deadlock.
Rehearsed in SITL first¶
Every mission and every milestone runs against the ArduPilot SITL simulator before
it goes anywhere near hardware — same code, one flag (python main.py --sim; without
the flag the real-aircraft profile runs, deliberately, so a forgotten flag fails on
the safe side). SITL is configured to mimic the indoor sensor suite (simulated optical
flow + rangefinder, GPS off, origin set by the companion), so the entire
search → approach → drop sequence has been flown green in simulation on the same
ArduCopter 4.6.3 the aircraft runs. The full simulation→hardware transition is
documented in the Pi-Code repo (docs/SIM_TO_REAL.md).
Status
The mission logic, failsafes and SITL rehearsals of all six milestones are done and green in simulation. None of the milestones has flown on the real aircraft yet — the drone is grounded pending the post-crash barometer/I2C repair (incident report), and sensor/camera mounts are still to be built.