From 6592adf597a900b92b02a4609f985ef788e3c8a2 Mon Sep 17 00:00:00 2001 From: Julian Oes Date: Thu, 30 Jul 2026 12:28:02 +1200 Subject: [PATCH 1/2] fix(PositionManager): track arrival time of accepted GCS positions 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(). --- src/PositionManager/PositionManager.cpp | 5 +++++ src/PositionManager/PositionManager.h | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/src/PositionManager/PositionManager.cpp b/src/PositionManager/PositionManager.cpp index 96a07de0f2dc..e69099b40668 100644 --- a/src/PositionManager/PositionManager.cpp +++ b/src/PositionManager/PositionManager.cpp @@ -134,6 +134,10 @@ void QGCPositionManager::_positionUpdated(const QGeoPositionInfo &update) if (_gcsPositionHorizontalAccuracy <= kMinHorizonalAccuracyMeters) { newGCSPosition.setLatitude(update.coordinate().latitude()); newGCSPosition.setLongitude(update.coordinate().longitude()); + // Stamp the local arrival time so consumers can tell how fresh gcsPosition is. + // Updates rejected by the accuracy gate leave the stamp alone, since they leave + // the previous coordinate in place as well. + _gcsPositionTimestamp = QDateTime::currentDateTimeUtc(); } emit gcsPositionHorizontalAccuracyChanged(_gcsPositionHorizontalAccuracy); } @@ -197,6 +201,7 @@ void QGCPositionManager::_setPositionSource(QGCPositionSource source) emit positionInfoUpdated(_geoPositionInfo); _setGCSPosition(QGeoCoordinate()); + _gcsPositionTimestamp = QDateTime(); _setGCSHeading(qQNaN()); diff --git a/src/PositionManager/PositionManager.h b/src/PositionManager/PositionManager.h index 7bc4c749a9dd..a45aa626dae7 100644 --- a/src/PositionManager/PositionManager.h +++ b/src/PositionManager/PositionManager.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -34,6 +35,13 @@ class QGCPositionManager : public QObject QGeoPositionInfo geoPositionInfo() const { return _geoPositionInfo; } QGeoPositionInfoSource::Error gcsPositioningError() const { return _gcsPositioningError; } + /// Local arrival time of the last position update which passed the accuracy gates and was + /// copied into gcsPosition. Invalid until the first such update arrives. This is the local + /// clock rather than the position source's own timestamp, which on some platforms (e.g. + /// Android) is offset from the system clock. + /// @return Arrival time, in UTC, of the last position update applied to gcsPosition. + QDateTime gcsPositionTimestamp() const { return _gcsPositionTimestamp; } + int updateInterval() const { return _updateInterval; } void setNmeaSourceDevice(QIODevice *device); @@ -74,6 +82,7 @@ private slots: QGeoPositionInfoSource::Error _gcsPositioningError = QGeoPositionInfoSource::NoError; QGeoCoordinate _gcsPosition; + QDateTime _gcsPositionTimestamp; qreal _gcsHeading = qQNaN(); qreal _gcsPositionHorizontalAccuracy = std::numeric_limits::infinity(); qreal _gcsPositionVerticalAccuracy = std::numeric_limits::infinity(); From 847f415696dcd22a3c2119b26e3d6fece530b9aa Mon Sep 17 00:00:00 2001 From: Julian Oes Date: Thu, 30 Jul 2026 12:28:03 +1200 Subject: [PATCH 2/2] fix(RemoteIDManager): fix GCS Live GNSS position 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. --- src/Vehicle/RemoteIDManager.cc | 25 +++++++++++++------------ src/Vehicle/RemoteIDManager.h | 2 -- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/Vehicle/RemoteIDManager.cc b/src/Vehicle/RemoteIDManager.cc index c5c7598e0d8f..d367f42f2ece 100644 --- a/src/Vehicle/RemoteIDManager.cc +++ b/src/Vehicle/RemoteIDManager.cc @@ -45,9 +45,6 @@ RemoteIDManager::RemoteIDManager(Vehicle* vehicle) _sendMessagesTimer.setInterval(SENDING_RATE_MSEC); connect(&_sendMessagesTimer, &QTimer::timeout, this, &RemoteIDManager::_sendMessages); - // GCS GPS position updates to track the health of the GPS data - connect(QGCPositionManager::instance(), &QGCPositionManager::positionInfoUpdated, this, &RemoteIDManager::_updateLastGCSPositionInfo); - // Assign vehicle sysid and compid. GCS must target these messages to autopilot, and autopilot will redirect them to RID device _targetSystem = _vehicle->id(); _targetComponent = _vehicle->compId(); @@ -302,11 +299,22 @@ void RemoteIDManager::_sendSystem() QGCPositionManager* positionManager = QGCPositionManager::instance(); QGeoPositionInfo geoPositionInfo = positionManager->geoPositionInfo(); gcsPosition = positionManager->gcsPosition(); + const QDateTime gcsPositionTimestamp = positionManager->gcsPositionTimestamp(); + + // gcsPosition only carries an altitude when the fix's vertical accuracy is within the + // strict gate QGCPositionManager applies for consumers which act on it, such as Follow Me + // and update-home-position. Remote ID mandates an operator altitude in FAA regions and + // OPEN_DRONE_ID_SYSTEM has no accuracy field for it, so a loosely known altitude is better + // than none here: take it straight from the fix whenever the fix reports one. + const QGeoCoordinate fixCoordinate = geoPositionInfo.coordinate(); + if (fixCoordinate.type() == QGeoCoordinate::Coordinate3D) { + gcsPosition.setAltitude(fixCoordinate.altitude()); + } if (!geoPositionInfo.isValid()) { // Only warn if we've previously received a valid fix; otherwise the source is // still initializing and the absence of data is expected, not an error. - _updateGcsPositionStatus(false, _lastGeoPositionTimeStamp.isValid() + _updateGcsPositionStatus(false, gcsPositionTimestamp.isValid() ? QStringLiteral("GCS GPS data is not valid.") : QString()); } else if (positionManager->gcsPositioningError() != QGeoPositionInfoSource::NoError && positionManager->gcsPositioningError() != QGeoPositionInfoSource::UpdateTimeoutError) { @@ -316,7 +324,7 @@ void RemoteIDManager::_sendSystem() } else if (_settings->region()->rawValue().toInt() == static_cast(RemoteIDSettings::RegionOperation::FAA) && gcsPosition.type() != QGeoCoordinate::Coordinate3D) { // FAA requires altitude data, or else the GPS data is not good _updateGcsPositionStatus(false, "GCS GPS data error: Altitude data is mandatory for FAA regions."); - } else if (_lastGeoPositionTimeStamp.msecsTo(QDateTime::currentDateTime().currentDateTimeUtc()) > ALLOWED_GPS_DELAY) { + } else if (!gcsPositionTimestamp.isValid() || (gcsPositionTimestamp.msecsTo(QDateTime::currentDateTimeUtc()) > ALLOWED_GPS_DELAY)) { _updateGcsPositionStatus(false, "GCS GPS data is older than 5 seconds"); } else { _updateGcsPositionStatus(true); @@ -399,10 +407,3 @@ void RemoteIDManager::setEmergency(bool declare) qCDebug(RemoteIDManagerLog) << ( declare ? "Emergency declared." : "Emergency cleared."); } - -void RemoteIDManager::_updateLastGCSPositionInfo(QGeoPositionInfo update) -{ - if (update.isValid()) { - _lastGeoPositionTimeStamp = update.timestamp().toUTC(); - } -} diff --git a/src/Vehicle/RemoteIDManager.h b/src/Vehicle/RemoteIDManager.h index db25bdf2e762..5c8d8b22f1f2 100644 --- a/src/Vehicle/RemoteIDManager.h +++ b/src/Vehicle/RemoteIDManager.h @@ -59,7 +59,6 @@ class RemoteIDManager : public QObject private slots: void _odidTimeout(); void _sendMessages(); - void _updateLastGCSPositionInfo(QGeoPositionInfo update); private: void _handleArmStatus(mavlink_message_t& message); @@ -94,7 +93,6 @@ private slots: bool _vehicleReportsBasicIDMissing; bool _emergencyDeclared; - QDateTime _lastGeoPositionTimeStamp; int _targetSystem; int _targetComponent;