LinkManager.cc 33.1 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 180 181
        int mavlinkChannel = _reserveMavlinkChannel();
        if (mavlinkChannel != 0) {
            link->_setMavlinkChannel(mavlinkChannel);
        } else {
182
            qWarning() << "Ran out of mavlink channels";
183
            return;
184 185
        }

186
        _sharedLinks.append(SharedLinkInterfacePointer(link));
187 188
        emit newLink(link);
    }
189

Don Gagne's avatar
Don Gagne committed
190 191
    connect(link, &LinkInterface::communicationError,   _app,               &QGCApplication::criticalMessageBoxOnMainThread);
    connect(link, &LinkInterface::bytesReceived,        _mavlinkProtocol,   &MAVLinkProtocol::receiveBytes);
192

193
    _mavlinkProtocol->resetMetadataForLink(link);
194

195 196 197 198 199 200
    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);
201
}
pixhawk's avatar
pixhawk committed
202

Don Gagne's avatar
Don Gagne committed
203
void LinkManager::disconnectAll(void)
pixhawk's avatar
pixhawk committed
204
{
Don Gagne's avatar
Don Gagne committed
205
    // Walk list in reverse order to preserve indices during delete
206 207
    for (int i=_sharedLinks.count()-1; i>=0; i--) {
        disconnectLink(_sharedLinks[i].data());
208
    }
pixhawk's avatar
pixhawk committed
209 210 211 212
}

bool LinkManager::connectLink(LinkInterface* link)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
213 214 215 216 217 218 219
    if (link) {
        if (_connectionsSuspendedMsg()) {
            return false;
        }
        return link->_connect();
    } else {
        qWarning() << "Internal error";
220 221
        return false;
    }
pixhawk's avatar
pixhawk committed
222 223
}

Don Gagne's avatar
Don Gagne committed
224
void LinkManager::disconnectLink(LinkInterface* link)
pixhawk's avatar
pixhawk committed
225
{
226
    if (!link || !containsLink(link)) {
227 228
        return;
    }
Don Gagne's avatar
Don Gagne committed
229

Don Gagne's avatar
Don Gagne committed
230
    link->_disconnect();
231

Don Gagne's avatar
Don Gagne committed
232
    LinkConfiguration* config = link->getLinkConfiguration();
233 234 235 236 237
    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;
238
        }
239
    }
240

Don Gagne's avatar
Don Gagne committed
241
    _deleteLink(link);
pixhawk's avatar
pixhawk committed
242 243
}

244
void LinkManager::_deleteLink(LinkInterface* link)
245
{
Don Gagne's avatar
Don Gagne committed
246 247 248 249 250 251 252 253
    if (thread() != QThread::currentThread()) {
        qWarning() << "_deleteLink called from incorrect thread";
        return;
    }

    if (!link) {
        return;
    }
254

255
    // Free up the mavlink channel associated with this link
256
    _freeMavlinkChannel(link->mavlinkChannel());
257

258 259 260 261 262 263
    for (int i=0; i<_sharedLinks.count(); i++) {
        if (_sharedLinks[i].data() == link) {
            _sharedLinks.removeAt(i);
            break;
        }
    }
264

Don Gagne's avatar
Don Gagne committed
265
    // Emit removal of link
266
    emit linkDeleted(link);
pixhawk's avatar
pixhawk committed
267 268
}

269 270 271 272 273 274 275 276 277 278 279 280
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);
}

281 282 283 284 285
/// @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) {
286
        qgcApp()->showMessage(QString("Connect not allowed: %1").arg(_connectionsSuspendedReason));
287 288 289 290 291 292 293 294 295 296 297
        return true;
    } else {
        return false;
    }
}

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

299 300 301 302 303 304 305 306 307
void LinkManager::_linkConnected(void)
{
    emit linkConnected((LinkInterface*)sender());
}

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

309 310 311 312 313 314
void LinkManager::_linkConnectionRemoved(LinkInterface* link)
{
    // Link has been removed from system, disconnect it automatically
    disconnectLink(link);
}

315 316 317 318 319 320 321 322 323
void LinkManager::suspendConfigurationUpdates(bool suspend)
{
    _configUpdateSuspended = suspend;
}

