QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
NTRIPGgaProvider.cc
Go to the documentation of this file.
1#include "NTRIPGgaProvider.h"
2
3#include <QtCore/QDateTime>
4
5#include "Fact.h"
6#include "FactGroup.h"
7#ifndef QGC_NO_SERIAL_LINK
8#include "GPSManager.h"
9#include "GPSRtk.h"
10#endif
11#include "MultiVehicleManager.h"
12#include "NMEAUtils.h"
13#include "NTRIPSettings.h"
14#include "NTRIPTransport.h"
15#include "PositionManager.h"
16#include "Vehicle.h"
17
18namespace {
19
23bool isSaneCoord(double lat, double lon)
24{
25 return qIsFinite(lat) && qIsFinite(lon) && !(lat == 0.0 && lon == 0.0) && qAbs(lat) <= 90.0 && qAbs(lon) <= 180.0;
26}
27
28PositionResult getVehicleGPSPosition()
29{
31 if (!mvm)
32 return {};
33 Vehicle* veh = mvm->activeVehicle();
34 if (!veh)
35 return {};
36
37 FactGroup* gps = veh->gpsFactGroup();
38 if (!gps)
39 return {};
40
41 Fact* latF = gps->getFact(QStringLiteral("lat"));
42 Fact* lonF = gps->getFact(QStringLiteral("lon"));
43 if (!latF || !lonF)
44 return {};
45
46 const double lat = latF->rawValue().toDouble();
47 const double lon = lonF->rawValue().toDouble();
48
49 if (isSaneCoord(lat, lon)) {
50 return {QGeoCoordinate(lat, lon, veh->coordinate().altitude()), QStringLiteral("Vehicle GPS")};
51 }
52 return {};
53}
54
55PositionResult getVehicleEKFPosition()
56{
58 if (!mvm)
59 return {};
60 Vehicle* veh = mvm->activeVehicle();
61 if (!veh)
62 return {};
63
64 const QGeoCoordinate coord = veh->coordinate();
65 if (coord.isValid() && isSaneCoord(coord.latitude(), coord.longitude())) {
66 return {coord, QStringLiteral("Vehicle EKF")};
67 }
68 return {};
69}
70
71#ifndef QGC_NO_SERIAL_LINK
72PositionResult getRTKBasePosition()
73{
74 GPSManager* gpsManager = GPSManager::instance();
75 if (!gpsManager)
76 return {};
77 GPSRtk* rtk = gpsManager->gpsRtk();
78 if (!rtk)
79 return {};
80
81 FactGroup* rtkGroup = rtk->gpsRtkFactGroup();
82 if (!rtkGroup)
83 return {};
84
85 Fact* validF = rtkGroup->getFact(QStringLiteral("valid"));
86 if (!validF || !validF->rawValue().toBool())
87 return {};
88
89 Fact* latF = rtkGroup->getFact(QStringLiteral("currentLatitude"));
90 Fact* lonF = rtkGroup->getFact(QStringLiteral("currentLongitude"));
91 if (!latF || !lonF)
92 return {};
93
94 Fact* altF = rtkGroup->getFact(QStringLiteral("currentAltitude"));
95 const double lat = latF->rawValue().toDouble();
96 const double lon = lonF->rawValue().toDouble();
97 const double alt = altF ? altF->rawValue().toDouble() : 0.0;
98
99 if (isSaneCoord(lat, lon)) {
100 return {QGeoCoordinate(lat, lon, alt), QStringLiteral("RTK Base")};
101 }
102 return {};
103}
104#endif // QGC_NO_SERIAL_LINK
105
106PositionResult getGCSPosition()
107{
109 if (!posMgr)
110 return {};
111
112 const QGeoCoordinate coord = posMgr->gcsPosition();
113 if (coord.isValid() && isSaneCoord(coord.latitude(), coord.longitude())) {
114 return {coord, QStringLiteral("GCS Position")};
115 }
116 return {};
117}
118
119} // anonymous namespace
120
121NTRIPGgaProvider::NTRIPGgaProvider(QObject* parent) : QObject(parent)
122{
123 _timer.setInterval(_normalInterval);
124 connect(&_timer, &QChronoTimer::timeout, this, &NTRIPGgaProvider::_sendGGA);
125}
126
128{
129 // Cache the user-selected source and interval so the hot path (_sendGGA)
130 // avoids a SettingsManager::instance()->ntripSettings()->...->rawValue()
131 // chain per tick.
132 if (!settings) {
133 return;
134 }
135
136 auto* sourceFact = settings->ntripGgaPositionSource();
137 auto refreshSource = [this, sourceFact]() {
138 _cachedSource = static_cast<PositionSource>(sourceFact->rawValue().toUInt());
139 };
140 refreshSource();
141 connect(sourceFact, &Fact::rawValueChanged, this, refreshSource);
142
143 auto* intervalFact = settings->ntripGgaIntervalSec();
144 auto refreshInterval = [this, intervalFact]() {
145 const uint seconds = intervalFact->rawValue().toUInt();
146 // Guard against 0 from a stale config — fall back to the default.
147 _normalInterval =
148 (seconds > 0) ? std::chrono::milliseconds{static_cast<qint64>(seconds) * 1000} : kDefaultInterval;
149 if (_retryPhase == RetryPhase::Normal) {
150 _timer.setInterval(_normalInterval);
151 }
152 };
153 refreshInterval();
154 connect(intervalFact, &Fact::rawValueChanged, this, refreshInterval);
155}
156
158{
159 _providers[source] = std::move(provider);
160}
161
163{
164 _transport = transport;
165 _fastRetryCount = 0;
166 _clearSource();
167 _setRetryPhase(RetryPhase::Fast);
168 _sendGGA();
169 _timer.start();
170}
171
173{
174 _timer.stop();
175 _transport = nullptr;
176 _clearSource();
177}
178
179void NTRIPGgaProvider::_setRetryPhase(RetryPhase phase)
180{
181 _retryPhase = phase;
182 _timer.setInterval(phase == RetryPhase::Fast ? kFastRetryInterval : _normalInterval);
183}
184
185void NTRIPGgaProvider::_clearSource()
186{
187 if (_source.isEmpty()) {
188 return;
189 }
190 _source.clear();
191 emit sourceChanged(_source);
192}
193
194void NTRIPGgaProvider::_sendGGA()
195{
196 if (!_transport) {
197 return;
198 }
199
200 _ensureDefaultProviders();
201
202 const auto position = _getBestPosition();
203
204 if (!position.isValid()) {
205 if (++_fastRetryCount >= 5 && _retryPhase == RetryPhase::Fast) {
206 _setRetryPhase(RetryPhase::Normal);
207 }
208 return;
209 }
210
211 _fastRetryCount = 0;
212 if (_retryPhase != RetryPhase::Normal) {
213 _setRetryPhase(RetryPhase::Normal);
214 }
215
216 double alt_msl = position.coordinate.altitude();
217 if (!qIsFinite(alt_msl)) {
218 alt_msl = 0.0;
219 }
220
221 const QByteArray gga = NMEAUtils::makeGGA(position.coordinate, alt_msl);
222 _transport->sendNMEA(gga);
223
224 if (!position.source.isEmpty() && position.source != _source) {
225 _source = position.source;
226 emit sourceChanged(_source);
227 }
228}
229
230void NTRIPGgaProvider::_ensureDefaultProviders()
231{
232 // Lazily install the singleton-reaching default providers so construction
233 // touches no singletons and tests can override any source via
234 // setPositionProvider() before the first GGA tick. Only absent slots are
235 // filled, so partial overrides are preserved.
236 static const std::pair<PositionSource, PositionProvider> kDefaults[] = {
237 {PositionSource::VehicleGPS, &getVehicleGPSPosition},
238 {PositionSource::VehicleEKF, &getVehicleEKFPosition},
239#ifndef QGC_NO_SERIAL_LINK
240 {PositionSource::RTKBase, &getRTKBasePosition},
241#endif
242 {PositionSource::GCSPosition, &getGCSPosition},
243 };
244 for (const auto& [source, provider] : kDefaults) {
245 if (!_providers.contains(source)) {
246 _providers.insert(source, provider);
247 }
248 }
249}
250
251PositionResult NTRIPGgaProvider::_getBestPosition() const
252{
253 const PositionSource source = _cachedSource;
254
255 // If a specific source is requested, try only that one
256 if (source != PositionSource::Auto) {
257 auto it = _providers.find(source);
258 if (it != _providers.end()) {
259 return it.value()();
260 }
261 return {};
262 }
263
264 // Auto: try each provider in priority order
265 static constexpr PositionSource kPriority[] = {
270 };
271
272 for (PositionSource s : kPriority) {
273 auto it = _providers.find(s);
274 if (it != _providers.end()) {
275 auto result = it.value()();
276 if (result.isValid()) {
277 return result;
278 }
279 }
280 }
281
282 return {};
283}
Used to group Facts together into an object hierarachy.
Definition FactGroup.h:16
Q_INVOKABLE Fact * getFact(const QString &name) const
Definition FactGroup.cc:72
A Fact is used to hold a single value within the system.
Definition Fact.h:17
void rawValueChanged(const QVariant &value)
QVariant rawValue() const
Value after translation.
Definition Fact.h:85
GPSRtk * gpsRtk()
Definition GPSManager.h:17
static GPSManager * instance()
Definition GPSManager.cc:23
FactGroup * gpsRtkFactGroup()
Definition GPSRtk.cc:160
static MultiVehicleManager * instance()
Vehicle * activeVehicle() const
static constexpr std::chrono::milliseconds kDefaultInterval
Fallback when no NTRIPSettings are available (unit tests, early init).
void sourceChanged(const QString &source)
NTRIPGgaProvider(QObject *parent=nullptr)
std::function< PositionResult()> PositionProvider
static constexpr std::chrono::milliseconds kFastRetryInterval
void init(NTRIPSettings *settings)
void start(NTRIPTransport *transport)
void setPositionProvider(PositionSource source, PositionProvider provider)
static QGCPositionManager * instance()
QGeoCoordinate gcsPosition() const
QGeoCoordinate coordinate()
Definition Vehicle.h:413
FactGroup * gpsFactGroup()
Definition Vehicle.cc:411
QByteArray makeGGA(const QGeoCoordinate &coord, double altitudeMsl, int fixQuality, int numSatellites)
Build a GGA sentence from a coordinate and altitude.
Definition NMEAUtils.cc:68