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#endif
25
26#ifdef QT_DEBUG
27#include "MockLink.h"
28#endif
29
30#include <QtCore/QApplicationStatic>
31#include <QtCore/QTimer>
32
33QGC_LOGGING_CATEGORY(LinkManagerLog, "Comms.LinkManager")
34QGC_LOGGING_CATEGORY(LinkManagerVerboseLog, "Comms.LinkManager:verbose")
35
36Q_APPLICATION_STATIC(LinkManager, _linkManagerInstance);
37
38LinkManager::LinkManager(QObject *parent)
39 : QObject(parent)
40 , _portListTimer(new QTimer(this))
41 , _qmlConfigurations(new QmlObjectListModel(this))
42 , _nmeaSocket(new UdpIODevice(this))
43{
44 qCDebug(LinkManagerLog) << this;
45
46 (void) qRegisterMetaType<QAbstractSocket::SocketError>("QAbstractSocket::SocketError");
47 (void) qRegisterMetaType<LinkInterface*>("LinkInterface*");
48#ifndef QGC_NO_SERIAL_LINK
49 (void) qRegisterMetaType<QGCSerialPortInfo>("QGCSerialPortInfo");
50#endif
51}
52
54{
55 qCDebug(LinkManagerLog) << this;
56}
57
59{
60 return _linkManagerInstance();
61}
62
64{
65 _autoConnectSettings = SettingsManager::instance()->autoConnectSettings();
66
67 if (!QGC::runningUnitTests()) {
68 (void) connect(_portListTimer, &QTimer::timeout, this, &LinkManager::_updateAutoConnectLinks);
69 _portListTimer->start(_autoconnectUpdateTimerMSecs); // timeout must be long enough to get past bootloader on second pass
70 }
71}
72
73QList<SharedLinkInterfacePtr> LinkManager::links()
74{
75 QMutexLocker locker(&_linksMutex);
76 return _rgLinks;
77}
78
79QmlObjectListModel *LinkManager::_qmlLinkConfigurations()
80{
81 return _qmlConfigurations;
82}
83
85{
86 for (SharedLinkConfigurationPtr &sharedConfig : _rgLinkConfigs) {
87 if (sharedConfig.get() == config) {
88 sharedConfig->setAutoConnectStarted(true);
89 sharedConfig->resetReconnectBackoff();
90 createConnectedLink(sharedConfig);
91 }
92 }
93}
94
96{
97 if (!link) {
98 return;
99 }
100
102 if (config) {
103 config->setSuppressAutoReconnect(true);
104 }
105
106 link->disconnect();
107}
108
110{
111 if (!config) {
112 return;
113 }
114
115 config->setSuppressAutoReconnect(true);
116
117 if (LinkInterface *const link = config->link()) {
118 link->disconnect();
119 }
120}
121
123{
124 config->setSuppressAutoReconnect(false);
125
126 SharedLinkInterfacePtr link = nullptr;
127
128 switch(config->type()) {
129#ifndef QGC_NO_SERIAL_LINK
131 link = std::make_shared<SerialLink>(config);
132 break;
133#endif
135 link = std::make_shared<UDPLink>(config);
136 break;
138 link = std::make_shared<TCPLink>(config);
139 break;
141 link = std::make_shared<BluetoothLink>(config);
142 break;
144 link = std::make_shared<LogReplayLink>(config);
145 break;
146#ifdef QT_DEBUG
147 case LinkConfiguration::TypeMock:
148 link = std::make_shared<MockLink>(config);
149 break;
150#endif
152 default:
153 break;
154 }
155
156 if (!link) {
157 return false;
158 }
159
160 if (!link->_allocateMavlinkChannel()) {
161 qCWarning(LinkManagerLog) << "Link failed to setup mavlink channels";
162 return false;
163 }
164
165 // Set up signal connections before adding to list, so link is fully initialized
166 (void) connect(link.get(), &LinkInterface::communicationError, this, &LinkManager::_communicationError);
169 (void) connect(link.get(), &LinkInterface::connected, this, &LinkManager::_linkConnected);
170 (void) connect(link.get(), &LinkInterface::disconnected, this, &LinkManager::_linkDisconnected);
171
173
174 // Try to connect before adding to active links list
175 if (!link->_connect()) {
176 (void) disconnect(link.get(), &LinkInterface::communicationError, this, &LinkManager::_communicationError);
179 (void) disconnect(link.get(), &LinkInterface::disconnected, this, &LinkManager::_linkDisconnected);
180 link->_freeMavlinkChannel();
181 config->setLink(nullptr);
182 return false;
183 }
184
185 {
186 QMutexLocker locker(&_linksMutex);
187 _rgLinks.append(link);
188 }
189 config->setLink(link);
190
191 return true;
192}
193
194void LinkManager::_linkConnected()
195{
196 const LinkInterface *const link = qobject_cast<LinkInterface*>(sender());
197 const SharedLinkConfigurationPtr config = link ? link->linkConfiguration() : nullptr;
198 if (config) {
199 config->noteConnected();
200 }
201}
202
203void LinkManager::_communicationError(const QString &title, const QString &error)
204{
205 const LinkInterface *const link = qobject_cast<LinkInterface*>(sender());
206 const SharedLinkConfigurationPtr config = link ? link->linkConfiguration() : nullptr;
207
208 // Auto-connect links retry on a timer; a popup per failed attempt is just noise. Log only.
209 if (config && config->isAutoConnect() && !config->suppressAutoReconnect()) {
210 qCDebug(LinkManagerLog) << "Auto-connect link error (will retry):" << title << error;
211 return;
212 }
213
215}
216
218{
219 QMutexLocker locker(&_linksMutex);
220
221 for (const SharedLinkInterfacePtr &link : _rgLinks) {
222 const SharedLinkConfigurationPtr linkConfig = link->linkConfiguration();
223 if (linkConfig && (linkConfig->type() == LinkConfiguration::TypeUdp) && (linkConfig->name() == _mavlinkForwardingLinkName)) {
224 return link;
225 }
226 }
227
228 return nullptr;
229}
230
232{
233 QMutexLocker locker(&_linksMutex);
234
235 for (const SharedLinkInterfacePtr &link : _rgLinks) {
236 const SharedLinkConfigurationPtr linkConfig = link->linkConfiguration();
237 if (linkConfig && (linkConfig->type() == LinkConfiguration::TypeUdp) && (linkConfig->name() == _mavlinkForwardingSupportLinkName)) {
238 return link;
239 }
240 }
241
242 return nullptr;
243}
244
246{
247 QList<SharedLinkInterfacePtr> links;
248 {
249 QMutexLocker locker(&_linksMutex);
250 links = _rgLinks;
251 }
252
253 for (const SharedLinkInterfacePtr &sharedLink: links) {
254 sharedLink->disconnect();
255 }
256}
257
258void LinkManager::_linkDisconnected()
259{
260 LinkInterface* const link = qobject_cast<LinkInterface*>(sender());
261
262 if (!link) {
263 return;
264 }
265
266 SharedLinkInterfacePtr linkToCleanup;
268 {
269 QMutexLocker locker(&_linksMutex);
270
271 for (auto it = _rgLinks.begin(); it != _rgLinks.end(); ++it) {
272 if (it->get() == link) {
273 config = it->get()->linkConfiguration();
274 const QString linkName = config ? config->name() : QStringLiteral("<null config>");
275 qCDebug(LinkManagerLog) << linkName << "use_count:" << it->use_count();
276 linkToCleanup = *it;
277 (void) _rgLinks.erase(it);
278 break;
279 }
280 }
281 }
282
283 if (!linkToCleanup) {
284 qCDebug(LinkManagerLog) << "link already removed";
285 return;
286 }
287
288 if (config) {
289 config->noteDisconnected();
290 config->setLink(nullptr);
291 }
292
293 (void) disconnect(link, &LinkInterface::communicationError, this, &LinkManager::_communicationError);
296 (void) disconnect(link, &LinkInterface::connected, this, &LinkManager::_linkConnected);
297 (void) disconnect(link, &LinkInterface::disconnected, this, &LinkManager::_linkDisconnected);
298
299 link->_freeMavlinkChannel();
300}
301
303{
304 QMutexLocker locker(&_linksMutex);
305
306 for (const SharedLinkInterfacePtr &sharedLink: _rgLinks) {
307 if (sharedLink.get() == link) {
308 return sharedLink;
309 }
310 }
311
312 // Link not found - this is normal during disconnect when queued signals are still processing.
313 // Callers should check for nullptr return value.
314 qCDebug(LinkManagerLog) << "link not in list (likely disconnected)";
315 return SharedLinkInterfacePtr(nullptr);
316}
317
318bool LinkManager::_connectionsSuspendedMsg() const
319{
320 if (_connectionsSuspended) {
321 QGC::showAppMessage(tr("Connect not allowed: %1").arg(_connectionsSuspendedReason));
322 return true;
323 }
324
325 return false;
326}
327
329{
330 QSettings settings;
331 settings.remove(LinkConfiguration::settingsRoot());
332
333 int trueCount = 0;
334 for (int i = 0; i < _rgLinkConfigs.count(); i++) {
335 SharedLinkConfigurationPtr linkConfig = _rgLinkConfigs[i];
336 if (!linkConfig) {
337 qCWarning(LinkManagerLog) << "Internal error for link configuration in LinkManager";
338 continue;
339 }
340
341 if (linkConfig->isDynamic()) {
342 continue;
343 }
344
345 const QString root = LinkConfiguration::settingsRoot() + QStringLiteral("/Link%1").arg(trueCount++);
346 settings.setValue(root + "/name", linkConfig->name());
347 settings.setValue(root + "/type", linkConfig->type());
348 settings.setValue(root + "/auto", linkConfig->isAutoConnect());
349 settings.setValue(root + "/high_latency", linkConfig->isHighLatency());
350 linkConfig->saveSettings(settings, root);
351 }
352
353 const QString root = QString(LinkConfiguration::settingsRoot());
354 settings.setValue(root + "/count", trueCount);
355}
356
358{
359 QSettings settings;
360 // Is the group even there?
361 if (settings.contains(LinkConfiguration::settingsRoot() + "/count")) {
362 // Find out how many configurations we have
363 const int count = settings.value(LinkConfiguration::settingsRoot() + "/count").toInt();
364 for (int i = 0; i < count; i++) {
365 const QString root = LinkConfiguration::settingsRoot() + QStringLiteral("/Link%1").arg(i);
366 if (!settings.contains(root + "/type")) {
367 qCWarning(LinkManagerLog) << "Link Configuration" << root << "has no type.";
368 continue;
369 }
370
371 LinkConfiguration::LinkType type = static_cast<LinkConfiguration::LinkType>(settings.value(root + "/type").toInt());
372 if (type >= LinkConfiguration::TypeLast) {
373 qCWarning(LinkManagerLog) << "Link Configuration" << root << "an invalid type:" << type;
374 continue;
375 }
376
377 if (!settings.contains(root + "/name")) {
378 qCWarning(LinkManagerLog) << "Link Configuration" << root << "has no name.";
379 continue;
380 }
381
382 const QString name = settings.value(root + "/name").toString();
383 if (name.isEmpty()) {
384 qCWarning(LinkManagerLog) << "Link Configuration" << root << "has an empty name.";
385 continue;
386 }
387
388 LinkConfiguration* link = nullptr;
389 switch(type) {
390#ifndef QGC_NO_SERIAL_LINK
392 link = new SerialConfiguration(name);
393 break;
394#endif
396 link = new UDPConfiguration(name);
397 break;
399 link = new TCPConfiguration(name);
400 break;
402 link = new BluetoothConfiguration(name);
403 break;
405 link = new LogReplayConfiguration(name);
406 break;
407#ifdef QT_DEBUG
408 case LinkConfiguration::TypeMock:
409 link = new MockConfiguration(name);
410 break;
411#endif
413 default:
414 break;
415 }
416
417 if (link) {
418 const bool autoConnect = settings.value(root + "/auto").toBool();
419 link->setAutoConnect(autoConnect);
420 const bool highLatency = settings.value(root + "/high_latency").toBool();
421 link->setHighLatency(highLatency);
422 link->loadSettings(settings, root);
423 addConfiguration(link);
424 }
425 }
426 }
427
428 // Enable automatic Serial PX4/3DR Radio hunting
429 _configurationsLoaded = true;
430}
431
432void LinkManager::_addUDPAutoConnectLink()
433{
434 if (!_autoConnectSettings->autoConnectUDP()->rawValue().toBool()) {
435 return;
436 }
437
438 {
439 QMutexLocker locker(&_linksMutex);
440 for (const SharedLinkInterfacePtr &link : _rgLinks) {
441 const SharedLinkConfigurationPtr linkConfig = link->linkConfiguration();
442 if (linkConfig && (linkConfig->type() == LinkConfiguration::TypeUdp) && (linkConfig->name() == _defaultUDPLinkName)) {
443 return;
444 }
445 }
446 }
447
448 qCDebug(LinkManagerLog) << "New auto-connect UDP port added";
449 UDPConfiguration* const udpConfig = new UDPConfiguration(_defaultUDPLinkName);
450 udpConfig->setDynamic(true);
451 udpConfig->setAutoConnect(true);
454}
455
456void LinkManager::_addMAVLinkForwardingLink()
457{
458 if (!SettingsManager::instance()->mavlinkSettings()->forwardMavlink()->rawValue().toBool()) {
459 return;
460 }
461
462 {
463 QMutexLocker locker(&_linksMutex);
464 for (const SharedLinkInterfacePtr &link : _rgLinks) {
465 const SharedLinkConfigurationPtr linkConfig = link->linkConfiguration();
466 if (linkConfig && (linkConfig->type() == LinkConfiguration::TypeUdp) && (linkConfig->name() == _mavlinkForwardingLinkName)) {
467 // TODO: should we check if the host/port matches the mavlinkForwardHostName setting and update if it does not match?
468 return;
469 }
470 }
471 }
472
473 const QString hostName = SettingsManager::instance()->mavlinkSettings()->forwardMavlinkHostName()->rawValue().toString();
474 _createDynamicForwardLink(_mavlinkForwardingLinkName, hostName);
475}
476
477void LinkManager::_reconnectAutoConnectLinks()
478{
479 for (SharedLinkConfigurationPtr &config : _rgLinkConfigs) {
480 if (!config || config->isDynamic() || !config->isAutoConnect()) {
481 continue;
482 }
483
484 // Only re-establish links started this session (boot or manual connect); a freshly
485 // added auto-connect config waits for next app start rather than connecting now.
486 if (config->link() || config->suppressAutoReconnect() || !config->autoConnectStarted()) {
487 continue;
488 }
489
490 // Exponential backoff between attempts so a dead host isn't hammered every tick.
491 if (!config->reconnectReady()) {
492 continue;
493 }
494
495 qCDebug(LinkManagerLog) << "Reconnecting auto-connect link" << config->name();
496 config->noteReconnectAttempt();
498 }
499}
500
501void LinkManager::_updateAutoConnectLinks()
502{
503 if (_connectionsSuspended) {
504 return;
505 }
506
507 _addUDPAutoConnectLink();
508 _addMAVLinkForwardingLink();
509 _reconnectAutoConnectLinks();
510
511 // check to see if nmea gps is configured for UDP input, if so, set it up to connect
512 if (_autoConnectSettings->autoConnectNmeaPort()->cookedValueString() == "UDP Port") {
513 if ((_nmeaSocket->localPort() != _autoConnectSettings->nmeaUdpPort()->rawValue().toUInt()) || (_nmeaSocket->state() != UdpIODevice::BoundState)) {
514 qCDebug(LinkManagerLog) << "Changing port for UDP NMEA stream";
515 _nmeaSocket->close();
516 _nmeaSocket->bind(QHostAddress::AnyIPv4, _autoConnectSettings->nmeaUdpPort()->rawValue().toUInt());
518 }
519#ifndef QGC_NO_SERIAL_LINK
520 if (_nmeaPort) {
521 _nmeaPort->close();
522 delete _nmeaPort;
523 _nmeaPort = nullptr;
524 _nmeaDeviceName = "";
525 }
526#endif
527 } else {
528 _nmeaSocket->close();
529 }
530
531#ifndef QGC_NO_SERIAL_LINK
532 _addSerialAutoConnectLink();
533#endif
534}
535
537{
538 setConnectionsSuspended(tr("Shutdown"));
540
541 // Wait for all the vehicles to go away to ensure an orderly shutdown and deletion of all objects
542 while (MultiVehicleManager::instance()->vehicles()->count()) {
543 QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
544 }
545}
546
548{
549 //-- Must follow same order as enum LinkType in LinkConfiguration.h
550 static QStringList list;
551 if (!list.isEmpty()) {
552 return list;
553 }
554
555#ifndef QGC_NO_SERIAL_LINK
556 list += tr("Serial");
557#endif
558 list += tr("UDP");
559 list += tr("TCP");
560 list += tr("Bluetooth");
561#ifdef QT_DEBUG
562 list += tr("Mock Link");
563#endif
564 list += tr("Log Replay");
565
566 if (list.size() != static_cast<int>(LinkConfiguration::TypeLast)) {
567 qCWarning(LinkManagerLog) << "Internal error";
568 }
569
570 return list;
571}
572
574{
575 if (!config || !editedConfig) {
576 qCWarning(LinkManagerLog) << "Internal error";
577 return;
578 }
579
580 config->copyFrom(editedConfig);
582 emit config->nameChanged(config->name());
583 // Discard temporary duplicate
584 delete editedConfig;
585}
586
588{
589 if (!config) {
590 qCWarning(LinkManagerLog) << "Internal error";
591 return;
592 }
593
596}
597
599{
600#ifndef QGC_NO_SERIAL_LINK
602 _updateSerialPorts();
603 }
604#endif
605
606 return LinkConfiguration::createSettings(type, name);
607}
608
610{
611 if (!config) {
612 qCWarning(LinkManagerLog) << "Internal error";
613 return nullptr;
614 }
615
616#ifndef QGC_NO_SERIAL_LINK
617 if (config->type() == LinkConfiguration::TypeSerial) {
618 _updateSerialPorts();
619 }
620#endif
621
623}
624
626{
627 if (!config) {
628 qCWarning(LinkManagerLog) << "Internal error";
629 return;
630 }
631
632 LinkInterface* const link = config->link();
633 if (link) {
634 link->disconnect();
635 }
636
637 _removeConfiguration(config);
639}
640
642{
643 const QString hostName = SettingsManager::instance()->mavlinkSettings()->forwardMavlinkAPMSupportHostName()->rawValue().toString();
644 _createDynamicForwardLink(_mavlinkForwardingSupportLinkName, hostName);
645 _mavlinkSupportForwardingEnabled = true;
647}
648
649void LinkManager::_removeConfiguration(const LinkConfiguration *config)
650{
651 (void) _qmlConfigurations->removeOne(config);
652
653 for (auto it = _rgLinkConfigs.begin(); it != _rgLinkConfigs.end(); ++it) {
654 if (it->get() == config) {
655 (void) _rgLinkConfigs.erase(it);
656 return;
657 }
658 }
659
660 qCWarning(LinkManagerLog) << "called with unknown config";
661}
662
667
669{
670 QMutexLocker locker(&_linksMutex);
671
672 for (const SharedLinkInterfacePtr &sharedLink : _rgLinks) {
673 if (sharedLink.get() == link) {
674 return true;
675 }
676 }
677
678 return false;
679}
680
682{
683 (void) _qmlConfigurations->append(config);
684 (void) _rgLinkConfigs.append(SharedLinkConfigurationPtr(config));
685
686 return _rgLinkConfigs.last();
687}
688
690{
691 for (SharedLinkConfigurationPtr &sharedConfig : _rgLinkConfigs) {
692 if (sharedConfig->isAutoConnect()) {
693 sharedConfig->setAutoConnectStarted(true);
694 createConnectedLink(sharedConfig);
695 }
696 }
697}
698
700{
701 for (uint8_t mavlinkChannel = 0; mavlinkChannel < MAVLINK_COMM_NUM_BUFFERS; mavlinkChannel++) {
702 if (_mavlinkChannelsUsedBitMask & (1 << mavlinkChannel)) {
703 continue;
704 }
705
706 mavlink_reset_channel_status(mavlinkChannel);
707 mavlink_status_t* const mavlinkStatus = mavlink_get_channel_status(mavlinkChannel);
708 mavlinkStatus->flags |= MAVLINK_STATUS_FLAG_OUT_MAVLINK1;
709 _mavlinkChannelsUsedBitMask |= (1 << mavlinkChannel);
710 qCDebug(LinkManagerLog) << "allocateMavlinkChannel" << mavlinkChannel;
711 return mavlinkChannel;
712 }
713
714 qWarning(LinkManagerLog) << "allocateMavlinkChannel: all channels reserved!";
715 return invalidMavlinkChannel();
716}
717
719{
720 qCDebug(LinkManagerLog) << "freeMavlinkChannel" << channel;
721
722 if (invalidMavlinkChannel() == channel) {
723 return;
724 }
725
726 _mavlinkChannelsUsedBitMask &= ~(1 << channel);
727}
728
730{
731 LogReplayConfiguration* const linkConfig = new LogReplayConfiguration(tr("Log Replay"));
732 linkConfig->setLogFilename(logFile);
733 linkConfig->setName(linkConfig->logFilenameShort());
734
735 SharedLinkConfigurationPtr sharedConfig = addConfiguration(linkConfig);
736 if (createConnectedLink(sharedConfig)) {
737 return qobject_cast<LogReplayLink*>(sharedConfig->link());
738 }
739
740 return nullptr;
741}
742
743void LinkManager::_createDynamicForwardLink(const char *linkName, const QString &hostName)
744{
745 UDPConfiguration* const udpConfig = new UDPConfiguration(linkName);
746
747 udpConfig->setDynamic(true);
748 udpConfig->setForwarding(true);
749 udpConfig->addHost(hostName);
750
753
754 qCDebug(LinkManagerLog) << "New dynamic MAVLink forwarding port added:" << linkName << " hostname:" << hostName;
755}
756
757bool LinkManager::isLinkUSBDirect([[maybe_unused]] const LinkInterface *link)
758{
759#ifndef QGC_NO_SERIAL_LINK
760 const SerialLink* const serialLink = qobject_cast<const SerialLink*>(link);
761 if (!serialLink) {
762 return false;
763 }
764
766 if (!config) {
767 return false;
768 }
769
770 const SerialConfiguration* const serialConfig = qobject_cast<const SerialConfiguration*>(config.get());
771 if (serialConfig && serialConfig->usbDirect()) {
772 return link;
773 }
774#endif
775
776 return false;
777}
778
779#ifndef QGC_NO_SERIAL_LINK // Serial Only Functions
780
781void LinkManager::_filterCompositePorts(QList<QGCSerialPortInfo> &portList)
782{
783 typedef QPair<quint16, quint16> VidPidPair_t;
784
785 QMap<VidPidPair_t, QStringList> seenSerialNumbers;
786
787 for (auto it = portList.begin(); it != portList.end();) {
788 const QGCSerialPortInfo &portInfo = *it;
789 if (portInfo.hasVendorIdentifier() && portInfo.hasProductIdentifier() && !portInfo.serialNumber().isEmpty() && portInfo.serialNumber() != "0") {
790 VidPidPair_t vidPid(portInfo.vendorIdentifier(), portInfo.productIdentifier());
791 if (seenSerialNumbers.contains(vidPid) && seenSerialNumbers[vidPid].contains(portInfo.serialNumber())) {
792 // 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.
793 // However internal NMEA devices can present like this, so dont skip anything with NMEA in description
794 if(!portInfo.description().contains("NMEA")) {
795 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());
796 it = portList.erase(it);
797 continue;
798 }
799 }
800 seenSerialNumbers[vidPid].append(portInfo.serialNumber());
801 }
802 it++;
803 }
804}
805
806void LinkManager::_addSerialAutoConnectLink()
807{
808 QList<QGCSerialPortInfo> portList;
809#ifdef Q_OS_ANDROID
810 // Android builds only support a single serial connection. Repeatedly calling availablePorts after that one serial
811 // port is connected leaks file handles due to a bug somewhere in android serial code. In order to work around that
812 // bug after we connect the first serial port we stop probing for additional ports.
813 if (!_isSerialPortConnected()) {
815 }
816#else
818#endif
819
820 _filterCompositePorts(portList);
821
822 QStringList currentPorts;
823 for (const QGCSerialPortInfo &portInfo: portList) {
824 qCDebug(LinkManagerVerboseLog) << "-----------------------------------------------------";
825 qCDebug(LinkManagerVerboseLog) << "portName: " << portInfo.portName();
826 qCDebug(LinkManagerVerboseLog) << "systemLocation: " << portInfo.systemLocation();
827 qCDebug(LinkManagerVerboseLog) << "description: " << portInfo.description();
828 qCDebug(LinkManagerVerboseLog) << "manufacturer: " << portInfo.manufacturer();
829 qCDebug(LinkManagerVerboseLog) << "serialNumber: " << portInfo.serialNumber();
830 qCDebug(LinkManagerVerboseLog) << "vendorIdentifier: " << portInfo.vendorIdentifier();
831 qCDebug(LinkManagerVerboseLog) << "productIdentifier: " << portInfo.productIdentifier();
832
833 currentPorts << portInfo.systemLocation();
834
836 QString boardName;
837
838 // check to see if nmea gps is configured for current Serial port, if so, set it up to connect
839 if (portInfo.systemLocation().trimmed() == _autoConnectSettings->autoConnectNmeaPort()->cookedValueString()) {
840 if (portInfo.systemLocation().trimmed() != _nmeaDeviceName) {
841 _nmeaDeviceName = portInfo.systemLocation().trimmed();
842 qCDebug(LinkManagerLog) << "Configuring nmea port" << _nmeaDeviceName;
843 QSerialPort* newPort = new QSerialPort(portInfo, this);
844 _nmeaBaud = _autoConnectSettings->autoConnectNmeaBaud()->cookedValue().toUInt();
845 newPort->setBaudRate(static_cast<qint32>(_nmeaBaud));
846 qCDebug(LinkManagerLog) << "Configuring nmea baudrate" << _nmeaBaud;
847 // This will stop polling old device if previously set
849 if (_nmeaPort) {
850 delete _nmeaPort;
851 }
852 _nmeaPort = newPort;
853 } else if (_autoConnectSettings->autoConnectNmeaBaud()->cookedValue().toUInt() != _nmeaBaud) {
854 _nmeaBaud = _autoConnectSettings->autoConnectNmeaBaud()->cookedValue().toUInt();
855 _nmeaPort->setBaudRate(static_cast<qint32>(_nmeaBaud));
856 qCDebug(LinkManagerLog) << "Configuring nmea baudrate" << _nmeaBaud;
857 }
858 } else if (portInfo.getBoardInfo(boardType, boardName)) {
859 // Should we be auto-connecting to this board type?
860 if (!_allowAutoConnectToBoard(boardType)) {
861 continue;
862 }
863
864 if (portInfo.isBootloader()) {
865 // Don't connect to bootloader
866 qCDebug(LinkManagerLog) << "Waiting for bootloader to finish" << portInfo.systemLocation();
867 continue;
868 }
869 if (_portAlreadyConnected(portInfo.systemLocation()) || (_autoConnectRTKPort == portInfo.systemLocation())) {
870 qCDebug(LinkManagerVerboseLog) << "Skipping existing autoconnect" << portInfo.systemLocation();
871 } else if (!_autoconnectPortWaitList.contains(portInfo.systemLocation())) {
872 // We don't connect to the port the first time we see it. The ability to correctly detect whether we
873 // are in the bootloader is flaky from a cross-platform standpoint. So by putting it on a wait list
874 // and only connect on the second pass we leave enough time for the board to boot up.
875 qCDebug(LinkManagerLog) << "Waiting for next autoconnect pass" << portInfo.systemLocation() << boardName;
876 _autoconnectPortWaitList[portInfo.systemLocation()] = 1;
877 } else if ((++_autoconnectPortWaitList[portInfo.systemLocation()] * _autoconnectUpdateTimerMSecs) > _autoconnectConnectDelayMSecs) {
878 SerialConfiguration* pSerialConfig = nullptr;
879 _autoconnectPortWaitList.remove(portInfo.systemLocation());
880 switch (boardType) {
882 pSerialConfig = new SerialConfiguration(tr("%1 on %2 (AutoConnect)").arg(boardName, portInfo.portName().trimmed()));
883 pSerialConfig->setUsbDirect(true);
884 break;
886 pSerialConfig = new SerialConfiguration(tr("%1 on %2 (AutoConnect)").arg(boardName, portInfo.portName().trimmed()));
887 break;
889 pSerialConfig = new SerialConfiguration(tr("%1 on %2 (AutoConnect)").arg(boardName, portInfo.portName().trimmed()));
890 break;
892 qCDebug(LinkManagerLog) << "RTK GPS auto-connected" << portInfo.portName().trimmed();
893 _autoConnectRTKPort = portInfo.systemLocation();
894 GPSManager::instance()->gpsRtk()->connectGPS(portInfo.systemLocation(), boardName);
895 break;
896 default:
897 qCWarning(LinkManagerLog) << "Internal error: Unknown board type" << boardType;
898 continue;
899 }
900
901 if (pSerialConfig) {
902 qCDebug(LinkManagerLog) << "New auto-connect port added: " << pSerialConfig->name() << portInfo.systemLocation();
903 pSerialConfig->setBaud((boardType == QGCSerialPortInfo::BoardTypeSiKRadio) ? 57600 : 115200);
904 pSerialConfig->setDynamic(true);
905 pSerialConfig->setPortName(portInfo.systemLocation());
906 pSerialConfig->setAutoConnect(true);
907
908 SharedLinkConfigurationPtr sharedConfig(pSerialConfig);
909 createConnectedLink(sharedConfig);
910 }
911 }
912 }
913 }
914
915 // Check for RTK GPS connection gone
916 if (!_autoConnectRTKPort.isEmpty() && !currentPorts.contains(_autoConnectRTKPort)) {
917 qCDebug(LinkManagerLog) << "RTK GPS disconnected" << _autoConnectRTKPort;
919 _autoConnectRTKPort.clear();
920 }
921}
922
923bool LinkManager::_allowAutoConnectToBoard(QGCSerialPortInfo::BoardType_t boardType) const
924{
925 switch (boardType) {
927 if (_autoConnectSettings->autoConnectPixhawk()->rawValue().toBool()) {
928 return true;
929 }
930 break;
932 if (_autoConnectSettings->autoConnectSiKRadio()->rawValue().toBool()) {
933 return true;
934 }
935 break;
937 if (_autoConnectSettings->autoConnectLibrePilot()->rawValue().toBool()) {
938 return true;
939 }
940 break;
942 if (_autoConnectSettings->autoConnectRTKGPS()->rawValue().toBool() && !GPSManager::instance()->gpsRtk()->connected()) {
943 return true;
944 }
945 break;
946 default:
947 qCWarning(LinkManagerLog) << "Internal error: Unknown board type" << boardType;
948 return false;
949 }
950
951 return false;
952}
953
954bool LinkManager::_portAlreadyConnected(const QString &portName)
955{
956 QMutexLocker locker(&_linksMutex);
957
958 const QString searchPort = portName.trimmed();
959 for (const SharedLinkInterfacePtr &linkInterface : _rgLinks) {
960 const SharedLinkConfigurationPtr linkConfig = linkInterface->linkConfiguration();
961 const SerialConfiguration* const serialConfig = qobject_cast<const SerialConfiguration*>(linkConfig.get());
962 if (serialConfig && (serialConfig->portName() == searchPort)) {
963 return true;
964 }
965 }
966
967 return false;
968}
969
970void LinkManager::_updateSerialPorts()
971{
972 _commPortList.clear();
973 _commPortDisplayList.clear();
974 const QList<QGCSerialPortInfo> portList = QGCSerialPortInfo::availablePorts();
975 for (const QGCSerialPortInfo &info: portList) {
976 const QString port = info.systemLocation().trimmed();
977 _commPortList += port;
978 _commPortDisplayList += SerialConfiguration::cleanPortDisplayName(port);
979 }
980}
981
983{
984 if (_commPortDisplayList.isEmpty()) {
985 _updateSerialPorts();
986 }
987
988 return _commPortDisplayList;
989}
990
992{
993 if (_commPortList.isEmpty()) {
994 _updateSerialPorts();
995 }
996
997 return _commPortList;
998}
999
1004
1005bool LinkManager::_isSerialPortConnected()
1006{
1007 QMutexLocker locker(&_linksMutex);
1008
1009 for (const SharedLinkInterfacePtr &link: _rgLinks) {
1010 if (qobject_cast<const SerialLink*>(link.get())) {
1011 return true;
1012 }
1013 }
1014
1015 return false;
1016}
1017
1018#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 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