Add support for MAV_CMD_REQUEST_OPERATOR_CONTROL#33332
Conversation
Task #9 — takeover notification: when a takeover request is rejected (allow_takeover=0), broadcast MAV_CMD_REQUEST_OPERATOR_CONTROL on all active channels to notify the current owner so they can release or grant permission. Task #10 — heartbeat disconnect releases control: rate-limited check (1 Hz) in GCS::update_receive() releases operator control when the operator's heartbeat has been absent for GCS_OPERATOR_HEARTBEAT_TIMEOUT_MS (5 s). Uses the global sysid_mygcs_last_seen_time_ms() timestamp which is updated by any GCS in the operator range, so control is only released when ALL owners in a range disconnect simultaneously. Task #11 — gcs_secondary in CONTROL_STATUS: track secondary connected GCS sysids (sysids in the operator range that are not gcs_main) via heartbeats in a small per-GCS array. Populate gcs_secondary[] in CONTROL_STATUS from recently-seen secondaries so multi-owner range mode is correctly reflected. Autotest additions: notification forwarded to owner on takeover reject, heartbeat disconnect releases control after timeout, gcs_secondary populated when range operator is active. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ation send Restrict RC_CHANNELS_OVERRIDE and MANUAL_CONTROL to the primary operator (gcs_main) only. Secondaries can send state-changing MAVLink commands but not manual stick input, matching the spec: "only one GCS owner can control manual input of the vehicle". Fix operator control takeover notification: the original direct call to mavlink_msg_command_long_send() was silently dropped when the TX buffer was momentarily full (comm_send_lock sets chan_discard=true). Route the notification through the queued send_message()/try_send_message() path (MSG_OPERATOR_CONTROL_NOTIFICATION) so it goes out as soon as TX space is available, like all other deferred messages. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Georacer
left a comment
There was a problem hiding this comment.
This one's looking good, Peter! Thank you for your work!
I've left a couple of comments. I'll try to test it with MAVProxy next.
| if (now_ms - _operator_control_last_hb_check_ms >= 1000) { | ||
| _operator_control_last_hb_check_ms = now_ms; | ||
| if (_operator_control_sysid != 0) { | ||
| const uint32_t last_seen = sysid_mygcs_last_seen_time_ms(); | ||
| if (last_seen != 0 && | ||
| now_ms - last_seen > GCS_OPERATOR_HEARTBEAT_TIMEOUT_MS) { | ||
| set_operator_control(0, 0, false); |
There was a problem hiding this comment.
I read here:
"Every 1s, check _operator_control_sysid. If it's not 0 and I have ever seen any of my GCSs, then release the control.
That doesn't sound right. Am I reading this wrong?
I guess the intention is that if the controlling GCS goes away for more than 1s, then the operator control will be released?
And the sysid_is_gcs() logic will fall back to sysid ranges.
There was a problem hiding this comment.
You're right to be wary here, we flagged the same thing. The check keys on "any GCS in the range seen recently", so a secondary's heartbeats can mask the primary disconnecting, and a primary that's configured but never connects is never timed out (with takeover off, nobody can then take control). I think the fix is to track the primary's own last-seen separately from the range, so the timeout reflects the controller specifically. Happy to put that together if you and Peter agree on the direction.
| flags |= GCS_CONTROL_STATUS_FLAGS_TAKEOVER_ALLOWED; | ||
| } | ||
| uint8_t gcs_secondary[10] {}; | ||
| gcs().get_secondary_gcs(gcs_secondary); |
There was a problem hiding this comment.
The MAVLink spec part "It should only include IDs for connected GCS" must have been a major pain.
We're essentially maintaining this secondaries list just to report it, IIUC.
There was a problem hiding this comment.
Agreed it's a real cost. Now that the operator configures the secondary set GCS-side and it rides in the request range, ArduPilot already knows the authorized set without separately tracking who's been "seen", which is the only thing that bookkeeping buys is the spec's "only include connected GCS" wording. If that relaxed to "authorized range", AP could just report the range (the catch: gcs_secondary[10] caps at 10 while a range can be wider).
|
I gave this a test today, using the modified MAVProxy. I went through the 3 scenarios outlined in mavlink/mavlink#2158. They all seem to work... most of the time. Here are 3 successful .tlogs: I haven't actually verified that both GCSs have been logged in there. And some of the time I would get this. It would switch control and then immediately release it: Here are two failed .tlogs with that issue: |
|
Hi @peterbarker @Georacer, I've been testing this against my QGC side mavlink/qgroundcontrol#14560 (multi-GCS SITL, 4 × QGC + MAVProxy to relay traffic) and the core request / grant / takeover handshake seems solid end-to-end. I needed a few fixes on the ArduPilot side, opened as a PR against Peter's branch (peterbarker#46). The fixes: 1. Separate 2. Secondary GCS bypassed the takeover check ( 3. Notification lifecycle ( 4. Restrict release to the primary ( 5. Clamp request timeout ( Autotest ( Design questions (not addressed):
QGC already ACKs the takeover notification (COMMAND_ACK/ACCEPTED), so the full handshake completes against this branch with fix 3. |
|
@Georacer The "switch and immediately release" bug you hit might be fixed by the notification lifecycle fix (peterbarker@55117b5) Also, if you have cycles, would love your take on the open design questions from my comment above, especially the heartbeat timeout tracking the whole range instead of the primary specifically. That one's the most likely to bite us in real multi-GCS setups. @hamishwillee also said his thoughts on it in the last part of his comment ArduPilot/mavlink#503 (comment). |

Summary
Adds support for development.xml messages (still under review) to pass control of the system from one GCS to another (or several)
Classification & Testing (check all that apply and add your own)
Description
Much discussion in various places, most notably in the mavlink PR.
There's a MAVProxy PR adding support
QGC has support in the codebase but (a) it is not enabled by default and (b) the message set that QGC compiles against by default has an older version of this message.