7#include <QtNetwork/QHostAddress>
8#include <QtNetwork/QNetworkInterface>
18bool reachesThisHost(
const QHostAddress& target)
20 const auto matches = [&target](
const QHostAddress& address) {
21 return !address.isNull() && address.isEqual(target, QHostAddress::TolerantConversion);
23 if (target.isLoopback() || target.isBroadcast() || target.isMulticast() || matches(QHostAddress::Any)) {
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());
36 const QString& instance)
41 manager->_beginSource(source);
47 _manager->_endSource(_source);
54 _manager->_submit(_source, instance.isEmpty() ? _instance : instance, data, receivedAtMs);
61 , _refreshTask(_scheduler, this)
62 , _tickTask(_scheduler, this)
63 , _selectedRate([scheduler = _scheduler]() {
return scheduler->
nowUs(); })
64 , _udpInput(
this, _scheduler)
66 qCDebug(GPSCorrectionManagerLog) <<
this;
68 [
this](
const QString& sender,
const QByteArray& data, qint64 receivedAtMs) {
70 _udpSource->submit(data, receivedAtMs, sender);
89 qCDebug(GPSCorrectionManagerLog) <<
this;
95 if (_shutdown || configuration == _configuration) {
98 const std::optional<Configuration> previous = std::exchange(_configuration, configuration);
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);
104 const bool outputNotified = (inputChanged || previous->udpOutput != configuration.
udpOutput) && _applyUdpOutput();
109 if (outputNotified) {
114bool GPSCorrectionManager::_applyUdpOutput()
116 const UdpOutputConfiguration& output = _configuration->udpOutput;
117 const QString address = output.address.trimmed();
120 if (output.enabled) {
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;
127 error = tr(
"Not forwarding to %1 port %2: this computer's UDP input receives on that port.")
130 }
else if (!_udpOutput.
configure(address, output.port)) {
131 error = tr(
"Enter an IP address and a port for UDP output.");
134 return std::exchange(_udpOutputError,
error) !=
error;
137bool GPSCorrectionManager::_applyUdpInput()
139 const QString previousError = _udpInputError;
142 _udpInputError.clear();
143 if (_udpInputEnabled()) {
146 return _udpInputError != previousError;
149void GPSCorrectionManager::_startUdpInput()
151 const quint16
port = _configuration->udpInput.port;
154 const QString reason = tr(
"Cannot listen on UDP port %1: %2").arg(port).arg(
error);
156 if (std::exchange(_udpInputError, reason) != reason) {
157 qCWarning(GPSCorrectionManagerLog) <<
"UDP correction input unavailable:" << reason;
161 _udpInputError.clear();
165void GPSCorrectionManager::_retryUdpInput()
167 if (!_udpInputEnabled() || _udpInputError.isEmpty()) {
170 const QString previousError = _udpInputError;
172 if (_udpInputError != previousError) {
177bool GPSCorrectionManager::_udpInputEnabled()
const
179 return _configuration && _configuration->udpInput.enabled;
195 const QByteArray& data, qint64 receivedAtMs)
197 if (_selector.
submit(source, instance, receivedAtMs, _scheduler->
nowMs())) {
202 (void) _udpOutput.
forward(data);
211 return tr(
"Local base station");
219 return tr(
"Unclassified");
222void GPSCorrectionManager::_refresh()
224 const std::optional<GPSCorrectionStream> selected = _selector.
selectedStream(_scheduler->
nowMs());
230 if (_selector.
hasSources() || _udpInputEnabled()) {
234 const quint64 rate =
static_cast<quint64
>(qRound64(_selectedRate.
bytesPerSec()));
235 const bool rateChanged = std::exchange(_selectedBytesPerSecond, rate) != rate;
237 const bool streamChanged = std::exchange(_selected, stream) != stream;
238 if (std::exchange(_state,
state) !=
state || streamChanged) {
246void GPSCorrectionManager::_scheduleRefresh()
248 if (!_shutdown && !_refreshTask.
active()) {
249 _refreshTask.
schedule(std::chrono::milliseconds(100), [
this]() { _refresh(); });
#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()
~GPSCorrectionManager() override
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
~GPSCorrectionSourceHandle()
quint64 submitToOutputs(QByteArrayView data)
void setOutputProvider(OutputProvider provider)
virtual quint64 nowUs() const =0
bool scheduleRepeating(std::chrono::microseconds interval, RuntimeScheduler::Callback callback)
bool schedule(std::chrono::microseconds delay, RuntimeScheduler::Callback callback)
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 ...
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.