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
40UDPConfiguration::UDPConfiguration(const UDPConfiguration *source, QObject *parent)
41 : LinkConfiguration(source, parent)
42{
43 qCDebug(UDPLinkLog) << this;
44
45 UDPConfiguration::copyFrom(source);
46}
47
48UDPConfiguration::~UDPConfiguration()
49{
50 _targetHosts.clear();
51
52 qCDebug(UDPLinkLog) << this;
53}
54
55void UDPConfiguration::setAutoConnect(bool autoc)
56{
57 if (isAutoConnect() != autoc) {
58 AutoConnectSettings *const settings = SettingsManager::instance()->autoConnectSettings();
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 }
72 LinkConfiguration::setAutoConnect(autoc);
73 }
74}
75
76void UDPConfiguration::copyFrom(const LinkConfiguration *source)
77{
78 LinkConfiguration::copyFrom(source);
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#ifdef QGC_ZEROCONF_ENABLED
348 _registerZeroconf(_udpConfig->localPort());
349#endif
350}
351
353{
354#ifdef QGC_ZEROCONF_ENABLED
355 _deregisterZeroconf();
356#endif
357
358 if (!isConnected()) {
359 qCDebug(UDPLinkLog) << "Already disconnected";
360 return;
361 }
362
363 qCDebug(UDPLinkLog) << "Disconnecting UDP link";
364
365 (void) _socket->leaveMulticastGroup(_multicastGroup);
366 _socket->close();
367
368 _sessionTargets.clear();
369}
370
371void UDPWorker::writeData(const QByteArray &data)
372{
373 if (!isConnected()) {
374 emit errorOccurred(tr("Could Not Send Data - Link is Disconnected!"));
375 return;
376 }
377
378 QMutexLocker locker(&_sessionTargetsMutex);
379
380 // Send to all manually targeted systems
381 for (const std::shared_ptr<UDPClient> &target : _udpConfig->targetHosts()) {
382 if (!containsTarget(_sessionTargets, target->address, target->port)) {
383 if (_socket->writeDatagram(data, target->address, target->port) < 0) {
384 qCWarning(UDPLinkLog) << "Could Not Send Data - Write Failed!";
385 }
386 }
387 }
388
389 // Send to all connected systems
390 for (const std::shared_ptr<UDPClient> &target: _sessionTargets) {
391 if (_socket->writeDatagram(data, target->address, target->port) < 0) {
392 qCWarning(UDPLinkLog) << "Could Not Send Data - Write Failed!";
393 }
394 }
395
396 locker.unlock();
397
398 emit dataSent(data);
399}
400
401void UDPWorker::_onSocketConnected()
402{
403 qCDebug(UDPLinkLog) << "UDP connected to" << _udpConfig->localPort();
404 _isConnected = true;
405 _errorEmitted = false;
406 emit connected();
407}
408
409void UDPWorker::_onSocketDisconnected()
410{
411 qCDebug(UDPLinkLog) << "UDP disconnected from" << _udpConfig->localPort();
412 _isConnected = false;
413 _errorEmitted = false;
414 emit disconnected();
415}
416
417void UDPWorker::_onSocketReadyRead()
418{
419 if (!isConnected()) {
420 emit errorOccurred(tr("Could Not Read Data - Link is Disconnected!"));
421 return;
422 }
423
424 const qint64 byteCount = _socket->pendingDatagramSize();
425 if (byteCount <= 0) {
426 emit errorOccurred(tr("Could Not Read Data - No Data Available!"));
427 return;
428 }
429
430 QByteArray buffer;
431 buffer.reserve(BUFFER_TRIGGER_SIZE);
432 QElapsedTimer timer;
433 timer.start();
434 bool received = false;
435 while (_socket->hasPendingDatagrams()) {
436 const QNetworkDatagram datagramIn = _socket->receiveDatagram();
437 if (datagramIn.isNull() || datagramIn.data().isEmpty()) {
438 continue;
439 }
440
441 (void) buffer.append(datagramIn.data());
442
443 if ((buffer.size() > BUFFER_TRIGGER_SIZE) || (timer.elapsed() > RECEIVE_TIME_LIMIT_MS)) {
444 received = true;
445 emit dataReceived(buffer);
446 buffer.clear();
447 (void) timer.restart();
448 }
449
450 const bool ipLocal = datagramIn.senderAddress().isLoopback() || _localAddresses.contains(datagramIn.senderAddress());
451 const QHostAddress senderAddress = ipLocal ? QHostAddress(QHostAddress::SpecialAddress::LocalHost) : datagramIn.senderAddress();
452
453 QMutexLocker locker(&_sessionTargetsMutex);
454 if (!containsTarget(_sessionTargets, senderAddress, datagramIn.senderPort())) {
455 qCDebug(UDPLinkLog) << "UDP Adding target:" << senderAddress << datagramIn.senderPort();
456 _sessionTargets.append(std::make_shared<UDPClient>(senderAddress, datagramIn.senderPort()));
457 }
458 locker.unlock();
459 }
460
461 if (!received && buffer.isEmpty()) {
462 qCWarning(UDPLinkLog) << "No Data Available to Read!";
463 return;
464 }
465
466 emit dataReceived(buffer);
467}
468
469void UDPWorker::_onSocketBytesWritten(qint64 bytes)
470{
471 qCDebug(UDPLinkLog) << "Wrote" << bytes << "bytes";
472}
473
474void UDPWorker::_onSocketErrorOccurred(QUdpSocket::SocketError error)
475{
476 const QString errorString = _socket->errorString();
477 qCWarning(UDPLinkLog) << "UDP Link error:" << error << _socket->errorString();
478
479 if (!_errorEmitted) {
481 _errorEmitted = true;
482 }
483}
484
485#ifdef QGC_ZEROCONF_ENABLED
486void UDPWorker::_zeroconfRegisterCallback(DNSServiceRef sdRef, DNSServiceFlags flags, DNSServiceErrorType errorCode, const char *name, const char *regtype, const char *domain, void *context)
487{
488 Q_UNUSED(sdRef); Q_UNUSED(flags); Q_UNUSED(name); Q_UNUSED(regtype); Q_UNUSED(domain);
489
490 UDPWorker *const worker = static_cast<UDPWorker*>(context);
491 if (errorCode != kDNSServiceErr_NoError) {
492 emit worker->errorOccurred(tr("Zeroconf Register Error: %1").arg(errorCode));
493 }
494}
495
496void UDPWorker::_registerZeroconf(uint16_t port)
497{
498 static constexpr const char *regType = "_qgroundcontrol._udp";
499
500 if (_dnssServiceRef) {
501 qCWarning(UDPLinkLog) << "Already registered zeroconf";
502 return;
503 }
504
505 const DNSServiceErrorType result = DNSServiceRegister(
506 &_dnssServiceRef,
507 0,
508 0,
509 0,
510 regType,
511 NULL,
512 NULL,
513 qToBigEndian(port),
514 0,
515 NULL,
516 &UDPWorker::_zeroconfRegisterCallback,
517 this
518 );
519
520 if (result != kDNSServiceErr_NoError) {
521 _dnssServiceRef = NULL;
522 emit errorOccurred(tr("Error Registering Zeroconf: %1").arg(result));
523 return;
524 }
525
526 const int sockfd = DNSServiceRefSockFD(_dnssServiceRef);
527 if (sockfd == -1) {
528 emit errorOccurred(tr("Invalid sockfd"));
529 return;
530 }
531
532 QSocketNotifier *const socketNotifier = new QSocketNotifier(sockfd, QSocketNotifier::Read, this);
533 (void) connect(socketNotifier, &QSocketNotifier::activated, this, [this, socketNotifier]() {
534 const DNSServiceErrorType error = DNSServiceProcessResult(_dnssServiceRef);
535 if (error != kDNSServiceErr_NoError) {
536 emit errorOccurred(tr("DNSServiceProcessResult Error: %1").arg(error));
537 }
538 socketNotifier->deleteLater();
539 });
540}
541
542void UDPWorker::_deregisterZeroconf()
543{
544 if (_dnssServiceRef) {
545 DNSServiceRefDeallocate(_dnssServiceRef);
546 _dnssServiceRef = NULL;
547 }
548}
549#endif // QGC_ZEROCONF_ENABLED
550
551/*===========================================================================*/
552
554 : LinkInterface(config, parent)
555 , _udpConfig(qobject_cast<const UDPConfiguration*>(config.get()))
556 , _worker(new UDPWorker(_udpConfig))
557 , _workerThread(new QThread(this))
558{
559 qCDebug(UDPLinkLog) << this;
560
561 _workerThread->setObjectName(QStringLiteral("UDP_%1").arg(_udpConfig->name()));
562
563 _worker->moveToThread(_workerThread);
564
565 (void) connect(_workerThread, &QThread::started, _worker, &UDPWorker::setupSocket);
566 (void) connect(_workerThread, &QThread::finished, _worker, &QObject::deleteLater);
567
568 (void) connect(_worker, &UDPWorker::connected, this, &UDPLink::_onConnected, Qt::QueuedConnection);
569 (void) connect(_worker, &UDPWorker::disconnected, this, &UDPLink::_onDisconnected, Qt::QueuedConnection);
570 (void) connect(_worker, &UDPWorker::errorOccurred, this, &UDPLink::_onErrorOccurred, Qt::QueuedConnection);
571 (void) connect(_worker, &UDPWorker::dataReceived, this, &UDPLink::_onDataReceived, Qt::QueuedConnection);
572 (void) connect(_worker, &UDPWorker::dataSent, this, &UDPLink::_onDataSent, Qt::QueuedConnection);
573
574 _workerThread->start();
575}
576
578{
579 if (isConnected()) {
580 (void) QMetaObject::invokeMethod(_worker, "disconnectLink", Qt::BlockingQueuedConnection);
581 _onDisconnected();
582 }
583
584 _workerThread->quit();
585 if (!_workerThread->wait()) {
586 qCWarning(UDPLinkLog) << "Failed to wait for UDP Thread to close";
587 }
588
589 qCDebug(UDPLinkLog) << this;
590}
591
593{
594 return _worker && _worker->isConnected();
595}
596
598{
599 return QMetaObject::invokeMethod(_worker, "connectLink", Qt::QueuedConnection);
600}
601
603{
604 if (isConnected()) {
605 (void) QMetaObject::invokeMethod(_worker, "disconnectLink", Qt::QueuedConnection);
606 }
607}
608
609void UDPLink::_onConnected()
610{
611 _disconnectedEmitted = false;
612 emit connected();
613}
614
615void UDPLink::_onDisconnected()
616{
617 if (!_disconnectedEmitted.exchange(true)) {
618 emit disconnected();
619 }
620}
621
622void UDPLink::_onErrorOccurred(const QString &errorString)
623{
624 qCWarning(UDPLinkLog) << "Communication error:" << errorString;
625 emit communicationError(tr("UDP Link Error"), tr("Link %1: %2").arg(_udpConfig->name(), errorString));
626}
627
628void UDPLink::_onDataReceived(const QByteArray &data)
629{
630 emit bytesReceived(this, data);
631}
632
633void UDPLink::_onDataSent(const QByteArray &data)
634{
635 emit bytesSent(this, data);
636}
637
638void UDPLink::_writeBytes(const QByteArray& bytes)
639{
640 (void) QMetaObject::invokeMethod(_worker, "writeData", Qt::QueuedConnection, Q_ARG(QByteArray, bytes));
641}
642
std::shared_ptr< LinkConfiguration > SharedLinkConfigurationPtr
QString errorString
Error error
#define QGC_LOGGING_CATEGORY(name, categoryStr)
Fact *udpTargetHostIP READ udpTargetHostIP CONSTANT Fact * udpTargetHostIP()
Fact *udpListenPort READ udpListenPort CONSTANT Fact * udpListenPort()
Fact *udpTargetHostPort READ udpTargetHostPort CONSTANT Fact * udpTargetHostPort()
Interface holding link specific settings.
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()
void hostListChanged()
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:371
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:352
bool isNetworkEthernet()
Check if current network connection is Ethernet.