QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
RTCMUdpInput.cc
Go to the documentation of this file.
1#include "RTCMUdpInput.h"
2
3#include <utility>
4
5#include <QtCore/QPointer>
6#include <QtNetwork/QNetworkDatagram>
7#include <QtNetwork/QUdpSocket>
8
9#include "MonotonicClock.h"
10#include "QGCLoggingCategory.h"
11#include "RuntimeScheduler.h"
12#include "UdpPeer.h"
13
14QGC_LOGGING_CATEGORY(RTCMUdpInputLog, "GPS.Corrections.RTCMUdpInput")
15
16namespace {
18QHostAddress senderAddress(const QNetworkDatagram& datagram)
19{
20 const QHostAddress sender = datagram.senderAddress();
21 bool ipv4 = false;
22 const quint32 address = sender.toIPv4Address(&ipv4);
23 return ipv4 ? QHostAddress(address) : sender;
24}
25} // namespace
26
27RTCMUdpInput::RTCMUdpInput(QObject* parent, RuntimeScheduler* scheduler)
28 : QObject(parent)
29 , _scheduler(RuntimeScheduler::orDefault(scheduler, this))
30{
31 qCDebug(RTCMUdpInputLog) << this;
32}
33
35{
36 qCDebug(RTCMUdpInputLog) << this;
37 stop();
38}
39
40bool RTCMUdpInput::start(quint16 port, QString* errorString)
41{
42 stop();
43 auto* const socket = new QUdpSocket(this);
44 if (!socket->bind(QHostAddress::Any, port)) {
45 qCDebug(RTCMUdpInputLog) << "Failed to bind UDP socket on port" << port << ":" << socket->errorString();
46 if (errorString) {
47 *errorString = socket->errorString();
48 }
49 delete socket;
50 return false;
51 }
52 connect(socket, &QUdpSocket::readyRead, this, &RTCMUdpInput::_readDatagrams);
53 connect(socket, &QAbstractSocket::errorOccurred, this, [socket]() {
54 qCWarning(RTCMUdpInputLog) << "UDP socket error on port" << socket->localPort() << ":" << socket->errorString();
55 });
56 _socket = socket;
57 qCDebug(RTCMUdpInputLog) << "Listening for RTCM data on UDP port" << socket->localPort();
58 return true;
59}
60
62{
63 _drainScheduled = false;
64 _peerParsers.clear();
65 QUdpSocket* const socket = std::exchange(_socket, nullptr);
66 if (!socket) {
67 return;
68 }
69 qCDebug(RTCMUdpInputLog) << "Stopped listening on UDP port" << socket->localPort();
70 socket->disconnect(this);
71 socket->close();
72 socket->deleteLater();
73}
74
75void RTCMUdpInput::_readDatagrams()
76{
77 QUdpSocket* const socket = _socket;
78 if (!socket) {
79 return;
80 }
81 // An observer that stops or restarts the input retires this socket.
82 const auto current = [this, socket]() { return socket == _socket; };
83 UdpDrainBudget budget;
84 while (current() && socket->hasPendingDatagrams() && budget.available()) {
85 const QNetworkDatagram datagram = socket->receiveDatagram();
86 if (!datagram.isValid()) {
87 break;
88 }
89 const QByteArray data = datagram.data();
90 budget.consume(data.size());
91 if (data.isEmpty()) {
92 continue;
93 }
94 const qint64 receivedAtMs = _scheduler->nowMs();
95 const QHostAddress sender = senderAddress(datagram);
96 // A stream is named by its sender's address, so selection stays on a sender that restarts on a new source
97 // port; each source port still frames independently.
98 const QString instance = sender.toString();
99 const auto peer = _parserForPeer(sender, datagram.senderPort());
100 int framesFound = 0;
101 int framesDropped = 0;
102 const bool delivered = peer->decoder.feed(data, receivedAtMs, [&](const RTCMDecodedFrame& decoded) {
103 if (!decoded.valid) {
104 ++framesDropped;
105 return current();
106 }
107 ++framesFound;
108 emit frameReceived(instance, decoded.data, decoded.receivedAtMs);
109 return current();
110 });
111 if (!delivered) {
112 return;
113 }
114
115 qCDebug(RTCMUdpInputLog) << "Datagram" << data.size() << "bytes -" << "framesFound:" << framesFound
116 << "framesDropped:" << framesDropped;
117 }
118 _scheduleRead();
119}
120
121void RTCMUdpInput::_scheduleRead()
122{
123 if (!_socket || !_socket->hasPendingDatagrams() || _drainScheduled) {
124 return;
125 }
126 _drainScheduled = true;
127 // Bounded drains yield to the event loop; a stop or restart before this runs retires the socket.
128 QMetaObject::invokeMethod(
129 this,
130 [this, socket = QPointer<QUdpSocket>(_socket)]() {
131 if (socket && socket == _socket) {
132 _drainScheduled = false;
133 _readDatagrams();
134 }
135 },
136 Qt::QueuedConnection);
137}
138
139std::shared_ptr<RTCMUdpInput::PeerParser> RTCMUdpInput::_parserForPeer(const QHostAddress& address, quint16 port)
140{
141 const qint64 now = _scheduler->nowMs();
142 for (auto it = _peerParsers.begin(); it != _peerParsers.end();) {
143 if (const auto idle = MonotonicClock::age(std::chrono::milliseconds(it.value()->lastReceivedMs),
144 std::chrono::milliseconds(now));
145 idle && *idle >= PEER_IDLE_TIMEOUT) {
146 it = _peerParsers.erase(it);
147 } else {
148 ++it;
149 }
150 }
151 const QString key = udpPeerKey(address, port);
152 auto peer = _peerParsers.value(key);
153 if (!peer) {
154 if (_peerParsers.size() >= MAX_PEERS) {
155 auto oldest = _peerParsers.begin();
156 for (auto it = _peerParsers.begin(); it != _peerParsers.end(); ++it) {
157 if (it.value()->lastReceivedMs < oldest.value()->lastReceivedMs) {
158 oldest = it;
159 }
160 }
161 _peerParsers.erase(oldest);
162 }
163 peer = std::make_shared<PeerParser>();
164 _peerParsers.insert(key, peer);
165 }
166 peer->lastReceivedMs = now;
167 return peer;
168}
std::atomic< quint64 > delivered
QString errorString
#define QGC_LOGGING_CATEGORY(name, categoryStr)
QString udpPeerKey(const QHostAddress &address, quint16 port)
Definition UdpPeer.h:6
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.
~RTCMUdpInput() override
RTCMUdpInput(QObject *parent=nullptr, RuntimeScheduler *scheduler=nullptr)
bool start(quint16 port, QString *errorString=nullptr)
qint64 nowMs() const
constexpr std::optional< std::chrono::duration< Rep, Period > > age(std::chrono::duration< Rep, Period > timestamp, std::chrono::duration< Rep, Period > now)
Age of timestamp at now, both in the same unit; empty for a missing or future timestamp.
QByteArray data
Definition RTCMFramer.h:93
qint64 receivedAtMs
Definition RTCMFramer.h:95
void consume(qsizetype size)
Definition UdpPeer.h:20
bool available() const
Definition UdpPeer.h:18