void LinkManager::saveLinkConfigurationList()
{
    QSettings settings;
    settings.remove(LinkConfiguration::settingsRoot());
324
    int trueCount = 0;
325 326
    for (int i = 0; i < _sharedConfigurations.count(); i++) {
        SharedLinkConfigurationPointer linkConfig = _sharedConfigurations[i];
Don Gagne's avatar
Don Gagne committed
327
        if (linkConfig) {
328
            if (!linkConfig->isDynamic()) {
Don Gagne's avatar
Don Gagne committed
329
                QString root = LinkConfiguration::settingsRoot();
330
                root += QString("/Link%1").arg(trueCount++);
Don Gagne's avatar
Don Gagne committed
331 332
                settings.setValue(root + "/name", linkConfig->name());
                settings.setValue(root + "/type", linkConfig->type());
333
                settings.setValue(root + "/auto", linkConfig->isAutoConnect());
Don Gagne's avatar
Don Gagne committed
334 335 336 337
                // Have the instance save its own values
                linkConfig->saveSettings(settings, root);
            }
        } else {
338
            qWarning() << "Internal error for link configuration in LinkManager";
dogmaphobic's avatar
dogmaphobic committed
339
        }
340
    }
dogmaphobic's avatar
dogmaphobic committed
341
    QString root(LinkConfiguration::settingsRoot());
342 343
    settings.setValue(root + "/count", trueCount);
    emit linkConfigurationsChanged();
344 345 346 347
}

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

    if(linksChanged) {
419
        emit linkConfigurationsChanged();
420 421
    }
    // Enable automatic Serial PX4/3DR Radio hunting
422 423 424
    _configurationsLoaded = true;
}

Gus Grubba's avatar
Gus Grubba committed
425
#ifndef NO_SERIAL_LINK
Don Gagne's avatar
Don Gagne committed
426
SerialConfiguration* LinkManager::_autoconnectConfigurationsContainsPort(const QString& portName)
427 428
{
    QString searchPort = portName.trimmed();
Don Gagne's avatar
Don Gagne committed
429

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

433 434 435
        if (serialConfig) {
            if (serialConfig->portName() == searchPort) {
                return serialConfig;
436
            }
Don Gagne's avatar
Don Gagne committed
437 438
        } else {
            qWarning() << "Internal error";
439 440 441 442
        }
    }
    return NULL;
}
dogmaphobic's avatar
dogmaphobic committed
443
#endif
444

