LinkManager.cc 36.4 KB
Newer Older
1 2 3 4 5 6 7 8
/****************************************************************************
 *
 *   (c) 2009-2016 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
 *
 * QGroundControl is licensed according to the terms in the file
 * COPYING.md in the root of the source code directory.
 *
 ****************************************************************************/
lm's avatar
lm committed
9

pixhawk's avatar
pixhawk committed
10 11
#include <QList>
#include <QApplication>
12
#include <QDebug>
13
#include <QSignalSpy>
dogmaphobic's avatar
dogmaphobic committed
14

Gus Grubba's avatar
Gus Grubba committed
15
#ifndef NO_SERIAL_LINK
Don Gagne's avatar
Don Gagne committed
16
#include "QGCSerialPortInfo.h"
dogmaphobic's avatar
dogmaphobic committed
17
#endif
18

19
#include "LinkManager.h"
20
#include "QGCApplication.h"
Don Gagne's avatar
Don Gagne committed
21 22
#include "UDPLink.h"
#include "TCPLink.h"
23
#include "SettingsManager.h"
dogmaphobic's avatar
dogmaphobic committed
24
#ifdef QGC_ENABLE_BLUETOOTH
dogmaphobic's avatar
dogmaphobic committed
25 26
#include "BluetoothLink.h"
#endif
27 28 29
#if defined(QGC_GST_TAISYNC_ENABLED)
#include "TaisyncLink.h"
#endif
30

Don Gagne's avatar
Don Gagne committed
31
#ifndef __mobile__
DonLakeFlyer's avatar
DonLakeFlyer committed
32
#include "GPSManager.h"
33
#include "PositionManager.h"
Don Gagne's avatar
Don Gagne committed
34 35
#endif

Don Gagne's avatar
Don Gagne committed
36
QGC_LOGGING_CATEGORY(LinkManagerLog, "LinkManagerLog")
Don Gagne's avatar
Don Gagne committed
37 38
QGC_LOGGING_CATEGORY(LinkManagerVerboseLog, "LinkManagerVerboseLog")

Don Gagne's avatar
Don Gagne committed
39
const char* LinkManager::_defaultUPDLinkName =       "UDP Link (AutoConnect)";
40

41 42 43 44 45 46 47 48
const int LinkManager::_autoconnectUpdateTimerMSecs =   1000;
#ifdef Q_OS_WIN
// Have to manually let the bootloader go by on Windows to get a working connect
const int LinkManager::_autoconnectConnectDelayMSecs =  6000;
#else
const int LinkManager::_autoconnectConnectDelayMSecs =  1000;
#endif

49 50
LinkManager::LinkManager(QGCApplication* app, QGCToolbox* toolbox)
    : QGCTool(app, toolbox)
51 52 53
    , _configUpdateSuspended(false)
    , _configurationsLoaded(false)
    , _connectionsSuspended(false)
54
    , _mavlinkChannelsUsedBitMask(1)    // We never use channel 0 to avoid sequence numbering problems
55 56
    , _autoConnectSettings(nullptr)
    , _mavlinkProtocol(nullptr)
57
#ifndef __mobile__
58
#ifndef NO_SERIAL_LINK
59
    , _nmeaPort(nullptr)
60
#endif
61
#endif
pixhawk's avatar
pixhawk committed
62
{
Don Gagne's avatar
Don Gagne committed
63 64 65 66
    qmlRegisterUncreatableType<LinkManager>         ("QGroundControl", 1, 0, "LinkManager",         "Reference only");
    qmlRegisterUncreatableType<LinkConfiguration>   ("QGroundControl", 1, 0, "LinkConfiguration",   "Reference only");
    qmlRegisterUncreatableType<LinkInterface>       ("QGroundControl", 1, 0, "LinkInterface",       "Reference only");

Gus Grubba's avatar
Gus Grubba committed
67
#ifndef NO_SERIAL_LINK
Don Gagne's avatar
Don Gagne committed
68 69 70
    _activeLinkCheckTimer.setInterval(_activeLinkCheckTimeoutMSecs);
    _activeLinkCheckTimer.setSingleShot(false);
    connect(&_activeLinkCheckTimer, &QTimer::timeout, this, &LinkManager::_activeLinkCheck);
Don Gagne's avatar
Don Gagne committed
71
#endif
pixhawk's avatar
pixhawk committed
72 73 74 75
}

LinkManager::~LinkManager()
{
76
#ifndef __mobile__
77
#ifndef NO_SERIAL_LINK
78
    delete _nmeaPort;
79
#endif
80
#endif
pixhawk's avatar
pixhawk committed
81 82
}

83 84
void LinkManager::setToolbox(QGCToolbox *toolbox)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
85
    QGCTool::setToolbox(toolbox);
86

DonLakeFlyer's avatar
DonLakeFlyer committed
87 88
    _autoConnectSettings = toolbox->settingsManager()->autoConnectSettings();
    _mavlinkProtocol = _toolbox->mavlinkProtocol();
89

90
    connect(_mavlinkProtocol, &MAVLinkProtocol::messageReceived, this, &LinkManager::_mavlinkMessageReceived);
91

Don Gagne's avatar
Don Gagne committed
92
    connect(&_portListTimer, &QTimer::timeout, this, &LinkManager::_updateAutoConnectLinks);
93
    _portListTimer.start(_autoconnectUpdateTimerMSecs); // timeout must be long enough to get past bootloader on second pass
94

95 96
}

Don Gagne's avatar
Don Gagne committed
97 98 99 100 101 102 103 104 105 106
// This should only be used by Qml code
void LinkManager::createConnectedLink(LinkConfiguration* config)
{
    for(int i = 0; i < _sharedConfigurations.count(); i++) {
        SharedLinkConfigurationPointer& sharedConf = _sharedConfigurations[i];
        if (sharedConf->name() == config->name())
            createConnectedLink(sharedConf);
    }
}

