LinkManager.cc 35.7 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"
dogmaphobic's avatar
dogmaphobic committed
23
#ifdef QGC_ENABLE_BLUETOOTH
dogmaphobic's avatar
dogmaphobic committed
24 25
#include "BluetoothLink.h"
#endif
26

Don Gagne's avatar
Don Gagne committed
27 28 29 30
#ifndef __mobile__
    #include "GPSManager.h"
#endif

Don Gagne's avatar
Don Gagne committed
31
QGC_LOGGING_CATEGORY(LinkManagerLog, "LinkManagerLog")
Don Gagne's avatar
Don Gagne committed
32 33
QGC_LOGGING_CATEGORY(LinkManagerVerboseLog, "LinkManagerVerboseLog")

34 35 36 37 38 39 40
const char* LinkManager::_settingsGroup =            "LinkManager";
const char* LinkManager::_autoconnectUDPKey =        "AutoconnectUDP";
const char* LinkManager::_autoconnectPixhawkKey =    "AutoconnectPixhawk";
const char* LinkManager::_autoconnect3DRRadioKey =   "Autoconnect3DRRadio";
const char* LinkManager::_autoconnectPX4FlowKey =    "AutoconnectPX4Flow";
const char* LinkManager::_autoconnectRTKGPSKey =     "AutoconnectRTKGPS";
const char* LinkManager::_autoconnectLibrePilotKey = "AutoconnectLibrePilot";
Don Gagne's avatar
Don Gagne committed
41
const char* LinkManager::_defaultUPDLinkName =       "UDP Link (AutoConnect)";
42

43 44 45 46 47 48 49 50
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

51 52
LinkManager::LinkManager(QGCApplication* app)
    : QGCTool(app)
53 54 55
    , _configUpdateSuspended(false)
    , _configurationsLoaded(false)
    , _connectionsSuspended(false)
56
    , _mavlinkChannelsUsedBitMask(1)    // We never use channel 0 to avoid sequence numbering problems
57
    , _mavlinkProtocol(NULL)
Don Gagne's avatar
Don Gagne committed
58 59 60 61
    , _autoconnectUDP(true)
    , _autoconnectPixhawk(true)
    , _autoconnect3DRRadio(true)
    , _autoconnectPX4Flow(true)
Don Gagne's avatar
Don Gagne committed
62
    , _autoconnectRTKGPS(true)
63
    , _autoconnectLibrePilot(true)
pixhawk's avatar
pixhawk committed
64
{
Don Gagne's avatar
Don Gagne committed
65 66 67 68 69
    qmlRegisterUncreatableType<LinkManager>         ("QGroundControl", 1, 0, "LinkManager",         "Reference only");
    qmlRegisterUncreatableType<LinkConfiguration>   ("QGroundControl", 1, 0, "LinkConfiguration",   "Reference only");
    qmlRegisterUncreatableType<LinkInterface>       ("QGroundControl", 1, 0, "LinkInterface",       "Reference only");

    QSettings settings;
70

Don Gagne's avatar
Don Gagne committed
71
    settings.beginGroup(_settingsGroup);
72 73 74 75 76 77
    _autoconnectUDP =        settings.value(_autoconnectUDPKey, true).toBool();
    _autoconnectPixhawk =    settings.value(_autoconnectPixhawkKey, true).toBool();
    _autoconnect3DRRadio =   settings.value(_autoconnect3DRRadioKey, true).toBool();
    _autoconnectPX4Flow =    settings.value(_autoconnectPX4FlowKey, true).toBool();
    _autoconnectRTKGPS =     settings.value(_autoconnectRTKGPSKey, true).toBool();
    _autoconnectLibrePilot = settings.value(_autoconnectLibrePilotKey, true).toBool();
Don Gagne's avatar
Don Gagne committed
78

Gus Grubba's avatar
Gus Grubba committed
79
#ifndef NO_SERIAL_LINK
Don Gagne's avatar
Don Gagne committed
80 81 82
    _activeLinkCheckTimer.setInterval(_activeLinkCheckTimeoutMSecs);
    _activeLinkCheckTimer.setSingleShot(false);
    connect(&_activeLinkCheckTimer, &QTimer::timeout, this, &LinkManager::_activeLinkCheck);
Don Gagne's avatar
Don Gagne committed
83
#endif
pixhawk's avatar
pixhawk committed
84 85 86 87
}

LinkManager::~LinkManager()
{
88

pixhawk's avatar
pixhawk committed
89 90
}

91 92 93 94 95 96
void LinkManager::setToolbox(QGCToolbox *toolbox)
{
   QGCTool::setToolbox(toolbox);

   _mavlinkProtocol = _toolbox->mavlinkProtocol();

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

100 101
}

