-
-
Notifications
You must be signed in to change notification settings - Fork 4.5k
Add GPS resilience monitoring with GNSS_INTEGRITY message #13812
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds GPS resilience monitoring to QGroundControl, enabling the display of jamming, spoofing, and authentication status from MAVLink GNSS_INTEGRITY messages. The implementation includes a dual-GPS aggregation system that merges integrity data from GPS1 and GPS2 using worst-case logic, a layered toolbar indicator with shield and interference wave icons, and a per-GPS drill-down panel for detailed status information.
Key Changes
- Created
VehicleGPSAggregateFactGroupto aggregate integrity data from dual GPS receivers using worst-case merging logic - Extended
VehicleGPSFactGroupandVehicleGPS2FactGroupto handle GNSS_INTEGRITY MAVLink messages with new facts for jamming, spoofing, authentication, and quality metrics - Added
GPSResilienceIndicator.qmltoolbar component with layered icons and color-coded status display
Reviewed changes
Copilot reviewed 15 out of 17 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Vehicle/Vehicle.h/cc | Integrated VehicleGPSAggregateFactGroup into Vehicle fact group hierarchy |
| src/Vehicle/FactGroups/VehicleGPSFactGroup.h/cc | Added GNSS_INTEGRITY message handling and 8 new integrity-related facts |
| src/Vehicle/FactGroups/VehicleGPSAggregateFactGroup.h/cc | New class implementing dual-GPS aggregation with worst-case merging and stale data detection |
| src/Vehicle/FactGroups/VehicleGPS2FactGroup.h/cc | Configured GPS2 to handle GNSS_INTEGRITY messages with ID=1 |
| src/Vehicle/FactGroups/GPSFact.json | Added metadata for 8 new GPS integrity facts with enum definitions |
| src/UI/toolbar/GPSResilienceIndicator.qml | New toolbar indicator with layered shield/interference icons and drill-down panel |
| src/QmlControls/GPSIndicatorPage.qml | Added GPS error field with system error descriptions |
| src/UI/toolbar/Images/*.svg | Added authentication shield and interference wave SVG icons |
| src/Vehicle/FactGroups/CMakeLists.txt | Added VehicleGPSAggregateFactGroup source files |
| src/UI/toolbar/CMakeLists.txt | Added GPSResilienceIndicator.qml to toolbar module |
| qgcimages.qrc | Registered new GPS authentication and interference SVG resources |
| src/FirmwarePlugin/FirmwarePlugin.cc | Added GPSResilienceIndicator to default toolbar indicators list |
| _isStaleFact.setRawValue(true); | ||
|
|
||
| _staleTimer.setSingleShot(true); | ||
| _staleTimer.setInterval(5000); |
Copilot
AI
Dec 25, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The stale timer interval is hardcoded to 5000 milliseconds. Consider defining this as a named constant (e.g., static constexpr int GNSS_INTEGRITY_STALE_TIMEOUT_MS = 5000;) to improve code readability and make it easier to adjust if needed.
| 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"); | ||
| } |
Copilot
AI
Dec 25, 2025
There was a problem hiding this comment.
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.
| 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"); | |
| } |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
f722357 to
20b869e
Compare
|
you can ignore the pre-commit for now. Is this ready? |
|
Hi @HTRamsey. Yes, I think it is ready for review. |
|
Gotcha, in general it look good to me. We'll wait for @DonLakeFlyer to check out the QML and your attached picture |
dakejahl
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we really want to aggregate the data? I would probably try and use the FactGroupListModel so that you can show the data from both GPS if available rather than aggregating and only showing the worst.
| 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"); | ||
| } |
There was a problem hiding this comment.
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
| case -1: return -1; | ||
| case 0: return 0; | ||
| case 4: return 1; | ||
| case 1: return 2; | ||
| case 3: return 3; | ||
| case 2: return 4; | ||
| default: return -1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these should be defined somewhere, not magic numbers
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the catch, addressed this in commit 507556e
| , _isStaleFact (0, "isStale", FactMetaData::valueTypeBool, this) | ||
| { | ||
| _spoofingStateFact.setEnumInfo( | ||
| QStringList({ "Unknown", "Not spoofed", "Mitigated", "Ongoing" }), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is already done during the metadata parsing, why do it here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When I added it initially I wasn’t loading the parent metadata, but it makes more sense to rely on it. I dropped the redundant setEnumInfo in commit 34a7f52
| #include "Vehicle.h" | ||
| #include "QGCGeo.h" | ||
| #include "QGCLoggingCategory.h" | ||
| #include "development/mavlink_msg_gnss_integrity.h" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So QGC is indeed building and shipping development messages? I thought recently we decided not to do that anymore due to the standard modes fiasco? Should we put the mavlink development message features behind a preprocessor guard for debug builds only?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it was like that, and then we needed development for some particular reason that I can't recall right now
| : VehicleGPSFactGroup(parent) {} | ||
| : VehicleGPSFactGroup(parent) | ||
| { | ||
| _setGnssIntegrityContext(1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what's this supposed to do?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted each GPS fact group to filter GNSS_INTEGRITY msgs only for its corresponding receiver (gps1/gps2). That’s why I added gnssIntegrityId, which is checked in _handleGnssIntegrity before setting values. Earlier I used a helper _setGnssIntegrityContext(), but it was simpler to set the ID directly in the constructor. This is in commit b9c531e
This aggregation strategy was actually recommended in earlier discussions (see PR #13009). The idea is to use the toolbar icon for a "worst-case" summary—if either GPS has an issue, the pilot sees it immediately without having to mentally parse two separate statuses. Since we still show the full breakdown for GPS 1 and GPS 2 in the expanded details view, we get the immediate warning on the main screen with the specific details just a click away |
Description
Adds GPS resilience monitoring to display jamming, spoofing, and authentication status from MAVLink GNSS_INTEGRITY messages.
Based on: PR #13009
Key Features:
Type of Change
New feature (non-breaking change that adds functionality)
Related Work
MAVLink Message Definition (development. xml): mavlink/mavlink#2110
Message value naming updated: mavlink/mavlink#2385
PX4 implementation using GNSS_INTEGRITY: PX4/PX4-Autopilot#25012 (merged)
ArduPilot implementation using GNSS_INTEGRITY: ArduPilot/ardupilot#31040 (in review)
Hardware Tested:
Flight Controller: CubeOrange+
Flight Stacks: ArduPilot & PX4 implementations
GNSS Receiver: Septentrio Mosaic X5
Platform: Linux