107
LinkInterface* LinkManager::createConnectedLink(SharedLinkConfigurationPointer& config, bool isPX4Flow)
108
{
109
    if (!config) {
110 111
        qWarning() << "LinkManager::createConnectedLink called with nullptr config";
        return nullptr;
112 113
    }

114
    LinkInterface* pLink = nullptr;
115
    switch(config->type()) {
Gus Grubba's avatar
Gus Grubba committed
116
#ifndef NO_SERIAL_LINK
DonLakeFlyer's avatar
DonLakeFlyer committed
117 118 119 120
    case LinkConfiguration::TypeSerial:
    {
        SerialConfiguration* serialConfig = dynamic_cast<SerialConfiguration*>(config.data());
        if (serialConfig) {
121
            pLink = new SerialLink(config, isPX4Flow);
DonLakeFlyer's avatar
DonLakeFlyer committed
122
            if (serialConfig->usbDirect()) {
123
                _activeLinkCheckList.append(dynamic_cast<SerialLink*>(pLink));
DonLakeFlyer's avatar
DonLakeFlyer committed
124 125
                if (!_activeLinkCheckTimer.isActive()) {
                    _activeLinkCheckTimer.start();
Don Gagne's avatar
Don Gagne committed
126 127 128
                }
            }
        }
DonLakeFlyer's avatar
DonLakeFlyer committed
129
    }
Don Gagne's avatar
Don Gagne committed
130
        break;
DonLakeFlyer's avatar
DonLakeFlyer committed
131 132
#else
    Q_UNUSED(isPX4Flow)
dogmaphobic's avatar
dogmaphobic committed
133
#endif
DonLakeFlyer's avatar
DonLakeFlyer committed
134 135 136 137 138 139
    case LinkConfiguration::TypeUdp:
        pLink = new UDPLink(config);
        break;
    case LinkConfiguration::TypeTcp:
        pLink = new TCPLink(config);
        break;
dogmaphobic's avatar
dogmaphobic committed
140
#ifdef QGC_ENABLE_BLUETOOTH
DonLakeFlyer's avatar
DonLakeFlyer committed
141 142 143
    case LinkConfiguration::TypeBluetooth:
        pLink = new BluetoothLink(config);
        break;
dogmaphobic's avatar
dogmaphobic committed
144
#endif
dogmaphobic's avatar
dogmaphobic committed
145
#ifndef __mobile__
DonLakeFlyer's avatar
DonLakeFlyer committed
146 147 148
    case LinkConfiguration::TypeLogReplay:
        pLink = new LogReplayLink(config);
        break;
dogmaphobic's avatar
dogmaphobic committed
149
#endif
150
#ifdef QT_DEBUG
DonLakeFlyer's avatar
DonLakeFlyer committed
151 152 153
    case LinkConfiguration::TypeMock:
        pLink = new MockLink(config);
        break;
154 155 156 157 158
#endif
#if defined(QGC_GST_TAISYNC_ENABLED)
    case LinkConfiguration::TypeTaisync:
        pLink = new TaisyncLink(config);
        break;
159
#endif
DonLakeFlyer's avatar
DonLakeFlyer committed
160 161
    case LinkConfiguration::TypeLast:
        break;
162
    }
163
    if (pLink) {
164 165
        _addLink(pLink);
        connectLink(pLink);
166 167 168 169
    }
    return pLink;
}

Don Gagne's avatar
Don Gagne committed
170
LinkInterface* LinkManager::createConnectedLink(const QString& name)
171
{
DonLakeFlyer's avatar
DonLakeFlyer committed
172 173 174 175 176 177 178 179 180
    if (name.isEmpty()) {
        qWarning() << "Internal error";
    } else {
        for(int i = 0; i < _sharedConfigurations.count(); i++) {
            SharedLinkConfigurationPointer& conf = _sharedConfigurations[i];
            if (conf->name() == name) {
                return createConnectedLink(conf);
            }
        }
181
    }
182
    return nullptr;
183 184
}

185
void LinkManager::_addLink(LinkInterface* link)
pixhawk's avatar
pixhawk committed
186
{
Don Gagne's avatar
Don Gagne committed
187
    if (thread() != QThread::currentThread()) {
188
        qWarning() << "_addLink called from incorrect thread";
Don Gagne's avatar
Don Gagne committed
189 190
        return;
    }
191

Don Gagne's avatar
Don Gagne committed
192 193 194
    if (!link) {
        return;
    }
195

196
    if (!containsLink(link)) {
197 198 199 200
        int mavlinkChannel = _reserveMavlinkChannel();
        if (mavlinkChannel != 0) {
            link->_setMavlinkChannel(mavlinkChannel);
        } else {
201
            qWarning() << "Ran out of mavlink channels";
202
            return;
203 204
        }

205
        _sharedLinks.append(SharedLinkInterfacePointer(link));
206 207
        emit newLink(link);
    }
208

Don Gagne's avatar
Don Gagne committed
209 210
    connect(link, &LinkInterface::communicationError,   _app,               &QGCApplication::criticalMessageBoxOnMainThread);
    connect(link, &LinkInterface::bytesReceived,        _mavlinkProtocol,   &MAVLinkProtocol::receiveBytes);
211

212
    _mavlinkProtocol->resetMetadataForLink(link);
213
    _mavlinkProtocol->setVersion(_mavlinkProtocol->getCurrentVersion());
214

215 216 217 218 219 220
    connect(link, &LinkInterface::connected,            this, &LinkManager::_linkConnected);
    connect(link, &LinkInterface::disconnected,         this, &LinkManager::_linkDisconnected);

    // This connection is queued since it will cloe the link. So we want the link emitter to return otherwise we would
    // close the link our from under itself.
    connect(link, &LinkInterface::connectionRemoved,    this, &LinkManager::_linkConnectionRemoved, Qt::QueuedConnection);
221
}
pixhawk's avatar
pixhawk committed
222

Don Gagne's avatar
Don Gagne committed
223
void LinkManager::disconnectAll(void)
pixhawk's avatar
pixhawk committed
224
{
Don Gagne's avatar
Don Gagne committed
225
    // Walk list in reverse order to preserve indices during delete
226 227
    for (int i=_sharedLinks.count()-1; i>=0; i--) {
        disconnectLink(_sharedLinks[i].data());
228
    }
pixhawk's avatar
pixhawk committed
229 230 231 232
}

bool LinkManager::connectLink(LinkInterface* link)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
233 234 235 236 237 238 239
    if (link) {
        if (_connectionsSuspendedMsg()) {
            return false;
        }
        return link->_connect();
    } else {
        qWarning() << "Internal error";
240 241
        return false;
    }
pixhawk's avatar
pixhawk committed
242 243
}

Don Gagne's avatar
Don Gagne committed
244
void LinkManager::disconnectLink(LinkInterface* link)
pixhawk's avatar
pixhawk committed
245
{
246
    if (!link || !containsLink(link)) {
247 248
        return;
    }
Don Gagne's avatar
Don Gagne committed
249

Don Gagne's avatar
Don Gagne committed
250
    link->_disconnect();
251

Don Gagne's avatar
Don Gagne committed
252
    LinkConfiguration* config = link->getLinkConfiguration();
253 254 255 256 257
    for (int i=0; i<_sharedAutoconnectConfigurations.count(); i++) {
        if (_sharedAutoconnectConfigurations[i].data() == config) {
            qCDebug(LinkManagerLog) << "Removing disconnected autoconnect config" << config->name();
            _sharedAutoconnectConfigurations.removeAt(i);
            break;
258
        }
259
    }
260

Don Gagne's avatar
Don Gagne committed
261
    _deleteLink(link);
pixhawk's avatar
pixhawk committed
262 263
}