102
LinkInterface* LinkManager::createConnectedLink(SharedLinkConfigurationPointer& config)
103
{
104 105 106 107 108
    if (!config) {
        qWarning() << "LinkManager::createConnectedLink called with NULL config";
        return NULL;
    }

109 110
    LinkInterface* pLink = NULL;
    switch(config->type()) {
Gus Grubba's avatar
Gus Grubba committed
111
#ifndef NO_SERIAL_LINK
112
        case LinkConfiguration::TypeSerial:
Don Gagne's avatar
Don Gagne committed
113
        {
114
            SerialConfiguration* serialConfig = dynamic_cast<SerialConfiguration*>(config.data());
Don Gagne's avatar
Don Gagne committed
115
            if (serialConfig) {
116
                pLink = new SerialLink(config);
Don Gagne's avatar
Don Gagne committed
117
                if (serialConfig->usbDirect()) {
118
                    _activeLinkCheckList.append((SerialLink*)pLink);
Don Gagne's avatar
Don Gagne committed
119 120 121 122 123 124 125
                    if (!_activeLinkCheckTimer.isActive()) {
                        _activeLinkCheckTimer.start();
                    }
                }
            }
        }
        break;
dogmaphobic's avatar
dogmaphobic committed
126
#endif
127
        case LinkConfiguration::TypeUdp:
128
            pLink = new UDPLink(config);
129
            break;
130
        case LinkConfiguration::TypeTcp:
131
            pLink = new TCPLink(config);
132
            break;
dogmaphobic's avatar
dogmaphobic committed
133
#ifdef QGC_ENABLE_BLUETOOTH
dogmaphobic's avatar
dogmaphobic committed
134
        case LinkConfiguration::TypeBluetooth:
135
            pLink = new BluetoothLink(config);
dogmaphobic's avatar
dogmaphobic committed
136 137
            break;
#endif
dogmaphobic's avatar
dogmaphobic committed
138
#ifndef __mobile__
Don Gagne's avatar
Don Gagne committed
139
        case LinkConfiguration::TypeLogReplay:
140
            pLink = new LogReplayLink(config);
Don Gagne's avatar
Don Gagne committed
141
            break;
dogmaphobic's avatar
dogmaphobic committed
142
#endif
143
#ifdef QT_DEBUG
144
        case LinkConfiguration::TypeMock:
145
            pLink = new MockLink(config);
146
            break;
147
#endif
148 149 150
        case LinkConfiguration::TypeLast:
        default:
            break;
151
    }
152 153

    if (pLink) {
154 155
        _addLink(pLink);
        connectLink(pLink);
156
    }
157

158 159 160
    return pLink;
}

Don Gagne's avatar
Don Gagne committed
161
LinkInterface* LinkManager::createConnectedLink(const QString& name)
162 163
{
    Q_ASSERT(name.isEmpty() == false);
164 165 166
    for(int i = 0; i < _sharedConfigurations.count(); i++) {
        SharedLinkConfigurationPointer& conf = _sharedConfigurations[i];
        if (conf->name() == name)
Don Gagne's avatar
Don Gagne committed
167
            return createConnectedLink(conf);
168 169 170 171
    }
    return NULL;
}

172
void LinkManager::_addLink(LinkInterface* link)
pixhawk's avatar
pixhawk committed
173
{
Don Gagne's avatar
Don Gagne committed
174 175 176 177
    if (thread() != QThread::currentThread()) {
        qWarning() << "_deleteLink called from incorrect thread";
        return;
    }
178

Don Gagne's avatar
Don Gagne committed
179 180 181
    if (!link) {
        return;
    }
182

183
    if (!containsLink(link)) {
184 185
        bool channelSet = false;

186 187
        // Find a mavlink channel to use for this link, Channel 0 is reserved for internal use.
        for (int i=1; i<32; i++) {
188
            if (!(_mavlinkChannelsUsedBitMask & 1 << i)) {
189
                mavlink_reset_channel_status(i);
190
                link->_setMavlinkChannel(i);
Don Gagne's avatar
Don Gagne committed
191
                // Start the channel on Mav 1 protocol
192 193 194
                mavlink_status_t* mavlinkStatus = mavlink_get_channel_status(i);
                mavlinkStatus->flags = mavlink_get_channel_status(i)->flags | MAVLINK_STATUS_FLAG_OUT_MAVLINK1;
                qDebug() << "LinkManager mavlinkStatus" << mavlinkStatus << i << mavlinkStatus->flags;
Don Gagne's avatar
Don Gagne committed
195
                _mavlinkChannelsUsedBitMask |= 1 << i;
196
                channelSet = true;
197 198 199
                break;
            }
        }
200

201 202 203 204
        if (!channelSet) {
            qWarning() << "Ran out of mavlink channels";
        }

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

214 215 216 217 218 219
    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);
220
}
pixhawk's avatar
pixhawk committed
221

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

