I’ve been reviewing ArduPilot’s MAVLink command handling code (Copter/Plane/Rover, Copter v4.6.3 reference) and noticed several commands that accept a mission upload with no error, then at execution time produce behavior that doesn’t match what the operator asked for. Parameters get dropped during mission parsing, or the mission executor silently throws away a failure return from a downstream module.
I’m posting these together rather than filing individual issues since the root causes are clustered around mission upload/execution path divergence. I’d like to get your take on which ones are worth fixing vs. intentional.
Seven commands affected, three failure patterns:
A. Parser stores the command but drops specific fields
A1. MAV_CMD_DO_FENCE_ENABLE — param2 fence mask discarded in mission path
The direct command handler at GCS_Fence.cpp:19-33 reads param2 as a bitmask of fence types to enable, letting the operator selectively toggle altitude fence, polygon fence, or circle fence:
// GCS_Fence.cpp:19-33 (direct command handler)
const uint8_t fences = (uint8_t)packet.param2;
if (fences) {
fence->enable(false, fences); // selective: only the masked fence types
}
The mission parser at AP_Mission.cpp:1306-1308 stores only param1:
// AP_Mission.cpp:1306-1308 (mission upload)
cmd.p1 = packet.param1;
// param2 never stored
At execution, AP_Mission_Commands.cpp:360-366 calls enable_configured(), which unconditionally toggles all configured fence types:
// AP_Mission_Commands.cpp:360-366
fence.enable_configured(true); // all fences, not just the ones in param2
An operator who uploads a mission item to selectively disable only the altitude fence while keeping the polygon fence active instead disables all fences. The operator has no way to express selective fence toggling in a mission.
B. Protocol encoding mismatch — same field, different meaning by ingress path
B1. MAV_CMD_DO_SET_HOME — param1=0 means “use current location” in direct path, but (0,0,0) in mission path
The direct command handler at GCS_Common.cpp:5511 treats zero lat/lon as “use the vehicle’s current position”:
// GCS_Common.cpp:5511 (direct command)
if (is_equal(packet.param1, 1.0f) || (packet.x == 0 && packet.y == 0)) {
// special case: use current location
}
But the mission executors in Plane (commands_logic.cpp:1005-1016) and Rover (mode_auto.cpp:998-1009) have no such fallback. A mission-embedded DO_SET_HOME with param1=0 and placeholder coordinates applies (0, 0, 0) as the new home position. All subsequent RTL and home-relative navigation targets the equator.
B2. MAV_CMD_DO_SET_HOME — mission path never locks home, letting auto-updates overwrite it
The direct command path passes lock=true to prevent the home position from being changed by subsequent automatic updates:
// GCS_Common.cpp:5513,5526 (direct command)
set_home(new_home_loc, true); // lock=true
Copter, Rover, and Sub mission executors pass lock=false:
// Copter/mode_auto.cpp:2006,2010, Rover/mode_auto.cpp:1001,1005,
// Sub/commands_logic.cpp:628,632 — all lock=false
set_home(cmd.content.location, false); // lock=false
Plane’s mission executor (commands_logic.cpp:1005-1016) uses different functions — set_home_persistently() or AP::ahrs().set_home() — neither of which calls lock_home(). The end result is the same: home is not locked after a mission DO_SET_HOME, while the direct command path locks it.
On vehicles like Rover that auto-update an unlocked home position from GPS estimates, a mission-set home can be silently overwritten mid-mission.
C. Silent no-op or discarded failure — command succeeds but work never happens
C1. MAV_CMD_DO_PAUSE_CONTINUE — mission accepts item but Copter never executes it
The mission parser stores DO_PAUSE_CONTINUE at upload (AP_Mission.cpp:1422-1424) and includes it in mission download (AP_Mission.cpp:1944-1946). But ArduCopter/mode_auto.cpp:699-835 — Copter’s mission command dispatch switch — has no case for MAV_CMD_DO_PAUSE_CONTINUE:
// ArduCopter/mode_auto.cpp:699-835 (start_command dispatch)
switch (cmd.id) {
case MAV_CMD_NAV_TAKEOFF: ...
case MAV_CMD_NAV_LAND: ...
// ... many cases ...
default:
return false; // DO_PAUSE_CONTINUE falls here
}
The direct command path at ArduCopter/GCS_MAVLink_Copter.cpp:487-488 correctly handles the pause. A mission-embedded pause step uploads cleanly, round-trips correctly, and is silently skipped during AUTO flight.
C2. MAV_CMD_DO_ENGINE_CONTROL — mission discards ICE return value, always advances
The direct command handler at GCS_MAVLink_Plane.cpp:764-768 checks the return value from the ICE controller:
// GCS_MAVLink_Plane.cpp:764-768 (direct command)
if (plane.g2.ice_control.engine_control(...)) {
return MAV_RESULT_ACCEPTED;
} else {
return MAV_RESULT_FAILED;
}
The mission path at commands_logic.cpp:186-191 calls the same function and discards the return value:
// ArduPlane/commands_logic.cpp:186-191 (mission executor)
plane.g2.ice_control.engine_control(
(int8_t)cmd.content.location.lat,
(int8_t)cmd.content.location.lng,
cmd.content.location.alt > 0);
// return value discarded
Then at line 322-323, the command is unconditionally marked complete:
// commands_logic.cpp:322-323
case MAV_CMD_DO_ENGINE_CONTROL:
return true; // always advances mission
For a fixed-wing ICE aircraft, a mission item that fails to start the engine silently advances to the next waypoint. The vehicle continues autonomous flight with the engine off.
C3. MAV_CMD_NAV_LOITER_TO_ALT — altitude conversion failure silently skips the altitude gate
When do_loiter_to_alt() at ArduCopter/mode_auto.cpp:1785-1789 fails to convert the altitude frame (e.g. terrain data unavailable), it sets both completion flags to true and returns immediately without entering the LOITER_TO_ALT submode:
// ArduCopter/mode_auto.cpp:1785-1789
if (!target_loc.get_alt_m(AltFrame::ABOVE_HOME, target_alt_cm)) {
loiter_to_alt.reached_destination_xy = true;
loiter_to_alt.reached_alt = true;
return;
}
The verifier at mode_auto.cpp:2213-2220 then sees both flags true and advances the mission:
// mode_auto.cpp:2213-2220
if (loiter_to_alt.reached_destination_xy && loiter_to_alt.reached_alt) {
return true; // mission advances
}
The loiter-to-altitude item silently skips its altitude gate. The vehicle proceeds to subsequent legs without reaching the commanded altitude, with no operator warning beyond a log message.
C4. MAV_CMD_NAV_LOITER_UNLIM — Plane returns ACCEPTED even when mode switch fails
The Plane handler at GCS_MAVLink_Plane.cpp:825-827 unconditionally returns MAV_RESULT_ACCEPTED:
// GCS_MAVLink_Plane.cpp:825-827
plane.set_mode(plane.mode_loiter, ModeReason::LOITER_COMMAND);
return MAV_RESULT_ACCEPTED; // unconditional — ignores set_mode return value
set_mode() can return false for several reasons: GCS mode-change blocking (FLTMODE_GCSBLOCK at system.cpp:301-305), fence recovery preventing a mode switch, or the new mode failing its own entry checks. Copter’s equivalent handler (GCS_MAVLink_Copter.cpp:524-528) checks the return value and reports failure. Plane does not. A GCS or companion computer relying on the ACK believes the vehicle is safely loitering when it continues in its previous mode.
Summary
| Type | What goes wrong | Commands |
|---|---|---|
| A | Parser drops fields | DO_FENCE_ENABLE |
| B | Protocol encoding mismatch | DO_SET_HOME (×2) |
| C | Silent no-op or discarded failure | DO_PAUSE_CONTINUE, DO_ENGINE_CONTROL, LOITER_TO_ALT, LOITER_UNLIM |
All findings are confirmed by source inspection at the cited locations against the Copter v4.6.3 reference. Most were also verified by SITL log analysis showing the described misbehavior.