264
void LinkManager::_deleteLink(LinkInterface* link)
265
{
Don Gagne's avatar
Don Gagne committed
266 267 268 269 270 271 272 273
    if (thread() != QThread::currentThread()) {
        qWarning() << "_deleteLink called from incorrect thread";
        return;
    }

    if (!link) {
        return;
    }
274

275
    // Free up the mavlink channel associated with this link
276
    _freeMavlinkChannel(link->mavlinkChannel());
277

278 279 280 281 282 283
    for (int i=0; i<_sharedLinks.count(); i++) {
        if (_sharedLinks[i].data() == link) {
            _sharedLinks.removeAt(i);
            break;
        }
    }
284

Don Gagne's avatar
Don Gagne committed
285
    // Emit removal of link
286
    emit linkDeleted(link);
pixhawk's avatar
pixhawk committed
287 288
}

289 290 291 292 293 294 295 296
SharedLinkInterfacePointer LinkManager::sharedLinkInterfacePointerForLink(LinkInterface* link)
{
    for (int i=0; i<_sharedLinks.count(); i++) {
        if (_sharedLinks[i].data() == link) {
            return _sharedLinks[i];
        }
    }

297 298
    qWarning() << "LinkManager::sharedLinkInterfaceForLink returning nullptr";
    return SharedLinkInterfacePointer(nullptr);
299 300
}

301 302 303 304 305
/// @brief If all new connections should be suspended a message is displayed to the user and true
///         is returned;
bool LinkManager::_connectionsSuspendedMsg(void)
{
    if (_connectionsSuspended) {
306
        qgcApp()->showMessage(tr("Connect not allowed: %1").arg(_connectionsSuspendedReason));
307 308 309 310 311 312 313 314 315 316 317
        return true;
    } else {
        return false;
    }
}

void LinkManager::setConnectionsSuspended(QString reason)
{
    _connectionsSuspended = true;
    _connectionsSuspendedReason = reason;
}
318

319 320 321 322 323 324 325 326 327
void LinkManager::_linkConnected(void)
{
    emit linkConnected((LinkInterface*)sender());
}

void LinkManager::_linkDisconnected(void)
{
    emit linkDisconnected((LinkInterface*)sender());
}
328

329 330 331 332 333 334
void LinkManager::_linkConnectionRemoved(LinkInterface* link)
{
    // Link has been removed from system, disconnect it automatically
    disconnectLink(link);
}

335 336 337 338 339 340 341 342 343
void LinkManager::suspendConfigurationUpdates(bool suspend)
{
    _configUpdateSuspended = suspend;
}

void LinkManager::saveLinkConfigurationList()
{
    QSettings settings;
    settings.remove(LinkConfiguration::settingsRoot());
344
    int trueCount = 0;
345 346
    for (int i = 0; i < _sharedConfigurations.count(); i++) {
        SharedLinkConfigurationPointer linkConfig = _sharedConfigurations[i];
Don Gagne's avatar
Don Gagne committed
347
        if (linkConfig) {
348
            if (!linkConfig->isDynamic()) {
Don Gagne's avatar
Don Gagne committed
349
                QString root = LinkConfiguration::settingsRoot();
350
                root += QString("/Link%1").arg(trueCount++);
Don Gagne's avatar
Don Gagne committed
351 352
                settings.setValue(root + "/name", linkConfig->name());
                settings.setValue(root + "/type", linkConfig->type());
353
                settings.setValue(root + "/auto", linkConfig->isAutoConnect());
354
                settings.setValue(root + "/high_latency", linkConfig->isHighLatency());
Don Gagne's avatar
Don Gagne committed
355 356 357 358
                // Have the instance save its own values
                linkConfig->saveSettings(settings, root);
            }
        } else {
359
            qWarning() << "Internal error for link configuration in LinkManager";
dogmaphobic's avatar
dogmaphobic committed
360
        }
361
    }
dogmaphobic's avatar
dogmaphobic committed
362
    QString root(LinkConfiguration::settingsRoot());
363 364
    settings.setValue(root + "/count", trueCount);
    emit linkConfigurationsChanged();
365 366 367 368
}

