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):
A GCS sends a MAV_CMD_DO_SET_MODE command to switch the vehicle to "Position" mode.
Immediately after, the GCS sends another MAV_CMD_DO_SET_MODE command to switch the vehicle to "Manual" mode.
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
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:
GCS applications typically employ a combination of strategies to mitigate this ambiguity, primarily focusing on state management and timeouts:
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.
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.
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.
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.
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.
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.