Don Gagne's avatar
Don Gagne committed
445
void LinkManager::_updateAutoConnectLinks(void)
446
{
Don Gagne's avatar
Don Gagne committed
447
    if (_connectionsSuspended || qgcApp()->runningUnitTests()) {
448 449
        return;
    }
Don Gagne's avatar
Don Gagne committed
450

Don Gagne's avatar
Don Gagne committed
451 452
    // Re-add UDP if we need to
    bool foundUDP = false;
453 454
    for (int i=0; i<_sharedLinks.count(); i++) {
        LinkConfiguration* linkConfig = _sharedLinks[i]->getLinkConfiguration();
Don Gagne's avatar
Don Gagne committed
455 456 457 458 459
        if (linkConfig->type() == LinkConfiguration::TypeUdp && linkConfig->name() == _defaultUPDLinkName) {
            foundUDP = true;
            break;
        }
    }
460
    if (!foundUDP && _autoConnectSettings->autoConnectUDP()->rawValue().toBool()) {
Don Gagne's avatar
Don Gagne committed
461
        qCDebug(LinkManagerLog) << "New auto-connect UDP port added";
Don Gagne's avatar
Don Gagne committed
462 463
        UDPConfiguration* udpConfig = new UDPConfiguration(_defaultUPDLinkName);
        udpConfig->setLocalPort(QGC_UDP_LOCAL_PORT);
DonLakeFlyer's avatar
DonLakeFlyer committed
464
        udpConfig->setDynamic(true);
465 466
        SharedLinkConfigurationPointer config = addConfiguration(udpConfig);
        createConnectedLink(config);
467
        emit linkConfigurationsChanged();
Don Gagne's avatar
Don Gagne committed
468 469
    }

Gus Grubba's avatar
Gus Grubba committed
470
#ifndef NO_SERIAL_LINK
dogmaphobic's avatar
dogmaphobic committed
471
    QStringList currentPorts;
472 473 474 475 476 477
    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.
478
    if (!_sharedAutoconnectConfigurations.count()) {
479 480
        portList = QGCSerialPortInfo::availablePorts();
    }
481 482
#else
    portList = QGCSerialPortInfo::availablePorts();
483
#endif
Don Gagne's avatar
Don Gagne committed
484

485
    // Iterate Comm Ports
Don Gagne's avatar
Don Gagne committed
486
    foreach (QGCSerialPortInfo portInfo, portList) {
Don Gagne's avatar
Don Gagne committed
487 488 489 490 491 492 493 494 495
        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
496 497
        // Save port name
        currentPorts << portInfo.systemLocation();
Don Gagne's avatar
Don Gagne committed
498

499 500
        QGCSerialPortInfo::BoardType_t boardType;
        QString boardName;
Don Gagne's avatar
Don Gagne committed
501

502
        if (portInfo.getBoardInfo(boardType, boardName)) {
Don Gagne's avatar
Don Gagne committed
503 504
            if (portInfo.isBootloader()) {
                // Don't connect to bootloader
505
                qCDebug(LinkManagerLog) << "Waiting for bootloader to finish" << portInfo.systemLocation();
Don Gagne's avatar
Don Gagne committed
506 507
                continue;
            }
508

509 510 511 512 513 514 515
            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();
516 517
                _autoconnectWaitList[portInfo.systemLocation()] = 1;
            } else if (++_autoconnectWaitList[portInfo.systemLocation()] * _autoconnectUpdateTimerMSecs > _autoconnectConnectDelayMSecs) {
Don Gagne's avatar
Don Gagne committed
518 519
                SerialConfiguration* pSerialConfig = NULL;

520
                _autoconnectWaitList.remove(portInfo.systemLocation());
521

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

Don Gagne's avatar
Don Gagne committed
563 564
                if (pSerialConfig) {
                    qCDebug(LinkManagerLog) << "New auto-connect port added: " << pSerialConfig->name() << portInfo.systemLocation();
565
                    pSerialConfig->setBaud(boardType == QGCSerialPortInfo::BoardTypeSiKRadio ? 57600 : 115200);
Don Gagne's avatar
Don Gagne committed
566 567
                    pSerialConfig->setDynamic(true);
                    pSerialConfig->setPortName(portInfo.systemLocation());
568 569
                    _sharedAutoconnectConfigurations.append(SharedLinkConfigurationPointer(pSerialConfig));
                    createConnectedLink(_sharedAutoconnectConfigurations.last());
Don Gagne's avatar
Don Gagne committed
570
                }
dogmaphobic's avatar
dogmaphobic committed
571 572 573
            }
        }
    }
Don Gagne's avatar
Don Gagne committed
574

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

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

Don Gagne's avatar
Don Gagne committed
622 623 624
void LinkManager::shutdown(void)
{
    setConnectionsSuspended("Shutdown");
Don Gagne's avatar
Don Gagne committed
625
    disconnectAll();
Don Gagne's avatar
Don Gagne committed
626 627
}

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

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

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

QStringList LinkManager::serialPorts(void)
{
    if(!_commPortList.size())
    {
        _updateSerialPorts();
    }
685 686 687 688 689
    return _commPortList;
}

QStringList LinkManager::serialBaudRates(void)
{
Gus Grubba's avatar
Gus Grubba committed
690
#ifdef NO_SERIAL_LINK
691 692 693 694 695 696
    QStringList foo;
    return foo;
#else
    return SerialConfiguration::supportedBaudRates();
#endif
}
697 698 699

bool LinkManager::endConfigurationEditing(LinkConfiguration* config, LinkConfiguration* editedConfig)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
700 701 702 703 704 705 706 707 708 709 710
    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";
    }
711 712 713 714 715
    return true;
}

bool LinkManager::endCreateConfiguration(LinkConfiguration* config)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
716 717 718 719 720 721 722
    if (config) {
        _fixUnnamed(config);
        addConfiguration(config);
        saveLinkConfigurationList();
    } else {
        qWarning() << "Internal error";
    }
723 724 725 726 727
    return true;
}