void LinkManager::loadLinkConfigurationList()
{
369
    bool linksChanged = false;
370 371 372 373 374 375 376 377 378
    QSettings settings;
    // Is the group even there?
    if(settings.contains(LinkConfiguration::settingsRoot() + "/count")) {
        // Find out how many configurations we have
        int count = settings.value(LinkConfiguration::settingsRoot() + "/count").toInt();
        for(int i = 0; i < count; i++) {
            QString root(LinkConfiguration::settingsRoot());
            root += QString("/Link%1").arg(i);
            if(settings.contains(root + "/type")) {
379 380
                LinkConfiguration::LinkType type = static_cast<LinkConfiguration::LinkType>(settings.value(root + "/type").toInt());
                if(type < LinkConfiguration::TypeLast) {
381 382 383
                    if(settings.contains(root + "/name")) {
                        QString name = settings.value(root + "/name").toString();
                        if(!name.isEmpty()) {
384
                            LinkConfiguration* pLink = nullptr;
385
                            bool autoConnect = settings.value(root + "/auto").toBool();
386
                            bool highLatency = settings.value(root + "/high_latency").toBool();
387
                            switch(type) {
Gus Grubba's avatar
Gus Grubba committed
388
#ifndef NO_SERIAL_LINK
DonLakeFlyer's avatar
DonLakeFlyer committed
389
                            case LinkConfiguration::TypeSerial:
390
                                pLink = dynamic_cast<LinkConfiguration*>(new SerialConfiguration(name));
DonLakeFlyer's avatar
DonLakeFlyer committed
391
                                break;
dogmaphobic's avatar
dogmaphobic committed
392
#endif
DonLakeFlyer's avatar
DonLakeFlyer committed
393
                            case LinkConfiguration::TypeUdp:
394
                                pLink = dynamic_cast<LinkConfiguration*>(new UDPConfiguration(name));
DonLakeFlyer's avatar
DonLakeFlyer committed
395 396
                                break;
                            case LinkConfiguration::TypeTcp:
397
                                pLink = dynamic_cast<LinkConfiguration*>(new TCPConfiguration(name));
DonLakeFlyer's avatar
DonLakeFlyer committed
398
                                break;
dogmaphobic's avatar
dogmaphobic committed
399
#ifdef QGC_ENABLE_BLUETOOTH
DonLakeFlyer's avatar
DonLakeFlyer committed
400
                            case LinkConfiguration::TypeBluetooth:
401
                                pLink = dynamic_cast<LinkConfiguration*>(new BluetoothConfiguration(name));
DonLakeFlyer's avatar
DonLakeFlyer committed
402
                                break;
dogmaphobic's avatar
dogmaphobic committed
403
#endif
dogmaphobic's avatar
dogmaphobic committed
404
#ifndef __mobile__
DonLakeFlyer's avatar
DonLakeFlyer committed
405
                            case LinkConfiguration::TypeLogReplay:
406
                                pLink = dynamic_cast<LinkConfiguration*>(new LogReplayLinkConfiguration(name));
DonLakeFlyer's avatar
DonLakeFlyer committed
407
                                break;
dogmaphobic's avatar
dogmaphobic committed
408
#endif
409
#ifdef QT_DEBUG
DonLakeFlyer's avatar
DonLakeFlyer committed
410
                            case LinkConfiguration::TypeMock:
411 412 413 414 415 416
                                pLink = dynamic_cast<LinkConfiguration*>(new MockConfiguration(name));
                                break;
#endif
#if defined(QGC_GST_TAISYNC_ENABLED)
                            case LinkConfiguration::TypeTaisync:
                                pLink = dynamic_cast<LinkConfiguration*>(new TaisyncConfiguration(name));
DonLakeFlyer's avatar
DonLakeFlyer committed
417
                                break;
418
#endif
DonLakeFlyer's avatar
DonLakeFlyer committed
419 420
                            case LinkConfiguration::TypeLast:
                                break;
421 422
                            }
                            if(pLink) {
423 424
                                //-- Have the instance load its own values
                                pLink->setAutoConnect(autoConnect);
425
                                pLink->setHighLatency(highLatency);
426
                                pLink->loadSettings(settings, root);
427
                                addConfiguration(pLink);
428
                                linksChanged = true;
429 430
                            }
                        } else {
431
                            qWarning() << "Link Configuration" << root << "has an empty name." ;
432 433
                        }
                    } else {
434
                        qWarning() << "Link Configuration" << root << "has no name." ;
435 436
                    }
                } else {
437
                    qWarning() << "Link Configuration" << root << "an invalid type: " << type;
438 439
                }
            } else {
440
                qWarning() << "Link Configuration" << root << "has no type." ;
441 442 443
            }
        }
    }
444 445

    if(linksChanged) {
446
        emit linkConfigurationsChanged();
447 448
    }
    // Enable automatic Serial PX4/3DR Radio hunting
449 450 451
    _configurationsLoaded = true;
}

Gus Grubba's avatar
Gus Grubba committed
452
#ifndef NO_SERIAL_LINK
Don Gagne's avatar
Don Gagne committed
453
SerialConfiguration* LinkManager::_autoconnectConfigurationsContainsPort(const QString& portName)
454 455
{
    QString searchPort = portName.trimmed();
Don Gagne's avatar
Don Gagne committed
456

457 458
    for (int i=0; i<_sharedAutoconnectConfigurations.count(); i++) {
        SerialConfiguration* serialConfig = qobject_cast<SerialConfiguration*>(_sharedAutoconnectConfigurations[i].data());
Don Gagne's avatar
Don Gagne committed
459

460 461 462
        if (serialConfig) {
            if (serialConfig->portName() == searchPort) {
                return serialConfig;
463
            }
Don Gagne's avatar
Don Gagne committed
464 465
        } else {
            qWarning() << "Internal error";
466 467
        }
    }
468
    return nullptr;
469
}
dogmaphobic's avatar
dogmaphobic committed
470
#endif
471

