QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
UDPLink.cc
Go to the documentation of this file.
1#include "UDPLink.h"
4#include "QGCNetworkHelper.h"
5#include "SettingsManager.h"
6
7#include <QtCore/QMutexLocker>
8#include <QtCore/QThread>
9#include <QtNetwork/QHostInfo>
10#include <QtNetwork/QNetworkDatagram>
11#include <QtNetwork/QNetworkInterface>
12#include <QtNetwork/QNetworkProxy>
13#include <QtNetwork/QUdpSocket>
14
15QGC_LOGGING_CATEGORY(UDPLinkLog, "Comms.UDPLink")
16
17namespace {
18 constexpr int BUFFER_TRIGGER_SIZE = 10 * 1024;
19 constexpr int RECEIVE_TIME_LIMIT_MS = 50;
20
21 bool containsTarget(const QList<std::shared_ptr<UDPClient>> &list, const QHostAddress &address, quint16 port)
22 {
23 for (const std::shared_ptr<UDPClient> &target : list) {
24 if ((target->address == address) && (target->port == port)) {
25 return true;
26 }
27 }
28
29 return false;
30 }
31}
32
33/*===========================================================================*/
34
35UDPConfiguration::UDPConfiguration(const QString &name, QObject *parent)
36 : LinkConfiguration(name, parent)
37{
38}
39
41 : LinkConfiguration(source, parent)
42{
43 qCDebug(UDPLinkLog) << this;
44
46}
47
49{
50 _targetHosts.clear();
51
52 qCDebug(UDPLinkLog) << this;
53}
54
56{
57 if (isAutoConnect() != autoc) {
59 const QString targetHostIP = settings->udpTargetHostIP()->rawValue().toString();
60 const quint16 targetHostPort = settings->udpTargetHostPort()->rawValue().toUInt();
61 if (autoc) {
62 setLocalPort(settings->udpListenPort()->rawValue().toInt());
63 if (!targetHostIP.isEmpty()) {
64 addHost(targetHostIP, targetHostPort);
65 }
66 } else {
67 setLocalPort(0);
68 if (!targetHostIP.isEmpty()) {
69 removeHost(targetHostIP, targetHostPort);
70 }
71 }
73 }
74}
75
77{
79
80 const UDPConfiguration *udpSource = qobject_cast<const UDPConfiguration*>(source);
81
82 setLocalPort(udpSource->localPort());
83 _targetHosts.clear();
84
85 for (const std::shared_ptr<UDPClient> &target : udpSource->targetHosts()) {
86 if (!containsTarget(_targetHosts, target->address, target->port)) {
87 _targetHosts.append(std::make_shared<UDPClient>(target.get()));
88 _updateHostList();
89 }
90 }
91}
92
93void UDPConfiguration::loadSettings(QSettings &settings, const QString &root)
94{
95 settings.beginGroup(root);
96
97 setLocalPort(static_cast<quint16>(settings.value("port", SettingsManager::instance()->autoConnectSettings()->udpListenPort()->rawValue().toUInt()).toUInt()));
98
99 _targetHosts.clear();
100 const qsizetype hostCount = settings.value("hostCount", 0).toUInt();
101 for (qsizetype i = 0; i < hostCount; i++) {
102 const QString hkey = QStringLiteral("host%1").arg(i);
103 const QString pkey = QStringLiteral("port%1").arg(i);
104 if (settings.contains(hkey) && settings.contains(pkey)) {
105 addHost(settings.value(hkey).toString(), settings.value(pkey).toUInt());
106 }
107 }
108
109 _updateHostList();
110
111 settings.endGroup();
112}
113
114void UDPConfiguration::saveSettings(QSettings &settings, const QString &root) const
115{
116 settings.beginGroup(root);
117
118 settings.setValue(QStringLiteral("hostCount"), _targetHosts.size());
119 settings.setValue(QStringLiteral("port"), _localPort);
120
121 for (qsizetype i = 0; i < _targetHosts.size(); i++) {
122 const std::shared_ptr<UDPClient> target = _targetHosts.at(i);
123 const QString hkey = QStringLiteral("host%1").arg(i);
124 settings.setValue(hkey, target->address.toString());
125 const QString pkey = QStringLiteral("port%1").arg(i);
126 settings.setValue(pkey, target->port);
127 }
128
129 settings.endGroup();
130}
131
132void UDPConfiguration::addHost(const QString &host)
133{
134 if (host.contains(":")) {
135 const QStringList hostInfo = host.split(":");
136 if (hostInfo.size() != 2) {
137 qCWarning(UDPLinkLog) << "Invalid host format:" << host;
138 return;
139 }
140
141 const QString address = hostInfo.constFirst();
142 const quint16 port = hostInfo.constLast().toUInt();
143
144 addHost(address, port);
145 } else {
146 addHost(host, _localPort);
147 }
148}
149
150void UDPConfiguration::addHost(const QString &host, quint16 port)
151{
152 const QString ipAdd = _getIpAddress(host);
153 if (ipAdd.isEmpty()) {
154 qCWarning(UDPLinkLog) << "Could not resolve host:" << host << "port:" << port;
155 return;
156 }
157
158 const QHostAddress address(ipAdd);
159 if (!containsTarget(_targetHosts, address, port)) {
160 _targetHosts.append(std::make_shared<UDPClient>(address, port));
161 _updateHostList();
162 }
163}
164
165void UDPConfiguration::removeHost(const QString &host)
166{
167 if (host.contains(":")) {
168 const QStringList hostInfo = host.split(":");
169 if (hostInfo.size() != 2) {
170 qCWarning(UDPLinkLog) << "Invalid host format:" << host;
171 return;
172 }
173
174 const QHostAddress address = QHostAddress(_getIpAddress(hostInfo.constFirst()));
175 const quint16 port = hostInfo.constLast().toUInt();
176
177 if (!containsTarget(_targetHosts, address, port)) {
178 qCWarning(UDPLinkLog) << "Could not remove unknown host:" << host << "port:" << port;
179 return;
180 }
181
182 for (qsizetype i = 0; i < _targetHosts.size(); ++i) {
183 const std::shared_ptr<UDPClient> &target = _targetHosts[i];
184 if (target->address == address && target->port == port) {
185 _targetHosts.removeAt(i);
186 _updateHostList();
187 return;
188 }
189 }
190 } else {
191 removeHost(host, _localPort);
192 }
193}
194
195void UDPConfiguration::removeHost(const QString &host, quint16 port)
196{
197 const QString ipAdd = _getIpAddress(host);
198 if (ipAdd.isEmpty()) {
199 qCWarning(UDPLinkLog) << "Could not resolve host:" << host << "port:" << port;
200 return;
201 }
202
203 const QHostAddress address(ipAdd);
204 if (!containsTarget(_targetHosts, address, port)) {
205 qCWarning(UDPLinkLog) << "Could not remove unknown host:" << host << "port:" << port;
206 return;
207 }
208
209 for (qsizetype i = 0; i < _targetHosts.size(); ++i) {
210 const std::shared_ptr<UDPClient> &target = _targetHosts[i];
211 if (target->address == address && target->port == port) {
212 _targetHosts.removeAt(i);
213 _updateHostList();
214 return;
215 }
216 }
217}
218
219void UDPConfiguration::_updateHostList()
220{
221 _hostList.clear();
222 for (const std::shared_ptr<UDPClient> &target : _targetHosts) {
223 const QString host = target->address.toString() + ":" + QString::number(target->port);
224 _hostList.append(host);
225 }
226
227 emit hostListChanged();
228}
229
230QString UDPConfiguration::_getIpAddress(const QString &address)
231{
232 const QHostAddress host(address);
233 if (!host.isNull()) {
234 return address;
235 }
236
237 const QHostInfo info = QHostInfo::fromName(address);
238 if (info.error() != QHostInfo::NoError) {
239 return QString();
240 }
241
242 const QList<QHostAddress> hostAddresses = info.addresses();
243 for (const QHostAddress &hostAddress : hostAddresses) {
244 if (hostAddress.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) {
245 return hostAddress.toString();
246 }
247 }
248
249 return QString();
250}
251
252/*===========================================================================*/
253
254const QHostAddress UDPWorker::_multicastGroup = QHostAddress(QStringLiteral("224.0.0.1"));
255
256UDPWorker::UDPWorker(const UDPConfiguration *config, QObject *parent)
257 : QObject(parent)
258 , _udpConfig(config)
259{
260 qCDebug(UDPLinkLog) << this;
261}
262
264{
266
267 qCDebug(UDPLinkLog) << this;
268}
269
271{
272 return (_socket && _socket->isValid() && _isConnected);
273}
274
276{
277 if (!_socket) {
278 _socket = new QUdpSocket(this);
279 }
280
281 const QList<QHostAddress> localAddresses = QNetworkInterface::allAddresses();
282 _localAddresses = QSet(localAddresses.constBegin(), localAddresses.constEnd());
283
284 _socket->setProxy(QNetworkProxy::NoProxy);
285
286 (void) connect(_socket, &QUdpSocket::connected, this, &UDPWorker::_onSocketConnected);
287 (void) connect(_socket, &QUdpSocket::disconnected, this, &UDPWorker::_onSocketDisconnected);
288 (void) connect(_socket, &QUdpSocket::readyRead, this, &UDPWorker::_onSocketReadyRead);
289 (void) connect(_socket, &QUdpSocket::errorOccurred, this, &UDPWorker::_onSocketErrorOccurred);
290 (void) connect(_socket, &QUdpSocket::stateChanged, this, [this](QUdpSocket::SocketState state) {
291 qCDebug(UDPLinkLog) << "UDP State Changed:" << state;
292 switch (state) {
293 case QAbstractSocket::BoundState:
294 _onSocketConnected();
295 break;
296 case QAbstractSocket::ClosingState:
297 case QAbstractSocket::UnconnectedState:
298 _onSocketDisconnected();
299 break;
300 default:
301 break;
302 }
303 });
304
305 if (UDPLinkLog().isDebugEnabled()) {
306 // (void) connect(_socket, &QUdpSocket::bytesWritten, this, &UDPWorker::_onSocketBytesWritten);
307
308 (void) QObject::connect(_socket, &QUdpSocket::hostFound, this, []() {
309 qCDebug(UDPLinkLog) << "UDP Host Found";
310 });
311 }
312}
313
315{
316 if (isConnected()) {
317 qCWarning(UDPLinkLog) << "Already connected to" << _udpConfig->localPort();
318 return;
319 }
320
321 _errorEmitted = false;
322
323 qCDebug(UDPLinkLog) << "Attempting to bind to port:" << _udpConfig->localPort();
324 const bool bindSuccess = _socket->bind(QHostAddress::AnyIPv4, _udpConfig->localPort(), QAbstractSocket::ReuseAddressHint | QAbstractSocket::ShareAddress);
325 if (!bindSuccess) {
326 qCWarning(UDPLinkLog) << "Failed to bind UDP socket to port" << _udpConfig->localPort();
327
328 if (!_errorEmitted) {
329 emit errorOccurred(tr("Failed to bind UDP socket to port"));
330 _errorEmitted = true;
331 }
332
333 // Disconnecting here on autoconnect will cause continuous error popups
334 /*if (!_udpConfig->isAutoConnect()) {
335 _onSocketDisconnected();
336 }*/
337
338 return;
339 }
340
341 qCDebug(UDPLinkLog) << "Attempting to join multicast group:" << _multicastGroup.toString();
342 const bool joinSuccess = _socket->joinMulticastGroup(_multicastGroup);
343 if (!joinSuccess) {
344 qCWarning(UDPLinkLog) << "Failed to join multicast group" << _multicastGroup.toString();
345 }
346
347}
348
350{
351 if (!isConnected()) {
352 qCDebug(UDPLinkLog) << "Already disconnected";
353 return;
354 }
355
356 qCDebug(UDPLinkLog) << "Disconnecting UDP link";
357
358 (void) _socket->leaveMulticastGroup(_multicastGroup);
359 _socket->close();
360
361 _sessionTargets.clear();
362}
363
364void UDPWorker::writeData(const QByteArray &data)
365{
366 if (!isConnected()) {
367 emit errorOccurred(tr("Could Not Send Data - Link is Disconnected!"));
368 return;
369 }
370
371 QMutexLocker locker(&_sessionTargetsMutex);
372
373 // Send to all manually targeted systems
374 for (const std::shared_ptr<UDPClient> &target : _udpConfig->targetHosts()) {
375 if (!containsTarget(_sessionTargets, target->address, target->port)) {
376 if (_socket->writeDatagram(data, target->address, target->port) < 0) {
377 qCWarning(UDPLinkLog) << "Could Not Send Data - Write Failed!";
378 }
379 }
380 }
381
382 // Send to all connected systems
383 for (const std::shared_ptr<UDPClient> &target: _sessionTargets) {
384 if (_socket->writeDatagram(data, target->address, target->port) < 0) {
385 qCWarning(UDPLinkLog) << "Could Not Send Data - Write Failed!";
386 }
387 }
388
389 locker.unlock();
390
391 emit dataSent(data);
392}
393
394void UDPWorker::_onSocketConnected()
395{
396 qCDebug(UDPLinkLog) << "UDP connected to" << _udpConfig->localPort();
397 _isConnected = true;
398 _errorEmitted = false;
399 emit connected();
400}
401
402void UDPWorker::_onSocketDisconnected()
403{
404 qCDebug(UDPLinkLog) << "UDP disconnected from" << _udpConfig->localPort();
405 _isConnected = false;
406 _errorEmitted = false;
407 emit disconnected();
408}
409
410void UDPWorker::_onSocketReadyRead()
411{
412 if (!isConnected()) {
413 emit errorOccurred(tr("Could Not Read Data - Link is Disconnected!"));
414 return;
415 }
416
417 const qint64 byteCount = _socket->pendingDatagramSize();
418 if (byteCount <= 0) {
419 emit errorOccurred(tr("Could Not Read Data - No Data Available!"));
420 return;
421 }
422
423 QByteArray buffer;
424 buffer.reserve(BUFFER_TRIGGER_SIZE);
425 QElapsedTimer timer;
426 timer.start();
427 bool received = false;
428 while (_socket->hasPendingDatagrams()) {
429 const QNetworkDatagram datagramIn = _socket->receiveDatagram();
430 if (datagramIn.isNull() || datagramIn.data().isEmpty()) {
431 continue;
432 }
433
434 (void) buffer.append(datagramIn.data());
435
436 if ((buffer.size() > BUFFER_TRIGGER_SIZE) || (timer.elapsed() > RECEIVE_TIME_LIMIT_MS)) {
437 received = true;
438 emit dataReceived(buffer);
439 buffer.clear();
440 (void) timer.restart();
441 }
442
443 const bool ipLocal = datagramIn.senderAddress().isLoopback() || _localAddresses.contains(datagramIn.senderAddress());
444 const QHostAddress senderAddress = ipLocal ? QHostAddress(QHostAddress::SpecialAddress::LocalHost) : datagramIn.senderAddress();
445
446 QMutexLocker locker(&_sessionTargetsMutex);
447 if (!containsTarget(_sessionTargets, senderAddress, datagramIn.senderPort())) {
448 qCDebug(UDPLinkLog) << "UDP Adding target:" << senderAddress << datagramIn.senderPort();
449 _sessionTargets.append(std::make_shared<UDPClient>(senderAddress, datagramIn.senderPort()));
450 }
451 locker.unlock();
452 }
453
454 if (!received && buffer.isEmpty()) {
455 qCWarning(UDPLinkLog) << "No Data Available to Read!";
456 return;
457 }
458
459 emit dataReceived(buffer);
460}
461
462void UDPWorker::_onSocketBytesWritten(qint64 bytes)
463{
464 qCDebug(UDPLinkLog) << "Wrote" << bytes << "bytes";
465}
466
467void UDPWorker::_onSocketErrorOccurred(QUdpSocket::SocketError error)
468{
469 const QString errorString = _socket->errorString();
470 qCWarning(UDPLinkLog) << "UDP Link error:" << error << _socket->errorString();
471
472 if (!_errorEmitted) {
474 _errorEmitted = true;
475 }
476}
477
478/*===========================================================================*/
479
481 : LinkInterface(config, parent)
482 , _udpConfig(qobject_cast<const UDPConfiguration*>(config.get()))
483 , _worker(new UDPWorker(_udpConfig))
484 , _workerThread(new QThread(this))
485{
486 qCDebug(UDPLinkLog) << this;
487
488 _workerThread->setObjectName(QStringLiteral("UDP_%1").arg(_udpConfig->name()));
489
490 _worker->moveToThread(_workerThread);
491
492 (void) connect(_workerThread, &QThread::started, _worker, &UDPWorker::setupSocket);
493 (void) connect(_workerThread, &QThread::finished, _worker, &QObject::deleteLater);
494
495 (void) connect(_worker, &UDPWorker::connected, this, &UDPLink::_onConnected, Qt::QueuedConnection);
496 (void) connect(_worker, &UDPWorker::disconnected, this, &UDPLink::_onDisconnected, Qt::QueuedConnection);
497 (void) connect(_worker, &UDPWorker::errorOccurred, this, &UDPLink::_onErrorOccurred, Qt::QueuedConnection);
498 (void) connect(_worker, &UDPWorker::dataReceived, this, &UDPLink::_onDataReceived, Qt::QueuedConnection);
499 (void) connect(_worker, &UDPWorker::dataSent, this, &UDPLink::_onDataSent, Qt::QueuedConnection);
500
501 _workerThread->start();
502}
503
505{
506 if (isConnected()) {
507 (void) QMetaObject::invokeMethod(_worker, "disconnectLink", Qt::BlockingQueuedConnection);
508 _onDisconnected();
509 }
510
511 _workerThread->quit();
512 if (!_workerThread->wait()) {
513 qCWarning(UDPLinkLog) << "Failed to wait for UDP Thread to close";
514 }
515
516 qCDebug(UDPLinkLog) << this;
517}
518
520{
521 return _worker && _worker->isConnected();
522}
523
525{
526 return QMetaObject::invokeMethod(_worker, "connectLink", Qt::QueuedConnection);
527}
528
530{
531 if (isConnected()) {
532 (void) QMetaObject::invokeMethod(_worker, "disconnectLink", Qt::QueuedConnection);
533 }
534}
535
536void UDPLink::_onConnected()
537{
538 _disconnectedEmitted = false;
539 emit connected();
540}
541
542void UDPLink::_onDisconnected()
543{
544 if (!_disconnectedEmitted.exchange(true)) {
545 emit disconnected();
546 }
547}
548
549void UDPLink::_onErrorOccurred(const QString &errorString)
550{
551 qCWarning(UDPLinkLog) << "Communication error:" << errorString;
552 emit communicationError(tr("UDP Link Error"), tr("Link %1: %2").arg(_udpConfig->name(), errorString));
553}
554
555void UDPLink::_onDataReceived(const QByteArray &data)
556{
557 emit bytesReceived(this, data);
558}
559
560void UDPLink::_onDataSent(const QByteArray &data)
561{
562 emit bytesSent(this, data);
563}
564
565void UDPLink::_writeBytes(const QByteArray& bytes)
566{
567 (void) QMetaObject::invokeMethod(_worker, "writeData", Qt::QueuedConnection, Q_ARG(QByteArray, bytes));
568}
569
std::shared_ptr< LinkConfiguration > SharedLinkConfigurationPtr
QString errorString
Error error
#define QGC_LOGGING_CATEGORY(name, categoryStr)
Auto connect settings.
Interface holding link specific settings.
virtual void copyFrom(const LinkConfiguration *source)
bool isAutoConnect() const
QString name() const
virtual void setAutoConnect(bool autoc=true)
Set if this is this an Auto Connect configuration.
The link interface defines the interface for all links used to communicate with the ground station ap...
void bytesReceived(LinkInterface *link, const QByteArray &data)
void disconnected()
void communicationError(const QString &title, const QString &error)
void bytesSent(LinkInterface *link, const QByteArray &data)
void connected()
AutoConnectSettings * autoConnectSettings() const
static SettingsManager * instance()
void saveSettings(QSettings &settings, const QString &root) const override
Definition UDPLink.cc:114
UDPConfiguration(const QString &name, QObject *parent=nullptr)
Definition UDPLink.cc:35
QList< std::shared_ptr< UDPClient > > targetHosts() const
Definition UDPLink.h:76
quint16 localPort() const
Definition UDPLink.h:77
Q_INVOKABLE void removeHost(const QString &host)
Definition UDPLink.cc:165
void hostListChanged()
void setAutoConnect(bool autoc=true) override
Set if this is this an Auto Connect configuration.
Definition UDPLink.cc:55
void setLocalPort(quint16 port)
Definition UDPLink.h:78
Q_INVOKABLE void addHost(const QString &host)
Definition UDPLink.cc:132
virtual ~UDPConfiguration()
Definition UDPLink.cc:48
void copyFrom(const LinkConfiguration *source) override
Definition UDPLink.cc:76
void loadSettings(QSettings &settings, const QString &root) override
Definition UDPLink.cc:93
void dataReceived(const QByteArray &data)
void disconnected()
bool isConnected() const
Definition UDPLink.cc:270
void dataSent(const QByteArray &data)
void errorOccurred(const QString &errorString)
void writeData(const QByteArray &data)
Definition UDPLink.cc:364
void setupSocket()
Definition UDPLink.cc:275
virtual ~UDPWorker()
Definition UDPLink.cc:263
UDPWorker(const UDPConfiguration *config, QObject *parent=nullptr)
Definition UDPLink.cc:256
void connected()
void connectLink()
Definition UDPLink.cc:314
void disconnectLink()
Definition UDPLink.cc:349
bool isNetworkEthernet()
Check if current network connection is Ethernet.