From b6eb2ca22162778b1332bcfdefdf2069fe59897a Mon Sep 17 00:00:00 2001 From: Don Gagne Date: Thu, 19 Jun 2025 10:55:43 -0700 Subject: [PATCH 01/69] Fix leak of static pad on shutdown This would cause huge leaks if the stream was continuously restarting due to video timeout --- src/VideoManager/VideoReceiver/GStreamer/GstVideoReceiver.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/VideoManager/VideoReceiver/GStreamer/GstVideoReceiver.cc b/src/VideoManager/VideoReceiver/GStreamer/GstVideoReceiver.cc index d2046d384126..8216d9b71d09 100644 --- a/src/VideoManager/VideoReceiver/GStreamer/GstVideoReceiver.cc +++ b/src/VideoManager/VideoReceiver/GStreamer/GstVideoReceiver.cc @@ -242,7 +242,7 @@ void GstVideoReceiver::stop() GstPad *sinkpad = gst_element_get_static_pad(_tee, "sink"); if (sinkpad) { gst_pad_remove_probe(sinkpad, _teeProbeId); - sinkpad = nullptr; + gst_clear_object(&sinkpad); } _teeProbeId = 0; } From 45d9ca19b7286a8e04de9dbb894e69c50904bae7 Mon Sep 17 00:00:00 2001 From: Don Gagne Date: Fri, 20 Jun 2025 09:42:00 -0700 Subject: [PATCH 02/69] Only restart video once a second This way we don't hammer the main thread with silly work --- src/VideoManager/VideoManager.cc | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/VideoManager/VideoManager.cc b/src/VideoManager/VideoManager.cc index 49283a7bb839..ae254767b690 100644 --- a/src/VideoManager/VideoManager.cc +++ b/src/VideoManager/VideoManager.cc @@ -32,6 +32,7 @@ #include #include #include +#include QGC_LOGGING_CATEGORY(VideoManagerLog, "qgc.videomanager.videomanager") @@ -680,12 +681,15 @@ void VideoManager::_initVideoReceiver(VideoReceiver *receiver, QQuickWindow *win }); (void) connect(receiver, &VideoReceiver::onStopComplete, this, [this, receiver](VideoReceiver::STATUS status) { - qCDebug(VideoManagerLog) << "Video" << receiver->name() << "Stop complete, status:" << status; + qCDebug(VideoManagerLog) << "Stop complete" << receiver->name() << receiver->uri() << ", status:" << status; receiver->setStarted(false); if (status == VideoReceiver::STATUS_INVALID_URL) { qCDebug(VideoManagerLog) << "Invalid video URL. Not restarting"; } else { - _startReceiver(receiver); + QTimer::singleShot(1000, receiver, [this, receiver]() { + qCDebug(VideoManagerLog) << "Restarting video receiver" << receiver->name() << receiver->uri(); + _startReceiver(receiver); + }); } }); From 18c1fdf0c582993d190f7519672622af0c0384b8 Mon Sep 17 00:00:00 2001 From: Don Gagne Date: Fri, 20 Jun 2025 09:42:08 -0700 Subject: [PATCH 03/69] Better logging --- .../VideoReceiver/GStreamer/GstVideoReceiver.cc | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/VideoManager/VideoReceiver/GStreamer/GstVideoReceiver.cc b/src/VideoManager/VideoReceiver/GStreamer/GstVideoReceiver.cc index 8216d9b71d09..359daebe77db 100644 --- a/src/VideoManager/VideoReceiver/GStreamer/GstVideoReceiver.cc +++ b/src/VideoManager/VideoReceiver/GStreamer/GstVideoReceiver.cc @@ -70,7 +70,7 @@ void GstVideoReceiver::start(uint32_t timeout) _timeout = timeout; _buffer = lowLatency() ? -1 : 0; - qCDebug(GstVideoReceiverLog) << "Starting" << _uri << ", buffer" << _buffer; + qCDebug(GstVideoReceiverLog) << "Starting" << _uri << ", lowLatency" << lowLatency() << ", timeout" << _timeout; _endOfStream = false; @@ -1176,10 +1176,12 @@ void GstVideoReceiver::_dispatchSignal(Task emitter) _signalDepth -= 1; } -gboolean GstVideoReceiver::_onBusMessage(GstBus *bus, GstMessage *msg, gpointer data) +gboolean GstVideoReceiver::_onBusMessage(GstBus * /* bus */, GstMessage *msg, gpointer data) { - Q_UNUSED(bus) - Q_ASSERT(msg); Q_ASSERT(data); + if (!msg || !data) { + qCCritical(GstVideoReceiverLog) << "Invalid parameters in _onBusMessage: msg=" << msg << "data=" << data; + return TRUE; + } GstVideoReceiver *pThis = static_cast(data); From 2d543f49d08e11863ac4f90cde3c4f916d27fe5a Mon Sep 17 00:00:00 2001 From: Don Gagne Date: Sat, 21 Jun 2025 10:11:17 -0700 Subject: [PATCH 04/69] Fix positioning of control within containing RowLayout --- .../Widgets/HorizontalCompassAttitude.qml | 56 ++++++++----------- 1 file changed, 23 insertions(+), 33 deletions(-) diff --git a/src/FlightMap/Widgets/HorizontalCompassAttitude.qml b/src/FlightMap/Widgets/HorizontalCompassAttitude.qml index e104af691570..84f35ffc5454 100644 --- a/src/FlightMap/Widgets/HorizontalCompassAttitude.qml +++ b/src/FlightMap/Widgets/HorizontalCompassAttitude.qml @@ -8,20 +8,18 @@ ****************************************************************************/ import QtQuick -import QtQuick.Layouts import QGroundControl import QGroundControl.Controls import QGroundControl.ScreenTools -import QGroundControl.FactSystem -import QGroundControl.FlightMap -import QGroundControl.FlightDisplay import QGroundControl.Palette -ColumnLayout { - id: root - spacing: ScreenTools.defaultFontPixelHeight / 4 - width: Math.min(_defaultWidth, _maxWidth) +Rectangle { + id: control + width: Math.min(_defaultWidth, _maxWidth) + height: _outerRadius * 2 + radius: _outerRadius + color: qgcPal.window property real extraInset: 0 property real extraValuesWidth: _outerRadius @@ -33,33 +31,25 @@ ColumnLayout { property real _spacing: ScreenTools.defaultFontPixelHeight * 0.33 property real _topBottomMargin: (width * 0.05) / 2 - QGCPalette { id: qgcPal } - - Rectangle { - id: visualInstrument - height: _outerRadius * 2 - Layout.fillWidth: true - radius: _outerRadius - color: qgcPal.window + DeadMouseArea { anchors.fill: parent } - DeadMouseArea { anchors.fill: parent } + QGCPalette { id: qgcPal } - QGCAttitudeWidget { - id: attitude - anchors.leftMargin: _topBottomMargin - anchors.left: parent.left - size: _innerRadius * 2 - vehicle: globals.activeVehicle - anchors.verticalCenter: parent.verticalCenter - } + QGCAttitudeWidget { + id: attitude + anchors.leftMargin: control._topBottomMargin + anchors.left: parent.left + size: control._innerRadius * 2 + vehicle: globals.activeVehicle + anchors.verticalCenter: parent.verticalCenter + } - QGCCompassWidget { - id: compass - anchors.leftMargin: _spacing - anchors.left: attitude.right - size: _innerRadius * 2 - vehicle: globals.activeVehicle - anchors.verticalCenter: parent.verticalCenter - } + QGCCompassWidget { + id: compass + anchors.leftMargin: control._spacing + anchors.left: attitude.right + size: control._innerRadius * 2 + vehicle: globals.activeVehicle + anchors.verticalCenter: parent.verticalCenter } } From b9712fb69b25ff343bcbc70499c934d0bc6c49fa Mon Sep 17 00:00:00 2001 From: Don Gagne Date: Sun, 22 Jun 2025 09:41:52 -0700 Subject: [PATCH 05/69] Handle all cases of parameters available/missing --- src/AutoPilotPlugins/PX4/BatteryParams.qml | 52 ++++------- src/AutoPilotPlugins/PX4/PowerComponent.qml | 91 ++++++++++++++----- .../PX4/PowerComponentSummary.qml | 22 +++-- 3 files changed, 100 insertions(+), 65 deletions(-) diff --git a/src/AutoPilotPlugins/PX4/BatteryParams.qml b/src/AutoPilotPlugins/PX4/BatteryParams.qml index fa3252472001..68173d4a92b4 100644 --- a/src/AutoPilotPlugins/PX4/BatteryParams.qml +++ b/src/AutoPilotPlugins/PX4/BatteryParams.qml @@ -19,43 +19,25 @@ import QGroundControl.Controls import QGroundControl.ScreenTools import QGroundControl.Controllers -// Exposes the set of battery parameters for new and old firmwares -// Older firmware: BAT_* naming -// Newer firmware: BAT#_* naming, with indices starting at 1 +// Exposes the set of battery parameters taking into account the availability of the parameters. +// Only the _SOURCE parameter can be assumed to be always available. The remainder of the parameters +// may or may not be available depending on the _SOURCE setting. QtObject { property var controller ///< FactPanelController property int batteryIndex ///< 1-based battery index - - property Fact battSource: controller.getParameterFact(-1, "BAT#_SOURCE".replace ("#", _indexedBatteryParamsAvailable ? batteryIndex : "")) - property Fact battNumCells: controller.getParameterFact(-1, "BAT#_N_CELLS".replace ("#", _indexedBatteryParamsAvailable ? batteryIndex : "")) - property Fact battHighVolt: controller.getParameterFact(-1, "BAT#_V_CHARGED".replace ("#", _indexedBatteryParamsAvailable ? batteryIndex : "")) - property Fact battLowVolt: controller.getParameterFact(-1, "BAT#_V_EMPTY".replace ("#", _indexedBatteryParamsAvailable ? batteryIndex : "")) - property Fact battVoltLoadDrop: controller.getParameterFact(-1, "BAT#_V_LOAD_DROP".replace ("#", _indexedBatteryParamsAvailable ? batteryIndex : ""), false) - property Fact battVoltageDivider: controller.getParameterFact(-1, "BAT#_V_DIV".replace ("#", _indexedBatteryParamsAvailable ? batteryIndex : ""), false) - property Fact battAmpsPerVolt: controller.getParameterFact(-1, "BAT#_A_PER_V".replace ("#", _indexedBatteryParamsAvailable ? batteryIndex : ""), false) - - property bool battVoltLoadDropAvailable: controller.parameterExists(-1, "BAT#_V_LOAD_DROP".replace ("#", _indexedBatteryParamsAvailable ? batteryIndex : "")) - property bool battVoltageDividerAvailable: controller.parameterExists(-1, "BAT#_V_DIV".replace ("#", _indexedBatteryParamsAvailable ? batteryIndex : "")) - property bool battAmpsPerVoltAvailable: controller.parameterExists(-1, "BAT#_A_PER_V".replace ("#", _indexedBatteryParamsAvailable ? batteryIndex : "")) - - property string _batNCellsIndexedParamName: "BAT#_N_CELLS" - property bool _indexedBatteryParamsAvailable: controller.parameterExists(-1, _batNCellsIndexedParamName.replace("#", 1)) - property int _indexedBatteryParamCount: getIndexedBatteryParamCount() - - Component.onCompleted: { - if (batteryIndex > 1 && !_indexedBatteryParamsAvailable) { - console.warn("Internal Error: BatteryParams.qml batteryIndex > 1 while indexed params are not available", batteryIndex) - } - } - - function getIndexedBatteryParamCount() { - var batteryIndex = 1 - do { - if (!controller.parameterExists(-1, _batNCellsIndexedParamName.replace("#", batteryIndex))) { - return batteryIndex - 1 - } - batteryIndex++ - } while (true) - } + property Fact battSource: controller.getParameterFact(-1, "BAT#_SOURCE".replace ("#", batteryIndex)) + property Fact battNumCells: controller.getParameterFact(-1, "BAT#_N_CELLS".replace ("#", batteryIndex), false) + property Fact battHighVolt: controller.getParameterFact(-1, "BAT#_V_CHARGED".replace ("#", batteryIndex), false) + property Fact battLowVolt: controller.getParameterFact(-1, "BAT#_V_EMPTY".replace ("#", batteryIndex), false) + property Fact battVoltLoadDrop: controller.getParameterFact(-1, "BAT#_V_LOAD_DROP".replace ("#", batteryIndex), false) + property Fact battVoltageDivider: controller.getParameterFact(-1, "BAT#_V_DIV".replace ("#", batteryIndex), false) + property Fact battAmpsPerVolt: controller.getParameterFact(-1, "BAT#_A_PER_V".replace ("#", batteryIndex), false) + + property bool battNumCellsAvailable: controller.parameterExists(-1, "BAT#_N_CELLS".replace ("#", batteryIndex)) + property bool battHighVoltAvailable: controller.parameterExists(-1, "BAT#_V_CHARGED".replace ("#", batteryIndex)) + property bool battLowVoltAvailable: controller.parameterExists(-1, "BAT#_V_EMPTY".replace ("#", batteryIndex)) + property bool battVoltLoadDropAvailable: controller.parameterExists(-1, "BAT#_V_LOAD_DROP".replace ("#", batteryIndex)) + property bool battVoltageDividerAvailable: controller.parameterExists(-1, "BAT#_V_DIV".replace ("#", batteryIndex)) + property bool battAmpsPerVoltAvailable: controller.parameterExists(-1, "BAT#_A_PER_V".replace ("#", batteryIndex)) } diff --git a/src/AutoPilotPlugins/PX4/PowerComponent.qml b/src/AutoPilotPlugins/PX4/PowerComponent.qml index cc164540f607..d316790da1fe 100644 --- a/src/AutoPilotPlugins/PX4/PowerComponent.qml +++ b/src/AutoPilotPlugins/PX4/PowerComponent.qml @@ -20,9 +20,8 @@ import QGroundControl.ScreenTools import QGroundControl.Controllers import QGroundControl.AutoPilotPlugins.PX4 -// Note: This setup supports back compat on battery parameter naming -// Older firmware: Single battery setup using BAT_* naming -// Newer firmware: Multiple battery setup using BAT#_* naming, with indices starting at 1 +// Note: Only the _SOURCE parameter can be assumed to be always available. The remainder of the parameters +// may or may not be available depending on the _SOURCE setting. SetupPage { id: powerPage pageComponent: pageComponent @@ -36,17 +35,15 @@ SetupPage { readonly property string _highlightPrefix: "" readonly property string _highlightSuffix: "" - readonly property string _batNCellsIndexedParamName: "BAT#_N_CELLS" - property int _textEditWidth: ScreenTools.defaultFontPixelWidth * 8 - property Fact _uavcanEnable: controller.getParameterFact(-1, "UAVCAN_ENABLE", false) - property bool _indexedBatteryParamsAvailable: controller.parameterExists(-1, _batNCellsIndexedParamName.replace("#", 1)) - property int _indexedBatteryParamCount: getIndexedBatteryParamCount() + property int _textEditWidth: ScreenTools.defaultFontPixelWidth * 8 + property Fact _uavcanEnable: controller.getParameterFact(-1, "UAVCAN_ENABLE", false) + property int _indexedBatteryParamCount: getIndexedBatteryParamCount() function getIndexedBatteryParamCount() { var batteryIndex = 1 do { - if (!controller.parameterExists(-1, _batNCellsIndexedParamName.replace("#", batteryIndex))) { + if (!controller.parameterExists(-1, "BAT#_SOURCE".replace("#", batteryIndex))) { return batteryIndex - 1 } batteryIndex++ @@ -54,7 +51,7 @@ SetupPage { } PowerComponentController { - id: controller + id: controller onOldFirmware: mainWindow.showMessageDialog(qsTr("ESC Calibration"), qsTr("%1 cannot perform ESC Calibration with this version of firmware. You will need to upgrade to a newer firmware.").arg(QGroundControl.appName)) onNewerFirmware: mainWindow.showMessageDialog(qsTr("ESC Calibration"), qsTr("%1 cannot perform ESC Calibration with this version of firmware. You will need to upgrade %1.").arg(QGroundControl.appName)) onDisconnectBattery: mainWindow.showMessageDialog(qsTr("ESC Calibration failed"), qsTr("You must disconnect the battery prior to performing ESC Calibration. Disconnect your battery and try again.")) @@ -93,14 +90,13 @@ SetupPage { Repeater { id: batterySetupRepeater - model: _indexedBatteryParamsAvailable ? _indexedBatteryParamCount : 1 + model: _indexedBatteryParamCount Loader { sourceComponent: batterySetupComponent property int batteryIndex: index + 1 property bool showBatteryIndex: batterySetupRepeater.count > 1 - property bool useIndexedParamNames: _indexedBatteryParamsAvailable } } @@ -223,6 +219,9 @@ SetupPage { batteryIndex: _batteryIndex } + property bool battNumCellsAvailable: batParams.battNumCellsAvailable + property bool battHighVoltAvailable: batParams.battHighVoltAvailable + property bool battLowVoltAvailable: batParams.battLowVoltAvailable property bool battVoltLoadDropAvailable: batParams.battVoltLoadDropAvailable property bool battVoltageDividerAvailable: batParams.battVoltageDividerAvailable property bool battAmpsPerVoltAvailable: batParams.battAmpsPerVoltAvailable @@ -277,6 +276,7 @@ SetupPage { } QGCColoredImage { + id: battImage Layout.rowSpan: 4 width: height * 0.75 height: 100 @@ -286,36 +286,81 @@ SetupPage { color: qgcPal.text cache: false source: getBatteryImage(batteryIndex) + visible: battNumCellsAvailable && battLowVoltAvailable && battHighVoltAvailable } - Item { width: 1; height: 1; Layout.columnSpan: 2 } + Item { + width: 1 + height: 1 + Layout.columnSpan: battImage.visible ? 2 : 3 + } - QGCLabel { text: qsTr("Number of Cells (in Series)") } + QGCLabel { + text: qsTr("Number of Cells (in Series)") + visible: battNumCellsAvailable + } FactTextField { width: _textEditWidth fact: battNumCells showUnits: true + visible: battNumCellsAvailable + } + QGCLabel { + text: qsTr("Battery Max:") + visible: battImage.visible + } + QGCLabel { + text: visible ? (battNumCells.value * battHighVolt.value).toFixed(1) + ' V' : "" + visible: battImage.visible + } + Item { + width: 1 + height: 1 + Layout.columnSpan: 3 + visible: !battImage.visible } - QGCLabel { text: qsTr("Battery Max:") } - QGCLabel { text: (battNumCells.value * battHighVolt.value).toFixed(1) + ' V' } - QGCLabel { text: qsTr("Empty Voltage (per cell)") } + QGCLabel { + text: qsTr("Empty Voltage (per cell)") + visible: battLowVoltAvailable + } FactTextField { width: _textEditWidth fact: battLowVolt showUnits: true + visible: battLowVoltAvailable + } + QGCLabel { + text: qsTr("Battery Min:") + visible: battImage.visible + } + QGCLabel { + text: visible ? (battNumCells.value * battLowVolt.value).toFixed(1) + ' V' : "" + visible: battImage.visible + } + Item { + width: 1 + height: 1 + Layout.columnSpan: 3 + visible: battLowVoltAvailable && !battImage.visible } - QGCLabel { text: qsTr("Battery Min:") } - QGCLabel { text: (battNumCells.value * battLowVolt.value).toFixed(1) + ' V' } - - QGCLabel { text: qsTr("Full Voltage (per cell)") } + QGCLabel { + text: qsTr("Full Voltage (per cell)") + visible: battHighVoltAvailable + } FactTextField { width: _textEditWidth fact: battHighVolt showUnits: true + visible: battHighVoltAvailable + } + Item { + width: 1 + height: 1 + Layout.columnSpan: battImage.visible ? 2 : 3 + visible: battHighVoltAvailable } - Item { width: 1; height: 1; Layout.columnSpan: 2 } QGCLabel { text: qsTr("Voltage divider") @@ -401,7 +446,7 @@ SetupPage { visible: showAdvanced.checked } QGCLabel { - text: ((battNumCells.value * battLowVolt.value) - (battNumCells.value * battVoltLoadDrop.value)).toFixed(1) + qsTr(" V") + text: visible ? ((battNumCells.value * battLowVolt.value) - (battNumCells.value * battVoltLoadDrop.value)).toFixed(1) + qsTr(" V") : "" visible: showAdvanced.checked } Item { width: 1; height: 1; Layout.columnSpan: 3; visible: showAdvanced.checked } diff --git a/src/AutoPilotPlugins/PX4/PowerComponentSummary.qml b/src/AutoPilotPlugins/PX4/PowerComponentSummary.qml index 90a87a6ef068..4cb53be08eec 100644 --- a/src/AutoPilotPlugins/PX4/PowerComponentSummary.qml +++ b/src/AutoPilotPlugins/PX4/PowerComponentSummary.qml @@ -23,29 +23,37 @@ import QGroundControl.Palette Item { anchors.fill: parent - QGCPalette { id: qgcPal; colorGroupEnabled: enabled } + property string _naString: qsTr("N/A") + FactPanelController { id: controller; } - property Fact batVChargedFact: controller.getParameterFact(-1, "BAT1_V_CHARGED") - property Fact batVEmptyFact: controller.getParameterFact(-1, "BAT1_V_EMPTY") - property Fact batCellsFact: controller.getParameterFact(-1, "BAT1_N_CELLS") + BatteryParams { + id: battParams + controller: controller + batteryIndex: 1 + } Column { anchors.fill: parent + VehicleSummaryRow { + labelText: qsTr("Battery Source") + valueText: battParams.battSource.enumStringValue + } + VehicleSummaryRow { labelText: qsTr("Battery Full") - valueText: batVChargedFact ? batVChargedFact.valueString + " " + batVChargedFact.units : "" + valueText: battParams.battHighVoltAvailable ? battParams.battHighVolt.valueString + " " + battParams.battHighVolt.units : _naString } VehicleSummaryRow { labelText: qsTr("Battery Empty") - valueText: batVEmptyFact ? batVEmptyFact.valueString + " " + batVEmptyFact.units : "" + valueText: battParams.battLowVoltAvailable ? battParams.battLowVolt.valueString + " " + battParams.battLowVolt.units : _naString } VehicleSummaryRow { labelText: qsTr("Number of Cells") - valueText: batCellsFact ? batCellsFact.valueString : "" + valueText: battParams.battNumCellsAvailable ? battParams.battNumCells.valueString : _naString } } } From eb2e351a5a27824f3f6e68c65e11009d36f23f43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20P=C3=A9rez=20Alonso?= Date: Mon, 16 Jun 2025 11:46:01 +0200 Subject: [PATCH 06/69] Revert "Fix radius reset when editing FW Landing Patterns" This reverts commit e0f6a0ad46a5f6f6e665cdac33fdcaa7bbfef2d7. --- src/QmlControls/FWLandingPatternEditor.qml | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/QmlControls/FWLandingPatternEditor.qml b/src/QmlControls/FWLandingPatternEditor.qml index 2de934c709a6..512658bb5247 100644 --- a/src/QmlControls/FWLandingPatternEditor.qml +++ b/src/QmlControls/FWLandingPatternEditor.qml @@ -41,7 +41,7 @@ Rectangle { property string _setToVehicleLocationStr: qsTr("Set to vehicle location") property bool _showCameraSection: !_missionVehicle.apmFirmware property int _altitudeMode: missionItem.altitudesAreRelative ? QGroundControl.AltitudeModeRelative : QGroundControl.AltitudeModeAbsolute - property real _previousLoiterRadius: missionItem.loiterRadius.rawValue + property real _previousLoiterRadius: 0 Column { id: editorColumn @@ -70,18 +70,16 @@ Rectangle { text: qsTr("Use loiter to altitude") fact: missionItem.useLoiterToAlt - Component.onCompleted: { - if (!missionItem.useLoiterToAlt.rawValue) { - _previousLoiterRadius = missionItem.loiterRadius.defaultValue - } - } - // When not using loiter to altitude, set radius to 0 to set the // glide slope heading correctly onCheckedChanged: { if (checked) { - // Restore the previous loiter radius - missionItem.loiterRadius.rawValue = _previousLoiterRadius + // Restore the previous loiter radius or set the default value + if (_previousLoiterRadius > 0) { + missionItem.loiterRadius.rawValue = _previousLoiterRadius + } else { + missionItem.loiterRadius.rawValue = missionItem.loiterRadius.defaultValue + } } else { _previousLoiterRadius = missionItem.loiterRadius.rawValue missionItem.loiterRadius.rawValue = 0 From 2718524b24aed596f8cfb51f992a0e0492fbaa62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20P=C3=A9rez=20Alonso?= Date: Mon, 16 Jun 2025 11:49:06 +0200 Subject: [PATCH 07/69] Revert "Fix waypoint glide slope heading calculations" This reverts commit ddd97adb7c4485a78faca0e4d4313c58e88dd366. --- src/QmlControls/FWLandingPatternEditor.qml | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/src/QmlControls/FWLandingPatternEditor.qml b/src/QmlControls/FWLandingPatternEditor.qml index 512658bb5247..472cf11ef1e2 100644 --- a/src/QmlControls/FWLandingPatternEditor.qml +++ b/src/QmlControls/FWLandingPatternEditor.qml @@ -41,7 +41,7 @@ Rectangle { property string _setToVehicleLocationStr: qsTr("Set to vehicle location") property bool _showCameraSection: !_missionVehicle.apmFirmware property int _altitudeMode: missionItem.altitudesAreRelative ? QGroundControl.AltitudeModeRelative : QGroundControl.AltitudeModeAbsolute - property real _previousLoiterRadius: 0 + Column { id: editorColumn @@ -69,22 +69,6 @@ Rectangle { FactCheckBox { text: qsTr("Use loiter to altitude") fact: missionItem.useLoiterToAlt - - // When not using loiter to altitude, set radius to 0 to set the - // glide slope heading correctly - onCheckedChanged: { - if (checked) { - // Restore the previous loiter radius or set the default value - if (_previousLoiterRadius > 0) { - missionItem.loiterRadius.rawValue = _previousLoiterRadius - } else { - missionItem.loiterRadius.rawValue = missionItem.loiterRadius.defaultValue - } - } else { - _previousLoiterRadius = missionItem.loiterRadius.rawValue - missionItem.loiterRadius.rawValue = 0 - } - } } GridLayout { From a48ffca5541bc07d0acfe4966d19f02069749d21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20P=C3=A9rez=20Alonso?= Date: Tue, 17 Jun 2025 13:51:13 +0200 Subject: [PATCH 08/69] MissionManager: Fix landing approach item handling Updated the LandingComplexItem recalculation logic to properly handle waypoint approach items, fixing several issues with heading calculations and when loading plans, such as the loiter radius being taken into account despite the "Use loiter to altitude" option being disabled. Refactored the relevant areas to unify the naming and logic between waypoint and loiter-based approaches. This replaces previous GUI-level workarounds (ddd97ad, e0f6a0a) that partially fixed these heading calculation problems by manipulating the loiter radius. This commit addresses the root cause instead, so it also fixes the issues for VTOL landing sequences, where the GUI workarounds hadn't yet been applied. Key changes: - Updated the coordinate recalculation logic to handle waypoint and loiter approaches as distinct modes, rather than forcing the waypoint approach through the loiter-based architecture. - Added _recalcFromApproachModeChange method to properly handle switching between loiter and waypoint approach items, instead of using the generic coordinate change handler. - Renamed "loiter tangent" to "slope start". - Updated all UI components to remove some explicit approach item-dependant logic which is no longer needed, and to use the new naming. --- .../FixedWingLandingComplexItem.cc | 4 +- src/MissionManager/LandingComplexItem.cc | 125 ++++++++++++------ src/MissionManager/LandingComplexItem.h | 11 +- src/MissionManager/VTOLLandingComplexItem.cc | 4 +- src/QmlControls/FWLandingPatternMapVisual.qml | 24 ++-- .../VTOLLandingPatternMapVisual.qml | 6 +- test/MissionManager/LandingComplexItemTest.cc | 8 +- test/MissionManager/LandingComplexItemTest.h | 4 +- 8 files changed, 115 insertions(+), 71 deletions(-) diff --git a/src/MissionManager/FixedWingLandingComplexItem.cc b/src/MissionManager/FixedWingLandingComplexItem.cc index f65b9aaea98b..462c8be2b507 100644 --- a/src/MissionManager/FixedWingLandingComplexItem.cc +++ b/src/MissionManager/FixedWingLandingComplexItem.cc @@ -173,8 +173,8 @@ void FixedWingLandingComplexItem::_updateFlightPathSegmentsDontCallDirectly(void _flightPathSegments.beginResetModel(); _flightPathSegments.clearAndDeleteContents(); if (useLoiterToAlt()->rawValue().toBool()) { - _appendFlightPathSegment(FlightPathSegment::SegmentTypeGeneric, finalApproachCoordinate(), amslEntryAlt(), loiterTangentCoordinate(), amslEntryAlt()); // Best we can do to simulate loiter circle terrain profile - _appendFlightPathSegment(FlightPathSegment::SegmentTypeLand, loiterTangentCoordinate(), amslEntryAlt(), landingCoordinate(), amslExitAlt()); + _appendFlightPathSegment(FlightPathSegment::SegmentTypeGeneric, finalApproachCoordinate(), amslEntryAlt(), slopeStartCoordinate(), amslEntryAlt()); // Best we can do to simulate loiter circle terrain profile + _appendFlightPathSegment(FlightPathSegment::SegmentTypeLand, slopeStartCoordinate(), amslEntryAlt(), landingCoordinate(), amslExitAlt()); } else { _appendFlightPathSegment(FlightPathSegment::SegmentTypeLand, finalApproachCoordinate(), amslEntryAlt(), landingCoordinate(), amslExitAlt()); } diff --git a/src/MissionManager/LandingComplexItem.cc b/src/MissionManager/LandingComplexItem.cc index 3c701b82c79e..1764059157cf 100644 --- a/src/MissionManager/LandingComplexItem.cc +++ b/src/MissionManager/LandingComplexItem.cc @@ -48,9 +48,10 @@ void LandingComplexItem::_init(void) connect(loiterRadius(), &Fact::valueChanged, this, &LandingComplexItem::_recalcFromRadiusChange); connect(loiterClockwise(), &Fact::rawValueChanged, this, &LandingComplexItem::_recalcFromRadiusChange); + connect(useLoiterToAlt(), &Fact::rawValueChanged, this, &LandingComplexItem::_recalcFromApproachModeChange); + connect(this, &LandingComplexItem::finalApproachCoordinateChanged,this, &LandingComplexItem::_recalcFromCoordinateChange); connect(this, &LandingComplexItem::landingCoordinateChanged, this, &LandingComplexItem::_recalcFromCoordinateChange); - connect(useLoiterToAlt(), &Fact::rawValueChanged, this, &LandingComplexItem::_recalcFromCoordinateChange); connect(finalApproachAltitude(), &Fact::valueChanged, this, &LandingComplexItem::_setDirty); connect(useDoChangeSpeed(), &Fact::valueChanged, this, &LandingComplexItem::_setDirty); @@ -81,10 +82,10 @@ void LandingComplexItem::_init(void) connect(this, &LandingComplexItem::wizardModeChanged, this, &LandingComplexItem::readyForSaveStateChanged); connect(this, &LandingComplexItem::finalApproachCoordinateChanged,this, &LandingComplexItem::complexDistanceChanged); - connect(this, &LandingComplexItem::loiterTangentCoordinateChanged,this, &LandingComplexItem::complexDistanceChanged); + connect(this, &LandingComplexItem::slopeStartCoordinateChanged, this, &LandingComplexItem::complexDistanceChanged); connect(this, &LandingComplexItem::landingCoordinateChanged, this, &LandingComplexItem::complexDistanceChanged); - connect(this, &LandingComplexItem::loiterTangentCoordinateChanged,this, &LandingComplexItem::_updateFlightPathSegmentsSignal); + connect(this, &LandingComplexItem::slopeStartCoordinateChanged, this, &LandingComplexItem::_updateFlightPathSegmentsSignal); connect(this, &LandingComplexItem::finalApproachCoordinateChanged,this, &LandingComplexItem::_updateFlightPathSegmentsSignal); connect(this, &LandingComplexItem::landingCoordinateChanged, this, &LandingComplexItem::_updateFlightPathSegmentsSignal); connect(finalApproachAltitude(), &Fact::valueChanged, this, &LandingComplexItem::_updateFlightPathSegmentsSignal); @@ -109,7 +110,7 @@ void LandingComplexItem::setLandingHeadingToTakeoffHeading() double LandingComplexItem::complexDistance(void) const { - return finalApproachCoordinate().distanceTo(loiterTangentCoordinate()) + loiterTangentCoordinate().distanceTo(landingCoordinate()); + return finalApproachCoordinate().distanceTo(slopeStartCoordinate()) + slopeStartCoordinate().distanceTo(landingCoordinate()); } void LandingComplexItem::setLandingCoordinate(const QGeoCoordinate& coordinate) @@ -159,25 +160,30 @@ void LandingComplexItem::_recalcFromHeadingAndDistanceChange(void) // distance // radius // Adjusted: - // loiter - // loiter tangent - // glide slope + // final approach + // slope start if (!_ignoreRecalcSignals && _landingCoordSet) { // These are our known values - double radius = loiterRadius()->rawValue().toDouble(); - double landToTangentDistance = landingDistance()->rawValue().toDouble(); + double distance = landingDistance()->rawValue().toDouble(); double heading = landingHeading()->rawValue().toDouble(); - // Heading is from loiter to land, hence +180 - _loiterTangentCoordinate = _landingCoordinate.atDistanceAndAzimuth(landToTangentDistance, heading + 180); + // Heading is from slope start to land, hence +180 + _slopeStartCoordinate = _landingCoordinate.atDistanceAndAzimuth(distance, heading + 180); + + if (useLoiterToAlt()->rawValue().toBool()) { + double radius = loiterRadius()->rawValue().toDouble(); + + // Loiter coord is 90 degrees counter clockwise from tangent coord + _finalApproachCoordinate = _slopeStartCoordinate.atDistanceAndAzimuth(radius, heading - 180 + (_loiterClockwise()->rawValue().toBool() ? -90 : 90)); + } else { + _finalApproachCoordinate = _slopeStartCoordinate; + } - // Loiter coord is 90 degrees counter clockwise from tangent coord - _finalApproachCoordinate = _loiterTangentCoordinate.atDistanceAndAzimuth(radius, heading - 180 + (_loiterClockwise()->rawValue().toBool() ? -90 : 90)); _finalApproachCoordinate.setAltitude(finalApproachAltitude()->rawValue().toDouble()); _ignoreRecalcSignals = true; - emit loiterTangentCoordinateChanged(_loiterTangentCoordinate); + emit slopeStartCoordinateChanged(_slopeStartCoordinate); emit finalApproachCoordinateChanged(_finalApproachCoordinate); emit coordinateChanged(_finalApproachCoordinate); _calcGlideSlope(); @@ -189,7 +195,7 @@ void LandingComplexItem::_recalcFromRadiusChange(void) { // Fixed: // land - // loiter tangent + // slope start // distance // radius // heading @@ -199,22 +205,22 @@ void LandingComplexItem::_recalcFromRadiusChange(void) if (!_ignoreRecalcSignals) { // These are our known values double radius = loiterRadius()->rawValue().toDouble(); - double landToTangentDistance = landingDistance()->rawValue().toDouble(); + double distance = landingDistance()->rawValue().toDouble(); double heading = landingHeading()->rawValue().toDouble(); double landToLoiterDistance = _landingCoordinate.distanceTo(_finalApproachCoordinate); if (landToLoiterDistance < radius) { // Degnenerate case: Move tangent to loiter point - _loiterTangentCoordinate = _finalApproachCoordinate; + _slopeStartCoordinate = _finalApproachCoordinate; - double heading = _landingCoordinate.azimuthTo(_loiterTangentCoordinate); + double heading = _landingCoordinate.azimuthTo(_slopeStartCoordinate); _ignoreRecalcSignals = true; landingHeading()->setRawValue(heading); - emit loiterTangentCoordinateChanged(_loiterTangentCoordinate); + emit slopeStartCoordinateChanged(_slopeStartCoordinate); _ignoreRecalcSignals = false; } else { - double landToLoiterDistance = qSqrt(qPow(radius, 2) + qPow(landToTangentDistance, 2)); + double landToLoiterDistance = qSqrt(qPow(radius, 2) + qPow(distance, 2)); double angleLoiterToTangent = qRadiansToDegrees(qAsin(radius/landToLoiterDistance)) * (_loiterClockwise()->rawValue().toBool() ? -1 : 1); _finalApproachCoordinate = _landingCoordinate.atDistanceAndAzimuth(landToLoiterDistance, heading + 180 + angleLoiterToTangent); @@ -228,43 +234,80 @@ void LandingComplexItem::_recalcFromRadiusChange(void) } } +void LandingComplexItem::_recalcFromApproachModeChange(void) +{ + // Fixed: + // land + // slope start + // heading + // distance + // Adjusted: + // final approach + + if (!_ignoreRecalcSignals && _landingCoordSet) { + if (useLoiterToAlt()->rawValue().toBool()) { + double radius = loiterRadius()->rawValue().toDouble(); + double offsetAngle = + landingHeading()->rawValue().toDouble() - 180 + + (_loiterClockwise()->rawValue().toBool() ? -90 : 90); + + _finalApproachCoordinate = + _slopeStartCoordinate.atDistanceAndAzimuth(radius, offsetAngle); + } else { + _finalApproachCoordinate = _slopeStartCoordinate; + } + + _finalApproachCoordinate.setAltitude(finalApproachAltitude()->rawValue().toDouble()); + + _ignoreRecalcSignals = true; + emit finalApproachCoordinateChanged(_finalApproachCoordinate); + emit coordinateChanged(_finalApproachCoordinate); + _calcGlideSlope(); + _ignoreRecalcSignals = false; + } +} + void LandingComplexItem::_recalcFromCoordinateChange(void) { // Fixed: // land - // loiter + // final approach // radius // Adjusted: - // loiter tangent // heading // distance - // glide slope + // slope start if (!_ignoreRecalcSignals && _landingCoordSet) { - // These are our known values - double radius = loiterRadius()->rawValue().toDouble(); - double landToLoiterDistance = _landingCoordinate.distanceTo(_finalApproachCoordinate); - double landToLoiterHeading = _landingCoordinate.azimuthTo(_finalApproachCoordinate); - - double landToTangentDistance; - if (landToLoiterDistance < radius) { - // Degenerate case, set tangent to loiter coordinate - _loiterTangentCoordinate = _finalApproachCoordinate; - landToTangentDistance = _landingCoordinate.distanceTo(_loiterTangentCoordinate); + double distance; + + if (useLoiterToAlt()->rawValue().toBool()) { + // These are our known values + double radius = loiterRadius()->rawValue().toDouble(); + double landToLoiterDistance = _landingCoordinate.distanceTo(_finalApproachCoordinate); + double landToLoiterHeading = _landingCoordinate.azimuthTo(_finalApproachCoordinate); + + if (landToLoiterDistance < radius) { + // Degenerate case: tangent at loiter coordinate + _slopeStartCoordinate = _finalApproachCoordinate; + distance = _landingCoordinate.distanceTo(_slopeStartCoordinate); + } else { + // Calculate tangent point using circle geometry + double loiterToTangentAngle = qRadiansToDegrees(qAsin(radius/landToLoiterDistance)) * (_loiterClockwise()->rawValue().toBool() ? 1 : -1); + distance = qSqrt(qPow(landToLoiterDistance, 2) - qPow(radius, 2)); + _slopeStartCoordinate = _landingCoordinate.atDistanceAndAzimuth(distance, landToLoiterHeading + loiterToTangentAngle); + } } else { - double loiterToTangentAngle = qRadiansToDegrees(qAsin(radius/landToLoiterDistance)) * (_loiterClockwise()->rawValue().toBool() ? 1 : -1); - landToTangentDistance = qSqrt(qPow(landToLoiterDistance, 2) - qPow(radius, 2)); - - _loiterTangentCoordinate = _landingCoordinate.atDistanceAndAzimuth(landToTangentDistance, landToLoiterHeading + loiterToTangentAngle); - + _slopeStartCoordinate = _finalApproachCoordinate; + distance = _landingCoordinate.distanceTo(_slopeStartCoordinate); } - double heading = _loiterTangentCoordinate.azimuthTo(_landingCoordinate); + double heading = _slopeStartCoordinate.azimuthTo(_landingCoordinate); _ignoreRecalcSignals = true; landingHeading()->setRawValue(heading); - landingDistance()->setRawValue(landToTangentDistance); - emit loiterTangentCoordinateChanged(_loiterTangentCoordinate); + landingDistance()->setRawValue(distance); + emit slopeStartCoordinateChanged(_slopeStartCoordinate); _calcGlideSlope(); _ignoreRecalcSignals = false; } diff --git a/src/MissionManager/LandingComplexItem.h b/src/MissionManager/LandingComplexItem.h index 52e5c56c2d43..0cc10b27a286 100644 --- a/src/MissionManager/LandingComplexItem.h +++ b/src/MissionManager/LandingComplexItem.h @@ -42,7 +42,7 @@ class LandingComplexItem : public ComplexMissionItem Q_PROPERTY(Fact* stopTakingPhotos READ stopTakingPhotos CONSTANT) Q_PROPERTY(Fact* stopTakingVideo READ stopTakingVideo CONSTANT) Q_PROPERTY(QGeoCoordinate finalApproachCoordinate READ finalApproachCoordinate WRITE setFinalApproachCoordinate NOTIFY finalApproachCoordinateChanged) - Q_PROPERTY(QGeoCoordinate loiterTangentCoordinate READ loiterTangentCoordinate NOTIFY loiterTangentCoordinateChanged) + Q_PROPERTY(QGeoCoordinate slopeStartCoordinate READ slopeStartCoordinate NOTIFY slopeStartCoordinateChanged) Q_PROPERTY(QGeoCoordinate landingCoordinate READ landingCoordinate WRITE setLandingCoordinate NOTIFY landingCoordinateChanged) Q_PROPERTY(bool altitudesAreRelative READ altitudesAreRelative WRITE setAltitudesAreRelative NOTIFY altitudesAreRelativeChanged) Q_PROPERTY(bool landingCoordSet READ landingCoordSet NOTIFY landingCoordSetChanged) @@ -77,7 +77,7 @@ class LandingComplexItem : public ComplexMissionItem bool landingCoordSet (void) const { return _landingCoordSet; } QGeoCoordinate landingCoordinate (void) const { return _landingCoordinate; } QGeoCoordinate finalApproachCoordinate (void) const { return _finalApproachCoordinate; } - QGeoCoordinate loiterTangentCoordinate (void) const { return _loiterTangentCoordinate; } + QGeoCoordinate slopeStartCoordinate (void) const { return _slopeStartCoordinate; } void setLandingCoordinate (const QGeoCoordinate& coordinate); void setFinalApproachCoordinate (const QGeoCoordinate& coordinate); @@ -131,7 +131,7 @@ class LandingComplexItem : public ComplexMissionItem signals: void finalApproachCoordinateChanged (QGeoCoordinate coordinate); - void loiterTangentCoordinateChanged (QGeoCoordinate coordinate); + void slopeStartCoordinateChanged (QGeoCoordinate coordinate); void landingCoordinateChanged (QGeoCoordinate coordinate); void landingCoordSetChanged (bool landingCoordSet); void altitudesAreRelativeChanged (bool altitudesAreRelative); @@ -176,7 +176,7 @@ protected slots: int _sequenceNumber = 0; bool _dirty = false; QGeoCoordinate _finalApproachCoordinate; - QGeoCoordinate _loiterTangentCoordinate; + QGeoCoordinate _slopeStartCoordinate; QGeoCoordinate _landingCoordinate; bool _landingCoordSet = false; bool _ignoreRecalcSignals = false; @@ -204,9 +204,10 @@ protected slots: private slots: void _recalcFromRadiusChange (void); + void _recalcFromApproachModeChange (void); void _signalLastSequenceNumberChanged (void); void _updateFinalApproachCoodinateAltitudeFromFact (void); - void _updateLandingCoodinateAltitudeFromFact (void); + void _updateLandingCoodinateAltitudeFromFact (void); friend class LandingComplexItemTest; }; diff --git a/src/MissionManager/VTOLLandingComplexItem.cc b/src/MissionManager/VTOLLandingComplexItem.cc index 6a2e3d1a8da9..0b8ccc71087b 100644 --- a/src/MissionManager/VTOLLandingComplexItem.cc +++ b/src/MissionManager/VTOLLandingComplexItem.cc @@ -143,8 +143,8 @@ void VTOLLandingComplexItem::_updateFlightPathSegmentsDontCallDirectly(void) _flightPathSegments.beginResetModel(); _flightPathSegments.clearAndDeleteContents(); if (useLoiterToAlt()->rawValue().toBool()) { - _appendFlightPathSegment(FlightPathSegment::SegmentTypeGeneric, finalApproachCoordinate(), amslEntryAlt(), loiterTangentCoordinate(), amslEntryAlt()); // Best we can do to simulate loiter circle terrain profile - _appendFlightPathSegment(FlightPathSegment::SegmentTypeGeneric, loiterTangentCoordinate(), amslEntryAlt(), landingCoordinate(), amslEntryAlt()); + _appendFlightPathSegment(FlightPathSegment::SegmentTypeGeneric, finalApproachCoordinate(), amslEntryAlt(), slopeStartCoordinate(), amslEntryAlt()); // Best we can do to simulate loiter circle terrain profile + _appendFlightPathSegment(FlightPathSegment::SegmentTypeGeneric, slopeStartCoordinate(), amslEntryAlt(), landingCoordinate(), amslEntryAlt()); } else { _appendFlightPathSegment(FlightPathSegment::SegmentTypeGeneric, finalApproachCoordinate(), amslEntryAlt(), landingCoordinate(), amslEntryAlt()); } diff --git a/src/QmlControls/FWLandingPatternMapVisual.qml b/src/QmlControls/FWLandingPatternMapVisual.qml index ea0cda77bf7f..81dcec1538d5 100644 --- a/src/QmlControls/FWLandingPatternMapVisual.qml +++ b/src/QmlControls/FWLandingPatternMapVisual.qml @@ -43,12 +43,12 @@ Item { property real _landingAltitudeMeters: _missionItem.landingAltitude.rawValue property real _finalApproachAltitudeMeters: _missionItem.finalApproachAltitude.rawValue property bool _useLoiterToAlt: _missionItem.useLoiterToAlt.rawValue - property real _landingAreaBearing: _missionItem.landingCoordinate.azimuthTo(_useLoiterToAlt ? _missionItem.loiterTangentCoordinate : _missionItem.finalApproachCoordinate) + property real _landingAreaBearing: _missionItem.landingCoordinate.azimuthTo(_missionItem.slopeStartCoordinate) function _calcGlideSlopeHeights() { var adjacent if (_useLoiterToAlt) { - adjacent = _missionItem.landingCoordinate.distanceTo(_missionItem.loiterTangentCoordinate) + adjacent = _missionItem.landingCoordinate.distanceTo(_missionItem.slopeStartCoordinate) } else { adjacent = _missionItem.landingCoordinate.distanceTo(_missionItem.finalApproachCoordinate) } @@ -106,7 +106,7 @@ Item { function _setFlightPath() { if (_useLoiterToAlt) { - _flightPath = [ _missionItem.loiterTangentCoordinate, _missionItem.landingCoordinate ] + _flightPath = [ _missionItem.slopeStartCoordinate, _missionItem.landingCoordinate ] } else { _flightPath = [ _missionItem.finalApproachCoordinate, _missionItem.landingCoordinate ] } @@ -177,7 +177,7 @@ Item { _setFlightPath() } - onLoiterTangentCoordinateChanged: { + onSlopeStartCoordinateChanged: { _calcGlideSlopeHeights() _setFlightPath() } @@ -400,7 +400,7 @@ Item { Connections { target: _missionItem onLandingCoordinateChanged: recalc() - onLoiterTangentCoordinateChanged: recalc() + onSlopeStartCoordinateChanged: recalc() onFinalApproachCoordinateChanged: recalc() } } @@ -433,7 +433,7 @@ Item { Connections { target: _missionItem onLandingCoordinateChanged: recalc() - onLoiterTangentCoordinateChanged: recalc() + onSlopeStartCoordinateChanged: recalc() onFinalApproachCoordinateChanged: recalc() } } @@ -457,7 +457,7 @@ Item { path = [ ] addCoordinate(_missionItem.landingCoordinate.atDistanceAndAzimuth(hypotenuse, _landingAreaBearing - angleDegrees)) addCoordinate(_missionItem.landingCoordinate.atDistanceAndAzimuth(hypotenuse, _landingAreaBearing + angleDegrees)) - addCoordinate(_useLoiterToAlt ? _missionItem.loiterTangentCoordinate : _missionItem.finalApproachCoordinate) + addCoordinate(_useLoiterToAlt ? _missionItem.slopeStartCoordinate : _missionItem.finalApproachCoordinate) } Component.onCompleted: recalc() @@ -465,7 +465,7 @@ Item { Connections { target: _missionItem onLandingCoordinateChanged: recalc() - onLoiterTangentCoordinateChanged: recalc() + onSlopeStartCoordinateChanged: recalc() onFinalApproachCoordinateChanged: recalc() } @@ -502,7 +502,7 @@ Item { Connections { target: _missionItem onLandingCoordinateChanged: recalc() - onLoiterTangentCoordinateChanged: recalc() + onSlopeStartCoordinateChanged: recalc() onFinalApproachCoordinateChanged: recalc() } } @@ -525,7 +525,7 @@ Item { function recalc() { var transitionCoordinate = _missionItem.landingCoordinate.atDistanceAndAzimuth(_landingLengthMeters / 2, _landingAreaBearing) - var halfDistance = transitionCoordinate.distanceTo(_useLoiterToAlt ? _missionItem.loiterTangentCoordinate : _missionItem.finalApproachCoordinate) / 2 + var halfDistance = transitionCoordinate.distanceTo(_useLoiterToAlt ? _missionItem.slopeStartCoordinate : _missionItem.finalApproachCoordinate) / 2 var centeredCoordinate = transitionCoordinate.atDistanceAndAzimuth(halfDistance, _landingAreaBearing) var angleIncrement = _landingAreaBearing > 180 ? -90 : 90 coordinate = centeredCoordinate.atDistanceAndAzimuth(_landingWidthMeters / 2, _landingAreaBearing + angleIncrement) @@ -536,7 +536,7 @@ Item { Connections { target: _missionItem onLandingCoordinateChanged: recalc() - onLoiterTangentCoordinateChanged: recalc() + onSlopeStartCoordinateChanged: recalc() onFinalApproachCoordinateChanged: recalc() } @@ -555,7 +555,7 @@ Item { anchorPoint.y: 0 z: QGroundControl.zOrderMapItems visible: _missionItem.isCurrentItem - coordinate: _useLoiterToAlt ? _missionItem.loiterTangentCoordinate : _missionItem.finalApproachCoordinate + coordinate: _missionItem.slopeStartCoordinate sourceItem: HeightIndicator { map: _root.map diff --git a/src/QmlControls/VTOLLandingPatternMapVisual.qml b/src/QmlControls/VTOLLandingPatternMapVisual.qml index b867381f739a..c2e2186d41ce 100644 --- a/src/QmlControls/VTOLLandingPatternMapVisual.qml +++ b/src/QmlControls/VTOLLandingPatternMapVisual.qml @@ -36,7 +36,7 @@ Item { property var _loiterPointObject property var _landingPointObject property bool _useLoiterToAlt: _missionItem.useLoiterToAlt.rawValue - property real _landingAreaBearing: _missionItem.landingCoordinate.azimuthTo(_useLoiterToAlt ? _missionItem.loiterTangentCoordinate : _missionItem.finalApproachCoordinate) + property real _landingAreaBearing: _missionItem.landingCoordinate.azimuthTo(_missionItem.slopeStartCoordinate) function hideItemVisuals() { objMgr.destroyObjects() @@ -81,7 +81,7 @@ Item { function _setFlightPath() { if (_useLoiterToAlt) { - _flightPath = [ _missionItem.loiterTangentCoordinate, _missionItem.landingCoordinate ] + _flightPath = [ _missionItem.slopeStartCoordinate, _missionItem.landingCoordinate ] } else { _flightPath = [ _missionItem.finalApproachCoordinate, _missionItem.landingCoordinate ] } @@ -146,7 +146,7 @@ Item { } onLandingCoordinateChanged: _setFlightPath() - onLoiterTangentCoordinateChanged: _setFlightPath() + onSlopeStartCoordinateChanged: _setFlightPath() onFinalApproachCoordinateChanged: _setFlightPath() } diff --git a/test/MissionManager/LandingComplexItemTest.cc b/test/MissionManager/LandingComplexItemTest.cc index 1a684fee306f..03bf3d9f0395 100644 --- a/test/MissionManager/LandingComplexItemTest.cc +++ b/test/MissionManager/LandingComplexItemTest.cc @@ -23,7 +23,7 @@ const char* SimpleLandingComplexItem::jsonComplexItemTypeValue = "utSimpleLandi LandingComplexItemTest::LandingComplexItemTest(void) { rgSignals[finalApproachCoordinateChangedIndex] = SIGNAL(finalApproachCoordinateChanged(QGeoCoordinate)); - rgSignals[loiterTangentCoordinateChangedIndex] = SIGNAL(loiterTangentCoordinateChanged(QGeoCoordinate)); + rgSignals[slopeStartCoordinateChangedIndex] = SIGNAL(slopeStartCoordinateChanged(QGeoCoordinate)); rgSignals[landingCoordinateChangedIndex] = SIGNAL(landingCoordinateChanged(QGeoCoordinate)); rgSignals[landingCoordSetChangedIndex] = SIGNAL(landingCoordSetChanged(bool)); rgSignals[altitudesAreRelativeChangedIndex] = SIGNAL(altitudesAreRelativeChanged(bool)); @@ -333,7 +333,7 @@ void LandingComplexItemTest::_validateItem(LandingComplexItem* actualItem, Landi QVERIFY(fuzzyCompareLatLon(actualItem->finalApproachCoordinate(), expectedItem->finalApproachCoordinate())); QVERIFY(fuzzyCompareLatLon(actualItem->landingCoordinate(), expectedItem->landingCoordinate())); if (actualItem->useLoiterToAlt()->rawValue().toBool()) { - QVERIFY(fuzzyCompareLatLon(actualItem->loiterTangentCoordinate(), expectedItem->loiterTangentCoordinate())); + QVERIFY(fuzzyCompareLatLon(actualItem->slopeStartCoordinate(), expectedItem->slopeStartCoordinate())); QCOMPARE(actualItem->loiterRadius()->rawValue().toInt(), expectedItem->loiterRadius()->rawValue().toInt()); QCOMPARE(actualItem->loiterClockwise()->rawValue().toBool(), expectedItem->loiterClockwise()->rawValue().toBool()); } @@ -398,8 +398,8 @@ void SimpleLandingComplexItem::_updateFlightPathSegmentsDontCallDirectly(void) _flightPathSegments.beginResetModel(); _flightPathSegments.clearAndDeleteContents(); if (useLoiterToAlt()->rawValue().toBool()) { - _appendFlightPathSegment(FlightPathSegment::SegmentTypeGeneric, finalApproachCoordinate(), amslEntryAlt(), loiterTangentCoordinate(), amslEntryAlt()); // Best we can do to simulate loiter circle terrain profile - _appendFlightPathSegment(FlightPathSegment::SegmentTypeLand, loiterTangentCoordinate(), amslEntryAlt(), landingCoordinate(), amslExitAlt()); + _appendFlightPathSegment(FlightPathSegment::SegmentTypeGeneric, finalApproachCoordinate(), amslEntryAlt(), slopeStartCoordinate(), amslEntryAlt()); // Best we can do to simulate loiter circle terrain profile + _appendFlightPathSegment(FlightPathSegment::SegmentTypeLand, slopeStartCoordinate(), amslEntryAlt(), landingCoordinate(), amslExitAlt()); } else { _appendFlightPathSegment(FlightPathSegment::SegmentTypeLand, finalApproachCoordinate(), amslEntryAlt(), landingCoordinate(), amslExitAlt()); } diff --git a/test/MissionManager/LandingComplexItemTest.h b/test/MissionManager/LandingComplexItemTest.h index 526fc49889ff..0f7d257a2abb 100644 --- a/test/MissionManager/LandingComplexItemTest.h +++ b/test/MissionManager/LandingComplexItemTest.h @@ -38,7 +38,7 @@ private slots: enum { finalApproachCoordinateChangedIndex = 0, - loiterTangentCoordinateChangedIndex, + slopeStartCoordinateChangedIndex, landingCoordinateChangedIndex, landingCoordSetChangedIndex, altitudesAreRelativeChangedIndex, @@ -48,7 +48,7 @@ private slots: enum { finalApproachCoordinateChangedMask = 1 << finalApproachCoordinateChangedIndex, - loiterTangentCoordinateChangedMask = 1 << loiterTangentCoordinateChangedIndex, + slopeStartCoordinateChangedMask = 1 << slopeStartCoordinateChangedIndex, landingCoordinateChangedMask = 1 << landingCoordinateChangedIndex, landingCoordSetChangedMask = 1 << landingCoordSetChangedIndex, altitudesAreRelativeChangedMask = 1 << altitudesAreRelativeChangedIndex, From cc994066b7005824968d38f5e82ea0b156a71375 Mon Sep 17 00:00:00 2001 From: Don Gagne Date: Mon, 23 Jun 2025 12:02:06 -0700 Subject: [PATCH 09/69] Rework Aux channel config to be single column * This makes it fit on small screens like Herelink * Tried supporting dynamic single/double columns based on size but too complicated --- .../Common/RadioComponent.qml | 22 +++++-------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/src/AutoPilotPlugins/Common/RadioComponent.qml b/src/AutoPilotPlugins/Common/RadioComponent.qml index fca76f6b127f..26cba3c01c4d 100644 --- a/src/AutoPilotPlugins/Common/RadioComponent.qml +++ b/src/AutoPilotPlugins/Common/RadioComponent.qml @@ -349,12 +349,10 @@ SetupPage { QGCLabel { text: qsTr("Additional Radio setup:") } - GridLayout { + ColumnLayout { id: switchSettingsGrid anchors.left: parent.left anchors.right: parent.right - columns: 2 - columnSpacing: ScreenTools.defaultFontPixelWidth Repeater { model: QGroundControl.multiVehicleManager.activeVehicle.px4Firmware ? @@ -363,20 +361,10 @@ SetupPage { [ "RC_MAP_FLAPS", "RC_MAP_AUX1", "RC_MAP_AUX2", "RC_MAP_PARAM1", "RC_MAP_PARAM2", "RC_MAP_PARAM3"]) : 0 - RowLayout { - Layout.fillWidth: true - - property Fact fact: controller.getParameterFact(-1, modelData) - - QGCLabel { - Layout.fillWidth: true - text: fact.shortDescription - } - FactComboBox { - width: ScreenTools.defaultFontPixelWidth * 15 - fact: parent.fact - indexModel: false - } + LabelledFactComboBox { + label: fact.shortDescription + fact: controller.getParameterFact(-1, modelData) + indexModel: false } } } From 324442037c9461dec0afbbe805f26ed64099fef2 Mon Sep 17 00:00:00 2001 From: Don Gagne Date: Tue, 24 Jun 2025 09:52:50 -0700 Subject: [PATCH 10/69] Use parameter metadata for rotations Fixes compass rotation setting off by one on rotation param set --- src/AutoPilotPlugins/PX4/SensorsSetup.qml | 47 ----------------------- 1 file changed, 47 deletions(-) diff --git a/src/AutoPilotPlugins/PX4/SensorsSetup.qml b/src/AutoPilotPlugins/PX4/SensorsSetup.qml index 7cbfef11b092..6cfd83976821 100644 --- a/src/AutoPilotPlugins/PX4/SensorsSetup.qml +++ b/src/AutoPilotPlugins/PX4/SensorsSetup.qml @@ -55,50 +55,6 @@ Item { // Used to pass help text to the preCalibrationDialog dialog property string preCalibrationDialogHelp - readonly property var rotations: [ - "ROTATION_NONE", - "ROTATION_YAW_45", - "ROTATION_YAW_90", - "ROTATION_YAW_135", - "ROTATION_YAW_180", - "ROTATION_YAW_225", - "ROTATION_YAW_270", - "ROTATION_YAW_315", - "ROTATION_ROLL_180", - "ROTATION_ROLL_180_YAW_45", - "ROTATION_ROLL_180_YAW_90", - "ROTATION_ROLL_180_YAW_135", - "ROTATION_PITCH_180", - "ROTATION_ROLL_180_YAW_225", - "ROTATION_ROLL_180_YAW_270", - "ROTATION_ROLL_180_YAW_315", - "ROTATION_ROLL_90", - "ROTATION_ROLL_90_YAW_45", - "ROTATION_ROLL_90_YAW_90", - "ROTATION_ROLL_90_YAW_135", - "ROTATION_ROLL_270", - "ROTATION_ROLL_270_YAW_45", - "ROTATION_ROLL_270_YAW_90", - "ROTATION_ROLL_270_YAW_135", - "ROTATION_PITCH_90", - "ROTATION_PITCH_270", - "ROTATION_PITCH_180_YAW_90", - "ROTATION_PITCH_180_YAW_270", - "ROTATION_ROLL_90_PITCH_90", - "ROTATION_ROLL_180_PITCH_90", - "ROTATION_ROLL_270_PITCH_90", - "ROTATION_ROLL_90_PITCH_180", - "ROTATION_ROLL_270_PITCH_180", - "ROTATION_ROLL_90_PITCH_270", - "ROTATION_ROLL_180_PITCH_270", - "ROTATION_ROLL_270_PITCH_270", - "ROTATION_ROLL_90_PITCH_180_YAW_90", - "ROTATION_ROLL_90_YAW_270", - "ROTATION_ROLL_90_PITCH_68_YAW_293", - "ROTATION_PITCH_315", - "ROTATION_ROLL_90_PITCH_315" - ] - property Fact cal_mag0_id: controller.getParameterFact(-1, "CAL_MAG0_ID") property Fact cal_mag1_id: controller.getParameterFact(-1, "CAL_MAG1_ID") property Fact cal_mag2_id: controller.getParameterFact(-1, "CAL_MAG2_ID") @@ -291,7 +247,6 @@ Item { FactComboBox { sizeToContents: true - model: rotations fact: sens_board_rot } @@ -348,7 +303,6 @@ Item { FactComboBox { sizeToContents: true - model: rotations fact: sens_board_rot } } @@ -369,7 +323,6 @@ Item { FactComboBox { sizeToContents: true - model: rotations fact: parent.calMagRotFact } } From 57a254f8ee2797ad4a5e13d29ae6c36f2a0f89cd Mon Sep 17 00:00:00 2001 From: Don Gagne Date: Tue, 24 Jun 2025 09:24:55 -0700 Subject: [PATCH 11/69] Hack fix for crash on usb disconnect vehicle removal --- src/QmlControls/MAVLinkChart.qml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/QmlControls/MAVLinkChart.qml b/src/QmlControls/MAVLinkChart.qml index 6051c41d6832..3bd776a47189 100644 --- a/src/QmlControls/MAVLinkChart.qml +++ b/src/QmlControls/MAVLinkChart.qml @@ -48,6 +48,16 @@ ChartView { } } + Connections { + target: QGroundControl.multiVehicleManager + + function onVehicleRemoved(vehicle) { + // Hack to prevent references to deleted QGCMavlinkSystem fields. https://github.com/mavlink/qgroundcontrol/issues/13077 + controller.deleteChart(chartController); + chartController = null; + } + } + DateTimeAxis { id: axisX min: chartController ? chartController.rangeXMin : new Date() From 9ff2a4d73e0a7fff2fff6adf5a9b7cc12bb7000b Mon Sep 17 00:00:00 2001 From: Don Gagne Date: Wed, 25 Jun 2025 13:02:21 -0700 Subject: [PATCH 12/69] Hack workaround for chart display without gstreamer compiled in --- src/QmlControls/MAVLinkChart.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/QmlControls/MAVLinkChart.qml b/src/QmlControls/MAVLinkChart.qml index 3bd776a47189..dfd134731a4a 100644 --- a/src/QmlControls/MAVLinkChart.qml +++ b/src/QmlControls/MAVLinkChart.qml @@ -31,7 +31,7 @@ ChartView { var serie = createSeries(ChartView.SeriesTypeLine, field.label) serie.axisX = axisX serie.axisY = axisY - serie.useOpenGL = true + serie.useOpenGL = QGroundControl.videoManager.gstreamerEnabled // Details on why here: https://github.com/mavlink/qgroundcontrol/issues/13068 serie.color = color serie.width = 1 chartController.addSeries(field, serie) From c026deab668282e3ca719cdcd489c694bc6e322b Mon Sep 17 00:00:00 2001 From: Don Gagne Date: Fri, 27 Jun 2025 13:08:33 -0700 Subject: [PATCH 13/69] Add StandardModes support to MockLink This supports request for standard modes. It will also on a delay report new modes available which should trigger a new mode query and ui update. --- src/Comms/MockLink/MockLink.cc | 112 +++++++++++++++++++++++++++ src/Comms/MockLink/MockLink.h | 19 ++++- src/FirmwarePlugin/FirmwarePlugin.cc | 3 +- src/Vehicle/StandardModes.cc | 2 +- 4 files changed, 133 insertions(+), 3 deletions(-) diff --git a/src/Comms/MockLink/MockLink.cc b/src/Comms/MockLink/MockLink.cc index f188c7eb175e..0716bcc01a8d 100644 --- a/src/Comms/MockLink/MockLink.cc +++ b/src/Comms/MockLink/MockLink.cc @@ -13,6 +13,7 @@ #include "MockLinkWorker.h" #include "QGCApplication.h" #include "QGCLoggingCategory.h" +#include "FirmwarePlugin.h" #include #include @@ -26,6 +27,26 @@ QGC_LOGGING_CATEGORY(MockLinkVerboseLog, "qgc.comms.mocklink.mocklink:verbose") int MockLink::_nextVehicleSystemId = 128; +QList MockLink::_availableFlightModes = { + // Mode Name Standard Mode Custom Mode CanBeSet adv + { "Manual", 0, PX4CustomMode::MANUAL, true, true }, + { "Stabilized", 0, PX4CustomMode::STABILIZED, true, true }, + { "Acro", 0, PX4CustomMode::ACRO, true, true }, + { "Altitude", 0, PX4CustomMode::ALTCTL, true, false}, + { "Offboard", 0, PX4CustomMode::OFFBOARD, true, true }, + { "Position", 0, PX4CustomMode::POSCTL_POSCTL, true, false}, + { "Orbit", 0, PX4CustomMode::POSCTL_ORBIT, false, true }, + { "Hold", 0, PX4CustomMode::AUTO_LOITER, true, true }, + { "Mission", 0, PX4CustomMode::AUTO_MISSION, true, true }, + { "Return", 0, PX4CustomMode::AUTO_RTL, true, true }, + { "Land", MAV_STANDARD_MODE_LAND, PX4CustomMode::AUTO_LAND, false, true }, + { "Precision Landing", 0, PX4CustomMode::AUTO_PRECLAND, true, true }, + { "Takeoff", MAV_STANDARD_MODE_TAKEOFF, PX4CustomMode::AUTO_TAKEOFF, false, false}, + { "MockLink Mode", 0, PX4CustomMode::RATTITUDE, true, false}, + { "(Mode not available)", 0, PX4CustomMode::AUTO_RTGS, false, false}, + { "MockLink Mode (delayed)",0, PX4CustomMode::AUTO_FOLLOW_TARGET, true, false}, +}; + MockLink::MockLink(SharedLinkConfigurationPtr &config, QObject *parent) : LinkInterface(config, parent) , _mockConfig(qobject_cast(_config.get())) @@ -129,6 +150,7 @@ void MockLink::run1HzTasks() _sendSysStatus(); _sendADSBVehicles(); _sendRemoteIDArmStatus(); + _sendAvailableModesMonitor(); // _sendVideoInfo(); if (!qgcApp()->runningUnitTests()) { // Sending RC Channels during unit test breaks RC tests which does it's own RC simulation @@ -140,6 +162,11 @@ void MockLink::run1HzTasks() _sendHomePositionDelayCount--; } else { _sendHomePosition(); + // We piggy back on this delay to signal we have new standard modes available + if (_availableModesMonitorSeqNumber == 0) { + qCDebug(MockLinkLog) << "Bumping sequence number for available modes monitor to trigger requery of modes"; + _availableModesMonitorSeqNumber = 1; + } } } @@ -171,6 +198,7 @@ void MockLink::run500HzTasks() if (_mavlinkStarted && _connected) { _paramRequestListWorker(); _logDownloadWorker(); + _availableModesWorker(); } } @@ -1726,6 +1754,27 @@ bool MockLink::_handleRequestMessage(const mavlink_command_long_t &request, bool return true; } + case MAVLINK_MSG_ID_AVAILABLE_MODES: + { + if (request.param2 == 0) { + // Request for available modes to be streamed out + if (_availableModesWorkerNextModeIndex != 0) { + qCWarning(MockLinkLog) << "MAVLINK_MSG_ID_AVAILABLE_MODES: _availableModesWorker already running - _availableModesWorkerNextModeIndex:" << _availableModesWorkerNextModeIndex; + return false; + } + qCDebug(MockLinkLog) << "MAVLINK_MSG_ID_AVAILABLE_MODES: starting available modes sequence worker"; + _availableModesWorkerNextModeIndex = 1; // Start with the first mode in sequence (1-based index) + } else { + // Request for specific mode + if (request.param2 > _availableFlightModes.count()) { + qCWarning(MockLinkLog) << "MAVLINK_MSG_ID_AVAILABLE_MODES: requested mode index out of range" << request.param2 << _availableFlightModes.count(); + return false; + } + qCDebug(MockLinkLog) << "MAVLINK_MSG_ID_AVAILABLE_MODES: received specific mode request for index" << request.param2; + _availableModesWorkerNextModeIndex = -request.param2; // Negative index indicates a specific single mode request + } + return true; + } } return false; @@ -1859,3 +1908,66 @@ void MockLink::_sendVideoInfo() } } } + +void MockLink::_sendAvailableMode(uint8_t modeIndexOneBased) +{ + if (modeIndexOneBased > _availableModesCount()) { + qCWarning(MockLinkLog) << "modeIndexOneBased out of range" << modeIndexOneBased << _availableModesCount(); + return; + } + + qCDebug(MockLinkLog) << "_sendAvailableMode modeIndexOneBased:" << modeIndexOneBased; + + const FlightMode_t &availableMode = _availableFlightModes[modeIndexOneBased - 1]; + mavlink_message_t msg{}; + + (void) mavlink_msg_available_modes_pack_chan( + _vehicleSystemId, + _vehicleComponentId, + mavlinkChannel(), + &msg, + _availableModesCount(), + modeIndexOneBased, + availableMode.standard_mode, + availableMode.custom_mode, + availableMode.canBeSet ? 0 : MAV_MODE_PROPERTY_NOT_USER_SELECTABLE, + availableMode.name); + respondWithMavlinkMessage(msg); +} + +void MockLink::_availableModesWorker() +{ + if (_availableModesWorkerNextModeIndex == 0) { + // Not active + return; + } + + _sendAvailableMode(qAbs(_availableModesWorkerNextModeIndex)); + + if (_availableModesWorkerNextModeIndex < 0) { + // Single mode request, stop worker + _availableModesWorkerNextModeIndex = 0; + } else if (++_availableModesWorkerNextModeIndex > _availableModesCount()) { + // All modes sent, stop worker + _availableModesWorkerNextModeIndex = 0; + qCDebug(MockLinkLog) << "_availableModesWorker: all modes sent, stopping worker"; + } +} + +void MockLink::_sendAvailableModesMonitor() +{ + mavlink_message_t msg{}; + + (void) mavlink_msg_available_modes_monitor_pack_chan( + _vehicleSystemId, + _vehicleComponentId, + mavlinkChannel(), + &msg, + _availableModesMonitorSeqNumber); + respondWithMavlinkMessage(msg); +} + +int MockLink::_availableModesCount() const +{ + return _availableFlightModes.count() - (_availableModesMonitorSeqNumber == 0 ? 1 : 0); // Exclude the delayed mode +} diff --git a/src/Comms/MockLink/MockLink.h b/src/Comms/MockLink/MockLink.h index c13fbb95d655..56a76984ee30 100644 --- a/src/Comms/MockLink/MockLink.h +++ b/src/Comms/MockLink/MockLink.h @@ -121,6 +121,14 @@ private slots: void _writeBytesQueued(const QByteArray &bytes); private: + typedef struct { + const char *name; + uint8_t standard_mode; + uint32_t custom_mode; + bool canBeSet; + bool advanced; + } FlightMode_t; + bool _connect() final; bool _allocateMavlinkChannel() final; void _freeMavlinkChannel() final; @@ -172,10 +180,13 @@ private slots: void _sendGeneralMetaData(); void _sendRemoteIDArmStatus(); void _sendVideoInfo(); + void _sendAvailableModesMonitor(); - /// Sends the next parameter to the vehicle void _paramRequestListWorker(); void _logDownloadWorker(); + void _availableModesWorker(); + void _sendAvailableMode(uint8_t modeIndexOneBased); + int _availableModesCount() const; void _moveADSBVehicle(int vehicleIndex); static MockLink *_startMockLinkWorker(const QString &configName, MAV_AUTOPILOT firmwareType, MAV_TYPE vehicleType, bool sendStatusText, MockConfiguration::FailureMode_t failureMode); @@ -233,6 +244,10 @@ private slots: int _currentParamRequestListComponentIndex = -1; ///< Current component index for param request list workflow, -1 for no request in progress int _currentParamRequestListParamIndex = -1; ///< Current parameter index for param request list workflow + // Mavlink standard modes worker information + int _availableModesWorkerNextModeIndex = 0; ///< 0: not active, +index: next mode the send in sequence, -index: send a single mode (indices are 1-based) + uint8_t _availableModesMonitorSeqNumber = 0; ///< Sequence number for the next available mode message to send + QString _logDownloadFilename; ///< Filename for log download which is in progress uint32_t _logDownloadCurrentOffset = 0; ///< Current offset we are sending from uint32_t _logDownloadBytesRemaining = 0; ///< Number of bytes still to send, 0 = send inactive @@ -269,4 +284,6 @@ private slots: static constexpr uint32_t _logDownloadFileSize = 1000; ///< Size of simulated log file static constexpr bool _mavlinkStarted = true; + + static QList _availableFlightModes; }; diff --git a/src/FirmwarePlugin/FirmwarePlugin.cc b/src/FirmwarePlugin/FirmwarePlugin.cc index 27525dc34aa2..a10812aeb1e0 100644 --- a/src/FirmwarePlugin/FirmwarePlugin.cc +++ b/src/FirmwarePlugin/FirmwarePlugin.cc @@ -470,7 +470,8 @@ void FirmwarePlugin::_addNewFlightMode(FirmwareFlightMode &newFlightMode) { for (const FirmwareFlightMode &existingFlightMode : _flightModeList) { if (existingFlightMode.custom_mode == newFlightMode.custom_mode) { - // Already exists + qCDebug(FirmwarePluginLog) << "Flight Mode:" << newFlightMode.mode_name << " Custom Mode:" << newFlightMode.custom_mode + << " already exists, not adding again."; return; } } diff --git a/src/Vehicle/StandardModes.cc b/src/Vehicle/StandardModes.cc index c385c98aba5e..4a031dc8367c 100644 --- a/src/Vehicle/StandardModes.cc +++ b/src/Vehicle/StandardModes.cc @@ -100,7 +100,7 @@ void StandardModes::gotMessage(MAV_RESULT result, const mavlink_message_t &messa requestMode(availableModes.mode_index + 1); } } else { - qCDebug(StandardModesLog) << "Failed to retrieve available modes" << result; + qCDebug(StandardModesLog) << "Failed to retrieve available modes - REQUEST_MESSAGE:MAV_RESULT" << result; emit requestCompleted(); } } From 34aee6e3006ec75c40a6f38f3b6de7ed96c217d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20P=C3=A9rez=20Alonso?= Date: Thu, 5 Jun 2025 16:58:53 +0200 Subject: [PATCH 14/69] FlightMap: Fix drawing of very long mission lines This fixes a bug where extremely long legs in flight plans were drawn as straight lines on the map, instead of following the great circle path. Key changes: - Break down mission legs longer than 50km into smaller segments that follow the great circle path. - Update leg arrow heading calculation to account for changing bearing along great circle paths. --- src/FlightMap/MapItems/MapLineArrow.qml | 4 +-- src/FlightMap/MapItems/MissionLineView.qml | 31 +++++++++++++++++++++- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/FlightMap/MapItems/MapLineArrow.qml b/src/FlightMap/MapItems/MapLineArrow.qml index 89bcaed36f43..b46bb0921294 100644 --- a/src/FlightMap/MapItems/MapLineArrow.qml +++ b/src/FlightMap/MapItems/MapLineArrow.qml @@ -30,9 +30,9 @@ MapQuickItem { function _updateArrowDetails() { if (fromCoord && fromCoord.isValid && toCoord && toCoord.isValid) { - _arrowHeading = fromCoord.azimuthTo(toCoord) var lineDistanceQuarter = fromCoord.distanceTo(toCoord) / 4 - coordinate = fromCoord.atDistanceAndAzimuth(lineDistanceQuarter * arrowPosition, _arrowHeading) + coordinate = fromCoord.atDistanceAndAzimuth(lineDistanceQuarter * arrowPosition, fromCoord.azimuthTo(toCoord)) + _arrowHeading = coordinate.azimuthTo(toCoord) // Account for changing bearing along great circle path } else { coordinate = QtPositioning.coordinate() _arrowHeading = 0 diff --git a/src/FlightMap/MapItems/MissionLineView.qml b/src/FlightMap/MapItems/MissionLineView.qml index ac8c01633c15..1121297dc014 100644 --- a/src/FlightMap/MapItems/MissionLineView.qml +++ b/src/FlightMap/MapItems/MissionLineView.qml @@ -24,9 +24,38 @@ MapItemView { "red" : (false/*showSpecialVisual*/ ? "green" : QGroundControl.globalPalette.mapMissionTrajectory) z: QGroundControl.zOrderWaypointLines - path: object && object.coordinate1.isValid && object.coordinate2.isValid ? [ object.coordinate1, object.coordinate2 ] : [] + path: _calcMissionLinePath() property bool _terrainCollision: object && object.terrainCollision property bool _showSpecialVisual: object && showSpecialVisual && object.specialVisual + + readonly property real _maxSegmentLengthM: 50000 // 50 km + + function _calcMissionLinePath() { + if (!object || !object.coordinate1.isValid || !object.coordinate2.isValid) { + return [] + } + + var coord1 = object.coordinate1 + var coord2 = object.coordinate2 + + var distance = coord1.distanceTo(coord2) + if (distance <= _maxSegmentLengthM) { + return [coord1, coord2] + } + + // For longer distances, draw great circle path + var pathPoints = [coord1] + var numSegments = Math.ceil(distance / _maxSegmentLengthM) + + for (var i = 1; i < numSegments; i++) { + var segmentDist = (i * distance) / numSegments + var interpolatedCoord = coord1.atDistanceAndAzimuth(segmentDist, coord1.azimuthTo(coord2)) + pathPoints.push(interpolatedCoord) + } + + pathPoints.push(coord2) + return pathPoints + } } } From 4b360ca8cdd4394f4d9f8053b47f9edcd759ec86 Mon Sep 17 00:00:00 2001 From: Holden Ramsey Date: Mon, 30 Jun 2025 12:09:21 -0400 Subject: [PATCH 15/69] CMake: Verify NDK Version --- .github/actions/qt-android/action.yml | 2 +- CMakeLists.txt | 30 ++++++++++++++--------- cmake/Prechecks.cmake | 34 --------------------------- 3 files changed, 20 insertions(+), 46 deletions(-) delete mode 100644 cmake/Prechecks.cmake diff --git a/.github/actions/qt-android/action.yml b/.github/actions/qt-android/action.yml index 947efba96d0e..e28b66cea251 100644 --- a/.github/actions/qt-android/action.yml +++ b/.github/actions/qt-android/action.yml @@ -42,7 +42,7 @@ runs: uses: nttld/setup-ndk@v1 id: setup-ndk with: - ndk-version: r26b + ndk-version: ${{ inputs.version == '6.8.3' && 'r26b' || 'r25b' }} add-to-path: false - run: | diff --git a/CMakeLists.txt b/CMakeLists.txt index 4feb91a0dc23..a667722c8e11 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,8 +31,6 @@ endif() # Project Info ####################################################### -# include(Prechecks) - # set(CMAKE_OSX_ARCHITECTURES "arm64") # set(CMAKE_OSX_SYSROOT "iphoneos") if(APPLE) @@ -415,21 +413,24 @@ elseif(APPLE) qt_add_ios_ffmpeg_libraries(${CMAKE_PROJECT_NAME}) endif() elseif(ANDROID) - CPMAddPackage( - NAME android_openssl - URL https://github.com/KDAB/android_openssl/archive/refs/heads/master.zip - ) - include(${android_openssl_SOURCE_DIR}/android_openssl.cmake) - add_android_openssl_libraries(${CMAKE_PROJECT_NAME}) + if(${Qt6_VERSION} VERSION_EQUAL 6.6.3) + if(NOT ${CMAKE_ANDROID_NDK_VERSION} VERSION_EQUAL 25.1) + message(FATAL_ERROR "Invalid NDK Version: ${CMAKE_ANDROID_NDK_VERSION}, Use Version 25B instead.") + endif() + elseif(${Qt6_VERSION} VERSION_EQUAL 6.8.3) + if(NOT ${CMAKE_ANDROID_NDK_VERSION} VERSION_EQUAL 26.1) + message(FATAL_ERROR "Invalid NDK Version: ${CMAKE_ANDROID_NDK_VERSION}, Use Version 26B instead.") + endif() + endif() # Generation of android version numbers must be consistent release to release such that they are always increasing - if(${PROJECT_VERSION_MAJOR} GREATER 9) + if(${CMAKE_PROJECT_VERSION_MAJOR} GREATER 9) message(FATAL_ERROR "Major version larger than 1 digit: ${CMAKE_PROJECT_VERSION_MAJOR}") endif() - if(${PROJECT_VERSION_MINOR} GREATER 9) + if(${CMAKE_PROJECT_VERSION_MINOR} GREATER 9) message(FATAL_ERROR "Minor version larger than 1 digit: ${CMAKE_PROJECT_VERSION_MINOR}") endif() - if(${PROJECT_VERSION_PATCH} GREATER 99) + if(${CMAKE_PROJECT_VERSION_PATCH} GREATER 99) message(FATAL_ERROR "Patch version larger than 2 digits: ${CMAKE_PROJECT_VERSION_PATCH}") endif() @@ -475,6 +476,13 @@ elseif(ANDROID) # endif() list(APPEND QT_ANDROID_MULTI_ABI_FORWARD_VARS QGC_STABLE_BUILD QT_HOST_PATH) + + CPMAddPackage( + NAME android_openssl + URL https://github.com/KDAB/android_openssl/archive/refs/heads/master.zip + ) + include(${android_openssl_SOURCE_DIR}/android_openssl.cmake) + add_android_openssl_libraries(${CMAKE_PROJECT_NAME}) endif() target_compile_definitions(${CMAKE_PROJECT_NAME} diff --git a/cmake/Prechecks.cmake b/cmake/Prechecks.cmake deleted file mode 100644 index 7f6b27239211..000000000000 --- a/cmake/Prechecks.cmake +++ /dev/null @@ -1,34 +0,0 @@ -set(QT_DEFAULT_MAJOR_VERSION 6) - -find_program(QMAKE_EXECUTABLE - NAMES qmake6 - HINTS ${QT_HOST_PATH} ${QT_ROOT_DIR} ${QTDIR} - ENV QTDIR - PATH_SUFFIXES bin -) - -if(NOT QMAKE_EXECUTABLE) - message(FATAL_ERROR "qmake6 not found. Please set QT_ROOT_DIR or QTDIR correctly.") -endif() - -execute_process( - COMMAND "${QMAKE_EXECUTABLE}" -query QT_VERSION - OUTPUT_VARIABLE QT_VERSION - OUTPUT_STRIP_TRAILING_WHITESPACE - RESULT_VARIABLE _qmake_query_res -) -if(_qmake_query_res) - message(FATAL_ERROR "Failed to run ${QMAKE_EXECUTABLE} -query QT_VERSION") -endif() - -if(QT_VERSION VERSION_LESS "${QGC_QT_MINIMUM_VERSION}") - message(FATAL_ERROR - "Qt version too old: need ≥ ${QGC_QT_MINIMUM_VERSION}, " - "found ${QT_VERSION}" - ) -elseif(QT_VERSION VERSION_GREATER "${QGC_QT_MAXIMUM_VERSION}") - message(FATAL_ERROR - "Qt version too new: need ≤ ${QGC_QT_MAXIMUM_VERSION}, " - "found ${QT_VERSION}" - ) -endif() From 556610c2aebf92a5835d6212e05e014bcc88a2cb Mon Sep 17 00:00:00 2001 From: Holden Date: Sat, 28 Jun 2025 17:50:00 -0400 Subject: [PATCH 16/69] CMake: Force Upper Limit for GStreamer --- cmake/find-modules/FindGStreamer.cmake | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cmake/find-modules/FindGStreamer.cmake b/cmake/find-modules/FindGStreamer.cmake index 8bc92bfc4a7e..d7bf04d042b2 100644 --- a/cmake/find-modules/FindGStreamer.cmake +++ b/cmake/find-modules/FindGStreamer.cmake @@ -1,8 +1,6 @@ if(NOT DEFINED GStreamer_FIND_VERSION) if(LINUX) set(GStreamer_FIND_VERSION 1.20) - # elseif(ANDROID) - # set(GStreamer_FIND_VERSION 1.26.2) else() set(GStreamer_FIND_VERSION 1.22.12) endif() @@ -299,7 +297,11 @@ endif() find_package(PkgConfig REQUIRED QUIET) list(PREPEND CMAKE_PREFIX_PATH ${GStreamer_ROOT_DIR}) -pkg_check_modules(PC_GSTREAMER REQUIRED gstreamer-1.0>=${GStreamer_FIND_VERSION}) +if(LINUX) + pkg_check_modules(PC_GSTREAMER REQUIRED gstreamer-1.0>=${GStreamer_FIND_VERSION}) +else() + pkg_check_modules(PC_GSTREAMER REQUIRED gstreamer-1.0=${GStreamer_FIND_VERSION}) +endif() set(GStreamer_VERSION "${PC_GSTREAMER_VERSION}") ################################################################################ From 7d2a97d73b420b13054bb9c0f02aa2f56077f3e0 Mon Sep 17 00:00:00 2001 From: Niki-dev12 Date: Tue, 1 Jul 2025 13:18:33 +0200 Subject: [PATCH 17/69] Fix: Improve auto stream handling - Ensure stream info is updated for all video receivers, including thermal streams. - Update auto-stream configuration logic for reliability when changing video sources or camera settings. - Motivation: Fixes issue where video stream would not update properly in the UI after switching cameras or stream types. --- src/Camera/QGCCameraManager.cc | 1 + src/VideoManager/VideoManager.cc | 24 +++++++++++++++++++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/Camera/QGCCameraManager.cc b/src/Camera/QGCCameraManager.cc index 00d9ff062f7a..fb793f4ca8c3 100644 --- a/src/Camera/QGCCameraManager.cc +++ b/src/Camera/QGCCameraManager.cc @@ -365,6 +365,7 @@ QGCCameraManager::_handleVideoStreamInfo(const mavlink_message_t& message) mavlink_video_stream_information_t streamInfo; mavlink_msg_video_stream_information_decode(&message, &streamInfo); pCamera->handleVideoInfo(&streamInfo); + emit streamChanged(); } } diff --git a/src/VideoManager/VideoManager.cc b/src/VideoManager/VideoManager.cc index ae254767b690..046c98266094 100644 --- a/src/VideoManager/VideoManager.cc +++ b/src/VideoManager/VideoManager.cc @@ -345,9 +345,24 @@ bool VideoManager::isStreamSource() const void VideoManager::_videoSourceChanged() { bool changed = false; - - for (VideoReceiver *receiver : std::as_const(_videoReceivers)) { - changed |= _updateSettings(receiver); + if (_activeVehicle) { + QGCCameraManager* camMgr = _activeVehicle->cameraManager(); + for (VideoReceiver *receiver : std::as_const(_videoReceivers)) { + QGCVideoStreamInfo* info = nullptr; + if (receiver->isThermal()) { + info = camMgr ? camMgr->thermalStreamInstance() : nullptr; + } else { + info = camMgr ? camMgr->currentStreamInstance() : nullptr; + } + // Assign stream info + receiver->setVideoStreamInfo(info); + changed |= _updateSettings(receiver); + } + } else { + for (VideoReceiver *receiver : std::as_const(_videoReceivers)) { + receiver->setVideoStreamInfo(nullptr); + changed |= _updateSettings(receiver); + } } if (changed) { @@ -416,6 +431,9 @@ bool VideoManager::_updateAutoStream(VideoReceiver *receiver) case VIDEO_STREAM_TYPE_RTSP: source = VideoSettings::videoSourceRTSP; url = pInfo->uri(); + if (source == VideoSettings::videoSourceRTSP) { + _videoSettings->rtspUrl()->setRawValue(url); + } break; case VIDEO_STREAM_TYPE_TCP_MPEG: source = VideoSettings::videoSourceTCP; From e919f8b92ba284e48eaed24fccee3cda79ab6133 Mon Sep 17 00:00:00 2001 From: Don Gagne Date: Wed, 2 Jul 2025 09:05:15 -0700 Subject: [PATCH 18/69] Allow manual zoom level adjust to increase unbounded This allows you to go past where tiles exists but mirrors how 4.4 works. It allows you to go as far as possible zoomed in. --- src/FlightMap/FlightMap.qml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/FlightMap/FlightMap.qml b/src/FlightMap/FlightMap.qml index 7cae7d31db2f..c235d265c5ac 100644 --- a/src/FlightMap/FlightMap.qml +++ b/src/FlightMap/FlightMap.qml @@ -39,8 +39,6 @@ Map { property bool firstVehiclePositionReceived: false ///< true: first vehicle position update was responded to property bool planView: false ///< true: map being using for Plan view, items should be draggable - readonly property real maxZoomLevel: 20 - property var _activeVehicle: QGroundControl.multiVehicleManager.activeVehicle property var _activeVehicleCoordinate: _activeVehicle ? _activeVehicle.coordinate : QtPositioning.coordinate() @@ -49,6 +47,7 @@ Map { // This works around a bug on Qt where if you set a visibleRegion and then the user moves or zooms the map // and then you set the same visibleRegion the map will not move/scale appropriately since it thinks there // is nothing to do. + let maxZoomLevel = 20 _map.visibleRegion = QtPositioning.rectangle(QtPositioning.coordinate(0, 0), QtPositioning.coordinate(0, 0)) _map.visibleRegion = region if (_map.zoomLevel > maxZoomLevel) { @@ -135,7 +134,7 @@ Map { } } onScaleChanged: (delta) => { - let newZoomLevel = Math.min(Math.max(_map.zoomLevel + Math.log2(delta), 0), maxZoomLevel) + let newZoomLevel = Math.max(_map.zoomLevel + Math.log2(delta), 0) _map.zoomLevel = newZoomLevel _map.alignCoordinateToPoint(pinchStartCentroid, pinchHandler.centroid.position) } @@ -152,11 +151,6 @@ Map { } - BoundaryRule on zoomLevel { - minimum: 0 - maximum: maxZoomLevel - } - // We specifically do not use a DragHandler for panning. It just causes too many problems if you overlay anything else like a Flickable above it. // Causes all sorts of crazy problems where dragging/scrolling no longerr works on items above in the hierarchy. // Since we are using a MouseArea we also can't use TapHandler for clicks. So we handle that here as well. From f969cfc9e9f48813a825b9e80be8ee3d099584d3 Mon Sep 17 00:00:00 2001 From: Don Gagne Date: Wed, 2 Jul 2025 15:48:48 -0700 Subject: [PATCH 19/69] Force android soft keyboard to close with Done instead of Next --- src/QmlControls/QGCTextField.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/QmlControls/QGCTextField.qml b/src/QmlControls/QGCTextField.qml index 892e8256683d..c35b63e8829f 100644 --- a/src/QmlControls/QGCTextField.qml +++ b/src/QmlControls/QGCTextField.qml @@ -21,6 +21,7 @@ TextField { rightPadding: _marginPadding + unitsHelpLayout.width topPadding: _marginPadding bottomPadding: _marginPadding + EnterKey.type: Qt.EnterKeyDone property bool showUnits: false property bool showHelp: false From 192b5e62c0181caeca876bdc3871e55b3c8cb456 Mon Sep 17 00:00:00 2001 From: Don Gagne Date: Thu, 3 Jul 2025 11:24:57 -0700 Subject: [PATCH 20/69] Better naming of CUSTOM_DIRECTORIES and support for custom Qt components --- CMakeLists.txt | 3 ++- custom-example/CMakeLists.txt | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a667722c8e11..c80bbe835da9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -522,9 +522,10 @@ if(QGC_BUILD_TESTING) endif() if(QGC_CUSTOM_BUILD) + find_package(Qt6 REQUIRED COMPONENTS ${CUSTOM_QT_COMPONENTS}) target_sources(${CMAKE_PROJECT_NAME} PRIVATE ${CUSTOM_SOURCES}) target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE ${CUSTOM_LIBRARIES}) - target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE ${CUSTOM_DIRECTORIES}) + target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE ${CUSTOM_INCLUDE_DIRECTORIES}) endif() file(GLOB TS_SOURCES ${CMAKE_SOURCE_DIR}/translations/qgc_*.ts) diff --git a/custom-example/CMakeLists.txt b/custom-example/CMakeLists.txt index baa480ad47af..de087737bc0f 100644 --- a/custom-example/CMakeLists.txt +++ b/custom-example/CMakeLists.txt @@ -84,7 +84,7 @@ set(CUSTOM_LIBRARIES CACHE INTERNAL "" FORCE ) -set(CUSTOM_DIRECTORIES +set(CUSTOM_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR}/src ${CMAKE_CURRENT_SOURCE_DIR}/src/AutoPilotPlugin ${CMAKE_CURRENT_SOURCE_DIR}/src/FirmwarePlugin From c7b8a04d9f724e68e76bfc31850cef92ee2a2aaf Mon Sep 17 00:00:00 2001 From: Don Gagne Date: Thu, 3 Jul 2025 11:25:07 -0700 Subject: [PATCH 21/69] Updates for 5.0 --- .github/workflows/docs_deploy.yml | 2 -- docs/.vitepress/config.mjs | 4 +++ docs/en/SUMMARY.md | 2 +- .../custom_build/create_repos.md | 15 ----------- .../qgc-dev-guide/custom_build/fork_repo.md | 14 ++++++++++ .../custom_build/resource_override.md | 27 +++++-------------- 6 files changed, 26 insertions(+), 38 deletions(-) delete mode 100644 docs/en/qgc-dev-guide/custom_build/create_repos.md create mode 100644 docs/en/qgc-dev-guide/custom_build/fork_repo.md diff --git a/.github/workflows/docs_deploy.yml b/.github/workflows/docs_deploy.yml index 0088ff0245ed..ee2a319e1cf5 100644 --- a/.github/workflows/docs_deploy.yml +++ b/.github/workflows/docs_deploy.yml @@ -5,8 +5,6 @@ on: branches: - master - 'Stable*' - tags: - - 'v*' paths: - 'docs/**' - 'package*.json' diff --git a/docs/.vitepress/config.mjs b/docs/.vitepress/config.mjs index abcc51bc2512..5e340471ee71 100644 --- a/docs/.vitepress/config.mjs +++ b/docs/.vitepress/config.mjs @@ -158,6 +158,10 @@ export default defineConfig({ text: "master", link: "https://docs.qgroundcontrol.com/master/en/", }, + { + text: "v5.0", + link: "https://docs.qgroundcontrol.com/Stable_V5.0/en/", + }, { text: "v4.4", link: "https://docs.qgroundcontrol.com/Stable_V4.4/en/", diff --git a/docs/en/SUMMARY.md b/docs/en/SUMMARY.md index 1000e9ea1ccc..91e42f7a3d38 100644 --- a/docs/en/SUMMARY.md +++ b/docs/en/SUMMARY.md @@ -103,7 +103,7 @@ - [Mock Link](qgc-dev-guide/tools/mock_link.md) - [Command Line Options](qgc-dev-guide/command_line_options.md) - [Custom Builds](qgc-dev-guide/custom_build/custom_build.md) - - [Initial Repository Setup For Custom Build](qgc-dev-guide/custom_build/create_repos.md) + - [Initial Repository Setup For Custom Build](qgc-dev-guide/custom_build/fork_repo.md) - [Custom Build Plugins](qgc-dev-guide/custom_build/plugins.md) - [Resources Overrides](qgc-dev-guide/custom_build/resource_override.md) - [Customization](qgc-dev-guide/custom_build/customization.md) diff --git a/docs/en/qgc-dev-guide/custom_build/create_repos.md b/docs/en/qgc-dev-guide/custom_build/create_repos.md deleted file mode 100644 index c4bce8ee1428..000000000000 --- a/docs/en/qgc-dev-guide/custom_build/create_repos.md +++ /dev/null @@ -1,15 +0,0 @@ -# Initial Repository Setup For Custom Build - -The suggested mechanism for working on QGC and a custom build version of QGC is to have two separate repositories. The first repo is your main QGC fork. The second repo is your custom build repo. - -## Main QGC Respository - -This repo is used to work on changes to mainline QGC. When creating your own custom build it is not uncommon to discover that you may need a tweak/addition to the custom build to achieve what you want. By discussing those needed changes firsthand with QGC devs and submitting pulls to make the custom build architecture better you make QGC more powerful for everyone and give back to the community. - -The best way to create this repo is to fork the regular QGC repo to your own GitHub account. - -## Custom Build Repository - -This is where you will do your main custom build development. All changes here should be within the custom directory as opposed to bleeding out into the regular QGC codebase. - -Since you can only fork a repo once, the way to create this repo is to "Create a new repository" in your GitHub account. Do not add any additional files to it like gitignore, readme's and so forth. Once it is created you will be given the option to setup up the Repo. Now you can select to "import code from another repository". Just import the regular QGC repo using the "Import Code" button. diff --git a/docs/en/qgc-dev-guide/custom_build/fork_repo.md b/docs/en/qgc-dev-guide/custom_build/fork_repo.md new file mode 100644 index 000000000000..22a4cddef91d --- /dev/null +++ b/docs/en/qgc-dev-guide/custom_build/fork_repo.md @@ -0,0 +1,14 @@ +# Initial Repository Setup For Custom Build + +* Navigate to the [QGC repo](https://github.com/mavlink/qgroundcontrol) and create your own fork. +* Copy the `custom_example` directory to a new `custom` directory at the root of the repo. +* Tweak the source in `custom` directory as needed. + +You can also rename the `custom_example` directory to `custom` but that can lead to merge problems when you bring your fork up to date with newer upstream version of regular QGC. + + +## Modifying Mainline QGC Source Code + +When creating your own custom build it is not uncommon to discover that you may need a tweak/addition to regular QGC source in places where the custom build architecture falls short. By discussing those needed changes firsthand with QGC devs and submitting pulls to make the custom build architecture better you make QGC more powerful for everyone and give back to the community. + +It is best to keep modifications in mainline QGC source to a minimum. Since every change there may make it more difficult for you to keep up to date with merging in changes from regular QGC as it moves forward. diff --git a/docs/en/qgc-dev-guide/custom_build/resource_override.md b/docs/en/qgc-dev-guide/custom_build/resource_override.md index f34fabff162e..572c1ad03026 100644 --- a/docs/en/qgc-dev-guide/custom_build/resource_override.md +++ b/docs/en/qgc-dev-guide/custom_build/resource_override.md @@ -1,27 +1,14 @@ # Resource Overrides A "resource" in QGC source code terminology is anything found in Qt resources file: -* [qgroundcontrol.qrc](https://github.com/mavlink/qgroundcontrol/blob/master/qgroundcontrol.qrc) and -* [qgcresources.qrc](https://github.com/mavlink/qgroundcontrol/blob/master/qgcresources.qrc) file. -* [InstrumentValueIcons.qrc](https://github.com/mavlink/qgroundcontrol/blob/master/resources/InstrumenValueIcons/InstrumentValueIcons.qrc) - -By overriding a resource you can replace it with your own version of it. This could be as simple as a single icon, or as complex as replacing an entire Vehicle Setup page of qml ui code. Be aware that using resource overrides does not isolate you from upstream QGC changes like the plugin architecture does. In a sense you are directly modify the upstream QGC resources used by the main code. - -## Exclusion Files - -The first step to overriding a resource is to "exclude" it from the standard portion of the upstream build. This means that you are going to provide that resource in your own custom build resource file(s). There are two files which achieve this: [qgroundcontrol.exclusion]() and [qgcresources.exclusion](https://github.com/mavlink/qgroundcontrol/blob/master/custom-example/qgcresources.exclusion). They correspond directly with the \*.qrc counterparts. In order to exclude a resource, copy the resource line from the .qrc file into the appropriate .exclusion file. -## Custom version of excluded resources - -You must include the custom version of the overriden resouce in you custom build resource file. The resource alias must exactly match the upstream alias. The name and actual location of the resource can be anywhere within your custom directory structure. - -## Generating the new modified versions of standard QGC resource file - -This is done using the resource update python scripts:`python updateqrc.py` and `python updateinstrumentqrc.py`. It will read the upstream resouce files and the corresponding exclusion files and output new versions of these files in your custom directory. These new versions will not have the resources you specified to exclude in them. The build system for custom builds uses these generated files (if they exist) to build with instead of the upstream versions. The generated version of these file should be added to your repo. Also whenever you update the upstream portion of QGC in your custom repo you must re-run the scripts to generate new versions of the files since the upstream resources may have changed. +* [qgroundcontrol.qrc](https://github.com/mavlink/qgroundcontrol/blob/master/qgroundcontrol.qrc) +* [qgcresources.qrc](https://github.com/mavlink/qgroundcontrol/blob/master/qgcresources.qrc) +* [InstrumentValueIcons.qrc](https://github.com/mavlink/qgroundcontrol/blob/master/resources/InstrumenValueIcons/InstrumentValueIcons.qrc) +* Qml files -## Custom Build Example +By overriding a resource you can replace it with your own version of it. This could be as simple as a single icon, or as complex as replacing an entire Vehicle Setup page of qml ui code. Be aware that using resource overrides means you are duplicating regular QGC source code. This can be a good or a bad thing depending on how you go about it. By using a resource override on a Qml file example it allows you to get away from dealing with crazy merge conflicts which you merge in newer versions of upstream QGC source. But it also means that you need to pay attention to what is going on in that upstream QGC source to see if you need to modify your copied code in a similar way. -You can see an examples of custom build qgcresource overrides in the repo custom build example: +Resource overrides work by using `QQmlEngine::addUrlInterceptor` to intercept requests for resources and re-route the request to available custom resources instead of the normal resource. Look at [custom_example/CustomPlugin.cc](https://github.com/mavlink/qgroundcontrol/blob/master/custom-example/CustomPlugin.cc) for how it's done and replicate that in your own custom build source. -- [qgcresources.qrc](https://github.com/mavlink/qgroundcontrol/blob/master/custom-example/qgcresources.exclusion) -- [custom.qrc](https://github.com/mavlink/qgroundcontrol/blob/master/custom-example/custom.qrc) +Custom resources that are meant for override are prepended with `/Custom` to the resource prefix in the custom resource file. The file alias for the resouce should be exactly the same as the normal QGC resource. Take a look at [custom_example/custom.qrc](https://github.com/mavlink/qgroundcontrol/blob/master/custom-example/custom.qrc) and [custom_example/CMakeLists.txt](https://github.com/mavlink/qgroundcontrol/blob/master/custom-example/CMakeLists.txt) for examples of how to do all of this. \ No newline at end of file From c6a09153a5d8dd2f6c8a249ae7ce7c2df3bd6ae9 Mon Sep 17 00:00:00 2001 From: Don Gagne Date: Wed, 9 Jul 2025 10:56:27 -0700 Subject: [PATCH 22/69] Update docs for stable release --- docs/en/SUMMARY.md | 2 +- docs/en/index.md | 2 +- docs/en/qgc-user-guide/index.md | 4 +--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/docs/en/SUMMARY.md b/docs/en/SUMMARY.md index 91e42f7a3d38..2bdb50744012 100644 --- a/docs/en/SUMMARY.md +++ b/docs/en/SUMMARY.md @@ -2,7 +2,7 @@ - [Overview](qgc-user-guide/index.md) - [Quick Start](qgc-user-guide/getting_started/quick_start.md) - - [Download and Install (Daily 5.0)](qgc-user-guide/releases/daily_builds.md) + - [Download and Install](qgc-user-guide/getting_started/download_and_install.md) - [Support](qgc-user-guide/support/support.md) - [Fly View](qgc-user-guide/fly_view/fly_view.md) - [Toolbar](qgc-user-guide/fly_view/fly_view_toolbar.md) diff --git a/docs/en/index.md b/docs/en/index.md index b9686aeb7dff..08e6ee217668 100644 --- a/docs/en/index.md +++ b/docs/en/index.md @@ -2,7 +2,7 @@ layout: home hero: - name: "QGroundControl Guide (v5.0)" + name: "QGroundControl Guide" tagline: For beginners, experienced users, and developers actions: - theme: brand diff --git a/docs/en/qgc-user-guide/index.md b/docs/en/qgc-user-guide/index.md index bfeb8044b9ec..067057e3c787 100644 --- a/docs/en/qgc-user-guide/index.md +++ b/docs/en/qgc-user-guide/index.md @@ -1,10 +1,8 @@ -# QGroundControl Guide (Daily Build 5.0) +# QGroundControl Guide [![Discuss](https://img.shields.io/badge/discuss-px4-ff69b4.svg)](http://discuss.px4.io/c/qgroundcontrol/qgroundcontrol-usage) [![Discuss](https://img.shields.io/badge/discuss-ardupilot-ff69b4.svg)](http://discuss.ardupilot.org/c/ground-control-software/qgroundcontrol) -_You are viewing the docs for the upcoming 5.0 release of QGroundControl. If you want docs for a Stable build select from the Version dropdown above._ - _QGroundControl_ provides full flight control and vehicle setup for PX4 or ArduPilot powered vehicles. It provides easy and straightforward usage for beginners, while still delivering high end feature support for experienced users. From 65e3b0038b108775b7bbc325877cf7ec7d35420b Mon Sep 17 00:00:00 2001 From: Don Gagne Date: Thu, 10 Jul 2025 09:39:25 -0700 Subject: [PATCH 23/69] Mavlink Inpector: Fix value updating on selected message --- src/AnalyzeView/MAVLinkMessage.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AnalyzeView/MAVLinkMessage.cc b/src/AnalyzeView/MAVLinkMessage.cc index c5697a79ff9f..1c8d8e6c80dc 100644 --- a/src/AnalyzeView/MAVLinkMessage.cc +++ b/src/AnalyzeView/MAVLinkMessage.cc @@ -110,7 +110,7 @@ void QGCMAVLinkMessage::update(const mavlink_message_t &message) _count++; _message = message; - if (_fieldSelected) { + if (_selected || _fieldSelected) { // Don't update field info unless selected to reduce perf hit of message processing _updateFields(); } From 4a9562bc366651680e20693b79d7948cfe8791fb Mon Sep 17 00:00:00 2001 From: Holden Ramsey <68555040+HTRamsey@users.noreply.github.com> Date: Sun, 20 Jul 2025 14:00:25 -0400 Subject: [PATCH 24/69] AnalyzeView: Fix Sending Commands (#13189) --- src/AnalyzeView/MAVLinkConsoleController.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/AnalyzeView/MAVLinkConsoleController.cc b/src/AnalyzeView/MAVLinkConsoleController.cc index aabaf2455970..eba4c7b60a98 100644 --- a/src/AnalyzeView/MAVLinkConsoleController.cc +++ b/src/AnalyzeView/MAVLinkConsoleController.cc @@ -23,7 +23,7 @@ MAVLinkConsoleController::MAVLinkConsoleController(QObject *parent) : QStringListModel(parent) , _palette(new QGCPalette(this)) { - // qCDebug(MAVLinkConsoleControllerLog) << Q_FUNC_INFO << this; + qCDebug(MAVLinkConsoleControllerLog) << this; (void) connect(MultiVehicleManager::instance(), &MultiVehicleManager::activeVehicleChanged, this, &MAVLinkConsoleController::_setActiveVehicle); @@ -37,7 +37,7 @@ MAVLinkConsoleController::~MAVLinkConsoleController() _sendSerialData(msg, true); } - // qCDebug(MAVLinkConsoleControllerLog) << Q_FUNC_INFO << this; + qCDebug(MAVLinkConsoleControllerLog) << this; } void MAVLinkConsoleController::sendCommand(const QString &command) @@ -148,7 +148,7 @@ void MAVLinkConsoleController::_sendSerialData(const QByteArray &data, bool clos // Send maximum sized chunks until the complete buffer is transmitted QByteArray output(data); while (output.size()) { - QByteArray chunk(data.left(MAVLINK_MSG_SERIAL_CONTROL_FIELD_DATA_LEN)); + QByteArray chunk(output.left(MAVLINK_MSG_SERIAL_CONTROL_FIELD_DATA_LEN)); const int dataSize = chunk.size(); // Ensure the buffer is large enough, as the MAVLink parser expects MAVLINK_MSG_SERIAL_CONTROL_FIELD_DATA_LEN bytes From 4f3c8203f864801460455d10022e8d9b9a3852a9 Mon Sep 17 00:00:00 2001 From: Don Gagne Date: Tue, 22 Jul 2025 09:49:49 -0700 Subject: [PATCH 25/69] Rovers don't require takeoff item in Plan --- src/MissionManager/MissionController.cc | 7 ++++++- src/QmlControls/PlanView.qml | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/MissionManager/MissionController.cc b/src/MissionManager/MissionController.cc index ca16daa597ce..b8f9e5ca5530 100644 --- a/src/MissionManager/MissionController.cc +++ b/src/MissionManager/MissionController.cc @@ -2385,7 +2385,7 @@ void MissionController::setCurrentPlanViewSeqNum(int sequenceNumber, bool force) _currentPlanViewItem = nullptr; _currentPlanViewSeqNum = -1; _currentPlanViewVIIndex = -1; - _onlyInsertTakeoffValid = !_planViewSettings->takeoffItemNotRequired()->rawValue().toBool() && _visualItems->count() == 1; // First item must be takeoff + _onlyInsertTakeoffValid = false; _isInsertTakeoffValid = true; _isInsertLandValid = true; _isROIActive = false; @@ -2393,6 +2393,11 @@ void MissionController::setCurrentPlanViewSeqNum(int sequenceNumber, bool force) _flyThroughCommandsAllowed = true; _previousCoordinate = QGeoCoordinate(); + bool noItemsAddedYet = _visualItems->count() == 1; + if (_masterController->controllerVehicle()->takeoffVehicleSupported() && !_planViewSettings->takeoffItemNotRequired()->rawValue().toBool() && noItemsAddedYet) { + _onlyInsertTakeoffValid = true; + } + for (int viIndex=0; viIndex<_visualItems->count(); viIndex++) { VisualMissionItem* pVI = qobject_cast(_visualItems->get(viIndex)); SimpleMissionItem* simpleItem = qobject_cast(pVI); diff --git a/src/QmlControls/PlanView.qml b/src/QmlControls/PlanView.qml index 7b2b6c378824..1ca5ad445044 100644 --- a/src/QmlControls/PlanView.qml +++ b/src/QmlControls/PlanView.qml @@ -281,7 +281,7 @@ Item { _missionController.insertComplexMissionItem(complexItemName, mapCenter(), nextIndex, true /* makeCurrentItem */) } - function insertTakeItemAfterCurrent() { + function insertTakeoffItemAfterCurrent() { var nextIndex = _missionController.currentPlanViewVIIndex + 1 _missionController.insertTakeoffItem(mapCenter(), nextIndex, true /* makeCurrentItem */) } @@ -589,7 +589,7 @@ Item { visible: (toolStrip._isMissionLayer || toolStrip._isUtmspLayer) && !_planMasterController.controllerVehicle.rover onTriggered: { toolStrip.allAddClickBoolsOff() - insertTakeItemAfterCurrent() + insertTakeoffItemAfterCurrent() _triggerSubmit = true } }, From 646950a7814e54248b9cfaece7bbebb65e51c366 Mon Sep 17 00:00:00 2001 From: Don Gagne Date: Tue, 22 Jul 2025 09:49:49 -0700 Subject: [PATCH 26/69] fix: correct telemetry color picker dialog bug Fixes a bug in the telemetry color picker dialog where the wrong color value was passed on acceptance. Changed the onAccepted handler to use selectedColor instead of color to ensure the correct hex color value is used when updating telemetry colors. Changes: - Modified colorPickerDialog's onAccepted from: onAccepted: updateColorValue(colorIndex, color) --- src/QmlControls/InstrumentValueEditDialog.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/QmlControls/InstrumentValueEditDialog.qml b/src/QmlControls/InstrumentValueEditDialog.qml index 005416795772..84cd222dfbe6 100644 --- a/src/QmlControls/InstrumentValueEditDialog.qml +++ b/src/QmlControls/InstrumentValueEditDialog.qml @@ -279,7 +279,7 @@ QGCPopupDialog { id: colorPickerDialog modality: Qt.ApplicationModal selectedColor: instrumentValueData.rangeColors.length ? instrumentValueData.rangeColors[colorIndex] : "white" - onAccepted: updateColorValue(colorIndex, color) + onAccepted: updateColorValue(colorIndex, selectedColor) property int colorIndex: 0 } From cd48ce10744ab0dd5dceebb7d6daf74b09c7ab83 Mon Sep 17 00:00:00 2001 From: Don Gagne Date: Tue, 12 Aug 2025 11:19:19 -0700 Subject: [PATCH 27/69] Explain when Gimbal Indicator shows up --- docs/en/qgc-user-guide/fly_view/fly_view_toolbar.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/qgc-user-guide/fly_view/fly_view_toolbar.md b/docs/en/qgc-user-guide/fly_view/fly_view_toolbar.md index 35568c04e9b3..ce065731b479 100644 --- a/docs/en/qgc-user-guide/fly_view/fly_view_toolbar.md +++ b/docs/en/qgc-user-guide/fly_view/fly_view_toolbar.md @@ -73,6 +73,6 @@ There are other indicators which only show in certain situations: * Telemetry RSSI * RC RSSI -* Gimbal +* Gimbal - Only displayed if the vehicle supports the [Mavlink Gimbal Protocol](https://mavlink.io/en/services/gimbal_v2.html) * VTOL transitions * Select from multiple connected vehicles From f484a9b29949eddcc11d193590fb3681cf9f301f Mon Sep 17 00:00:00 2001 From: Sergii Lisovenko <2522054+s-lisovenko@users.noreply.github.com> Date: Tue, 12 Aug 2025 13:16:56 +0200 Subject: [PATCH 28/69] Fix Qml signal leaking to underlying map Add TapHandler to block click event leakage to underlying map --- src/AnalyzeView/AnalyzeView.qml | 5 +++++ src/QmlControls/AppSettings.qml | 5 +++++ src/UI/MainWindow.qml | 5 +++++ src/Vehicle/VehicleSetup/SetupView.qml | 5 +++++ 4 files changed, 20 insertions(+) diff --git a/src/AnalyzeView/AnalyzeView.qml b/src/AnalyzeView/AnalyzeView.qml index fe74d44cf23d..738ce13b725b 100644 --- a/src/AnalyzeView/AnalyzeView.qml +++ b/src/AnalyzeView/AnalyzeView.qml @@ -30,6 +30,11 @@ Rectangle { readonly property real _verticalMargin: _defaultTextHeight / 2 readonly property real _buttonWidth: _defaultTextWidth * 18 + // This need to block click event leakage to underlying map. + DeadMouseArea { + anchors.fill: parent + } + GeoTagController { id: geoController } diff --git a/src/QmlControls/AppSettings.qml b/src/QmlControls/AppSettings.qml index 2ce1b26d559d..f2ce814fd59f 100644 --- a/src/QmlControls/AppSettings.qml +++ b/src/QmlControls/AppSettings.qml @@ -43,6 +43,11 @@ Rectangle { } } + // This need to block click event leakage to underlying map. + DeadMouseArea { + anchors.fill: parent + } + QGCPalette { id: qgcPal } Component.onCompleted: { diff --git a/src/UI/MainWindow.qml b/src/UI/MainWindow.qml index a06a2b0d057e..0c9367d34a4d 100644 --- a/src/UI/MainWindow.qml +++ b/src/UI/MainWindow.qml @@ -479,6 +479,11 @@ ApplicationWindow { } } + // This need to block click event leakage to underlying map. + DeadMouseArea { + anchors.fill: parent + } + Rectangle { id: toolDrawerToolbar anchors.left: parent.left diff --git a/src/Vehicle/VehicleSetup/SetupView.qml b/src/Vehicle/VehicleSetup/SetupView.qml index 9034cf8451a2..72fd6409c01e 100644 --- a/src/Vehicle/VehicleSetup/SetupView.qml +++ b/src/Vehicle/VehicleSetup/SetupView.qml @@ -23,6 +23,11 @@ Rectangle { color: qgcPal.window z: QGroundControl.zOrderTopMost + // This need to block click event leakage to underlying map. + DeadMouseArea { + anchors.fill: parent + } + QGCPalette { id: qgcPal; colorGroupEnabled: true } readonly property real _defaultTextHeight: ScreenTools.defaultFontPixelHeight From fcd79c77cba9c25272ede9ca1ccc8fa7a80ed1dc Mon Sep 17 00:00:00 2001 From: Don Gagne Date: Wed, 13 Aug 2025 10:20:12 -0700 Subject: [PATCH 29/69] Fix failing MissionController unit test Wasn't waiting long enough for queued recalcs to flow through --- src/MissionManager/CameraSection.h | 2 -- test/MissionManager/MissionControllerTest.cc | 6 +++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/MissionManager/CameraSection.h b/src/MissionManager/CameraSection.h index 4188144ae46f..827d816684b8 100644 --- a/src/MissionManager/CameraSection.h +++ b/src/MissionManager/CameraSection.h @@ -63,11 +63,9 @@ class CameraSection : public Section void setSpecifyGimbal (bool specifyGimbal); void setSpecifyCameraMode (bool specifyCameraMode); - ///< Signals specifiedGimbalYawChanged ///< @return The gimbal yaw specified by this item, NaN if not specified double specifiedGimbalYaw(void) const; - ///< Signals specifiedGimbalPitchChanged ///< @return The gimbal pitch specified by this item, NaN if not specified double specifiedGimbalPitch(void) const; diff --git a/test/MissionManager/MissionControllerTest.cc b/test/MissionManager/MissionControllerTest.cc index 8229f29213f8..acb6c9e16d3c 100644 --- a/test/MissionManager/MissionControllerTest.cc +++ b/test/MissionManager/MissionControllerTest.cc @@ -173,7 +173,7 @@ void MissionControllerTest::_testGimbalRecalc(void) item->cameraSection()->setSpecifyGimbal(true); item->cameraSection()->gimbalYaw()->setRawValue(0.0); SettingsManager::instance()->planViewSettings()->showGimbalOnlyWhenSet()->setRawValue(false); - QTest::qWait(100); // Recalcs in MissionController are queued to remove dups. Allow return to main message loop. + QTest::qWait(500); // Recalcs in MissionController are queued to remove dups. Allow return to main message loop. for (int i=1; i<_missionController->visualItems()->count(); i++) { //qDebug() << i; VisualMissionItem* visualItem = _missionController->visualItems()->value(i); @@ -202,7 +202,7 @@ void MissionControllerTest::_testVehicleYawRecalc(void) _missionController->insertSimpleMissionItem(currentCoord, i); } - QTest::qWait(100); // Recalcs in MissionController are queued to remove dups. Allow return to main message loop. + QTest::qWait(500); // Recalcs in MissionController are queued to remove dups. Allow return to main message loop. // No specific vehicle yaw set yet. Vehicle yaw should track flight path. double expectedVehicleYaw = wpAngleInc; @@ -218,7 +218,7 @@ void MissionControllerTest::_testVehicleYawRecalc(void) SimpleMissionItem* simpleItem = _missionController->visualItems()->value(3); simpleItem->missionItem().setParam4(66); - QTest::qWait(100); // Recalcs in MissionController are queued to remove dups. Allow return to main message loop. + QTest::qWait(500); // Recalcs in MissionController are queued to remove dups. Allow return to main message loop. // All item should track vehicle path except for the one changed expectedVehicleYaw = wpAngleInc; From c316bdccaec352d6d0fbfd9c120d89c39dedf4f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20P=C3=A9rez=20Alonso?= Date: Mon, 9 Dec 2024 14:47:29 +0100 Subject: [PATCH 30/69] Fix centering of inner circle in PhotoVideoControl The inner circle in the PhotoVideoControl widget was not always perfectly centered due to a bug in Qt's anchors.centerIn property (QTBUG-95224) which causes it to return integer positions instead of subpixel values. Implemented a workaround by setting alignWhenCentered to false. Related Qt bug: https://bugreports.qt.io/browse/QTBUG-95224 Qt fix commit: https://github.com/qt/qtdeclarative/commit/fd23a222efe189607eebd5c6782ca73eafa7080c --- src/FlightMap/Widgets/PhotoVideoControl.qml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/FlightMap/Widgets/PhotoVideoControl.qml b/src/FlightMap/Widgets/PhotoVideoControl.qml index 22bc144f3382..9a5e30189b05 100644 --- a/src/FlightMap/Widgets/PhotoVideoControl.qml +++ b/src/FlightMap/Widgets/PhotoVideoControl.qml @@ -165,7 +165,13 @@ Rectangle { border.width: 3 Rectangle { - anchors.centerIn: parent + // anchors.centerIn snaps to integer coordinates, which + // depending on DPI can throw the centering off. + // Setting alignWhenCentered to false avoids this issue. + anchors { + centerIn: parent + alignWhenCentered: false + } width: parent.width * (_isShootingInCurrentMode ? 0.5 : 0.75) height: width radius: _isShootingInCurrentMode ? 0 : width * 0.5 From 0b137f3dc8ad91a0f94f04dcc03f1d9f3af65287 Mon Sep 17 00:00:00 2001 From: Don Gagne Date: Tue, 12 Aug 2025 10:05:52 -0700 Subject: [PATCH 31/69] Fix crash when loading parameter which does not currently exist --- src/QmlControls/ParameterEditorController.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/QmlControls/ParameterEditorController.cc b/src/QmlControls/ParameterEditorController.cc index 13d9a9ce8ca8..ec339bfc99d3 100644 --- a/src/QmlControls/ParameterEditorController.cc +++ b/src/QmlControls/ParameterEditorController.cc @@ -129,7 +129,7 @@ Fact* ParameterTableModel::factAt(int row) const return nullptr; } - return _tableData[row][0].value(); + return _tableData[row][ValueColumn].value(); } From 8e03bd87bd35432663501eb261f3d6826603c2b4 Mon Sep 17 00:00:00 2001 From: Don Gagne Date: Thu, 4 Sep 2025 15:11:42 -0700 Subject: [PATCH 32/69] Remove hardcoded flight mode names --- src/FirmwarePlugin/APM/ArduPlaneFirmwarePlugin.cc | 5 +++++ src/FirmwarePlugin/APM/ArduPlaneFirmwarePlugin.h | 2 +- src/FirmwarePlugin/APM/ArduRoverFirmwarePlugin.cc | 10 ++++++++++ src/FirmwarePlugin/APM/ArduRoverFirmwarePlugin.h | 4 ++-- src/FirmwarePlugin/FirmwarePlugin.cc | 2 ++ 5 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/FirmwarePlugin/APM/ArduPlaneFirmwarePlugin.cc b/src/FirmwarePlugin/APM/ArduPlaneFirmwarePlugin.cc index 80bf253dfdda..2e77014be471 100644 --- a/src/FirmwarePlugin/APM/ArduPlaneFirmwarePlugin.cc +++ b/src/FirmwarePlugin/APM/ArduPlaneFirmwarePlugin.cc @@ -113,6 +113,11 @@ QString ArduPlaneFirmwarePlugin::stabilizedFlightMode() const return _modeEnumToString.value(APMPlaneMode::STABILIZE, _stabilizeFlightMode); } +QString ArduPlaneFirmwarePlugin::pauseFlightMode() const +{ + return _modeEnumToString.value(APMPlaneMode::LOITER, _loiterFlightMode); +} + void ArduPlaneFirmwarePlugin::updateAvailableFlightModes(FlightModeList &modeList) { for (FirmwareFlightMode &mode: modeList) { diff --git a/src/FirmwarePlugin/APM/ArduPlaneFirmwarePlugin.h b/src/FirmwarePlugin/APM/ArduPlaneFirmwarePlugin.h index a61643aca675..b33871ad9b47 100644 --- a/src/FirmwarePlugin/APM/ArduPlaneFirmwarePlugin.h +++ b/src/FirmwarePlugin/APM/ArduPlaneFirmwarePlugin.h @@ -52,11 +52,11 @@ class ArduPlaneFirmwarePlugin : public APMFirmwarePlugin explicit ArduPlaneFirmwarePlugin(QObject *parent = nullptr); ~ArduPlaneFirmwarePlugin(); - QString pauseFlightMode() const override { return QString("Loiter"); } QString offlineEditingParamFile(Vehicle *vehicle) const override { Q_UNUSED(vehicle); return QStringLiteral(":/FirmwarePlugin/APM/Plane.OfflineEditing.params"); } QString autoDisarmParameter(Vehicle *vehicle) const override { Q_UNUSED(vehicle); return QStringLiteral("LAND_DISARMDELAY"); } int remapParamNameHigestMinorVersionNumber(int majorVersionNumber) const override; const FirmwarePlugin::remapParamNameMajorVersionMap_t ¶mNameRemapMajorVersionMap() const override { return _remapParamName; } + QString pauseFlightMode() const override; QString takeOffFlightMode() const override; QString stabilizedFlightMode() const override; void updateAvailableFlightModes(FlightModeList &modeList) override; diff --git a/src/FirmwarePlugin/APM/ArduRoverFirmwarePlugin.cc b/src/FirmwarePlugin/APM/ArduRoverFirmwarePlugin.cc index 0c02adc26a95..1f6d682c95d0 100644 --- a/src/FirmwarePlugin/APM/ArduRoverFirmwarePlugin.cc +++ b/src/FirmwarePlugin/APM/ArduRoverFirmwarePlugin.cc @@ -86,6 +86,16 @@ QString ArduRoverFirmwarePlugin::stabilizedFlightMode() const return _modeEnumToString.value(APMRoverMode::MANUAL, _manualFlightMode); } +QString ArduRoverFirmwarePlugin::pauseFlightMode() const +{ + return _modeEnumToString.value(APMRoverMode::HOLD, _holdFlightMode); +} + +QString ArduRoverFirmwarePlugin::followFlightMode() const +{ + return _modeEnumToString.value(APMRoverMode::FOLLOW, _followFlightMode); +} + void ArduRoverFirmwarePlugin::updateAvailableFlightModes(FlightModeList &modeList) { for (FirmwareFlightMode &mode: modeList) { diff --git a/src/FirmwarePlugin/APM/ArduRoverFirmwarePlugin.h b/src/FirmwarePlugin/APM/ArduRoverFirmwarePlugin.h index 9f65de12bf7d..3a20415bbf0c 100644 --- a/src/FirmwarePlugin/APM/ArduRoverFirmwarePlugin.h +++ b/src/FirmwarePlugin/APM/ArduRoverFirmwarePlugin.h @@ -40,8 +40,6 @@ class ArduRoverFirmwarePlugin : public APMFirmwarePlugin explicit ArduRoverFirmwarePlugin(QObject *parent = nullptr); ~ArduRoverFirmwarePlugin(); - QString pauseFlightMode() const override { return QStringLiteral("Hold"); } - QString followFlightMode() const override { return QStringLiteral("Follow"); } void guidedModeChangeAltitude(Vehicle* vehicle, double altitudeChange, bool pauseVehicle) override; int remapParamNameHigestMinorVersionNumber(int majorVersionNumber) const override; const FirmwarePlugin::remapParamNameMajorVersionMap_t& paramNameRemapMajorVersionMap() const override { return _remapParamName; } @@ -49,6 +47,8 @@ class ArduRoverFirmwarePlugin : public APMFirmwarePlugin bool supportsSmartRTL() const override { return true; } QString offlineEditingParamFile(Vehicle *vehicle) const override { Q_UNUSED(vehicle); return QStringLiteral(":/FirmwarePlugin/APM/Rover.OfflineEditing.params"); } + QString pauseFlightMode() const override; + QString followFlightMode() const override; QString stabilizedFlightMode() const override; void updateAvailableFlightModes(FlightModeList &modeList) override; diff --git a/src/FirmwarePlugin/FirmwarePlugin.cc b/src/FirmwarePlugin/FirmwarePlugin.cc index a10812aeb1e0..169cc0ea1c15 100644 --- a/src/FirmwarePlugin/FirmwarePlugin.cc +++ b/src/FirmwarePlugin/FirmwarePlugin.cc @@ -266,6 +266,8 @@ bool FirmwarePlugin::_setFlightModeAndValidate(Vehicle *vehicle, const QString & return true; } + qDebug() << "Setting flight mode to" << vehicle->flightMode() << flightMode; + bool flightModeChanged = false; // We try 3 times From 1b6faac3255a4e51a3ebeb34b6fdbfe977f4c187 Mon Sep 17 00:00:00 2001 From: Don Gagne Date: Thu, 4 Sep 2025 15:19:37 -0700 Subject: [PATCH 33/69] Remove logging --- src/FirmwarePlugin/FirmwarePlugin.cc | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/FirmwarePlugin/FirmwarePlugin.cc b/src/FirmwarePlugin/FirmwarePlugin.cc index 169cc0ea1c15..a10812aeb1e0 100644 --- a/src/FirmwarePlugin/FirmwarePlugin.cc +++ b/src/FirmwarePlugin/FirmwarePlugin.cc @@ -266,8 +266,6 @@ bool FirmwarePlugin::_setFlightModeAndValidate(Vehicle *vehicle, const QString & return true; } - qDebug() << "Setting flight mode to" << vehicle->flightMode() << flightMode; - bool flightModeChanged = false; // We try 3 times From a11717e595a7f18e0320610e5ed889b1a6166a11 Mon Sep 17 00:00:00 2001 From: Don Gagne Date: Fri, 5 Sep 2025 12:09:48 -0700 Subject: [PATCH 34/69] Fix bugs with flight mode names changing from Standared Modes --- .../FirmwarePlugin/CustomFirmwarePlugin.cc | 2 +- src/FirmwarePlugin/PX4/PX4FirmwarePlugin.cc | 134 +++++++++--------- src/FirmwarePlugin/PX4/PX4FirmwarePlugin.h | 22 --- src/Vehicle/StandardModes.cc | 8 +- 4 files changed, 73 insertions(+), 93 deletions(-) diff --git a/custom-example/src/FirmwarePlugin/CustomFirmwarePlugin.cc b/custom-example/src/FirmwarePlugin/CustomFirmwarePlugin.cc index 89ed94bd6cbc..f3591137fa3d 100644 --- a/custom-example/src/FirmwarePlugin/CustomFirmwarePlugin.cc +++ b/custom-example/src/FirmwarePlugin/CustomFirmwarePlugin.cc @@ -21,7 +21,7 @@ CustomFirmwarePlugin::CustomFirmwarePlugin() { for (auto &mode: _flightModeList){ //-- Narrow the flight mode options to only these - if(mode.mode_name != _holdFlightMode && mode.mode_name != _rtlFlightMode && mode.mode_name != _missionFlightMode){ + if ((mode.mode_name != pauseFlightMode()) && (mode.mode_name != rtlFlightMode()) && (mode.mode_name != missionFlightMode())) { // No other flight modes can be set mode.canBeSet = false; } diff --git a/src/FirmwarePlugin/PX4/PX4FirmwarePlugin.cc b/src/FirmwarePlugin/PX4/PX4FirmwarePlugin.cc index c934b455b6a5..bff1caf3f855 100644 --- a/src/FirmwarePlugin/PX4/PX4FirmwarePlugin.cc +++ b/src/FirmwarePlugin/PX4/PX4FirmwarePlugin.cc @@ -21,6 +21,7 @@ #include "Vehicle.h" #include +#include #include "px4_custom_mode.h" @@ -32,70 +33,72 @@ PX4FirmwarePluginInstanceData::PX4FirmwarePluginInstanceData(QObject* parent) } PX4FirmwarePlugin::PX4FirmwarePlugin() - : _manualFlightMode (tr("Manual")) - , _acroFlightMode (tr("Acro")) - , _stabilizedFlightMode (tr("Stabilized")) - , _rattitudeFlightMode (tr("Rattitude")) - , _altCtlFlightMode (tr("Altitude")) - , _posCtlFlightMode (tr("Position")) - , _offboardFlightMode (tr("Offboard")) - , _readyFlightMode (tr("Ready")) - , _takeoffFlightMode (tr("Takeoff")) - , _holdFlightMode (tr("Hold")) - , _missionFlightMode (tr("Mission")) - , _rtlFlightMode (tr("Return")) - , _landingFlightMode (tr("Land")) - , _preclandFlightMode (tr("Precision Land")) - , _rtgsFlightMode (tr("Return to Groundstation")) - , _followMeFlightMode (tr("Follow Me")) - , _simpleFlightMode (tr("Simple")) - , _orbitFlightMode (tr("Orbit")) { qmlRegisterType ("QGroundControl.Controllers", 1, 0, "PX4SimpleFlightModesController"); qmlRegisterType ("QGroundControl.Controllers", 1, 0, "AirframeComponentController"); qmlRegisterType ("QGroundControl.Controllers", 1, 0, "SensorsComponentController"); qmlRegisterType ("QGroundControl.Controllers", 1, 0, "PowerComponentController"); + const QString manualFlightModeName = tr("Manual"); + const QString acroFlightModeName = tr("Acro"); + const QString stabilizedFlightModeName = tr("Stabilized"); + const QString rattitudeFlightModeName = tr("Rattitude"); + const QString altCtlFlightModeName = tr("Altitude"); + const QString posCtlFlightModeName = tr("Position"); + const QString offboardFlightModeName = tr("Offboard"); + const QString readyFlightModeName = tr("Ready"); + const QString takeoffFlightModeName = tr("Takeoff"); + const QString holdFlightModeName = tr("Hold"); + const QString missionFlightModeName = tr("Mission"); + const QString rtlFlightModeName = tr("Return"); + const QString landingFlightModeName = tr("Land"); + const QString preclandFlightModeName = tr("Precision Land"); + const QString rtgsFlightModeName = tr("Return to Groundstation"); + const QString followMeFlightModeName = tr("Follow Me"); + const QString simpleFlightModeName = tr("Simple"); + const QString orbitFlightModeName = tr("Orbit"); + _setModeEnumToModeStringMapping({ - { PX4CustomMode::MANUAL , _manualFlightMode }, - { PX4CustomMode::STABILIZED , _stabilizedFlightMode }, - { PX4CustomMode::ACRO , _acroFlightMode }, - { PX4CustomMode::RATTITUDE , _rattitudeFlightMode }, - { PX4CustomMode::ALTCTL , _altCtlFlightMode }, - { PX4CustomMode::OFFBOARD , _offboardFlightMode }, - { PX4CustomMode::SIMPLE , _simpleFlightMode }, - { PX4CustomMode::POSCTL_POSCTL , _posCtlFlightMode }, - { PX4CustomMode::POSCTL_ORBIT , _orbitFlightMode }, - { PX4CustomMode::AUTO_LOITER , _holdFlightMode }, - { PX4CustomMode::AUTO_MISSION , _missionFlightMode }, - { PX4CustomMode::AUTO_RTL , _rtlFlightMode }, - { PX4CustomMode::AUTO_LAND , _landingFlightMode }, - { PX4CustomMode::AUTO_PRECLAND , _preclandFlightMode }, - { PX4CustomMode::AUTO_READY , _readyFlightMode }, - { PX4CustomMode::AUTO_RTGS , _rtgsFlightMode }, - { PX4CustomMode::AUTO_TAKEOFF , _takeoffFlightMode }, + { PX4CustomMode::MANUAL, manualFlightModeName }, + { PX4CustomMode::STABILIZED, stabilizedFlightModeName }, + { PX4CustomMode::ACRO, acroFlightModeName }, + { PX4CustomMode::RATTITUDE, rattitudeFlightModeName }, + { PX4CustomMode::ALTCTL, altCtlFlightModeName }, + { PX4CustomMode::OFFBOARD, offboardFlightModeName }, + { PX4CustomMode::SIMPLE, simpleFlightModeName }, + { PX4CustomMode::POSCTL_POSCTL, posCtlFlightModeName }, + { PX4CustomMode::POSCTL_ORBIT, orbitFlightModeName }, + { PX4CustomMode::AUTO_LOITER, holdFlightModeName }, + { PX4CustomMode::AUTO_MISSION, missionFlightModeName }, + { PX4CustomMode::AUTO_RTL, rtlFlightModeName }, + { PX4CustomMode::AUTO_LAND, landingFlightModeName }, + { PX4CustomMode::AUTO_PRECLAND, preclandFlightModeName }, + { PX4CustomMode::AUTO_READY, readyFlightModeName }, + { PX4CustomMode::AUTO_RTGS, rtgsFlightModeName }, + { PX4CustomMode::AUTO_TAKEOFF, takeoffFlightModeName }, }); static FlightModeList availableFlightModes = { - // Mode Name , Custom Mode CanBeSet adv - { _manualFlightMode , PX4CustomMode::MANUAL , true , true }, - { _stabilizedFlightMode , PX4CustomMode::STABILIZED , true , true }, - { _acroFlightMode , PX4CustomMode::ACRO , true , true }, - { _rattitudeFlightMode , PX4CustomMode::RATTITUDE , true , false}, - { _altCtlFlightMode , PX4CustomMode::ALTCTL , true , false}, - { _offboardFlightMode , PX4CustomMode::OFFBOARD , true , true }, - { _simpleFlightMode , PX4CustomMode::SIMPLE , false, false}, - { _posCtlFlightMode , PX4CustomMode::POSCTL_POSCTL , true , false}, - { _orbitFlightMode , PX4CustomMode::POSCTL_ORBIT , false, true }, - { _holdFlightMode , PX4CustomMode::AUTO_LOITER , true , true }, - { _missionFlightMode , PX4CustomMode::AUTO_MISSION , true , true }, - { _rtlFlightMode , PX4CustomMode::AUTO_RTL , true , true }, - { _landingFlightMode , PX4CustomMode::AUTO_LAND , false, true }, - { _preclandFlightMode , PX4CustomMode::AUTO_PRECLAND , true , true }, - { _readyFlightMode , PX4CustomMode::AUTO_READY , false, false}, - { _rtgsFlightMode , PX4CustomMode::AUTO_RTGS , false, false}, - { _takeoffFlightMode , PX4CustomMode::AUTO_TAKEOFF , false, false}, + // Mode Name Custom Mode CanBeSet adv + { manualFlightModeName, PX4CustomMode::MANUAL, true, true }, + { stabilizedFlightModeName, PX4CustomMode::STABILIZED, true, true }, + { acroFlightModeName, PX4CustomMode::ACRO, true, true }, + { rattitudeFlightModeName, PX4CustomMode::RATTITUDE, true, false}, + { altCtlFlightModeName, PX4CustomMode::ALTCTL, true, false}, + { offboardFlightModeName, PX4CustomMode::OFFBOARD, true, true }, + { simpleFlightModeName, PX4CustomMode::SIMPLE, false, false}, + { posCtlFlightModeName, PX4CustomMode::POSCTL_POSCTL, true, false}, + { orbitFlightModeName, PX4CustomMode::POSCTL_ORBIT, false, true }, + { holdFlightModeName, PX4CustomMode::AUTO_LOITER, true, true }, + { missionFlightModeName, PX4CustomMode::AUTO_MISSION, true, true }, + { rtlFlightModeName, PX4CustomMode::AUTO_RTL, true, true }, + { landingFlightModeName, PX4CustomMode::AUTO_LAND, false, true }, + { preclandFlightModeName, PX4CustomMode::AUTO_PRECLAND, true, true }, + { readyFlightModeName, PX4CustomMode::AUTO_READY, false, false}, + { rtgsFlightModeName, PX4CustomMode::AUTO_RTGS, false, false}, + { takeoffFlightModeName, PX4CustomMode::AUTO_TAKEOFF, false, false}, }; + updateAvailableFlightModes(availableFlightModes); } @@ -306,12 +309,12 @@ void PX4FirmwarePlugin::pauseVehicle(Vehicle* vehicle) const void PX4FirmwarePlugin::guidedModeRTL(Vehicle* vehicle, bool smartRTL) const { Q_UNUSED(smartRTL); - _setFlightModeAndValidate(vehicle, _rtlFlightMode); + _setFlightModeAndValidate(vehicle, rtlFlightMode()); } void PX4FirmwarePlugin::guidedModeLand(Vehicle* vehicle) const { - _setFlightModeAndValidate(vehicle, _landingFlightMode); + _setFlightModeAndValidate(vehicle, landFlightMode()); } void PX4FirmwarePlugin::_mavCommandResult(int vehicleId, int component, int command, int result, int failureCode) @@ -605,54 +608,53 @@ void PX4FirmwarePlugin::setGuidedMode(Vehicle* vehicle, bool guidedMode) const QString PX4FirmwarePlugin::pauseFlightMode() const { - return _modeEnumToString.value(PX4CustomMode::AUTO_LOITER, _holdFlightMode); + return _modeEnumToString.value(PX4CustomMode::AUTO_LOITER); } QString PX4FirmwarePlugin::missionFlightMode() const { - return _modeEnumToString.value(PX4CustomMode::AUTO_MISSION, _missionFlightMode); + return _modeEnumToString.value(PX4CustomMode::AUTO_MISSION); } QString PX4FirmwarePlugin::rtlFlightMode() const { - return _modeEnumToString.value(PX4CustomMode::AUTO_RTL, _rtlFlightMode); + return _modeEnumToString.value(PX4CustomMode::AUTO_RTL); } QString PX4FirmwarePlugin::landFlightMode() const { - return _modeEnumToString.value(PX4CustomMode::AUTO_LAND, _landingFlightMode); + return _modeEnumToString.value(PX4CustomMode::AUTO_LAND); } QString PX4FirmwarePlugin::takeControlFlightMode() const { - return _modeEnumToString.value(PX4CustomMode::MANUAL, _manualFlightMode); + return _modeEnumToString.value(PX4CustomMode::MANUAL); } QString PX4FirmwarePlugin::gotoFlightMode() const { - return _modeEnumToString.value(PX4CustomMode::AUTO_LOITER, _holdFlightMode); + return _modeEnumToString.value(PX4CustomMode::AUTO_LOITER); } QString PX4FirmwarePlugin::followFlightMode() const { - return _modeEnumToString.value(PX4CustomMode::AUTO_FOLLOW_TARGET, _followMeFlightMode); + return _modeEnumToString.value(PX4CustomMode::AUTO_FOLLOW_TARGET); } QString PX4FirmwarePlugin::takeOffFlightMode() const { - return _modeEnumToString.value(PX4CustomMode::AUTO_TAKEOFF, _takeoffFlightMode); + return _modeEnumToString.value(PX4CustomMode::AUTO_TAKEOFF); } QString PX4FirmwarePlugin::stabilizedFlightMode() const { - return _modeEnumToString.value(PX4CustomMode::STABILIZED, _stabilizedFlightMode); + return _modeEnumToString.value(PX4CustomMode::STABILIZED); } bool PX4FirmwarePlugin::isGuidedMode(const Vehicle* vehicle) const { // Not supported by generic vehicle - return (vehicle->flightMode() == _holdFlightMode || vehicle->flightMode() == _takeoffFlightMode - || vehicle->flightMode() == _landingFlightMode); + return (vehicle->flightMode() == pauseFlightMode() || vehicle->flightMode() == takeOffFlightMode() || vehicle->flightMode() == landFlightMode()); } bool PX4FirmwarePlugin::adjustIncomingMavlinkMessage(Vehicle* vehicle, mavlink_message_t* message) diff --git a/src/FirmwarePlugin/PX4/PX4FirmwarePlugin.h b/src/FirmwarePlugin/PX4/PX4FirmwarePlugin.h index 05dd01a768ba..4f9226174f9b 100644 --- a/src/FirmwarePlugin/PX4/PX4FirmwarePlugin.h +++ b/src/FirmwarePlugin/PX4/PX4FirmwarePlugin.h @@ -80,28 +80,6 @@ class PX4FirmwarePlugin : public FirmwarePlugin void updateAvailableFlightModes (FlightModeList &modeList) override; -protected: - - // If plugin superclass wants to change a mode name, then set a new name for the flight mode in the superclass constructor - QString _manualFlightMode; - QString _acroFlightMode; - QString _stabilizedFlightMode; - QString _rattitudeFlightMode; - QString _altCtlFlightMode; - QString _posCtlFlightMode; - QString _offboardFlightMode; - QString _readyFlightMode; - QString _takeoffFlightMode; - QString _holdFlightMode; - QString _missionFlightMode; - QString _rtlFlightMode; - QString _landingFlightMode; - QString _preclandFlightMode; - QString _rtgsFlightMode; - QString _followMeFlightMode; - QString _simpleFlightMode; - QString _orbitFlightMode; - private slots: void _mavCommandResult(int vehicleId, int component, int command, int result, int failureCode); diff --git a/src/Vehicle/StandardModes.cc b/src/Vehicle/StandardModes.cc index 4a031dc8367c..30196164f147 100644 --- a/src/Vehicle/StandardModes.cc +++ b/src/Vehicle/StandardModes.cc @@ -48,6 +48,7 @@ void StandardModes::gotMessage(MAV_RESULT result, const mavlink_message_t &messa break; case MAV_STANDARD_MODE_ORBIT: name = "Orbit"; + cannotBeSet = true; // These are exposed in the UI as separate buttons break; case MAV_STANDARD_MODE_CRUISE: name = "Cruise"; @@ -57,22 +58,21 @@ void StandardModes::gotMessage(MAV_RESULT result, const mavlink_message_t &messa break; case MAV_STANDARD_MODE_SAFE_RECOVERY: name = "Safe Recovery"; + cannotBeSet = true; // These are exposed in the UI as separate buttons break; case MAV_STANDARD_MODE_MISSION: name = "Mission"; break; case MAV_STANDARD_MODE_LAND: name = "Land"; + cannotBeSet = true; // These are exposed in the UI as separate buttons break; case MAV_STANDARD_MODE_TAKEOFF: name = "Takeoff"; + cannotBeSet = true; // These are exposed in the UI as separate buttons break; } - if (name == "Takeoff" || name == "VTOL Takeoff" || name == "Orbit" || name == "Land" || name == "Return") { // These are exposed in the UI as separate buttons - cannotBeSet = true; - } - qCDebug(StandardModesLog) << "Available mode received - name:" << name << "index:" << availableModes.mode_index << "standard_mode:" << availableModes.standard_mode << From 01d7aae64ee6bb7b0d4502459f7b47744f3f7926 Mon Sep 17 00:00:00 2001 From: Don Gagne Date: Mon, 15 Sep 2025 10:13:14 -0700 Subject: [PATCH 35/69] Tweaks to create-dmg checks/usage --- cmake/CreateMacDMG.cmake | 2 +- cmake/Install.cmake | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/cmake/CreateMacDMG.cmake b/cmake/CreateMacDMG.cmake index a1007da3a796..e195482237cf 100644 --- a/cmake/CreateMacDMG.cmake +++ b/cmake/CreateMacDMG.cmake @@ -13,6 +13,6 @@ file(COPY ${STAGING_BUNDLE_PATH} DESTINATION ${CMAKE_BINARY_DIR}/package) message(STATUS "Creating DMG: ${TARGET_APP_NAME}.dmg") execute_process( - COMMAND create-dmg --volname "${TARGET_APP_NAME}" --filesystem "APFS" "${TARGET_APP_NAME}.dmg" "${CMAKE_BINARY_DIR}/package/" + COMMAND ${CREATE_DMG_PROGRAM} --volname "${TARGET_APP_NAME}" --filesystem "APFS" "${TARGET_APP_NAME}.dmg" "${CMAKE_BINARY_DIR}/package/" COMMAND_ERROR_IS_FATAL ANY ) diff --git a/cmake/Install.cmake b/cmake/Install.cmake index d28dceb610d1..b90cf9b73475 100644 --- a/cmake/Install.cmake +++ b/cmake/Install.cmake @@ -76,6 +76,11 @@ elseif(WIN32) install(SCRIPT "${CMAKE_SOURCE_DIR}/cmake/CreateWinInstaller.cmake") elseif(MACOS) install(CODE "set(TARGET_APP_NAME ${QGC_APP_NAME})") + find_program(CREATE_DMG_PROGRAM create-dmg) + if(NOT CREATE_DMG_PROGRAM) + message(FATAL_ERROR "create-dmg not found. Please install it using `sh qgroundcontrol/tools/setup/install-dependencies-osx.sh`") + endif() + install(CODE "set(CREATE_DMG_PROGRAM \"${CREATE_DMG_PROGRAM}\")") install(CODE "set(MACDEPLOYQT ${Qt6_DIR}/../../../bin/macdeployqt)") install(SCRIPT "${CMAKE_SOURCE_DIR}/cmake/CreateMacDMG.cmake") endif() From 8ebdad2bc8735ece6f7e6d71847275f5ec094670 Mon Sep 17 00:00:00 2001 From: Don Gagne Date: Mon, 15 Sep 2025 10:13:29 -0700 Subject: [PATCH 36/69] Update to new osx dependency script name --- docs/en/qgc-dev-guide/getting_started/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/qgc-dev-guide/getting_started/index.md b/docs/en/qgc-dev-guide/getting_started/index.md index f30fa5d05d1b..70b6cbb89e33 100644 --- a/docs/en/qgc-dev-guide/getting_started/index.md +++ b/docs/en/qgc-dev-guide/getting_started/index.md @@ -88,7 +88,7 @@ To install Qt: - **Ubuntu:** `sudo bash ./qgroundcontrol/tools/setup/install-dependencies-debian.sh` - **Fedora:** `sudo dnf install speech-dispatcher SDL2-devel SDL2 systemd-devel patchelf` - **Arch Linux:** `pacman -Sy speech-dispatcher patchelf` - - **Mac** `sh qgroundcontrol/tools/setup/macos-dependencies.sh` + - **Mac** `sh qgroundcontrol/tools/setup/install-dependencies-osx.sh` - **Android** [Setup](https://doc.qt.io/qt-6/android-getting-started.html). JDK17 is required for the latest updated versions. NDK Version: 25.1.8937393 You can confirm it is being used by reviewing the project setting: **Projects > Manage Kits > Devices > Android (tab) > Android Settings > _JDK location_**. Note: Visit here for more detailed configurations [android.yml](.github/workflows/android.yml) From 5337d7608e935753f807634c73dfa41c92b35b8e Mon Sep 17 00:00:00 2001 From: Don Gagne Date: Mon, 15 Sep 2025 11:16:17 -0700 Subject: [PATCH 37/69] Don't use latest XCode It includes a change to AGL framework which causes QGC + Qt 6.8 to no longer build --- .github/workflows/macos.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index f5d680fc79df..62bd1607f93b 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -57,7 +57,7 @@ jobs: - name: Setup Xcode uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: latest-stable + xcode-version: '<=16.x' - name: Install Dependencies (include GStreamer) working-directory: ${{ github.workspace }}/tools/setup From da27f1bc9bb4410f70810293b65a472bcbed73ad Mon Sep 17 00:00:00 2001 From: Don Gagne Date: Mon, 15 Sep 2025 13:46:14 -0700 Subject: [PATCH 38/69] Update supported OS --- .../getting_started/download_and_install.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/en/qgc-user-guide/getting_started/download_and_install.md b/docs/en/qgc-user-guide/getting_started/download_and_install.md index c4e5c1753606..8ea75169fe84 100644 --- a/docs/en/qgc-user-guide/getting_started/download_and_install.md +++ b/docs/en/qgc-user-guide/getting_started/download_and_install.md @@ -16,7 +16,7 @@ For the best experience and compatibility, we recommend you the newest version o ## Windows {#windows} -_QGroundControl_ can be installed on 64 bit versions of Windows 10 (1809 or later) or Windows 11: +Supported versions: Windows 10 (1809 or later), Windows 11: 1. Download [QGroundControl-installer.exe](https://d176tv9ibo4jno.cloudfront.net/latest/QGroundControl-installer.exe). 1. Double click the executable to launch the installer. @@ -27,9 +27,9 @@ Use the first shortcut unless you experience startup or video rendering issues. For more information see [Troubleshooting QGC Setup > Windows: UI Rendering/Video Driver Issues](../troubleshooting/qgc_setup.md#opengl_troubleshooting). ::: -## Mac OS X {#macOS} +## Mac OS {#macOS} -_QGroundControl_ can be installed on macOS 12 (Monterey) or later: +Supported versions: macOS 12 (Monterey) or later: @@ -43,7 +43,7 @@ QGroundControl continues to not be signed. You will not to allow permission for ## Ubuntu Linux {#ubuntu} -_QGroundControl_ can be installed/run on Ubuntu LTS 22.04 (and later): +Supported versions: Ubuntu 22.04, 24.04: Ubuntu comes with a serial modem manager that interferes with any robotics related use of a serial port (or USB serial). Before installing _QGroundControl_ you should remove the modem manager and grant yourself permissions to access the serial port. @@ -74,7 +74,7 @@ To install _QGroundControl_: ## Android {#android} -_QGroundControl_ can be installed/run on Android 9 or later: +Supported versions: Android 9 to 15 (arm 32/64): - [Android 32/64 bit APK](https://qgroundcontrol.s3-us-west-2.amazonaws.com/latest/QGroundControl.apk) From 4abeaf01325703c82c14d0c64461178494a226eb Mon Sep 17 00:00:00 2001 From: Julian Oes Date: Fri, 27 Jun 2025 10:35:26 +1200 Subject: [PATCH 39/69] Camera: try to make discovery more robust This is an attempt to fix some of the corner cases trying to discover cameras, namely: - Implement missing retries for CAMERA_INFORMATION, us old specific commands and REQUEST_MESSAGE. - Try to simplify CameraControl::_initWhenReady a bit. - Fix _requestStreamInfo() and _requestStreamStatus() using wrong retry variable. - Add a few missing timers. --- src/Camera/QGCCameraManager.cc | 79 +++++++++++++++++++++---- src/Camera/QGCCameraManager.h | 1 + src/Camera/VehicleCameraControl.cc | 95 +++++++++++++++++++++++------- src/Camera/VehicleCameraControl.h | 4 ++ 4 files changed, 148 insertions(+), 31 deletions(-) diff --git a/src/Camera/QGCCameraManager.cc b/src/Camera/QGCCameraManager.cc index fb793f4ca8c3..9bd0d2df8c81 100644 --- a/src/Camera/QGCCameraManager.cc +++ b/src/Camera/QGCCameraManager.cc @@ -238,6 +238,8 @@ QGCCameraManager::_handleCameraInfo(const mavlink_message_t& message) if(_cameraInfoRequest.contains(sCompID) && !_cameraInfoRequest[sCompID]->infoReceived) { //-- Flag it as done _cameraInfoRequest[sCompID]->infoReceived = true; + _cameraInfoRequest[sCompID]->retryCount = 0; // Reset retry counter on success + qCDebug(CameraManagerLog) << "_handleCameraInfo: Success for compId" << message.compid << "- reset retry counter"; mavlink_camera_information_t info; mavlink_msg_camera_information_decode(&message, &info); qCDebug(CameraManagerLog) << "_handleCameraInfo:" << reinterpret_cast(info.model_name) << reinterpret_cast(info.vendor_name) << "Comp ID:" << message.compid; @@ -405,12 +407,39 @@ QGCCameraManager::_handleTrackingImageStatus(const mavlink_message_t& message) } } +// Forward declarations for mutually recursive handler functions +static void _requestCameraInfoCommandResultHandler(void* resultHandlerData, int compId, const mavlink_command_ack_t& ack, Vehicle::MavCmdResultFailureCode_t failureCode); +static void _requestCameraInfoMessageResultHandler(void* resultHandlerData, MAV_RESULT result, Vehicle::RequestMessageResultHandlerFailureCode_t failureCode, const mavlink_message_t& message); + static void _requestCameraInfoCommandResultHandler(void* resultHandlerData, int compId, const mavlink_command_ack_t& ack, Vehicle::MavCmdResultFailureCode_t failureCode) { auto cameraInfo = static_cast(resultHandlerData); if (ack.result != MAV_RESULT_ACCEPTED) { - qCDebug(CameraManagerLog) << "MAV_CMD_REQUEST_CAMERA_INFORMATION failed. compId" << cameraInfo->compID << "Result:" << ack.result << "FailureCode:" << failureCode; + qCDebug(CameraManagerLog) << "MAV_CMD_REQUEST_CAMERA_INFORMATION failed. compId" << cameraInfo->compID << "Result:" << ack.result << "FailureCode:" << failureCode << "retryCount:" << cameraInfo->retryCount; + + // Retry logic - up to 5 attempts + if (cameraInfo->retryCount < 5) { + cameraInfo->retryCount++; + + // Use REQUEST_MESSAGE on even attempts, legacy REQUEST_CAMERA_INFORMATION on odd + if (cameraInfo->retryCount % 2 == 0) { + qCDebug(CameraManagerLog) << "Retrying with MAV_CMD_REQUEST_MESSAGE, attempt" << cameraInfo->retryCount; + cameraInfo->vehicle->requestMessage(_requestCameraInfoMessageResultHandler, cameraInfo, cameraInfo->compID, MAVLINK_MSG_ID_CAMERA_INFORMATION); + } else { + qCDebug(CameraManagerLog) << "Retrying with MAV_CMD_REQUEST_CAMERA_INFORMATION, attempt" << cameraInfo->retryCount; + + Vehicle::MavCmdAckHandlerInfo_t ackHandlerInfo; + ackHandlerInfo.resultHandler = _requestCameraInfoCommandResultHandler; + ackHandlerInfo.resultHandlerData = cameraInfo; + ackHandlerInfo.progressHandler = nullptr; + ackHandlerInfo.progressHandlerData = nullptr; + + cameraInfo->vehicle->sendMavCommandWithHandler(&ackHandlerInfo, cameraInfo->compID, MAV_CMD_REQUEST_CAMERA_INFORMATION, 1 /* request camera capabilities */); + } + } else { + qCWarning(CameraManagerLog) << "Giving up requesting camera info after" << cameraInfo->retryCount << "attempts for compId" << cameraInfo->compID; + } } } @@ -419,15 +448,30 @@ static void _requestCameraInfoMessageResultHandler(void* resultHandlerData, MAV_ auto cameraInfo = static_cast(resultHandlerData); if (result != MAV_RESULT_ACCEPTED) { - qCDebug(CameraManagerLog) << "MAV_CMD_REQUEST_MESSAGE:MAVLINK_MSG_ID_CAMERA_INFORMATION failed. Falling back to MAV_CMD_REQUEST_CAMERA_INFORMATION. compId" << cameraInfo->compID << "Result:" << result << "FailureCode:" << failureCode; + qCDebug(CameraManagerLog) << "MAV_CMD_REQUEST_MESSAGE:MAVLINK_MSG_ID_CAMERA_INFORMATION failed. compId" << cameraInfo->compID << "Result:" << result << "FailureCode:" << failureCode << "retryCount:" << cameraInfo->retryCount; - Vehicle::MavCmdAckHandlerInfo_t ackHandlerInfo; - ackHandlerInfo.resultHandler = _requestCameraInfoCommandResultHandler; - ackHandlerInfo.resultHandlerData = cameraInfo; - ackHandlerInfo.progressHandler = nullptr; - ackHandlerInfo.progressHandlerData = nullptr; + // Retry logic - up to 5 attempts + if (cameraInfo->retryCount < 5) { + cameraInfo->retryCount++; - cameraInfo->vehicle->sendMavCommandWithHandler(&ackHandlerInfo, cameraInfo->compID, MAV_CMD_REQUEST_CAMERA_INFORMATION, 1 /* request camera capabilities */); + // Use REQUEST_MESSAGE on even attempts, legacy REQUEST_CAMERA_INFORMATION on odd + if (cameraInfo->retryCount % 2 == 0) { + qCDebug(CameraManagerLog) << "Retrying with MAV_CMD_REQUEST_MESSAGE, attempt" << cameraInfo->retryCount; + cameraInfo->vehicle->requestMessage(_requestCameraInfoMessageResultHandler, cameraInfo, cameraInfo->compID, MAVLINK_MSG_ID_CAMERA_INFORMATION); + } else { + qCDebug(CameraManagerLog) << "Retrying with MAV_CMD_REQUEST_CAMERA_INFORMATION, attempt" << cameraInfo->retryCount; + + Vehicle::MavCmdAckHandlerInfo_t ackHandlerInfo; + ackHandlerInfo.resultHandler = _requestCameraInfoCommandResultHandler; + ackHandlerInfo.resultHandlerData = cameraInfo; + ackHandlerInfo.progressHandler = nullptr; + ackHandlerInfo.progressHandlerData = nullptr; + + cameraInfo->vehicle->sendMavCommandWithHandler(&ackHandlerInfo, cameraInfo->compID, MAV_CMD_REQUEST_CAMERA_INFORMATION, 1 /* request camera capabilities */); + } + } else { + qCWarning(CameraManagerLog) << "Giving up requesting camera info after" << cameraInfo->retryCount << "attempts for compId" << cameraInfo->compID; + } } } @@ -435,10 +479,23 @@ static void _requestCameraInfoMessageResultHandler(void* resultHandlerData, MAV_ void QGCCameraManager::_requestCameraInfo(CameraStruct* pInfo) { - qCDebug(CameraManagerLog) << Q_FUNC_INFO << pInfo->compID; + qCDebug(CameraManagerLog) << Q_FUNC_INFO << pInfo->compID << "retryCount:" << pInfo->retryCount; + + // Use REQUEST_MESSAGE on even attempts (including 0), legacy REQUEST_CAMERA_INFORMATION on odd + if (pInfo->retryCount % 2 == 0) { + qCDebug(CameraManagerLog) << "Using MAV_CMD_REQUEST_MESSAGE for compId" << pInfo->compID; + _vehicle->requestMessage(_requestCameraInfoMessageResultHandler, pInfo, pInfo->compID, MAVLINK_MSG_ID_CAMERA_INFORMATION); + } else { + qCDebug(CameraManagerLog) << "Using MAV_CMD_REQUEST_CAMERA_INFORMATION for compId" << pInfo->compID; - // We first try using the newish MAV_CMD_REQUEST_MESSAGE mechanism - _vehicle->requestMessage(_requestCameraInfoMessageResultHandler, pInfo, pInfo->compID, MAVLINK_MSG_ID_CAMERA_INFORMATION); + Vehicle::MavCmdAckHandlerInfo_t ackHandlerInfo; + ackHandlerInfo.resultHandler = _requestCameraInfoCommandResultHandler; + ackHandlerInfo.resultHandlerData = pInfo; + ackHandlerInfo.progressHandler = nullptr; + ackHandlerInfo.progressHandlerData = nullptr; + + pInfo->vehicle->sendMavCommandWithHandler(&ackHandlerInfo, pInfo->compID, MAV_CMD_REQUEST_CAMERA_INFORMATION, 1 /* request camera capabilities */); + } } //---------------------------------------------------------------------------------------- diff --git a/src/Camera/QGCCameraManager.h b/src/Camera/QGCCameraManager.h index 113cc087e042..7b1db603e4db 100644 --- a/src/Camera/QGCCameraManager.h +++ b/src/Camera/QGCCameraManager.h @@ -65,6 +65,7 @@ class QGCCameraManager : public QObject bool infoReceived = false; uint8_t compID = 0; Vehicle* vehicle = nullptr; + int retryCount = 0; }; signals: diff --git a/src/Camera/VehicleCameraControl.cc b/src/Camera/VehicleCameraControl.cc index db3ee912cb73..24db1968e5f9 100644 --- a/src/Camera/VehicleCameraControl.cc +++ b/src/Camera/VehicleCameraControl.cc @@ -175,25 +175,28 @@ VehicleCameraControl::_initWhenReady() { qCDebug(CameraControlLog) << "_initWhenReady()"; if(isBasic()) { - qCDebug(CameraControlLog) << "Basic, MAVLink only messages."; - QTimer::singleShot(500, this, &VehicleCameraControl::_requestCameraSettings); - // For now manually request a second time - QTimer::singleShot(1500, this, &VehicleCameraControl::_requestCameraSettings); - QTimer::singleShot(250, this, &VehicleCameraControl::_checkForVideoStreams); + qCDebug(CameraControlLog) << "Basic, MAVLink only messages, no parameters."; //-- Basic cameras have no parameters _paramComplete = true; emit parametersReady(); } else { _requestAllParameters(); - //-- Give some time to load the parameters before going after the camera settings - QTimer::singleShot(2000, this, &VehicleCameraControl::_requestCameraSettings); - QTimer::singleShot(3000, this, &VehicleCameraControl::_requestCameraSettings); } + + QTimer::singleShot(1000, this, &VehicleCameraControl::_requestCameraSettings); + connect(&_cameraSettingsTimer, &QTimer::timeout, this, &VehicleCameraControl::_cameraSettingsTimeout); + + QTimer::singleShot(1500, this, &VehicleCameraControl::_checkForVideoStreams); + connect(_vehicle, &Vehicle::mavCommandResult, this, &VehicleCameraControl::_mavCommandResult); + connect(&_captureStatusTimer, &QTimer::timeout, this, &VehicleCameraControl::_requestCaptureStatus); _captureStatusTimer.setSingleShot(true); - QTimer::singleShot(2500, this, &VehicleCameraControl::_requestStorageInfo); _captureStatusTimer.start(2750); + + connect(&_storageInfoTimer, &QTimer::timeout, this, &VehicleCameraControl::_storageInfoTimeout); + QTimer::singleShot(2500, this, &VehicleCameraControl::_requestStorageInfo); + emit infoChanged(); delete _netManager; @@ -1429,24 +1432,32 @@ VehicleCameraControl::_requestParamUpdates() void VehicleCameraControl::_requestCameraSettings() { - qCDebug(CameraControlLog) << "_requestCameraSettings()"; + qCDebug(CameraControlLog) << "_requestCameraSettings() - retries:" << _cameraSettingsRetries << "timer active:" << _cameraSettingsTimer.isActive(); if(_vehicle) { // Use REQUEST_MESSAGE instead of deprecated REQUEST_CAMERA_SETTINGS // first time and every other time after that. - if(_cameraSettingsRetries++ % 2 == 0) { + if(_cameraSettingsRetries % 2 == 0) { + qCDebug(CameraControlLog) << "_requestCameraSettings() - using REQUEST_MESSAGE"; _vehicle->sendMavCommand( _compID, // target component MAV_CMD_REQUEST_MESSAGE, // command id false, // showError MAVLINK_MSG_ID_CAMERA_SETTINGS); // msgid } else { + qCDebug(CameraControlLog) << "_requestCameraSettings() - using legacy MAV_CMD_REQUEST_CAMERA_SETTINGS"; _vehicle->sendMavCommand( _compID, // Target component MAV_CMD_REQUEST_CAMERA_SETTINGS, // command id false, // showError 1); // Do Request } + if(_cameraSettingsTimer.isActive()) { + qCDebug(CameraControlLog) << "_requestCameraSettings() - RESTARTING already active timer"; + } else { + qCDebug(CameraControlLog) << "_requestCameraSettings() - starting timer"; + } + _cameraSettingsTimer.start(1000); // Wait up to a second for it } } @@ -1455,11 +1466,12 @@ VehicleCameraControl::_requestCameraSettings() void VehicleCameraControl::_requestStorageInfo() { - qCDebug(CameraControlLog) << "_requestStorageInfo()"; + qCDebug(CameraControlLog) << "_requestStorageInfo() - retries:" << _storageInfoRetries << "timer active:" << _storageInfoTimer.isActive(); if(_vehicle) { - // Use REQUEST_MESSAGE instead of deprecated REQUEST_CAMERA_SETTINGS + // Use REQUEST_MESSAGE instead of deprecated REQUEST_STORAGE_INFORMATION // first time and every other time after that. - if(_storageInfoRetries++ % 2 == 0) { + if(_storageInfoRetries % 2 == 0) { + qCDebug(CameraControlLog) << "_requestStorageInfo() - using REQUEST_MESSAGE"; _vehicle->sendMavCommand( _compID, // target component MAV_CMD_REQUEST_MESSAGE, // command id @@ -1467,6 +1479,7 @@ VehicleCameraControl::_requestStorageInfo() MAVLINK_MSG_ID_STORAGE_INFORMATION, // msgid 0); // storage ID } else { + qCDebug(CameraControlLog) << "_requestStorageInfo() - using legacy MAV_CMD_REQUEST_STORAGE_INFORMATION"; _vehicle->sendMavCommand( _compID, // Target component MAV_CMD_REQUEST_STORAGE_INFORMATION, // command id @@ -1474,6 +1487,8 @@ VehicleCameraControl::_requestStorageInfo() 0, // Storage ID (0 for all, 1 for first, 2 for second, etc.) 1); // Do Request } + qCDebug(CameraControlLog) << "_requestStorageInfo() - starting timer"; + _storageInfoTimer.start(1000); // Wait up to a second for it } } @@ -1481,7 +1496,9 @@ VehicleCameraControl::_requestStorageInfo() void VehicleCameraControl::handleSettings(const mavlink_camera_settings_t& settings) { - qCDebug(CameraControlLog) << "handleSettings() Mode:" << settings.mode_id; + qCDebug(CameraControlLog) << "handleSettings() Mode:" << settings.mode_id << "- stopping timer, resetting retries"; + _cameraSettingsTimer.stop(); + _cameraSettingsRetries = 0; _setCameraMode(static_cast(settings.mode_id)); qreal z = static_cast(settings.zoomLevel); qreal f = static_cast(settings.focusLevel); @@ -1499,6 +1516,9 @@ VehicleCameraControl::handleSettings(const mavlink_camera_settings_t& settings) void VehicleCameraControl::handleStorageInfo(const mavlink_storage_information_t& st) { + qCDebug(CameraControlLog) << "handleStorageInfo() - stopping timer, resetting retries"; + _storageInfoTimer.stop(); + _storageInfoRetries = 0; qCDebug(CameraControlLog) << "handleStorageInfo:" << "\n\tStorage id:" << st.storage_id << "\n\tStorage count:" << st.storage_count @@ -1776,7 +1796,7 @@ VehicleCameraControl::_requestStreamInfo(uint8_t streamID) qCDebug(CameraControlLog) << "Requesting video stream info for:" << streamID; // By default, try to use new REQUEST_MESSAGE command instead of // deprecated MAV_CMD_REQUEST_VIDEO_STREAM_INFORMATION. - if (_videoStreamStatusRetries++ % 2 == 0) { + if (_videoStreamInfoRetries % 2 == 0) { _vehicle->sendMavCommand( _compID, // target component MAV_CMD_REQUEST_MESSAGE, // command id @@ -1790,6 +1810,7 @@ VehicleCameraControl::_requestStreamInfo(uint8_t streamID) false, // ShowError streamID); // Stream ID } + _streamInfoTimer.start(1000); // Wait up to a second for it } //----------------------------------------------------------------------------- @@ -1798,8 +1819,8 @@ VehicleCameraControl::_requestStreamStatus(uint8_t streamID) { qCDebug(CameraControlLog) << "Requesting video stream status for:" << streamID; // By default, try to use new REQUEST_MESSAGE command instead of - // deprecated MAV_CMD_REQUEST_VIDEO_STREAM_INFORMATION. - if (_videoStreamInfoRetries % 2 == 0) { + // deprecated MAV_CMD_REQUEST_VIDEO_STREAM_STATUS. + if (_videoStreamStatusRetries % 2 == 0) { _vehicle->sendMavCommand( _compID, // target component MAV_CMD_REQUEST_MESSAGE, // command id @@ -1884,12 +1905,48 @@ VehicleCameraControl::_streamInfoTimeout() void VehicleCameraControl::_streamStatusTimeout() { + _videoStreamStatusRetries++; + if(_videoStreamStatusRetries > 5) { + qCWarning(CameraControlLog) << "Giving up requesting video stream status"; + _streamStatusTimer.stop(); + return; + } QGCVideoStreamInfo* pStream = currentStreamInstance(); if(pStream) { _requestStreamStatus(static_cast(pStream->streamID())); } } +//----------------------------------------------------------------------------- +void +VehicleCameraControl::_cameraSettingsTimeout() +{ + _cameraSettingsRetries++; + qCDebug(CameraControlLog) << "_cameraSettingsTimeout() - retries now:" << _cameraSettingsRetries; + if(_cameraSettingsRetries > 5) { + qCWarning(CameraControlLog) << "Giving up requesting camera settings after" << _cameraSettingsRetries << "retries"; + _cameraSettingsTimer.stop(); + return; + } + qCDebug(CameraControlLog) << "_cameraSettingsTimeout() - calling _requestCameraSettings()"; + _requestCameraSettings(); +} + +//----------------------------------------------------------------------------- +void +VehicleCameraControl::_storageInfoTimeout() +{ + _storageInfoRetries++; + qCDebug(CameraControlLog) << "_storageInfoTimeout() - retries now:" << _storageInfoRetries; + if(_storageInfoRetries > 5) { + qCWarning(CameraControlLog) << "Giving up requesting storage info after" << _storageInfoRetries << "retries"; + _storageInfoTimer.stop(); + return; + } + qCDebug(CameraControlLog) << "_storageInfoTimeout() - calling _requestStorageInfo()"; + _requestStorageInfo(); +} + //----------------------------------------------------------------------------- QStringList VehicleCameraControl::_loadExclusions(QDomNode option) @@ -2198,8 +2255,6 @@ VehicleCameraControl::_paramDone() //-- All parameters loaded (or timed out) _paramComplete = true; emit parametersReady(); - //-- Check for video streaming - _checkForVideoStreams(); } //----------------------------------------------------------------------------- diff --git a/src/Camera/VehicleCameraControl.h b/src/Camera/VehicleCameraControl.h index 15b7ac2a8794..11c61359e8b7 100644 --- a/src/Camera/VehicleCameraControl.h +++ b/src/Camera/VehicleCameraControl.h @@ -232,6 +232,8 @@ protected slots: virtual void _paramDone (); virtual void _streamInfoTimeout (); virtual void _streamStatusTimeout (); + virtual void _cameraSettingsTimeout (); + virtual void _storageInfoTimeout (); virtual void _recTimerHandler (); virtual void _checkForVideoStreams (); @@ -306,6 +308,8 @@ protected slots: int _expectedCount = 1; QTimer _streamInfoTimer; QTimer _streamStatusTimer; + QTimer _cameraSettingsTimer; + QTimer _storageInfoTimer; QmlObjectListModel _streams; QStringList _streamLabels; ThermalViewMode _thermalMode = THERMAL_BLEND; From b0bbad3b1b22a85d6f798c7bef1df61e44674e07 Mon Sep 17 00:00:00 2001 From: Julian Oes Date: Mon, 7 Jul 2025 16:45:15 +1200 Subject: [PATCH 40/69] Camera: fixup comment regarding components --- src/Camera/QGCCameraManager.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Camera/QGCCameraManager.cc b/src/Camera/QGCCameraManager.cc index 9bd0d2df8c81..7e9a987320ec 100644 --- a/src/Camera/QGCCameraManager.cc +++ b/src/Camera/QGCCameraManager.cc @@ -97,7 +97,8 @@ void QGCCameraManager::_vehicleReady(bool ready) void QGCCameraManager::_mavlinkMessageReceived(const mavlink_message_t& message) { - //-- Only pay attention to camera components, as identified by their compId + //-- Only pay attention to the camera components, as identified by their compId, + // as well as the autopilot, as it might have a non-MAVLink camera connected. if(message.sysid == _vehicle->id() && (message.compid == MAV_COMP_ID_AUTOPILOT1 || (message.compid >= MAV_COMP_ID_CAMERA && message.compid <= MAV_COMP_ID_CAMERA6))) { switch (message.msgid) { From f2751718bd502e9a4674d6b913cf1b64c849564f Mon Sep 17 00:00:00 2001 From: Julian Oes Date: Mon, 7 Jul 2025 16:45:30 +1200 Subject: [PATCH 41/69] Camera: consolidate requesting message The logic was spread across 3 functions, we can actually remove a bit of this duplication. --- src/Camera/QGCCameraManager.cc | 69 +++++++++++----------------------- src/Camera/QGCCameraManager.h | 3 ++ 2 files changed, 25 insertions(+), 47 deletions(-) diff --git a/src/Camera/QGCCameraManager.cc b/src/Camera/QGCCameraManager.cc index 7e9a987320ec..5a87f6be2a6b 100644 --- a/src/Camera/QGCCameraManager.cc +++ b/src/Camera/QGCCameraManager.cc @@ -411,6 +411,7 @@ QGCCameraManager::_handleTrackingImageStatus(const mavlink_message_t& message) // Forward declarations for mutually recursive handler functions static void _requestCameraInfoCommandResultHandler(void* resultHandlerData, int compId, const mavlink_command_ack_t& ack, Vehicle::MavCmdResultFailureCode_t failureCode); static void _requestCameraInfoMessageResultHandler(void* resultHandlerData, MAV_RESULT result, Vehicle::RequestMessageResultHandlerFailureCode_t failureCode, const mavlink_message_t& message); +static void _requestCameraInfoHelper(QGCCameraManager* manager, QGCCameraManager::CameraStruct* pInfo); static void _requestCameraInfoCommandResultHandler(void* resultHandlerData, int compId, const mavlink_command_ack_t& ack, Vehicle::MavCmdResultFailureCode_t failureCode) { @@ -419,28 +420,9 @@ static void _requestCameraInfoCommandResultHandler(void* resultHandlerData, int if (ack.result != MAV_RESULT_ACCEPTED) { qCDebug(CameraManagerLog) << "MAV_CMD_REQUEST_CAMERA_INFORMATION failed. compId" << cameraInfo->compID << "Result:" << ack.result << "FailureCode:" << failureCode << "retryCount:" << cameraInfo->retryCount; - // Retry logic - up to 5 attempts - if (cameraInfo->retryCount < 5) { - cameraInfo->retryCount++; - - // Use REQUEST_MESSAGE on even attempts, legacy REQUEST_CAMERA_INFORMATION on odd - if (cameraInfo->retryCount % 2 == 0) { - qCDebug(CameraManagerLog) << "Retrying with MAV_CMD_REQUEST_MESSAGE, attempt" << cameraInfo->retryCount; - cameraInfo->vehicle->requestMessage(_requestCameraInfoMessageResultHandler, cameraInfo, cameraInfo->compID, MAVLINK_MSG_ID_CAMERA_INFORMATION); - } else { - qCDebug(CameraManagerLog) << "Retrying with MAV_CMD_REQUEST_CAMERA_INFORMATION, attempt" << cameraInfo->retryCount; - - Vehicle::MavCmdAckHandlerInfo_t ackHandlerInfo; - ackHandlerInfo.resultHandler = _requestCameraInfoCommandResultHandler; - ackHandlerInfo.resultHandlerData = cameraInfo; - ackHandlerInfo.progressHandler = nullptr; - ackHandlerInfo.progressHandlerData = nullptr; - - cameraInfo->vehicle->sendMavCommandWithHandler(&ackHandlerInfo, cameraInfo->compID, MAV_CMD_REQUEST_CAMERA_INFORMATION, 1 /* request camera capabilities */); - } - } else { - qCWarning(CameraManagerLog) << "Giving up requesting camera info after" << cameraInfo->retryCount << "attempts for compId" << cameraInfo->compID; - } + // Retry logic + cameraInfo->retryCount++; + _requestCameraInfoHelper(static_cast(cameraInfo->parent()), cameraInfo); } } @@ -451,41 +433,27 @@ static void _requestCameraInfoMessageResultHandler(void* resultHandlerData, MAV_ if (result != MAV_RESULT_ACCEPTED) { qCDebug(CameraManagerLog) << "MAV_CMD_REQUEST_MESSAGE:MAVLINK_MSG_ID_CAMERA_INFORMATION failed. compId" << cameraInfo->compID << "Result:" << result << "FailureCode:" << failureCode << "retryCount:" << cameraInfo->retryCount; - // Retry logic - up to 5 attempts - if (cameraInfo->retryCount < 5) { - cameraInfo->retryCount++; - - // Use REQUEST_MESSAGE on even attempts, legacy REQUEST_CAMERA_INFORMATION on odd - if (cameraInfo->retryCount % 2 == 0) { - qCDebug(CameraManagerLog) << "Retrying with MAV_CMD_REQUEST_MESSAGE, attempt" << cameraInfo->retryCount; - cameraInfo->vehicle->requestMessage(_requestCameraInfoMessageResultHandler, cameraInfo, cameraInfo->compID, MAVLINK_MSG_ID_CAMERA_INFORMATION); - } else { - qCDebug(CameraManagerLog) << "Retrying with MAV_CMD_REQUEST_CAMERA_INFORMATION, attempt" << cameraInfo->retryCount; - - Vehicle::MavCmdAckHandlerInfo_t ackHandlerInfo; - ackHandlerInfo.resultHandler = _requestCameraInfoCommandResultHandler; - ackHandlerInfo.resultHandlerData = cameraInfo; - ackHandlerInfo.progressHandler = nullptr; - ackHandlerInfo.progressHandlerData = nullptr; - - cameraInfo->vehicle->sendMavCommandWithHandler(&ackHandlerInfo, cameraInfo->compID, MAV_CMD_REQUEST_CAMERA_INFORMATION, 1 /* request camera capabilities */); - } - } else { - qCWarning(CameraManagerLog) << "Giving up requesting camera info after" << cameraInfo->retryCount << "attempts for compId" << cameraInfo->compID; - } + // Retry logic + cameraInfo->retryCount++; + _requestCameraInfoHelper(static_cast(cameraInfo->parent()), cameraInfo); } } //----------------------------------------------------------------------------- -void -QGCCameraManager::_requestCameraInfo(CameraStruct* pInfo) +static void _requestCameraInfoHelper(QGCCameraManager* manager, QGCCameraManager::CameraStruct* pInfo) { qCDebug(CameraManagerLog) << Q_FUNC_INFO << pInfo->compID << "retryCount:" << pInfo->retryCount; + // Check retry limit + if (pInfo->retryCount >= 6) { + qCWarning(CameraManagerLog) << "Giving up requesting camera info after" << pInfo->retryCount << "attempts for compId" << pInfo->compID; + return; + } + // Use REQUEST_MESSAGE on even attempts (including 0), legacy REQUEST_CAMERA_INFORMATION on odd if (pInfo->retryCount % 2 == 0) { qCDebug(CameraManagerLog) << "Using MAV_CMD_REQUEST_MESSAGE for compId" << pInfo->compID; - _vehicle->requestMessage(_requestCameraInfoMessageResultHandler, pInfo, pInfo->compID, MAVLINK_MSG_ID_CAMERA_INFORMATION); + manager->vehicle()->requestMessage(_requestCameraInfoMessageResultHandler, pInfo, pInfo->compID, MAVLINK_MSG_ID_CAMERA_INFORMATION); } else { qCDebug(CameraManagerLog) << "Using MAV_CMD_REQUEST_CAMERA_INFORMATION for compId" << pInfo->compID; @@ -499,6 +467,13 @@ QGCCameraManager::_requestCameraInfo(CameraStruct* pInfo) } } +//----------------------------------------------------------------------------- +void +QGCCameraManager::_requestCameraInfo(CameraStruct* pInfo) +{ + _requestCameraInfoHelper(this, pInfo); +} + //---------------------------------------------------------------------------------------- void QGCCameraManager::_activeJoystickChanged(Joystick* joystick) diff --git a/src/Camera/QGCCameraManager.h b/src/Camera/QGCCameraManager.h index 7b1db603e4db..4b1ac3a9f1c2 100644 --- a/src/Camera/QGCCameraManager.h +++ b/src/Camera/QGCCameraManager.h @@ -57,6 +57,9 @@ class QGCCameraManager : public QObject /// Returns a list of CameraMetaData objects for available cameras on the vehicle. virtual const QVariantList &cameraList(); + // Helper method for static functions to access vehicle + Vehicle* vehicle() const { return _vehicle; } + // This is public to avoid some circular include problems caused by statics class CameraStruct : public QObject { public: From 6f68e709e9ae99c49d4d4b242b0d2867788af99f Mon Sep 17 00:00:00 2001 From: Julian Oes Date: Mon, 7 Jul 2025 16:59:10 +1200 Subject: [PATCH 42/69] Camera: retry camera discovery after silence This makes us retry to discover a camera if it disappears after not having been connected previously (for whatever reason). --- src/Camera/QGCCameraManager.cc | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Camera/QGCCameraManager.cc b/src/Camera/QGCCameraManager.cc index 5a87f6be2a6b..09e785aa7272 100644 --- a/src/Camera/QGCCameraManager.cc +++ b/src/Camera/QGCCameraManager.cc @@ -157,6 +157,17 @@ void QGCCameraManager::_handleHeartbeat(const mavlink_message_t &message) if (pInfo->infoReceived) { //-- We have it. Just update the heartbeat timeout pInfo->lastHeartbeat.start(); + } else { + //-- Camera info not received yet. Check if camera was silent and is now back + if (pInfo->lastHeartbeat.elapsed() > 5000) { + qCDebug(CameraManagerLog) << "Camera" << message.compid << "reappeared after being silent. Resetting retry count and requesting info."; + pInfo->retryCount = 0; // Reset retry count for fresh attempts + pInfo->lastHeartbeat.start(); + _requestCameraInfo(pInfo); + } else { + //-- Just update heartbeat + pInfo->lastHeartbeat.start(); + } } } else { qWarning() << Q_FUNC_INFO << "_cameraInfoRequest[" << sCompID << "] is null"; From e8d2f4fb00d63d743c6de2a720fbdbfde6a4dd2e Mon Sep 17 00:00:00 2001 From: Julian Oes Date: Tue, 8 Jul 2025 10:46:52 +1200 Subject: [PATCH 43/69] Camera: use exponential backoff for discovery In case a camera takes a while to properly respond, it's useful to wait a while with retries while also not spamming the component with commands the whole time. Therefore, retry with exponential backoff should do the job. --- src/Camera/QGCCameraManager.cc | 48 ++++++++++++++++++++++++++-------- src/Camera/QGCCameraManager.h | 1 + 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/src/Camera/QGCCameraManager.cc b/src/Camera/QGCCameraManager.cc index 09e785aa7272..37d22a088984 100644 --- a/src/Camera/QGCCameraManager.cc +++ b/src/Camera/QGCCameraManager.cc @@ -35,6 +35,8 @@ QGCCameraManager::CameraStruct::CameraStruct(QObject* parent, uint8_t compID_, V , compID (compID_) , vehicle (vehicle_) { + backoffTimer = new QTimer(this); + backoffTimer->setSingleShot(true); } //----------------------------------------------------------------------------- @@ -162,6 +164,7 @@ void QGCCameraManager::_handleHeartbeat(const mavlink_message_t &message) if (pInfo->lastHeartbeat.elapsed() > 5000) { qCDebug(CameraManagerLog) << "Camera" << message.compid << "reappeared after being silent. Resetting retry count and requesting info."; pInfo->retryCount = 0; // Reset retry count for fresh attempts + pInfo->backoffTimer->stop(); // Stop any pending backoff timer pInfo->lastHeartbeat.start(); _requestCameraInfo(pInfo); } else { @@ -251,6 +254,7 @@ QGCCameraManager::_handleCameraInfo(const mavlink_message_t& message) //-- Flag it as done _cameraInfoRequest[sCompID]->infoReceived = true; _cameraInfoRequest[sCompID]->retryCount = 0; // Reset retry counter on success + _cameraInfoRequest[sCompID]->backoffTimer->stop(); // Stop any pending backoff timer qCDebug(CameraManagerLog) << "_handleCameraInfo: Success for compId" << message.compid << "- reset retry counter"; mavlink_camera_information_t info; mavlink_msg_camera_information_decode(&message, &info); @@ -424,6 +428,34 @@ static void _requestCameraInfoCommandResultHandler(void* resultHandlerData, int static void _requestCameraInfoMessageResultHandler(void* resultHandlerData, MAV_RESULT result, Vehicle::RequestMessageResultHandlerFailureCode_t failureCode, const mavlink_message_t& message); static void _requestCameraInfoHelper(QGCCameraManager* manager, QGCCameraManager::CameraStruct* pInfo); + +static void _handleCameraInfoRetry(QGCCameraManager::CameraStruct* cameraInfo) +{ + cameraInfo->retryCount++; + auto manager = static_cast(cameraInfo->parent()); + + // For even attempts >= 2, use exponential backoff + if (cameraInfo->retryCount >= 2 && cameraInfo->retryCount % 2 == 0) { + // Calculate delay: 2^(retryCount/2) seconds + int delaySeconds = 1 << (cameraInfo->retryCount / 2); + int delayMs = delaySeconds * 1000; + + qCDebug(CameraManagerLog) << "Waiting" << delaySeconds << "seconds before retry for compId" << cameraInfo->compID; + + // Stop any existing timer and set up new one + cameraInfo->backoffTimer->stop(); + QObject::disconnect(cameraInfo->backoffTimer, nullptr, nullptr, nullptr); + QObject::connect(cameraInfo->backoffTimer, &QTimer::timeout, cameraInfo, [=]() { + _requestCameraInfoHelper(manager, cameraInfo); + }); + + cameraInfo->backoffTimer->start(delayMs); + } else { + // Make immediate retry + _requestCameraInfoHelper(manager, cameraInfo); + } +} + static void _requestCameraInfoCommandResultHandler(void* resultHandlerData, int compId, const mavlink_command_ack_t& ack, Vehicle::MavCmdResultFailureCode_t failureCode) { auto cameraInfo = static_cast(resultHandlerData); @@ -431,9 +463,7 @@ static void _requestCameraInfoCommandResultHandler(void* resultHandlerData, int if (ack.result != MAV_RESULT_ACCEPTED) { qCDebug(CameraManagerLog) << "MAV_CMD_REQUEST_CAMERA_INFORMATION failed. compId" << cameraInfo->compID << "Result:" << ack.result << "FailureCode:" << failureCode << "retryCount:" << cameraInfo->retryCount; - // Retry logic - cameraInfo->retryCount++; - _requestCameraInfoHelper(static_cast(cameraInfo->parent()), cameraInfo); + _handleCameraInfoRetry(cameraInfo); } } @@ -444,24 +474,20 @@ static void _requestCameraInfoMessageResultHandler(void* resultHandlerData, MAV_ if (result != MAV_RESULT_ACCEPTED) { qCDebug(CameraManagerLog) << "MAV_CMD_REQUEST_MESSAGE:MAVLINK_MSG_ID_CAMERA_INFORMATION failed. compId" << cameraInfo->compID << "Result:" << result << "FailureCode:" << failureCode << "retryCount:" << cameraInfo->retryCount; - // Retry logic - cameraInfo->retryCount++; - _requestCameraInfoHelper(static_cast(cameraInfo->parent()), cameraInfo); + _handleCameraInfoRetry(cameraInfo); } } //----------------------------------------------------------------------------- static void _requestCameraInfoHelper(QGCCameraManager* manager, QGCCameraManager::CameraStruct* pInfo) { - qCDebug(CameraManagerLog) << Q_FUNC_INFO << pInfo->compID << "retryCount:" << pInfo->retryCount; - - // Check retry limit - if (pInfo->retryCount >= 6) { + // Give up after 10 attempts + if (pInfo->retryCount >= 10) { qCWarning(CameraManagerLog) << "Giving up requesting camera info after" << pInfo->retryCount << "attempts for compId" << pInfo->compID; return; } - // Use REQUEST_MESSAGE on even attempts (including 0), legacy REQUEST_CAMERA_INFORMATION on odd + // Make immediate request - alternate between REQUEST_MESSAGE and REQUEST_CAMERA_INFORMATION if (pInfo->retryCount % 2 == 0) { qCDebug(CameraManagerLog) << "Using MAV_CMD_REQUEST_MESSAGE for compId" << pInfo->compID; manager->vehicle()->requestMessage(_requestCameraInfoMessageResultHandler, pInfo, pInfo->compID, MAVLINK_MSG_ID_CAMERA_INFORMATION); diff --git a/src/Camera/QGCCameraManager.h b/src/Camera/QGCCameraManager.h index 4b1ac3a9f1c2..6249e812919e 100644 --- a/src/Camera/QGCCameraManager.h +++ b/src/Camera/QGCCameraManager.h @@ -69,6 +69,7 @@ class QGCCameraManager : public QObject uint8_t compID = 0; Vehicle* vehicle = nullptr; int retryCount = 0; + QTimer* backoffTimer = nullptr; }; signals: From 2791cf5068a0e129c222af37ee3723b02ae61fd9 Mon Sep 17 00:00:00 2001 From: Julian Oes Date: Tue, 8 Jul 2025 11:32:07 +1200 Subject: [PATCH 44/69] Camera: standardize and fix VehicleCameraControl retry logic - Fix missing retry counter reset in handleCaptureStatus() - Fix missing timer stop in handleCaptureStatus() - Consolidate inconsistent counter usage for REQUEST_CAMERA_CAPTURE_STATUS - Standardize all retry limits to 6 attempts (was mixed 5/6) - Standardize all retry timeouts to 1000ms (was mixed 500ms/1000ms) - Standardize video stream info retry factor from 5 to 6 (_expectedCount * 6) - Standardize _initWhenReady timing to consistent 500ms spacing - Add consistent debug logging across all request functions - Verify all success handlers properly stop timers and reset counters - Ensure all requests use proper alternating message patterns --- src/Camera/VehicleCameraControl.cc | 40 +++++++++++++++++++----------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/src/Camera/VehicleCameraControl.cc b/src/Camera/VehicleCameraControl.cc index 24db1968e5f9..d839c004e273 100644 --- a/src/Camera/VehicleCameraControl.cc +++ b/src/Camera/VehicleCameraControl.cc @@ -183,19 +183,19 @@ VehicleCameraControl::_initWhenReady() _requestAllParameters(); } - QTimer::singleShot(1000, this, &VehicleCameraControl::_requestCameraSettings); + QTimer::singleShot(500, this, &VehicleCameraControl::_requestCameraSettings); connect(&_cameraSettingsTimer, &QTimer::timeout, this, &VehicleCameraControl::_cameraSettingsTimeout); - QTimer::singleShot(1500, this, &VehicleCameraControl::_checkForVideoStreams); + QTimer::singleShot(1000, this, &VehicleCameraControl::_checkForVideoStreams); connect(_vehicle, &Vehicle::mavCommandResult, this, &VehicleCameraControl::_mavCommandResult); connect(&_captureStatusTimer, &QTimer::timeout, this, &VehicleCameraControl::_requestCaptureStatus); _captureStatusTimer.setSingleShot(true); - _captureStatusTimer.start(2750); + _captureStatusTimer.start(1500); connect(&_storageInfoTimer, &QTimer::timeout, this, &VehicleCameraControl::_storageInfoTimeout); - QTimer::singleShot(2500, this, &VehicleCameraControl::_requestStorageInfo); + QTimer::singleShot(2000, this, &VehicleCameraControl::_requestStorageInfo); emit infoChanged(); @@ -640,15 +640,17 @@ VehicleCameraControl::stopZoom() void VehicleCameraControl::_requestCaptureStatus() { - qCDebug(CameraControlLog) << "_requestCaptureStatus()"; + qCDebug(CameraControlLog) << "_requestCaptureStatus() - retries:" << _cameraCaptureStatusRetries; if(_cameraCaptureStatusRetries++ % 2 == 0) { + qCDebug(CameraControlLog) << "_requestCaptureStatus() - using REQUEST_MESSAGE"; _vehicle->sendMavCommand( _compID, // target component MAV_CMD_REQUEST_MESSAGE, // command id false, // showError MAVLINK_MSG_ID_CAMERA_CAPTURE_STATUS); // msgid } else { + qCDebug(CameraControlLog) << "_requestCaptureStatus() - using legacy MAV_CMD_REQUEST_CAMERA_CAPTURE_STATUS"; _vehicle->sendMavCommand( _compID, // target component MAV_CMD_REQUEST_CAMERA_CAPTURE_STATUS, // command id @@ -698,7 +700,7 @@ VehicleCameraControl::_mavCommandResult(int vehicleId, int component, int comman _captureStatusTimer.start(1000); break; case MAV_CMD_REQUEST_CAMERA_CAPTURE_STATUS: - _captureInfoRetries = 0; + _cameraCaptureStatusRetries = 0; break; case MAV_CMD_REQUEST_STORAGE_INFORMATION: _storageInfoRetries = 0; @@ -721,7 +723,7 @@ VehicleCameraControl::_mavCommandResult(int vehicleId, int component, int comman break; case MAV_CMD_IMAGE_START_CAPTURE: case MAV_CMD_IMAGE_STOP_CAPTURE: - if(++_captureInfoRetries < 5) { + if(++_captureInfoRetries <= 5) { _captureStatusTimer.start(1000); } else { qCDebug(CameraControlLog) << "Giving up start/stop image capture"; @@ -729,15 +731,15 @@ VehicleCameraControl::_mavCommandResult(int vehicleId, int component, int comman } break; case MAV_CMD_REQUEST_CAMERA_CAPTURE_STATUS: - if(++_captureInfoRetries < 5) { - _captureStatusTimer.start(500); + if(++_cameraCaptureStatusRetries <= 5) { + _captureStatusTimer.start(1000); } else { qCDebug(CameraControlLog) << "Giving up requesting capture status"; } break; case MAV_CMD_REQUEST_STORAGE_INFORMATION: - if(++_storageInfoRetries < 5) { - QTimer::singleShot(500, this, &VehicleCameraControl::_requestStorageInfo); + if(++_storageInfoRetries <= 5) { + QTimer::singleShot(1000, this, &VehicleCameraControl::_requestStorageInfo); } else { qCDebug(CameraControlLog) << "Giving up requesting storage status"; } @@ -1561,6 +1563,9 @@ void VehicleCameraControl::handleCaptureStatus(const mavlink_camera_capture_status_t& cap) { //-- This is a response to MAV_CMD_REQUEST_CAMERA_CAPTURE_STATUS + qCDebug(CameraControlLog) << "handleCaptureStatus() - stopping timer, resetting retries"; + _captureStatusTimer.stop(); + _cameraCaptureStatusRetries = 0; qCDebug(CameraControlLog).noquote() << "handleCaptureStatus:" << "\n\tImage status:" << captureImageStatusToStr(cap.image_status) << "\n\tVideo status:" << captureVideoStatusToStr(cap.video_status) @@ -1639,13 +1644,14 @@ VehicleCameraControl::handleVideoInfo(const mavlink_video_stream_information_t* void VehicleCameraControl::handleVideoStatus(const mavlink_video_stream_status_t* vs) { + qCDebug(CameraControlLog) << "handleVideoStatus() - stopping timer, resetting retries"; _streamStatusTimer.stop(); + _videoStreamStatusRetries = 0; qCDebug(CameraControlLog) << "handleVideoStatus:" << vs->stream_id; QGCVideoStreamInfo* pInfo = _findStream(vs->stream_id); if(pInfo) { pInfo->update(*vs); } - _videoStreamStatusRetries = 0; } //----------------------------------------------------------------------------- @@ -1793,10 +1799,11 @@ VehicleCameraControl::thermalStreamInstance() void VehicleCameraControl::_requestStreamInfo(uint8_t streamID) { - qCDebug(CameraControlLog) << "Requesting video stream info for:" << streamID; + qCDebug(CameraControlLog) << "_requestStreamInfo() - stream:" << streamID << "retries:" << _videoStreamInfoRetries; // By default, try to use new REQUEST_MESSAGE command instead of // deprecated MAV_CMD_REQUEST_VIDEO_STREAM_INFORMATION. if (_videoStreamInfoRetries % 2 == 0) { + qCDebug(CameraControlLog) << "_requestStreamInfo() - using REQUEST_MESSAGE"; _vehicle->sendMavCommand( _compID, // target component MAV_CMD_REQUEST_MESSAGE, // command id @@ -1804,6 +1811,7 @@ VehicleCameraControl::_requestStreamInfo(uint8_t streamID) MAVLINK_MSG_ID_VIDEO_STREAM_INFORMATION, // msgid streamID); // stream ID } else { + qCDebug(CameraControlLog) << "_requestStreamInfo() - using legacy MAV_CMD_REQUEST_VIDEO_STREAM_INFORMATION"; _vehicle->sendMavCommand( _compID, // Target component MAV_CMD_REQUEST_VIDEO_STREAM_INFORMATION, // Command id @@ -1817,10 +1825,11 @@ VehicleCameraControl::_requestStreamInfo(uint8_t streamID) void VehicleCameraControl::_requestStreamStatus(uint8_t streamID) { - qCDebug(CameraControlLog) << "Requesting video stream status for:" << streamID; + qCDebug(CameraControlLog) << "_requestStreamStatus() - stream:" << streamID << "retries:" << _videoStreamStatusRetries; // By default, try to use new REQUEST_MESSAGE command instead of // deprecated MAV_CMD_REQUEST_VIDEO_STREAM_STATUS. if (_videoStreamStatusRetries % 2 == 0) { + qCDebug(CameraControlLog) << "_requestStreamStatus() - using REQUEST_MESSAGE"; _vehicle->sendMavCommand( _compID, // target component MAV_CMD_REQUEST_MESSAGE, // command id @@ -1828,6 +1837,7 @@ VehicleCameraControl::_requestStreamStatus(uint8_t streamID) MAVLINK_MSG_ID_VIDEO_STREAM_STATUS, // msgid streamID); // stream id } else { + qCDebug(CameraControlLog) << "_requestStreamStatus() - using legacy MAV_CMD_REQUEST_VIDEO_STREAM_STATUS"; _vehicle->sendMavCommand( _compID, // Target component MAV_CMD_REQUEST_VIDEO_STREAM_STATUS, // Command id @@ -1881,7 +1891,7 @@ void VehicleCameraControl::_streamInfoTimeout() { _videoStreamInfoRetries++; - int count = _expectedCount * 5; + int count = _expectedCount * 6; if(_videoStreamInfoRetries > count) { qCWarning(CameraControlLog) << "Giving up requesting video stream info"; _streamInfoTimer.stop(); From c83f6958940fdda5bac3d3eb5602eff8d2265818 Mon Sep 17 00:00:00 2001 From: Julian Oes Date: Thu, 17 Jul 2025 14:09:00 +1200 Subject: [PATCH 45/69] Vehicle/Camera: prevent segfaults on destruction This fixes the unittests segfaulting. --- src/Camera/QGCCameraManager.cc | 15 ++++++++++++--- src/Camera/VehicleCameraControl.cc | 8 ++++++++ src/Vehicle/Vehicle.cc | 7 ++----- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/Camera/QGCCameraManager.cc b/src/Camera/QGCCameraManager.cc index 37d22a088984..ccc032c54f76 100644 --- a/src/Camera/QGCCameraManager.cc +++ b/src/Camera/QGCCameraManager.cc @@ -64,9 +64,18 @@ QGCCameraManager::QGCCameraManager(Vehicle *vehicle) QGCCameraManager::~QGCCameraManager() { - for (QVariant cam : _cameraList) { - delete cam.value(); + // Stop all camera info request timers and clean up + for (auto* cameraInfo : _cameraInfoRequest) { + if (cameraInfo->backoffTimer) { + cameraInfo->backoffTimer->stop(); + QObject::disconnect(cameraInfo->backoffTimer, nullptr, nullptr, nullptr); + } + delete cameraInfo; } + _cameraInfoRequest.clear(); + + // Stop the main heartbeat timer + _camerasLostHeartbeatTimer.stop(); } void QGCCameraManager::registerQmlTypes() @@ -445,7 +454,7 @@ static void _handleCameraInfoRetry(QGCCameraManager::CameraStruct* cameraInfo) // Stop any existing timer and set up new one cameraInfo->backoffTimer->stop(); QObject::disconnect(cameraInfo->backoffTimer, nullptr, nullptr, nullptr); - QObject::connect(cameraInfo->backoffTimer, &QTimer::timeout, cameraInfo, [=]() { + QObject::connect(cameraInfo->backoffTimer, &QTimer::timeout, manager, [=]() { _requestCameraInfoHelper(manager, cameraInfo); }); diff --git a/src/Camera/VehicleCameraControl.cc b/src/Camera/VehicleCameraControl.cc index d839c004e273..3dcbd1f50d5f 100644 --- a/src/Camera/VehicleCameraControl.cc +++ b/src/Camera/VehicleCameraControl.cc @@ -165,6 +165,14 @@ VehicleCameraControl::VehicleCameraControl(const mavlink_camera_information_t *i //----------------------------------------------------------------------------- VehicleCameraControl::~VehicleCameraControl() { + // Stop all timers to prevent them from firing during or after destruction + _captureStatusTimer.stop(); + _recTimer.stop(); + _streamInfoTimer.stop(); + _streamStatusTimer.stop(); + _cameraSettingsTimer.stop(); + _storageInfoTimer.stop(); + delete _netManager; _netManager = nullptr; } diff --git a/src/Vehicle/Vehicle.cc b/src/Vehicle/Vehicle.cc index 2ede3a0bb891..ba6a7be316db 100644 --- a/src/Vehicle/Vehicle.cc +++ b/src/Vehicle/Vehicle.cc @@ -381,9 +381,7 @@ Vehicle::~Vehicle() void Vehicle::prepareDelete() { -#if 0 - // I believe this should no longer be needed with new PhtoVideoControl implmenentation. - // Leaving in for now, just in case it need to come back. + // Clean up camera manager to stop all timers and prevent crashes during destruction if(_cameraManager) { // because of _cameraManager QML bindings check for nullptr won't work in the binding pipeline // the dangling pointer access will cause a runtime fault @@ -391,9 +389,8 @@ void Vehicle::prepareDelete() _cameraManager = nullptr; delete tmpCameras; emit cameraManagerChanged(); - qApp->processEvents(); + // Note: Removed qApp->processEvents() to prevent MAVLink crashes during destruction } -#endif } void Vehicle::deleteCameraManager() From 15f61a16d3084b1d0bc583eab199f684b62ace1d Mon Sep 17 00:00:00 2001 From: Julian Oes Date: Sat, 2 Aug 2025 16:52:02 +1200 Subject: [PATCH 46/69] Camera discovery fixups (#13197) * VideoManager: fix crash on camera disconnect This fixes a segfault in: src/VideoManager/VideoManager.cc:551 MavlinkCameraControl *pCamera = _activeVehicle->cameraManager()->currentCameraInstance(); * FlightMap: drive-by bugfix --------- Co-authored-by: Holden Ramsey <68555040+HTRamsey@users.noreply.github.com> --- src/FlightMap/Widgets/PhotoVideoControl.qml | 2 +- src/VideoManager/VideoManager.cc | 20 +++++++++++++------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/FlightMap/Widgets/PhotoVideoControl.qml b/src/FlightMap/Widgets/PhotoVideoControl.qml index 9a5e30189b05..f7afa680eb1b 100644 --- a/src/FlightMap/Widgets/PhotoVideoControl.qml +++ b/src/FlightMap/Widgets/PhotoVideoControl.qml @@ -284,7 +284,7 @@ Rectangle { onClicked: { _camera.trackingEnabled = !_camera.trackingEnabled; if (!_camera.trackingEnabled) { - !camera.stopTracking() + _camera.stopTracking() } } } diff --git a/src/VideoManager/VideoManager.cc b/src/VideoManager/VideoManager.cc index 046c98266094..d8d888065e7e 100644 --- a/src/VideoManager/VideoManager.cc +++ b/src/VideoManager/VideoManager.cc @@ -563,17 +563,23 @@ void VideoManager::_setActiveVehicle(Vehicle *vehicle) _activeVehicle = vehicle; if (_activeVehicle) { (void) connect(_activeVehicle->vehicleLinkManager(), &VehicleLinkManager::communicationLostChanged, this, &VideoManager::_communicationLostChanged); - (void) connect(_activeVehicle->cameraManager(), &QGCCameraManager::streamChanged, this, &VideoManager::_videoSourceChanged); - MavlinkCameraControl *pCamera = _activeVehicle->cameraManager()->currentCameraInstance(); - if (pCamera) { - pCamera->resumeStream(); + if (_activeVehicle->cameraManager()) { + (void) connect(_activeVehicle->cameraManager(), &QGCCameraManager::streamChanged, this, &VideoManager::_videoSourceChanged); + MavlinkCameraControl *pCamera = _activeVehicle->cameraManager()->currentCameraInstance(); + if (pCamera) { + pCamera->resumeStream(); + } } for (VideoReceiver *receiver : std::as_const(_videoReceivers)) { - if (receiver->isThermal()) { - receiver->setVideoStreamInfo(_activeVehicle->cameraManager()->thermalStreamInstance()); + if (_activeVehicle->cameraManager()) { + if (receiver->isThermal()) { + receiver->setVideoStreamInfo(_activeVehicle->cameraManager()->thermalStreamInstance()); + } else { + receiver->setVideoStreamInfo(_activeVehicle->cameraManager()->currentStreamInstance()); + } } else { - receiver->setVideoStreamInfo(_activeVehicle->cameraManager()->currentStreamInstance()); + receiver->setVideoStreamInfo(nullptr); } // connect(receiver->videoStreamInfo(), &QGCVideoStreamInfo::infoChanged, )) } From ddb1230889c599e46b7b5e03dacc5b300885c60a Mon Sep 17 00:00:00 2001 From: Don Gagne Date: Mon, 15 Sep 2025 14:09:34 -0700 Subject: [PATCH 47/69] Update heading --- docs/.vitepress/config.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/.vitepress/config.mjs b/docs/.vitepress/config.mjs index 5e340471ee71..297759c2194d 100644 --- a/docs/.vitepress/config.mjs +++ b/docs/.vitepress/config.mjs @@ -3,7 +3,7 @@ import { defineConfig } from "vitepress"; // https://vitepress.dev/reference/site-config export default defineConfig({ - title: "QGC Guide (master)", + title: "QGC Guide (v5.0)", description: "How to use and develop QGroundControl for PX4 or ArduPilot powered vehicles.", ignoreDeadLinks: true, // Do this for stable, where we don't yet have all translations From 74a694b7584b6f047aa080af5cd84d2320dde9ed Mon Sep 17 00:00:00 2001 From: Don Gagne Date: Mon, 15 Sep 2025 16:50:26 -0700 Subject: [PATCH 48/69] Setup for automatic release notes --- .github/release.yml | 20 +++++++++++++++++++ .../qgc-dev-guide/contribute/pull_requests.md | 13 ++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 .github/release.yml diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 000000000000..56a42d76f9ba --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,20 @@ +changelog: + categories: + - title: Features + labels: + - "RN: MAJOR FEATURE" + - "RN: MINOR FEATURE" + - "RN: MAJOR FEATURE - CUSTOM BUILD" + - "RN: MINOR FEATURE - CUSTOM BUILD" + - title: Improvements + labels: + - "RN: IMPROVEMENT" + - "RN: IMPROVEMENT - CUSTOM BUILD" + - "RN: REFACTORING" + - title: Fixes + labels: + - "RN: BUGFIX" + - "RN: BUGFIX - CUSTOM BUILD" + - title: Targets + labels: + - "RN: NEW BOARD SUPPORT" \ No newline at end of file diff --git a/docs/en/qgc-dev-guide/contribute/pull_requests.md b/docs/en/qgc-dev-guide/contribute/pull_requests.md index fe120dcf7dca..fb84583b4e3f 100644 --- a/docs/en/qgc-dev-guide/contribute/pull_requests.md +++ b/docs/en/qgc-dev-guide/contribute/pull_requests.md @@ -1,3 +1,16 @@ # Pull Requests All pull requests go through the QGC CI build system which builds release and debug version. Builds will fail if there are compiler warnings. Also unit tests are run against supported OS debug builds. + +## Automatic Release Note Generation + +Releases notes are generated from the following GitHub labels qhich should be set on Pull Requests as appropriate: + +* "RN: MAJOR FEATURE" +* "RN: MINOR FEATURE" +* "RN: IMPROVEMENT" +* "RN: REFACTORING" +* "RN: BUGFIX" +* "RN: NEW BOARD SUPPORT" + +There are also a set of the above labels which end in "- CUSTOM BUILD" which indicate the changes is associated with the custom build architecture. \ No newline at end of file From 748c5e5b1bba07ba9cae5ac27afc1c60ea452d25 Mon Sep 17 00:00:00 2001 From: Don Gagne Date: Sat, 20 Sep 2025 10:29:18 -0700 Subject: [PATCH 49/69] Sign mac app bundle with real signing identity * QGC_MACOS_SIGN_WITH_IDENTITY=ON for sign/notarize/staple * Requires QGC_MACOS_SIGNING_IDENTITY, QGC_MACOS_NOTARIZATION_USERNAME, QGC_MACOS_NOTARIZATION_PASSWORD, QGC_MACOS_NOTARIZATION_TEAM_ID --- .github/workflows/macos.yml | 31 ++++---- cmake/CreateMacDMG.cmake | 6 -- cmake/Install.cmake | 20 +++++- cmake/SignMacBundle.cmake | 70 +++++++++++++++++++ .../en/qgc-dev-guide/getting_started/index.md | 7 ++ 5 files changed, 111 insertions(+), 23 deletions(-) create mode 100644 cmake/SignMacBundle.cmake diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 62bd1607f93b..66a40aedec45 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -82,40 +82,43 @@ jobs: modules: qtcharts qtlocation qtpositioning qtspeech qt5compat qtmultimedia qtserialport qtimageformats qtshadertools qtconnectivity qtquick3d qtsensors cache: true - - name: Import Code Signing Certificate - if: github.event_name != 'pull_request' - uses: apple-actions/import-codesign-certs@v5 - with: - p12-file-base64: ${{ secrets.MACOS_SIGNING_CERTS_P12 }} - p12-password: ${{ secrets.MACOS_SIGNING_CERTS_PASS }} - - - name: Configure + - name: CMake configure working-directory: ${{ runner.temp }}/shadow_build_dir run: ${{ env.QT_ROOT_DIR }}/bin/qt-cmake -S ${{ github.workspace }} -B . -G Ninja -DCMAKE_BUILD_TYPE=${{ matrix.BuildType }} -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -DQGC_STABLE_BUILD=${{ github.ref_type == 'tag' || contains(github.ref, 'Stable') && 'ON' || 'OFF' }} + -DQGC_MACOS_SIGN_WITH_IDENTITY=${{ github.event_name != 'pull_request' && 'ON' || 'OFF' }} - name: Build working-directory: ${{ runner.temp }}/shadow_build_dir run: cmake --build . --target all --config ${{ matrix.BuildType }} - - name: Sanity check dev build excecutable - if: matrix.BuildType == 'Release' + - name: Sanity check dev build executable working-directory: ${{ runner.temp }}/shadow_build_dir/Release/QGroundControl.app/Contents/MacOS run: ./QGroundControl --simple-boot-test - - name: Create DMG + - name: Import Code Signing Certificate + if: github.event_name != 'pull_request' + uses: apple-actions/import-codesign-certs@v5 + with: + p12-file-base64: ${{ secrets.MACOS_CERT_P12_BASE64 }} + p12-password: ${{ secrets.MACOS_CERT_P12_PASSWORD }} + + - name: Create signed/notarized/stapled app bundle working-directory: ${{ runner.temp }}/shadow_build_dir run: cmake --install . --config ${{ matrix.BuildType }} + env: + QGC_MACOS_SIGNING_IDENTITY: ${{ secrets.MACOS_SIGNING_IDENTITY }} + QGC_MACOS_NOTARIZATION_USERNAME: ${{ secrets.MACOS_NOTARIZATION_USERNAME }} + QGC_MACOS_NOTARIZATION_PASSWORD: ${{ secrets.MACOS_NOTARIZATION_PASSWORD }} + QGC_MACOS_NOTARIZATION_TEAM_ID: ${{ secrets.MACOS_NOTARIZATION_TEAM_ID }} - name: Mount DMG - if: matrix.BuildType == 'Release' working-directory: ${{ runner.temp }}/shadow_build_dir run: hdiutil attach QGroundControl.dmg - - name: Sanity check DMG exectuable - if: matrix.BuildType == 'Release' + - name: Sanity check DMG executable working-directory: /Volumes/QGroundControl/QGroundControl.app/Contents/MacOS run: ./QGroundControl --simple-boot-test diff --git a/cmake/CreateMacDMG.cmake b/cmake/CreateMacDMG.cmake index e195482237cf..722819d370f0 100644 --- a/cmake/CreateMacDMG.cmake +++ b/cmake/CreateMacDMG.cmake @@ -1,12 +1,6 @@ set(STAGING_BUNDLE_PATH ${CMAKE_BINARY_DIR}/staging/${TARGET_APP_NAME}.app) -message(STATUS "Signing bundle: ${STAGING_BUNDLE_PATH}") -execute_process( - COMMAND codesign --force --deep -s - "${STAGING_BUNDLE_PATH}" - COMMAND_ERROR_IS_FATAL ANY -) - file(REMOVE_RECURSE ${CMAKE_BINARY_DIR}/package) file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/package) file(COPY ${STAGING_BUNDLE_PATH} DESTINATION ${CMAKE_BINARY_DIR}/package) diff --git a/cmake/Install.cmake b/cmake/Install.cmake index b90cf9b73475..419d55366bbf 100644 --- a/cmake/Install.cmake +++ b/cmake/Install.cmake @@ -11,9 +11,8 @@ install( set(deploy_tool_options_arg "") if(MACOS OR WIN32) set(deploy_tool_options_arg "-qmldir=${CMAKE_SOURCE_DIR}") - if(MACOS_SIGNING_IDENTITY) - message(STATUS "Signing MacOS Bundle") - set(deploy_tool_options_arg "${deplay_tool_options_arg} -sign-for-notarization=${MACOS_SIGNING_IDENTITY}") + if(MACOS) + list(APPEND deploy_tool_options_arg "-appstore-compliant") endif() endif() @@ -75,6 +74,21 @@ elseif(WIN32) install(CODE "set(QGC_WINDOWS_INSTALLER_SCRIPT ${CMAKE_SOURCE_DIR}/deploy/windows/nullsoft_installer.nsi)") install(SCRIPT "${CMAKE_SOURCE_DIR}/cmake/CreateWinInstaller.cmake") elseif(MACOS) + install(CODE "set(QGC_STAGING_BUNDLE_PATH \"${CMAKE_BINARY_DIR}/staging/${CMAKE_PROJECT_NAME}.app\")") + if(QGC_MACOS_SIGN_WITH_IDENTITY) + message(STATUS "QGC: Signing Bundle using signing identity") + install(SCRIPT "${CMAKE_SOURCE_DIR}/cmake/SignMacBundle.cmake") + else() + message(STATUS "QGC: Signing Bundle using Ad-Hoc signing") + install(CODE " + message(STATUS \"QGC: Signing Bundle using Ad-Hoc signing\") + execute_process( + COMMAND codesign --force --deep -s - \"\${QGC_STAGING_BUNDLE_PATH}\" + COMMAND_ERROR_IS_FATAL ANY + ) + ") + endif() + install(CODE "set(TARGET_APP_NAME ${QGC_APP_NAME})") find_program(CREATE_DMG_PROGRAM create-dmg) if(NOT CREATE_DMG_PROGRAM) diff --git a/cmake/SignMacBundle.cmake b/cmake/SignMacBundle.cmake new file mode 100644 index 000000000000..d9720d6ab1d7 --- /dev/null +++ b/cmake/SignMacBundle.cmake @@ -0,0 +1,70 @@ +message(STATUS "QGC: Signing Bundle using signing identity") +if(NOT DEFINED ENV{QGC_MACOS_SIGNING_IDENTITY} OR "$ENV{QGC_MACOS_SIGNING_IDENTITY}" STREQUAL "") + message(FATAL_ERROR "QGC: QGC_MACOS_SIGNING_IDENTITY environment variable must be set to sign MacOS bundle") +endif() +if(NOT DEFINED ENV{QGC_MACOS_NOTARIZATION_USERNAME} OR "$ENV{QGC_MACOS_NOTARIZATION_USERNAME}" STREQUAL "") + message(FATAL_ERROR "QGC: QGC_MACOS_NOTARIZATION_USERNAME environment variable must be set to notarize MacOS bundle") +endif() +if(NOT DEFINED ENV{QGC_MACOS_NOTARIZATION_TEAM_ID} OR "$ENV{QGC_MACOS_NOTARIZATION_TEAM_ID}" STREQUAL "") + message(FATAL_ERROR "QGC: QGC_MACOS_NOTARIZATION_TEAM_ID environment variable must be set to notarize MacOS bundle") +endif() +if(NOT DEFINED ENV{QGC_MACOS_NOTARIZATION_PASSWORD} OR "$ENV{QGC_MACOS_NOTARIZATION_PASSWORD}" STREQUAL "") + message(FATAL_ERROR "QGC: QGC_MACOS_NOTARIZATION_PASSWORD environment variable must be set to notarize MacOS bundle") +endif() +file(REMOVE ${QGC_STAGING_BUNDLE_PATH}/Contents/Frameworks/GStreamer.framework/Commands) +file(REMOVE ${QGC_STAGING_BUNDLE_PATH}/Contents/Frameworks/GStreamer.framework/Versions/1.0/Commands) +execute_process( + COMMAND find ${QGC_STAGING_BUNDLE_PATH}/Contents -type f -name "*.dylib" -exec codesign --timestamp --options=runtime --force -s "$ENV{QGC_MACOS_SIGNING_IDENTITY}" "{}" \\; + COMMAND_ERROR_IS_FATAL ANY +) +execute_process( + COMMAND find ${QGC_STAGING_BUNDLE_PATH}/Contents -type f -name "*.so" -exec codesign --timestamp --options=runtime --force -s "$ENV{QGC_MACOS_SIGNING_IDENTITY}" "{}" \\; + COMMAND_ERROR_IS_FATAL ANY +) +execute_process( + COMMAND find ${QGC_STAGING_BUNDLE_PATH}/Contents/Frameworks/GStreamer.framework/Versions/1.0/libexec/gstreamer-1.0 -type f -name "*" -exec codesign --timestamp --options=runtime --force -s "$ENV{QGC_MACOS_SIGNING_IDENTITY}" "{}" \\; + COMMAND_ERROR_IS_FATAL ANY +) +execute_process( + COMMAND codesign --timestamp --options=runtime --force -s "$ENV{QGC_MACOS_SIGNING_IDENTITY}" ${QGC_STAGING_BUNDLE_PATH}/Contents/Frameworks/GStreamer.framework/Versions/1.0/lib/GStreamer + COMMAND_ERROR_IS_FATAL ANY +) +execute_process( + COMMAND codesign --timestamp --options=runtime --force -s "$ENV{QGC_MACOS_SIGNING_IDENTITY}" ${QGC_STAGING_BUNDLE_PATH}/Contents/Frameworks/GStreamer.framework/Versions/1.0/GStreamer + COMMAND_ERROR_IS_FATAL ANY +) +file(GLOB FRAMEWORK_DIRS ${QGC_STAGING_BUNDLE_PATH}/Contents/Frameworks/*.framework) +foreach(FRAMEWORK_DIR ${FRAMEWORK_DIRS}) + if (EXISTS "${FRAMEWORK_DIR}/Versions/1.0") + execute_process( + COMMAND find ${FRAMEWORK_DIR}/Versions/1.0 -type f -exec codesign --timestamp --options=runtime --force -s "$ENV{QGC_MACOS_SIGNING_IDENTITY}" "{}" \\; + COMMAND_ERROR_IS_FATAL ANY + ) + endif() + if (EXISTS "${FRAMEWORK_DIR}/Versions/A") + execute_process( + COMMAND find ${FRAMEWORK_DIR}/Versions/A -type f -exec codesign --timestamp --options=runtime --force -s "$ENV{QGC_MACOS_SIGNING_IDENTITY}" "{}" \\; + COMMAND_ERROR_IS_FATAL ANY + ) + endif() +endforeach() +execute_process( + COMMAND codesign --timestamp --options=runtime --force -s "$ENV{QGC_MACOS_SIGNING_IDENTITY}" ${QGC_STAGING_BUNDLE_PATH} + COMMAND_ERROR_IS_FATAL ANY +) +message(STATUS "QGC: Archiving Bundle for Notarization upload") +file(REMOVE qgc_notarization_upload.zip) +execute_process( + COMMAND ditto -c -k --keepParent ${QGC_STAGING_BUNDLE_PATH} qgc_notarization_upload.zip + COMMAND_ERROR_IS_FATAL ANY +) +message(STATUS "QGC: Notarizing app bundle. This may take a while...") +execute_process( + COMMAND xcrun notarytool submit qgc_notarization_upload.zip --apple-id "$ENV{QGC_MACOS_NOTARIZATION_USERNAME}" --team-id "$ENV{QGC_MACOS_NOTARIZATION_TEAM_ID}" --password "$ENV{QGC_MACOS_NOTARIZATION_PASSWORD}" --wait + COMMAND_ERROR_IS_FATAL ANY +) +message(STATUS "QGC: Stapling notarization ticket to app bundle") +execute_process( + COMMAND xcrun stapler staple ${QGC_STAGING_BUNDLE_PATH} + COMMAND_ERROR_IS_FATAL ANY +) diff --git a/docs/en/qgc-dev-guide/getting_started/index.md b/docs/en/qgc-dev-guide/getting_started/index.md index 70b6cbb89e33..6a37d53f851f 100644 --- a/docs/en/qgc-dev-guide/getting_started/index.md +++ b/docs/en/qgc-dev-guide/getting_started/index.md @@ -141,6 +141,13 @@ Example commands to build a default QGC and run it afterwards: ``` Change the directory for qt-cmake to match your install location for Qt and the kit you want to use. + + **Mac**: To Sign/Notarize/Staple the QGC app bundle, add `-DQGC_MACOS_SIGN_WITH_IDENTITY=ON` to the configure command line. During the `install` phase the following environment variables will need to be available: + + * `QGC_MACOS_SIGNING_IDENTITY` - Signing identity for your Developer ID certificate which must be in the keychain + * `QGC_MACOS_NOTARIZATION_USERNAME` - Username for your Apple Developer Account + * `QGC_MACOS_NOTARIZATION_PASSWORD` - App specific password for Notarization from your Apple Developer Account + * `QGC_MACOS_NOTARIZATION_TEAM_ID` - Apple Developer Account Team ID 1. Build From b8a8dadd1bc6f6e78e02c787c81934ba708a374c Mon Sep 17 00:00:00 2001 From: Don Gagne Date: Sun, 21 Sep 2025 09:24:40 -0700 Subject: [PATCH 50/69] Install nsis into github windows runner Used to be included automatically in runner but it was removed --- .github/workflows/windows.yml | 4 ++++ cmake/CreateWinInstaller.cmake | 1 + 2 files changed, 5 insertions(+) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 1276a1c7ac5b..b6bd9a997c27 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -69,6 +69,10 @@ jobs: with: version: ${{ env.GST_VERSION }} + - name: Install NSIS + run: choco install nsis -y + shell: powershell + - name: Setup Caching uses: ./.github/actions/cache with: diff --git a/cmake/CreateWinInstaller.cmake b/cmake/CreateWinInstaller.cmake index e568f616b507..fb6be7a62908 100644 --- a/cmake/CreateWinInstaller.cmake +++ b/cmake/CreateWinInstaller.cmake @@ -5,6 +5,7 @@ set(_PF86 "PROGRAMFILES(x86)") find_program(QGC_NSIS_INSTALLER_CMD makensis PATHS "$ENV{PROGRAMFILES}/NSIS" "$ENV{${_PF86}}/NSIS" "$ENV{PROGRAMW6432}/NSIS" DOC "Path to the makensis utility." + REQUIRED ) file(TO_NATIVE_PATH "${QGC_WINDOWS_ICON_PATH}" QGC_INSTALLER_ICON) From 839309b22644de26afbbdf2436ce97c2a92f4f04 Mon Sep 17 00:00:00 2001 From: Don Gagne Date: Mon, 22 Sep 2025 11:48:03 -0700 Subject: [PATCH 51/69] Fix crash on double signaling of allLinksRemoved --- src/Vehicle/MultiVehicleManager.cc | 1 + src/Vehicle/VehicleLinkManager.cc | 3 ++- src/Vehicle/VehicleLinkManager.h | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Vehicle/MultiVehicleManager.cc b/src/Vehicle/MultiVehicleManager.cc index 4048bd10d38c..4e5d6e4aa405 100644 --- a/src/Vehicle/MultiVehicleManager.cc +++ b/src/Vehicle/MultiVehicleManager.cc @@ -208,6 +208,7 @@ void MultiVehicleManager::_deleteVehiclePhase1(Vehicle *vehicle) if (!found) { qCWarning(MultiVehicleManagerLog) << "Vehicle not found in map!"; + return; } deselectVehicle(vehicle->id()); diff --git a/src/Vehicle/VehicleLinkManager.cc b/src/Vehicle/VehicleLinkManager.cc index 903fea46d773..4670e751d19b 100644 --- a/src/Vehicle/VehicleLinkManager.cc +++ b/src/Vehicle/VehicleLinkManager.cc @@ -252,7 +252,7 @@ void VehicleLinkManager::_linkDisconnected() _removeLink(link); _updatePrimaryLink(); - if (_rgLinkInfo.isEmpty()) { + if (_rgLinkInfo.isEmpty() && !_allLinksRemovedSignalledByCloseVehicle) { qCDebug(VehicleLog) << "All links removed. Closing down Vehicle."; emit allLinksRemoved(_vehicle); } @@ -364,6 +364,7 @@ void VehicleLinkManager::closeVehicle() _rgLinkInfo.clear(); + _allLinksRemovedSignalledByCloseVehicle = true; // Prevent double signal of allLinksRemoved emit allLinksRemoved(_vehicle); } diff --git a/src/Vehicle/VehicleLinkManager.h b/src/Vehicle/VehicleLinkManager.h index 700940bb85c3..d123f4d9802b 100644 --- a/src/Vehicle/VehicleLinkManager.h +++ b/src/Vehicle/VehicleLinkManager.h @@ -86,6 +86,7 @@ private slots: bool _communicationLost = false; bool _communicationLostEnabled = true; bool _autoDisconnect = false; ///< true: Automatically disconnect vehicle when last connection goes away or lost heartbeat + bool _allLinksRemovedSignalledByCloseVehicle = false; static constexpr int _commLostCheckTimeoutMSecs = 1000; ///< Check for comm lost once a second static constexpr int _heartbeatMaxElpasedMSecs = 3500; ///< No heartbeat for longer than this indicates comm loss From 924d676d6b7127d4b6ace13288ad95abe0e1c81f Mon Sep 17 00:00:00 2001 From: Don Gagne Date: Tue, 23 Sep 2025 12:02:18 -0700 Subject: [PATCH 52/69] Add missing platform plugins (#13426) --- CMakeLists.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index c80bbe835da9..db0f01ae8d4b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -560,6 +560,16 @@ qt_import_plugins(${CMAKE_PROJECT_NAME} # INCLUDE_BY_TYPE styles Qt6::qtquickcontrols2basicstyleplugin Qt6::qtquickcontrols2basicstyleimplplugin ) +if(LINUX) + qt_import_plugins(${CMAKE_PROJECT_NAME} + INCLUDE + Qt6::QWaylandIntegrationPlugin + Qt6::QXcbIntegrationPlugin + Qt6::QEglFSIntegrationPlugin + Qt6::QWaylandEglPlatformIntegrationPlugin + ) +endif() + include(Install) include(PrintSummary) From e0648e409663f7c1495177363f00092077bade5c Mon Sep 17 00:00:00 2001 From: Holden Date: Sun, 20 Jul 2025 14:56:04 -0400 Subject: [PATCH 53/69] Vehicle: Minor Telemetry Fixes --- src/Vehicle/FactGroups/VehicleDistanceSensorFactGroup.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Vehicle/FactGroups/VehicleDistanceSensorFactGroup.cc b/src/Vehicle/FactGroups/VehicleDistanceSensorFactGroup.cc index 9791f3089fbd..5954ea3529a0 100644 --- a/src/Vehicle/FactGroups/VehicleDistanceSensorFactGroup.cc +++ b/src/Vehicle/FactGroups/VehicleDistanceSensorFactGroup.cc @@ -63,6 +63,7 @@ void VehicleDistanceSensorFactGroup::handleMessage(Vehicle *vehicle, const mavli } } + minDistance()->setRawValue(distanceSensor.min_distance / 100.0); maxDistance()->setRawValue(distanceSensor.max_distance / 100.0); _setTelemetryAvailable(true); From 292c145467b1dc7fa88f972ba6fbcf5bffa2cac2 Mon Sep 17 00:00:00 2001 From: Don Gagne Date: Tue, 30 Sep 2025 12:15:08 -0700 Subject: [PATCH 54/69] Check for null usbManager to prevent debug spew from availableDevicesInfo --- .../org/mavlink/qgroundcontrol/QGCUsbSerialManager.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/android/src/org/mavlink/qgroundcontrol/QGCUsbSerialManager.java b/android/src/org/mavlink/qgroundcontrol/QGCUsbSerialManager.java index 76e041fd43cc..fb2011b5154d 100644 --- a/android/src/org/mavlink/qgroundcontrol/QGCUsbSerialManager.java +++ b/android/src/org/mavlink/qgroundcontrol/QGCUsbSerialManager.java @@ -60,6 +60,10 @@ public static void initialize(Context context) { } usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE); + if (usbManager == null) { + QGCLogger.e(TAG, "Failed to get UsbManager"); + return; + } setupUsbPermissionIntent(context); registerUsbReceiver(context); usbSerialProber = UsbSerialProber.getDefaultProber(); @@ -413,6 +417,10 @@ private static UsbSerialPort findPortByDeviceId(final int deviceId) { public static String[] availableDevicesInfo() { // updateCurrentDrivers(); + if (usbManager == null) { + return null; + } + if (usbManager.getDeviceList().size() < 1) { return null; } From ca8245ec4482356999d334a5325688576d28338b Mon Sep 17 00:00:00 2001 From: Don Gagne Date: Wed, 1 Oct 2025 11:13:24 -0700 Subject: [PATCH 55/69] Remote ID: Close toolbar dropdown when going to Configure page --- src/QmlControls/RemoteIDIndicatorPage.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/QmlControls/RemoteIDIndicatorPage.qml b/src/QmlControls/RemoteIDIndicatorPage.qml index c13d224d5f4f..134e3b75c906 100644 --- a/src/QmlControls/RemoteIDIndicatorPage.qml +++ b/src/QmlControls/RemoteIDIndicatorPage.qml @@ -58,6 +58,7 @@ ToolIndicatorPage { function goToSettings() { if (mainWindow.allowViewSwitch()) { + mainWindow.closeIndicatorDrawer() globals.commingFromRIDIndicator = true mainWindow.showSettingsTool() } From ac3d6844cfa350e19551a4a786ca584982b088c3 Mon Sep 17 00:00:00 2001 From: Don Gagne Date: Thu, 2 Oct 2025 12:28:28 -0700 Subject: [PATCH 56/69] Fix Android SD Card permissions for API 30+ (Android 11+) (#13456) * Initial plan * Fix Android SD card permissions for API 30+ - Add MANAGE_EXTERNAL_STORAGE permission to manifest for Android 11+ full SD card access - Replace deprecated QtAndroidPrivate permission APIs with Java-based implementation - Add proper permission handling for Android 11+ (API 30) and Android 6+ (API 23) - Fix permission check to open settings for MANAGE_EXTERNAL_STORAGE grant on Android 11+ Co-authored-by: HTRamsey <68555040+HTRamsey@users.noreply.github.com> * Fix QJniObject::callStaticMethod usage for boolean return Co-authored-by: HTRamsey <68555040+HTRamsey@users.noreply.github.com> * Fix SD card path detection for Android 11+ Use StorageVolume.getDirectory() API for Android 11+ instead of deprecated reflection-based getPath() method. This fixes the "/dev/null" path issue where the reflection method returns invalid paths on newer Android versions. Co-authored-by: HTRamsey <68555040+HTRamsey@users.noreply.github.com> * Fix Android Activity Name --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: HTRamsey <68555040+HTRamsey@users.noreply.github.com> Co-authored-by: Holden --- android/AndroidManifest.xml | 2 + .../mavlink/qgroundcontrol/QGCActivity.java | 111 +++++++++++++++--- src/Android/AndroidInterface.cc | 30 ++--- 3 files changed, 110 insertions(+), 33 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index d7742b82d11e..e2a840e9ed3e 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -21,6 +21,8 @@ + + diff --git a/android/src/org/mavlink/qgroundcontrol/QGCActivity.java b/android/src/org/mavlink/qgroundcontrol/QGCActivity.java index c40ed7273987..bf6c39597dd9 100644 --- a/android/src/org/mavlink/qgroundcontrol/QGCActivity.java +++ b/android/src/org/mavlink/qgroundcontrol/QGCActivity.java @@ -1,17 +1,26 @@ package org.mavlink.qgroundcontrol; +import java.io.File; import java.util.List; import java.lang.reflect.Method; import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Build; import android.os.Bundle; +import android.os.Environment; import android.os.PowerManager; import android.net.wifi.WifiManager; +import android.provider.Settings; import android.util.Log; import android.view.WindowManager; import android.app.Activity; import android.os.storage.StorageManager; import android.os.storage.StorageVolume; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; import org.qtproject.qt.android.bindings.QtActivity; @@ -119,32 +128,102 @@ private void releaseMulticastLock() { public static String getSDCardPath() { StorageManager storageManager = (StorageManager)m_instance.getSystemService(Activity.STORAGE_SERVICE); List volumes = storageManager.getStorageVolumes(); - Method mMethodGetPath; - String path = ""; + for (StorageVolume vol : volumes) { - try { - mMethodGetPath = vol.getClass().getMethod("getPath"); - } catch (NoSuchMethodException e) { - e.printStackTrace(); + if (!vol.isRemovable()) { continue; } - try { - path = (String) mMethodGetPath.invoke(vol); - } catch (Exception e) { - e.printStackTrace(); - continue; + + String path = null; + + // For Android 11+ (API 30+), use the proper getDirectory() method + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + File directory = vol.getDirectory(); + if (directory != null) { + path = directory.getAbsolutePath(); + } + } else { + // For older versions, use reflection to get the path + try { + Method mMethodGetPath = vol.getClass().getMethod("getPath"); + path = (String) mMethodGetPath.invoke(vol); + } catch (Exception e) { + Log.e(TAG, "Failed to get path via reflection", e); + continue; + } } - - if (vol.isRemovable() == true) { - Log.i(TAG, "removable sd card mounted " + path); + + if (path != null && !path.isEmpty()) { + Log.i(TAG, "removable sd card mounted at " + path); return path; - } else { - Log.i(TAG, "storage mounted " + path); } } + + Log.w(TAG, "No removable SD card found"); return ""; } + /** + * Checks and requests storage permissions for SD card access. + * For Android 11+ (API 30+), this requires MANAGE_EXTERNAL_STORAGE permission. + * + * @return true if permissions are granted, false otherwise + */ + public static boolean checkStoragePermissions() { + if (m_instance == null) { + Log.e(TAG, "Activity instance is null"); + return false; + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + // Android 11+ (API 30+) requires MANAGE_EXTERNAL_STORAGE for full SD card access + if (!Environment.isExternalStorageManager()) { + Log.i(TAG, "MANAGE_EXTERNAL_STORAGE not granted, requesting..."); + try { + Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION); + intent.setData(Uri.parse("package:" + m_instance.getPackageName())); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + m_instance.startActivity(intent); + } catch (Exception e) { + Log.e(TAG, "Failed to open storage permission settings", e); + // Fallback to general settings + Intent intent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + m_instance.startActivity(intent); + } + return false; + } + Log.i(TAG, "MANAGE_EXTERNAL_STORAGE already granted"); + return true; + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + // Android 6.0+ (API 23+) requires runtime permissions + String[] permissions = { + android.Manifest.permission.READ_EXTERNAL_STORAGE, + android.Manifest.permission.WRITE_EXTERNAL_STORAGE + }; + + boolean allGranted = true; + for (String permission : permissions) { + if (ContextCompat.checkSelfPermission(m_instance, permission) != PackageManager.PERMISSION_GRANTED) { + allGranted = false; + break; + } + } + + if (!allGranted) { + Log.i(TAG, "Storage permissions not granted, requesting..."); + ActivityCompat.requestPermissions(m_instance, permissions, 1); + return false; + } + + Log.i(TAG, "Storage permissions already granted"); + return true; + } else { + // Below Android 6.0, permissions are granted at install time + return true; + } + } + // Native C++ functions public native boolean nativeInit(); public native void qgcLogDebug(final String message); diff --git a/src/Android/AndroidInterface.cc b/src/Android/AndroidInterface.cc index e63b95f2d2a7..05e072471680 100644 --- a/src/Android/AndroidInterface.cc +++ b/src/Android/AndroidInterface.cc @@ -12,7 +12,6 @@ #include #include -#include QGC_LOGGING_CATEGORY(AndroidInterfaceLog, "qgc.android.src.androidinterface") @@ -107,23 +106,20 @@ void jniLogWarning(JNIEnv *envA, jobject thizA, jstring messageA) bool checkStoragePermissions() { - const QString readPermission("android.permission.READ_EXTERNAL_STORAGE"); - const QString writePermission("android.permission.WRITE_EXTERNAL_STORAGE"); - - const QStringList permissions = { readPermission, writePermission }; - for (const auto& permission: permissions) { - QFuture futurePermissionResult = QtAndroidPrivate::checkPermission(permission); - QtAndroidPrivate::PermissionResult permissionResult = futurePermissionResult.result(); - if (permissionResult == QtAndroidPrivate::PermissionResult::Denied) { - futurePermissionResult = QtAndroidPrivate::requestPermission(permission); - permissionResult = futurePermissionResult.result(); - if (permissionResult == QtAndroidPrivate::PermissionResult::Denied) { - return false; - } - } + // Call the Java method to check and request storage permissions + const bool hasPermission = QJniObject::callStaticMethod( + kJniQGCActivityClassName, + "checkStoragePermissions", + "()Z" + ); + + if (hasPermission) { + qCDebug(AndroidInterfaceLog) << "Storage permissions granted"; + } else { + qCWarning(AndroidInterfaceLog) << "Storage permissions not granted"; } - - return true; + + return hasPermission; } QString getSDCardPath() From 780dcad4de6c0b80c9a8c671061b261a999d9b6c Mon Sep 17 00:00:00 2001 From: Holden Date: Wed, 1 Oct 2025 14:19:13 -0400 Subject: [PATCH 57/69] CI: Free Up Disk Space for Android --- .github/workflows/android-linux.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/android-linux.yml b/.github/workflows/android-linux.yml index bb065eb20791..6b7b44d7777e 100644 --- a/.github/workflows/android-linux.yml +++ b/.github/workflows/android-linux.yml @@ -61,6 +61,14 @@ jobs: - name: Initial Setup uses: ./.github/actions/common + - name: Free Disk Space + if: runner.os == 'Linux' + uses: jlumbroso/free-disk-space@main + with: + tool-cache: false + android: false + large-packages: false + - name: Install Qt for Android uses: ./.github/actions/qt-android with: From 4948c3cc0662288f17903ee81c3790d16032f808 Mon Sep 17 00:00:00 2001 From: Don Gagne Date: Wed, 8 Oct 2025 09:58:20 -0700 Subject: [PATCH 58/69] Fix daily build windows download links --- docs/en/qgc-user-guide/releases/daily_builds.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/en/qgc-user-guide/releases/daily_builds.md b/docs/en/qgc-user-guide/releases/daily_builds.md index b107a52a1c24..d1226b0a5696 100644 --- a/docs/en/qgc-user-guide/releases/daily_builds.md +++ b/docs/en/qgc-user-guide/releases/daily_builds.md @@ -9,7 +9,9 @@ Use at your own risk! These can be downloaded from the links below (install as described in [Download and Install](../getting_started/download_and_install.md)): -- [Windows](https://d176tv9ibo4jno.cloudfront.net/builds/master/QGroundControl-installer.exe) +- Windows + - [x86_64](https://d176tv9ibo4jno.cloudfront.net/builds/master/QGroundControl-installer-AMD64.exe) + - [Arm_64](https://d176tv9ibo4jno.cloudfront.net/builds/master/QGroundControl-installer-ARM64.exe) - [OS X](https://d176tv9ibo4jno.cloudfront.net/builds/master/QGroundControl.dmg) - [Linux](https://d176tv9ibo4jno.cloudfront.net/builds/master/QGroundControl-x86_64.AppImage) - Before running do the following: - `chmod +x QGroundControl-x86_64.AppImage` From d40ce1e32f7256b88a0f6596b36ec0ab2c980b21 Mon Sep 17 00:00:00 2001 From: Don Gagne Date: Wed, 8 Oct 2025 11:46:49 -0700 Subject: [PATCH 59/69] Fix crash on Vehicle shutdown caused by null CameraManager --- src/Vehicle/VehicleLinkManager.cc | 2 +- src/VideoManager/VideoManager.cc | 13 +++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Vehicle/VehicleLinkManager.cc b/src/Vehicle/VehicleLinkManager.cc index 4670e751d19b..7d72753c4251 100644 --- a/src/Vehicle/VehicleLinkManager.cc +++ b/src/Vehicle/VehicleLinkManager.cc @@ -253,7 +253,7 @@ void VehicleLinkManager::_linkDisconnected() _removeLink(link); _updatePrimaryLink(); if (_rgLinkInfo.isEmpty() && !_allLinksRemovedSignalledByCloseVehicle) { - qCDebug(VehicleLog) << "All links removed. Closing down Vehicle."; + qCDebug(VehicleLog) << "signalling allLinksRemoved"; emit allLinksRemoved(_vehicle); } } diff --git a/src/VideoManager/VideoManager.cc b/src/VideoManager/VideoManager.cc index d8d888065e7e..50f91a3bf55e 100644 --- a/src/VideoManager/VideoManager.cc +++ b/src/VideoManager/VideoManager.cc @@ -546,13 +546,18 @@ bool VideoManager::_updateSettings(VideoReceiver *receiver) void VideoManager::_setActiveVehicle(Vehicle *vehicle) { + qCDebug(VideoManagerLog) << Q_FUNC_INFO << "new vehicle" << vehicle << "old active vehicle" << _activeVehicle; + if (_activeVehicle) { (void) disconnect(_activeVehicle->vehicleLinkManager(), &VehicleLinkManager::communicationLostChanged, this, &VideoManager::_communicationLostChanged); - MavlinkCameraControl *pCamera = _activeVehicle->cameraManager()->currentCameraInstance(); - if (pCamera) { - pCamera->stopStream(); + auto cameraManager = _activeVehicle->cameraManager(); + if (cameraManager) { + MavlinkCameraControl *pCamera = cameraManager->currentCameraInstance(); + if (pCamera) { + pCamera->stopStream(); + } + (void) disconnect(cameraManager, &QGCCameraManager::streamChanged, this, &VideoManager::_videoSourceChanged); } - (void) disconnect(_activeVehicle->cameraManager(), &QGCCameraManager::streamChanged, this, &VideoManager::_videoSourceChanged); for (VideoReceiver *receiver : std::as_const(_videoReceivers)) { // disconnect(receiver->videoStreamInfo(), &QGCVideoStreamInfo::infoChanged, )) From e0816c957602789200ae5ba0af45217f0f2f1db4 Mon Sep 17 00:00:00 2001 From: Don Gagne Date: Wed, 8 Oct 2025 14:42:18 -0700 Subject: [PATCH 60/69] Modify code to build on Qt 6.9+ --- src/Vehicle/VehicleSetup/Bootloader.cc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Vehicle/VehicleSetup/Bootloader.cc b/src/Vehicle/VehicleSetup/Bootloader.cc index 71f5face9214..2fdd87caf0ff 100644 --- a/src/Vehicle/VehicleSetup/Bootloader.cc +++ b/src/Vehicle/VehicleSetup/Bootloader.cc @@ -651,7 +651,10 @@ bool Bootloader::_ihxVerifyBytes(const FirmwareImage* image) for (int i=0; i(static_cast(imageBytes[bytesIndex + i])), 2, 16, QLatin1Char('0')) + .arg(static_cast(static_cast(readBuf[i])), 2, 16, QLatin1Char('0')) + .arg(static_cast(readAddress + i), 8, 16, QLatin1Char('0')); return false; } } From beed0a4934abd9dd352a9ba622b79d1a24db38ff Mon Sep 17 00:00:00 2001 From: Ilya Mukhamadeev Date: Fri, 27 Feb 2026 11:29:35 +0300 Subject: [PATCH 61/69] Add qml plugin's interfaces --- fix-qml-plugins.cmake | 123 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 fix-qml-plugins.cmake diff --git a/fix-qml-plugins.cmake b/fix-qml-plugins.cmake new file mode 100644 index 000000000000..c4bf684b9d28 --- /dev/null +++ b/fix-qml-plugins.cmake @@ -0,0 +1,123 @@ +function(create_qml_plugin_targets) + set(QML_PLUGINS + qtquick2plugin + qmlplugin + modelsplugin + workscriptplugin + qtquickcontrols2plugin + qtquickcontrols2fusionstyleplugin + qtquickcontrols2materialstyleplugin + qtquickcontrols2imaginestyleplugin + qtquickcontrols2universalstyleplugin + qtquickcontrols2fluentwinui3styleplugin + qtquickcontrols2basicstyleplugin + qtquicktemplates2plugin + qtquickcontrols2implplugin + qtquickcontrols2fusionstyleimplplugin + quickwindowplugin + qtquickcontrols2materialstyleimplplugin + qtquickcontrols2imaginestyleimplplugin + qtquickcontrols2universalstyleimplplugin + qtquickcontrols2fluentwinui3styleimplplugin + effectsplugin + qquicklayoutsplugin + qmlshapesplugin + qtquickcontrols2basicstyleimplplugin + labsmodelsplugin + qtquickdialogsplugin + labsplatformplugin + qtchartsqml2plugin + declarative_locationplugin + positioningquickplugin + labsanimationplugin + qtgraphicaleffectsplugin + qtqmlcoreplugin + qtquickdialogs2quickimplplugin + qtgraphicaleffectsprivateplugin + quickmultimediaplugin + qmlfolderlistmodelplugin + qquick3dplugin + workerscriptplugin + ) + + foreach(plugin ${QML_PLUGINS}) + if(NOT TARGET Qt6::${plugin}) + add_library(Qt6::${plugin} INTERFACE IMPORTED) + set_target_properties(Qt6::${plugin} PROPERTIES + INTERFACE_QT_QML_PLUGIN_TYPE "${plugin}" + ) + message(STATUS "Created interface target: Qt6::${plugin}") + endif() + endforeach() +endfunction() + +function(create_special_qml_targets) + if(NOT TARGET Qt6::quickwindow) + add_library(Qt6::quickwindow INTERFACE IMPORTED) + set_target_properties(Qt6::quickwindow PROPERTIES + INTERFACE_QT_QML_PLUGIN_TYPE "quickwindow" + ) + endif() + + if(NOT TARGET Qt6::LabsPlatformplugin) + add_library(Qt6::LabsPlatformplugin INTERFACE IMPORTED) + set_target_properties(Qt6::LabsPlatformplugin PROPERTIES + INTERFACE_QT_QML_PLUGIN_TYPE "labsplatform" + ) + endif() + + if(NOT TARGET Qt6::qtchartsqml2) + add_library(Qt6::qtchartsqml2 INTERFACE IMPORTED) + set_target_properties(Qt6::qtchartsqml2 PROPERTIES + INTERFACE_QT_QML_PLUGIN_TYPE "qtchartsqml2" + ) + endif() + + if(NOT TARGET Qt6::declarative_location) + add_library(Qt6::declarative_location INTERFACE IMPORTED) + set_target_properties(Qt6::declarative_location PROPERTIES + INTERFACE_QT_QML_PLUGIN_TYPE "declarative_location" + ) + endif() + + if(NOT TARGET Qt6::qtgraphicaleffectsprivate) + add_library(Qt6::qtgraphicaleffectsprivate INTERFACE IMPORTED) + set_target_properties(Qt6::qtgraphicaleffectsprivate PROPERTIES + INTERFACE_QT_QML_PLUGIN_TYPE "qtgraphicaleffectsprivate" + ) + endif() + + if(NOT TARGET Qt6::quickmultimedia) + add_library(Qt6::quickmultimedia INTERFACE IMPORTED) + set_target_properties(Qt6::quickmultimedia PROPERTIES + INTERFACE_QT_QML_PLUGIN_TYPE "quickmultimedia" + ) + endif() +endfunction() + +function(link_all_qml_plugins target_name) + if(NOT TARGET ${target_name}) + message(WARNING "Target ${target_name} not found") + return() + endif() + + set(ESSENTIAL_PLUGINS + Qt6::qtqmlcoreplugin + Qt6::qtquick2plugin + Qt6::qmlplugin + Qt6::qtquickcontrols2plugin + Qt6::qtquicktemplates2plugin + Qt6::qtgraphicaleffectsplugin + Qt6::qquicklayoutsplugin + ) + + target_link_libraries(${target_name} + PRIVATE + ${ESSENTIAL_PLUGINS} + ) + + message(STATUS "Linked QML plugins to target: ${target_name}") +endfunction() + +create_qml_plugin_targets() +create_special_qml_targets() From c9d78c715c85418beddc7fe7e49e90a6d9f811c2 Mon Sep 17 00:00:00 2001 From: Ilya Mukhamadeev Date: Fri, 27 Feb 2026 11:33:31 +0300 Subject: [PATCH 62/69] Add info without git --- cmake/Git.cmake | 142 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 98 insertions(+), 44 deletions(-) diff --git a/cmake/Git.cmake b/cmake/Git.cmake index 2372fbf3b109..89dd307cb340 100644 --- a/cmake/Git.cmake +++ b/cmake/Git.cmake @@ -1,5 +1,12 @@ find_package(Git) +# Initialize default values +set(QGC_GIT_BRANCH "unknown") +set(QGC_GIT_HASH "unknown") +set(QGC_APP_VERSION_STR "v5.0.8") +set(QGC_APP_VERSION "5.0.8") +set(QGC_APP_DATE "unknown") + if(GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git") option(GIT_SUBMODULE "Check submodules during build" OFF) if(GIT_SUBMODULE) @@ -17,58 +24,105 @@ if(GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git") message(FATAL_ERROR "git submodule update --init failed with ${GIT_SUBMODULE_RESULT}, please checkout submodules") endif() endif() -endif() -include(CMakePrintHelpers) + include(CMakePrintHelpers) -execute_process( - COMMAND ${GIT_EXECUTABLE} rev-parse --abbrev-ref @ - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - OUTPUT_VARIABLE QGC_GIT_BRANCH - OUTPUT_STRIP_TRAILING_WHITESPACE -) -# cmake_print_variables(QGC_GIT_BRANCH) + # Get git branch with error handling + execute_process( + COMMAND ${GIT_EXECUTABLE} rev-parse --abbrev-ref HEAD + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + RESULT_VARIABLE GIT_BRANCH_RESULT + OUTPUT_VARIABLE QGC_GIT_BRANCH + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + if(NOT GIT_BRANCH_RESULT EQUAL 0) + set(QGC_GIT_BRANCH "unknown") + endif() -execute_process( - COMMAND ${GIT_EXECUTABLE} rev-parse --short @ - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - OUTPUT_VARIABLE QGC_GIT_HASH - OUTPUT_STRIP_TRAILING_WHITESPACE -) -# cmake_print_variables(QGC_GIT_HASH) + # Get git hash with error handling + execute_process( + COMMAND ${GIT_EXECUTABLE} rev-parse --short HEAD + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + RESULT_VARIABLE GIT_HASH_RESULT + OUTPUT_VARIABLE QGC_GIT_HASH + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + if(NOT GIT_HASH_RESULT EQUAL 0) + set(QGC_GIT_HASH "unknown") + endif() -execute_process( - COMMAND ${GIT_EXECUTABLE} describe --always --tags - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - OUTPUT_VARIABLE QGC_APP_VERSION_STR - OUTPUT_STRIP_TRAILING_WHITESPACE -) -# cmake_print_variables(QGC_APP_VERSION_STR) + # Get version string with error handling + execute_process( + COMMAND ${GIT_EXECUTABLE} describe --always --tags + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + RESULT_VARIABLE GIT_DESCRIBE_RESULT + OUTPUT_VARIABLE QGC_APP_VERSION_STR + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + if(NOT GIT_DESCRIBE_RESULT EQUAL 0) + set(QGC_APP_VERSION_STR "v5.0.8") + endif() + + # Get version tag with error handling + execute_process( + COMMAND ${GIT_EXECUTABLE} describe --always --abbrev=0 + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + RESULT_VARIABLE GIT_VERSION_RESULT + OUTPUT_VARIABLE QGC_APP_VERSION + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + if(NOT GIT_VERSION_RESULT EQUAL 0) + set(QGC_APP_VERSION "v5.0.8") + endif() -execute_process( - COMMAND ${GIT_EXECUTABLE} describe --always --abbrev=0 - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - OUTPUT_VARIABLE QGC_APP_VERSION - OUTPUT_STRIP_TRAILING_WHITESPACE -) -# cmake_print_variables(QGC_APP_VERSION) + # Get commit date with error handling + execute_process( + COMMAND ${GIT_EXECUTABLE} log -1 --format=%aI HEAD + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + RESULT_VARIABLE GIT_DATE_RESULT + OUTPUT_VARIABLE QGC_APP_DATE + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + if(NOT GIT_DATE_RESULT EQUAL 0) + set(QGC_APP_DATE "unknown") + endif() -execute_process( - COMMAND ${GIT_EXECUTABLE} log -1 --format=%aI ${QGC_APP_VERSION} - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - OUTPUT_VARIABLE QGC_APP_DATE - OUTPUT_STRIP_TRAILING_WHITESPACE -) -# cmake_print_variables(QGC_APP_DATE) +else() + # Not a git repository, use hardcoded values + message(STATUS "Not a git repository, using hardcoded version 5.0.7") + set(QGC_GIT_BRANCH "release") + set(QGC_GIT_HASH "unknown") + set(QGC_APP_VERSION_STR "v5.0.8") + set(QGC_APP_VERSION "v5.0.8") + set(QGC_APP_DATE "2025-01-01") +endif() -string(FIND ${QGC_APP_VERSION} "v" QGC_APP_VERSION_VALID) +# Safe version parsing +string(FIND "${QGC_APP_VERSION}" "v" QGC_APP_VERSION_VALID) if(QGC_APP_VERSION_VALID GREATER -1) string(REPLACE "v" "" QGC_APP_VERSION ${QGC_APP_VERSION}) else() - set(QGC_APP_VERSION "0.0.0") + set(QGC_APP_VERSION "5.0.8") endif() -string(REGEX MATCH "([0-9]+)\\.([0-9]+)\\.([0-9]+)" QGC_APP_VERSION_MATCH ${QGC_APP_VERSION}) -set(QGC_APP_VERSION_MAJOR ${CMAKE_MATCH_1}) -set(QGC_APP_VERSION_MINOR ${CMAKE_MATCH_2}) -set(QGC_APP_VERSION_PATCH ${CMAKE_MATCH_3}) -# cmake_print_variables(QGC_APP_VERSION QGC_APP_VERSION_MAJOR QGC_APP_VERSION_MINOR QGC_APP_VERSION_PATCH) + +# Safe regex matching +string(REGEX MATCH "([0-9]+)\\.([0-9]+)\\.([0-9]+)" QGC_APP_VERSION_MATCH "${QGC_APP_VERSION}") +if(QGC_APP_VERSION_MATCH) + set(QGC_APP_VERSION_MAJOR ${CMAKE_MATCH_1}) + set(QGC_APP_VERSION_MINOR ${CMAKE_MATCH_2}) + set(QGC_APP_VERSION_PATCH ${CMAKE_MATCH_3}) +else() + # Fallback if regex fails + set(QGC_APP_VERSION_MAJOR "5") + set(QGC_APP_VERSION_MINOR "0") + set(QGC_APP_VERSION_PATCH "8") +endif() + +message(STATUS "QGC Version: ${QGC_APP_VERSION_MAJOR}.${QGC_APP_VERSION_MINOR}.${QGC_APP_VERSION_PATCH}") +message(STATUS "Git Branch: ${QGC_GIT_BRANCH}") +message(STATUS "Git Hash: ${QGC_GIT_HASH}") \ No newline at end of file From 533649b9d226d086df1995e7c5462d7d0e30242b Mon Sep 17 00:00:00 2001 From: Ilya Mukhamadeev Date: Fri, 27 Feb 2026 11:34:01 +0300 Subject: [PATCH 63/69] Bump Qt maximum version, add LocationPrivate module --- CMakeLists.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index db0f01ae8d4b..87895d90ab63 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -166,11 +166,11 @@ endif() if(QGC_ENABLE_HERELINK AND ANDROID) set(QGC_QT_MINIMUM_VERSION "6.6.3" CACHE STRING "Minimum Supported Qt Version") - set(QGC_QT_MAXIMUM_VERSION "6.6.3" CACHE STRING "Maximum Supported Qt Version") + set(QGC_QT_MAXIMUM_VERSION "6.10.3" CACHE STRING "Maximum Supported Qt Version") set(QGC_QT_ANDROID_MIN_SDK_VERSION "25" CACHE STRING "Android Min SDK Version") else() set(QGC_QT_MINIMUM_VERSION "6.8.3" CACHE STRING "Minimum Supported Qt Version") - set(QGC_QT_MAXIMUM_VERSION "6.8.3" CACHE STRING "Maximum Supported Qt Version") + set(QGC_QT_MAXIMUM_VERSION "6.10.3" CACHE STRING "Maximum Supported Qt Version") set(QGC_QT_ANDROID_MIN_SDK_VERSION "28" CACHE STRING "Android Min SDK Version") endif() @@ -185,6 +185,7 @@ find_package(Qt6 Gui LinguistTools Location + LocationPrivate Multimedia Network OpenGL From d9ba304cdba6dbdca9b75aca6af74f03f34bed29 Mon Sep 17 00:00:00 2001 From: Ilya Mukhamadeev Date: Fri, 27 Feb 2026 11:34:37 +0300 Subject: [PATCH 64/69] Fix GStreamer path --- cmake/find-modules/FindGStreamer.cmake | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cmake/find-modules/FindGStreamer.cmake b/cmake/find-modules/FindGStreamer.cmake index d7bf04d042b2..af5266201e4c 100644 --- a/cmake/find-modules/FindGStreamer.cmake +++ b/cmake/find-modules/FindGStreamer.cmake @@ -83,10 +83,10 @@ elseif(LINUX) message(FATAL_ERROR "Could not locate GStreamer - check installation or set environment/cmake variables") endif() - if((EXISTS "${GStreamer_ROOT_DIR}/lib/${CMAKE_SYSTEM_PROCESSOR}-linux-gnu" ) AND (EXISTS "${GStreamer_ROOT_DIR}/lib/${CMAKE_SYSTEM_PROCESSOR}-linux-gnu/gstreamer-1.0")) - set(GSTREAMER_LIB_PATH "${GStreamer_ROOT_DIR}/lib/${CMAKE_SYSTEM_PROCESSOR}-linux-gnu") - elseif(EXISTS "${GStreamer_ROOT_DIR}/lib") - set(GSTREAMER_LIB_PATH "${GStreamer_ROOT_DIR}/lib") + if((EXISTS "${GStreamer_ROOT_DIR}/${LIB_DIR_NAME}/${CMAKE_SYSTEM_PROCESSOR}-linux-gnu" ) AND (EXISTS "${GStreamer_ROOT_DIR}/${LIB_DIR_NAME}/${CMAKE_SYSTEM_PROCESSOR}-linux-gnu/gstreamer-1.0")) + set(GSTREAMER_LIB_PATH "${GStreamer_ROOT_DIR}/${LIB_DIR_NAME}/${CMAKE_SYSTEM_PROCESSOR}-linux-gnu") + elseif(EXISTS "${GStreamer_ROOT_DIR}/${LIB_DIR_NAME}") + set(GSTREAMER_LIB_PATH "${GStreamer_ROOT_DIR}/${LIB_DIR_NAME}") else() message(FATAL_ERROR "Could not locate GStreamer - check installation or set environment/cmake variables") endif() From fe95e9921aac09299ec74917864af92fe34c9d51 Mon Sep 17 00:00:00 2001 From: Ilya Mukhamadeev Date: Fri, 27 Feb 2026 11:35:21 +0300 Subject: [PATCH 65/69] Add type set --- src/ADSB/ADSBVehicle.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ADSB/ADSBVehicle.cc b/src/ADSB/ADSBVehicle.cc index 115fea20196e..94156e286ea7 100644 --- a/src/ADSB/ADSBVehicle.cc +++ b/src/ADSB/ADSBVehicle.cc @@ -36,7 +36,7 @@ void ADSBVehicle::update(const ADSB::VehicleInfo_t &vehicleInfo) return; } - qCDebug(ADSBVehicleLog) << "Updating" << QStringLiteral("%1 Flags: %2").arg(vehicleInfo.icaoAddress, 0, 16).arg(vehicleInfo.availableFlags, 0, 2); + qCDebug(ADSBVehicleLog) << "Updating" << QStringLiteral("%1 Flags: %2").arg(QLatin1StringView(QByteArray::number(vehicleInfo.icaoAddress, 16))).arg(QLatin1StringView(QByteArray::number(vehicleInfo.availableFlags, 2))); if (vehicleInfo.availableFlags & ADSB::LocationAvailable) { if (!QGC::fuzzyCompare(vehicleInfo.location.latitude(), coordinate().latitude()) || !QGC::fuzzyCompare(vehicleInfo.location.longitude(), coordinate().longitude())) { From b007d2b933b32ebe164d508ba058bebc3310a709 Mon Sep 17 00:00:00 2001 From: Ilya Mukhamadeev Date: Fri, 27 Feb 2026 11:36:25 +0300 Subject: [PATCH 66/69] Add GRIPPER_ACTION definitions --- src/Joystick/Joystick.cc | 2 ++ src/MAVLink/QGCMAVLink.h | 2 ++ src/Vehicle/Vehicle.cc | 2 ++ 3 files changed, 6 insertions(+) diff --git a/src/Joystick/Joystick.cc b/src/Joystick/Joystick.cc index 23a35c1ea860..404d61b9a61c 100644 --- a/src/Joystick/Joystick.cc +++ b/src/Joystick/Joystick.cc @@ -8,6 +8,8 @@ ****************************************************************************/ +#define GRIPPER_ACTION_RELEASE GRIPPER_ACTION_OPEN +#define GRIPPER_ACTION_GRAB GRIPPER_ACTION_CLOSE #include "Joystick.h" #include "MavlinkAction.h" #include "MavlinkActionManager.h" diff --git a/src/MAVLink/QGCMAVLink.h b/src/MAVLink/QGCMAVLink.h index c572f7cc79f3..a0659734343c 100644 --- a/src/MAVLink/QGCMAVLink.h +++ b/src/MAVLink/QGCMAVLink.h @@ -7,6 +7,8 @@ * ****************************************************************************/ +#define GRIPPER_ACTION_RELEASE GRIPPER_ACTION_OPEN +#define GRIPPER_ACTION_GRAB GRIPPER_ACTION_CLOSE #pragma once #include diff --git a/src/Vehicle/Vehicle.cc b/src/Vehicle/Vehicle.cc index ba6a7be316db..b6d60f4a0154 100644 --- a/src/Vehicle/Vehicle.cc +++ b/src/Vehicle/Vehicle.cc @@ -7,6 +7,8 @@ * ****************************************************************************/ +#define GRIPPER_ACTION_RELEASE GRIPPER_ACTION_OPEN +#define GRIPPER_ACTION_GRAB GRIPPER_ACTION_CLOSE #include "Vehicle.h" #include "Actuators.h" #include "ADSBVehicleManager.h" From 0a8730f89a5f3799fcd82d8d888dc12e654a2d3f Mon Sep 17 00:00:00 2001 From: Ilya Mukhamadeev Date: Fri, 27 Feb 2026 11:37:14 +0300 Subject: [PATCH 67/69] Uncomment qml6 plugin registration --- src/VideoManager/VideoReceiver/GStreamer/GStreamer.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VideoManager/VideoReceiver/GStreamer/GStreamer.cc b/src/VideoManager/VideoReceiver/GStreamer/GStreamer.cc index 4c884af775f4..86e13a289e2b 100644 --- a/src/VideoManager/VideoReceiver/GStreamer/GStreamer.cc +++ b/src/VideoManager/VideoReceiver/GStreamer/GStreamer.cc @@ -120,9 +120,9 @@ void _registerPlugins() #endif #endif -// #if !defined(GST_PLUGIN_qml6_FOUND) && defined(QGC_GST_STATIC_BUILD) +#if !defined(GST_PLUGIN_qml6_FOUND) && defined(QGC_GST_STATIC_BUILD) GST_PLUGIN_STATIC_REGISTER(qml6); -// #endif +#endif GST_PLUGIN_STATIC_REGISTER(qgc); } From 10b78597458d7be61bb5a4227eeebebd9530eaac Mon Sep 17 00:00:00 2001 From: Ilya Mukhamadeev Date: Fri, 27 Feb 2026 11:37:54 +0300 Subject: [PATCH 68/69] Add QElapsedTimer --- src/QtLocationPlugin/QGCTileCacheWorker.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/QtLocationPlugin/QGCTileCacheWorker.h b/src/QtLocationPlugin/QGCTileCacheWorker.h index eb1615cd296e..b14925af7d11 100644 --- a/src/QtLocationPlugin/QGCTileCacheWorker.h +++ b/src/QtLocationPlugin/QGCTileCacheWorker.h @@ -24,6 +24,7 @@ #include #include #include +#include Q_DECLARE_LOGGING_CATEGORY(QGCTileCacheWorkerLog) From 4d0e343c470f3e9a0a9cf0596dd18e15af8ceea2 Mon Sep 17 00:00:00 2001 From: Ilya Mukhamadeev Date: Fri, 27 Feb 2026 11:38:11 +0300 Subject: [PATCH 69/69] Build with system libs --- src/AnalyzeView/CMakeLists.txt | 5 ++++ src/Comms/CMakeLists.txt | 6 +++++ src/GPS/CMakeLists.txt | 4 +++ src/Joystick/CMakeLists.txt | 10 ++++++++ src/MAVLink/CMakeLists.txt | 15 +++++++++++ src/MAVLink/LibEvents/CMakeLists.txt | 25 +++++++++++++++++++ src/Utilities/Compression/CMakeLists.txt | 25 +++++++++++++++++++ src/Utilities/Geo/CMakeLists.txt | 5 ++++ src/Utilities/Shape/CMakeLists.txt | 2 ++ .../VideoReceiver/GStreamer/CMakeLists.txt | 8 +++++- 10 files changed, 104 insertions(+), 1 deletion(-) diff --git a/src/AnalyzeView/CMakeLists.txt b/src/AnalyzeView/CMakeLists.txt index 25bc1bc3b5f7..711494af8522 100644 --- a/src/AnalyzeView/CMakeLists.txt +++ b/src/AnalyzeView/CMakeLists.txt @@ -48,6 +48,7 @@ qt_add_qml_module(AnalyzeViewModule #===========================================================================# +if(NOT USE_SYSTEM_ULOG_CPP) CPMAddPackage( NAME ulog_cpp GITHUB_REPOSITORY PX4/ulog_cpp @@ -59,3 +60,7 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") endif() target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE ulog_cpp::ulog_cpp) +else() +find_package(ulog_cpp REQUIRED) +target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE ulog_cpp) +endif() diff --git a/src/Comms/CMakeLists.txt b/src/Comms/CMakeLists.txt index a4e89ff71d84..b2a8b026b903 100644 --- a/src/Comms/CMakeLists.txt +++ b/src/Comms/CMakeLists.txt @@ -57,6 +57,7 @@ endif() #===========================================================================# if(QGC_ZEROCONF_ENABLED) + if(NOT USE_SYSTEM_QMDNSENGINE) CPMAddPackage( NAME qmdnsengine GITHUB_REPOSITORY nitroshare/qmdnsengine @@ -71,6 +72,11 @@ if(QGC_ZEROCONF_ENABLED) target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE qmdnsengine) target_compile_definitions(${CMAKE_PROJECT_NAME} PRIVATE QGC_ZEROCONF_ENABLED) endif() + else() + find_package(qmdnsengine REQUIRED) + target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE qmdnsengine) + target_compile_definitions(${CMAKE_PROJECT_NAME} PRIVATE QGC_ZEROCONF_ENABLED) + endif() endif() #===========================================================================# diff --git a/src/GPS/CMakeLists.txt b/src/GPS/CMakeLists.txt index 4b75ad46c956..dde64092f196 100644 --- a/src/GPS/CMakeLists.txt +++ b/src/GPS/CMakeLists.txt @@ -25,6 +25,7 @@ target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_ #===========================================================================# +if(NOT USE_SYSTEM_PX4-GPSDRIVERS) CPMAddPackage( NAME px4-gpsdrivers GITHUB_REPOSITORY PX4/PX4-GPSDrivers @@ -32,6 +33,9 @@ CPMAddPackage( SOURCE_SUBDIR src ) +else() +set(px4-gpsdrivers_SOURCE_DIR /usr/src/PX4-GPSDrivers) +endif() file(GLOB GPS_DRIVERS_SOURCES "${px4-gpsdrivers_SOURCE_DIR}/src/*") target_sources(${CMAKE_PROJECT_NAME} PRIVATE diff --git a/src/Joystick/CMakeLists.txt b/src/Joystick/CMakeLists.txt index 941047bf2911..bf81f522208a 100644 --- a/src/Joystick/CMakeLists.txt +++ b/src/Joystick/CMakeLists.txt @@ -27,12 +27,16 @@ target_compile_definitions(${CMAKE_PROJECT_NAME} PRIVATE QGC_SDL_JOYSTICK) #===========================================================================# +if(NOT USE_SYSTEM_SDL_GAMECONTROLLERDB) CPMAddPackage( NAME sdl_gamecontrollerdb GITHUB_REPOSITORY mdqinc/SDL_GameControllerDB GIT_TAG master ) +else() +set(sdl_gamecontrollerdb_SOURCE_DIR /usr/share/SDL_GameControllerDB) +endif() set(SDL_GAMECONTROLLERDB_PATH "${sdl_gamecontrollerdb_SOURCE_DIR}/gamecontrollerdb.txt" CACHE FILEPATH "SDL GameControllerDB Path") #===========================================================================# @@ -45,6 +49,7 @@ if(WIN32) ) endif() +if(NOT USE_SYSTEM_SDL2) CPMAddPackage( NAME SDL2 VERSION 2.32.4 @@ -86,6 +91,11 @@ CPMAddPackage( target_compile_definitions(SDL2-static PRIVATE SDL_MAIN_HANDLED) target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE SDL2::SDL2-static) +else() +find_package(SDL2 2.32.4 REQUIRED CONFIG) +target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE SDL2) +target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE ${SDL2_INCLUDE_DIRS}) +endif() # Default list of config overrides - TODO: Add to Custom Build Options # set(ENV{SDL_GAMECONTROLLERCONFIG} diff --git a/src/MAVLink/CMakeLists.txt b/src/MAVLink/CMakeLists.txt index 15e37b0411fc..5c58272ae0fd 100644 --- a/src/MAVLink/CMakeLists.txt +++ b/src/MAVLink/CMakeLists.txt @@ -33,12 +33,27 @@ message(STATUS "Building MAVLink") # "MAVLINK_VERSION 2.0" # ) +if(NOT USE_SYSTEM_MAVLINK) CPMAddPackage( NAME mavlink GIT_REPOSITORY ${QGC_MAVLINK_GIT_REPO} GIT_TAG ${QGC_MAVLINK_GIT_TAG} ) +else() +set(mavlink_SOURCE_DIR /usr/include/c_library_v2) +file(GLOB MAVLINK_SOURCES1 ${mavlink_SOURCE_DIR}/*) +file(GLOB MAVLINK_SOURCES2 ${mavlink_SOURCE_DIR}/all/*) +file(GLOB MAVLINK_SOURCES3 ${mavlink_SOURCE_DIR}/common/*) +file(GLOB MAVLINK_SOURCES4 ${mavlink_SOURCE_DIR}/development/*) +target_sources(${CMAKE_PROJECT_NAME} + PRIVATE + ${MAVLINK_SOURCES1} + ${MAVLINK_SOURCES2} + ${MAVLINK_SOURCES3} + ${MAVLINK_SOURCES4} +) +endif() # For QGC all dialects means common and development. Though use of development mavlink code should be restricted to debug builds only. target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE diff --git a/src/MAVLink/LibEvents/CMakeLists.txt b/src/MAVLink/LibEvents/CMakeLists.txt index 35554453bc85..65f54842d90a 100644 --- a/src/MAVLink/LibEvents/CMakeLists.txt +++ b/src/MAVLink/LibEvents/CMakeLists.txt @@ -11,6 +11,7 @@ target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_ #===========================================================================# +if(NOT USE_SYSTEM_LIBEVENTS) CPMAddPackage( NAME libevents GITHUB_REPOSITORY mavlink/libevents @@ -20,3 +21,27 @@ CPMAddPackage( target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE libevents) target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE ${libevents_SOURCE_DIR}/libs/cpp) +else() +find_package(libevents REQUIRED) +set(LIBEVENTS_INCLUDE_DIRS /usr/include/libevents) +target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE ${LIBEVENTS_INCLUDE_DIRS}) +find_library(EVENTS_HEALTH_LIB events_health_and_arming_checks) +find_library(EVENTS_PARSER_LIB events_parser) +target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE ${EVENTS_HEALTH_LIB}) +target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE ${EVENTS_PARSER_LIB}) +find_package(PkgConfig) + +if(TARGET libevents) + if(TARGET events_health_and_arming_checks) + if(TARGET events_parser) + target_link_libraries(events_health_and_arming_checks PRIVATE events_parser) + message(STATUS "Fixed: events_health_and_arming_checks -> events_parser") + endif() + endif() + + if(TARGET events::health_and_arming_checks AND TARGET events::parser) + target_link_libraries(events::health_and_arming_checks PRIVATE events::parser) + message(STATUS "Fixed: events::health_and_arming_checks -> events::parser") + endif() +endif() +endif() diff --git a/src/Utilities/Compression/CMakeLists.txt b/src/Utilities/Compression/CMakeLists.txt index 900010494534..982816c0d35c 100644 --- a/src/Utilities/Compression/CMakeLists.txt +++ b/src/Utilities/Compression/CMakeLists.txt @@ -19,6 +19,7 @@ if(WIN32) ) endif() +if(NOT USE_SYSTEM_ZLIB) CPMAddPackage( NAME zlib GITHUB_REPOSITORY madler/zlib @@ -35,8 +36,12 @@ CPMAddPackage( target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE ZLIB::ZLIBSTATIC) +else() +target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE z) +endif() #===========================================================================# +if(NOT USE_SYSTEM_XZ-EMBEDDED) CPMAddPackage( NAME xz-embedded VERSION 2024-12-30 @@ -75,3 +80,23 @@ target_compile_definitions(xz ) target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE xz) +else() + +add_library(xz STATIC ${LIB_PREFIX}/libxz.a) +set_target_properties(xz PROPERTIES + IMPORTED_LOCATION "${LIB_PREFIX}/libxz.a" + INTERFACE_INCLUDE_DIRECTORIES "/usr/include/xz-embedded" +) + +target_compile_definitions(xz + PUBLIC + XZ_USE_CRC64 + XZ_USE_CRC32 + XZ_DEC_ANY_CHECK +) + + +target_include_directories(xz INTERFACE /usr/include/xz-embedded) +target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE xz) +target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE "${LIB_PREFIX}/libxz.a") +endif() diff --git a/src/Utilities/Geo/CMakeLists.txt b/src/Utilities/Geo/CMakeLists.txt index adb84b120b59..a4c7846c3846 100644 --- a/src/Utilities/Geo/CMakeLists.txt +++ b/src/Utilities/Geo/CMakeLists.txt @@ -8,6 +8,7 @@ target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_ #===========================================================================# +if(NOT USE_SYSTEM_GEOGRAPHICLIB) CPMAddPackage( NAME geographiclib VERSION 2.5 @@ -35,3 +36,7 @@ CPMAddPackage( target_compile_options(GeographicLib_STATIC PRIVATE $<$:/wd9025>) target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE GeographicLib::GeographicLib) +else() +find_package(GeographicLib REQUIRED) +target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE GeographicLib) +endif() diff --git a/src/Utilities/Shape/CMakeLists.txt b/src/Utilities/Shape/CMakeLists.txt index 7dde5199c7d7..850ba1e174d8 100644 --- a/src/Utilities/Shape/CMakeLists.txt +++ b/src/Utilities/Shape/CMakeLists.txt @@ -14,6 +14,7 @@ target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_ #===========================================================================# +if(NOT USE_SYSTEM_SHAPE) CPMAddPackage( NAME Shapelib VERSION 1.6.1 @@ -23,5 +24,6 @@ CPMAddPackage( "BUILD_APPS OFF" "BUILD_TESTING OFF" ) +endif() target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE shp) diff --git a/src/VideoManager/VideoReceiver/GStreamer/CMakeLists.txt b/src/VideoManager/VideoReceiver/GStreamer/CMakeLists.txt index 7a85b09c2ece..9acd1a24c351 100644 --- a/src/VideoManager/VideoReceiver/GStreamer/CMakeLists.txt +++ b/src/VideoManager/VideoReceiver/GStreamer/CMakeLists.txt @@ -10,8 +10,14 @@ if(QGC_ENABLE_GST_VIDEOSTREAMING) COMPONENTS Core Base Video Gl GlPrototypes Rtsp OPTIONAL_COMPONENTS GlEgl GlWayland GlX11) endif() + if(USE_SYSTEM_GSTQML6) + target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE "${LIB_PREFIX}/gstreamer-1.0/libgstqml6.so") + else() + add_subdirectory(gstqml6gl) + endif() + + - add_subdirectory(gstqml6gl) # TODO: https://gstreamer.freedesktop.org/documentation/qt6d3d11/index.html#qml6d3d11sink-page endif()