LinkManager.cc 32.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"
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

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

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

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

37 38 39 40 41 42 43 44
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

45 46
LinkManager::LinkManager(QGCApplication* app, QGCToolbox* toolbox)
    : QGCTool(app, toolbox)
47 48 49
    , _configUpdateSuspended(false)
    , _configurationsLoaded(false)
    , _connectionsSuspended(false)
50
    , _mavlinkChannelsUsedBitMask(1)    // We never use channel 0 to avoid sequence numbering problems
51
    , _autoConnectSettings(NULL)
52
    , _mavlinkProtocol(NULL)
pixhawk's avatar
pixhawk committed
53
{
Don Gagne's avatar
Don Gagne committed
54 55 56 57
    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
58
#ifndef NO_SERIAL_LINK
Don Gagne's avatar
Don Gagne committed
59 60 61
    _activeLinkCheckTimer.setInterval(_activeLinkCheckTimeoutMSecs);
    _activeLinkCheckTimer.setSingleShot(false);
    connect(&_activeLinkCheckTimer, &QTimer::timeout, this, &LinkManager::_activeLinkCheck);
Don Gagne's avatar
Don Gagne committed
62
#endif
pixhawk's avatar
pixhawk committed
63 64 65 66
}

LinkManager::~LinkManager()
{
67

pixhawk's avatar
pixhawk committed
68 69
}

70 71
void LinkManager::setToolbox(QGCToolbox *toolbox)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
72
    QGCTool::setToolbox(toolbox);
73

DonLakeFlyer's avatar
DonLakeFlyer committed
74 75
    _autoConnectSettings = toolbox->settingsManager()->autoConnectSettings();
    _mavlinkProtocol = _toolbox->mavlinkProtocol();
76

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

80 81
}

Don Gagne's avatar
Don Gagne committed
82 83 84 85 86 87 88 89 90 91
// 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);
    }
}

92
LinkInterface* LinkManager::createConnectedLink(SharedLinkConfigurationPointer& config)
93
{
94 95 96 97 98
    if (!config) {
        qWarning() << "LinkManager::createConnectedLink called with NULL config";
        return NULL;
    }

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

    if (pLink) {
144 145
        _addLink(pLink);
        connectLink(pLink);
146
    }
147

148 149 150
    return pLink;
}

Don Gagne's avatar
Don Gagne committed
151
LinkInterface* LinkManager::createConnectedLink(const QString& name)
152
{
DonLakeFlyer's avatar
DonLakeFlyer committed
153 154 155 156 157 158 159 160 161
    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);
            }
        }
162 163 164 165
    }
    return NULL;
}

166
void LinkManager::_addLink(LinkInterface* link)
pixhawk's avatar
pixhawk committed
167
{
Don Gagne's avatar
Don Gagne committed
168 169 170 171
    if (thread() != QThread::currentThread()) {
        qWarning() << "_deleteLink called from incorrect thread";
        return;
    }
172

Don Gagne's avatar
Don Gagne committed
173 174 175
    if (!link) {
        return;
    }
176

177
    if (!containsLink(link)) {
178 179
        bool channelSet = false;

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

195 196 197 198
        if (!channelSet) {
            qWarning() << "Ran out of mavlink channels";
        }

199
        _sharedLinks.append(SharedLinkInterfacePointer(link));
200 201
        emit newLink(link);
    }
202

Don Gagne's avatar
Don Gagne committed
203 204
    connect(link, &LinkInterface::communicationError,   _app,               &QGCApplication::criticalMessageBoxOnMainThread);
    connect(link, &LinkInterface::bytesReceived,        _mavlinkProtocol,   &MAVLinkProtocol::receiveBytes);
205

206
    _mavlinkProtocol->resetMetadataForLink(link);
207

208 209 210 211 212 213
    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);
214
}
pixhawk's avatar
pixhawk committed
215