Don Gagne's avatar
Don Gagne committed
472
void LinkManager::_updateAutoConnectLinks(void)
473
{
Don Gagne's avatar
Don Gagne committed
474
    if (_connectionsSuspended || qgcApp()->runningUnitTests()) {
475 476
        return;
    }
Don Gagne's avatar
Don Gagne committed
477 478
    // Re-add UDP if we need to
    bool foundUDP = false;
479
    for (int i = 0; i < _sharedLinks.count(); i++) {
480
        LinkConfiguration* linkConfig = _sharedLinks[i]->getLinkConfiguration();
Don Gagne's avatar
Don Gagne committed
481 482 483 484 485
        if (linkConfig->type() == LinkConfiguration::TypeUdp && linkConfig->name() == _defaultUPDLinkName) {
            foundUDP = true;
            break;
        }
    }
486
    if (!foundUDP && _autoConnectSettings->autoConnectUDP()->rawValue().toBool()) {
Don Gagne's avatar
Don Gagne committed
487
        qCDebug(LinkManagerLog) << "New auto-connect UDP port added";
488
        //-- Default UDPConfiguration is set up for autoconnect
Don Gagne's avatar
Don Gagne committed
489
        UDPConfiguration* udpConfig = new UDPConfiguration(_defaultUPDLinkName);
DonLakeFlyer's avatar
DonLakeFlyer committed
490
        udpConfig->setDynamic(true);
491 492
        SharedLinkConfigurationPointer config = addConfiguration(udpConfig);
        createConnectedLink(config);
493
        emit linkConfigurationsChanged();
Don Gagne's avatar
Don Gagne committed
494 495
    }

Gus Grubba's avatar
Gus Grubba committed
496
#ifndef NO_SERIAL_LINK
dogmaphobic's avatar
dogmaphobic committed
497
    QStringList currentPorts;
498 499 500 501 502
    QList<QGCSerialPortInfo> portList;
#ifdef __android__
    // Android builds only support a single serial connection. Repeatedly calling availablePorts after that one serial
    // port is connected leaks file handles due to a bug somewhere in android serial code. In order to work around that
    // bug after we connect the first serial port we stop probing for additional ports.
503
    if (!_sharedAutoconnectConfigurations.count()) {
504 505
        portList = QGCSerialPortInfo::availablePorts();
    }
506 507
#else
    portList = QGCSerialPortInfo::availablePorts();
508
#endif
Don Gagne's avatar
Don Gagne committed
509

510
    // Iterate Comm Ports
511
    for (QGCSerialPortInfo portInfo: portList) {
Don Gagne's avatar
Don Gagne committed
512 513 514 515 516 517 518 519 520
        qCDebug(LinkManagerVerboseLog) << "-----------------------------------------------------";
        qCDebug(LinkManagerVerboseLog) << "portName:          " << portInfo.portName();
        qCDebug(LinkManagerVerboseLog) << "systemLocation:    " << portInfo.systemLocation();
        qCDebug(LinkManagerVerboseLog) << "description:       " << portInfo.description();
        qCDebug(LinkManagerVerboseLog) << "manufacturer:      " << portInfo.manufacturer();
        qCDebug(LinkManagerVerboseLog) << "serialNumber:      " << portInfo.serialNumber();
        qCDebug(LinkManagerVerboseLog) << "vendorIdentifier:  " << portInfo.vendorIdentifier();
        qCDebug(LinkManagerVerboseLog) << "productIdentifier: " << portInfo.productIdentifier();

dogmaphobic's avatar
dogmaphobic committed
521 522
        // Save port name
        currentPorts << portInfo.systemLocation();
Don Gagne's avatar
Don Gagne committed
523

524 525
        QGCSerialPortInfo::BoardType_t boardType;
        QString boardName;
Don Gagne's avatar
Don Gagne committed
526

527
#ifndef NO_SERIAL_LINK
528
#ifndef __mobile__
529 530 531 532 533 534
        if (portInfo.systemLocation().trimmed() == _autoConnectSettings->autoConnectNmeaPort()->cookedValueString()) {
            if (portInfo.systemLocation().trimmed() != _nmeaDeviceName) {
                _nmeaDeviceName = portInfo.systemLocation().trimmed();
                qCDebug(LinkManagerLog) << "Configuring nmea port" << _nmeaDeviceName;
                QSerialPort* newPort = new QSerialPort(portInfo);
                _nmeaBaud = _autoConnectSettings->autoConnectNmeaBaud()->cookedValue().toUInt();
535
                newPort->setBaudRate(static_cast<qint32>(_nmeaBaud));
536 537 538 539 540 541 542 543 544
                qCDebug(LinkManagerLog) << "Configuring nmea baudrate" << _nmeaBaud;
                // This will stop polling old device if previously set
                _toolbox->qgcPositionManager()->setNmeaSourceDevice(newPort);
                if (_nmeaPort) {
                    delete _nmeaPort;
                }
                _nmeaPort = newPort;
            } else if (_autoConnectSettings->autoConnectNmeaBaud()->cookedValue().toUInt() != _nmeaBaud) {
                _nmeaBaud = _autoConnectSettings->autoConnectNmeaBaud()->cookedValue().toUInt();
545
                _nmeaPort->setBaudRate(static_cast<qint32>(_nmeaBaud));
546 547
                qCDebug(LinkManagerLog) << "Configuring nmea baudrate" << _nmeaBaud;
            }
548
        } else
549
#endif
550 551
#endif
        if (portInfo.getBoardInfo(boardType, boardName)) {
Don Gagne's avatar
Don Gagne committed
552 553
            if (portInfo.isBootloader()) {
                // Don't connect to bootloader
554
                qCDebug(LinkManagerLog) << "Waiting for bootloader to finish" << portInfo.systemLocation();
Don Gagne's avatar
Don Gagne committed
555 556
                continue;
            }
557
            if (_autoconnectConfigurationsContainsPort(portInfo.systemLocation()) || _autoConnectRTKPort == portInfo.systemLocation()) {
558 559 560 561 562 563
                qCDebug(LinkManagerVerboseLog) << "Skipping existing autoconnect" << portInfo.systemLocation();
            } else if (!_autoconnectWaitList.contains(portInfo.systemLocation())) {
                // We don't connect to the port the first time we see it. The ability to correctly detect whether we
                // are in the bootloader is flaky from a cross-platform standpoint. So by putting it on a wait list
                // and only connect on the second pass we leave enough time for the board to boot up.
                qCDebug(LinkManagerLog) << "Waiting for next autoconnect pass" << portInfo.systemLocation();
564 565
                _autoconnectWaitList[portInfo.systemLocation()] = 1;
            } else if (++_autoconnectWaitList[portInfo.systemLocation()] * _autoconnectUpdateTimerMSecs > _autoconnectConnectDelayMSecs) {
566
                SerialConfiguration* pSerialConfig = nullptr;
567
                _autoconnectWaitList.remove(portInfo.systemLocation());
Don Gagne's avatar
Don Gagne committed
568
                switch (boardType) {
569
                case QGCSerialPortInfo::BoardTypePixhawk:
570
                    if (_autoConnectSettings->autoConnectPixhawk()->rawValue().toBool()) {
571
                        pSerialConfig = new SerialConfiguration(tr("%1 on %2 (AutoConnect)").arg(boardName).arg(portInfo.portName().trimmed()));
572 573 574
                        pSerialConfig->setUsbDirect(true);
                    }
                    break;
Don Gagne's avatar
Don Gagne committed
575
                case QGCSerialPortInfo::BoardTypePX4Flow:
576
                    if (_autoConnectSettings->autoConnectPX4Flow()->rawValue().toBool()) {
577
                        pSerialConfig = new SerialConfiguration(tr("%1 on %2 (AutoConnect)").arg(boardName).arg(portInfo.portName().trimmed()));
Don Gagne's avatar
Don Gagne committed
578
                    }
Don Gagne's avatar
Don Gagne committed
579
                    break;
580
                case QGCSerialPortInfo::BoardTypeSiKRadio:
581
                    if (_autoConnectSettings->autoConnectSiKRadio()->rawValue().toBool()) {
582
                        pSerialConfig = new SerialConfiguration(tr("%1 on %2 (AutoConnect)").arg(boardName).arg(portInfo.portName().trimmed()));
Don Gagne's avatar
Don Gagne committed
583 584
                    }
                    break;
585
                case QGCSerialPortInfo::BoardTypeOpenPilot:
586
                    if (_autoConnectSettings->autoConnectLibrePilot()->rawValue().toBool()) {
587
                        pSerialConfig = new SerialConfiguration(tr("%1 on %2 (AutoConnect)").arg(boardName).arg(portInfo.portName().trimmed()));
588 589
                    }
                    break;
Don Gagne's avatar
Don Gagne committed
590 591
#ifndef __mobile__
                case QGCSerialPortInfo::BoardTypeRTKGPS:
592
                    if (_autoConnectSettings->autoConnectRTKGPS()->rawValue().toBool() && !_toolbox->gpsManager()->connected()) {
593 594
                        qCDebug(LinkManagerLog) << "RTK GPS auto-connected" << portInfo.portName().trimmed();
                        _autoConnectRTKPort = portInfo.systemLocation();
595
                        _toolbox->gpsManager()->connectGPS(portInfo.systemLocation(), boardName);
Don Gagne's avatar
Don Gagne committed
596 597 598
                    }
                    break;
#endif
Don Gagne's avatar
Don Gagne committed
599 600
                default:
                    qWarning() << "Internal error";
Don Gagne's avatar
Don Gagne committed
601
                    continue;
dogmaphobic's avatar
dogmaphobic committed
602
                }
Don Gagne's avatar
Don Gagne committed
603 604
                if (pSerialConfig) {
                    qCDebug(LinkManagerLog) << "New auto-connect port added: " << pSerialConfig->name() << portInfo.systemLocation();
605
                    pSerialConfig->setBaud(boardType == QGCSerialPortInfo::BoardTypeSiKRadio ? 57600 : 115200);
Don Gagne's avatar
Don Gagne committed
606 607
                    pSerialConfig->setDynamic(true);
                    pSerialConfig->setPortName(portInfo.systemLocation());
608
                    _sharedAutoconnectConfigurations.append(SharedLinkConfigurationPointer(pSerialConfig));
609
                    createConnectedLink(_sharedAutoconnectConfigurations.last(), boardType == QGCSerialPortInfo::BoardTypePX4Flow);
Don Gagne's avatar
Don Gagne committed
610
                }
dogmaphobic's avatar
dogmaphobic committed
611 612 613
            }
        }
    }
Don Gagne's avatar
Don Gagne committed
614

615 616 617
#ifndef __android__
    // Android builds only support a single serial connection. Repeatedly calling availablePorts after that one serial
    // port is connected leaks file handles due to a bug somewhere in android serial code. In order to work around that
618
    // bug after we connect the first serial port we stop probing for additional ports. That means we must rely on
619 620 621
    // the port disconnecting itself when the radio is pulled to signal communication list as opposed to automatically
    // closing the Link.

dogmaphobic's avatar
dogmaphobic committed
622 623
    // Now we go through the current configuration list and make sure any dynamic config has gone away
    QList<LinkConfiguration*>  _confToDelete;
624 625 626 627 628 629 630
    for (int i=0; i<_sharedAutoconnectConfigurations.count(); i++) {
        SerialConfiguration* serialConfig = qobject_cast<SerialConfiguration*>(_sharedAutoconnectConfigurations[i].data());
        if (serialConfig) {
            if (!currentPorts.contains(serialConfig->portName())) {
                if (serialConfig->link()) {
                    if (serialConfig->link()->isConnected()) {
                        if (serialConfig->link()->active()) {
631 632 633 634 635 636
                            // We don't remove links which are still connected which have been active with a vehicle on them
                            // even though at this point the cable may have been pulled. Instead we wait for the user to
                            // Disconnect. Once the user disconnects, the link will be removed.
                            continue;
                        }
                    }
Don Gagne's avatar
Don Gagne committed
637
                }
638
                _confToDelete.append(serialConfig);
dogmaphobic's avatar
dogmaphobic committed
639
            }
Don Gagne's avatar
Don Gagne committed
640 641
        } else {
            qWarning() << "Internal error";
dogmaphobic's avatar
dogmaphobic committed
642 643
        }
    }
Don Gagne's avatar
Don Gagne committed
644

Don Gagne's avatar
Don Gagne committed
645
    // Now remove all configs that are gone
646
    for (LinkConfiguration* pDeleteConfig: _confToDelete) {
Don Gagne's avatar
Don Gagne committed
647
        qCDebug(LinkManagerLog) << "Removing unused autoconnect config" << pDeleteConfig->name();
Don Gagne's avatar
Don Gagne committed
648 649 650
        if (pDeleteConfig->link()) {
            disconnectLink(pDeleteConfig->link());
        }
651 652 653 654 655 656
        for (int i=0; i<_sharedAutoconnectConfigurations.count(); i++) {
            if (_sharedAutoconnectConfigurations[i].data() == pDeleteConfig) {
                _sharedAutoconnectConfigurations.removeAt(i);
                break;
            }
        }
657
    }
658 659

    // Check for RTK GPS connection gone
660
#if !defined(__mobile__)
661 662 663 664 665
    if (!_autoConnectRTKPort.isEmpty() && !currentPorts.contains(_autoConnectRTKPort)) {
        qCDebug(LinkManagerLog) << "RTK GPS disconnected" << _autoConnectRTKPort;
        _toolbox->gpsManager()->disconnectGPS();
        _autoConnectRTKPort.clear();
    }
666
#endif
667

668
#endif
Gus Grubba's avatar
Gus Grubba committed
669
#endif // NO_SERIAL_LINK
670 671
}

