QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
GPSCorrectionManager.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <chrono>
5#include <utility>
6
7#include <QtNetwork/QHostAddress>
8#include <QtNetwork/QNetworkInterface>
9
10#include "QGCLoggingCategory.h"
11#include "RuntimeScheduler.h"
12
13QGC_LOGGING_CATEGORY(GPSCorrectionManagerLog, "GPS.Corrections.GPSCorrectionManager")
14
15namespace {
18bool reachesThisHost(const QHostAddress& target)
19{
20 const auto matches = [&target](const QHostAddress& address) {
21 return !address.isNull() && address.isEqual(target, QHostAddress::TolerantConversion);
22 };
23 if (target.isLoopback() || target.isBroadcast() || target.isMulticast() || matches(QHostAddress::Any)) {
24 return true;
25 }
26 return std::ranges::any_of(QNetworkInterface::allInterfaces(), [&matches](const QNetworkInterface& interface) {
27 return std::ranges::any_of(interface.addressEntries(), [&matches](const QNetworkAddressEntry& entry) {
28 return matches(entry.ip()) || matches(entry.broadcast());
29 });
30 });
31}
32} // namespace
33
34GPSCorrectionSourceHandle::GPSCorrectionSourceHandle(GPSCorrectionManager* manager,
36 const QString& instance)
37 : _manager(manager)
38 , _source(source)
39 , _instance(instance)
40{
41 manager->_beginSource(source);
42}
43
45{
46 if (_manager) {
47 _manager->_endSource(_source);
48 }
49}
50
51void GPSCorrectionSourceHandle::submit(const QByteArray& data, qint64 receivedAtMs, const QString& instance) const
52{
53 if (_manager) {
54 _manager->_submit(_source, instance.isEmpty() ? _instance : instance, data, receivedAtMs);
55 }
56}
57
59 : QObject(parent)
60 , _scheduler(RuntimeScheduler::orDefault(scheduler, this))
61 , _refreshTask(_scheduler, this)
62 , _tickTask(_scheduler, this)
63 , _selectedRate([scheduler = _scheduler]() { return scheduler->nowUs(); })
64 , _udpInput(this, _scheduler)
65{
66 qCDebug(GPSCorrectionManagerLog) << this;
67 connect(&_udpInput, &RTCMUdpInput::frameReceived, this,
68 [this](const QString& sender, const QByteArray& data, qint64 receivedAtMs) {
69 if (_udpSource) {
70 _udpSource->submit(data, receivedAtMs, sender);
71 }
72 });
73 _tickTask.scheduleRepeating(std::chrono::seconds(1), [this]() {
74 _retryUdpInput();
75 _selectedRate.refresh();
76 _refresh();
77 });
78 _refresh();
79}
80
81std::unique_ptr<GPSCorrectionSourceHandle> GPSCorrectionManager::openSource(
82 GPSCorrectionSettings::CorrectionSource source, const QString& instance)
83{
84 return std::unique_ptr<GPSCorrectionSourceHandle>(new GPSCorrectionSourceHandle(this, source, instance));
85}
86
88{
89 qCDebug(GPSCorrectionManagerLog) << this;
90 shutdown();
91}
92
94{
95 if (_shutdown || configuration == _configuration) {
96 return;
97 }
98 const std::optional<Configuration> previous = std::exchange(_configuration, configuration);
99 _selector.configure(configuration.source, _scheduler->nowMs());
100 const bool inputChanged = !previous || previous->udpInput != configuration.udpInput;
101 const bool enabledChanged = (previous && previous->udpInput.enabled) != configuration.udpInput.enabled;
102 const bool inputNotified = inputChanged && (_applyUdpInput() || enabledChanged);
103 // The output also depends on the input port, which it must not feed back into.
104 const bool outputNotified = (inputChanged || previous->udpOutput != configuration.udpOutput) && _applyUdpOutput();
105 _refresh();
106 if (inputNotified) {
107 emit udpInputChanged();
108 }
109 if (outputNotified) {
110 emit udpOutputChanged();
111 }
112}
113
114bool GPSCorrectionManager::_applyUdpOutput()
115{
116 const UdpOutputConfiguration& output = _configuration->udpOutput;
117 const QString address = output.address.trimmed();
118 _udpOutput.stop();
119 QString error;
120 if (output.enabled) {
121 // Forwarding to this host's own UDP input would feed the selected stream back into itself.
122 if (_udpInputEnabled() && output.port == _configuration->udpInput.port &&
123 reachesThisHost(QHostAddress(address))) {
124 qCWarning(GPSCorrectionManagerLog)
125 << "Not forwarding corrections to this host's own UDP input port" << output.port;
126 //: %1 is an IP address, %2 a UDP port
127 error = tr("Not forwarding to %1 port %2: this computer's UDP input receives on that port.")
128 .arg(address)
129 .arg(output.port);
130 } else if (!_udpOutput.configure(address, output.port)) {
131 error = tr("Enter an IP address and a port for UDP output.");
132 }
133 }
134 return std::exchange(_udpOutputError, error) != error;
135}
136
137bool GPSCorrectionManager::_applyUdpInput()
138{
139 const QString previousError = _udpInputError;
140 _udpSource.reset();
141 _udpInput.stop();
142 _udpInputError.clear();
143 if (_udpInputEnabled()) {
144 _startUdpInput();
145 }
146 return _udpInputError != previousError;
147}
148
149void GPSCorrectionManager::_startUdpInput()
150{
151 const quint16 port = _configuration->udpInput.port;
152 QString error;
153 if (!_udpInput.start(port, &error)) {
154 const QString reason = tr("Cannot listen on UDP port %1: %2").arg(port).arg(error);
155 // The tick retries every second, so only a new reason is worth a warning.
156 if (std::exchange(_udpInputError, reason) != reason) {
157 qCWarning(GPSCorrectionManagerLog) << "UDP correction input unavailable:" << reason;
158 }
159 return;
160 }
161 _udpInputError.clear();
162 _udpSource = openSource(GPSCorrectionSettings::Udp, QString());
163}
164
165void GPSCorrectionManager::_retryUdpInput()
166{
167 if (!_udpInputEnabled() || _udpInputError.isEmpty()) {
168 return;
169 }
170 const QString previousError = _udpInputError;
171 _startUdpInput();
172 if (_udpInputError != previousError) {
173 emit udpInputChanged();
174 }
175}
176
177bool GPSCorrectionManager::_udpInputEnabled() const
178{
179 return _configuration && _configuration->udpInput.enabled;
180}
181
182void GPSCorrectionManager::_beginSource(GPSCorrectionSettings::CorrectionSource source)
183{
184 _selector.beginSource(source, _scheduler->nowMs());
185 _scheduleRefresh();
186}
187
188void GPSCorrectionManager::_endSource(GPSCorrectionSettings::CorrectionSource source)
189{
190 _selector.endSource(source, _scheduler->nowMs());
191 _scheduleRefresh();
192}
193
194void GPSCorrectionManager::_submit(GPSCorrectionSettings::CorrectionSource source, const QString& instance,
195 const QByteArray& data, qint64 receivedAtMs)
196{
197 if (_selector.submit(source, instance, receivedAtMs, _scheduler->nowMs())) {
198 _selectedRate.recordBytes(data.size());
199 if (_rtcmMavlink.submitToOutputs(data) > 0) {
201 }
202 (void) _udpOutput.forward(data);
203 }
204 _scheduleRefresh();
205}
206
208{
209 switch (static_cast<GPSCorrectionSettings::CorrectionSource>(source)) {
211 return tr("Local base station");
213 return tr("NTRIP");
215 return tr("UDP");
217 break;
218 }
219 return tr("Unclassified");
220}
221
222void GPSCorrectionManager::_refresh()
223{
224 const std::optional<GPSCorrectionStream> selected = _selector.selectedStream(_scheduler->nowMs());
226 if (selected) {
228 } else {
229 _selectedRate.resetRate();
230 if (_selector.hasSources() || _udpInputEnabled()) {
232 }
233 }
234 const quint64 rate = static_cast<quint64>(qRound64(_selectedRate.bytesPerSec()));
235 const bool rateChanged = std::exchange(_selectedBytesPerSecond, rate) != rate;
236 const GPSCorrectionStream stream = selected.value_or(GPSCorrectionStream{});
237 const bool streamChanged = std::exchange(_selected, stream) != stream;
238 if (std::exchange(_state, state) != state || streamChanged) {
239 emit stateChanged();
240 }
241 if (rateChanged) {
243 }
244}
245
246void GPSCorrectionManager::_scheduleRefresh()
247{
248 if (!_shutdown && !_refreshTask.active()) {
249 _refreshTask.schedule(std::chrono::milliseconds(100), [this]() { _refresh(); });
250 }
251}
252
254{
255 if (_shutdown) {
256 return;
257 }
258 _shutdown = true;
259 _tickTask.cancel();
260 _refreshTask.cancel();
261 _rtcmMavlink.setOutputProvider({});
262 _udpOutput.stop();
263 _udpSource.reset();
264 _selector.shutdown();
265 _udpInput.stop();
266}
Error error
#define QGC_LOGGING_CATEGORY(name, categoryStr)
void recordBytes(qsizetype bytes)
Record incoming/outgoing bytes. Call whenever data passes through.
void refresh()
Advance the rate window without changing cumulative bytes.
void resetRate()
Zero the rate and restart its window; cumulative bytes are kept.
double bytesPerSec() const
Rate of the last completed window, in bytes/sec.
static Q_INVOKABLE QString sourceName(int source)
void vehicleBytesSubmittedChanged()
GPSCorrectionManager(QObject *parent=nullptr, RuntimeScheduler *scheduler=nullptr)
void selectedBytesPerSecondChanged()
friend class GPSCorrectionSourceHandle
State
Whether vehicles receive RTK corrections, across NTRIP, UDP input, and the local receiver.
@ Inactive
No correction source is enabled.
@ Waiting
A source is enabled, but no fresh stream is selected for vehicles.
@ Fresh
A fresh stream is selected for vehicles.
std::unique_ptr< GPSCorrectionSourceHandle > openSource(GPSCorrectionSettings::CorrectionSource source, const QString &instance)
State state() const
As last published with stateChanged().
void setConfiguration(const Configuration &configuration)
Applies the parts that changed; ignored after shutdown().
bool hasSources() const
Whether any source is begun.
bool submit(GPSCorrectionSettings::CorrectionSource source, const QString &instance, qint64 receivedAtMs, qint64 now)
void beginSource(GPSCorrectionSettings::CorrectionSource source, qint64 now)
Starts source. A category has one source at a time, which ends itself before a replacement begins.
std::optional< GPSCorrectionStream > selectedStream(qint64 now) const
The selected fresh stream, if any.
void endSource(GPSCorrectionSettings::CorrectionSource source, qint64 now)
Stops routing source.
void configure(GPSCorrectionSettings::CorrectionSource source, qint64 now)
Routes only source, or selects automatically for HighestPriority. A valid, changed source reselects a...
CorrectionSource
Persisted values of correctionSource. The storage type keeps any saved value a valid enum value.
void submit(const QByteArray &data, qint64 receivedAtMs, const QString &instance={}) const
void frameReceived(const QString &sender, const QByteArray &data, qint64 receivedAtMs)
A valid frame from sender, the sender's address, stamped receivedAtMs with the scheduler's time.
bool start(quint16 port, QString *errorString=nullptr)
virtual quint64 nowUs() const =0
qint64 nowMs() const
bool scheduleRepeating(std::chrono::microseconds interval, RuntimeScheduler::Callback callback)
bool schedule(std::chrono::microseconds delay, RuntimeScheduler::Callback callback)
bool active() const
qint64 forward(const QByteArray &data)
bool configure(const QString &address, quint16 port)
GPSCommandSequence port(QByteArrayView port, unsigned baud)
Clears the SBF output of port, keeps a serial port at the link's baud, and selects SBF and the WGS84 ...
Definition SBFPlan.cc:34
The correction settings as values; the application's settings binding supplies them.
GPSCorrectionSettings::CorrectionSource source
The only source routed to vehicles, or HighestPriority to select automatically.
UdpInputConfiguration udpInput
Listens for RTCM on a UDP port as the Udp source. A port that cannot be bound is retried every second...
UdpOutputConfiguration udpOutput
Forwards the selected stream to a UDP peer.
The stream vehicles receive: its source category and the instance within it.