Refuse MAV_CMD_NAV_VTOL_TAKEOFF sent as COMMAND_LONG - #34420
peterbarker wants to merge 8 commits into
Conversation
d154a4f to
c7c63ab
Compare
Previous review (2026-09-16)Automated review note — AI-generated (Claude), validated against the live diff. Please sanity-check before acting. Reviewed at head Full report, including everything that was checked and found clean: https://uav.tridgell.net/DevCallReviews/2026_09_17_AIReview/devcall_pr_reviews.html#pr34420 Verdict: REQUEST CHANGES Draft. The mechanism, the layer and the test are all correct, and no real-world COMMAND_LONG sender was found for this command. The blocking concern is scope: the refusal is added to the vehicle-agnostic base class, but ISSUE — The refusal is global but the command is Copter-only, so other vehicles now return a misleading ACK. On quadplane SITL built from this head: ISSUE — The only surviving form is COMMAND_INT with frame ISSUE — The new subtest asserts only the refusal; nothing in the tree asserts the COMMAND_INT form still works. A regression breaking COMMAND_INT would pass CI. The full-tree grep for Lower-priority notes (4)
What was checked and found cleanBackward compatibility and test coverage both check out. |
mav_frame_for_command_long is only consulted for commands which store a location, and neither NAV_TAKEOFF nor NAV_VTOL_TAKEOFF is one of those
mav_frame_for_command_long is only consulted for commands which store a location, and NAV_TAKEOFF is not one of those
no vehicle overrides it any more
vehicles can override command_int_only() to have COMMAND_LONG forms of a command refused with MAV_RESULT_COMMAND_INT_ONLY
this command is now only accepted via COMMAND_INT
c7c63ab to
7aff1ca
Compare
| bool GCS_MAVLINK_Copter::mav_frame_for_command_long(MAV_FRAME &frame, MAV_CMD packet_command) const | ||
| bool GCS_MAVLINK_Copter::command_int_only(MAV_CMD command) const | ||
| { | ||
| if (packet_command == MAV_CMD_NAV_TAKEOFF || |
There was a problem hiding this comment.
The default frame is MAV_FRAME_GLOBAL_RELATIVE_ALT, so MAV_CMD_NAV_TAKEOFF still gets that as the frame to assume.
Previous review (head `7aff1cadc4`)Automated review note — AI-generated (Claude), validated against the live diff. Please sanity-check before acting. Full report: https://uav.tridgell.net/DevCallReviews/2026_09_17_AIReview/devcall_pr_reviews.html#pr34420 Re-reviewed at head The blocking finding from last round is fixed. Verdict moves REQUEST CHANGES → COMMENT. Previous round
Correction to my own previous commentI said last round that no GCS sends
So a user with a script, a MAVProxy alias, or a custom QGC action that issues this command will now get For the record, the built-in flows really are unaffected: QGC's only Still openISSUE — Smaller points
CI at this head: the one |
…_INT-only commands which carry a location but have no frame defined for COMMAND_LONG can only be handled via COMMAND_INT, so say so rather than claiming the command is unsupported
|
Automated review note — AI-generated (Claude), cross-checked by an independent Codex pass against the live diff. Please sanity-check before acting. Full report, including everything that was checked and found clean: https://uav.tridgell.net/DevCallReviews/2026_09_17_AIReview/devcall_pr_reviews.html#pr34420 COMMENTRe-reviewed at head The two new commits close the one finding that was still open from last round — the generic path and the new Still open
Smaller points
Dropped from my previous comment: the suggestion to also accept |
|
@stephendade on the RockBLOCK stuff |
ArduPilot is not the only stack which supports this command, and others do support lat/lng. Us restricting to int-only is an effort to make GCS writer's life easier. |
Summary
Copter now refuses
MAV_CMD_NAV_VTOL_TAKEOFFsent viaCOMMAND_LONGwithMAV_RESULT_COMMAND_INT_ONLY. The command is still accepted viaCOMMAND_INT. Positional commands that have no frame defined forCOMMAND_LONGnow also getMAV_RESULT_COMMAND_INT_ONLYinstead ofMAV_RESULT_UNSUPPORTED.Classification & Testing (check all that apply and add your own)
These autotests pass in SITL:
test.Copter.MAV_CMD_NAV_TAKEOFF:NAV_VTOL_TAKEOFFviaCOMMAND_LONGreturnsMAV_RESULT_COMMAND_INT_ONLY(8).test.Copter.MAV_CMD_NAV_TAKEOFF_command_int:NAV_VTOL_TAKEOFFviaCOMMAND_INTinMAV_FRAME_GLOBAL_RELATIVE_ALTis accepted, and the vehicle climbs to the requested altitude.test.QuadPlane.MAV_CMD_NAV_TAKEOFF:NAV_VTOL_TAKEOFFviaCOMMAND_LONGreturnsMAV_RESULT_UNSUPPORTED(3), because Plane doesn't handle this command at runtime.test.Copter.COMMAND_LONG_positional_command_int_only(new):DO_REPOSITION,EXTERNAL_POSITION_ESTIMATEandDO_SET_GLOBAL_ORIGINviaCOMMAND_LONGeach returnMAV_RESULT_COMMAND_INT_ONLY(8).test.Copter.MAV_CMD_DO_SET_GLOBAL_ORIGINandtest.Copter.DO_CHANGE_SPEED_in_guidedstill pass. Both send these commands viaCOMMAND_INT.Description
Copter refuses this command via
COMMAND_LONGbecause the MAVLink spec asks for it.common.xmlmarksMAV_CMD_NAV_VTOL_TAKEOFFhasLocation="true", and the command protocol encourages flight stacks "to only support positional commands inCOMMAND_INT". It also says to reject the other message type withMAV_RESULT_COMMAND_INT_ONLY.Copter's takeoff handler didn't give wrong results via
COMMAND_LONG. It ignores latitude/longitude, and it accepts onlyMAV_FRAME_GLOBAL_RELATIVE_ALT, which theCOMMAND_LONGconversion already supplies by default. This PR refuses the command to follow the spec, not to fix a precision or frame bug.Copter is the only vehicle that handles this command from a GCS, so the refusal is Copter-specific. Other vehicles still answer
MAV_RESULT_UNSUPPORTED.Who is affected: mavlink/mavlink#1981 found that no GCS's built-in takeoff flow sends this command. QGC's guided takeoff uses
MAV_CMD_NAV_TAKEOFF, and MAVSDK doesn't reference the command. Generic pass-through paths can still send it viaCOMMAND_LONG, for example MAVProxy'slong NAV_VTOL_TAKEOFF ...or a user-configured QGCMavlinkAction. Those users now getMAV_RESULT_COMMAND_INT_ONLY, which tells them exactly what to do. MAVProxy can send the command withcommand_int. We have found no evidence that anyone uses these paths for this command.Changes:
mav_frame_for_command_long()overrides, which were never called. The base class only calls that method for commands that store a location, and neither takeoff command does. Those commands get the defaultMAV_FRAME_GLOBAL_RELATIVE_ALTset intry_command_long_as_command_int(), so their behaviour is unchanged.mav_frame_for_command_long()non-virtual, since nothing overrides it now.command_int_only(MAV_CMD), which returns false by default. If a vehicle returns true for a command,try_command_long_as_command_int()refuses theCOMMAND_LONGform withMAV_RESULT_COMMAND_INT_ONLY.command_int_only()forMAV_CMD_NAV_VTOL_TAKEOFF.MAV_RESULT_COMMAND_INT_ONLYinstead ofMAV_RESULT_UNSUPPORTEDfor a positional command with noCOMMAND_LONGframe. That coversDO_REPOSITION,EXTERNAL_POSITION_ESTIMATEandDO_SET_GLOBAL_ORIGIN, which could never work viaCOMMAND_LONG.COMMAND_LONGhandling now gives one answer for every command it can't accept. It also matches builds withAP_MAVLINK_COMMAND_LONG_ENABLEDdisabled, which already answerCOMMAND_INT_ONLY.What
MAV_RESULT_COMMAND_INT_ONLYmeans here: it says which message type ArduPilot accepts for the command. It doesn't promise that the vehicle supports the command viaCOMMAND_INT.DO_REPOSITIONhandler. It answersCOMMAND_INT_ONLYviaCOMMAND_LONG, thenUNSUPPORTEDwhen the GCS retries viaCOMMAND_INT. The same can happen forEXTERNAL_POSITION_ESTIMATEon boards built withoutAP_AHRS_POSITION_RESET_ENABLED.AP_MAVLINK_COMMAND_LONG_ENABLEDdisabled already answerCOMMAND_INT_ONLYto everyCOMMAND_LONG, whether or not the vehicle supports the command. The devguide likewise describes these results as a way "to indicate the correct message type for a command". TheCOMMAND_INTreply then says whether the vehicle supports it.COMMAND_LONGanswer to each vehicle'sCOMMAND_INThandling would need a per-vehicle list for every positional command. That isn't worth saving one round trip.NAV_VTOL_TAKEOFFis different: the three positional commands can't work viaCOMMAND_LONGon any vehicle, because ArduPilot has noCOMMAND_LONGframe for them. So the generic answer is true everywhere.NAV_VTOL_TAKEOFFcould still work viaCOMMAND_LONG, because it gets the default frame. Refusing it is therefore a per-vehicle choice, made throughcommand_int_only().Frames: ArduPilot will not add support for
MAV_FRAME_GLOBAL_RELATIVE_ALT_INTor any otherMAV_FRAME_*_INTframe. MAVLink superseded them in 2024-03 in favour of the equivalent non-_INTframes.COMMAND_INTcarries scaled-integer coordinates whatever the frame, so the_INTvariants add nothing. Copter's takeoff handlers acceptMAV_FRAME_GLOBAL_RELATIVE_ALT, and a GCS moving toCOMMAND_INTmust use that frame. A GCS that sends a superseded frame should be fixed; ArduPilot will not accommodate it.Unchanged:
MAV_CMD_NAV_TAKEOFFviaCOMMAND_LONG.mavlink:block_command(). Scripting receives the command before these checks run.Not in this PR:
No text message with the refusal.
MAV_RESULT_COMMAND_INT_ONLYhas been incommon.xmlsince 2023-11 (mavlinkb5b1429e), and Mission Planner decodes it. AGCS_SEND_TEXTexplaining a spec-defined result code would cost flash in every build for little gain.RockBLOCK applet.
libraries/AP_Scripting/applets/RockBlock.luadoes its ownCOMMAND_LONGtoCOMMAND_INTconversion and callsgcs:run_command_int()directly, so this PR doesn't affect it. The applet has two existing bugs that stop takeoff working over RockBLOCK on Copter:int_frame, but the binding readsframe, so the frame is always 0.MAV_FRAME_GLOBAL) for takeoff anyway.Both will be fixed together in a separate PR.