Skip to content

Multi GCS update#14560

Open
Davidsastresas wants to merge 23 commits into
masterfrom
pr-multi-gcs-June2026
Open

Multi GCS update#14560
Davidsastresas wants to merge 23 commits into
masterfrom
pr-multi-gcs-June2026

Conversation

@Davidsastresas

@Davidsastresas Davidsastresas commented Jun 18, 2026

Copy link
Copy Markdown
Member

What this does

Brings QGC's GCS operator-control support up to the final MAV_CMD_REQUEST_OPERATOR_CONTROL / CONTROL_STATUS protocol (mavlink/mavlink#2313), succeeding the earlier work in #12410.

Long story short - Now multiple GCS can share a vehicle with one primary holding exclusive manual control; others are recognized secondaries and can send commands, missions, etc but not manual control. Adds the operator-control panel, request/release flow, takeover handshake, and a configurable secondary-GCS range.

Tested against peterbarker/ardupilot#46 in multi-GCS SITL (4× QGC + MAVProxy for routing).

Changes

Protocol / Vehicle

  • Send the requester's actual GCS sysid in param4, with an optional param4/5 range from the configured secondaries (GCS Operator control updates mavlink#2313 changed param4 from earlier implementation).
  • Parse full CONTROL_STATUS (gcs_main + gcs_secondary[10]) and expose the secondary list.
  • Gate MANUAL_CONTROL / RC override so joystick is sent only when this GCS is in control (or the vehicle is uncontrolled).
  • ACK the forwarded takeover notification (COMMAND_ACK/ACCEPTED) so the handshake completes.
  • Address the command to the system-manager component learned from CONTROL_STATUS, not assumed to be the autopilot.

UI (GCSControlIndicator)

  • Role-aware toolbar indicator: stacked aircraft / link / role glyph (solid device = primary, outlined = secondary, open/closed lock = no role) with a MAIN/SEC label; green is reserved for the control relationship.
  • Operator-control panel as a status roster (Control status, Main GCS, Secondary GCS with this GCS marked, Takeover), with request / take / change / release actions and editable settings behind a "More options" expander and a "control group" toggle.
  • "Take full control" when granted immediately (uncontrolled or takeover allowed, no countdown) vs "Request full control" otherwise.
  • Request countdown lifecycle fixed (stops on grant / release / uncontrolled; no stale restart on reopen).
  • A recognized secondary shows its role even when the vehicle is uncontrolled (gcs_main == 0).
  • Configurable secondary-GCS range, with a warning when the contiguous range would also accept unconfigured ids.

Settings

  • New persisted operatorControlSecondaryGCS (secondary sysids, comma- or space-separated).

EDIT - Pending items after discussions in ArduPilot/ardupilot#33332 and ArduPilot/mavlink#503

Some screenshots, Artwork and UI by @alexdelatorre

Top toolbar Icon, depending on status:

Screenshot from 2026-06-18 20-32-57 Screenshot from 2026-06-18 20-33-18

reduced popup, after single click, depending on status:

Screenshot from 2026-06-18 20-36-12 Screenshot from 2026-06-18 20-35-58

Command request from a GCS to the main GCS:

Screenshot from 2026-06-18 20-37-40

expanded panel with aditional options:

Screenshot from 2026-06-18 20-38-10 Screenshot from 2026-06-18 20-38-17

Secondary GCS input, detail of warning for user awareness:

Screenshot from 2026-06-18 20-38-27

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates QGroundControl’s multi-GCS operator-control support to align with the finalized MAV_CMD_REQUEST_OPERATOR_CONTROL / CONTROL_STATUS protocol by moving control-status handling into a dedicated manager, gating manual-control outputs, and expanding the toolbar UI and settings needed for multi-operator workflows.

Changes:

  • Introduces GCSControlManager to own CONTROL_STATUS parsing, operator-control request/release flow, and role state exposed to QML.
  • Gates joystick/RC override output in Vehicle based on operator-control ownership.
  • Refreshes the toolbar indicator/panel UI, adds a persisted “secondary GCS IDs” setting, and updates bundled indicator artwork and MAVLink dependency pin.

Reviewed changes