Don Gagne's avatar
Don Gagne committed
216
void LinkManager::disconnectAll(void)
pixhawk's avatar
pixhawk committed
217
{
Don Gagne's avatar
Don Gagne committed
218
    // Walk list in reverse order to preserve indices during delete
219 220
    for (int i=_sharedLinks.count()-1; i>=0; i--) {
        disconnectLink(_sharedLinks[i].data());
221
    }
pixhawk's avatar
pixhawk committed
222 223 224 225
}

bool LinkManager::connectLink(LinkInterface* link)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
226 227 228 229 230 231 232
    if (link) {
        if (_connectionsSuspendedMsg()) {
            return false;
        }
        return link->_connect();
    } else {
        qWarning() << "Internal error";
233 234
        return false;
    }
pixhawk's avatar
pixhawk committed
235 236
}

Don Gagne's avatar
Don Gagne committed
237
void LinkManager::disconnectLink(LinkInterface* link)
pixhawk's avatar
pixhawk committed
238
{
239
    if (!link || !containsLink(link)) {
240 241
        return;
    }
Don Gagne's avatar
Don Gagne committed
242

Don Gagne's avatar
Don Gagne committed
243
    link->_disconnect();
244

Don Gagne's avatar
Don Gagne committed
245
    LinkConfiguration* config = link->getLinkConfiguration();
246 247 248 249 250
    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;
251
        }
252
    }
253

Don Gagne's avatar
Don Gagne committed
254
    _deleteLink(link);
pixhawk's avatar
pixhawk committed
255 256
}

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

    if (!link) {
        return;
    }
267

268
    // Free up the mavlink channel associated with this link
269
    _mavlinkChannelsUsedBitMask &= ~(1 << link->mavlinkChannel());
270

271 272 273 274 275 276
    for (int i=0; i<_sharedLinks.count(); i++) {
        if (_sharedLinks[i].data() == link) {
            _sharedLinks.removeAt(i);
            break;
        }
    }
277

Don Gagne's avatar
Don Gagne committed
278
    // Emit removal of link
279
    emit linkDeleted(link);
pixhawk's avatar
pixhawk committed
280 281
}

282 283 284 285 286 287 288 289 290 291 292 293
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);
}

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

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

312 313 314 315 316 317 318 319 320
void LinkManager::_linkConnected(void)
{
    emit linkConnected((LinkInterface*)sender());
}

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

322 323 324 325 326 327
void LinkManager::_linkConnectionRemoved(LinkInterface* link)
{
    // Link has been removed from system, disconnect it automatically
    disconnectLink(link);
}

328 329 330 331 332 333 334 335 336
void LinkManager::suspendConfigurationUpdates(bool suspend)
{
    _configUpdateSuspended = suspend;
}

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

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

    if(linksChanged) {
432
        emit linkConfigurationsChanged();
433 434
    }
    // Enable automatic Serial PX4/3DR Radio hunting
435 436 437
    _configurationsLoaded = true;
}

Gus Grubba's avatar
Gus Grubba committed
438
#ifndef NO_SERIAL_LINK
Don Gagne's avatar
Don Gagne committed
439
SerialConfiguration* LinkManager::_autoconnectConfigurationsContainsPort(const QString& portName)
440 441
{
    QString searchPort = portName.trimmed();
Don Gagne's avatar
Don Gagne committed
442

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

446 447 448
        if (serialConfig) {
            if (serialConfig->portName() == searchPort) {
                return serialConfig;
449
            }
Don Gagne's avatar
Don Gagne committed
450 451
        } else {
            qWarning() << "Internal error";
452 453 454 455
        }
    }
    return NULL;
}
dogmaphobic's avatar
dogmaphobic committed
456
#endif
457