LinkConfiguration* LinkManager::createConfiguration(int type, const QString& name)
{
Gus Grubba's avatar
Gus Grubba committed
728
#ifndef NO_SERIAL_LINK
729 730
    if((LinkConfiguration::LinkType)type == LinkConfiguration::TypeSerial)
        _updateSerialPorts();
dogmaphobic's avatar
dogmaphobic committed
731
#endif
732 733 734 735 736
    return LinkConfiguration::createSettings(type, name);
}

LinkConfiguration* LinkManager::startConfigurationEditing(LinkConfiguration* config)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
737
    if (config) {
Gus Grubba's avatar
Gus Grubba committed
738
#ifndef NO_SERIAL_LINK
DonLakeFlyer's avatar
DonLakeFlyer committed
739 740
        if(config->type() == LinkConfiguration::TypeSerial)
            _updateSerialPorts();
dogmaphobic's avatar
dogmaphobic committed
741
#endif
DonLakeFlyer's avatar
DonLakeFlyer committed
742 743 744 745 746
        return LinkConfiguration::duplicateSettings(config);
    } else {
        qWarning() << "Internal error";
        return NULL;
    }
747 748 749 750 751
}


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

void LinkManager::removeConfiguration(LinkConfiguration* config)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
817 818 819 820 821
    if (config) {
        LinkInterface* iface = config->link();
        if(iface) {
            disconnectLink(iface);
        }
822

DonLakeFlyer's avatar
DonLakeFlyer committed
823 824 825 826 827
        _removeConfiguration(config);
        saveLinkConfigurationList();
    } else {
        qWarning() << "Internal error";
    }
828
}
829

830 831
bool LinkManager::isAutoconnectLink(LinkInterface* link)
{
832 833 834 835 836 837
    for (int i=0; i<_sharedAutoconnectConfigurations.count(); i++) {
        if (_sharedAutoconnectConfigurations[i].data() == link->getLinkConfiguration()) {
            return true;
        }
    }
    return false;
838
}
dogmaphobic's avatar
dogmaphobic committed
839 840 841 842 843

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

Gus Grubba's avatar
Gus Grubba committed
845
#ifndef NO_SERIAL_LINK
Don Gagne's avatar
Don Gagne committed
846 847
void LinkManager::_activeLinkCheck(void)
{
848
    SerialLink* link = NULL;
Don Gagne's avatar
Don Gagne committed
849 850 851
    bool found = false;

    if (_activeLinkCheckList.count() != 0) {
852
        link = _activeLinkCheckList.takeFirst();
853
        if (containsLink(link) && link->isConnected()) {
Don Gagne's avatar
Don Gagne committed
854 855 856 857 858 859 860 861 862
            // 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;
                }
            }
863 864
        } else {
            link = NULL;
Don Gagne's avatar
Don Gagne committed
865 866 867 868 869 870 871
        }
    }

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

872 873 874
    if (!found && link) {
        // See if we can get an NSH prompt on this link
        bool foundNSHPrompt = false;
875
        link->writeBytesSafe("\r", 1);
876 877 878 879 880 881 882 883 884
        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 ?
885 886
                                  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
887 888
    }
}
Don Gagne's avatar
Don Gagne committed
889
#endif
890 891 892 893 894 895 896 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

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;
}
932 933 934 935 936 937 938 939 940 941 942

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

    for(int i = 0; i < _sharedConfigurations.count(); i++) {
        conf = _sharedConfigurations[i];
        if (conf->isAutoConnect())
            createConnectedLink(conf);
    }
}
943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964

int LinkManager::_reserveMavlinkChannel(void)
{
    // Find a mavlink channel to use for this link, Channel 0 is reserved for internal use.
    for (int mavlinkChannel=1; mavlinkChannel<32; mavlinkChannel++) {
        if (!(_mavlinkChannelsUsedBitMask & 1 << mavlinkChannel)) {
            mavlink_reset_channel_status(mavlinkChannel);
            // Start the channel on Mav 1 protocol
            mavlink_status_t* mavlinkStatus = mavlink_get_channel_status(mavlinkChannel);
            mavlinkStatus->flags |= MAVLINK_STATUS_FLAG_OUT_MAVLINK1;
            _mavlinkChannelsUsedBitMask |= 1 << mavlinkChannel;
            return mavlinkChannel;
        }
    }

    return 0;   // All channels reserved
}

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