bool LinkManager::connectLink(LinkInterface* link)
{
232
    Q_ASSERT(link);
233

234 235 236 237
    if (_connectionsSuspendedMsg()) {
        return false;
    }

238
    return link->_connect();
pixhawk's avatar
pixhawk committed
239 240
}

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

Don Gagne's avatar
Don Gagne committed
247
    link->_disconnect();
248

Don Gagne's avatar
Don Gagne committed
249
    LinkConfiguration* config = link->getLinkConfiguration();
250 251 252 253 254
    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;
255
        }
256
    }
257

Don Gagne's avatar
Don Gagne committed
258
    _deleteLink(link);
pixhawk's avatar
pixhawk committed
259 260
}

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

    if (!link) {
        return;
    }
271

272
    // Free up the mavlink channel associated with this link
273
    _mavlinkChannelsUsedBitMask &= ~(1 << link->mavlinkChannel());
274

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

Don Gagne's avatar
Don Gagne committed
282
    // Emit removal of link
283
    emit linkDeleted(link);
pixhawk's avatar
pixhawk committed
284 285
}

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

    qWarning() << "LinkManager::sharedLinkInterfaceForLink returning NULL";
    return SharedLinkInterfacePointer(NULL);
}

298 299 300 301 302
/// @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) {
303
        qgcApp()->showMessage(QString("Connect not allowed: %1").arg(_connectionsSuspendedReason));
304 305 306 307 308 309 310 311 312 313 314 315
        return true;
    } else {
        return false;
    }
}

void LinkManager::setConnectionsSuspended(QString reason)
{
    _connectionsSuspended = true;
    _connectionsSuspendedReason = reason;
    Q_ASSERT(!reason.isEmpty());
}
316

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

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

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

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

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

void LinkManager::loadLinkConfigurationList()
{
366
    bool linksChanged = false;
367 368 369 370 371 372 373 374 375 376
    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")) {
                int type = settings.value(root + "/type").toInt();
377
                if((LinkConfiguration::LinkType)type < LinkConfiguration::TypeLast) {
378 379 380 381
                    if(settings.contains(root + "/name")) {
                        QString name = settings.value(root + "/name").toString();
                        if(!name.isEmpty()) {
                            LinkConfiguration* pLink = NULL;
382 383
                            bool autoConnect = settings.value(root + "/auto").toBool();
                            switch((LinkConfiguration::LinkType)type) {
Gus Grubba's avatar
Gus Grubba committed
384
#ifndef NO_SERIAL_LINK
385 386 387
                                case LinkConfiguration::TypeSerial:
                                    pLink = (LinkConfiguration*)new SerialConfiguration(name);
                                    break;
dogmaphobic's avatar
dogmaphobic committed
388
#endif
389 390 391
                                case LinkConfiguration::TypeUdp:
                                    pLink = (LinkConfiguration*)new UDPConfiguration(name);
                                    break;
392 393 394
                                case LinkConfiguration::TypeTcp:
                                    pLink = (LinkConfiguration*)new TCPConfiguration(name);
                                    break;
dogmaphobic's avatar
dogmaphobic committed
395
#ifdef QGC_ENABLE_BLUETOOTH
dogmaphobic's avatar
dogmaphobic committed
396 397 398 399
                                case LinkConfiguration::TypeBluetooth:
                                    pLink = (LinkConfiguration*)new BluetoothConfiguration(name);
                                    break;
#endif
dogmaphobic's avatar
dogmaphobic committed
400
#ifndef __mobile__
Don Gagne's avatar
Don Gagne committed
401 402 403
                                case LinkConfiguration::TypeLogReplay:
                                    pLink = (LinkConfiguration*)new LogReplayLinkConfiguration(name);
                                    break;
dogmaphobic's avatar
dogmaphobic committed
404
#endif
405
#ifdef QT_DEBUG
406 407 408
                                case LinkConfiguration::TypeMock:
                                    pLink = (LinkConfiguration*)new MockConfiguration(name);
                                    break;
409
#endif
410 411 412
                                default:
                                case LinkConfiguration::TypeLast:
                                    break;
413 414
                            }
                            if(pLink) {
415 416
                                //-- Have the instance load its own values
                                pLink->setAutoConnect(autoConnect);
417
                                pLink->loadSettings(settings, root);
418
                                addConfiguration(pLink);
419
                                linksChanged = true;
420 421
                            }
                        } else {
422
                            qWarning() << "Link Configuration" << root << "has an empty name." ;
423 424
                        }
                    } else {
425
                        qWarning() << "Link Configuration" << root << "has no name." ;
426 427
                    }
                } else {
428
                    qWarning() << "Link Configuration" << root << "an invalid type: " << type;
429 430
                }
            } else {
431
                qWarning() << "Link Configuration" << root << "has no type." ;
432 433 434
            }
        }
    }
