Question about handling command ACK ambiguity

19 views
Skip to first unread message

Mathieu David

unread,
Jun 23, 2025, 11:37:25 AMJun 23
to MAVLink

Hello MAVLink Developers,

I'm hoping to get some clarification on a design aspect of the MAVLink command protocol and more specifically with a possible limitation with how ACKs work.

It looks like there's a potential ambiguity when a GCS sends the same command ID multiple times in rapid succession. The COMMAND_ACK message includes the command ID it's acknowledging, but it can’'t specify which instance of that command it responds to.

For example, consider this scenario (this is just a made up example):

  1. A GCS sends a MAV_CMD_DO_SET_MODE command to switch the vehicle to "Position" mode.

  2. Immediately after, the GCS sends another MAV_CMD_DO_SET_MODE command to switch the vehicle to "Manual" mode.

  3. The vehicle responds with a single COMMAND_ACK for MAV_CMD_DO_SET_MODE because of some packet loss.

In this situation, the GCS cannot definitively know whether the acknowledgement is for the first command (Position mode) or the second command (Manual mode).

My questions for the community are:

  • How do GCS applications typically handle this?

  • Is it expected that the GCS never sends the same command ID before it either received an ACK or timed out?

  • Is there another mechanism within the MAVLink protocol that I might be overlooking which is intended to solve this?

Any insights or examples of how GCS projects handle this would be greatly appreciated.

Thank you for your time and help.

Best regards,

Mathieu


Steven Owens

unread,
Jun 23, 2025, 8:03:21 PMJun 23
to mav...@googlegroups.com
I'm curious about this also.

There are some work arounds that come to mind, for example for modes
check the heartbeat base_mode and custom_mode flags to see what mode
the autopilot is currently in:

https://mavlink.io/en/messages/common.html#HEARTBEAT

Similarly to your MAV_CMD_DO_SET_MODE question, when I use
COMMAND_LONG to send MAV_CMD_COMPONENT_ARM_DISARM, I get a COMMAND_ACK
which identifies that I sent command 400 enum value, which is
MAV_CMD_COMPONENT_ARM_DISARM, but as far as I can find there's nothing
in the ACK that tells me what the acknowledged command set ARM/DISARM
to, i.e. was that ACK'ing a request to ARM or a request to DISARM?

This is what I get when I pass the pymavlink COMMAND_ACK object I
receive back from ArduPilot when I send COMMAND_LONG for arming:

COMMAND_ACK {command : 400, result : 4, progress : 0, result_param2 :
0, target_system : 255, target_component : 0}

And this is a python rich inspection dump of the command_ack_message
object, which doesn't show any other fields.

╭─ <class 'pymavlink.dialects.v20.ardupilotmega.MAVLink_command_ack_message'> ─╮
│ Report status of a command. Includes feedback whether the command

│ was executed. The command microservice is documented at

https://mavlink.io/en/services/command.html



│ ╭──────────────────────────────────────────────────────────────────────────╮ │
│ │ <pymavlink.dialects.v20.ardupilotmega.MAVLink_command_ack_message
object │ │
│ │ at 0x7e300f329730>
│ │
│ ╰──────────────────────────────────────────────────────────────────────────╯ │


│ array_lengths = [0, 0, 0, 0, 0, 0]

│ command = 410

│ crc_extra = 143

│ fielddisplays_by_name = {}

│ fieldenums_by_name = {'command': 'MAV_CMD', 'result':
'MAV_RESULT'} │
│ fieldnames = [

│ 'command',

│ 'result',

│ 'progress',

│ 'result_param2',

│ 'target_system',

│ 'target_component'

│ ]

│ fieldtypes = [

│ 'uint16_t',

│ 'uint8_t',

│ 'uint8_t',

│ 'int32_t',

│ 'uint8_t',

│ 'uint8_t'

│ ]

│ fieldunits_by_name = {}

│ id = 77

│ instance_field = None

│ instance_offset = -1

│ lengths = [1, 1, 1, 1, 1, 1]

│ msgname = 'COMMAND_ACK'