Don Gagne's avatar
Don Gagne committed
458
void LinkManager::_updateAutoConnectLinks(void)
459
{
Don Gagne's avatar
Don Gagne committed
460
    if (_connectionsSuspended || qgcApp()->runningUnitTests()) {
461 462
        return;
    }
Don Gagne's avatar
Don Gagne committed
463

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

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

498
    // Iterate Comm Ports
Don Gagne's avatar
Don Gagne committed
499
    foreach (QGCSerialPortInfo portInfo, portList) {
Don Gagne's avatar
Don Gagne committed
500 501 502 503 504 505 506 507 508
        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
509 510
        // Save port name
        currentPorts << portInfo.systemLocation();
Don Gagne's avatar
Don Gagne committed
511

512 513
        QGCSerialPortInfo::BoardType_t boardType;
        QString boardName;
Don Gagne's avatar
Don Gagne committed
514

515
        if (portInfo.getBoardInfo(boardType, boardName)) {
Don Gagne's avatar
Don Gagne committed
516 517
            if (portInfo.isBootloader()) {
                // Don't connect to bootloader
518
                qCDebug(LinkManagerLog) << "Waiting for bootloader to finish" << portInfo.systemLocation();
Don Gagne's avatar
Don Gagne committed
519 520
                continue;
            }
521

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

533
                _autoconnectWaitList.remove(portInfo.systemLocation());
534

Don Gagne's avatar
Don Gagne committed
535
                switch (boardType) {
536
                case QGCSerialPortInfo::BoardTypePixhawk:
537
                    if (_autoConnectSettings->autoConnectPixhawk()->rawValue().toBool()) {
538
                        pSerialConfig = new SerialConfiguration(tr("%1 on %2 (AutoConnect)").arg(boardName).arg(portInfo.portName().trimmed()));
539 540 541
                        pSerialConfig->setUsbDirect(true);
                    }
                    break;
Don Gagne's avatar
Don Gagne committed
542
                case QGCSerialPortInfo::BoardTypePX4Flow:
543
                    if (_autoConnectSettings->autoConnectPX4Flow()->rawValue().toBool()) {
544
                        pSerialConfig = new SerialConfiguration(tr("%1 on %2 (AutoConnect)").arg(boardName).arg(portInfo.portName().trimmed()));
Don Gagne's avatar
Don Gagne committed
545
                    }
Don Gagne's avatar
Don Gagne committed
546
                    break;
547
                case QGCSerialPortInfo::BoardTypeSiKRadio:
548
                    if (_autoConnectSettings->autoConnectSiKRadio()->rawValue().toBool()) {
549
                        pSerialConfig = new SerialConfiguration(tr("%1 on %2 (AutoConnect)").arg(boardName).arg(portInfo.portName().trimmed()));
Don Gagne's avatar
Don Gagne committed
550 551
                    }
                    break;
552
                case QGCSerialPortInfo::BoardTypeOpenPilot:
553
                    if (_autoConnectSettings->autoConnectLibrePilot()->rawValue().toBool()) {
554
                        pSerialConfig = new SerialConfiguration(tr("%1 on %2 (AutoConnect)").arg(boardName).arg(portInfo.portName().trimmed()));
555 556
                    }
                    break;
Don Gagne's avatar
Don Gagne committed
557 558
#ifndef __mobile__
                case QGCSerialPortInfo::BoardTypeRTKGPS:
559
                    if (_autoConnectSettings->autoConnectRTKGPS()->rawValue().toBool() && !_toolbox->gpsManager()->connected()) {
Don Gagne's avatar
Don Gagne committed
560 561 562 563 564
                        qCDebug(LinkManagerLog) << "RTK GPS auto-connected";
                        _toolbox->gpsManager()->connectGPS(portInfo.systemLocation());
                    }
                    break;
#endif
Don Gagne's avatar
Don Gagne committed
565 566
                default:
                    qWarning() << "Internal error";
Don Gagne's avatar
Don Gagne committed
567
                    continue;
dogmaphobic's avatar
dogmaphobic committed
568
                }
Don Gagne's avatar
Don Gagne committed
569

Don Gagne's avatar
Don Gagne committed
570 571
                if (pSerialConfig) {
                    qCDebug(LinkManagerLog) << "New auto-connect port added: " << pSerialConfig->name() << portInfo.systemLocation();
572
                    pSerialConfig->setBaud(boardType == QGCSerialPortInfo::BoardTypeSiKRadio ? 57600 : 115200);
Don Gagne's avatar
Don Gagne committed
573 574
                    pSerialConfig->setDynamic(true);
                    pSerialConfig->setPortName(portInfo.systemLocation());
575 576
                    _sharedAutoconnectConfigurations.append(SharedLinkConfigurationPointer(pSerialConfig));
                    createConnectedLink(_sharedAutoconnectConfigurations.last());
Don Gagne's avatar
Don Gagne committed
577
                }
dogmaphobic's avatar
dogmaphobic committed
578 579 580
            }
        }
    }
