QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
GPSReceiverWorker.cc
Go to the documentation of this file.
1#include "GPSReceiverWorker.h"
2
3#include <algorithm>
4#include <utility>
5
6#include "GPSDeadline.h"
7#include "GPSDriver.h"
8#include "GPSInputMonitor.h"
9#include "GPSTransport.h"
10#include "MonotonicClock.h"
11#include "QGCLoggingCategory.h"
12
13QGC_LOGGING_CATEGORY(GPSReceiverWorkerLog, "GPS.Receiver.GPSProvider")
14
16 QObject* parent, GPSClock clock)
17 : QObject(parent)
18 , _transportFactory(std::move(transportFactory))
19 , _type(type)
20 , _config(config)
21 , _clock(std::move(clock))
22{
23 qCDebug(GPSReceiverWorkerLog) << this;
24}
25
27{
28 // Owners delete a started worker only after finished(); joining here covers direct destruction.
29 stop();
30 (void) wait();
31}
32
34{
35 if (_thread) {
36 return;
37 }
38 _thread.reset(QThread::create([this] { _runSession(); }));
39 _thread->setObjectName(QStringLiteral("GPSReceiverWorker"));
40 (void) connect(_thread.get(), &QThread::finished, this, &GPSReceiverWorker::finished, Qt::QueuedConnection);
41 _thread->start();
42}
43
45{
46 _cancelSource.cancel();
47}
48
50{
51 if (!_thread) {
52 _endsWhenIdle = endsWhenIdle;
53 }
54}
55
56bool GPSReceiverWorker::wait(QDeadlineTimer deadline)
57{
58 return !_thread || _thread->wait(deadline);
59}
60
61void GPSReceiverWorker::_runSession()
62{
63 // Keep factory captures alive until the transport is destroyed, including on early returns.
64 auto transportFactory = std::exchange(_transportFactory, {});
65 const GPSCancelToken cancelToken = _cancelSource.token();
66 if (cancelToken.isCancelled()) {
67 return;
68 }
69
70 auto transport = transportFactory ? transportFactory(cancelToken) : nullptr;
71 if (cancelToken.isCancelled()) {
72 return;
73 }
74 const GPSOpenResult opened = transport ? transport->open() : GPSOpenResult{};
75 if (opened.status != GPSOpenStatus::Opened) {
76 if (!cancelToken.isCancelled()) {
77 emit connectionError(GPSConnectionError::OpenFailed, opened.detail);
78 }
79 return;
80 }
81 if (cancelToken.isCancelled()) {
82 return;
83 }
84
85 GPSDeadline inactiveAt{0};
86 const auto usefulDataReceived = [this, &inactiveAt] {
87 inactiveAt = GPSDeadline::after(_clock.nowUs(), USEFUL_DATA_TIMEOUT);
88 };
89 const auto inactiveFor = [this, &inactiveAt] { return inactiveAt.remaining(_clock.nowUs()); };
90 GPSDriverSinks sinks;
91 sinks.onPosition = [this](const GPSPositionReport& message) { emit positionUpdated(message); };
92 sinks.onSatelliteInfo = [this](const GPSSatelliteReport& message) { emit satelliteInfoUpdated(message); };
93 // Stamped on receipt with the production scheduler's clock (QtRuntimeScheduler), so consumers can compare it with
94 // their scheduler time; the session's injected clock may differ.
95 sinks.onRTCM = [this](const QByteArray& message) {
96 emit rtcmDataReceived(message, static_cast<qint64>(MonotonicClock::nowUs() / 1000));
97 };
98 sinks.onSurveyIn = [this](const GPSSurveyReport& report) { emit surveyInStatusUpdated(report); };
99 bool identified = false;
100 sinks.onReceiverDetected = [this, &identified](GPSType type) {
101 identified = true;
102 emit receiverDetected(type);
103 };
104
105 GPSDriver driver(_type, *transport, _config, std::move(sinks), _clock);
106
107 if (!driver.configure()) {
108 if (!cancelToken.isCancelled()) {
109 emit connectionError(driver.configurationNeedsConsent() ? GPSConnectionError::ConsentRequired
111 driver.configurationError());
112 }
113 return;
114 }
115 if (cancelToken.isCancelled()) {
116 return;
117 }
118 emit receiverReady(driver.receiverIdentity());
119
120 usefulDataReceived();
121 GPSInputMonitor inputMonitor(_clock.nowUs(), USEFUL_DATA_TIMEOUT);
122 bool cancelled = false;
124 while (!cancelToken.isCancelled() && !transport->fatalError() &&
125 (!_endsWhenIdle || inactiveFor() > std::chrono::milliseconds::zero())) {
126 const auto timeout = _endsWhenIdle ? std::min(RECEIVE_TIMEOUT, inactiveFor()) : RECEIVE_TIMEOUT;
127 auto result = driver.receiveOutcome(timeout);
128 if (result.liveness == GPSReceiveLiveness::Data) {
129 usefulDataReceived();
130 }
131 if (result.terminal()) {
132 failure = std::move(result);
133 break;
134 }
135 if (result.error == GPSProtocolError::Cancelled) {
136 cancelled = true;
137 break;
138 }
139 if (!_endsWhenIdle && inputMonitor.update(result, identified, _clock.nowUs())) {
140 emit inputProblem(inputMonitor.problem());
141 }
142 }
143 if (!cancelToken.isCancelled() && !cancelled) {
144 emit connectionError(
145 failure.error == GPSProtocolError::Protocol || failure.error == GPSProtocolError::ConsentRequired
146 ? GPSConnectionError::ProtocolError
148 failure.detail);
149 }
150
151 qCDebug(GPSReceiverWorkerLog) << "Exiting GPS thread";
152}
@ Protocol
The receiver violated or rejected the control protocol.
@ 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.
@ Data
Useful reports, even without a registered sink.
Config config
#define QGC_LOGGING_CATEGORY(name, categoryStr)
GPSCancelToken token() const
Classifies why a session that stays connected delivers no position, from the receive cycles of the la...
static constexpr std::chrono::milliseconds RECEIVE_TIMEOUT
Longest wait for one read before the session re-checks cancellation.
void inputProblem(GPSInputProblem problem)
virtual void start()
Starts the session once; later calls are ignored.
void connectionError(GPSConnectionError error, const QString &detail={})
detail is the transport's or the protocol's diagnostic, or empty when it gave none.
bool wait(QDeadlineTimer deadline=QDeadlineTimer(QDeadlineTimer::Forever))
Waits for the worker to exit; returns true at once when the session never started.
void finished()
Delivered on this object's thread once the session thread has finished.
void receiverReady(const QString &identity={})
identity is the receiver model and firmware, or empty when the family does not report them.
void satelliteInfoUpdated(const GPSSatelliteReport &message)
void surveyInStatusUpdated(const GPSSurveyReport &report)
~GPSReceiverWorker() override
void receiverDetected(GPSType type)
void setEndsWhenIdle(bool endsWhenIdle)
std::function< std::unique_ptr< GPSTransport >(GPSCancelToken)> TransportFactory
Consumed on the worker thread, so transport construction, I/O and destruction share that thread.
static constexpr std::chrono::milliseconds USEFUL_DATA_TIMEOUT
void rtcmDataReceived(const QByteArray &message, qint64 receivedAtMs)
RTCM output the receiver sent, stamped with the steady clock when it was read.
void positionUpdated(const GPSPositionReport &report)
@ DeviceError
The connection to the receiver failed or went silent.
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
static GPSDeadline after(uint64_t nowUs, std::chrono::microseconds timeout)
A negative timeout is already due at nowUs.
Definition GPSDeadline.h:14
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
GPSOpenStatus status