│ name = AttributeError("Class
MAVLink_command_ack_message │
│ has no attribute 'name'")

│ native_format = bytearray(b'<HBBiBB')

│ ordered_fieldnames = [

│ 'command',

│ 'result',

│ 'progress',

│ 'result_param2',

│ 'target_system',

│ 'target_component'

│ ]

│ orders = [0, 1, 2, 3, 4, 5]

│ progress = 0

│ result = 4

│ result_param2 = 0

│ target_component = 230

│ target_system = 255

│ unpacker = <_struct.Struct object at 0x7e300d1b03b0>

╰──────────────────────────────────────────────────────────────────────────────╯
> --
> Sie erhalten diese Nachricht, weil Sie in Google Groups E-Mails von der Gruppe "MAVLink" abonniert haben.
> Wenn Sie sich von dieser Gruppe abmelden und keine E-Mails mehr von dieser Gruppe erhalten möchten, senden Sie eine E-Mail an mavlink+u...@googlegroups.com.
> Diese Diskussion finden Sie unter https://groups.google.com/d/msgid/mavlink/b68f0e96-c116-435e-b93b-09d09b8132e7n%40googlegroups.com.



--
Steven J. Owens
steven...@gmail.com
pu...@darksleep.com
412-401-8060

Hamish Willee

unread,
Jun 24, 2025, 8:17:24 PMJun 24
to MAVLink
Hi  Mathieu, Steven

I've included an answer from Gemini AI at the end. I wouldn't normally do this, but I was curious and it is remarkably hallucination free IMO :-).

The summary is that you're not missing anything. There isn't any way to reliably differentiate multiple commands from the same source with the same ID
- An autopilot should handle one command from one source with the same ID at a time, and if it gets another it should reject that command with https://mavlink.io/en/messages/common.html#MAV_RESULT_TEMPORARILY_REJECTED
- A GCS should check the response and wait if the command is rejected.
- Commands are designed to be idempotent, so if the autopilot does get a stream of them it can execute them ignoring previous state.
- An autopilot MAY also generally reject a command with a sequence id on the channel that is older than its last one. That's to stop us from handling old messages that might have come through a different channel.
- Despite what it says below it is not common for a GCS to check the mode change unless it has to. It never has to when using the COMMAND protocol, but some autopilots only support the older set mode message, so there is no other way to know about the success state.

Does that answer sufficiently?

Regards
Hamish



-----
GEMINI ANSWER

You've hit upon a valid and important point regarding the MAVLink command protocol's COMMAND_ACK message. You are correct that it lacks a direct mechanism (like a unique transaction ID or sequence number specific to the command instance) to disambiguate acknowledgements for multiple, identical commands sent in rapid succession.

Let's break down your questions and common practices:

How do GCS applications typically handle this?

GCS applications typically employ a combination of strategies to mitigate this ambiguity, primarily focusing on state management and timeouts:

  1. Strict Command Sequencing and State Machines:

    • The most common approach is for the GCS to operate with a strict state machine. A command is sent, and the GCS enters a "waiting for ACK" state for that specific command. It does not send another command of the same type until it receives an ACK or the command times out.

    • In your example, the GCS would send MAV_CMD_DO_SET_MODE for "Position" mode. It would then wait for the COMMAND_ACK. Only upon receiving that ACK (or timing out and deciding to resend or declare failure) would it then send the MAV_CMD_DO_SET_MODE for "Manual" mode.

    • This "one command at a time" for critical operations greatly reduces the ambiguity.

  2. Idempotency:

    • MAVLink encourages commands to be designed as idempotent where possible. This means that executing the same command multiple times has the same effect as executing it once. For example, commanding MAV_CMD_NAV_TAKEOFF when the vehicle is already taking off or flying should ideally result in an MAV_RESULT_ACCEPTED ACK, essentially indicating "already done" or "no change needed."

    • While helpful, this doesn't fully solve the acknowledgement ambiguity itself, but it makes the consequences of receiving a "stale" ACK less problematic.

  3. Application-Level Correlation (Less Common for Standard Commands):

    • For very specific, complex, or custom command flows, a GCS could implement its own application-level correlation. This might involve sending additional custom MAVLink messages (if allowed by the autopilot) that include a unique transaction ID alongside the command, and then expecting a corresponding ACK that also carries that ID. However, this is not part of the standard COMMAND_ACK protocol and would require custom autopilot firmware support.

  4. Observation of Vehicle State (Post-Command Verification):

    • For commands like MAV_CMD_DO_SET_MODE, the GCS doesn't solely rely on the COMMAND_ACK. It also monitors the vehicle's state messages (e.g., HEARTBEAT which contains the current mode).

    • After sending MAV_CMD_DO_SET_MODE for "Position," the GCS expects to receive the ACK and then observe the vehicle's mode changing to "Position" in subsequent HEARTBEAT messages. If the ACK comes, but the mode doesn't change as expected, the GCS knows something went wrong. This provides an additional layer of verification.

Is it expected that the GCS never sends the same command ID before it either received an ACK or timed out?

Yes, this is generally the expected behavior for critical, state-changing commands. For commands where an explicit COMMAND_ACK is expected (which is most MAV_CMDs), the GCS should implement a robust retry mechanism with a timeout.

If a COMMAND_ACK is not received within a defined timeout period, the GCS should:

  • Resend the original command: This is the standard MAVLink command protocol behavior.

  • Increment a retry counter: To avoid infinite retries.

  • Eventually declare failure: If the command continues to time out after several retries.