Don Gagne's avatar
Don Gagne committed
672 673
void LinkManager::shutdown(void)
{
674
    setConnectionsSuspended(tr("Shutdown"));
Don Gagne's avatar
Don Gagne committed
675
    disconnectAll();
Don Gagne's avatar
Don Gagne committed
676 677
}

678 679 680 681 682 683
QStringList LinkManager::linkTypeStrings(void) const
{
    //-- Must follow same order as enum LinkType in LinkConfiguration.h
    static QStringList list;
    if(!list.size())
    {
Gus Grubba's avatar
Gus Grubba committed
684
#ifndef NO_SERIAL_LINK
685 686 687 688 689 690
        list += tr("Serial");
#endif
        list += tr("UDP");
        list += tr("TCP");
#if defined(QGC_GST_TAISYNC_ENABLED)
        list += tr("Taisync");
691
#endif
dogmaphobic's avatar
dogmaphobic committed
692
#ifdef QGC_ENABLE_BLUETOOTH
dogmaphobic's avatar
dogmaphobic committed
693 694
        list += "Bluetooth";
#endif
dogmaphobic's avatar
dogmaphobic committed
695
#ifdef QT_DEBUG
696
        list += tr("Mock Link");
dogmaphobic's avatar
dogmaphobic committed
697 698
#endif
#ifndef __mobile__
699
        list += tr("Log Replay");
dogmaphobic's avatar
dogmaphobic committed
700
#endif
701
        if (list.size() != static_cast<int>(LinkConfiguration::TypeLast)) {
DonLakeFlyer's avatar
DonLakeFlyer committed
702 703
            qWarning() << "Internal error";
        }
704 705 706 707
    }
    return list;
}