Don Gagne's avatar
Don Gagne committed
581

582 583 584 585 586 587 588
#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
589 590
    // Now we go through the current configuration list and make sure any dynamic config has gone away
    QList<LinkConfiguration*>  _confToDelete;
591 592 593 594 595 596 597
    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()) {
598 599 600 601 602 603
                            // 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
604
                }
605
                _confToDelete.append(serialConfig);
dogmaphobic's avatar
dogmaphobic committed
606
            }
Don Gagne's avatar
Don Gagne committed
607 608
        } else {
            qWarning() << "Internal error";
dogmaphobic's avatar
dogmaphobic committed
609 610
        }
    }
Don Gagne's avatar
Don Gagne committed
611

Don Gagne's avatar
Don Gagne committed
612
    // Now remove all configs that are gone
Don Gagne's avatar
Don Gagne committed
613
    foreach (LinkConfiguration* pDeleteConfig, _confToDelete) {
Don Gagne's avatar
Don Gagne committed
614
        qCDebug(LinkManagerLog) << "Removing unused autoconnect config" << pDeleteConfig->name();
Don Gagne's avatar
Don Gagne committed
615 616 617
        if (pDeleteConfig->link()) {
            disconnectLink(pDeleteConfig->link());
        }
618 619 620 621 622 623
        for (int i=0; i<_sharedAutoconnectConfigurations.count(); i++) {
            if (_sharedAutoconnectConfigurations[i].data() == pDeleteConfig) {
                _sharedAutoconnectConfigurations.removeAt(i);
                break;
            }
        }
624
    }
625
#endif
Gus Grubba's avatar
Gus Grubba committed
626
#endif // NO_SERIAL_LINK
627 628
}

Don Gagne's avatar
Don Gagne committed
629 630 631
void LinkManager::shutdown(void)
{
    setConnectionsSuspended("Shutdown");
Don Gagne's avatar
Don Gagne committed
632
    disconnectAll();
Don Gagne's avatar
Don Gagne committed
633 634
}

635 636 637 638 639 640
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
641
#ifndef NO_SERIAL_LINK
642 643 644 645
        list += "Serial";
#endif
        list += "UDP";
        list += "TCP";
dogmaphobic's avatar
dogmaphobic committed
646
#ifdef QGC_ENABLE_BLUETOOTH
dogmaphobic's avatar
dogmaphobic committed
647 648
        list += "Bluetooth";
#endif
dogmaphobic's avatar
dogmaphobic committed
649
#ifdef QT_DEBUG
650
        list += "Mock Link";
dogmaphobic's avatar
dogmaphobic committed
651 652
#endif
#ifndef __mobile__
653
        list += "Log Replay";
dogmaphobic's avatar
dogmaphobic committed
654
#endif
DonLakeFlyer's avatar
DonLakeFlyer committed
655 656 657
        if (list.size() != (int)LinkConfiguration::TypeLast) {
            qWarning() << "Internal error";
        }
658 659 660 661
    }
    return list;
}

662
void LinkManager::_updateSerialPorts()
663
{
664 665
    _commPortList.clear();
    _commPortDisplayList.clear();
Gus Grubba's avatar
Gus Grubba committed
666
#ifndef NO_SERIAL_LINK
667 668
    QList<QSerialPortInfo> portList = QSerialPortInfo::availablePorts();
    foreach (const QSerialPortInfo &info, portList)
669
    {
670 671 672
        QString port = info.systemLocation().trimmed();
        _commPortList += port;
        _commPortDisplayList += SerialConfiguration::cleanPortDisplayname(port);
673 674
    }
#endif
675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691
}

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