435 436

    if(linksChanged) {
437
        emit linkConfigurationsChanged();
438 439
    }
    // Enable automatic Serial PX4/3DR Radio hunting
440 441 442
    _configurationsLoaded = true;
}

Gus Grubba's avatar
Gus Grubba committed
443
#ifndef NO_SERIAL_LINK
Don Gagne's avatar
Don Gagne committed
444
SerialConfiguration* LinkManager::_autoconnectConfigurationsContainsPort(const QString& portName)
445 446
{
    QString searchPort = portName.trimmed();
Don Gagne's avatar
Don Gagne committed
447

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

451 452 453
        if (serialConfig) {
            if (serialConfig->portName() == searchPort) {
                return serialConfig;
454
            }
Don Gagne's avatar
Don Gagne committed
455 456
        } else {
            qWarning() << "Internal error";
457 458 459 460
        }
    }
    return NULL;
}
dogmaphobic's avatar
dogmaphobic committed
461
#endif
462

Don Gagne's avatar
Don Gagne committed
463
void LinkManager::_updateAutoConnectLinks(void)
464
{
Don Gagne's avatar
Don Gagne committed
465
    if (_connectionsSuspended || qgcApp()->runningUnitTests()) {
466 467
        return;
    }
Don Gagne's avatar
Don Gagne committed
468

Don Gagne's avatar
Don Gagne committed
469 470
    // Re-add UDP if we need to
    bool foundUDP = false;
471 472
    for (int i=0; i<_sharedLinks.count(); i++) {
        LinkConfiguration* linkConfig = _sharedLinks[i]->getLinkConfiguration();
Don Gagne's avatar
Don Gagne committed
473 474 475 476 477
        if (linkConfig->type() == LinkConfiguration::TypeUdp && linkConfig->name() == _defaultUPDLinkName) {
            foundUDP = true;
            break;
        }
    }
Don Gagne's avatar
Don Gagne committed
478
    if (!foundUDP && _autoconnectUDP) {
Don Gagne's avatar
Don Gagne committed
479
        qCDebug(LinkManagerLog) << "New auto-connect UDP port added";
Don Gagne's avatar
Don Gagne committed
480 481
        UDPConfiguration* udpConfig = new UDPConfiguration(_defaultUPDLinkName);
        udpConfig->setLocalPort(QGC_UDP_LOCAL_PORT);
482 483 484
        udpConfig->setDynamic(true);        
        SharedLinkConfigurationPointer config = addConfiguration(udpConfig);
        createConnectedLink(config);
485
        emit linkConfigurationsChanged();
Don Gagne's avatar
Don Gagne committed
486 487
    }

Gus Grubba's avatar
Gus Grubba committed
488
#ifndef NO_SERIAL_LINK
dogmaphobic's avatar
dogmaphobic committed
489
    QStringList currentPorts;
490 491 492 493 494 495 496 497 498
    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.
    if (!_autoconnectConfigurations.count()) {
        portList = QGCSerialPortInfo::availablePorts();
    }
499 500
#else
    portList = QGCSerialPortInfo::availablePorts();
501
#endif
Don Gagne's avatar
Don Gagne committed
502

503
    // Iterate Comm Ports
Don Gagne's avatar
Don Gagne committed
504
    foreach (QGCSerialPortInfo portInfo, portList) {
Don Gagne's avatar
Don Gagne committed
505 506 507 508 509 510 511 512 513
        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
514 515
        // Save port name
        currentPorts << portInfo.systemLocation();
Don Gagne's avatar
Don Gagne committed
516 517 518 519 520 521

        QGCSerialPortInfo::BoardType_t boardType = portInfo.boardType();

        if (boardType != QGCSerialPortInfo::BoardTypeUnknown) {
            if (portInfo.isBootloader()) {
                // Don't connect to bootloader
522
                qCDebug(LinkManagerLog) << "Waiting for bootloader to finish" << portInfo.systemLocation();
Don Gagne's avatar
Don Gagne committed
523 524
                continue;
            }
525

526 527 528 529 530 531 532
            if (_autoconnectConfigurationsContainsPort(portInfo.systemLocation())) {
                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();
533 534
                _autoconnectWaitList[portInfo.systemLocation()] = 1;
            } else if (++_autoconnectWaitList[portInfo.systemLocation()] * _autoconnectUpdateTimerMSecs > _autoconnectConnectDelayMSecs) {
Don Gagne's avatar
Don Gagne committed
535 536
                SerialConfiguration* pSerialConfig = NULL;

537
                _autoconnectWaitList.remove(portInfo.systemLocation());
538

Don Gagne's avatar
Don Gagne committed
539 540 541
                switch (boardType) {
                case QGCSerialPortInfo::BoardTypePX4FMUV1:
                case QGCSerialPortInfo::BoardTypePX4FMUV2:
542
                case QGCSerialPortInfo::BoardTypePX4FMUV4:
Don Gagne's avatar
Don Gagne committed
543
                    if (_autoconnectPixhawk) {
Don Gagne's avatar
Don Gagne committed
544
                        pSerialConfig = new SerialConfiguration(tr("Pixhawk on %1 (AutoConnect)").arg(portInfo.portName().trimmed()));
545
                        pSerialConfig->setUsbDirect(true);
Don Gagne's avatar
Don Gagne committed
546
                    }
Don Gagne's avatar
Don Gagne committed
547 548
                    break;
                case QGCSerialPortInfo::BoardTypeAeroCore:
Don Gagne's avatar
Don Gagne committed
549
                    if (_autoconnectPixhawk) {
Don Gagne's avatar
Don Gagne committed
550
                        pSerialConfig = new SerialConfiguration(tr("AeroCore on %1 (AutoConnect)").arg(portInfo.portName().trimmed()));
551
                        pSerialConfig->setUsbDirect(true);
Don Gagne's avatar
Don Gagne committed
552
                    }
Don Gagne's avatar
Don Gagne committed
553
                    break;
Henry Zhang's avatar
Henry Zhang committed
554 555
                case QGCSerialPortInfo::BoardTypeMINDPXFMUV2:
                    if (_autoconnectPixhawk) {
Don Gagne's avatar
Don Gagne committed
556
                        pSerialConfig = new SerialConfiguration(tr("MindPX on %1 (AutoConnect)").arg(portInfo.portName().trimmed()));
Henry Zhang's avatar
Henry Zhang committed
557 558 559
                        pSerialConfig->setUsbDirect(true);
                    }
                    break;
560 561
                case QGCSerialPortInfo::BoardTypeTAPV1:
                    if (_autoconnectPixhawk) {
Don Gagne's avatar
Don Gagne committed
562
                        pSerialConfig = new SerialConfiguration(tr("TAP on %1 (AutoConnect)").arg(portInfo.portName().trimmed()));
563 564 565 566 567
                        pSerialConfig->setUsbDirect(true);
                    }
                    break;
                case QGCSerialPortInfo::BoardTypeASCV1:
                    if (_autoconnectPixhawk) {
Don Gagne's avatar
Don Gagne committed
568
                        pSerialConfig = new SerialConfiguration(tr("ASC on %1 (AutoConnect)").arg(portInfo.portName().trimmed()));
569 570 571
                        pSerialConfig->setUsbDirect(true);
                    }
                    break;
Don Gagne's avatar
Don Gagne committed
572
                case QGCSerialPortInfo::BoardTypePX4Flow:
Don Gagne's avatar
Don Gagne committed
573
                    if (_autoconnectPX4Flow) {
Don Gagne's avatar
Don Gagne committed
574
                        pSerialConfig = new SerialConfiguration(tr("PX4Flow on %1 (AutoConnect)").arg(portInfo.portName().trimmed()));
Don Gagne's avatar
Don Gagne committed
575
                    }
Don Gagne's avatar
Don Gagne committed
576
                    break;
Don Gagne's avatar
Don Gagne committed
577
                case QGCSerialPortInfo::BoardTypeSikRadio:
Don Gagne's avatar
Don Gagne committed
578
                    if (_autoconnect3DRRadio) {
Don Gagne's avatar
Don Gagne committed
579
                        pSerialConfig = new SerialConfiguration(tr("SiK Radio on %1 (AutoConnect)").arg(portInfo.portName().trimmed()));
Don Gagne's avatar
Don Gagne committed
580 581
                    }
                    break;
582 583
                case QGCSerialPortInfo::BoardTypeLibrePilot:
                    if (_autoconnectLibrePilot) {
Don Gagne's avatar
Don Gagne committed
584
                        pSerialConfig = new SerialConfiguration(tr("LibrePilot on %1 (AutoConnect)").arg(portInfo.portName().trimmed()));
585 586
                    }
                    break;
Don Gagne's avatar
Don Gagne committed
587 588 589 590 591 592 593 594
#ifndef __mobile__
                case QGCSerialPortInfo::BoardTypeRTKGPS:
                    if (_autoconnectRTKGPS && !_toolbox->gpsManager()->connected()) {
                        qCDebug(LinkManagerLog) << "RTK GPS auto-connected";
                        _toolbox->gpsManager()->connectGPS(portInfo.systemLocation());
                    }
                    break;
#endif
Don Gagne's avatar
Don Gagne committed
595 596
                default:
                    qWarning() << "Internal error";
Don Gagne's avatar
Don Gagne committed
597
                    continue;
dogmaphobic's avatar
dogmaphobic committed
598
                }
Don Gagne's avatar
Don Gagne committed
599

Don Gagne's avatar
Don Gagne committed
600 601
                if (pSerialConfig) {
                    qCDebug(LinkManagerLog) << "New auto-connect port added: " << pSerialConfig->name() << portInfo.systemLocation();
Don Gagne's avatar
Don Gagne committed
602
                    pSerialConfig->setBaud(boardType == QGCSerialPortInfo::BoardTypeSikRadio ? 57600 : 115200);
Don Gagne's avatar
Don Gagne committed
603 604
                    pSerialConfig->setDynamic(true);
                    pSerialConfig->setPortName(portInfo.systemLocation());
605 606
                    _sharedAutoconnectConfigurations.append(SharedLinkConfigurationPointer(pSerialConfig));
                    createConnectedLink(_sharedAutoconnectConfigurations.last());
Don Gagne's avatar
Don Gagne committed
607
                }
dogmaphobic's avatar
dogmaphobic committed
608 609 610
            }
        }
    }
Don Gagne's avatar
Don Gagne committed
611

612 613 614 615 616 617 618
#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
    // bug after we connect the first serial port we stop probing for additional ports. The means we must rely on
    // 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
619 620
    // Now we go through the current configuration list and make sure any dynamic config has gone away
    QList<LinkConfiguration*>  _confToDelete;
621 622 623 624 625 626 627
    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()) {
628 629 630 631 632 633
                            // 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
634
                }