708
void LinkManager::_updateSerialPorts()
709
{
710 711
    _commPortList.clear();
    _commPortDisplayList.clear();
Gus Grubba's avatar
Gus Grubba committed
712
#ifndef NO_SERIAL_LINK
713
    QList<QSerialPortInfo> portList = QSerialPortInfo::availablePorts();
714
    for (const QSerialPortInfo &info: portList)
715
    {
716 717 718
        QString port = info.systemLocation().trimmed();
        _commPortList += port;
        _commPortDisplayList += SerialConfiguration::cleanPortDisplayname(port);
719 720
    }
#endif
721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737
}

QStringList LinkManager::serialPortStrings(void)
{
    if(!_commPortDisplayList.size())
    {
        _updateSerialPorts();
    }
    return _commPortDisplayList;
}

QStringList LinkManager::serialPorts(void)
{
    if(!_commPortList.size())
    {
        _updateSerialPorts();
    }
738 739 740 741 742
    return _commPortList;
}

QStringList LinkManager::serialBaudRates(void)
{
Gus Grubba's avatar
Gus Grubba committed
743
#ifdef NO_SERIAL_LINK
744 745 746 747 748 749
    QStringList foo;
    return foo;
#else
    return SerialConfiguration::supportedBaudRates();
#endif
}
750 751 752

bool LinkManager::endConfigurationEditing(LinkConfiguration* config, LinkConfiguration* editedConfig)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
753 754 755 756 757 758 759 760 761 762 763
    if (config && editedConfig) {
        _fixUnnamed(editedConfig);
        config->copyFrom(editedConfig);
        saveLinkConfigurationList();
        // Tell link about changes (if any)
        config->updateSettings();
        // Discard temporary duplicate
        delete editedConfig;
    } else {
        qWarning() << "Internal error";
    }
764 765 766 767 768
    return true;
}

bool LinkManager::endCreateConfiguration(LinkConfiguration* config)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
769 770 771 772 773 774 775
    if (config) {
        _fixUnnamed(config);
        addConfiguration(config);
        saveLinkConfigurationList();
    } else {
        qWarning() << "Internal error";
    }
776 777 778 779 780
    return true;
}

LinkConfiguration* LinkManager::createConfiguration(int type, const QString& name)
{
Gus Grubba's avatar
Gus Grubba committed
781
#ifndef NO_SERIAL_LINK
782
    if(static_cast<LinkConfiguration::LinkType>(type) == LinkConfiguration::TypeSerial)
783
        _updateSerialPorts();
dogmaphobic's avatar
dogmaphobic committed
784
#endif
785 786 787 788 789
    return LinkConfiguration::createSettings(type, name);
}

LinkConfiguration* LinkManager::startConfigurationEditing(LinkConfiguration* config)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
790
    if (config) {
Gus Grubba's avatar
Gus Grubba committed
791
#ifndef NO_SERIAL_LINK
DonLakeFlyer's avatar
DonLakeFlyer committed
792 793
        if(config->type() == LinkConfiguration::TypeSerial)
            _updateSerialPorts();
dogmaphobic's avatar
dogmaphobic committed
794
#endif
DonLakeFlyer's avatar
DonLakeFlyer committed
795 796 797
        return LinkConfiguration::duplicateSettings(config);
    } else {
        qWarning() << "Internal error";
798
        return nullptr;
DonLakeFlyer's avatar
DonLakeFlyer committed
799
    }
800 801 802 803
}

void LinkManager::_fixUnnamed(LinkConfiguration* config)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
804 805 806 807
    if (config) {
        //-- Check for "Unnamed"
        if (config->name() == "Unnamed") {
            switch(config->type()) {
Gus Grubba's avatar
Gus Grubba committed
808
#ifndef NO_SERIAL_LINK
809 810
            case LinkConfiguration::TypeSerial: {
                QString tname = dynamic_cast<SerialConfiguration*>(config)->portName();
811
#ifdef Q_OS_WIN
812 813 814 815 816 817 818
                tname.replace("\\\\.\\", "");
#else
                tname.replace("/dev/cu.", "");
                tname.replace("/dev/", "");
#endif
                config->setName(QString("Serial Device on %1").arg(tname));
                break;
DonLakeFlyer's avatar
DonLakeFlyer committed
819
            }
820 821 822
#endif
            case LinkConfiguration::TypeUdp:
                config->setName(
823
                    QString("UDP Link on Port %1").arg(dynamic_cast<UDPConfiguration*>(config)->localPort()));
824 825
                break;
            case LinkConfiguration::TypeTcp: {
DonLakeFlyer's avatar
DonLakeFlyer committed
826 827 828
                TCPConfiguration* tconfig = dynamic_cast<TCPConfiguration*>(config);
                if(tconfig) {
                    config->setName(
829
                        QString("TCP Link %1:%2").arg(tconfig->address().toString()).arg(static_cast<int>(tconfig->port())));
830
                }
DonLakeFlyer's avatar
DonLakeFlyer committed
831
            }
832
                break;
833 834 835 836 837 838 839 840 841
#if defined(QGC_GST_TAISYNC_ENABLED)
            case LinkConfiguration::TypeTaisync: {
                TaisyncConfiguration* tconfig = dynamic_cast<TaisyncConfiguration*>(config);
                if(tconfig) {
                    config->setName(QString("Taisync Link"));
                }
            }
                break;
#endif
dogmaphobic's avatar
dogmaphobic committed
842
#ifdef QGC_ENABLE_BLUETOOTH
dogmaphobic's avatar
dogmaphobic committed
843
            case LinkConfiguration::TypeBluetooth: {
DonLakeFlyer's avatar
DonLakeFlyer committed
844 845 846
                BluetoothConfiguration* tconfig = dynamic_cast<BluetoothConfiguration*>(config);
                if(tconfig) {
                    config->setName(QString("%1 (Bluetooth Device)").arg(tconfig->device().name));
dogmaphobic's avatar
dogmaphobic committed
847
                }
DonLakeFlyer's avatar
DonLakeFlyer committed
848
            }
dogmaphobic's avatar
dogmaphobic committed
849 850
                break;
#endif
dogmaphobic's avatar
dogmaphobic committed
851
#ifndef __mobile__
852
            case LinkConfiguration::TypeLogReplay: {
DonLakeFlyer's avatar
DonLakeFlyer committed
853 854 855
                LogReplayLinkConfiguration* tconfig = dynamic_cast<LogReplayLinkConfiguration*>(config);
                if(tconfig) {
                    config->setName(QString("Log Replay %1").arg(tconfig->logFilenameShort()));
856
                }
DonLakeFlyer's avatar
DonLakeFlyer committed
857
            }
858
                break;
dogmaphobic's avatar
dogmaphobic committed
859
#endif
860 861
#ifdef QT_DEBUG
            case LinkConfiguration::TypeMock:
862
                config->setName(QString("Mock Link"));
863 864 865 866
                break;
#endif
            case LinkConfiguration::TypeLast:
                break;
DonLakeFlyer's avatar
DonLakeFlyer committed
867
            }
868
        }
DonLakeFlyer's avatar
DonLakeFlyer committed
869 870
    } else {
        qWarning() << "Internal error";
871 872 873 874 875
    }
}