QStringList LinkManager::serialPorts(void)
{
    if(!_commPortList.size())
    {
        _updateSerialPorts();
    }
692 693 694 695 696
    return _commPortList;
}

QStringList LinkManager::serialBaudRates(void)
{
Gus Grubba's avatar
Gus Grubba committed
697
#ifdef NO_SERIAL_LINK
698 699 700 701 702 703
    QStringList foo;
    return foo;
#else
    return SerialConfiguration::supportedBaudRates();
#endif
}
704 705 706

bool LinkManager::endConfigurationEditing(LinkConfiguration* config, LinkConfiguration* editedConfig)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
707 708 709 710 711 712 713 714 715 716 717
    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";
    }
718 719 720 721 722
    return true;
}

bool LinkManager::endCreateConfiguration(LinkConfiguration* config)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
723 724 725 726 727 728 729
    if (config) {
        _fixUnnamed(config);
        addConfiguration(config);
        saveLinkConfigurationList();
    } else {
        qWarning() << "Internal error";
    }
730 731 732 733 734
    return true;
}

LinkConfiguration* LinkManager::createConfiguration(int type, const QString& name)
{
Gus Grubba's avatar
Gus Grubba committed
735
#ifndef NO_SERIAL_LINK
736 737
    if((LinkConfiguration::LinkType)type == LinkConfiguration::TypeSerial)
        _updateSerialPorts();
dogmaphobic's avatar
dogmaphobic committed
738
#endif
739 740 741 742 743
    return LinkConfiguration::createSettings(type, name);
}

LinkConfiguration* LinkManager::startConfigurationEditing(LinkConfiguration* config)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
744
    if (config) {
Gus Grubba's avatar
Gus Grubba committed
745
#ifndef NO_SERIAL_LINK
DonLakeFlyer's avatar
DonLakeFlyer committed
746 747
        if(config->type() == LinkConfiguration::TypeSerial)
            _updateSerialPorts();
dogmaphobic's avatar
dogmaphobic committed
748
#endif
DonLakeFlyer's avatar
DonLakeFlyer committed
749 750 751 752 753
        return LinkConfiguration::duplicateSettings(config);
    } else {
        qWarning() << "Internal error";
        return NULL;
    }
754 755 756 757 758
}


void LinkManager::_fixUnnamed(LinkConfiguration* config)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
759 760 761 762
    if (config) {
        //-- Check for "Unnamed"
        if (config->name() == "Unnamed") {
            switch(config->type()) {
Gus Grubba's avatar
Gus Grubba committed
763
#ifndef NO_SERIAL_LINK
764 765
            case LinkConfiguration::TypeSerial: {
                QString tname = dynamic_cast<SerialConfiguration*>(config)->portName();
766
#ifdef Q_OS_WIN
767 768 769 770 771 772 773
                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
774
            }
775 776 777
#endif
            case LinkConfiguration::TypeUdp:
                config->setName(
DonLakeFlyer's avatar
DonLakeFlyer committed
778
                            QString("UDP Link on Port %1").arg(dynamic_cast<UDPConfiguration*>(config)->localPort()));
779 780
                break;
            case LinkConfiguration::TypeTcp: {
DonLakeFlyer's avatar
DonLakeFlyer committed
781 782 783 784
                TCPConfiguration* tconfig = dynamic_cast<TCPConfiguration*>(config);
                if(tconfig) {
                    config->setName(
                                QString("TCP Link %1:%2").arg(tconfig->address().toString()).arg((int)tconfig->port()));
785
                }
DonLakeFlyer's avatar
DonLakeFlyer committed
786
            }
787
                break;
dogmaphobic's avatar
dogmaphobic committed
788
#ifdef QGC_ENABLE_BLUETOOTH
dogmaphobic's avatar
dogmaphobic committed
789
            case LinkConfiguration::TypeBluetooth: {
DonLakeFlyer's avatar
DonLakeFlyer committed
790 791 792
                BluetoothConfiguration* tconfig = dynamic_cast<BluetoothConfiguration*>(config);
                if(tconfig) {
                    config->setName(QString("%1 (Bluetooth Device)").arg(tconfig->device().name));
dogmaphobic's avatar
dogmaphobic committed
793
                }
DonLakeFlyer's avatar
DonLakeFlyer committed
794
            }
dogmaphobic's avatar
dogmaphobic committed
795 796
                break;
#endif
dogmaphobic's avatar
dogmaphobic committed
797
#ifndef __mobile__
798
            case LinkConfiguration::TypeLogReplay: {
DonLakeFlyer's avatar
DonLakeFlyer committed
799 800 801
                LogReplayLinkConfiguration* tconfig = dynamic_cast<LogReplayLinkConfiguration*>(config);
                if(tconfig) {
                    config->setName(QString("Log Replay %1").arg(tconfig->logFilenameShort()));
802
                }
DonLakeFlyer's avatar
DonLakeFlyer committed
803
            }
804
                break;
dogmaphobic's avatar
dogmaphobic committed
805
#endif
806 807 808
#ifdef QT_DEBUG
            case LinkConfiguration::TypeMock:
                config->setName(
DonLakeFlyer's avatar
DonLakeFlyer committed
809
                            QString("Mock Link"));
810 811 812 813 814
                break;
#endif
            case LinkConfiguration::TypeLast:
            default:
                break;
DonLakeFlyer's avatar
DonLakeFlyer committed
815
            }
816
        }
DonLakeFlyer's avatar
DonLakeFlyer committed
817 818
    } else {
        qWarning() << "Internal error";
819 820 821 822 823
    }
}

