Skip to content

feat(gimbal): support mount gimbal manager#27121

Open
minhleGremsy wants to merge 7 commits into
PX4:mainfrom
minhleGremsy:gremsy-solutions-feature-support-mount-gimbal-manager
Open

feat(gimbal): support mount gimbal manager#27121
minhleGremsy wants to merge 7 commits into
PX4:mainfrom
minhleGremsy:gremsy-solutions-feature-support-mount-gimbal-manager

Conversation

@minhleGremsy

Copy link
Copy Markdown

Problem

Gremsy's smart camera gimbals implement their own gimbal manager and communicate directly with the gimbal device. PX4-Autopilot, however, also currently operates as a gimbal manager. As a result, both PX4 and Gremsy system manage the same gimbal control path, creating a role conflict that can lead to command ownership issues, duplicated control logic, and inconsistent gimbal behavior.

Solution

To solve this conflict, we introduce an output path for external gimbal managers instead of treating the downstream Gremsy system like a normal gimbal device. In this mode, PX4 does not act as the final gimbal manager anymore. It only generates mount setpoints and forwards them to the external gimbal manager.

On the output side, a new OutputMavlinkToGimbalManager module discovers the external gimbal manager, checks control ownership, requests control when needed, and publishes external_gimbal_manager_set_attitude only after a valid manager is found and PX4 is allowed to send commands.

On the input side, we add gating to reject invalid configurations where it would still act as a full gimbal manager while MNT_MODE_OUT is set to MNT_MODE_OUT_TO_GIMBAL_MANAGER. Therefore, this output mode is only allowed with MNT_MODE_IN = AUTO or RC. In the AUTO case, InputMavlinkGimbalV2 switches to a mission-only mode. This keeps support for ROI and mission-driven pointing, but avoids handling downstream gimbal-manager ownership commands inside PX4.

Test Coverage

  • Configuration guard
    • Cover invalid combinations where MNT_MODE_OUT=MNT_MODE_OUT_TO_GIMBAL_MANAGER is used with MNT_MODE_IN=MAVLINK_ROI, MAVLINK_DO_MOUNT, or MAVLINK_V2.
    • Cover valid startup with MNT_MODE_IN=AUTO and MNT_MODE_IN=RC.
  • Mission-only input behavior
    • Cover AUTO + MNT_MODE_OUT_TO_GIMBAL_MANAGER to confirm InputMavlinkGimbalV2 runs in mission-only mode.
    • Verify direct ownership-changing input command DO_GIMBAL_MANAGER_CONFIGURE is ignored in mission-only mode.
  • External gimbal manager discovery and control handoff
    • Cover periodic request of GIMBAL_MANAGER_INFORMATION until an external manager is detected.
    • Verify PX4 stores manager sysid, compid, and gimbal_device_id.
    • Cover GIMBAL_MANAGER_STATUS updates for primary, secondary, and no-control cases.
    • Cover take-control request, ACK handling (ACCEPTED, IN_PROGRESS, rejected), retry limit, and 15s backoff behavior.

#ifndef EXTERNAL_GIMBAL_MANAGER_SET_ATTITUDE_HPP
#define EXTERNAL_GIMBAL_MANAGER_SET_ATTITUDE_HPP

#include <uORB/topics/external_gimbal_manager_set_attitude.h>

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.

[error] clang-diagnostic-error [error]
uORB/topics/external_gimbal_manager_set_attitude.h file not found

@minhleGremsy minhleGremsy changed the title Gremsy solutions feature support mount gimbal manager feat(gimbal): support mount gimbal manager Apr 17, 2026
@github-actions

github-actions Bot commented Apr 17, 2026

Copy link
Copy Markdown
Contributor

🔎 FLASH Analysis