635
                _confToDelete.append(serialConfig);
dogmaphobic's avatar
dogmaphobic committed
636
            }
Don Gagne's avatar
Don Gagne committed
637 638
        } else {
            qWarning() << "Internal error";
dogmaphobic's avatar
dogmaphobic committed
639 640
        }
    }
Don Gagne's avatar
Don Gagne committed
641

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

Don Gagne's avatar
Don Gagne committed
660 661 662
void LinkManager::shutdown(void)
{
    setConnectionsSuspended("Shutdown");
Don Gagne's avatar
Don Gagne committed
663
    disconnectAll();
Don Gagne's avatar
Don Gagne committed
664 665
}

Don Gagne's avatar
Don Gagne committed
666
bool LinkManager::_setAutoconnectWorker(bool& currentAutoconnect, bool newAutoconnect, const char* autoconnectKey)
Don Gagne's avatar
Don Gagne committed
667
{
Don Gagne's avatar
Don Gagne committed
668
    if (currentAutoconnect != newAutoconnect) {
Don Gagne's avatar
Don Gagne committed
669 670 671
        QSettings settings;

        settings.beginGroup(_settingsGroup);
Don Gagne's avatar
Don Gagne committed
672 673 674 675 676 677 678
        settings.setValue(autoconnectKey, newAutoconnect);
        currentAutoconnect = newAutoconnect;
        return true;
    }

    return false;
}
Don Gagne's avatar
Don Gagne committed
679

