QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
GPSDriver.cc
Go to the documentation of this file.
1#include "GPSDriver.h"
2
3#include <algorithm>
4#include <chrono>
5#include <optional>
6#include <span>
7#include <utility>
8#include <variant>
9
10#include <QtCore/QDeadlineTimer>
11#include <QtCore/QMetaEnum>
12
13#include "GPSDecodedData.h"
14#include "GPSProtocolRuntime.h"
16#include "GPSReceiverDetector.h"
17#include "GPSReceiverFamilies.h"
18#include "GPSTransport.h"
19#include "MonotonicClock.h"
20#include "QGCLoggingCategory.h"
21
22QGC_LOGGING_CATEGORY(GPSDriverLog, "GPS.GPSDriver")
23
24namespace {
25
27QString mismatchHint(const GPSReceiverSignatures& signatures, GPSType configured)
28{
29 const auto& match = signatures.match();
30 if (!match || match->family->type == configured) {
31 return {};
32 }
33 QString evidence = match->evidence;
34 evidence[0] = evidence[0].toUpper();
35 return QStringLiteral("%1 were received; this looks like a %2 receiver")
36 .arg(evidence, gpsReceiverName(match->family->type));
37}
38
39} // namespace
40
42{
45 std::optional<GPSSatelliteReport> lastSatellites;
46 std::unique_ptr<GPSProtocolRuntime> runtime;
48 std::unique_ptr<GPSReceiverSignatures> signatures;
49 std::vector<GPSConfigurationEvidence> evidence;
51 bool consentRequired = false;
52 bool configuring = false;
53
55 {
56 GPSReceiveUpdates updates;
57 bool usefulData = false;
58 bool activity = false;
59 };
60
62};
63
65 GPSClock clock)
66 : _type(type)
67 , _transport(transport)
68 , _config(config)
69 , _sinks(std::move(sinks))
70 , _clock(std::move(clock))
71 , _state(std::make_unique<State>())
72{}
73
74GPSDriver::~GPSDriver() = default;
75
76const std::vector<GPSConfigurationEvidence>& GPSDriver::configurationEvidence() const
77{
78 return _state->evidence;
79}
80
81const QString& GPSDriver::configurationError() const
82{
83 return _state->configurationError;
84}
85
87{
88 return _state->consentRequired;
89}
90
92{
93 return _state->runtime ? _state->runtime->identity() : QString();
94}
95
97{
98 _state = std::make_unique<State>();
99 if (const QString error = gpsReceiverConfigError(_type, _config); !error.isEmpty()) {
100 _state->configurationError = error;
101 qCWarning(GPSDriverLog) << error;
102 return false;
103 }
104 if (_type != GPSType::passive) {
105 const auto& mode = _config.base.mode;
106 if (std::holds_alternative<GPSBaseStationConfig::Fixed>(mode)) {
107 qCDebug(GPSDriverLog) << "Fixed base configured";
108 } else if (const auto* averaging = std::get_if<GPSBaseStationConfig::ReceiverAveraging>(&mode)) {
109 qCDebug(GPSDriverLog) << "Receiver-managed averaging maximum duration (s):"
110 << averaging->maximumDuration.count();
111 } else if (const auto* survey = std::get_if<GPSBaseStationConfig::SurveyIn>(&mode)) {
112 qCDebug(GPSDriverLog) << "Survey-in accuracy (m):" << survey->accuracyMeters
113 << "minimum duration (s):" << survey->duration.count();
114 }
115 }
116
117 GPSRuntimeIO io;
118 io.clock = _clock;
119 io.read = [this](std::span<uint8_t> bytes, GPSDeadline deadline) {
120 const auto result = _transport.read(bytes, deadline.remaining(_clock.nowUs()));
121 const bool data = result.status == GPSReadStatus::Data && result.bytesRead > 0 &&
122 static_cast<size_t>(result.bytesRead) <= bytes.size();
123 _state->cycle.activity |= data;
124 if (data && _state->signatures && _state->configuring) {
125 _state->signatures->push(bytes.first(static_cast<size_t>(result.bytesRead)));
126 }
127 return result;
128 };
129 io.write = [this](std::span<const uint8_t> bytes, GPSDeadline deadline) {
130 // Do not submit another part of a multipart command after its absolute deadline.
131 const auto remaining = deadline.remaining(_clock.nowUs());
132 if (remaining == std::chrono::milliseconds::zero()) {
134 }
135 // The transport waits in real time; only the remaining time carries over from the driver's clock.
136 const QDeadlineTimer transportDeadline = deadline.untilUs == UINT64_MAX
137 ? QDeadlineTimer(QDeadlineTimer::Forever, Qt::PreciseTimer)
138 : QDeadlineTimer(remaining, Qt::PreciseTimer);
139 return _transport.write(bytes, transportDeadline);
140 };
141 io.setBaudrate = [this](unsigned baud) {
142 if (_transport.setBaudrate(baud)) {
144 }
146 };
147 io.cancelToken = _transport.cancelToken();
148 GPSRuntimeObserver observer;
149 observer.commandFinished = [this](const GPSConfigurationEvidence& result) {
150 if (_state->configuring) {
151 _state->evidence.push_back(result);
152 }
153 };
154 observer.decoded = [this](const GPSEventBatch& batch) { _publish(batch); };
155
156 unsigned baudrate = _config.baudRate ? _config.baudRate : _transport.fixedBaudrate();
157 if (_config.baudRate && _transport.fixedBaudrate() && _config.baudRate != _transport.fixedBaudrate()) {
158 _state->configurationError = QStringLiteral("Selected baud rate differs from the transport's fixed baud rate");
159 qCWarning(GPSDriverLog) << _state->configurationError;
160 return false;
161 }
162 if (_type == GPSType::automatic) {
163 return _configureDetected(std::move(io), std::move(observer), baudrate);
164 }
165 const GPSReceiverFamily* family = gpsReceiverFamily(_type);
166 if (!family) {
167 _state->configurationError =
168 QStringLiteral("Unsupported GPS type: %1")
169 .arg(QLatin1StringView(QMetaEnum::fromType<GPSType>().valueToKey(static_cast<int>(_type))));
170 qCWarning(GPSDriverLog) << "Unsupported GPS type:" << _type;
171 return false;
172 }
173 // Without a selected rate or a fixed link rate, the family may prefer one to detection.
174 if (family->autoBaudRate && !baudrate) {
175 baudrate = family->autoBaudRate;
176 }
177 if (_type != GPSType::passive) {
178 _state->signatures = std::make_unique<GPSReceiverSignatures>(gpsReceiverFamilies());
179 }
180 return _configureFamily(*family, std::move(io), std::move(observer),
181 {.base = _config.base, .allowPersistentChanges = _config.allowPersistentChanges}, baudrate,
182 {});
183}
184
185bool GPSDriver::_configureDetected(GPSRuntimeIO io, GPSRuntimeObserver observer, unsigned baudrate)
186{
187 _state->configuring = true;
188 const GPSReceiverDetection detection =
190 _state->configuring = false;
191 if (!detection.found()) {
192 _state->configurationError = !detection.errorDetail.isEmpty() ? detection.errorDetail
194 ? QStringLiteral("Receiver detection cancelled")
195 : QStringLiteral("No supported receiver was identified");
196 qCWarning(GPSDriverLog) << "Receiver detection failed:" << _state->configurationError;
197 return false;
198 }
199 const GPSType detected = detection.family->type;
200 const QString name = gpsReceiverName(detected);
201 if (_sinks.onReceiverDetected) {
202 _sinks.onReceiverDetected(detected);
203 }
204 bool compactFallback = false;
205 const GPSReceiverConfig config = gpsReceiverConfigForDetected(detected, _config, &compactFallback);
206 if (compactFallback) {
207 qCWarning(GPSDriverLog).noquote()
208 << QStringLiteral("Detected %1 receiver has no compact (MSM4) RTCM option; it sends its standard set")
209 .arg(name);
210 }
211 if (const QString error = gpsDetectedReceiverConfigError(detected, config); !error.isEmpty()) {
212 _state->configurationError = error;
213 qCWarning(GPSDriverLog).noquote() << error;
214 return false;
215 }
216 // Without a fixed or selected rate, the family's baud search starts at the detected rate and moves the link to
217 // the rate the family prefers.
218 return _configureFamily(
219 *detection.family, std::move(io), std::move(observer),
220 {.base = config.base, .allowPersistentChanges = config.allowPersistentChanges, .detectedBaud = detection.baud},
221 baudrate, QStringLiteral("Detected %1 receiver at %2 baud").arg(name).arg(detection.baud));
222}
223
224bool GPSDriver::_configureFamily(const GPSReceiverFamily& family, GPSRuntimeIO io, GPSRuntimeObserver observer,
225 const GPSConfig& config, unsigned baudrate, const QString& detected)
226{
227 _state->runtime = std::make_unique<GPSProtocolRuntime>(family, std::move(io), std::move(observer));
228 _state->configuring = true;
229 const bool configured = _state->runtime->configure(config, baudrate);
230 _state->configuring = false;
231 const auto signatures = std::exchange(_state->signatures, nullptr);
232 if (!configured) {
233 QString error = _state->runtime->errorDetail();
234 if (error.isEmpty()) {
235 error = QStringLiteral("Receiver configuration failed");
236 }
237 if (!detected.isEmpty()) {
238 error = QStringLiteral("%1: %2").arg(detected, error);
239 } else if (const QString hint = signatures ? mismatchHint(*signatures, family.type) : QString();
240 !hint.isEmpty()) {
241 error += (error.endsWith(u'.') ? QStringLiteral(" ") : QStringLiteral(". ")) + hint;
242 }
243 _state->configurationError = error;
244 _state->consentRequired = _state->runtime->error() == GPSProtocolError::ConsentRequired;
245 qCWarning(GPSDriverLog) << "Driver configuration failed for type" << family.type << error;
246 _state->runtime.reset();
247 return false;
248 }
249 _state->configurationError.clear();
250 return true;
251}
252
253GPSReceiveResult GPSDriver::receiveOutcome(std::chrono::milliseconds timeout)
254{
255 if (!_state->runtime) {
257 }
258 _state->cycle = {};
259 _publishExpiredSatellites();
260 const GPSReceiveUpdates decoded = _state->runtime->receive(timeout);
261 _publishExpiredSatellites();
262 if (_state->runtime->error() != GPSProtocolError::None) {
263 return {.error = _state->runtime->error(),
264 .updates = _state->cycle.updates,
265 .detail = _state->runtime->errorDetail()};
266 }
267 if (_transport.isCancelled()) {
268 return {.error = GPSProtocolError::Cancelled, .updates = _state->cycle.updates};
269 }
270 if (_transport.fatalError()) {
271 // An empty read reports why the link failed without consuming input.
272 const GPSReadResult failure = _transport.read({}, std::chrono::milliseconds::zero());
273 return {.error = GPSProtocolError::Transport, .updates = _state->cycle.updates, .detail = failure.detail};
274 }
275 return {.liveness = _state->cycle.usefulData ? GPSReceiveLiveness::Data
276 : _state->cycle.activity || decoded != GPSReceiveUpdates{} ? GPSReceiveLiveness::Activity
278 .updates = _state->cycle.updates};
279}
280
281void GPSDriver::_publish(const GPSEventBatch& batch)
282{
283 _state->cycle.activity |= !batch.events.empty();
284 for (const auto& event : batch.events) {
285 std::visit([this](const auto& report) { _handle(report); }, event);
286 }
287}
288
289void GPSDriver::_handle(const GPSIntegrityReport& report)
290{
291 _state->integrity = report;
292}
293
294void GPSDriver::_handle(const GPSDecodedPosition& report)
295{
296 if (_state->configuring) {
297 return;
298 }
299 _state->cycle.usefulData = true;
300 _state->cycle.updates |= GPSReceiveUpdate::Position;
301 if (_sinks.onPosition) {
302 _sinks.onPosition(GPSDecodedData::position(report, _state->integrity, _clock.nowUs()));
303 }
304}
305
306void GPSDriver::_handle(const GPSDecodedSatellites& report)
307{
308 if (!_state->configuring) {
309 _handleSatellites(_state->satellites.update(report, _clock.nowUs()));
310 }
311}
312
313void GPSDriver::_handle(const GPSDecodedSatelliteUsage& report)
314{
315 if (!_state->configuring) {
316 _handleSatellites(_state->satellites.update(report, _clock.nowUs()));
317 }
318}
319
320void GPSDriver::_handleSatellites(const GPSSatelliteReport& report)
321{
322 _state->cycle.usefulData = true;
323 _state->cycle.updates |= GPSReceiveUpdate::Satellites;
324 _publishSatellites(report);
325}
326
327void GPSDriver::_handle(const GPSSurveyReport& report)
328{
329 _state->cycle.usefulData = true;
330 if (_sinks.onSurveyIn) {
331 _sinks.onSurveyIn(report);
332 }
333}
334
335void GPSDriver::_handle(const GPSRTCMFrame& report)
336{
337 _state->cycle.usefulData = true;
338 if (!_state->configuring && _sinks.onRTCM) {
339 _sinks.onRTCM(report.bytes);
340 }
341}
342
343void GPSDriver::_handle(const GPSInputProtocol& report)
344{
345 // Satellite counts come from one protocol, as positions do.
346 _state->satellites = {};
347 if (_sinks.onReceiverDetected) {
348 _sinks.onReceiverDetected(report.family);
349 }
350}
351
352void GPSDriver::_publishExpiredSatellites()
353{
354 if (const auto expired = _state->satellites.expire(_clock.nowUs())) {
355 // Cache retirement is a notification, not new receiver traffic or navigation liveness.
356 _publishSatellites(*expired);
357 }
358}
359
360void GPSDriver::_publishSatellites(const GPSSatelliteReport& report)
361{
362 // Count-only usage can repeat every fix; consumers only need changed counts.
363 if (_state->lastSatellites == report) {
364 return;
365 }
366 _state->lastSatellites = report;
367 if (_sinks.onSatelliteInfo) {
368 _sinks.onSatelliteInfo(report);
369 }
370}
@ InvalidArgument
The driver requested an invalid write, or received before configuring.
@ Cancelled
A stop was requested; not a receiver or link fault.
@ ConsentRequired
Configuration needs a persistent receiver change the caller did not allow.
@ Transport
The link could not read, write, or change its baud rate.
@ Activity
Diagnostics or partial input, never proof of navigation liveness.
@ Data
Useful reports, even without a registered sink.
GPSReceiverConfig gpsReceiverConfigForDetected(GPSType detected, GPSReceiverConfig config, bool *compactFallback)
QString gpsDetectedReceiverConfigError(GPSType detected, const GPSReceiverConfig &config)
QString gpsReceiverConfigError(GPSType type, const GPSReceiverConfig &config)
An empty diagnostic means the receiver supports the role and all supplied settings.
QString gpsReceiverName(GPSType type)
The name of type's receiver family as shown to users; empty when no descriptor exists.
std::span< const GPSReceiverFamily *const > gpsReceiverFamilies()
Every native receiver family, in lookup order.
const GPSReceiverFamily * gpsReceiverFamily(GPSType type)
The family for type, or nullptr when no native family supports it.
Config config
Error error
#define QGC_LOGGING_CATEGORY(name, categoryStr)
QString receiverIdentity() const
Receiver model and firmware reported by the configured protocol; empty when unknown.
Definition GPSDriver.cc:91
const QString & configurationError() const
Diagnostic from the latest configure() failure; cleared when a new attempt starts.
Definition GPSDriver.cc:81
bool configurationNeedsConsent() const
Whether the latest configure() failed because the receiver needs persistent changes the request did n...
Definition GPSDriver.cc:86
GPSReceiveResult receiveOutcome(std::chrono::milliseconds timeout)
Definition GPSDriver.cc:253
bool configure()
Create and configure the underlying driver.
Definition GPSDriver.cc:96
GPSDriver(GPSType type, GPSTransport &transport, const GPSReceiverConfig &config, GPSDriverSinks sinks, GPSClock clock={})
Deadlines, report times and satellite freshness run on clock; the default is the steady clock.
Definition GPSDriver.cc:64
const std::vector< GPSConfigurationEvidence > & configurationEvidence() const
Latest configure() attempt; remains available after failure. Caller-thread access only.
Definition GPSDriver.cc:76
GPSReceiverDetection detect(unsigned baud=0)
Identifies the receiver at baud, which then every family is probed at, or else at each baudCandidates...
const std::optional< Match > & match() const
The first family signature since the last reset.
virtual bool fatalError() const =0
GPSWriteResult write(QByteArrayView bytes, QDeadlineTimer deadline)
virtual bool setBaudrate(unsigned baudrate)
bool isCancelled() const
virtual GPSReadResult read(std::span< uint8_t > buffer, std::chrono::milliseconds timeout)=0
virtual unsigned fixedBaudrate() const
Nonzero when the link cannot follow baud-rate changes (for example, a serial bridge).
const GPSCancelToken & cancelToken() const
GPSPositionReport position(const GPSDecodedPosition &source, const GPSIntegrityReport &diagnostic, uint64_t nowUs)
The monotonic clock receiver sessions run on: the steady clock unless tests inject a virtual one.
Definition GPSClock.h:11
std::function< uint64_t()> nowUs
Definition GPSClock.h:12
The receiver configuration requested for one configure() attempt.
Evidence from one configuration command, not a claim of physical receiver operation.
Definition GPSCommand.h:46
Decoded navigation plus whether the producer accepted its velocity solution.
std::function< void(const GPSSatelliteReport &)> onSatelliteInfo
Definition GPSDriver.h:30
std::function< void(const GPSPositionReport &)> onPosition
Definition GPSDriver.h:29
std::function< void(const GPSSurveyReport &)> onSurveyIn
Definition GPSDriver.h:33
std::function< void(const QByteArray &)> onRTCM
One complete frame; copying it shares the receiver's buffer.
Definition GPSDriver.h:32
std::function< void(GPSType)> onReceiverDetected
Definition GPSDriver.h:37
GPSReceiveUpdates updates
Definition GPSDriver.cc:56
GPSDecodedData::SatelliteCounts satellites
Definition GPSDriver.cc:44
GPSIntegrityReport integrity
Definition GPSDriver.cc:43
QString configurationError
Definition GPSDriver.cc:50
std::vector< GPSConfigurationEvidence > evidence
Definition GPSDriver.cc:49
std::optional< GPSSatelliteReport > lastSatellites
Definition GPSDriver.cc:45
ReceiveCycle cycle
Definition GPSDriver.cc:61
std::unique_ptr< GPSReceiverSignatures > signatures
Other families' signatures in the replies to a configured type, while it configures.
Definition GPSDriver.cc:48
std::unique_ptr< GPSProtocolRuntime > runtime
Definition GPSDriver.cc:46
Events decoded from one span of received bytes, in publication order.
std::vector< GPSProtocolEvent > events
One RTCM3 frame the receiver produced, including its header and CRC.
QByteArray bytes
GPSReadStatus status
bool allowPersistentChanges
Per-connection consent to persistent receiver settings and the required restart.
GPSBaseStationConfig base
uint32_t baudRate
Zero lets a configurable receiver detect the rate; passive serial input requires an explicit rate.
The receiver detection found, or why it found none.
const GPSReceiverFamily * family
Null when no family was identified.
GPSProtocolError error
Protocol when nothing was identified; Cancelled or Transport when the link ended detection.
unsigned baud
The rate the receiver answered at.
unsigned autoBaudRate
Rate passed to configure() when none is selected; zero requests detection.
std::function< GPSReadResult(std::span< uint8_t >, GPSDeadline)> read
GPSCancelToken cancelToken
Checked before each write and baud change; reads observe it in the transport.
std::function< GPSWriteResult(std::span< const uint8_t >, GPSDeadline)> write
GPSClock clock
Without a wait, the runtime sleeps in short slices until the duration elapses or cancelToken is cance...
std::function< GPSBaudStatus(unsigned)> setBaudrate
Observers of runtime output, invoked synchronously on the runtime thread.
std::function< void(const GPSConfigurationEvidence &)> commandFinished
Every completed command, during configuration and streaming alike.
std::function< void(const GPSEventBatch &)> decoded
Borrowed for the callback; it may decode nested input through GPSProtocolRuntime::consume().