px4_fmu-v5x [Total VM Diff: 3696 byte (0.18 %)]
    FILE SIZE        VM SIZE    
 --------------  -------------- 
  +0.2% +3.62Ki  +0.2% +3.62Ki    .text
   -97.5%    +660 -97.5%    +660    [50 Others]
    +0.3%    +516  +0.3%    +516    [section .text]
    [NEW]    +280  [NEW]    +280    gimbal::OutputMavlinkToGimbalManager::_acquire_control_for_autopilot()
    +1.1%    +228  +1.1%    +228    uORB::compressed_fields
    [NEW]    +188  [NEW]    +188    gimbal::OutputMavlinkToGimbalManager::update()
    [NEW]    +180  [NEW]    +180    gimbal::OutputMavlinkToGimbalManager::print_status()
    [NEW]    +176  [NEW]    +176    gimbal::OutputMavlinkToGimbalManager::_send_take_control_request()
     +51%    +168   +51%    +168    gimbal::InputMavlinkGimbalV2::update()
    [NEW]    +160  [NEW]    +160    gimbal::OutputMavlinkToGimbalManager::OutputMavlinkToGimbalManager()
    [NEW]    +152  [NEW]    +152    MavlinkStreamExternalGimbalManagerSetAttitude::send()
    [NEW]    +148  [NEW]    +148    gimbal::OutputMavlinkToGimbalManager::_publish_gimbal_manager_set_attitude()
    [NEW]    +140  [NEW]    +140    MavlinkReceiver::handle_message_external_gimbal_manager_information()
    [NEW]    +132  [NEW]    +132    gimbal::OutputMavlinkToGimbalManager::_check_for_gimbal_manager_status()
    [NEW]    +128  [NEW]    +128    MavlinkReceiver::handle_message_external_gimbal_manager_status()
    +0.1%    +120  +0.1%    +120    g_cromfs_image
    +1.7%     +96  +1.7%     +96    Mavlink::configure_streams_to_default()
    [NEW]     +84  [NEW]     +84    gimbal::OutputMavlinkToGimbalManager::_request_gimbal_manager_information()
    [NEW]     +78  [NEW]     +78    gimbal::OutputMavlinkToGimbalManager::~OutputMavlinkToGimbalManager()
    [NEW]     +70  [NEW]     +70    gimbal::OutputMavlinkToGimbalManager::_check_for_take_control_ack()
    [NEW]     +60  [NEW]     +60    CSWTCH.2009
    [DEL]     -60  [DEL]     -60    CSWTCH.1990
  +0.2% +3.27Ki  [ = ]       0    .debug_abbrev
  +0.2%    +320  [ = ]       0    .debug_aranges
  +0.2%    +972  [ = ]       0    .debug_frame
  +0.2% +55.7Ki  [ = ]       0    .debug_info
  +0.2% +7.30Ki  [ = ]       0    .debug_line
    +133%      +4  [ = ]       0    [Unmapped]
    +0.2% +7.30Ki  [ = ]       0    [section .debug_line]
  +0.1% +3.09Ki  [ = ]       0    .debug_loclists
  +0.1%    +417  [ = ]       0    .debug_rnglists
    [DEL]      -2  [ = ]       0    [Unmapped]
    +0.1%    +419  [ = ]       0    [section .debug_rnglists]
  +0.3% +9.04Ki  [ = ]       0    .debug_str
  -0.4%      -1  [ = ]       0    .shstrtab
  +0.3% +2.14Ki  [ = ]       0    .strtab
    [DEL]     -12  [ = ]       0    CSWTCH.1990
    [NEW]     +12  [ = ]       0    CSWTCH.2009
    [DEL]     -12  [ = ]       0    CSWTCH.2789
    [NEW]     +12  [ = ]       0    CSWTCH.2816
    [DEL]     -24  [ = ]       0    CSWTCH.3557
    [DEL]     -24  [ = ]       0    CSWTCH.3558
    [NEW]     +24  [ = ]       0    CSWTCH.3592
    [NEW]     +24  [ = ]       0    CSWTCH.3593
    [NEW]     +94  [ = ]       0    MavlinkReceiver::handle_message_external_gimbal_manager_information()
    [NEW]     +89  [ = ]       0    MavlinkReceiver::handle_message_external_gimbal_manager_status()
    [NEW]     +52  [ = ]       0    MavlinkStreamExternalGimbalManagerSetAttitude
    [NEW]     +60  [ = ]       0    MavlinkStreamExternalGimbalManagerSetAttitude::get_id()
    [NEW]     +63  [ = ]       0    MavlinkStreamExternalGimbalManagerSetAttitude::get_name()
    [NEW]     +62  [ = ]       0    MavlinkStreamExternalGimbalManagerSetAttitude::get_size()
    [NEW]     +75  [ = ]       0    MavlinkStreamExternalGimbalManagerSetAttitude::new_instance()
    [NEW]     +58  [ = ]       0    MavlinkStreamExternalGimbalManagerSetAttitude::send()
    [NEW]    +165  [ = ]       0    MavlinkStreamExternalGimbalManagerSetAttitude::~MavlinkStreamExternalGimbalManagerSetAttitude()
   +35e2% +1.39Ki  [ = ]       0    [20 Others]
    +0.1%     +27  [ = ]       0    [section .strtab]
   -26.7%     -16  [ = ]       0    ___ZL19param_get_cplusplustPf.isra.0_veneer
    [NEW]     +42  [ = ]       0    __orb_external_gimbal_manager_information
  +0.2% +1.34Ki  [ = ]       0    .symtab
    [DEL]     -32  [ = ]       0    CSWTCH.1990
    [NEW]     +32  [ = ]       0    CSWTCH.2009
    [DEL]     -32  [ = ]       0    CSWTCH.2789
    [NEW]     +32  [ = ]       0    CSWTCH.2816
    [DEL]     -48  [ = ]       0    CSWTCH.3557
    [DEL]     -48  [ = ]       0    CSWTCH.3558
    [NEW]     +48  [ = ]       0    CSWTCH.3592
    [NEW]     +48  [ = ]       0    CSWTCH.3593
     +20%     +16  [ = ]       0    ManualControlSelector::isInputValid()
    [NEW]     +32  [ = ]       0    MavlinkReceiver::handle_message_external_gimbal_manager_information()
    [NEW]     +32  [ = ]       0    MavlinkReceiver::handle_message_external_gimbal_manager_status()
    +100%     +16  [ = ]       0    MavlinkStreamAutopilotStateForGimbalDevice::send()
   -25.0%     -16  [ = ]       0    MavlinkStreamBatteryInfo::send()
    +100%     +16  [ = ]       0    MavlinkStreamESCStatus::update_data()
    [NEW]     +32  [ = ]       0    MavlinkStreamExternalGimbalManagerSetAttitude
    [NEW]     +16  [ = ]       0    MavlinkStreamExternalGimbalManagerSetAttitude::get_id()
    [NEW]     +64  [ = ]       0    MavlinkStreamExternalGimbalManagerSetAttitude::get_name()
    [NEW]     +32  [ = ]       0    MavlinkStreamExternalGimbalManagerSetAttitude::get_size()
    [NEW]     +48  [ = ]       0    MavlinkStreamExternalGimbalManagerSetAttitude::new_instance()
    [NEW]     +48  [ = ]       0    MavlinkStreamExternalGimbalManagerSetAttitude::send()
   -92.1% +1.02Ki  [ = ]       0    [39 Others]
 -30.9% -3.61Ki  [ = ]       0    [Unmapped]
  -0.1%      -8  -0.1%      -8    .ramfunc
    -1.2%      -4  -1.2%      -4    Ekf::measurementUpdate()
   -16.7%      -4 -16.7%      -4    get_orb_meta()
  +0.2% +83.5Ki  +0.2% +3.61Ki    TOTAL