Don Gagne's avatar
Don Gagne committed
680 681 682
void LinkManager::setAutoconnectUDP(bool autoconnect)
{
    if (_setAutoconnectWorker(_autoconnectUDP, autoconnect, _autoconnectUDPKey)) {
Don Gagne's avatar
Don Gagne committed
683
        emit autoconnectUDPChanged(autoconnect);
684
    }
Don Gagne's avatar
Don Gagne committed
685 686 687 688
}

void LinkManager::setAutoconnectPixhawk(bool autoconnect)
{
Don Gagne's avatar
Don Gagne committed
689
    if (_setAutoconnectWorker(_autoconnectPixhawk, autoconnect, _autoconnectPixhawkKey)) {
Don Gagne's avatar
Don Gagne committed
690 691 692 693 694 695
        emit autoconnectPixhawkChanged(autoconnect);
    }
}

void LinkManager::setAutoconnect3DRRadio(bool autoconnect)
{
Don Gagne's avatar
Don Gagne committed
696
    if (_setAutoconnectWorker(_autoconnect3DRRadio, autoconnect, _autoconnect3DRRadioKey)) {
Don Gagne's avatar
Don Gagne committed
697 698 699 700 701 702
        emit autoconnect3DRRadioChanged(autoconnect);
    }
}

void LinkManager::setAutoconnectPX4Flow(bool autoconnect)
{
Don Gagne's avatar
Don Gagne committed
703
    if (_setAutoconnectWorker(_autoconnectPX4Flow, autoconnect, _autoconnectPX4FlowKey)) {
Don Gagne's avatar
Don Gagne committed
704 705
        emit autoconnectPX4FlowChanged(autoconnect);
    }
Don Gagne's avatar
Don Gagne committed
706
}
Don Gagne's avatar
Don Gagne committed
707