Copilot reviewed 11 out of 17 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/Vehicle/Vehicle.h Exposes gcsControlManager to QML and adds an atomic gate setter for joystick output.
src/Vehicle/Vehicle.cc Instantiates/routs control messages to GCSControlManager and gates joystick send paths.
src/Vehicle/GCSControlManager.h New QObject/QML-facing manager API and properties for operator-control state and actions.
src/Vehicle/GCSControlManager.cc Implements CONTROL_STATUS parsing, operator-control request/release, and takeover ACK handshake.
src/Vehicle/CMakeLists.txt Adds new manager sources to the build.
src/Toolbar/GCSControlIndicator.qml Reworks indicator UI for MAIN/SEC role display, roster, actions, and expanded settings UI.
src/Toolbar/CMakeLists.txt Updates toolbar resource list to the new multi-GCS indicator SVGs.
src/Settings/FlyViewSettings.h Adds operatorControlSecondaryGCS SettingFact.
src/Settings/FlyViewSettings.cc Registers operatorControlSecondaryGCS SettingFact.
src/Settings/FlyView.SettingsGroup.json Defines persisted operatorControlSecondaryGCS string setting metadata.
resources/gcscontrolIndicator/multigcs_lock_open.svg New lock-open role glyph artwork.
resources/gcscontrolIndicator/multigcs_lock_closed.svg New lock-closed role glyph artwork.
resources/gcscontrolIndicator/multigcs_line.svg New “control relationship” line artwork.
resources/gcscontrolIndicator/multigcs_device.svg New primary-role device glyph artwork.
resources/gcscontrolIndicator/multigcs_device_alt.svg New secondary-role outlined device glyph artwork.
resources/gcscontrolIndicator/multigcs_aircraft.svg New aircraft glyph artwork.
cmake/CustomOptions.cmake Updates the pinned MAVLink git tag to a commit containing the finalized protocol.

