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

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

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

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

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

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

LinkManager::~LinkManager()
{
69
    delete _nmeaPort;
pixhawk's avatar
pixhawk committed
70 71
}

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

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

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

82 83
}

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

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

101 102
    LinkInterface* pLink = NULL;
    switch(config->type()) {
Gus Grubba's avatar
Gus Grubba committed
103
#ifndef NO_SERIAL_LINK
DonLakeFlyer's avatar
DonLakeFlyer committed
104 105 106 107 108 109 110 111 112
    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
113 114 115
                }
            }
        }
DonLakeFlyer's avatar
DonLakeFlyer committed
116
    }
Don Gagne's avatar
Don Gagne committed
117
        break;
dogmaphobic's avatar
dogmaphobic committed
118
#endif
DonLakeFlyer's avatar
DonLakeFlyer committed
119 120 121 122 123 124
    case LinkConfiguration::TypeUdp:
        pLink = new UDPLink(config);
        break;
    case LinkConfiguration::TypeTcp:
        pLink = new TCPLink(config);
        break;
dogmaphobic's avatar
dogmaphobic committed
125
#ifdef QGC_ENABLE_BLUETOOTH
DonLakeFlyer's avatar
DonLakeFlyer committed
126 127 128
    case LinkConfiguration::TypeBluetooth:
        pLink = new BluetoothLink(config);
        break;
dogmaphobic's avatar
dogmaphobic committed
129
#endif
dogmaphobic's avatar
dogmaphobic committed
130
#ifndef __mobile__
DonLakeFlyer's avatar
DonLakeFlyer committed
131 132 133
    case LinkConfiguration::TypeLogReplay:
        pLink = new LogReplayLink(config);
        break;
dogmaphobic's avatar
dogmaphobic committed
134
#endif
135
#ifdef QT_DEBUG
DonLakeFlyer's avatar
DonLakeFlyer committed
136 137 138
    case LinkConfiguration::TypeMock:
        pLink = new MockLink(config);
        break;
139
#endif
DonLakeFlyer's avatar
DonLakeFlyer committed
140 141 142
    case LinkConfiguration::TypeLast:
    default:
        break;
143
    }
144 145

    if (pLink) {
146 147
        _addLink(pLink);
        connectLink(pLink);
148
    }
149

150 151 152
    return pLink;
}

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

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

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

179
    if (!containsLink(link)) {
180 181 182 183
        int mavlinkChannel = _reserveMavlinkChannel();
        if (mavlinkChannel != 0) {
            link->_setMavlinkChannel(mavlinkChannel);
        } else {
184
            qWarning() << "Ran out of mavlink channels";
185
            return;
186 187
        }

188
        _sharedLinks.append(SharedLinkInterfacePointer(link));
189 190
        emit newLink(link);
    }
191

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

195
    _mavlinkProtocol->resetMetadataForLink(link);
196
    _mavlinkProtocol->setVersion(_mavlinkProtocol->getCurrentVersion());
197

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

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

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

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

Don Gagne's avatar
Don Gagne committed
233
    link->_disconnect();
234

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

Don Gagne's avatar
Don Gagne committed
244
    _deleteLink(link);
pixhawk's avatar
pixhawk committed
245 246
}

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

    if (!link) {
        return;
    }
257

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

502 503
        QGCSerialPortInfo::BoardType_t boardType;
        QString boardName;
Don Gagne's avatar
Don Gagne committed
504