Don Gagne's avatar
Don Gagne committed
708 709 710 711 712
void LinkManager::setAutoconnectRTKGPS(bool autoconnect)
{
    if (_setAutoconnectWorker(_autoconnectRTKGPS, autoconnect, _autoconnectRTKGPSKey)) {
        emit autoconnectRTKGPSChanged(autoconnect);
    }
713
}
714

715 716 717 718 719 720 721
void LinkManager::setAutoconnectLibrePilot(bool autoconnect)
{
    if (_setAutoconnectWorker(_autoconnectLibrePilot, autoconnect, _autoconnectLibrePilotKey)) {
        emit autoconnectLibrePilotChanged(autoconnect);
    }
}

722 723 724 725 726 727
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
728
#ifndef NO_SERIAL_LINK
729 730 731 732
        list += "Serial";
#endif
        list += "UDP";
        list += "TCP";
dogmaphobic's avatar
dogmaphobic committed
733
#ifdef QGC_ENABLE_BLUETOOTH
dogmaphobic's avatar
dogmaphobic committed
734 735
        list += "Bluetooth";
#endif
dogmaphobic's avatar
dogmaphobic committed
736
#ifdef QT_DEBUG
737
        list += "Mock Link";
dogmaphobic's avatar
dogmaphobic committed
738 739
#endif
#ifndef __mobile__
740
        list += "Log Replay";
dogmaphobic's avatar
dogmaphobic committed
741
#endif
dogmaphobic's avatar
dogmaphobic committed
742
        Q_ASSERT(list.size() == (int)LinkConfiguration::TypeLast);
743 744 745 746
    }
    return list;
}

747
void LinkManager::_updateSerialPorts()
748
{
749 750
    _commPortList.clear();
    _commPortDisplayList.clear();
Gus Grubba's avatar
Gus Grubba committed
751
#ifndef NO_SERIAL_LINK
752 753
    QList<QSerialPortInfo> portList = QSerialPortInfo::availablePorts();
    foreach (const QSerialPortInfo &info, portList)
754
    {
755 756 757
        QString port = info.systemLocation().trimmed();
        _commPortList += port;
        _commPortDisplayList += SerialConfiguration::cleanPortDisplayname(port);
758 759
    }
#endif
760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776
}

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

QStringList LinkManager::serialPorts(void)
{
    if(!_commPortList.size())
    {
        _updateSerialPorts();
    }
777 778 779 780 781
    return _commPortList;
}

QStringList LinkManager::serialBaudRates(void)
{
Gus Grubba's avatar
Gus Grubba committed
782
#ifdef NO_SERIAL_LINK
783 784 785 786 787 788
    QStringList foo;
    return foo;
#else
    return SerialConfiguration::supportedBaudRates();
#endif
}
789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807

bool LinkManager::endConfigurationEditing(LinkConfiguration* config, LinkConfiguration* editedConfig)
{
    Q_ASSERT(config != NULL);
    Q_ASSERT(editedConfig != NULL);
    _fixUnnamed(editedConfig);
    config->copyFrom(editedConfig);
    saveLinkConfigurationList();
    // Tell link about changes (if any)
    config->updateSettings();
    // Discard temporary duplicate
    delete editedConfig;
    return true;
}

bool LinkManager::endCreateConfiguration(LinkConfiguration* config)
{
    Q_ASSERT(config != NULL);
    _fixUnnamed(config);
808
    addConfiguration(config);
809 810 811 812 813 814
    saveLinkConfigurationList();
    return true;
}

LinkConfiguration* LinkManager::createConfiguration(int type, const QString& name)
{
Gus Grubba's avatar
Gus Grubba committed
815
#ifndef NO_SERIAL_LINK
816 817
    if((LinkConfiguration::LinkType)type == LinkConfiguration::TypeSerial)
        _updateSerialPorts();
dogmaphobic's avatar
dogmaphobic committed
818
#endif
819 820 821 822 823 824
    return LinkConfiguration::createSettings(type, name);
}

LinkConfiguration* LinkManager::startConfigurationEditing(LinkConfiguration* config)
{
    Q_ASSERT(config != NULL);
Gus Grubba's avatar
Gus Grubba committed
825
#ifndef NO_SERIAL_LINK
826 827
    if(config->type() == LinkConfiguration::TypeSerial)
        _updateSerialPorts();
dogmaphobic's avatar
dogmaphobic committed
828
#endif
829 830 831 832 833 834 835 836 837 838
    return LinkConfiguration::duplicateSettings(config);
}


