Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions qgcimages.qrc
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@
<file alias="GeoFenceLight.svg">src/AutoPilotPlugins/PX4/Images/GeoFenceLight.svg</file>
<file alias="GeoTagIcon.svg">src/AnalyzeView/GeoTagIcon.svg</file>
<file alias="Gps.svg">src/UI/toolbar/Images/Gps.svg</file>
<file alias="GpsAuthentication.svg">src/UI/toolbar/Images/GpsAuthentication.svg</file>
<file alias="GpsInterference.svg">src/UI/toolbar/Images/GpsInterference.svg</file>
<file alias="Hamburger.svg">src/UI/toolbar/Images/Hamburger.svg</file>
<file alias="HamburgerThin.svg">src/UI/toolbar/Images/HamburgerThin.svg</file>
<file alias="Help.svg">src/FlightMap/Images/Help.svg</file>
Expand Down
1 change: 1 addition & 0 deletions src/FirmwarePlugin/FirmwarePlugin.cc
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ const QVariantList &FirmwarePlugin::toolIndicators(const Vehicle*)
if (_toolIndicatorList.isEmpty()) {
_toolIndicatorList = QVariantList({
QVariant::fromValue(QUrl::fromUserInput("qrc:/qml/QGroundControl/Toolbar/VehicleGPSIndicator.qml")),
QVariant::fromValue(QUrl::fromUserInput("qrc:/qml/QGroundControl/Toolbar/GPSResilienceIndicator.qml")),
QVariant::fromValue(QUrl::fromUserInput("qrc:/qml/QGroundControl/Toolbar/TelemetryRSSIIndicator.qml")),
QVariant::fromValue(QUrl::fromUserInput("qrc:/qml/QGroundControl/Toolbar/RCRSSIIndicator.qml")),
QVariant::fromValue(QUrl::fromUserInput("qrc:/qml/QGroundControl/Controls/BatteryIndicator.qml")),
Expand Down
30 changes: 30 additions & 0 deletions src/QmlControls/GPSIndicatorPage.qml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,30 @@ ToolIndicatorPage {
updateSettingsDisplayId()
}

function errorText() {
if (!_activeVehicle) {
return qsTr("Disconnected");
}

switch (_activeVehicle.gps.systemErrors.value) {
case 1:
return qsTr("Incoming correction");
case 2:
return qsTr("Configuration");
case 4:
return qsTr("Software");
case 8:
return qsTr("Antenna");
case 16:
return qsTr("Event congestion");
case 32:
return qsTr("CPU overload");
case 64:
return qsTr("Output congestion");
default:
return qsTr("Multiple errors");
}
Comment on lines +71 to +88
Copy link

Copilot AI Dec 25, 2025

Choose a reason for hiding this comment

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

The systemErrors field appears to be a bitmask (values are powers of 2: 1, 2, 4, 8, 16, 32, 64), but the switch statement handles only exact single-bit values. When multiple errors occur simultaneously (e.g., value = 3 for bits 0 and 1 set), it will fall through to "Multiple errors" without identifying which specific errors are present. Consider implementing proper bit-flag checking to handle combinations of errors, or at least document this behavior if it's intentional.

Suggested change
switch (_activeVehicle.gps.systemErrors.value) {
case 1:
return qsTr("Incoming correction");
case 2:
return qsTr("Configuration");
case 4:
return qsTr("Software");
case 8:
return qsTr("Antenna");
case 16:
return qsTr("Event congestion");
case 32:
return qsTr("CPU overload");
case 64:
return qsTr("Output congestion");
default:
return qsTr("Multiple errors");
}
var errors = _activeVehicle.gps.systemErrors.value;
var messages = [];
if (errors & 1) {
messages.push(qsTr("Incoming correction"));
}
if (errors & 2) {
messages.push(qsTr("Configuration"));
}
if (errors & 4) {
messages.push(qsTr("Software"));
}
if (errors & 8) {
messages.push(qsTr("Antenna"));
}
if (errors & 16) {
messages.push(qsTr("Event congestion"));
}
if (errors & 32) {
messages.push(qsTr("CPU overload"));
}
if (errors & 64) {
messages.push(qsTr("Output congestion"));
}
if (messages.length === 1) {
return messages[0];
} else if (messages.length > 1) {
return messages.join(", ");
} else {
return qsTr("Multiple errors");
}

Copilot uses AI. Check for mistakes.
Copy link
Collaborator

Choose a reason for hiding this comment

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

This does need to be addressed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I intentionally grouped multiple errors into a single "Multiple errors" message to keep the UI clean, as there isn't much space to list several error strings like "Incoming correction, Configuration" without cluttering the display. Since these are exceptional situations, and having multiple at once is even rarer, a simple summary seemed like the best trade-off, especially since investigation would be required anyway. However, if you think seeing every individual error is critical, I can add the bit-flag checking

}

contentComponent: Component {
ColumnLayout {
Expand Down Expand Up @@ -96,6 +120,12 @@ ToolIndicatorPage {
label: qsTr("Course Over Ground")
labelText: activeVehicle ? activeVehicle.gps.courseOverGround.valueString : valueNA
}

LabelledLabel {
label: qsTr("GPS Error")
labelText: errorText()
visible: activeVehicle && activeVehicle.gps.systemErrors.value > 0
}
}

SettingsGroupLayout {
Expand Down
1 change: 1 addition & 0 deletions src/UI/toolbar/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ qt_add_qml_module(ToolbarModule
EscIndicatorPage.qml
GCSControlIndicator.qml
GimbalIndicator.qml
GPSResilienceIndicator.qml
JoystickIndicator.qml
ModeIndicator.qml
MultiVehicleSelector.qml
Expand Down
180 changes: 180 additions & 0 deletions src/UI/toolbar/GPSResilienceIndicator.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
/****************************************************************************
*
* (c) 2009-2024 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
*
* QGroundControl is licensed according to the terms in the file
* COPYING.md in the root of the source code directory.
*
****************************************************************************/

import QtQuick
import QtQuick.Layouts

import QGroundControl
import QGroundControl.Controls

//-------------------------------------------------------------------------
//-- GPS Resilience Indicator
Item {
id: control
width: height
anchors.top: parent.top
anchors.bottom: parent.bottom
visible: showIndicator

property var _activeVehicle: QGroundControl.multiVehicleManager.activeVehicle
property var _gpsAggregate: _activeVehicle ? _activeVehicle.gpsAggregate : null

property var qgcPal: QGroundControl.globalPalette

property bool showIndicator: _activeVehicle && _gpsAggregate && (
(_gpsAggregate.authenticationState.value > 0 && _gpsAggregate.authenticationState.value < 255) ||
(_gpsAggregate.spoofingState.value > 0 && _gpsAggregate.spoofingState.value < 255) ||
(_gpsAggregate.jammingState.value > 0 && _gpsAggregate.jammingState.value < 255)
)

// Authentication Icon (Outer/Bottom Layer)
QGCColoredImage {
id: authIcon
width: parent.height * 0.95
height: parent.height * 0.95
anchors.centerIn: parent
source: "/qmlimages/GpsAuthentication.svg"
fillMode: Image.PreserveAspectFit
sourceSize.height: height
color: _authColor()
visible: _gpsAggregate && _gpsAggregate.authenticationState.value > 0 && _gpsAggregate.authenticationState.value < 255
}

// Interference Icon (Inner/Top Layer)
QGCColoredImage {
id: interfIcon
width: parent.height * 0.55
height: parent.height * 0.55
anchors.centerIn: parent
source: "/qmlimages/GpsInterference.svg"
fillMode: Image.PreserveAspectFit
sourceSize.height: height
color: _interfColor()
visible: _gpsAggregate && (Math.max(_gpsAggregate.spoofingState.value, _gpsAggregate.jammingState.value) > 0) && (Math.max(_gpsAggregate.spoofingState.value, _gpsAggregate.jammingState.value) < 255)
}

function _authColor() {
if (!_gpsAggregate) return qgcPal.colorGrey;
switch (_gpsAggregate.authenticationState.value) {
case 1: return qgcPal.colorYellow; // Initializing
case 2: return qgcPal.colorRed; // Error
case 3: return qgcPal.colorGreen; // OK
default: return qgcPal.colorGrey; // Unknown or Disabled
}
}

function _interfColor() {
if (!_gpsAggregate) return qgcPal.colorGrey;
let maxState = Math.max(_gpsAggregate.spoofingState.value, _gpsAggregate.jammingState.value);
switch (maxState) {
case 1: return qgcPal.colorGreen; // Not spoofed/jammed
case 2: return qgcPal.colorOrange; // Mitigated
case 3: return qgcPal.colorRed; // Detected
default: return qgcPal.colorGrey; // Unknown
}
}

MouseArea {
anchors.fill: parent
onClicked: mainWindow.showIndicatorDrawer(resiliencePopup, control)
}

Component {
id: resiliencePopup
ToolIndicatorPage {
showExpand: expandedComponent ? true : false
contentComponent: resilienceContent
}
}

Component {
id: resilienceContent
ColumnLayout {
spacing: ScreenTools.defaultFontPixelHeight / 2

// Unified GPS Resilience Status
SettingsGroupLayout {
heading: qsTr("GPS Resilience Status")
showDividers: true

LabelledLabel {
label: qsTr("GPS Jamming")
labelText: _gpsAggregate ? (_gpsAggregate.jammingState.enumStringValue || qsTr("n/a")) : qsTr("n/a")
visible: _gpsAggregate && _gpsAggregate.jammingState.value > 0 && _gpsAggregate.jammingState.value < 255
}

LabelledLabel {
label: qsTr("GPS Spoofing")
labelText: _gpsAggregate ? (_gpsAggregate.spoofingState.enumStringValue || qsTr("n/a")) : qsTr("n/a")
visible: _gpsAggregate && _gpsAggregate.spoofingState.value > 0 && _gpsAggregate.spoofingState.value < 255
}

LabelledLabel {
label: qsTr("GPS Authentication")
labelText: _gpsAggregate ? (_gpsAggregate.authenticationState.enumStringValue || qsTr("n/a")) : qsTr("n/a")
visible: _gpsAggregate && _gpsAggregate.authenticationState.value > 0 && _gpsAggregate.authenticationState.value < 255
}
}

// GPS 1 Details
SettingsGroupLayout {
heading: qsTr("GPS 1 Details")
showDividers: true
visible: _activeVehicle && _activeVehicle.gps && (
(_activeVehicle.gps.jammingState.value > 0 && _activeVehicle.gps.jammingState.value < 255) ||
(_activeVehicle.gps.spoofingState.value > 0 && _activeVehicle.gps.spoofingState.value < 255) ||
(_activeVehicle.gps.authenticationState.value > 0 && _activeVehicle.gps.authenticationState.value < 255)
)

LabelledLabel {
label: qsTr("Jamming")
labelText: (_activeVehicle && _activeVehicle.gps) ? (_activeVehicle.gps.jammingState.enumStringValue || qsTr("n/a")) : qsTr("n/a")
visible: _activeVehicle.gps.jammingState.value > 0 && _activeVehicle.gps.jammingState.value < 255
}
LabelledLabel {
label: qsTr("Spoofing")
labelText: (_activeVehicle && _activeVehicle.gps) ? (_activeVehicle.gps.spoofingState.enumStringValue || qsTr("n/a")) : qsTr("n/a")
visible: _activeVehicle.gps.spoofingState.value > 0 && _activeVehicle.gps.spoofingState.value < 255
}
LabelledLabel {
label: qsTr("Authentication")
labelText: (_activeVehicle && _activeVehicle.gps) ? (_activeVehicle.gps.authenticationState.enumStringValue || qsTr("n/a")) : qsTr("n/a")
visible: _activeVehicle.gps.authenticationState.value > 0 && _activeVehicle.gps.authenticationState.value < 255
}
}

// GPS 2 Details
SettingsGroupLayout {
heading: qsTr("GPS 2 Details")
showDividers: true
visible: _activeVehicle && _activeVehicle.gps2 && (
(_activeVehicle.gps2.jammingState.value > 0 && _activeVehicle.gps2.jammingState.value < 255) ||
(_activeVehicle.gps2.spoofingState.value > 0 && _activeVehicle.gps2.spoofingState.value < 255) ||
(_activeVehicle.gps2.authenticationState.value > 0 && _activeVehicle.gps2.authenticationState.value < 255)
)

LabelledLabel {
label: qsTr("Jamming")
labelText: (_activeVehicle && _activeVehicle.gps2) ? (_activeVehicle.gps2.jammingState.enumStringValue || qsTr("n/a")) : qsTr("n/a")
visible: _activeVehicle.gps2.jammingState.value > 0 && _activeVehicle.gps2.jammingState.value < 255
}
LabelledLabel {
label: qsTr("Spoofing")
labelText: (_activeVehicle && _activeVehicle.gps2) ? (_activeVehicle.gps2.spoofingState.enumStringValue || qsTr("n/a")) : qsTr("n/a")
visible: _activeVehicle.gps2.spoofingState.value > 0 && _activeVehicle.gps2.spoofingState.value < 255
}
LabelledLabel {
label: qsTr("Authentication")
labelText: (_activeVehicle && _activeVehicle.gps2) ? (_activeVehicle.gps2.authenticationState.enumStringValue || qsTr("n/a")) : qsTr("n/a")
visible: _activeVehicle.gps2.authenticationState.value > 0 && _activeVehicle.gps2.authenticationState.value < 255
}
}
}
}
}
7 changes: 7 additions & 0 deletions src/UI/toolbar/Images/GpsAuthentication.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 18 additions & 0 deletions src/UI/toolbar/Images/GpsInterference.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions src/Vehicle/FactGroups/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ target_sources(${CMAKE_PROJECT_NAME}
VehicleGPS2FactGroup.h
VehicleGPSFactGroup.cc
VehicleGPSFactGroup.h
VehicleGPSAggregateFactGroup.cc
VehicleGPSAggregateFactGroup.h
VehicleLocalPositionFactGroup.cc
VehicleLocalPositionFactGroup.h
VehicleLocalPositionSetpointFactGroup.cc
Expand Down
49 changes: 49 additions & 0 deletions src/Vehicle/FactGroups/GPSFact.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,55 @@
"name": "count",
"shortDesc": "Sat Count",
"type": "uint32"
},
{
"name": "systemErrors",
"shortDesc": "General System Errors",
"type": "uint32"
},
{
"name": "spoofingState",
"shortDesc": "Signal Spoofing State",
"type": "uint8",
"enumStrings": "Unknown,Not spoofed,Mitigated,Ongoing",
"enumValues": "0,1,2,3",
"decimalPlaces": 0
},
{
"name": "jammingState",
"shortDesc": "Signal Jamming State",
"type": "uint8",
"enumStrings": "Unknown,Not jammed,Mitigated,Ongoing",
"enumValues": "0,1,2,3",
"decimalPlaces": 0
},
{
"name": "authenticationState",
"shortDesc": "Signal Authentication State",
"type": "uint8",
"enumStrings": "Unknown,Initializing,Error,Ok,Disabled",
"enumValues": "0,1,2,3,4",
"decimalPlaces": 0
},
{
"name": "correctionsQuality",
"shortDesc": "Corrections Quality",
"type": "uint8"
},
{
"name": "systemQuality",
"shortDesc": "System Status Quality",
"type": "uint8"
},
{
"name": "gnssSignalQuality",
"shortDesc": "Gnss Signal Quality",
"type": "uint8"
},
{
"name": "postProcessingQuality",
"shortDesc": "Post Processing Quality",
"type": "uint8"
}
]
}
4 changes: 4 additions & 0 deletions src/Vehicle/FactGroups/VehicleGPS2FactGroup.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "VehicleGPS2FactGroup.h"
#include "Vehicle.h"
#include "QGCGeo.h"
#include "development/mavlink_msg_gnss_integrity.h"

#include <QtPositioning/QGeoCoordinate>

Expand All @@ -21,6 +22,9 @@ void VehicleGPS2FactGroup::handleMessage(Vehicle *vehicle, const mavlink_message
case MAVLINK_MSG_ID_GPS2_RAW:
_handleGps2Raw(message);
break;
case MAVLINK_MSG_ID_GNSS_INTEGRITY:
_handleGnssIntegrity(message);
break;
default:
break;
}
Expand Down
5 changes: 4 additions & 1 deletion src/Vehicle/FactGroups/VehicleGPS2FactGroup.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ class VehicleGPS2FactGroup : public VehicleGPSFactGroup

public:
explicit VehicleGPS2FactGroup(QObject *parent = nullptr)
: VehicleGPSFactGroup(parent) {}
: VehicleGPSFactGroup(parent)
{
_gnssIntegrityId = 1;
}

// Overrides from VehicleGPSFactGroup
void handleMessage(Vehicle *vehicle, const mavlink_message_t &message) final;
Expand Down
Loading
Loading