px4_fmu-v6x [Total VM Diff: 3720 byte (0.19 %)]
    FILE SIZE        VM SIZE    
 --------------  -------------- 
  +0.2% +3.63Ki  +0.2% +3.63Ki    .text
   -97.5%    +652 -97.5%    +652    [52 Others]
    +0.3%    +540  +0.3%    +540    [section .text]
    [NEW]    +280  [NEW]    +280    gimbal::OutputMavlinkToGimbalManager::_acquire_control_for_autopilot()
    +1.1%    +228  +1.1%    +228    uORB::compressed_fields
    [NEW]    +188  [NEW]    +188    gimbal::OutputMavlinkToGimbalManager::update()
    [NEW]    +180  [NEW]    +180    gimbal::OutputMavlinkToGimbalManager::print_status()
    [NEW]    +176  [NEW]    +176    gimbal::OutputMavlinkToGimbalManager::_send_take_control_request()
     +51%    +168   +51%    +168    gimbal::InputMavlinkGimbalV2::update()
    [NEW]    +160  [NEW]    +160    gimbal::OutputMavlinkToGimbalManager::OutputMavlinkToGimbalManager()
    [NEW]    +152  [NEW]    +152    MavlinkStreamExternalGimbalManagerSetAttitude::send()
    [NEW]    +148  [NEW]    +148    gimbal::OutputMavlinkToGimbalManager::_publish_gimbal_manager_set_attitude()
    [NEW]    +140  [NEW]    +140    MavlinkReceiver::handle_message_external_gimbal_manager_information()
    [NEW]    +132  [NEW]    +132    gimbal::OutputMavlinkToGimbalManager::_check_for_gimbal_manager_status()
    [NEW]    +128  [NEW]    +128    MavlinkReceiver::handle_message_external_gimbal_manager_status()
    +0.1%    +120  +0.1%    +120    g_cromfs_image
    +1.7%     +96  +1.7%     +96    Mavlink::configure_streams_to_default()
    [NEW]     +84  [NEW]     +84    gimbal::OutputMavlinkToGimbalManager::_request_gimbal_manager_information()
    [NEW]     +78  [NEW]     +78    gimbal::OutputMavlinkToGimbalManager::~OutputMavlinkToGimbalManager()
    [NEW]     +70  [NEW]     +70    gimbal::OutputMavlinkToGimbalManager::_check_for_take_control_ack()
    [NEW]     +60  [NEW]     +60    CSWTCH.2009
    [DEL]     -60  [DEL]     -60    CSWTCH.1990
  +0.2% +3.27Ki  [ = ]       0    .debug_abbrev
  +0.2%    +320  [ = ]       0    .debug_aranges
  +0.2%    +972  [ = ]       0    .debug_frame
  +0.2% +55.3Ki  [ = ]       0    .debug_info
  +0.2% +7.28Ki  [ = ]       0    .debug_line
    [NEW]      +1  [ = ]       0    [Unmapped]
    +0.2% +7.28Ki  [ = ]       0    [section .debug_line]
  +0.1% +3.25Ki  [ = ]       0    .debug_loclists
  +0.1%    +419  [ = ]       0    .debug_rnglists
  +0.3% +9.04Ki  [ = ]       0    .debug_str
  -0.4%      -1  [ = ]       0    .shstrtab
  +0.3% +2.14Ki  [ = ]       0    .strtab
    [DEL]     -12  [ = ]       0    CSWTCH.1990
    [NEW]     +12  [ = ]       0    CSWTCH.2009
    [DEL]     -12  [ = ]       0    CSWTCH.2789
    [NEW]     +12  [ = ]       0    CSWTCH.2816
    [DEL]     -24  [ = ]       0    CSWTCH.3557
    [DEL]     -24  [ = ]       0    CSWTCH.3558
    [NEW]     +24  [ = ]       0    CSWTCH.3592
    [NEW]     +24  [ = ]       0    CSWTCH.3593
    [NEW]     +94  [ = ]       0    MavlinkReceiver::handle_message_external_gimbal_manager_information()
    [NEW]     +89  [ = ]       0    MavlinkReceiver::handle_message_external_gimbal_manager_status()
    [NEW]     +52  [ = ]       0    MavlinkStreamExternalGimbalManagerSetAttitude
    [NEW]     +60  [ = ]       0    MavlinkStreamExternalGimbalManagerSetAttitude::get_id()
    [NEW]     +63  [ = ]       0    MavlinkStreamExternalGimbalManagerSetAttitude::get_name()
    [NEW]     +62  [ = ]       0    MavlinkStreamExternalGimbalManagerSetAttitude::get_size()
    [NEW]     +75  [ = ]       0    MavlinkStreamExternalGimbalManagerSetAttitude::new_instance()
    [NEW]     +58  [ = ]       0    MavlinkStreamExternalGimbalManagerSetAttitude::send()
    [NEW]    +165  [ = ]       0    MavlinkStreamExternalGimbalManagerSetAttitude::~MavlinkStreamExternalGimbalManagerSetAttitude()
    [NEW] +1.33Ki  [ = ]       0    [18 Others]
    +0.1%     +27  [ = ]       0    [section .strtab]
    [NEW]     +42  [ = ]       0    __orb_external_gimbal_manager_information
    [NEW]     +43  [ = ]       0    __orb_external_gimbal_manager_set_attitude
  +0.2% +1.34Ki  [ = ]       0    .symtab
    [DEL]     -32  [ = ]       0    CSWTCH.1990
    [NEW]     +32  [ = ]       0    CSWTCH.2009
    [DEL]     -32  [ = ]       0    CSWTCH.2789
    [NEW]     +32  [ = ]       0    CSWTCH.2816
    [DEL]     -48  [ = ]       0    CSWTCH.3557
    [DEL]     -48  [ = ]       0    CSWTCH.3558
    [NEW]     +48  [ = ]       0    CSWTCH.3592
    [NEW]     +48  [ = ]       0    CSWTCH.3593
    [NEW]     +32  [ = ]       0    MavlinkReceiver::handle_message_external_gimbal_manager_information()
    [NEW]     +32  [ = ]       0    MavlinkReceiver::handle_message_external_gimbal_manager_status()
     +20%     +16  [ = ]       0    MavlinkReceiver::handle_messages_in_gimbal_mode()
    +100%     +16  [ = ]       0    MavlinkStreamAutopilotStateForGimbalDevice::send()
   -25.0%     -16  [ = ]       0    MavlinkStreamBatteryInfo::send()
    +100%     +16  [ = ]       0    MavlinkStreamESCStatus::update_data()
    [NEW]     +32  [ = ]       0    MavlinkStreamExternalGimbalManagerSetAttitude
    [NEW]     +16  [ = ]       0    MavlinkStreamExternalGimbalManagerSetAttitude::get_id()
    [NEW]     +64  [ = ]       0    MavlinkStreamExternalGimbalManagerSetAttitude::get_name()
    [NEW]     +32  [ = ]       0    MavlinkStreamExternalGimbalManagerSetAttitude::get_size()
    [NEW]     +48  [ = ]       0    MavlinkStreamExternalGimbalManagerSetAttitude::new_instance()
    [NEW]     +48  [ = ]       0    MavlinkStreamExternalGimbalManagerSetAttitude::send()
   -92.0% +1.02Ki  [ = ]       0    [38 Others]
  +8.9%    +376  [ = ]       0    [Unmapped]
  +0.2% +87.3Ki  +0.2% +3.63Ki    TOTAL