void LinkManager::removeConfiguration(LinkConfiguration* config)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
876 877 878 879 880
    if (config) {
        LinkInterface* iface = config->link();
        if(iface) {
            disconnectLink(iface);
        }
881

DonLakeFlyer's avatar
DonLakeFlyer committed
882 883 884 885 886
        _removeConfiguration(config);
        saveLinkConfigurationList();
    } else {
        qWarning() << "Internal error";
    }
887
}
888

889 890
bool LinkManager::isAutoconnectLink(LinkInterface* link)
{
891 892 893 894 895 896
    for (int i=0; i<_sharedAutoconnectConfigurations.count(); i++) {
        if (_sharedAutoconnectConfigurations[i].data() == link->getLinkConfiguration()) {
            return true;
        }
    }
    return false;
897
}
dogmaphobic's avatar
dogmaphobic committed
898 899 900 901 902

bool LinkManager::isBluetoothAvailable(void)
{
    return qgcApp()->isBluetoothAvailable();
}
Don Gagne's avatar
Don Gagne committed
903

Gus Grubba's avatar
Gus Grubba committed
904
#ifndef NO_SERIAL_LINK
Don Gagne's avatar
Don Gagne committed
905 906
void LinkManager::_activeLinkCheck(void)
{
907
    SerialLink* link = nullptr;
Don Gagne's avatar
Don Gagne committed
908 909
    bool found = false;
    if (_activeLinkCheckList.count() != 0) {
910
        link = _activeLinkCheckList.takeFirst();
911
        if (containsLink(link) && link->isConnected()) {
Don Gagne's avatar
Don Gagne committed
912 913 914 915 916 917 918 919 920
            // Make sure there is a vehicle on the link
            QmlObjectListModel* vehicles = _toolbox->multiVehicleManager()->vehicles();
            for (int i=0; i<vehicles->count(); i++) {
                Vehicle* vehicle = qobject_cast<Vehicle*>(vehicles->get(i));
                if (vehicle->containsLink(link)) {
                    found = true;
                    break;
                }
            }
921
        } else {
922
            link = nullptr;
Don Gagne's avatar
Don Gagne committed
923 924 925 926 927
        }
    }
    if (_activeLinkCheckList.count() == 0) {
        _activeLinkCheckTimer.stop();
    }
928 929 930
    if (!found && link) {
        // See if we can get an NSH prompt on this link
        bool foundNSHPrompt = false;
931
        link->writeBytesSafe("\r", 1);
932 933 934 935 936 937 938
        QSignalSpy spy(link, SIGNAL(bytesReceived(LinkInterface*, QByteArray)));
        if (spy.wait(100)) {
            QList<QVariant> arguments = spy.takeFirst();
            if (arguments[1].value<QByteArray>().contains("nsh>")) {
                foundNSHPrompt = true;
            }
        }
939 940 941 942
        qgcApp()->showMessage(
            foundNSHPrompt ?
                tr("Please check to make sure you have an SD Card inserted in your Vehicle and try again.") :
                tr("Your Vehicle is not responding. If this continues, shutdown %1, restart the Vehicle letting it boot completely, then start %1.").arg(qgcApp()->applicationName()));
Don Gagne's avatar
Don Gagne committed
943 944
    }
}
Don Gagne's avatar
Don Gagne committed
945
#endif
946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983

bool LinkManager::containsLink(LinkInterface* link)
{
    for (int i=0; i<_sharedLinks.count(); i++) {
        if (_sharedLinks[i].data() == link) {
            return true;
        }
    }
    return false;
}

SharedLinkConfigurationPointer LinkManager::addConfiguration(LinkConfiguration* config)
{
    _qmlConfigurations.append(config);
    _sharedConfigurations.append(SharedLinkConfigurationPointer(config));
    return _sharedConfigurations.last();
}

void LinkManager::_removeConfiguration(LinkConfiguration* config)
{
    _qmlConfigurations.removeOne(config);
    for (int i=0; i<_sharedConfigurations.count(); i++) {
        if (_sharedConfigurations[i].data() == config) {
            _sharedConfigurations.removeAt(i);
            return;
        }
    }
    qWarning() << "LinkManager::_removeConfiguration called with unknown config";
}

QList<LinkInterface*> LinkManager::links(void)
{
    QList<LinkInterface*> rawLinks;
    for (int i=0; i<_sharedLinks.count(); i++) {
        rawLinks.append(_sharedLinks[i].data());
    }
    return rawLinks;
}
984 985 986 987 988 989 990 991 992 993

void LinkManager::startAutoConnectedLinks(void)
{
    SharedLinkConfigurationPointer conf;
    for(int i = 0; i < _sharedConfigurations.count(); i++) {
        conf = _sharedConfigurations[i];
        if (conf->isAutoConnect())
            createConnectedLink(conf);
    }
}
994 995 996 997

int LinkManager::_reserveMavlinkChannel(void)
{
    // Find a mavlink channel to use for this link, Channel 0 is reserved for internal use.
998
    for (uint8_t mavlinkChannel = 1; mavlinkChannel < 32; mavlinkChannel++) {
999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014
        if (!(_mavlinkChannelsUsedBitMask & 1 << mavlinkChannel)) {
            mavlink_reset_channel_status(mavlinkChannel);
            // Start the channel on Mav 1 protocol
            mavlink_status_t* mavlinkStatus = mavlink_get_channel_status(mavlinkChannel);
            mavlinkStatus->flags |= MAVLINK_STATUS_FLAG_OUT_MAVLINK1;
            _mavlinkChannelsUsedBitMask |= 1 << mavlinkChannel;
            return mavlinkChannel;
        }
    }
    return 0;   // All channels reserved
}

void LinkManager::_freeMavlinkChannel(int channel)
{
    _mavlinkChannelsUsedBitMask &= ~(1 << channel);
}
1015

1016 1017
void LinkManager::_mavlinkMessageReceived(LinkInterface* link, mavlink_message_t message) {
    link->startMavlinkMessagesTimer(message.sysid);
1018
}