505 506 507 508 509 510 511 512 513 514 515 516
        if (portInfo.systemLocation().trimmed() == _autoConnectSettings->autoConnectNmeaPort()->cookedValueString()) {
            if (portInfo.systemLocation().trimmed() != _nmeaDeviceName) {
                _nmeaDeviceName = portInfo.systemLocation().trimmed();
                qCDebug(LinkManagerLog) << "Configuring nmea port" << _nmeaDeviceName;
                QSerialPort* newPort = new QSerialPort(portInfo);

                _nmeaBaud = _autoConnectSettings->autoConnectNmeaBaud()->cookedValue().toUInt();
                newPort->setBaudRate(_nmeaBaud);
                qCDebug(LinkManagerLog) << "Configuring nmea baudrate" << _nmeaBaud;

                // This will stop polling old device if previously set
                _toolbox->qgcPositionManager()->setNmeaSourceDevice(newPort);
517

518 519 520 521 522 523 524 525 526 527 528
                if (_nmeaPort) {
                    delete _nmeaPort;
                }
                _nmeaPort = newPort;

            } else if (_autoConnectSettings->autoConnectNmeaBaud()->cookedValue().toUInt() != _nmeaBaud) {
                _nmeaBaud = _autoConnectSettings->autoConnectNmeaBaud()->cookedValue().toUInt();
                _nmeaPort->setBaudRate(_nmeaBaud);
                qCDebug(LinkManagerLog) << "Configuring nmea baudrate" << _nmeaBaud;
            }
        } else if (portInfo.getBoardInfo(boardType, boardName)) {
Don Gagne's avatar
Don Gagne committed
529 530
            if (portInfo.isBootloader()) {
                // Don't connect to bootloader
531
                qCDebug(LinkManagerLog) << "Waiting for bootloader to finish" << portInfo.systemLocation();
Don Gagne's avatar
Don Gagne committed
532 533
                continue;
            }
534

535
            if (_autoconnectConfigurationsContainsPort(portInfo.systemLocation()) || _autoConnectRTKPort == portInfo.systemLocation()) {
536 537 538 539 540 541
                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();
542 543
                _autoconnectWaitList[portInfo.systemLocation()] = 1;
            } else if (++_autoconnectWaitList[portInfo.systemLocation()] * _autoconnectUpdateTimerMSecs > _autoconnectConnectDelayMSecs) {
Don Gagne's avatar
Don Gagne committed
544 545
                SerialConfiguration* pSerialConfig = NULL;

546
                _autoconnectWaitList.remove(portInfo.systemLocation());
547

Don Gagne's avatar
Don Gagne committed
548
                switch (boardType) {
549
                case QGCSerialPortInfo::BoardTypePixhawk:
550
                    if (_autoConnectSettings->autoConnectPixhawk()->rawValue().toBool()) {
551
                        pSerialConfig = new SerialConfiguration(tr("%1 on %2 (AutoConnect)").arg(boardName).arg(portInfo.portName().trimmed()));
552 553 554
                        pSerialConfig->setUsbDirect(true);
                    }
                    break;
Don Gagne's avatar
Don Gagne committed
555
                case QGCSerialPortInfo::BoardTypePX4Flow:
556
                    if (_autoConnectSettings->autoConnectPX4Flow()->rawValue().toBool()) {
557
                        pSerialConfig = new SerialConfiguration(tr("%1 on %2 (AutoConnect)").arg(boardName).arg(portInfo.portName().trimmed()));
Don Gagne's avatar
Don Gagne committed
558
                    }
Don Gagne's avatar
Don Gagne committed
559
                    break;
560
                case QGCSerialPortInfo::BoardTypeSiKRadio:
561
                    if (_autoConnectSettings->autoConnectSiKRadio()->rawValue().toBool()) {
562
                        pSerialConfig = new SerialConfiguration(tr("%1 on %2 (AutoConnect)").arg(boardName).arg(portInfo.portName().trimmed()));
Don Gagne's avatar
Don Gagne committed
563 564
                    }
                    break;
565
                case QGCSerialPortInfo::BoardTypeOpenPilot:
566
                    if (_autoConnectSettings->autoConnectLibrePilot()->rawValue().toBool()) {
567
                        pSerialConfig = new SerialConfiguration(tr("%1 on %2 (AutoConnect)").arg(boardName).arg(portInfo.portName().trimmed()));
568 569
                    }
                    break;
Don Gagne's avatar
Don Gagne committed
570 571
#ifndef __mobile__
                case QGCSerialPortInfo::BoardTypeRTKGPS:
572
                    if (_autoConnectSettings->autoConnectRTKGPS()->rawValue().toBool() && !_toolbox->gpsManager()->connected()) {
573 574
                        qCDebug(LinkManagerLog) << "RTK GPS auto-connected" << portInfo.portName().trimmed();
                        _autoConnectRTKPort = portInfo.systemLocation();
Don Gagne's avatar
Don Gagne committed
575 576 577 578
                        _toolbox->gpsManager()->connectGPS(portInfo.systemLocation());
                    }
                    break;
#endif
Don Gagne's avatar
Don Gagne committed
579 580
                default:
                    qWarning() << "Internal error";
Don Gagne's avatar
Don Gagne committed
581
                    continue;
dogmaphobic's avatar
dogmaphobic committed
582
                }
Don Gagne's avatar
Don Gagne committed
583

Don Gagne's avatar
Don Gagne committed
584 585
                if (pSerialConfig) {
                    qCDebug(LinkManagerLog) << "New auto-connect port added: " << pSerialConfig->name() << portInfo.systemLocation();
586
                    pSerialConfig->setBaud(boardType == QGCSerialPortInfo::BoardTypeSiKRadio ? 57600 : 115200);
Don Gagne's avatar
Don Gagne committed
587 588
                    pSerialConfig->setDynamic(true);
                    pSerialConfig->setPortName(portInfo.systemLocation());
589 590
                    _sharedAutoconnectConfigurations.append(SharedLinkConfigurationPointer(pSerialConfig));
                    createConnectedLink(_sharedAutoconnectConfigurations.last());
Don Gagne's avatar
Don Gagne committed
591
                }
dogmaphobic's avatar
dogmaphobic committed
592 593 594
            }
        }
    }
