20#ifndef QGC_NO_SERIAL_LINK
30#include <QtCore/QApplicationStatic>
31#include <QtCore/QTimer>
40 , _portListTimer(new QTimer(this))
44 qCDebug(LinkManagerLog) <<
this;
46 (void) qRegisterMetaType<QAbstractSocket::SocketError>(
"QAbstractSocket::SocketError");
47 (void) qRegisterMetaType<LinkInterface*>(
"LinkInterface*");
48#ifndef QGC_NO_SERIAL_LINK
49 (void) qRegisterMetaType<QGCSerialPortInfo>(
"QGCSerialPortInfo");
55 qCDebug(LinkManagerLog) <<
this;
60 return _linkManagerInstance();
68 (void) connect(_portListTimer, &QTimer::timeout,
this, &LinkManager::_updateAutoConnectLinks);
69 _portListTimer->start(_autoconnectUpdateTimerMSecs);
75 QMutexLocker locker(&_linksMutex);
81 return _qmlConfigurations;
87 if (sharedConfig.get() ==
config) {
88 sharedConfig->setAutoConnectStarted(
true);
89 sharedConfig->resetReconnectBackoff();
103 config->setSuppressAutoReconnect(
true);
115 config->setSuppressAutoReconnect(
true);
124 config->setSuppressAutoReconnect(
false);
129#ifndef QGC_NO_SERIAL_LINK
131 link = std::make_shared<SerialLink>(
config);
135 link = std::make_shared<UDPLink>(
config);
138 link = std::make_shared<TCPLink>(
config);
141 link = std::make_shared<BluetoothLink>(
config);
144 link = std::make_shared<LogReplayLink>(
config);
147 case LinkConfiguration::TypeMock:
148 link = std::make_shared<MockLink>(
config);
160 if (!link->_allocateMavlinkChannel()) {
161 qCWarning(LinkManagerLog) <<
"Link failed to setup mavlink channels";
175 if (!link->_connect()) {
180 link->_freeMavlinkChannel();
186 QMutexLocker locker(&_linksMutex);
187 _rgLinks.append(link);
194void LinkManager::_linkConnected()
196 const LinkInterface *
const link = qobject_cast<LinkInterface*>(sender());
203void LinkManager::_communicationError(
const QString &title,
const QString &
error)
205 const LinkInterface *
const link = qobject_cast<LinkInterface*>(sender());
210 qCDebug(LinkManagerLog) <<
"Auto-connect link error (will retry):" << title <<
error;
219 QMutexLocker locker(&_linksMutex);
233 QMutexLocker locker(&_linksMutex);
237 if (linkConfig && (linkConfig->type() ==
LinkConfiguration::TypeUdp) && (linkConfig->name() == _mavlinkForwardingSupportLinkName)) {
247 QList<SharedLinkInterfacePtr>
links;
249 QMutexLocker locker(&_linksMutex);
254 sharedLink->disconnect();
258void LinkManager::_linkDisconnected()
260 LinkInterface*
const link = qobject_cast<LinkInterface*>(sender());
269 QMutexLocker locker(&_linksMutex);
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();
277 (void) _rgLinks.erase(it);
283 if (!linkToCleanup) {
284 qCDebug(LinkManagerLog) <<
"link already removed";
289 config->noteDisconnected();
304 QMutexLocker locker(&_linksMutex);
307 if (sharedLink.get() == link) {
314 qCDebug(LinkManagerLog) <<
"link not in list (likely disconnected)";
318bool LinkManager::_connectionsSuspendedMsg()
const
320 if (_connectionsSuspended) {
334 for (
int i = 0; i < _rgLinkConfigs.count(); i++) {
337 qCWarning(LinkManagerLog) <<
"Internal error for link configuration in LinkManager";
341 if (linkConfig->isDynamic()) {
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);
354 settings.setValue(root +
"/count", trueCount);
364 for (
int i = 0; i < count; i++) {
366 if (!settings.contains(root +
"/type")) {
367 qCWarning(LinkManagerLog) <<
"Link Configuration" << root <<
"has no type.";
373 qCWarning(LinkManagerLog) <<
"Link Configuration" << root <<
"an invalid type:" << type;
377 if (!settings.contains(root +
"/name")) {
378 qCWarning(LinkManagerLog) <<
"Link Configuration" << root <<
"has no name.";
382 const QString name = settings.value(root +
"/name").toString();
383 if (name.isEmpty()) {
384 qCWarning(LinkManagerLog) <<
"Link Configuration" << root <<
"has an empty name.";
390#ifndef QGC_NO_SERIAL_LINK
408 case LinkConfiguration::TypeMock:
418 const bool autoConnect = settings.value(root +
"/auto").toBool();
420 const bool highLatency = settings.value(root +
"/high_latency").toBool();
429 _configurationsLoaded =
true;
432void LinkManager::_addUDPAutoConnectLink()
434 if (!_autoConnectSettings->autoConnectUDP()->rawValue().toBool()) {
439 QMutexLocker locker(&_linksMutex);
448 qCDebug(LinkManagerLog) <<
"New auto-connect UDP port added";
456void LinkManager::_addMAVLinkForwardingLink()
463 QMutexLocker locker(&_linksMutex);
474 _createDynamicForwardLink(_mavlinkForwardingLinkName, hostName);
477void LinkManager::_reconnectAutoConnectLinks()
486 if (
config->link() ||
config->suppressAutoReconnect() || !
config->autoConnectStarted()) {
491 if (!
config->reconnectReady()) {
495 qCDebug(LinkManagerLog) <<
"Reconnecting auto-connect link" <<
config->name();
496 config->noteReconnectAttempt();
501void LinkManager::_updateAutoConnectLinks()
503 if (_connectionsSuspended) {
507 _addUDPAutoConnectLink();
508 _addMAVLinkForwardingLink();
509 _reconnectAutoConnectLinks();
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());
519#ifndef QGC_NO_SERIAL_LINK
524 _nmeaDeviceName =
"";
528 _nmeaSocket->close();
531#ifndef QGC_NO_SERIAL_LINK
532 _addSerialAutoConnectLink();
543 QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
550 static QStringList list;
551 if (!list.isEmpty()) {
555#ifndef QGC_NO_SERIAL_LINK
556 list += tr(
"Serial");
560 list += tr(
"Bluetooth");
562 list += tr(
"Mock Link");
564 list += tr(
"Log Replay");
567 qCWarning(LinkManagerLog) <<
"Internal error";
575 if (!
config || !editedConfig) {
576 qCWarning(LinkManagerLog) <<
"Internal error";
580 config->copyFrom(editedConfig);
590 qCWarning(LinkManagerLog) <<
"Internal error";
600#ifndef QGC_NO_SERIAL_LINK
602 _updateSerialPorts();
612 qCWarning(LinkManagerLog) <<
"Internal error";
616#ifndef QGC_NO_SERIAL_LINK
618 _updateSerialPorts();
628 qCWarning(LinkManagerLog) <<
"Internal error";
637 _removeConfiguration(
config);
644 _createDynamicForwardLink(_mavlinkForwardingSupportLinkName, hostName);
645 _mavlinkSupportForwardingEnabled =
true;
653 for (
auto it = _rgLinkConfigs.begin(); it != _rgLinkConfigs.end(); ++it) {
654 if (it->get() ==
config) {
655 (void) _rgLinkConfigs.erase(it);
660 qCWarning(LinkManagerLog) <<
"called with unknown config";
670 QMutexLocker locker(&_linksMutex);
673 if (sharedLink.get() == link) {
686 return _rgLinkConfigs.last();
692 if (sharedConfig->isAutoConnect()) {
693 sharedConfig->setAutoConnectStarted(
true);
702 if (_mavlinkChannelsUsedBitMask & (1 << mavlinkChannel)) {
706 mavlink_reset_channel_status(mavlinkChannel);
708 mavlinkStatus->flags |= MAVLINK_STATUS_FLAG_OUT_MAVLINK1;
709 _mavlinkChannelsUsedBitMask |= (1 << mavlinkChannel);
710 qCDebug(LinkManagerLog) <<
"allocateMavlinkChannel" << mavlinkChannel;
711 return mavlinkChannel;
714 qWarning(LinkManagerLog) <<
"allocateMavlinkChannel: all channels reserved!";
720 qCDebug(LinkManagerLog) <<
"freeMavlinkChannel" << channel;
726 _mavlinkChannelsUsedBitMask &= ~(1 << channel);
737 return qobject_cast<LogReplayLink*>(sharedConfig->link());
743void LinkManager::_createDynamicForwardLink(
const char *linkName,
const QString &hostName)
754 qCDebug(LinkManagerLog) <<
"New dynamic MAVLink forwarding port added:" << linkName <<
" hostname:" << hostName;
759#ifndef QGC_NO_SERIAL_LINK
760 const SerialLink*
const serialLink = qobject_cast<const SerialLink*>(link);
771 if (serialConfig && serialConfig->
usbDirect()) {
779#ifndef QGC_NO_SERIAL_LINK
781void LinkManager::_filterCompositePorts(QList<QGCSerialPortInfo> &portList)
783 typedef QPair<quint16, quint16> VidPidPair_t;
785 QMap<VidPidPair_t, QStringList> seenSerialNumbers;
787 for (
auto it = portList.begin(); it != portList.end();) {
791 if (seenSerialNumbers.contains(vidPid) && seenSerialNumbers[vidPid].contains(portInfo.
serialNumber())) {
796 it = portList.erase(it);
800 seenSerialNumbers[vidPid].append(portInfo.
serialNumber());
806void LinkManager::_addSerialAutoConnectLink()
808 QList<QGCSerialPortInfo> portList;
813 if (!_isSerialPortConnected()) {
820 _filterCompositePorts(portList);
822 QStringList currentPorts;
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();
839 if (portInfo.
systemLocation().trimmed() == _autoConnectSettings->autoConnectNmeaPort()->cookedValueString()) {
842 qCDebug(LinkManagerLog) <<
"Configuring nmea port" << _nmeaDeviceName;
844 _nmeaBaud = _autoConnectSettings->autoConnectNmeaBaud()->cookedValue().toUInt();
845 newPort->
setBaudRate(
static_cast<qint32
>(_nmeaBaud));
846 qCDebug(LinkManagerLog) <<
"Configuring nmea baudrate" << _nmeaBaud;
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;
858 }
else if (portInfo.
getBoardInfo(boardType, boardName)) {
860 if (!_allowAutoConnectToBoard(boardType)) {
866 qCDebug(LinkManagerLog) <<
"Waiting for bootloader to finish" << portInfo.
systemLocation();
870 qCDebug(LinkManagerVerboseLog) <<
"Skipping existing autoconnect" << portInfo.
systemLocation();
871 }
else if (!_autoconnectPortWaitList.contains(portInfo.
systemLocation())) {
875 qCDebug(LinkManagerLog) <<
"Waiting for next autoconnect pass" << portInfo.
systemLocation() << boardName;
877 }
else if ((++_autoconnectPortWaitList[portInfo.
systemLocation()] * _autoconnectUpdateTimerMSecs) > _autoconnectConnectDelayMSecs) {
892 qCDebug(LinkManagerLog) <<
"RTK GPS auto-connected" << portInfo.
portName().trimmed();
897 qCWarning(LinkManagerLog) <<
"Internal error: Unknown board type" << boardType;
902 qCDebug(LinkManagerLog) <<
"New auto-connect port added: " << pSerialConfig->
name() << portInfo.
systemLocation();
916 if (!_autoConnectRTKPort.isEmpty() && !currentPorts.contains(_autoConnectRTKPort)) {
917 qCDebug(LinkManagerLog) <<
"RTK GPS disconnected" << _autoConnectRTKPort;
919 _autoConnectRTKPort.clear();
927 if (_autoConnectSettings->autoConnectPixhawk()->rawValue().toBool()) {
932 if (_autoConnectSettings->autoConnectSiKRadio()->rawValue().toBool()) {
937 if (_autoConnectSettings->autoConnectLibrePilot()->rawValue().toBool()) {
947 qCWarning(LinkManagerLog) <<
"Internal error: Unknown board type" << boardType;
954bool LinkManager::_portAlreadyConnected(
const QString &portName)
956 QMutexLocker locker(&_linksMutex);
958 const QString searchPort = portName.trimmed();
961 const SerialConfiguration*
const serialConfig = qobject_cast<const SerialConfiguration*>(linkConfig.get());
962 if (serialConfig && (serialConfig->
portName() == searchPort)) {
970void LinkManager::_updateSerialPorts()
972 _commPortList.clear();
973 _commPortDisplayList.clear();
976 const QString port = info.systemLocation().trimmed();
977 _commPortList += port;
984 if (_commPortDisplayList.isEmpty()) {
985 _updateSerialPorts();
988 return _commPortDisplayList;
993 if (_commPortList.isEmpty()) {
994 _updateSerialPorts();
997 return _commPortList;
1005bool LinkManager::_isSerialPortConnected()
1007 QMutexLocker locker(&_linksMutex);
1010 if (qobject_cast<const SerialLink*>(link.get())) {
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)
#define MAVLINK_COMM_NUM_BUFFERS
#define QGC_LOGGING_CATEGORY(name, categoryStr)
static GPSManager * instance()
void connectGPS(const QString &device, QStringView gps_type)
Interface holding link specific settings.
@ 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)
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)
virtual void _freeMavlinkChannel()
virtual Q_INVOKABLE void disconnect()=0
void communicationError(const QString &title, const QString &error)
void bytesSent(LinkInterface *link, const QByteArray &data)
SharedLinkConfigurationPtr linkConfiguration()
Manage communication links The Link Manager organizes the physical Links. It can manage arbitrary lin...
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)
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 isBootloader() const
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.
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)
static QStringList supportedBaudRates()
void setUsbDirect(bool usbDirect)
static QString cleanPortDisplayName(const QString &name)
void setPortName(const QString &name)
AutoConnectSettings * autoConnectSettings() const
static SettingsManager * instance()
MavlinkSettings * mavlinkSettings() const
void setAutoConnect(bool autoc=true) override
Set if this is this an Auto Connect configuration.
Q_INVOKABLE void addHost(const QString &host)
UdpIODevice provides a QIODevice interface over a QUdpSocket in server mode.
bool isBluetoothAvailable()
Check if Bluetooth is available on this device.
void showAppMessage(const QString &message, const QString &title)
Modal application message. Queued if the UI isn't ready yet.