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