Updated: 2026-05-20T02:27:49

@hamishwillee

hamishwillee commented Apr 19, 2026

Copy link
Copy Markdown
Contributor

@julianoes Might be worth you looking at this? I would have thought this just a PX4 configuration issue.

@julianoes

Copy link
Copy Markdown
Contributor

I currently have a full plate, so if this is urgent, Gremsy can consider sponsoring me to move this along.

#ifndef EXTERNAL_GIMBAL_MANAGER_SET_ATTITUDE_HPP
#define EXTERNAL_GIMBAL_MANAGER_SET_ATTITUDE_HPP

#include <uORB/topics/external_gimbal_manager_set_attitude.h>

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.

[error] clang-diagnostic-error [error]
uORB/topics/external_gimbal_manager_set_attitude.h file not found

#ifndef EXTERNAL_GIMBAL_MANAGER_SET_ATTITUDE_HPP
#define EXTERNAL_GIMBAL_MANAGER_SET_ATTITUDE_HPP

#include <uORB/topics/external_gimbal_manager_set_attitude.h>

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.

[error] clang-diagnostic-error [error]
uORB/topics/external_gimbal_manager_set_attitude.h file not found

@minhleGremsy

Copy link
Copy Markdown
Author

I currently have a full plate, so if this is urgent, Gremsy can consider sponsoring me to move this along.

