Skip to content

RemoteID/PositionManager: fix GCS Live GNSS position - #14587

Merged
DonLakeFlyer merged 2 commits into
mavlink:masterfrom
julianoes:pr-remoteid-live-gnss-position
Jul 30, 2026
Merged

RemoteID/PositionManager: fix GCS Live GNSS position#14587
DonLakeFlyer merged 2 commits into
mavlink:masterfrom
julianoes:pr-remoteid-live-gnss-position

Conversation

@julianoes

@julianoes julianoes commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Two fixes so the Live GNSS Remote ID GCS position works on Android / in FAA regions.

  • Freshness from local arrival time: _updateLastGCSPositionInfo() stored update.timestamp() (the source's clock), which on Android is offset from the system clock the freshness check compares against — making gcsGPSGood flap at 1 Hz near the 5 s threshold. Now stamps local arrival time.
  • Keep altitude on poor vertical accuracy: altitude was dropped above a strict 10 m, leaving a non-3D coordinate that the FAA path rejects. Now accept altitude on any 3D fix within threshold (or with no reported accuracy), and raise the threshold to 100 m to match the horizontal one.

Comment thread src/PositionManager/PositionManager.h Outdated

static constexpr qreal kMinHorizonalAccuracyMeters = 100.;
static constexpr qreal kMinVerticalAccuracyMeters = 10.;
static constexpr qreal kMinVerticalAccuracyMeters = 100.;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This might need discussion. It solved the problem for me with Herelink but might not be generic.

@codecov

codecov Bot commented Jul 6, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 33.33333% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 32.65%. Comparing base (f29efd3) to head (847f415).
⚠️ Report is 230 commits behind head on master.

Files with missing lines Patch % Lines
src/Vehicle/RemoteIDManager.cc 16.66% 2 Missing and 3 partials ⚠️
src/PositionManager/PositionManager.cpp 50.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           master   #14587      +/-   ##
==========================================
+ Coverage   25.47%   32.65%   +7.18%     
==========================================
  Files         769      784      +15     
  Lines       65912    67556    +1644     
  Branches    30495    31289     +794     
==========================================
+ Hits        16788    22059    +5271     
+ Misses      37285    30634    -6651     
- Partials    11839    14863    +3024     
Flag Coverage Δ
unittests 32.65% <33.33%> (+7.18%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/PositionManager/PositionManager.h 83.33% <100.00%> (+43.33%) ⬆️
src/Vehicle/RemoteIDManager.h 100.00% <ø> (+100.00%) ⬆️
src/PositionManager/PositionManager.cpp 36.43% <50.00%> (+21.17%) ⬆️
src/Vehicle/RemoteIDManager.cc 31.57% <16.66%> (+10.71%) ⬆️

... and 473 files with indirect coverage changes


Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 68787ab...847f415. Read the comment docs.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Build Results

Platform Status

Platform Status Details
Linux Passed View
Windows Passed View
MacOS Passed View
Android Passed View

All builds passed.

Pre-commit

Check Status Details
pre-commit Failed (non-blocking) View

Pre-commit hooks: 0 passed, 0 failed, 0 skipped.

Test Results

linux-coverage-integration: 37 passed, 0 skipped
linux-coverage-unit: 116 passed, 0 skipped
linux-sanitizers-integration: 20 passed, 0 skipped
linux-sanitizers-unit: 116 passed, 0 skipped
Total: 289 passed, 0 skipped

Code Coverage

Coverage Baseline Change
69.4% 69.4% -0.0%

Artifact Sizes

Artifact Size Δ from master
QGroundControl 221.82 MB -4.38 MB (decrease)
QGroundControl-aarch64 179.35 MB +0.01 MB (increase)
QGroundControl-installer-AMD64 137.83 MB -0.46 MB (decrease)
QGroundControl-installer-AMD64-ARM64 80.37 MB -0.21 MB (decrease)
QGroundControl-installer-ARM64 109.12 MB -0.01 MB (decrease)
QGroundControl-linux 87.04 MB -78.74 MB (decrease)
QGroundControl-mac 87.04 MB -0.00 MB (decrease)
QGroundControl-x86_64 191.57 MB +0.00 MB (increase)
Total size decreased by 83.79 MB

Updated: 2026-07-30 01:20:10 UTC • Commit: 847f415 • Triggered by: Android

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR improves the reliability of the Remote ID “Live GNSS” GCS position pipeline, primarily for Android and FAA-region requirements, by addressing timestamp freshness handling and altitude retention for 3D coordinates.

Changes:

  • Stamp GCS GNSS “last update” using local arrival time (UTC) instead of the source-provided timestamp to prevent freshness flapping on platforms with offset provider clocks (e.g. Android).
  • Increase the vertical accuracy gate from 10 m → 100 m and accept altitude when vertical accuracy is acceptable or not reported, to avoid dropping altitude and failing FAA 3D coordinate requirements.
  • Adjust altitude-setting logic in QGCPositionManager to use the new vertical-accuracy behavior.

Reviewed changes

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

File Description
src/Vehicle/RemoteIDManager.cc Changes freshness timestamping for Live GNSS updates to use local arrival time.
src/PositionManager/PositionManager.h Raises the vertical accuracy threshold constant to 100 m.
src/PositionManager/PositionManager.cpp Updates altitude acceptance logic to retain altitude under broader (and Android-compatible) conditions.

Comment thread src/PositionManager/PositionManager.cpp Outdated
Comment on lines 132 to 134
if ((update.coordinate().type() == QGeoCoordinate::Coordinate3D) && verticalAccuracyOk) {
newGCSPosition.setAltitude(update.coordinate().altitude());
}
Comment thread src/Vehicle/RemoteIDManager.cc Outdated
Comment on lines +490 to +495
if (update.isValid()) {
_lastGeoPositionTimeStamp = update.timestamp().toUTC();
// Stamp the local arrival time rather than update.timestamp(). The freshness
// check in _sendSystem() compares this against the local wall clock, and on some
// platforms (e.g. Android) the position source's own timestamp is offset from the
// system clock, which made gcsGPSGood flap green/red even with a healthy fix.
_lastGeoPositionTimeStamp = QDateTime::currentDateTimeUtc();
@DonLakeFlyer

Copy link
Copy Markdown
Contributor

@julianoes Can you look at the review?

@DonLakeFlyer

Copy link
Copy Markdown
Contributor

PR #14587 — Implications of the kMinVerticalAccuracyMeters Change (10 m → 100 m)

PR: RemoteID/PositionManager: fix GCS Live GNSS position

The 10 m → 100 m change in PositionManager.h (together with the accept-when-unreported logic in PositionManager.cpp _positionUpdated()) controls when the GCS device's altitude is copied into gcsPosition. Implications, by consumer:

1. Remote ID (the intended beneficiary)

RemoteIDManager::_sendSystem() broadcasts gcsPosition.altitude() as the operator geodetic altitude in OPEN_DRONE_ID_SYSTEM, and the FAA-region check hard-fails if the coordinate isn't 3D. Phones commonly report vertical accuracy in the 15–50 m range, so the old 10 m gate silently stripped altitude and made gcsGPSGood red in FAA regions. Raising to 100 m fixes that. The message carries no operator-altitude accuracy field, so broadcasting a ±100 m altitude is regulatorily better than broadcasting "unknown" — this is the right trade for Remote ID.

2. Follow Me — the real safety-relevant implication

FollowMe.cc sends gcsCoordinate.altitude() as altMetersAMSL in FOLLOW_TARGET. If the firmware is configured to track target altitude, the vehicle could now be commanded to follow an altitude that's up to ~100 m wrong (and accuracy figures are 1-σ, so occasionally worse), where previously it would have gotten the stale/last-good altitude instead. Mitigating factor: FollowMe forwards the vertical std-dev in pos_std_dev[2], so firmware can de-weight it — but PX4's follow-me altitude modes don't necessarily do so.

3. Set-home-to-GCS

Vehicle::_updateHomepoint() sends MAV_CMD_DO_SET_HOME with gcsPosition.altitude() when "update home position" is enabled. Home altitude is the reference for relative-altitude display and RTL behavior; a 100 m error there is operationally significant. Previously the loose altitude simply wasn't there (the coordinate kept the old/NaN altitude).

4. NTRIP GGA

NTRIPGgaProvider.cc sends the position to the caster for VRS selection; a 100 m altitude error is harmless here.

5. The "no reported accuracy → accept" part is the bigger loosening

Independent of the 10→100 constant, the new verticalAccuracyOk = true default means any 3D fix from a source that doesn't report VerticalAccuracy (e.g. plain NMEA without GST sentences) now passes its altitude straight through, with unknown quality. That's arguably correct for Remote ID, but it applies to Follow Me and set-home too.

6. Minor pre-existing quirk it doesn't change

When the gate does reject, newGCSPosition keeps the previous altitude while lat/lon update — so consumers can get a fresh 2D position paired with a stale altitude. Not made worse by this PR, just noting the gate never clears altitude.

Summary

The constant change is consistent (the horizontal gate is already 100 m via kMinHorizonalAccuracyMeters) and is what makes FAA Remote ID usable on phones. The cost is that Follow Me and update-home-position now consume altitude of much lower guaranteed quality. If that's a concern, the gate could be kept per-consumer (e.g. Remote ID reads altitude regardless, Follow Me keeps a stricter check) rather than loosened globally.

@DonLakeFlyer

Copy link
Copy Markdown
Contributor

2 and 3 are the main problems. 3 was already a bit whacky, but 2 will lead to real problems.

@julianoes

Copy link
Copy Markdown
Contributor Author

Sorry, missed this. I'll revisit it.

@julianoes
julianoes force-pushed the pr-remoteid-live-gnss-position branch from fced979 to b8fa876 Compare July 30, 2026 00:29
@julianoes

Copy link
Copy Markdown
Contributor Author

Reworked: the vertical accuracy gate in QGCPositionManager is untouched, so Follow Me, update-home-position and NTRIP GGA behave exactly as before (points 2, 3, 5).

  • RemoteIDManager::_sendSystem() now takes the altitude straight from the fix for OPEN_DRONE_ID_SYSTEM only — that message has no operator-altitude accuracy field, so a loose altitude beats none, and nothing else consumes it.
  • Freshness now comes from a new QGCPositionManager::gcsPositionTimestamp(), the local arrival time of updates that actually passed the gate. Fixes the Android clock-skew flapping and Copilot's point that a rejected fix used to look fresh while gcsPosition was stale.

Consumers which need to know how fresh gcsPosition is had to fall back on
the timestamp of the raw QGeoPositionInfo, which comes from the position
source's own clock (on Android offset from the system clock) and is stamped
even for updates the accuracy gate rejected.

Record the local arrival time of the updates which actually make it into
gcsPosition and expose it as gcsPositionTimestamp().
Two problems made Live GNSS unusable on Android in FAA regions:

Altitude: QGCPositionManager only copies the altitude into gcsPosition when
the fix reports a vertical accuracy within 10m, which the Android position
source almost never meets. That left a 2D coordinate, so the FAA check in
_sendSystem() hard-failed. Take the altitude straight from the fix for the
Remote ID message rather than loosening the gate for everyone:
OPEN_DRONE_ID_SYSTEM has no accuracy field for the operator altitude, so a
loosely known altitude beats none, whereas the other consumers of
gcsPosition act on it (Follow Me sends it as FOLLOW_TARGET altMetersAMSL,
update-home-position as MAV_CMD_DO_SET_HOME) and keep the strict gate.

Freshness: the check compared the position source's own timestamp against
the local wall clock. On Android the two clocks are offset, and an offset
near the 5s ALLOWED_GPS_DELAY threshold made gcsPositionUsable flap at 1 Hz
despite a healthy fix. It also stamped updates which the accuracy gate
rejected, so a fresh-looking status could accompany stale coordinates. Use
QGCPositionManager::gcsPositionTimestamp() instead, which measures the true
time since the last position that gcsPosition was actually updated from.
@julianoes
julianoes force-pushed the pr-remoteid-live-gnss-position branch from b8fa876 to 847f415 Compare July 30, 2026 00:39
@DonLakeFlyer

Copy link
Copy Markdown
Contributor

Thanks @julianoes

@DonLakeFlyer
DonLakeFlyer merged commit e9d4df9 into mavlink:master Jul 30, 2026
45 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants