QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
NTRIPGgaReporter.cc
Go to the documentation of this file.
1#include "NTRIPGgaReporter.h"
2
3#include <utility>
4
5#include <QtCore/QDateTime>
6#include <QtCore/QMetaEnum>
7#include <QtCore/qnumeric.h>
8
9#include "NMEASentence.h"
10#include "NTRIPTransport.h"
11#include "QGCLoggingCategory.h"
12#include "RuntimeScheduler.h"
13
14QGC_LOGGING_CATEGORY(NTRIPGgaReporterLog, "GPS.NTRIPGgaProvider")
15
16namespace {
17QString sourceName(NTRIPGgaReporter::PositionSource source)
18{
19 return QString::fromLatin1(
20 QMetaEnum::fromType<NTRIPGgaReporter::PositionSource>().valueToKey(static_cast<int>(source)));
21}
22
23unsigned ggaQuality(GPSFixQuality quality)
24{
25 using Quality = GPSFixQuality;
26 switch (quality) {
27 case Quality::NoFix:
29 case Quality::Differential:
31 case Quality::RTKFloat:
33 case Quality::RTKFixed:
35 case Quality::Extrapolated:
36 case Quality::Unknown:
38 case Quality::Fix2D:
39 case Quality::Fix3D:
41 }
43}
44
45bool reportable(const std::optional<GPSObservation>& observation)
46{
47 if (!observation || observation->altitudeDatum != GPSAltitudeDatum::MeanSeaLevel ||
48 observation->fixQuality == GPSFixQuality::NoFix) {
49 return false;
50 }
51 const QGeoCoordinate coordinate = observation->position.coordinate();
52 return coordinate.isValid() && qIsFinite(coordinate.altitude());
53}
54} // namespace
55
57 : QObject(parent)
58 , _scheduler(RuntimeScheduler::orDefault(scheduler, this))
59 , _ggaTask(_scheduler, this)
60{}
61
62QString NTRIPGgaReporter::_sourceLabel(PositionSource source)
63{
64 switch (source) {
66 return tr("Vehicle GPS");
68 return tr("Vehicle EKF");
70 return tr("RTK Receiver");
72 return tr("GCS Position");
74 break;
75 }
76 return {};
77}
78
80{
81 _requestedSource = configuration.source;
82 const auto previousInterval = _normalInterval;
83 _normalInterval = configuration.interval.count() > 0 ? configuration.interval : DEFAULT_INTERVAL;
84 if (!_awaitingFirstGga && previousInterval != _normalInterval && _transport) {
85 _scheduleNextGGA();
86 }
87}
88
90{
91 _providers[source] = std::move(provider);
92}
93
95{
96 _ggaTask.cancel();
97 _transport = transport;
98 _loggedSelection.reset();
99 _clearSource();
100 if (!_transport) {
101 return;
102 }
103 _awaitingFirstGga = true;
104 _sendGGA();
105 _scheduleNextGGA();
106}
107
109{
110 _ggaTask.cancel();
111 _transport = nullptr;
112 _clearSource();
113}
114
115void NTRIPGgaReporter::_scheduleNextGGA()
116{
117 if (!_transport) {
118 _ggaTask.cancel();
119 return;
120 }
121 // Rescheduling replaces the pending send, and start()/stop() cancel it, so a send never outlives its transport.
122 _ggaTask.schedule(_currentInterval(), [this]() {
123 _sendGGA();
124 _scheduleNextGGA();
125 });
126}
127
128std::chrono::milliseconds NTRIPGgaReporter::_currentInterval() const
129{
130 return _awaitingFirstGga ? FAST_RETRY_INTERVAL : _normalInterval;
131}
132
133void NTRIPGgaReporter::_clearSource()
134{
135 if (_source.isEmpty()) {
136 return;
137 }
138 _source.clear();
139 emit sourceChanged(_source);
140}
141
142void NTRIPGgaReporter::_sendGGA()
143{
144 if (!_transport) {
145 return;
146 }
147 const auto requested = _requestedSource;
148 const auto selection = _getBestPosition(requested);
149 _logSelection(requested, selection);
150 if (!selection.position) {
151 _clearSource();
152 return;
153 }
154
155 _awaitingFirstGga = false;
156
157 const GPSObservation& position = *selection.position;
158 const QGeoCoordinate coordinate = position.position.coordinate();
159 const NMEA::GGA fix{
160 .latitude = coordinate.latitude(),
161 .longitude = coordinate.longitude(),
162 .altitude = coordinate.altitude(),
163 .hdop = position.horizontalDop && qIsFinite(*position.horizontalDop) && *position.horizontalDop >= 0
164 ? *position.horizontalDop
165 : qQNaN(),
166 .quality = ggaQuality(position.fixQuality),
167 .satellitesUsed = position.satellitesUsed && *position.satellitesUsed >= 0
168 ? std::optional<unsigned>(static_cast<unsigned>(*position.satellitesUsed))
169 : std::nullopt,
170 };
171 const QByteArray gga = NMEAUtils::makeGGA(fix, QDateTime::currentDateTimeUtc().time());
172 _transport->sendNMEA(gga);
173 if (const QString label = _sourceLabel(selection.source); label != _source) {
174 _source = label;
175 emit sourceChanged(_source);
176 }
177}
178
179NTRIPGgaReporter::SelectedPosition NTRIPGgaReporter::_getBestPosition(PositionSource requested) const
180{
181 if (requested != PositionSource::Auto) {
182 const auto provider = _providers.value(requested);
183 auto position = provider ? provider() : std::nullopt;
184 return {reportable(position) ? std::move(position) : std::nullopt, requested};
185 }
186
187 static constexpr PositionSource kPriority[] = {
192 };
193 for (PositionSource source : kPriority) {
194 if (const auto provider = _providers.value(source)) {
195 if (auto position = provider(); reportable(position)) {
196 return {std::move(position), source};
197 }
198 }
199 }
200 return {};
201}
202
203void NTRIPGgaReporter::_logSelection(PositionSource requested, const SelectedPosition& selection)
204{
205 const auto current = std::make_tuple(requested, selection.source, selection.position.has_value());
206 if (std::exchange(_loggedSelection, current) == current) {
207 return;
208 }
209 if (!selection.position) {
210 qCDebug(NTRIPGgaReporterLog).noquote()
211 << QStringLiteral("GGA source selection: requested=%1 no eligible source").arg(sourceName(requested));
212 return;
213 }
214 const bool fallback = requested == PositionSource::Auto && selection.source != PositionSource::VehicleGPS;
215 const QString fallbackText = fallback ? QStringLiteral("yes") : QStringLiteral("no");
216 qCDebug(NTRIPGgaReporterLog).noquote()
217 << QStringLiteral("GGA source selection: requested=%1 provider=%2 fallback=%3")
218 .arg(sourceName(requested), sourceName(selection.source), fallbackText);
219}
GPSFixQualities::GPSFixQuality GPSFixQuality
#define QGC_LOGGING_CATEGORY(name, categoryStr)
std::function< std::optional< GPSObservation >()> PositionProvider
void start(NTRIPTransport *transport)
static constexpr std::chrono::milliseconds FAST_RETRY_INTERVAL
void sourceChanged(const QString &source)
NTRIPGgaReporter(QObject *parent=nullptr, RuntimeScheduler *scheduler=nullptr)
static constexpr std::chrono::milliseconds DEFAULT_INTERVAL
Interval of a default configuration, and of one whose interval is not positive.
void setPositionProvider(PositionSource source, PositionProvider provider)
void configure(const Configuration &configuration)
bool schedule(std::chrono::microseconds delay, RuntimeScheduler::Callback callback)
GPSPositionReport position(const GPSDecodedPosition &source, const GPSIntegrityReport &diagnostic, uint64_t nowUs)
QByteArray makeGGA(const NMEA::GGA &fix, const QTime &utc)
constexpr unsigned RTK_FLOAT
constexpr unsigned DIFFERENTIAL
constexpr unsigned ESTIMATED
constexpr unsigned RTK_FIXED
constexpr unsigned GPS
constexpr unsigned INVALID
std::optional< double > coordinate(std::string_view field, std::string_view hemisphere, bool latitude)
std::optional< GGA > gga(const Sentence &input)
Quality zero is a valid fix-loss observation with empty coordinates and optional hemisphere fields.
Receiver-independent data. Unknown metadata remains absent, never a manufactured zero.
double latitude
std::chrono::milliseconds interval