Hi @julianoes , currently, this feature is not urgent for us. However, we still look forward to your review. Do you have estimate time for the review? And which level of sponsor are required for this? Thank you

@julianoes

Copy link
Copy Markdown
Contributor

@minhleGremsy I had a quick first look through the changes. Can you explain to me why the gimbal needs/wants to be a gimbal manager and not a device? Why is that required, or what additional features does that bring? I really want to understand this fully before reviewing.

Also, how would we test this going forward? Could we create a gimbal manager for SITL?

Also, please check the conflicts and clean up some of the commits that have unrelated changes.

@minhleGremsy

minhleGremsy commented May 18, 2026

Copy link
Copy Markdown
Author

@minhleGremsy I had a quick first look through the changes. Can you explain to me why the gimbal needs/wants to be a gimbal manager and not a device? Why is that required, or what additional features does that bring? I really want to understand this fully before reviewing.

Also, how would we test this going forward? Could we create a gimbal manager for SITL?

Also, please check the conflicts and clean up some of the commits that have unrelated changes.

Hi @julianoes , thanks for your time.
In our payload, gimbal-camera and other modern/smart gimbal-camera in the market, the gimbal is not only controlled by the autopilot, but it also has other complicated control scenarios. The gimbal-camera needs self-control logic for its automatic features like tracking, following, scanning. In that case, if the autopilot is still the gimbal manager, the work flow is not trival.