Comment thread src/Vehicle/Vehicle.cc Outdated
Comment thread src/Vehicle/GCSControlManager.cc Outdated
@Davidsastresas
Davidsastresas removed the request for review from HTRamsey June 18, 2026 18:54
Davidsastresas and others added 16 commits June 18, 2026 21:04
- Send the actual GCS sysid in param4 of MAV_CMD_REQUEST_OPERATOR_CONTROL
  (spec changed from #2158 to #2313; autopilot rejects param4=0), with
  optional param4/param5 range computed from configured secondary GCS ids
- Gate MANUAL_CONTROL and RC override sends on operator control: block
  joystick output while another GCS is in control
- Parse full CONTROL_STATUS (gcs_main + gcs_secondary[10]) and expose
  secondary GCS list to QML
- Add release control support and secondary GCS range configuration UI
  to GCSControlIndicator
- New persisted setting operatorControlSecondaryGCS (comma-separated
  secondary GCS sysids)
The request countdown could keep running (or restart from a stale
value) after the request was already resolved:

- The stop check in _handleControlStatus() missed the uncontrolled
  case: when the owning GCS releases control, CONTROL_STATUS arrives
  with gcs_main=0 and takeover not allowed, so neither stop condition
  matched. Add _sysid_in_control == 0 to the check.
- The QML progress tracker only stopped on the takeover-allowed
  transition. Add watchers so it also stops when this GCS gains
  control or when the C++ side re-allows sending requests.
- requestOperatorControlRemainingMsecs was declared CONSTANT but wraps
  QTimer::remainingTime(), so reopening the popup restored the
  countdown from a stale cached value. Tie it to
  sendControlRequestAllowedChanged instead.
- releaseOperatorControl() left the request and takeover-revert timers
  running, firing stale signals after release. Stop them first.
- A pending takeover revert also kept running when control moved to
  another GCS. Stop it on control loss in _handleControlStatus().
When takeover is not allowed, the autopilot forwards
MAV_CMD_REQUEST_OPERATOR_CONTROL to the GCS in control as a
notification. QGC showed the popup but never sent COMMAND_ACK, leaving
the autopilot notification pending and the command unacknowledged at
the MAVLink level. Reply with MAV_RESULT_ACCEPTED to the sender.
…onent

The spec says a GCS should monitor for CONTROL_STATUS with the
GCS_CONTROL_STATUS_FLAGS_SYSTEM_MANAGER flag and address
MAV_CMD_REQUEST_OPERATOR_CONTROL to that component, which is not
necessarily the autopilot. Learn the component id from CONTROL_STATUS
and use it as the command target, falling back to the default
component id until the first CONTROL_STATUS is seen.
_joystickSendAllowed was read and written with memory_order_relaxed.
On weakly ordered architectures the store could in principle be
observed by the joystick thread after other effects of the
CONTROL_STATUS handling. Use release on the store and acquire on the
loads so the gate change is properly ordered with the control status
update that caused it.
The operator control request encodes secondary GCS as a contiguous
sysid range, so non-contiguous configured ids (e.g. 200, 254) silently
grant control to every id in between. Show a warning in the range
configuration panel with the number of unconfigured ids the computed
range would accept.
Replace the line/device/gcs icon set with a state-driven composition
using new artwork (aircraft, line, solid/outlined device, open/closed
lock):

- aircraft (top-left): green when any GCS controls the vehicle
- line (bottom-left): green only when this GCS has an operator role
  (its own control link)
- role glyph (right), always white: solid device = primary,
  outlined device = secondary, lock when this GCS has no role -
  open when control is acquirable now (uncontrolled or takeover
  allowed), closed when a request is required
- PRIM/SEC label shown in green when this GCS is primary/secondary

The lock open/closed condition reuses controlGrantedImmediately, so it
always agrees with the request button (Take Control / Acquire / Send
Request).

> Co-authored-by: alexdelatorre <alex.delatorre@lincesystems.com>
Replace the flat label list with a labelled status roster (Control
status / Main GCS / Secondary GCS / Takeover), mark this GCS in each
row, and move editable settings behind a More options expander with
a control group toggle. Recognize a secondary's role even when the
vehicle is uncontrolled (gcs_main == 0).
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@Davidsastresas
Davidsastresas force-pushed the pr-multi-gcs-June2026 branch from 817d6ee to 47d0f04 Compare June 18, 2026 19:30
@DonLakeFlyer DonLakeFlyer added this to the Release V5.2 milestone Jul 6, 2026
Secondary/owner membership is the flight stack's configuration
(MAV_GCS_SYSID/_HI on ArduPilot), not something a control request may
redefine: CONTROL_STATUS specifies gcs_secondary as set by the flight
stack, and MAV_CMD_REQUEST_OPERATOR_CONTROL.param5 has been removed
from the spec (mavlink/mavlink#2535).

Remove the secondary-GCS-IDs setting and its settings-panel UI, and
the range computation derived from it. A control request now only
asks for the gcs_main role: param4 carries this GCS's system id and
param5 is always 0. Secondaries shown in the indicator continue to
come from CONTROL_STATUS, which is now their only source.
…vehicle notify

Address the operator-control review findings:

- A request denied by the vehicle (requester not an authorized owner)
  was only logged while the UI kept a "Request sent" countdown and the
  request button stayed locked out.  Cancel the countdown and show an
  app message on MAV_RESULT_DENIED and other non-pending rejections;
  keep the countdown only for FAILED/IN_PROGRESS (owner notified,
  request genuinely pending).

- Per spec, CONTROL_STATUS without GCS_CONTROL_STATUS_FLAGS_SYSTEM_MANAGER
  describes only the emitting component; stop applying it to
  system-level control state.

- A takeover request arriving on a non-active vehicle was ACKed and
  silently dropped (the popup is bound to the active vehicle).  Show an
  app-level message directing the operator to that vehicle.

Also: ignore forwarded releases (param1 != 1) instead of raising a
takeover popup; use the sanitized timeout for the local request
lockout; fix the received-request timeout fallback which mixed seconds
into a milliseconds property (a param3=0 request would auto-close its
popup in ~10 ms); refresh stale param5/comment references and drop an
unused QML property binding.
@Davidsastresas

Davidsastresas commented Jul 15, 2026

Copy link
Copy Markdown
Member Author

The protocol model evolved after discussions in ArduPilot/ardupilot#33332 and mavlink/mavlink#2535, and the branch has been updated accordingly. The main change from the original description:

Owner/secondary membership is flight-stack configuration. QGC no longer configures the secondary GCS set — the "configurable secondary-GCS range" and its operatorControlSecondaryGCS setting described above are gone. A GCS only ever asks for control (param5 was removed from the spec) and displays the membership reported in CONTROL_STATUS.

Also since the description was written:

  • Rejections are surfaced: DENIED (not an authorized operator) and other non-pending results cancel the request countdown and show a message; FAILED (owner notified) keeps it running. The lockout lifts only when CONTROL_STATUS confirms the new state — the ack alone is not authoritative.
  • CONTROL_STATUS is only applied as system state when it carries GCS_CONTROL_STATUS_FLAGS_SYSTEM_MANAGER; the manager component is learned from it and a compid change is followed and warned about.
  • No repeated consent prompt while a takeover window this GCS just granted is still open; forwarded releases no longer raise a takeover popup; in multi-vehicle sessions a request arriving on a non-active vehicle shows an app-level message instead of being dropped.
  • New unit-test suite: GCSControlManagerTest (19 cases) covering the manager end-to-end against MockLink — CONTROL_STATUS handling, SYSTEM_MANAGER gating, joystick gating, the full request/lockout lifecycle, duplicate requests, the takeover-revert timer, and forwarded request/release handling.
  • MAVLink pin bumped past development(update): Remove MAV_CMD_REQUEST_OPERATOR_CONTROL.param5 (… mavlink#2535 (param5 removal).

Retested against peterbarker/ardupilot#46 in multi-GCS SITL (4× QGC + MAVProxy).

Executables for Linux
https://github.com/mavlink/qgroundcontrol/actions/runs/29431294699

Exectuables for Windows
https://github.com/mavlink/qgroundcontrol/actions/runs/29431292787

Updated screenshots of the panel with the new display-only membership:

Compacted and extended popups now
Screenshot from 2026-07-15 17-55-32

Screenshot from 2026-07-15 17-55-19

If multi owner mode, shown from a GCS out of the range trying to request control

Screenshot from 2026-07-15 17-51-51

@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Build results unavailable — artifact download from one or more platform workflows failed (likely artifact retention expiry or transient API error). The combined report cannot be generated for this run.

See the Build Results workflow run for details.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants