LinkManager.cc 33.3 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
    _mavlinkProtocol->setVersion(_mavlinkProtocol->getCurrentVersion());
195

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

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

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

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

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

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

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

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

    if (!link) {
        return;
    }
255

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    // Check for RTK GPS connection gone
    if (!_autoConnectRTKPort.isEmpty() && !currentPorts.contains(_autoConnectRTKPort)) {
        qCDebug(LinkManagerLog) << "RTK GPS disconnected" << _autoConnectRTKPort;
        _toolbox->gpsManager()->disconnectGPS();
        _autoConnectRTKPort.clear();
    }

622
#endif
Gus Grubba's avatar
Gus Grubba committed
623
#endif // NO_SERIAL_LINK
624 625
}

Don Gagne's avatar
Don Gagne committed
626 627
void LinkManager::shutdown(void)
{
628
    setConnectionsSuspended(tr("Shutdown"));
Don Gagne's avatar
Don Gagne committed
629
    disconnectAll();
Don Gagne's avatar
Don Gagne committed
630 631
}

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

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

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

QStringList LinkManager::serialPorts(void)
{
    if(!_commPortList.size())
    {
        _updateSerialPorts();
    }
689 690 691 692 693
    return _commPortList;
}

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

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

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

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

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


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

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

DonLakeFlyer's avatar
DonLakeFlyer committed
827 828 829 830 831
        _removeConfiguration(config);
        saveLinkConfigurationList();
    } else {
        qWarning() << "Internal error";
    }
832
}
833

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

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

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

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

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

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

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;
}
936 937 938 939 940 941 942 943 944 945 946

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

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

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