Sending the same command ID (like MAV_CMD_DO_SET_MODE) with different parameters (Position vs. Manual) before the first one is acknowledged would indeed lead to the ambiguity you described. This is why a sequential, "one-at-a-time" approach for command issuance is critical.

Is there another mechanism within the MAVLink protocol that I might be overlooking which is intended to solve this?

You're not overlooking a standard mechanism within the COMMAND_ACK message itself for distinguishing multiple instances of the same command ID. The COMMAND_ACK message primarily indicates the result of a command with that ID.

While the COMMAND_LONG and COMMAND_INT messages (which encapsulate the MAV_CMD) have a sequence number, this sequence number is part of the packet header and is primarily for detecting lost or out-of-order packets at the transport layer, not for uniquely identifying individual command transactions at the application layer when the same MAV_CMD is sent multiple times. The COMMAND_ACK message itself does not echo this sequence number from the original packet.

In summary, the MAVLink design for commands relies more on the GCS managing its command queue and state carefully, rather than a built-in transaction ID within the COMMAND_ACK payload. The idempotency principle and post-command state verification are also crucial for robust GCS implementations.

For long-running commands, the COMMAND_ACK can report MAV_RESULT_IN_PROGRESS and progress (0-100%). The MAVLink guide states, "Only one instance of a particular long running command can execute at a time; to restart a long running operation (i.e. with new parameters) it must first be cancelled! If the same command is recieved while the operation is in progress the new command should be ACKed with MAV_RESULT_TEMPORARILY_REJECTED (to indicate that the target is busy)." This explicitly addresses the issue for long-running commands by effectively enforcing a single instance. For instantaneous commands, the same principle of "only accept one at a time, or immediately ACK if already in that state" is implicitly followed by well-behaved autopilots.

So, your instincts are correct. The solution lies primarily in the GCS's logic and adherence to the implicit understanding of how commands should be issued and acknowledged in MAVLink, rather than a dedicated field for transaction ID in COMMAND_ACK.

Steven Owens

unread,
Jul 14, 2025, 6:15:57 PMJul 14
to MAVLink
Sorry to resurrect this thread after so long, but I was re-reading your post and something caught my eye.

You say:

> it is not common for a GCS to check the mode change unless it has to. It never has to when using
> the COMMAND protocol,

Can you clarify what you mean here about "it never has to"? I had thought the COMMAND "protocol" 
meant that this is a protocol my script should implement... by checking for the ACK to make sure 
that it was confirmed, based on reading this:

https://mavlink.io/en/services/command.html

But you seem to be implying otherwise.

As far as why I'm looking for an ACK, besides the COMMAND protocol page linked above, I'm 
assuming I can't count on the message getting through, do to the various sorts of radio messages
that might crop up.

> but some autopilots only support the older set mode message

Does this mean I should use COMMAND_LONG or COMMAND_INT instead of MAV_CMD_DO_SET_MODE?

Re: timeout, what's an appropriate timeout value to use when  using the pymavlink client library?

Steve

Steven Owens

unread,
Jul 30, 2025, 10:51:32 AMJul 30
to MAVLink
Dammit autocorrect:

> As far as why I'm looking for an ACK, besides the COMMAND protocol page linked above, I'm 
> assuming I can't count on the message getting through, do to the various sorts of radio messages
> that might crop up.

That last should have read "due to the various sorts of radio ISSUES that might crop up".   I.e. interference, range issues, etc.

Although, come to think of it, most of my own code is running on a companion computer, so those issues are unlikely, to say the least.

Steve

Hamish Willee

unread,
Aug 7, 2025, 1:38:00 AMAug 7
to MAVLink
I thought I replied to this.


" > it is not common for a GCS to check the mode change unless it has to. I "

There is no ambiguity. It never has to recheck with the command because it can see the ACK. But if you use the SET_MODE message it does have to because there is no ACK.


>> but some autopilots only support the older set mode message
>Does this mean I should use COMMAND_LONG or COMMAND_INT instead of MAV_CMD_DO_SET_MODE?

No. Commands are specially formatted enum values that are packaged in a message. That message is either COMMAND_LONG or COMMAND_INT. If you are sending MAV_CMD_DO_SET_MODE you ARE packaging in COMMAND_LONG or COMMAND_INT.

The "older SET_MODE" message is a message. You send it it set the mode, and that's it. But you get no feedback, so you to check from the heartbeat or whatever that it changed.

> Re: timeout, what's an appropriate timeout value to use when  using the pymavlink client library?

For what? Usually you'd expect an ACK to return in a second or so. With command protocol the resend would usually happen after that.
Reply all
Reply to author
Forward
0 new messages