QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
LinkManager.cc
Go to the documentation of this file.
1#include "LinkManager.h"
2#include "LogReplayLink.h"
3#include "QGCNetworkHelper.h"
4#include "MAVLinkProtocol.h"
6#include "AppMessages.h"
9#include "SettingsManager.h"
10#include "MavlinkSettings.h"
11#include "AutoConnectSettings.h"
12#include "TCPLink.h"
13#include "UDPLink.h"
14
15#include "BluetoothLink.h"
16
17#include "PositionManager.h"
18#include "UdpIODevice.h"
19
20#ifndef QGC_NO_SERIAL_LINK
21#include "SerialLink.h"
22#include "GPSManager.h"
23#include "GPSRtk.h"
24#ifdef Q_OS_ANDROID
25#include "AndroidSerial.h"
26#include "AppSettings.h"
27#endif
28#endif
29
30#ifdef QT_DEBUG
31#include "MockLink.h"
32#endif
33
34#include <QtCore/QApplicationStatic>
35#include <QtCore/QTimer>
36
37QGC_LOGGING_CATEGORY(LinkManagerLog, "Comms.LinkManager")
38QGC_LOGGING_CATEGORY(LinkManagerVerboseLog, "Comms.LinkManager:verbose")
39
40Q_APPLICATION_STATIC(LinkManager, _linkManagerInstance);
41
42LinkManager::LinkManager(QObject *parent)
43 : QObject(parent)
44 , _portListTimer(new QTimer(this))
45 , _qmlConfigurations(new QmlObjectListModel(this))
46 , _nmeaSocket(new UdpIODevice(this))
47{
48 qCDebug(LinkManagerLog) << this;
49
50 (void) qRegisterMetaType<QAbstractSocket::SocketError>("QAbstractSocket::SocketError");
51 (void) qRegisterMetaType<LinkInterface*>("LinkInterface*");
52#ifndef QGC_NO_SERIAL_LINK
53 (void) qRegisterMetaType<QGCSerialPortInfo>("QGCSerialPortInfo");
54#endif
55}
56
58{
59 qCDebug(LinkManagerLog) << this;
60}
61
63{
64 return _linkManagerInstance();
65}
66
68{
69 _autoConnectSettings = SettingsManager::instance()->autoConnectSettings();
70
71#if defined(Q_OS_ANDROID) && !defined(QGC_NO_SERIAL_LINK)
72 // The serial backend is fixed at startup. Changing the setting requires an app restart.
74 SettingsManager::instance()->appSettings()->androidUsePosixSerial()->rawValue().toBool());
75#endif
76
77 if (!QGC::runningUnitTests()) {
78 (void) connect(_portListTimer, &QTimer::timeout, this, &LinkManager::_updateAutoConnectLinks);
79 _portListTimer->start(_autoconnectUpdateTimerMSecs); // timeout must be long enough to get past bootloader on second pass
80 }
81}
82
83QList<SharedLinkInterfacePtr> LinkManager::links()
84{
85 QMutexLocker locker(&_linksMutex);
86 return _rgLinks;
87}
88
89QmlObjectListModel *LinkManager::_qmlLinkConfigurations()
90{
91 return _qmlConfigurations;
92}
93
95{
96 for (SharedLinkConfigurationPtr &sharedConfig : _rgLinkConfigs) {
97 if (sharedConfig.get() == config) {
98 sharedConfig->setAutoConnectStarted(true);
99 sharedConfig->resetReconnectBackoff();
100 createConnectedLink(sharedConfig);
101 }
102 }
103}
104
106{
107 if (!link) {
108 return;
109 }
110
112 if (config) {
113 config->setSuppressAutoReconnect(true);
114 }
115
116 link->disconnect();
117}
118
120{
121 if (!config) {
122 return;
123 }
124
125 config->setSuppressAutoReconnect(true);
126
127 if (LinkInterface *const link = config->link()) {
128 link->disconnect();
129 }
130}
131
133{
134 config->setSuppressAutoReconnect(false);
135
136 SharedLinkInterfacePtr link = nullptr;
137
138 switch(config->type()) {
139#ifndef QGC_NO_SERIAL_LINK
141 link = std::make_shared<SerialLink>(config);
142 break;
143#endif
145 link = std::make_shared<UDPLink>(config);
146 break;
148 link = std::make_shared<TCPLink>(config);
149 break;
151 link = std::make_shared<BluetoothLink>(config);
152 break;
154 link = std::make_shared<LogReplayLink>(config);
155 break;
156#ifdef QT_DEBUG
157 case LinkConfiguration::TypeMock:
158 link = std::make_shared<MockLink>(config);
159 break;
160#endif
162 default:
163 break;
164 }
165
166 if (!link) {
167 return false;
168 }
169
170 if (!link->_allocateMavlinkChannel()) {
171 qCWarning(LinkManagerLog) << "Link failed to setup mavlink channels";
172 return false;
173 }
174
175 // Set up signal connections before adding to list, so link is fully initialized
176 (void) connect(link.get(), &LinkInterface::communicationError, this, &LinkManager::_communicationError);
179 (void) connect(link.get(), &LinkInterface::connected, this, &LinkManager::_linkConnected);
180 (void) connect(link.get(), &LinkInterface::disconnected, this, &LinkManager::_linkDisconnected);
181
183
184 // Try to connect before adding to active links list
185 if (!link->_connect()) {
186 (void) disconnect(link.get(), &LinkInterface::communicationError, this, &LinkManager::_communicationError);
189 (void) disconnect(link.get(), &LinkInterface::disconnected, this, &LinkManager::_linkDisconnected);
190 link->_freeMavlinkChannel();
191 config->setLink(nullptr);
192 return false;
193 }
194
195 {
196 QMutexLocker locker(&_linksMutex);
197 _rgLinks.append(link);
198 }
199 config->setLink(link);
200
201 return true;
202}
203
204void LinkManager::_linkConnected()
205{
206 const LinkInterface *const link = qobject_cast<LinkInterface*>(sender());
207 const SharedLinkConfigurationPtr config = link ? link->linkConfiguration() : nullptr;
208 if (config) {
209 config->noteConnected();
210 }
211}
212
213void LinkManager::_communicationError(const QString &title, const QString &error)
214{
215 const LinkInterface *const link = qobject_cast<LinkInterface*>(sender());
216 const SharedLinkConfigurationPtr config = link ? link->linkConfiguration() : nullptr;
217
218 // Auto-connect links retry on a timer; a popup per failed attempt is just noise. Log only.
219 if (config && config->isAutoConnect() && !config->suppressAutoReconnect()) {
220 qCDebug(LinkManagerLog) << "Auto-connect link error (will retry):" << title << error;
221 return;
222 }
223
225}
226
228{
229 QMutexLocker locker(&_linksMutex);
230
231 for (const SharedLinkInterfacePtr &link : _rgLinks) {
232 const SharedLinkConfigurationPtr linkConfig = link->linkConfiguration();
233 if (linkConfig && (linkConfig->type() == LinkConfiguration::TypeUdp) && (linkConfig->name() == _mavlinkForwardingLinkName)) {
234 return link;
235 }
236 }
237
238 return nullptr;
239}
240
242{
243 QMutexLocker locker(&_linksMutex);
244
245 for (const SharedLinkInterfacePtr &link : _rgLinks) {
246 const SharedLinkConfigurationPtr linkConfig = link->linkConfiguration();
247 if (linkConfig && (linkConfig->type() == LinkConfiguration::TypeUdp) && (linkConfig->name() == _mavlinkForwardingSupportLinkName)) {
248 return link;
249 }
250 }
251
252 return nullptr;
253}
254
256{
257 QList<SharedLinkInterfacePtr> links;
258 {
259 QMutexLocker locker(&_linksMutex);
260 links = _rgLinks;
261 }
262
263 for (const SharedLinkInterfacePtr &sharedLink: links) {
264 sharedLink->disconnect();
265 }
266}
267
268void LinkManager::_linkDisconnected()
269{
270 LinkInterface* const link = qobject_cast<LinkInterface*>(sender());
271
272 if (!link) {
273 return;
274 }
275
276 SharedLinkInterfacePtr linkToCleanup;
278 {
279 QMutexLocker locker(&_linksMutex);
280
281 for (auto it = _rgLinks.begin(); it != _rgLinks.end(); ++it) {
282 if (it->get() == link) {
283 config = it->get()->linkConfiguration();
284 const QString linkName = config ? config->name() : QStringLiteral("<null config>");
285 qCDebug(LinkManagerLog) << linkName << "use_count:" << it->use_count();
286 linkToCleanup = *it;
287 (void) _rgLinks.erase(it);
288 break;
289 }
290 }
291 }
292
293 if (!linkToCleanup) {
294 qCDebug(LinkManagerLog) << "link already removed";
295 return;
296 }
297
298 if (config) {
299 config->noteDisconnected();
300 config->setLink(nullptr);
301 }
302
303 (void) disconnect(link, &LinkInterface::communicationError, this, &LinkManager::_communicationError);
306 (void) disconnect(link, &LinkInterface::connected, this, &LinkManager::_linkConnected);
307 (void) disconnect(link, &LinkInterface::disconnected, this, &LinkManager::_linkDisconnected);
308
309 link->_freeMavlinkChannel();
310}
311
313{
314 QMutexLocker locker(&_linksMutex);
315
316 for (const SharedLinkInterfacePtr &sharedLink: _rgLinks) {
317 if (sharedLink.get() == link) {
318 return sharedLink;
319 }
320 }
321
322 // Link not found - this is normal during disconnect when queued signals are still processing.
323 // Callers should check for nullptr return value.
324 qCDebug(LinkManagerLog) << "link not in list (likely disconnected)";
325 return SharedLinkInterfacePtr(nullptr);
326}
327
328bool LinkManager::_connectionsSuspendedMsg() const
329{
330 if (_connectionsSuspended) {
331 QGC::showAppMessage(tr("Connect not allowed: %1").arg(_connectionsSuspendedReason));
332 return true;
333 }
334
335 return false;
336}
337
339{
340 QSettings settings;
341 settings.remove(LinkConfiguration::settingsRoot());
342
343 int trueCount = 0;
344 for (int i = 0; i < _rgLinkConfigs.count(); i++) {
345 SharedLinkConfigurationPtr linkConfig = _rgLinkConfigs[i];
346 if (!linkConfig) {
347 qCWarning(LinkManagerLog) << "Internal error for link configuration in LinkManager";
348 continue;
349 }
350
351 if (linkConfig->isDynamic()) {
352 continue;
353 }
354
355 const QString root = LinkConfiguration::settingsRoot() + QStringLiteral("/Link%1").arg(trueCount++);
356 settings.setValue(root + "/name", linkConfig->name());
357 settings.setValue(root + "/type", linkConfig->type());
358 settings.setValue(root + "/auto", linkConfig->isAutoConnect());
359 settings.setValue(root + "/high_latency", linkConfig->isHighLatency());
360 linkConfig->saveSettings(settings, root);
361 }
362
363 const QString root = QString(LinkConfiguration::settingsRoot());
364 settings.setValue(root + "/count", trueCount);
365}
366
368{
369 QSettings settings;
370 // Is the group even there?
371 if (settings.contains(LinkConfiguration::settingsRoot() + "/count")) {
372 // Find out how many configurations we have
373 const int count = settings.value(LinkConfiguration::settingsRoot() + "/count").toInt();
374 for (int i = 0; i < count; i++) {
375 const QString root = LinkConfiguration::settingsRoot() + QStringLiteral("/Link%1").arg(i);
376 if (!settings.contains(root + "/type")) {
377 qCWarning(LinkManagerLog) << "Link Configuration" << root << "has no type.";
378 continue;
379 }
380
381 LinkConfiguration::LinkType type = static_cast<LinkConfiguration::LinkType>(settings.value(root + "/type").toInt());
382 if (type >= LinkConfiguration::TypeLast) {
383 qCWarning(LinkManagerLog) << "Link Configuration" << root << "an invalid type:" << type;
384 continue;
385 }
386
387 if (!settings.contains(root + "/name")) {
388 qCWarning(LinkManagerLog) << "Link Configuration" << root << "has no name.";
389 continue;
390 }
391
392 const QString name = settings.value(root + "/name").toString();
393 if (name.isEmpty()) {
394 qCWarning(LinkManagerLog) << "Link Configuration" << root << "has an empty name.";
395 continue;
396 }
397
398 LinkConfiguration* link = nullptr;
399 switch(type) {
400#ifndef QGC_NO_SERIAL_LINK
402 link = new SerialConfiguration(name);
403 break;
404#endif
406 link = new UDPConfiguration(name);
407 break;
409 link = new TCPConfiguration(name);
410 break;
412 link = new BluetoothConfiguration(name);
413 break;
415 link = new LogReplayConfiguration(name);
416 break;
417#ifdef QT_DEBUG
418 case LinkConfiguration::TypeMock:
419 link = new MockConfiguration(name);
420 break;
421#endif
423 default:
424 break;
425 }
426
427 if (link) {
428 const bool autoConnect = settings.value(root + "/auto").toBool();
429 link->setAutoConnect(autoConnect);
430 const bool highLatency = settings.value(root + "/high_latency").toBool();
431 link->setHighLatency(highLatency);
432 link->loadSettings(settings, root);
433 addConfiguration(link);
434 }
435 }
436 }
437
438 // Enable automatic Serial PX4/3DR Radio hunting
439 _configurationsLoaded = true;
440}
441
442void LinkManager::_addUDPAutoConnectLink()
443{
444 if (!_autoConnectSettings->autoConnectUDP()->rawValue().toBool()) {
445 return;
446 }
447
448 {
449 QMutexLocker locker(&_linksMutex);
450 for (const SharedLinkInterfacePtr &link : _rgLinks) {
451 const SharedLinkConfigurationPtr linkConfig = link->linkConfiguration();
452 if (linkConfig && (linkConfig->type() == LinkConfiguration::TypeUdp) && (linkConfig->name() == _defaultUDPLinkName)) {
453 return;
454 }
455 }
456 }
457
458 qCDebug(LinkManagerLog) << "New auto-connect UDP port added";
459 UDPConfiguration* const udpConfig = new UDPConfiguration(_defaultUDPLinkName);
460 udpConfig->setDynamic(true);
461 udpConfig->setAutoConnect(true);
464}
465
466void LinkManager::_addMAVLinkForwardingLink()
467{
468 if (!SettingsManager::instance()->mavlinkSettings()->forwardMavlink()->rawValue().toBool()) {
469 return;
470 }
471
472 {
473 QMutexLocker locker(&_linksMutex);
474 for (const SharedLinkInterfacePtr &link : _rgLinks) {
475 const SharedLinkConfigurationPtr linkConfig = link->linkConfiguration();
476 if (linkConfig && (linkConfig->type() == LinkConfiguration::TypeUdp) && (linkConfig->name() == _mavlinkForwardingLinkName)) {
477 // TODO: should we check if the host/port matches the mavlinkForwardHostName setting and update if it does not match?
478 return;
479 }
480 }
481 }
482
483 const QString hostName = SettingsManager::instance()->mavlinkSettings()->forwardMavlinkHostName()->rawValue().toString();
484 _createDynamicForwardLink(_mavlinkForwardingLinkName, hostName);
485}
486
487void LinkManager::_reconnectAutoConnectLinks()
488{
489 for (SharedLinkConfigurationPtr &config : _rgLinkConfigs) {
490 if (!config || config->isDynamic() || !config->isAutoConnect()) {
491 continue;
492 }
493
494 // Only re-establish links started this session (boot or manual connect); a freshly
495 // added auto-connect config waits for next app start rather than connecting now.
496 if (config->link() || config->suppressAutoReconnect() || !config->autoConnectStarted()) {
497 continue;
498 }
499
500 // Exponential backoff between attempts so a dead host isn't hammered every tick.
501 if (!config->reconnectReady()) {
502 continue;
503 }
504
505 qCDebug(LinkManagerLog) << "Reconnecting auto-connect link" << config->name();
506 config->noteReconnectAttempt();
508 }
509}
510
511void LinkManager::_updateAutoConnectLinks()
512{
513 if (_connectionsSuspended) {
514 return;
515 }
516
517 _addUDPAutoConnectLink();
518 _addMAVLinkForwardingLink();
519 _reconnectAutoConnectLinks();
520
521 const int nmeaSource = _autoConnectSettings->nmeaSource()->rawValue().toInt();
522 if (nmeaSource == AutoConnectSettings::NmeaSourceUdp) {
523 if ((_nmeaSocket->localPort() != _autoConnectSettings->nmeaUdpPort()->rawValue().toUInt()) || (_nmeaSocket->state() != UdpIODevice::BoundState)) {
524 qCDebug(LinkManagerLog) << "Changing port for UDP NMEA stream";
525 _nmeaSocket->close();
526 _nmeaSocket->bind(QHostAddress::AnyIPv4, _autoConnectSettings->nmeaUdpPort()->rawValue().toUInt());
528 }
529 } else {
530 _nmeaSocket->close();
531
532 if (nmeaSource == AutoConnectSettings::NmeaSourceDisabled) {
533 // Revert QGCPositionManager to the integrated GPS if it was using an NMEA source.
534 // Reset before deleting the port so the NMEA source never holds a dangling device.
536 }
537 }
538
539#ifndef QGC_NO_SERIAL_LINK
540 // Serial NMEA ports are set up by _addSerialAutoConnectLink() below
541 if ((nmeaSource != AutoConnectSettings::NmeaSourceSerial) && _nmeaPort) {
542 _nmeaPort->close();
543 delete _nmeaPort;
544 _nmeaPort = nullptr;
545 _nmeaDeviceName = "";
546 }
547
548 _addSerialAutoConnectLink();
549#endif
550}
551
553{
554 setConnectionsSuspended(tr("Shutdown"));
556
557 // Wait for all the vehicles to go away to ensure an orderly shutdown and deletion of all objects
558 while (MultiVehicleManager::instance()->vehicles()->count()) {
559 QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
560 }
561}
562
564{
565 //-- Must follow same order as enum LinkType in LinkConfiguration.h
566 static QStringList list;
567 if (!list.isEmpty()) {
568 return list;
569 }
570
571#ifndef QGC_NO_SERIAL_LINK
572 list += tr("Serial");
573#endif
574 list += tr("UDP");
575 list += tr("TCP");
576 list += tr("Bluetooth");
577#ifdef QT_DEBUG
578 list += tr("Mock Link");
579#endif
580 list += tr("Log Replay");
581
582 if (list.size() != static_cast<int>(LinkConfiguration::TypeLast)) {
583 qCWarning(LinkManagerLog) << "Internal error";
584 }
585
586 return list;
587}
588
590{
591 if (!config || !editedConfig) {
592 qCWarning(LinkManagerLog) << "Internal error";
593 return;
594 }
595
596 config->copyFrom(editedConfig);
598 emit config->nameChanged(config->name());
599 // Discard temporary duplicate
600 delete editedConfig;
601}
602
604{
605 if (!config) {
606 qCWarning(LinkManagerLog) << "Internal error";
607 return;
608 }
609
612}
613
615{
616#ifndef QGC_NO_SERIAL_LINK
618 _updateSerialPorts();
619 }
620#endif
621
622 return LinkConfiguration::createSettings(type, name);
623}
624
626{
627 if (!config) {
628 qCWarning(LinkManagerLog) << "Internal error";
629 return nullptr;
630 }
631
632#ifndef QGC_NO_SERIAL_LINK
633 if (config->type() == LinkConfiguration::TypeSerial) {
634 _updateSerialPorts();
635 }
636#endif
637
639}
640
642{
643 if (!config) {
644 qCWarning(LinkManagerLog) << "Internal error";
645 return;
646 }
647
648 LinkInterface* const link = config->link();
649 if (link) {
650 link->disconnect();
651 }
652
653 _removeConfiguration(config);
655}
656
658{
659 const QString hostName = SettingsManager::instance()->mavlinkSettings()->forwardMavlinkAPMSupportHostName()->rawValue().toString();
660 _createDynamicForwardLink(_mavlinkForwardingSupportLinkName, hostName);
661 _mavlinkSupportForwardingEnabled = true;
663}
664
665void LinkManager::_removeConfiguration(const LinkConfiguration *config)
666{
667 (void) _qmlConfigurations->removeOne(config);
668
669 for (auto it = _rgLinkConfigs.begin(); it != _rgLinkConfigs.end(); ++it) {
670 if (it->get() == config) {
671 (void) _rgLinkConfigs.erase(it);
672 return;
673 }
674 }
675
676 qCWarning(LinkManagerLog) << "called with unknown config";
677}
678
683
685{
686 QMutexLocker locker(&_linksMutex);
687
688 for (const SharedLinkInterfacePtr &sharedLink : _rgLinks) {
689 if (sharedLink.get() == link) {
690 return true;
691 }
692 }
693
694 return false;
695}
696
698{
699 (void) _qmlConfigurations->append(config);
700 (void) _rgLinkConfigs.append(SharedLinkConfigurationPtr(config));
701
702 return _rgLinkConfigs.last();
703}
704
706{
707 for (SharedLinkConfigurationPtr &sharedConfig : _rgLinkConfigs) {
708 if (sharedConfig->isAutoConnect()) {
709 sharedConfig->setAutoConnectStarted(true);
710 createConnectedLink(sharedConfig);
711 }
712 }
713}
714
716{
717 for (uint8_t mavlinkChannel = 0; mavlinkChannel < MAVLINK_COMM_NUM_BUFFERS; mavlinkChannel++) {
718 if (_mavlinkChannelsUsedBitMask & (1 << mavlinkChannel)) {
719 continue;
720 }
721
722 mavlink_reset_channel_status(mavlinkChannel);
723 mavlink_status_t* const mavlinkStatus = mavlink_get_channel_status(mavlinkChannel);
724 mavlinkStatus->flags |= MAVLINK_STATUS_FLAG_OUT_MAVLINK1;
725 _mavlinkChannelsUsedBitMask |= (1 << mavlinkChannel);
726 qCDebug(LinkManagerLog) << "allocateMavlinkChannel" << mavlinkChannel;
727 return mavlinkChannel;
728 }
729
730 qCWarning(LinkManagerLog) << "allocateMavlinkChannel: all channels reserved!";
731 return invalidMavlinkChannel();
732}
733
735{
736 qCDebug(LinkManagerLog) << "freeMavlinkChannel" << channel;
737
738 if (invalidMavlinkChannel() == channel) {
739 return;
740 }
741
742 _mavlinkChannelsUsedBitMask &= ~(1 << channel);
743}
744
746{
747 LogReplayConfiguration* const linkConfig = new LogReplayConfiguration(tr("Log Replay"));
748 linkConfig->setLogFilename(logFile);
749 linkConfig->setName(linkConfig->logFilenameShort());
750
751 SharedLinkConfigurationPtr sharedConfig = addConfiguration(linkConfig);
752 if (createConnectedLink(sharedConfig)) {
753 return qobject_cast<LogReplayLink*>(sharedConfig->link());
754 }
755
756 return nullptr;
757}
758
759void LinkManager::_createDynamicForwardLink(const char *linkName, const QString &hostName)
760{
761 UDPConfiguration* const udpConfig = new UDPConfiguration(linkName);
762
763 udpConfig->setDynamic(true);
764 udpConfig->setForwarding(true);
765 udpConfig->addHost(hostName);
766
769
770 qCDebug(LinkManagerLog) << "New dynamic MAVLink forwarding port added:" << linkName << " hostname:" << hostName;
771}
772
773bool LinkManager::isLinkUSBDirect([[maybe_unused]] const LinkInterface *link)
774{
775#ifndef QGC_NO_SERIAL_LINK
776 const SerialLink* const serialLink = qobject_cast<const SerialLink*>(link);
777 if (!serialLink) {
778 return false;
779 }
780
782 if (!config) {
783 return false;
784 }
785
786 const SerialConfiguration* const serialConfig = qobject_cast<const SerialConfiguration*>(config.get());
787 if (serialConfig && serialConfig->usbDirect()) {
788 return link;
789 }
790#endif
791
792 return false;
793}
794
795#ifndef QGC_NO_SERIAL_LINK // Serial Only Functions
796
797void LinkManager::_filterCompositePorts(QList<QGCSerialPortInfo> &portList)
798{
799 typedef QPair<quint16, quint16> VidPidPair_t;
800
801 QMap<VidPidPair_t, QStringList> seenSerialNumbers;
802
803 for (auto it = portList.begin(); it != portList.end();) {
804 const QGCSerialPortInfo &portInfo = *it;
805 if (portInfo.hasVendorIdentifier() && portInfo.hasProductIdentifier() && !portInfo.serialNumber().isEmpty() && portInfo.serialNumber() != "0") {
806 VidPidPair_t vidPid(portInfo.vendorIdentifier(), portInfo.productIdentifier());
807 if (seenSerialNumbers.contains(vidPid) && seenSerialNumbers[vidPid].contains(portInfo.serialNumber())) {
808 // Some boards are a composite USB device, with the first port being mavlink and the second something else. We only expose to first mavlink port.
809 // However internal NMEA devices can present like this, so dont skip anything with NMEA in description
810 if(!portInfo.description().contains("NMEA")) {
811 qCDebug(LinkManagerVerboseLog) << QStringLiteral("Removing secondary port on same device - port:%1 vid:%2 pid%3 sn:%4").arg(portInfo.portName()).arg(portInfo.vendorIdentifier()).arg(portInfo.productIdentifier()).arg(portInfo.serialNumber());
812 it = portList.erase(it);
813 continue;
814 }
815 }
816 seenSerialNumbers[vidPid].append(portInfo.serialNumber());
817 }
818 it++;
819 }
820}
821
822void LinkManager::_addSerialAutoConnectLink()
823{
824 QList<QGCSerialPortInfo> portList;
825#ifdef Q_OS_ANDROID
826 // With the Java USB serial backend only a single serial connection is supported. Repeatedly calling
827 // availablePorts after that one serial port is connected leaks file handles due to a bug somewhere in the
828 // android serial code. In order to work around that bug after we connect the first serial port we stop
829 // probing for additional ports. The POSIX backend does not have this problem.
830 if (AndroidSerial::usePosixSerial() || !_isSerialPortConnected()) {
832 }
833#else
835#endif
836
837 _filterCompositePorts(portList);
838
839 QStringList currentPorts;
840 for (const QGCSerialPortInfo &portInfo: portList) {
841 qCDebug(LinkManagerVerboseLog) << "-----------------------------------------------------";
842 qCDebug(LinkManagerVerboseLog) << "portName: " << portInfo.portName();
843 qCDebug(LinkManagerVerboseLog) << "systemLocation: " << portInfo.systemLocation();
844 qCDebug(LinkManagerVerboseLog) << "description: " << portInfo.description();
845 qCDebug(LinkManagerVerboseLog) << "manufacturer: " << portInfo.manufacturer();
846 qCDebug(LinkManagerVerboseLog) << "serialNumber: " << portInfo.serialNumber();
847 qCDebug(LinkManagerVerboseLog) << "vendorIdentifier: " << portInfo.vendorIdentifier();
848 qCDebug(LinkManagerVerboseLog) << "productIdentifier: " << portInfo.productIdentifier();
849
850 currentPorts << portInfo.systemLocation();
851
853 QString boardName;
854
855 // check to see if nmea gps is configured for current Serial port, if so, set it up to connect
856 if ((_autoConnectSettings->nmeaSource()->rawValue().toInt() == AutoConnectSettings::NmeaSourceSerial) &&
857 (portInfo.systemLocation().trimmed() == _autoConnectSettings->autoConnectNmeaPort()->cookedValueString())) {
858 if (portInfo.systemLocation().trimmed() != _nmeaDeviceName) {
859 _nmeaDeviceName = portInfo.systemLocation().trimmed();
860 qCDebug(LinkManagerLog) << "Configuring nmea port" << _nmeaDeviceName;
861 QSerialPort* newPort = new QSerialPort(portInfo, this);
862 _nmeaBaud = _autoConnectSettings->autoConnectNmeaBaud()->cookedValue().toUInt();
863 newPort->setBaudRate(static_cast<qint32>(_nmeaBaud));
864 qCDebug(LinkManagerLog) << "Configuring nmea baudrate" << _nmeaBaud;
865 // This will stop polling old device if previously set
867 if (_nmeaPort) {
868 delete _nmeaPort;
869 }
870 _nmeaPort = newPort;
871 } else if (_autoConnectSettings->autoConnectNmeaBaud()->cookedValue().toUInt() != _nmeaBaud) {
872 _nmeaBaud = _autoConnectSettings->autoConnectNmeaBaud()->cookedValue().toUInt();
873 _nmeaPort->setBaudRate(static_cast<qint32>(_nmeaBaud));
874 qCDebug(LinkManagerLog) << "Configuring nmea baudrate" << _nmeaBaud;
875 }
876 } else if (portInfo.getBoardInfo(boardType, boardName)) {
877 // Should we be auto-connecting to this board type?
878 if (!_allowAutoConnectToBoard(boardType)) {
879 continue;
880 }
881
882 if (portInfo.isBootloader()) {
883 // Don't connect to bootloader
884 qCDebug(LinkManagerLog) << "Waiting for bootloader to finish" << portInfo.systemLocation();
885 continue;
886 }
887 if (_portAlreadyConnected(portInfo.systemLocation()) || (_autoConnectRTKPort == portInfo.systemLocation())) {
888 qCDebug(LinkManagerVerboseLog) << "Skipping existing autoconnect" << portInfo.systemLocation();
889 } else if (!_autoconnectPortWaitList.contains(portInfo.systemLocation())) {
890 // We don't connect to the port the first time we see it. The ability to correctly detect whether we
891 // are in the bootloader is flaky from a cross-platform standpoint. So by putting it on a wait list
892 // and only connect on the second pass we leave enough time for the board to boot up.
893 qCDebug(LinkManagerLog) << "Waiting for next autoconnect pass" << portInfo.systemLocation() << boardName;
894 _autoconnectPortWaitList[portInfo.systemLocation()] = 1;
895 } else if ((++_autoconnectPortWaitList[portInfo.systemLocation()] * _autoconnectUpdateTimerMSecs) > _autoconnectConnectDelayMSecs) {
896 SerialConfiguration* pSerialConfig = nullptr;
897 _autoconnectPortWaitList.remove(portInfo.systemLocation());
898 switch (boardType) {
900 pSerialConfig = new SerialConfiguration(tr("%1 on %2 (AutoConnect)").arg(boardName, portInfo.portName().trimmed()));
901 pSerialConfig->setUsbDirect(true);
902 break;
904 pSerialConfig = new SerialConfiguration(tr("%1 on %2 (AutoConnect)").arg(boardName, portInfo.portName().trimmed()));
905 break;
907 pSerialConfig = new SerialConfiguration(tr("%1 on %2 (AutoConnect)").arg(boardName, portInfo.portName().trimmed()));
908 break;
910 qCDebug(LinkManagerLog) << "RTK GPS auto-connected" << portInfo.portName().trimmed();
911 _autoConnectRTKPort = portInfo.systemLocation();
912 GPSManager::instance()->gpsRtk()->connectGPS(portInfo.systemLocation(), boardName);
913 break;
914 default:
915 qCWarning(LinkManagerLog) << "Internal error: Unknown board type" << boardType;
916 continue;
917 }
918
919 if (pSerialConfig) {
920 qCDebug(LinkManagerLog) << "New auto-connect port added: " << pSerialConfig->name() << portInfo.systemLocation();
921 pSerialConfig->setBaud((boardType == QGCSerialPortInfo::BoardTypeSiKRadio) ? 57600 : 115200);
922 pSerialConfig->setDynamic(true);
923 pSerialConfig->setPortName(portInfo.systemLocation());
924 pSerialConfig->setAutoConnect(true);
925
926 SharedLinkConfigurationPtr sharedConfig(pSerialConfig);
927 createConnectedLink(sharedConfig);
928 }
929 }
930 }
931 }
932
933 // Check for RTK GPS connection gone
934 if (!_autoConnectRTKPort.isEmpty() && !currentPorts.contains(_autoConnectRTKPort)) {
935 qCDebug(LinkManagerLog) << "RTK GPS disconnected" << _autoConnectRTKPort;
937 _autoConnectRTKPort.clear();
938 }
939}
940
941bool LinkManager::_allowAutoConnectToBoard(QGCSerialPortInfo::BoardType_t boardType) const
942{
943 switch (boardType) {
945 if (_autoConnectSettings->autoConnectPixhawk()->rawValue().toBool()) {
946 return true;
947 }
948 break;
950 if (_autoConnectSettings->autoConnectSiKRadio()->rawValue().toBool()) {
951 return true;
952 }
953 break;
955 if (_autoConnectSettings->autoConnectLibrePilot()->rawValue().toBool()) {
956 return true;
957 }
958 break;
960 if (_autoConnectSettings->autoConnectRTKGPS()->rawValue().toBool() && !GPSManager::instance()->gpsRtk()->connected()) {
961 return true;
962 }
963 break;
964 default:
965 qCWarning(LinkManagerLog) << "Internal error: Unknown board type" << boardType;
966 return false;
967 }
968
969 return false;
970}
971
972bool LinkManager::_portAlreadyConnected(const QString &portName)
973{
974 QMutexLocker locker(&_linksMutex);
975
976 const QString searchPort = portName.trimmed();
977 for (const SharedLinkInterfacePtr &linkInterface : _rgLinks) {
978 const SharedLinkConfigurationPtr linkConfig = linkInterface->linkConfiguration();
979 const SerialConfiguration* const serialConfig = qobject_cast<const SerialConfiguration*>(linkConfig.get());
980 if (serialConfig && (serialConfig->portName() == searchPort)) {
981 return true;
982 }
983 }
984
985 return false;
986}
987
988void LinkManager::_updateSerialPorts()
989{
990 _commPortList.clear();
991 _commPortDisplayList.clear();
992 const QList<QGCSerialPortInfo> portList = QGCSerialPortInfo::availablePorts();
993 for (const QGCSerialPortInfo &info: portList) {
994 const QString port = info.systemLocation().trimmed();
995 _commPortList += port;
996 _commPortDisplayList += SerialConfiguration::cleanPortDisplayName(port);
997 }
998}
999
1001{
1002 if (_commPortDisplayList.isEmpty()) {
1003 _updateSerialPorts();
1004 }
1005
1006 return _commPortDisplayList;
1007}
1008
1010{
1011 if (_commPortList.isEmpty()) {
1012 _updateSerialPorts();
1013 }
1014
1015 return _commPortList;
1016}
1017
1022
1023bool LinkManager::_isSerialPortConnected()
1024{
1025 QMutexLocker locker(&_linksMutex);
1026
1027 for (const SharedLinkInterfacePtr &link: _rgLinks) {
1028 if (qobject_cast<const SerialLink*>(link.get())) {
1029 return true;
1030 }
1031 }
1032
1033 return false;
1034}
1035
1036#endif // QGC_NO_SERIAL_LINK
Config config
std::shared_ptr< LinkConfiguration > SharedLinkConfigurationPtr
std::shared_ptr< LinkInterface > SharedLinkInterfacePtr
Q_APPLICATION_STATIC(LinkManager, _linkManagerInstance)
mavlink_status_t * mavlink_get_channel_status(uint8_t chan)
Definition QGCMAVLink.cc:53
#define MAVLINK_COMM_NUM_BUFFERS
Error error
#define QGC_LOGGING_CATEGORY(name, categoryStr)
GPSRtk * gpsRtk()
Definition GPSManager.h:17
static GPSManager * instance()
Definition GPSManager.cc:23
void disconnectGPS()
Definition GPSRtk.cc:140
void connectGPS(const QString &device, QStringView gps_type)
Definition GPSRtk.cc:87
bool connected() const
Definition GPSRtk.cc:155
Interface holding link specific settings.
@ TypeSerial
Serial Link.
@ TypeBluetooth
Bluetooth Link.
virtual void loadSettings(QSettings &settings, const QString &root)=0
static LinkConfiguration * duplicateSettings(const LinkConfiguration *source)
void setDynamic(bool dynamic=true)
Set if this is this a dynamic configuration. (decided at runtime)
void setHighLatency(bool hl=false)
Set if this is this an High Latency configuration.
static LinkConfiguration * createSettings(int type, const QString &name)
void setForwarding(bool forwarding=true)
Set if this is this a forwarding link configuration. (decided at runtime)
static QString settingsRoot()
void setName(const QString &name)
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()
virtual void _freeMavlinkChannel()
virtual Q_INVOKABLE void disconnect()=0
void communicationError(const QString &title, const QString &error)
void bytesSent(LinkInterface *link, const QByteArray &data)
void connected()
SharedLinkConfigurationPtr linkConfiguration()
Manage communication links The Link Manager organizes the physical Links. It can manage arbitrary lin...
Definition LinkManager.h:32
Q_INVOKABLE void createConnectedLink(const LinkConfiguration *config)
This should only be used by Qml code.
SharedLinkInterfacePtr sharedLinkInterfacePointerForLink(const LinkInterface *link)
QStringList serialPorts()
Q_INVOKABLE void endConfigurationEditing(LinkConfiguration *config, LinkConfiguration *editedConfig)
SharedLinkConfigurationPtr addConfiguration(LinkConfiguration *config)
QStringList linkTypeStrings() const
void loadLinkConfigurationList()
static bool isLinkUSBDirect(const LinkInterface *link)
uint8_t allocateMavlinkChannel()
Q_INVOKABLE void createMavlinkForwardingSupportLink()
Q_INVOKABLE LinkConfiguration * createConfiguration(int type, const QString &name)
Create/Edit Link Configuration.
void startAutoConnectedLinks()
Q_INVOKABLE void endCreateConfiguration(LinkConfiguration *config)
Q_INVOKABLE void shutdown()
Called to signal app shutdown. Disconnects all links while turning off auto-connect.
static LinkManager * instance()
void freeMavlinkChannel(uint8_t channel)
Q_INVOKABLE void removeConfiguration(LinkConfiguration *config)
QList< SharedLinkInterfacePtr > links()
static constexpr uint8_t invalidMavlinkChannel()
void setConnectionsSuspended(const QString &reason)
Definition LinkManager.h:79
void disconnectAll()
Q_INVOKABLE LogReplayLink * startLogReplay(const QString &logFile)
QStringList serialPortStrings()
Q_INVOKABLE void disconnectLink(LinkInterface *link)
static QStringList serialBaudRates()
SharedLinkInterfacePtr mavlinkForwardingSupportLink()
Returns pointer to the mavlink support forwarding link, or nullptr if it does not exist.
Q_INVOKABLE void disconnectLinkConfiguration(LinkConfiguration *config)
Stop a link and suppress auto-reconnect, working whether or not a live link currently exists.
Q_INVOKABLE LinkConfiguration * startConfigurationEditing(LinkConfiguration *config)
bool containsLink(const LinkInterface *link)
SharedLinkInterfacePtr mavlinkForwardingLink()
Returns pointer to the mavlink forwarding link, or nullptr if it does not exist.
static bool isBluetoothAvailable()
void saveLinkConfigurationList()
void mavlinkSupportForwardingEnabledChanged()
QString logFilenameShort() const
void setLogFilename(const QString &logFilename)
void receiveBytes(LinkInterface *link, const QByteArray &data)
void logSentBytes(const LinkInterface *link, const QByteArray &data)
static MAVLinkProtocol * instance()
void resetMetadataForLink(LinkInterface *link)
static MultiVehicleManager * instance()
static QGCPositionManager * instance()
void setNmeaSourceDevice(QIODevice *device)
QGC's version of Qt QSerialPortInfo. It provides additional information about board types that QGC ca...
bool getBoardInfo(BoardType_t &boardType, QString &name) const
static QList< QGCSerialPortInfo > availablePorts()
Override of QSerialPortInfo::availablePorts.
quint16 productIdentifier() const
Returns the 16-bit product number for the serial port, if available; otherwise returns zero.
QString manufacturer() const
Returns the manufacturer string of the serial port, if available; otherwise returns an empty string.
QString portName() const
Returns the name of the serial port.
bool hasVendorIdentifier() const
Returns true if there is a valid 16-bit vendor number present; otherwise returns false.
QString serialNumber() const
QString systemLocation() const
Returns the system location of the serial port.
QString description() const
Returns the description string of the serial port, if available; otherwise returns an empty string.
quint16 vendorIdentifier() const
Returns the 16-bit vendor number for the serial port, if available; otherwise returns zero.
bool hasProductIdentifier() const
Returns true if there is a valid 16-bit product number present; otherwise returns false.
Provides functions to access serial ports.
Definition qserialport.h:17
void close() override
\reimp
bool setBaudRate(qint32 baudRate, Directions directions=AllDirections)
void append(QObject *object)
Caller maintains responsibility for object ownership and deletion.
QObject * removeOne(const QObject *object) override final
void setBaud(qint32 baud)
Definition SerialLink.h:46
bool usbDirect() const
Definition SerialLink.h:66
static QStringList supportedBaudRates()
QString portName() const
Definition SerialLink.h:60
void setUsbDirect(bool usbDirect)
Definition SerialLink.h:67
static QString cleanPortDisplayName(const QString &name)
void setPortName(const QString &name)
Definition SerialLink.cc:36
AutoConnectSettings * autoConnectSettings() const
static SettingsManager * instance()
MavlinkSettings * mavlinkSettings() const
void setAutoConnect(bool autoc=true) override
Set if this is this an Auto Connect configuration.
Definition UDPLink.cc:66
Q_INVOKABLE void addHost(const QString &host)
Definition UDPLink.cc:145
UdpIODevice provides a QIODevice interface over a QUdpSocket in server mode.
Definition UdpIODevice.h:11
bool usePosixSerial()
void setUsePosixSerial(bool use)
bool isBluetoothAvailable()
Check if Bluetooth is available on this device.
bool runningUnitTests()
void showAppMessage(const QString &message, const QString &title)
Modal application message. Queued if the UI isn't ready yet.
Definition AppMessages.cc:9