Don Gagne's avatar
Don Gagne committed
595

596 597 598 599 600 601 602
#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
603 604
    // Now we go through the current configuration list and make sure any dynamic config has gone away
    QList<LinkConfiguration*>  _confToDelete;
605 606 607 608 609 610 611
    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()) {
612 613 614 615 616 617
                            // 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
618
                }
619
                _confToDelete.append(serialConfig);
dogmaphobic's avatar
dogmaphobic committed
620
            }
Don Gagne's avatar
Don Gagne committed
621 622
        } else {
            qWarning() << "Internal error";
dogmaphobic's avatar
dogmaphobic committed
623 624
        }
    }
Don Gagne's avatar
Don Gagne committed
625

Don Gagne's avatar
Don Gagne committed
626
    // Now remove all configs that are gone
Don Gagne's avatar
Don Gagne committed
627
    foreach (LinkConfiguration* pDeleteConfig, _confToDelete) {
Don Gagne's avatar
Don Gagne committed
628
        qCDebug(LinkManagerLog) << "Removing unused autoconnect config" << pDeleteConfig->name();
Don Gagne's avatar
Don Gagne committed
629 630 631
        if (pDeleteConfig->link()) {
            disconnectLink(pDeleteConfig->link());
        }
632 633 634 635 636 637
        for (int i=0; i<_sharedAutoconnectConfigurations.count(); i++) {
            if (_sharedAutoconnectConfigurations[i].data() == pDeleteConfig) {
                _sharedAutoconnectConfigurations.removeAt(i);
                break;
            }
        }
638
    }
639 640

    // Check for RTK GPS connection gone
641
#if !defined(__mobile__)
642 643 644 645 646
    if (!_autoConnectRTKPort.isEmpty() && !currentPorts.contains(_autoConnectRTKPort)) {
        qCDebug(LinkManagerLog) << "RTK GPS disconnected" << _autoConnectRTKPort;
        _toolbox->gpsManager()->disconnectGPS();
        _autoConnectRTKPort.clear();
    }
647
#endif
648

649
#endif
Gus Grubba's avatar
Gus Grubba committed
650
#endif // NO_SERIAL_LINK
651 652
}

Don Gagne's avatar
Don Gagne committed
653 654
void LinkManager::shutdown(void)
{
655
    setConnectionsSuspended(tr("Shutdown"));
Don Gagne's avatar
Don Gagne committed
656
    disconnectAll();
Don Gagne's avatar
Don Gagne committed
657 658
}

659 660 661 662 663 664
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
665
#ifndef NO_SERIAL_LINK
666 667 668 669
        list += "Serial";
#endif
        list += "UDP";
        list += "TCP";
dogmaphobic's avatar
dogmaphobic committed
670
#ifdef QGC_ENABLE_BLUETOOTH
dogmaphobic's avatar
dogmaphobic committed
671 672
        list += "Bluetooth";
#endif
dogmaphobic's avatar
dogmaphobic committed
673
#ifdef QT_DEBUG
674
        list += "Mock Link";
dogmaphobic's avatar
dogmaphobic committed
675 676
#endif
#ifndef __mobile__
677
        list += "Log Replay";
dogmaphobic's avatar
dogmaphobic committed
678
#endif
DonLakeFlyer's avatar
DonLakeFlyer committed
679 680 681
        if (list.size() != (int)LinkConfiguration::TypeLast) {
            qWarning() << "Internal error";
        }