void LinkManager::_fixUnnamed(LinkConfiguration* config)
{
    Q_ASSERT(config != NULL);
    //-- Check for "Unnamed"
    if (config->name() == "Unnamed") {
        switch(config->type()) {
Gus Grubba's avatar
Gus Grubba committed
839
#ifndef NO_SERIAL_LINK
840 841
            case LinkConfiguration::TypeSerial: {
                QString tname = dynamic_cast<SerialConfiguration*>(config)->portName();
842
#ifdef Q_OS_WIN
843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863
                tname.replace("\\\\.\\", "");
#else
                tname.replace("/dev/cu.", "");
                tname.replace("/dev/", "");
#endif
                config->setName(QString("Serial Device on %1").arg(tname));
                break;
                }
#endif
            case LinkConfiguration::TypeUdp:
                config->setName(
                    QString("UDP Link on Port %1").arg(dynamic_cast<UDPConfiguration*>(config)->localPort()));
                break;
            case LinkConfiguration::TypeTcp: {
                    TCPConfiguration* tconfig = dynamic_cast<TCPConfiguration*>(config);
                    if(tconfig) {
                        config->setName(
                            QString("TCP Link %1:%2").arg(tconfig->address().toString()).arg((int)tconfig->port()));
                    }
                }
                break;
dogmaphobic's avatar
dogmaphobic committed
864
#ifdef QGC_ENABLE_BLUETOOTH
dogmaphobic's avatar
dogmaphobic committed
865 866 867
            case LinkConfiguration::TypeBluetooth: {
                    BluetoothConfiguration* tconfig = dynamic_cast<BluetoothConfiguration*>(config);
                    if(tconfig) {
868
                        config->setName(QString("%1 (Bluetooth Device)").arg(tconfig->device().name));
dogmaphobic's avatar
dogmaphobic committed
869 870 871 872
                    }
                }
                break;
#endif
dogmaphobic's avatar
dogmaphobic committed
873
#ifndef __mobile__
874
            case LinkConfiguration::TypeLogReplay: {
dogmaphobic's avatar
dogmaphobic committed
875 876 877 878
                    LogReplayLinkConfiguration* tconfig = dynamic_cast<LogReplayLinkConfiguration*>(config);
                    if(tconfig) {
                        config->setName(QString("Log Replay %1").arg(tconfig->logFilenameShort()));
                    }
879 880
                }
                break;
dogmaphobic's avatar
dogmaphobic committed
881
#endif
882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901
#ifdef QT_DEBUG
            case LinkConfiguration::TypeMock:
                config->setName(
                    QString("Mock Link"));
                break;
#endif
            case LinkConfiguration::TypeLast:
            default:
                break;
        }
    }
}

void LinkManager::removeConfiguration(LinkConfiguration* config)
{
    Q_ASSERT(config != NULL);
    LinkInterface* iface = config->link();
    if(iface) {
        disconnectLink(iface);
    }
902 903

    _removeConfiguration(config);
904 905
    saveLinkConfigurationList();
}
906

907 908
bool LinkManager::isAutoconnectLink(LinkInterface* link)
{
909 910 911 912 913 914
    for (int i=0; i<_sharedAutoconnectConfigurations.count(); i++) {
        if (_sharedAutoconnectConfigurations[i].data() == link->getLinkConfiguration()) {
            return true;
        }
    }
    return false;
915
}
dogmaphobic's avatar
dogmaphobic committed
916 917 918 919 920

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

Gus Grubba's avatar
Gus Grubba committed
922
#ifndef NO_SERIAL_LINK
Don Gagne's avatar
Don Gagne committed
923 924
void LinkManager::_activeLinkCheck(void)
{
925
    SerialLink* link = NULL;
Don Gagne's avatar
Don Gagne committed
926 927 928
    bool found = false;

    if (_activeLinkCheckList.count() != 0) {
929
        link = _activeLinkCheckList.takeFirst();
930
        if (containsLink(link) && link->isConnected()) {
Don Gagne's avatar
Don Gagne committed
931 932 933 934 935 936 937 938 939
            // 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;
                }
            }
940 941
        } else {
            link = NULL;
Don Gagne's avatar
Don Gagne committed
942 943 944 945 946 947 948
        }
    }

    if (_activeLinkCheckList.count() == 0) {
        _activeLinkCheckTimer.stop();
    }

949 950 951
    if (!found && link) {
        // See if we can get an NSH prompt on this link
        bool foundNSHPrompt = false;
952
        link->writeBytesSafe("\r", 1);
953 954 955 956 957 958 959 960 961 962 963
        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;
            }
        }

        qgcApp()->showMessage(foundNSHPrompt ?
                                  QStringLiteral("Please check to make sure you have an SD Card inserted in your Vehicle and try again.") :
                                  QStringLiteral("Your Vehicle is not responding. If this continues shutdown QGroundControl, restart the Vehicle letting it boot completely, then start QGroundControl."));
Don Gagne's avatar
Don Gagne committed
964 965
    }
}
Don Gagne's avatar
Don Gagne committed
966
#endif
967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008

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;
}