void LinkManager::removeConfiguration(LinkConfiguration* config)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
824 825 826 827 828
    if (config) {
        LinkInterface* iface = config->link();
        if(iface) {
            disconnectLink(iface);
        }
829

DonLakeFlyer's avatar
DonLakeFlyer committed
830 831 832 833 834
        _removeConfiguration(config);
        saveLinkConfigurationList();
    } else {
        qWarning() << "Internal error";
    }
835
}
836

837 838
bool LinkManager::isAutoconnectLink(LinkInterface* link)
{
839 840 841 842 843 844
    for (int i=0; i<_sharedAutoconnectConfigurations.count(); i++) {
        if (_sharedAutoconnectConfigurations[i].data() == link->getLinkConfiguration()) {
            return true;
        }
    }
    return false;
845
}
dogmaphobic's avatar
dogmaphobic committed
846 847 848 849 850

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

Gus Grubba's avatar
Gus Grubba committed
852
#ifndef NO_SERIAL_LINK
Don Gagne's avatar
Don Gagne committed
853 854
void LinkManager::_activeLinkCheck(void)
{
855
    SerialLink* link = NULL;
Don Gagne's avatar
Don Gagne committed
856 857 858
    bool found = false;

    if (_activeLinkCheckList.count() != 0) {
859
        link = _activeLinkCheckList.takeFirst();
860
        if (containsLink(link) && link->isConnected()) {
Don Gagne's avatar
Don Gagne committed
861 862 863 864 865 866 867 868 869
            // 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;
                }
            }
870 871
        } else {
            link = NULL;
Don Gagne's avatar
Don Gagne committed
872 873 874 875 876 877 878
        }
    }

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

879 880 881
    if (!found && link) {
        // See if we can get an NSH prompt on this link
        bool foundNSHPrompt = false;
882
        link->writeBytesSafe("\r", 1);
883 884 885 886 887 888 889 890 891
        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 ?
892 893
                                  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
894 895
    }
}
Don Gagne's avatar
Don Gagne committed
896
#endif
897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938

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;
}
939 940 941 942 943 944 945 946 947 948 949

void LinkManager::startAutoConnectedLinks(void)
{
    SharedLinkConfigurationPointer conf;

    for(int i = 0; i < _sharedConfigurations.count(); i++) {
        conf = _sharedConfigurations[i];
        if (conf->isAutoConnect())
            createConnectedLink(conf);
    }
}