682 683 684 685
    }
    return list;
}

686
void LinkManager::_updateSerialPorts()
687
{
688 689
    _commPortList.clear();
    _commPortDisplayList.clear();
Gus Grubba's avatar
Gus Grubba committed
690
#ifndef NO_SERIAL_LINK
691 692
    QList<QSerialPortInfo> portList = QSerialPortInfo::availablePorts();
    foreach (const QSerialPortInfo &info, portList)
693
    {
694 695 696
        QString port = info.systemLocation().trimmed();
        _commPortList += port;
        _commPortDisplayList += SerialConfiguration::cleanPortDisplayname(port);
697 698
    }
#endif
699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715
}

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

QStringList LinkManager::serialPorts(void)
{
    if(!_commPortList.size())
    {
        _updateSerialPorts();
    }
716 717 718 719 720
    return _commPortList;
}

QStringList LinkManager::serialBaudRates(void)
{
Gus Grubba's avatar
Gus Grubba committed
721
#ifdef NO_SERIAL_LINK
722 723 724 725 726 727
    QStringList foo;
    return foo;
#else
    return SerialConfiguration::supportedBaudRates();
#endif
}
728 729 730

bool LinkManager::endConfigurationEditing(LinkConfiguration* config, LinkConfiguration* editedConfig)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
731 732 733 734 735 736 737 738 739 740 741
    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";
    }
742 743 744 745 746
    return true;
}

bool LinkManager::endCreateConfiguration(LinkConfiguration* config)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
747 748 749 750 751 752 753
    if (config) {
        _fixUnnamed(config);
        addConfiguration(config);
        saveLinkConfigurationList();
    } else {
        qWarning() << "Internal error";
    }
754 755 756 757 758
    return true;
}

LinkConfiguration* LinkManager::createConfiguration(int type, const QString& name)
{
Gus Grubba's avatar
Gus Grubba committed
759
#ifndef NO_SERIAL_LINK
760 761
    if((LinkConfiguration::LinkType)type == LinkConfiguration::TypeSerial)
        _updateSerialPorts();
dogmaphobic's avatar
dogmaphobic committed
762
#endif
763 764 765 766 767
    return LinkConfiguration::createSettings(type, name);
}

LinkConfiguration* LinkManager::startConfigurationEditing(LinkConfiguration* config)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
768
    if (config) {
Gus Grubba's avatar
Gus Grubba committed
769
#ifndef NO_SERIAL_LINK
DonLakeFlyer's avatar
DonLakeFlyer committed
770 771
        if(config->type() == LinkConfiguration::TypeSerial)
            _updateSerialPorts();
dogmaphobic's avatar
dogmaphobic committed
772
#endif
DonLakeFlyer's avatar
DonLakeFlyer committed
773 774 775 776 777
        return LinkConfiguration::duplicateSettings(config);
    } else {
        qWarning() << "Internal error";
        return NULL;
    }
778 779 780 781 782
}


void LinkManager::_fixUnnamed(LinkConfiguration* config)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
783 784 785 786
    if (config) {
        //-- Check for "Unnamed"
        if (config->name() == "Unnamed") {
            switch(config->type()) {
Gus Grubba's avatar
Gus Grubba committed
787
#ifndef NO_SERIAL_LINK
788 789
            case LinkConfiguration::TypeSerial: {
                QString tname = dynamic_cast<SerialConfiguration*>(config)->portName();
790
#ifdef Q_OS_WIN
791 792 793 794 795 796 797
                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
798
            }
799 800 801
#endif
            case LinkConfiguration::TypeUdp:
                config->setName(
DonLakeFlyer's avatar
DonLakeFlyer committed
802
                            QString("UDP Link on Port %1").arg(dynamic_cast<UDPConfiguration*>(config)->localPort()));
803 804
                break;
            case LinkConfiguration::TypeTcp: {
DonLakeFlyer's avatar
DonLakeFlyer committed
805 806 807 808
                TCPConfiguration* tconfig = dynamic_cast<TCPConfiguration*>(config);
                if(tconfig) {
                    config->setName(
                                QString("TCP Link %1:%2").arg(tconfig->address().toString()).arg((int)tconfig->port()));
809
                }
DonLakeFlyer's avatar
DonLakeFlyer committed
810
            }
811
                break;
dogmaphobic's avatar
dogmaphobic committed
812
#ifdef QGC_ENABLE_BLUETOOTH
dogmaphobic's avatar
dogmaphobic committed
813
            case LinkConfiguration::TypeBluetooth: {
DonLakeFlyer's avatar
DonLakeFlyer committed
814 815 816
                BluetoothConfiguration* tconfig = dynamic_cast<BluetoothConfiguration*>(config);
                if(tconfig) {
                    config->setName(QString("%1 (Bluetooth Device)").arg(tconfig->device().name));
dogmaphobic's avatar
dogmaphobic committed
817
                }
DonLakeFlyer's avatar
DonLakeFlyer committed
818
            }
dogmaphobic's avatar
dogmaphobic committed
819 820
                break;
#endif
dogmaphobic's avatar
dogmaphobic committed
821
#ifndef __mobile__
822
            case LinkConfiguration::TypeLogReplay: {
DonLakeFlyer's avatar
DonLakeFlyer committed
823 824 825
                LogReplayLinkConfiguration* tconfig = dynamic_cast<LogReplayLinkConfiguration*>(config);
                if(tconfig) {
                    config->setName(QString("Log Replay %1").arg(tconfig->logFilenameShort()));
826
                }
DonLakeFlyer's avatar
DonLakeFlyer committed
827
            }
828
                break;
dogmaphobic's avatar
dogmaphobic committed
829
#endif
830 831 832
#ifdef QT_DEBUG
            case LinkConfiguration::TypeMock:
                config->setName(
DonLakeFlyer's avatar
DonLakeFlyer committed
833
                            QString("Mock Link"));
834 835 836 837 838
                break;
#endif
            case LinkConfiguration::TypeLast:
            default:
                break;
DonLakeFlyer's avatar
DonLakeFlyer committed
839
            }
840
        }
DonLakeFlyer's avatar
DonLakeFlyer committed
841 842
    } else {
        qWarning() << "Internal error";
843 844 845 846 847
    }
}

void LinkManager::removeConfiguration(LinkConfiguration* config)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
848 849 850 851 852
    if (config) {
        LinkInterface* iface = config->link();
        if(iface) {
            disconnectLink(iface);
        }
853

DonLakeFlyer's avatar
DonLakeFlyer committed
854 855 856 857 858
        _removeConfiguration(config);
        saveLinkConfigurationList();
    } else {
        qWarning() << "Internal error";
    }
859
}
860

861 862
bool LinkManager::isAutoconnectLink(LinkInterface* link)
{
863 864 865 866 867 868
    for (int i=0; i<_sharedAutoconnectConfigurations.count(); i++) {
        if (_sharedAutoconnectConfigurations[i].data() == link->getLinkConfiguration()) {
            return true;
        }
    }
    return false;
869
}
dogmaphobic's avatar
dogmaphobic committed
870 871 872 873 874

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

Gus Grubba's avatar
Gus Grubba committed
876
#ifndef NO_SERIAL_LINK
Don Gagne's avatar
Don Gagne committed
877 878
void LinkManager::_activeLinkCheck(void)
{
879
    SerialLink* link = NULL;
Don Gagne's avatar
Don Gagne committed
880 881 882
    bool found = false;

    if (_activeLinkCheckList.count() != 0) {
883
        link = _activeLinkCheckList.takeFirst();
884
        if (containsLink(link) && link->isConnected()) {
Don Gagne's avatar
Don Gagne committed
885 886 887 888 889 890 891 892 893
            // 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;
                }
            }
894 895
        } else {
            link = NULL;
Don Gagne's avatar
Don Gagne committed
896 897 898 899 900 901 902
        }
    }

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

903 904 905
    if (!found && link) {
        // See if we can get an NSH prompt on this link
        bool foundNSHPrompt = false;
906
        link->writeBytesSafe("\r", 1);
907 908 909 910 911 912 913 914 915
        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 ?
916 917
                                  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
918 919
    }
}
Don Gagne's avatar
Don Gagne committed
920
#endif
921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962

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;
}
963 964 965 966 967 968 969 970 971 972 973

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

    for(int i = 0; i < _sharedConfigurations.count(); i++) {
        conf = _sharedConfigurations[i];
        if (conf->isAutoConnect())
            createConnectedLink(conf);
    }
}
974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995

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