If you need hardware device for testing, we can lend it to you.

@julianoes

Copy link
Copy Markdown
Contributor

Ok, that makes sense.

I don't need to test it now, assuming you have tested it on your side. However, in the future, when there are gimbal-related changes, you will have to re-test it still works with your gimbal/camera.

@github-actions github-actions Bot added kind:feature Request or change that adds new functionality. kind:test Adds or improves tests. scope:build-system CMake, Kconfig, board config, or build tooling. scope:offboard Offboard mode, external setpoints, companion-computer control, or offboard failsafe behavior. scope:mavlink MAVLink module, streams, commands, or protocol handling. scope:uorb uORB messages, generated interfaces, or message translation. scope:parameters Parameter definitions, metadata, migration, or defaults. scope:testing Unit, integration, fuzzing, or test data. scope:boards Board-specific changes or hardware definitions. labels May 20, 2026
@minhleGremsy

Copy link
Copy Markdown
Author

Hi @julianoes thanks for your trust. We tested PX4 with our gimbal manager/gimbal device and QGC. The implementation follow MAVLink standard so it'd work with other smart gimbal as well.

I resolved the conflicts. Please, help me review PR. This is our first PR in PX4, many things new


// Conflict occurs when PX4 itself acts as a gimbal manager while also communicating with an external gimbal manager.
if (px4_acts_as_gimbal_manager && connected_to_external_gimbal_manager) {
PX4_ERR("MNT_MODE_OUT=MNT_MODE_OUT_TO_GIMBAL_MANAGER is only compatible with MNT_MODE_IN=AUTO or RC.");

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.

This is interesting. In theory, it should be possible to have two gimbal managers in a system: e.g. PX4 is a gimbal manager talking to its gimbal device while there is a second gimbal manager such as the integrated camera/gimbal.

Everything should then flow seamlessly through mavlink, so any gimbal command should just be sent or forwarded to the gimbal manager. I need to think more about that case.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Hi @julianoes , that works in theory. But, PX4 default find a gimbal device with COMP_ID=MNT_MAV_COMPID which is default as 154, similar to most mavlink gimbal device. It might make the gimbal device confuse, so I made this to prevent this situation.

Also, PX4 always broadcast it as a gimbal manager even if it does not find any its gimbal device. I think this behavior is not correct.

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.

Also, PX4 always broadcast it as a gimbal manager even if it does not find any its gimbal device. I think this behavior is not correct.

Indeed, so that might be the first thing to fix.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Hi @julianoes , I tried to fix the issue PX4 always act as a gimbal manager even when a gimbal device is not belong to it.

After deep dive to the code https://github.dev/PX4/PX4-Autopilot/blob/d5f5c503309c14321934602b037148cd3756c465/src/modules/gimbal/gimbal.cpp#L568-L569 I found params MNT_MAV_SYSID, MNT_MAV_COMPID are only use for gimbal protocol v1, which not support gimbal device discovering.

In gimbal protocol v2, https://github.dev/PX4/PX4-Autopilot/blob/d5f5c503309c14321934602b037148cd3756c465/src/modules/gimbal/output_mavlink.cpp#L191-L199 PX4 auto discover for gimbal device by gimbal_device_information, so it steal our gimbal device. Of course, we can modify our gimbal device to reject PX4 gimbal_manager message, but can not apply widely. I'd like to hear your thought about this. How would we decide which gimbal device below to which gimbal manager in a complex system with multiple gimbal or multi component want to act as gimbal manager. Currently, mavlink has not have any message field for the gimbal_device to public who is its manager.

Another comment, in our setup, our gimbal manager is successfully integrated with the GCS via standard mavlink GCS <-> gimbal_manger, tested with QGC and our NexGCS. We need integration to PX4, because we want to have manual control via RC input and control in a flight mission with precise control and gps. For now, PX4 support only one gimbal, if PX4 support mount to a gimbal manager, I think there is no meaning to keep PX4's gimbal_manager anymore. How do you think?

@julianoes julianoes self-assigned this Jul 12, 2026
@julianoes

Copy link
Copy Markdown
Contributor

FYI: I'm looking into this now.

@julianoes

Copy link
Copy Markdown
Contributor

@minhleGremsy I've looked into this and there are a few aspects we need to look at:

  1. Fundamentally, the way works is that we just need to forward MAVLink between the gimbal and the ground station. (And we don't need the plumbing added by this PR.)
  2. We need to make sure PX4 doesn't advertise itself as a gimbal manager if it doesn't have a gimbal configured. This is already the case if MNT_MODE_IN is -1. If MNT_MODE_IN is 4, then we need this fix to prevent it from sending out the gimbal manager messages.
  3. The gimbal manager can support ROI (if ROI is required) as PX4 does not do the translation for it unless it has a gimbal manager configured. ROI as part of a mission item works already in PX4 but if we hit ROI in QGC, that's not applied yet, because it's sent specifically to PX4 only (1/1). I'm looking into fixing this in QGC.

I have done some testing using a MAVSDK gimbal manager example, see: mavlink/MAVSDK#2921

@minhleGremsy

Copy link
Copy Markdown
Author

Fundamentally, the way works is that we just need to forward MAVLink between the gimbal and the ground station. (And we don't need the plumbing added by this PR.)
@julianoes , we made this PR to expand other options to control the gimbal manager in user's perspective, not only from GCS. We can control the gimbal manager from QGC already. But we need support from Autopilot as well to have manual control from remote controller joystick, wheel, which in most case will send to the drone via Sbus/Spektrum and go to the Autopilot. We want the autopilot support to translate these command to MAVLink for the gimbal manager.

In mission mode, the gimbal manager can handle ROI well. But for task like drone mapping, surveying. We need higher sync between gimbal operation, camera capture and drone position, so we prefer to have Autopilot control the gimbal manger in a flight mission, control gimbal attitude and trigger camera capture when reach a way point. Also, currently, when create a new flight mission from popular GCS like QGC, AFAIK, only generate way point and command target for the Autopilot, as payload side, we dont figure out any other way for the payload get the flight mission and sync the execution with the Autopilot. So, we think the Autopilot should be in charge to translate and send command to other components in the system.

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

Labels

kind:feature Request or change that adds new functionality. kind:test Adds or improves tests. scope:boards Board-specific changes or hardware definitions. scope:build-system CMake, Kconfig, board config, or build tooling. scope:mavlink MAVLink module, streams, commands, or protocol handling. scope:offboard Offboard mode, external setpoints, companion-computer control, or offboard failsafe behavior. scope:parameters Parameter definitions, metadata, migration, or defaults. scope:testing Unit, integration, fuzzing, or test data. scope:uorb uORB messages, generated interfaces, or message translation.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants