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

10

pixhawk's avatar
pixhawk committed
11 12 13 14 15 16 17 18 19 20
/**
 * @file
 *   @brief Brief Description
 *
 *   @author Lorenz Meier <mavteam@student.ethz.ch>
 *
 */

#include <QList>
#include <QApplication>
21
#include <QDebug>
22
#include <QSignalSpy>
dogmaphobic's avatar
dogmaphobic committed
23

Gus Grubba's avatar
Gus Grubba committed
24
#ifndef NO_SERIAL_LINK
Don Gagne's avatar
Don Gagne committed
25
#include "QGCSerialPortInfo.h"
dogmaphobic's avatar
dogmaphobic committed
26
#endif
27

28
#include "LinkManager.h"
29
#include "QGCApplication.h"
Don Gagne's avatar
Don Gagne committed
30 31
#include "UDPLink.h"
#include "TCPLink.h"
dogmaphobic's avatar
dogmaphobic committed
32
#ifdef QGC_ENABLE_BLUETOOTH
dogmaphobic's avatar
dogmaphobic committed
33 34
#include "BluetoothLink.h"
#endif
35

Don Gagne's avatar
Don Gagne committed
36 37 38 39
#ifndef __mobile__
    #include "GPSManager.h"
#endif

Don Gagne's avatar
Don Gagne committed
40
QGC_LOGGING_CATEGORY(LinkManagerLog, "LinkManagerLog")
Don Gagne's avatar
Don Gagne committed
41 42
QGC_LOGGING_CATEGORY(LinkManagerVerboseLog, "LinkManagerVerboseLog")

43 44 45 46 47 48 49 50
const char* LinkManager::_settingsGroup =            "LinkManager";
const char* LinkManager::_autoconnectUDPKey =        "AutoconnectUDP";
const char* LinkManager::_autoconnectPixhawkKey =    "AutoconnectPixhawk";
const char* LinkManager::_autoconnect3DRRadioKey =   "Autoconnect3DRRadio";
const char* LinkManager::_autoconnectPX4FlowKey =    "AutoconnectPX4Flow";
const char* LinkManager::_autoconnectRTKGPSKey =     "AutoconnectRTKGPS";
const char* LinkManager::_autoconnectLibrePilotKey = "AutoconnectLibrePilot";
const char* LinkManager::_defaultUPDLinkName =       "Default UDP Link";
51

52 53 54 55 56 57 58 59
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

60 61
LinkManager::LinkManager(QGCApplication* app)
    : QGCTool(app)
62 63 64
    , _configUpdateSuspended(false)
    , _configurationsLoaded(false)
    , _connectionsSuspended(false)
65
    , _mavlinkChannelsUsedBitMask(1)    // We never use channel 0 to avoid sequence numbering problems
66
    , _mavlinkProtocol(NULL)
Don Gagne's avatar
Don Gagne committed
67 68 69 70
    , _autoconnectUDP(true)
    , _autoconnectPixhawk(true)
    , _autoconnect3DRRadio(true)
    , _autoconnectPX4Flow(true)
Don Gagne's avatar
Don Gagne committed
71
    , _autoconnectRTKGPS(true)
72
    , _autoconnectLibrePilot(true)
pixhawk's avatar
pixhawk committed
73
{
Don Gagne's avatar
Don Gagne committed
74 75 76 77 78
    qmlRegisterUncreatableType<LinkManager>         ("QGroundControl", 1, 0, "LinkManager",         "Reference only");
    qmlRegisterUncreatableType<LinkConfiguration>   ("QGroundControl", 1, 0, "LinkConfiguration",   "Reference only");
    qmlRegisterUncreatableType<LinkInterface>       ("QGroundControl", 1, 0, "LinkInterface",       "Reference only");

    QSettings settings;
79

Don Gagne's avatar
Don Gagne committed
80
    settings.beginGroup(_settingsGroup);
81 82 83 84 85 86
    _autoconnectUDP =        settings.value(_autoconnectUDPKey, true).toBool();
    _autoconnectPixhawk =    settings.value(_autoconnectPixhawkKey, true).toBool();
    _autoconnect3DRRadio =   settings.value(_autoconnect3DRRadioKey, true).toBool();
    _autoconnectPX4Flow =    settings.value(_autoconnectPX4FlowKey, true).toBool();
    _autoconnectRTKGPS =     settings.value(_autoconnectRTKGPSKey, true).toBool();
    _autoconnectLibrePilot = settings.value(_autoconnectLibrePilotKey, true).toBool();
Don Gagne's avatar
Don Gagne committed
87

Gus Grubba's avatar
Gus Grubba committed
88
#ifndef NO_SERIAL_LINK
Don Gagne's avatar
Don Gagne committed
89 90 91
    _activeLinkCheckTimer.setInterval(_activeLinkCheckTimeoutMSecs);
    _activeLinkCheckTimer.setSingleShot(false);
    connect(&_activeLinkCheckTimer, &QTimer::timeout, this, &LinkManager::_activeLinkCheck);
Don Gagne's avatar
Don Gagne committed
92
#endif
pixhawk's avatar
pixhawk committed
93 94 95 96
}

LinkManager::~LinkManager()
{
97

pixhawk's avatar
pixhawk committed
98 99
}

100 101 102 103 104 105
void LinkManager::setToolbox(QGCToolbox *toolbox)
{
   QGCTool::setToolbox(toolbox);

   _mavlinkProtocol = _toolbox->mavlinkProtocol();

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

109 110
}

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

Don Gagne's avatar
Don Gagne committed
164
LinkInterface* LinkManager::createConnectedLink(const QString& name)
165 166 167
{
    Q_ASSERT(name.isEmpty() == false);
    for(int i = 0; i < _linkConfigurations.count(); i++) {
Don Gagne's avatar
Don Gagne committed
168
        LinkConfiguration* conf = _linkConfigurations.value<LinkConfiguration*>(i);
169
        if(conf && conf->name() == name)
Don Gagne's avatar
Don Gagne committed
170
            return createConnectedLink(conf);
171 172 173 174
    }
    return NULL;
}

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

Don Gagne's avatar
Don Gagne committed
182 183 184
    if (!link) {
        return;
    }
185

Don Gagne's avatar
Don Gagne committed
186
    if (!_links.contains(link)) {
187 188
        bool channelSet = false;

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

204 205 206 207
        if (!channelSet) {
            qWarning() << "Ran out of mavlink channels";
        }

Don Gagne's avatar
Don Gagne committed
208
        _links.append(link);
209 210
        emit newLink(link);
    }
211

Don Gagne's avatar
Don Gagne committed
212 213
    connect(link, &LinkInterface::communicationError,   _app,               &QGCApplication::criticalMessageBoxOnMainThread);
    connect(link, &LinkInterface::bytesReceived,        _mavlinkProtocol,   &MAVLinkProtocol::receiveBytes);
214

215
    _mavlinkProtocol->resetMetadataForLink(link);
216

217 218 219 220 221 222
    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);
223
}
pixhawk's avatar
pixhawk committed
224

Don Gagne's avatar
Don Gagne committed
225
void LinkManager::disconnectAll(void)
pixhawk's avatar
pixhawk committed
226
{
Don Gagne's avatar
Don Gagne committed
227 228
    // Walk list in reverse order to preserve indices during delete
    for (int i=_links.count()-1; i>=0; i--) {
Don Gagne's avatar
Don Gagne committed
229
        disconnectLink(_links.value<LinkInterface*>(i));
230
    }
pixhawk's avatar
pixhawk committed
231 232 233 234
}

bool LinkManager::connectLink(LinkInterface* link)
{
235
    Q_ASSERT(link);
236

237 238 239 240
    if (_connectionsSuspendedMsg()) {
        return false;
    }

241
    return link->_connect();
pixhawk's avatar
pixhawk committed
242 243
}

Don Gagne's avatar
Don Gagne committed
244
void LinkManager::disconnectLink(LinkInterface* link)
pixhawk's avatar
pixhawk committed
245
{
246 247 248
    if (!link || !_links.contains(link)) {
        return;
    }
Don Gagne's avatar
Don Gagne committed
249

Don Gagne's avatar
Don Gagne committed
250 251
    link->_disconnect();
    LinkConfiguration* config = link->getLinkConfiguration();
252 253 254 255
    if (config) {
        if (_autoconnectConfigurations.contains(config)) {
            config->setLink(NULL);
        }
256
    }
Don Gagne's avatar
Don Gagne committed
257
    _deleteLink(link);
258
    if (_autoconnectConfigurations.contains(config)) {
259
        qCDebug(LinkManagerLog) << "Removing disconnected autoconnect config" << config->name();
260 261 262
        _autoconnectConfigurations.removeOne(config);
        delete config;
    }
pixhawk's avatar
pixhawk committed
263 264
}

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

    if (!link) {
        return;
    }
275

276
    // Free up the mavlink channel associated with this link
277
    _mavlinkChannelsUsedBitMask &= ~(1 << link->mavlinkChannel());
278

Don Gagne's avatar
Don Gagne committed
279 280
    _links.removeOne(link);
    delete link;
281

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

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

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

305 306 307 308 309 310 311 312 313
void LinkManager::_linkConnected(void)
{
    emit linkConnected((LinkInterface*)sender());
}

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

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

321 322 323 324 325 326 327 328 329
void LinkManager::suspendConfigurationUpdates(bool suspend)
{
    _configUpdateSuspended = suspend;
}

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

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

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

Gus Grubba's avatar
Gus Grubba committed
446
#ifndef NO_SERIAL_LINK
Don Gagne's avatar
Don Gagne committed
447
SerialConfiguration* LinkManager::_autoconnectConfigurationsContainsPort(const QString& portName)
448 449
{
    QString searchPort = portName.trimmed();
Don Gagne's avatar
Don Gagne committed
450 451 452 453 454 455 456

    for (int i=0; i<_autoconnectConfigurations.count(); i++) {
        SerialConfiguration* linkConfig = _autoconnectConfigurations.value<SerialConfiguration*>(i);

        if (linkConfig) {
            if (linkConfig->portName() == searchPort) {
                return linkConfig;
457
            }
Don Gagne's avatar
Don Gagne committed
458 459
        } else {
            qWarning() << "Internal error";
460 461 462 463
        }
    }
    return NULL;
}
dogmaphobic's avatar
dogmaphobic committed
464
#endif
465

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

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

Gus Grubba's avatar
Gus Grubba committed
491
#ifndef NO_SERIAL_LINK
dogmaphobic's avatar
dogmaphobic committed
492
    QStringList currentPorts;
Don Gagne's avatar
Don Gagne committed
493
    QList<QGCSerialPortInfo> portList = QGCSerialPortInfo::availablePorts();
Don Gagne's avatar
Don Gagne committed
494

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

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

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

518 519 520 521 522 523 524
            if (_autoconnectConfigurationsContainsPort(portInfo.systemLocation())) {
                qCDebug(LinkManagerVerboseLog) << "Skipping existing autoconnect" << portInfo.systemLocation();
            } else if (!_autoconnectWaitList.contains(portInfo.systemLocation())) {
                // We don't connect to the port the first time we see it. The ability to correctly detect whether we
                // are in the bootloader is flaky from a cross-platform standpoint. So by putting it on a wait list
                // and only connect on the second pass we leave enough time for the board to boot up.
                qCDebug(LinkManagerLog) << "Waiting for next autoconnect pass" << portInfo.systemLocation();
525 526
                _autoconnectWaitList[portInfo.systemLocation()] = 1;
            } else if (++_autoconnectWaitList[portInfo.systemLocation()] * _autoconnectUpdateTimerMSecs > _autoconnectConnectDelayMSecs) {
Don Gagne's avatar
Don Gagne committed
527 528
                SerialConfiguration* pSerialConfig = NULL;

529
                _autoconnectWaitList.remove(portInfo.systemLocation());
530

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

Don Gagne's avatar
Don Gagne committed
592 593
                if (pSerialConfig) {
                    qCDebug(LinkManagerLog) << "New auto-connect port added: " << pSerialConfig->name() << portInfo.systemLocation();
Don Gagne's avatar
Don Gagne committed
594
                    pSerialConfig->setBaud(boardType == QGCSerialPortInfo::BoardTypeSikRadio ? 57600 : 115200);
Don Gagne's avatar
Don Gagne committed
595 596 597
                    pSerialConfig->setDynamic(true);
                    pSerialConfig->setPortName(portInfo.systemLocation());
                    _autoconnectConfigurations.append(pSerialConfig);
Don Gagne's avatar
Don Gagne committed
598
                    createConnectedLink(pSerialConfig);
Don Gagne's avatar
Don Gagne committed
599
                }
dogmaphobic's avatar
dogmaphobic committed
600 601 602
            }
        }
    }
Don Gagne's avatar
Don Gagne committed
603

dogmaphobic's avatar
dogmaphobic committed
604 605
    // Now we go through the current configuration list and make sure any dynamic config has gone away
    QList<LinkConfiguration*>  _confToDelete;
Don Gagne's avatar
Don Gagne committed
606 607 608 609
    for (int i=0; i<_autoconnectConfigurations.count(); i++) {
        SerialConfiguration* linkConfig = _autoconnectConfigurations.value<SerialConfiguration*>(i);
        if (linkConfig) {
            if (!currentPorts.contains(linkConfig->portName())) {
610 611 612 613 614 615 616 617 618
                if (linkConfig->link()) {
                    if (linkConfig->link()->isConnected()) {
                        if (linkConfig->link()->active()) {
                            // 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
619
                }
620
                _confToDelete.append(linkConfig);
dogmaphobic's avatar
dogmaphobic committed
621
            }
Don Gagne's avatar
Don Gagne committed
622 623
        } else {
            qWarning() << "Internal error";
dogmaphobic's avatar
dogmaphobic committed
624 625
        }
    }
Don Gagne's avatar
Don Gagne committed
626

Don Gagne's avatar
Don Gagne committed
627
    // Now remove all configs that are gone
Don Gagne's avatar
Don Gagne committed
628
    foreach (LinkConfiguration* pDeleteConfig, _confToDelete) {
Don Gagne's avatar
Don Gagne committed
629
        qCDebug(LinkManagerLog) << "Removing unused autoconnect config" << pDeleteConfig->name();
Don Gagne's avatar
Don Gagne committed
630
        _autoconnectConfigurations.removeOne(pDeleteConfig);
631 632 633
        if (pDeleteConfig->link()) {
            disconnectLink(pDeleteConfig->link());
        }
Don Gagne's avatar
Don Gagne committed
634
        delete pDeleteConfig;
635
    }
Gus Grubba's avatar
Gus Grubba committed
636
#endif // NO_SERIAL_LINK
637 638
}

Don Gagne's avatar
Don Gagne committed
639 640 641
void LinkManager::shutdown(void)
{
    setConnectionsSuspended("Shutdown");
Don Gagne's avatar
Don Gagne committed
642
    disconnectAll();
Don Gagne's avatar
Don Gagne committed
643 644
}

Don Gagne's avatar
Don Gagne committed
645
bool LinkManager::_setAutoconnectWorker(bool& currentAutoconnect, bool newAutoconnect, const char* autoconnectKey)
Don Gagne's avatar
Don Gagne committed
646
{
Don Gagne's avatar
Don Gagne committed
647
    if (currentAutoconnect != newAutoconnect) {
Don Gagne's avatar
Don Gagne committed
648 649 650
        QSettings settings;

        settings.beginGroup(_settingsGroup);
Don Gagne's avatar
Don Gagne committed
651 652 653 654 655 656 657
        settings.setValue(autoconnectKey, newAutoconnect);
        currentAutoconnect = newAutoconnect;
        return true;
    }

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

Don Gagne's avatar
Don Gagne committed
659 660 661
void LinkManager::setAutoconnectUDP(bool autoconnect)
{
    if (_setAutoconnectWorker(_autoconnectUDP, autoconnect, _autoconnectUDPKey)) {
Don Gagne's avatar
Don Gagne committed
662
        emit autoconnectUDPChanged(autoconnect);
663
    }
Don Gagne's avatar
Don Gagne committed
664 665 666 667
}

void LinkManager::setAutoconnectPixhawk(bool autoconnect)
{
Don Gagne's avatar
Don Gagne committed
668
    if (_setAutoconnectWorker(_autoconnectPixhawk, autoconnect, _autoconnectPixhawkKey)) {
Don Gagne's avatar
Don Gagne committed
669 670 671 672 673 674
        emit autoconnectPixhawkChanged(autoconnect);
    }
}

void LinkManager::setAutoconnect3DRRadio(bool autoconnect)
{
Don Gagne's avatar
Don Gagne committed
675
    if (_setAutoconnectWorker(_autoconnect3DRRadio, autoconnect, _autoconnect3DRRadioKey)) {
Don Gagne's avatar
Don Gagne committed
676 677 678 679 680 681
        emit autoconnect3DRRadioChanged(autoconnect);
    }
}

void LinkManager::setAutoconnectPX4Flow(bool autoconnect)
{
Don Gagne's avatar
Don Gagne committed
682
    if (_setAutoconnectWorker(_autoconnectPX4Flow, autoconnect, _autoconnectPX4FlowKey)) {
Don Gagne's avatar
Don Gagne committed
683 684
        emit autoconnectPX4FlowChanged(autoconnect);
    }
Don Gagne's avatar
Don Gagne committed
685
}
Don Gagne's avatar
Don Gagne committed
686

Don Gagne's avatar
Don Gagne committed
687 688 689 690 691
void LinkManager::setAutoconnectRTKGPS(bool autoconnect)
{
    if (_setAutoconnectWorker(_autoconnectRTKGPS, autoconnect, _autoconnectRTKGPSKey)) {
        emit autoconnectRTKGPSChanged(autoconnect);
    }
692
}
693

694 695 696 697 698 699 700
void LinkManager::setAutoconnectLibrePilot(bool autoconnect)
{
    if (_setAutoconnectWorker(_autoconnectLibrePilot, autoconnect, _autoconnectLibrePilotKey)) {
        emit autoconnectLibrePilotChanged(autoconnect);
    }
}

701 702 703 704 705 706
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
707
#ifndef NO_SERIAL_LINK
708 709 710 711
        list += "Serial";
#endif
        list += "UDP";
        list += "TCP";
dogmaphobic's avatar
dogmaphobic committed
712
#ifdef QGC_ENABLE_BLUETOOTH
dogmaphobic's avatar
dogmaphobic committed
713 714
        list += "Bluetooth";
#endif
dogmaphobic's avatar
dogmaphobic committed
715
#ifdef QT_DEBUG
716
        list += "Mock Link";
dogmaphobic's avatar
dogmaphobic committed
717 718
#endif
#ifndef __mobile__
719
        list += "Log Replay";
dogmaphobic's avatar
dogmaphobic committed
720
#endif
dogmaphobic's avatar
dogmaphobic committed
721
        Q_ASSERT(list.size() == (int)LinkConfiguration::TypeLast);
722 723 724 725
    }
    return list;
}

726
void LinkManager::_updateSerialPorts()
727
{
728 729
    _commPortList.clear();
    _commPortDisplayList.clear();
Gus Grubba's avatar
Gus Grubba committed
730
#ifndef NO_SERIAL_LINK
731 732
    QList<QSerialPortInfo> portList = QSerialPortInfo::availablePorts();
    foreach (const QSerialPortInfo &info, portList)
733
    {
734 735 736
        QString port = info.systemLocation().trimmed();
        _commPortList += port;
        _commPortDisplayList += SerialConfiguration::cleanPortDisplayname(port);
737 738
    }
#endif
739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755
}

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

QStringList LinkManager::serialPorts(void)
{
    if(!_commPortList.size())
    {
        _updateSerialPorts();
    }
756 757 758 759 760
    return _commPortList;
}

QStringList LinkManager::serialBaudRates(void)
{
Gus Grubba's avatar
Gus Grubba committed
761
#ifdef NO_SERIAL_LINK
762 763 764 765 766 767
    QStringList foo;
    return foo;
#else
    return SerialConfiguration::supportedBaudRates();
#endif
}
768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793

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

bool LinkManager::endCreateConfiguration(LinkConfiguration* config)
{
    Q_ASSERT(config != NULL);
    _fixUnnamed(config);
    _linkConfigurations.append(config);
    saveLinkConfigurationList();
    return true;
}

LinkConfiguration* LinkManager::createConfiguration(int type, const QString& name)
{
Gus Grubba's avatar
Gus Grubba committed
794
#ifndef NO_SERIAL_LINK
795 796
    if((LinkConfiguration::LinkType)type == LinkConfiguration::TypeSerial)
        _updateSerialPorts();
dogmaphobic's avatar
dogmaphobic committed
797
#endif
798 799 800 801 802 803
    return LinkConfiguration::createSettings(type, name);
}

LinkConfiguration* LinkManager::startConfigurationEditing(LinkConfiguration* config)
{
    Q_ASSERT(config != NULL);
Gus Grubba's avatar
Gus Grubba committed
804
#ifndef NO_SERIAL_LINK
805 806
    if(config->type() == LinkConfiguration::TypeSerial)
        _updateSerialPorts();
dogmaphobic's avatar
dogmaphobic committed
807
#endif
808 809 810 811 812 813 814 815 816 817
    return LinkConfiguration::duplicateSettings(config);
}


void LinkManager::_fixUnnamed(LinkConfiguration* config)
{
    Q_ASSERT(config != NULL);
    //-- Check for "Unnamed"
    if (config->name() == "Unnamed") {
        switch(config->type()) {
Gus Grubba's avatar
Gus Grubba committed
818
#ifndef NO_SERIAL_LINK
819 820
            case LinkConfiguration::TypeSerial: {
                QString tname = dynamic_cast<SerialConfiguration*>(config)->portName();
821
#ifdef Q_OS_WIN
822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842
                tname.replace("\\\\.\\", "");
#else
                tname.replace("/dev/cu.", "");
                tname.replace("/dev/", "");
#endif
                config->setName(QString("Serial Device on %1").arg(tname));
                break;
                }
#endif
            case LinkConfiguration::TypeUdp:
                config->setName(
                    QString("UDP Link on Port %1").arg(dynamic_cast<UDPConfiguration*>(config)->localPort()));
                break;
            case LinkConfiguration::TypeTcp: {
                    TCPConfiguration* tconfig = dynamic_cast<TCPConfiguration*>(config);
                    if(tconfig) {
                        config->setName(
                            QString("TCP Link %1:%2").arg(tconfig->address().toString()).arg((int)tconfig->port()));
                    }
                }
                break;
dogmaphobic's avatar
dogmaphobic committed
843
#ifdef QGC_ENABLE_BLUETOOTH
dogmaphobic's avatar
dogmaphobic committed
844 845 846
            case LinkConfiguration::TypeBluetooth: {
                    BluetoothConfiguration* tconfig = dynamic_cast<BluetoothConfiguration*>(config);
                    if(tconfig) {
847
                        config->setName(QString("%1 (Bluetooth Device)").arg(tconfig->device().name));
dogmaphobic's avatar
dogmaphobic committed
848 849 850 851
                    }
                }
                break;
#endif
dogmaphobic's avatar
dogmaphobic committed
852
#ifndef __mobile__
853
            case LinkConfiguration::TypeLogReplay: {
dogmaphobic's avatar
dogmaphobic committed
854 855 856 857
                    LogReplayLinkConfiguration* tconfig = dynamic_cast<LogReplayLinkConfiguration*>(config);
                    if(tconfig) {
                        config->setName(QString("Log Replay %1").arg(tconfig->logFilenameShort()));
                    }
858 859
                }
                break;
dogmaphobic's avatar
dogmaphobic committed
860
#endif
861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886
#ifdef QT_DEBUG
            case LinkConfiguration::TypeMock:
                config->setName(
                    QString("Mock Link"));
                break;
#endif
            case LinkConfiguration::TypeLast:
            default:
                break;
        }
    }
}

void LinkManager::removeConfiguration(LinkConfiguration* config)
{
    Q_ASSERT(config != NULL);
    LinkInterface* iface = config->link();
    if(iface) {
        disconnectLink(iface);
    }
    // Remove configuration
    _linkConfigurations.removeOne(config);
    delete config;
    // Save list
    saveLinkConfigurationList();
}
887

888 889 890 891
bool LinkManager::isAutoconnectLink(LinkInterface* link)
{
    return _autoconnectConfigurations.contains(link->getLinkConfiguration());
}
dogmaphobic's avatar
dogmaphobic committed
892 893 894 895 896

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

Gus Grubba's avatar
Gus Grubba committed
898
#ifndef NO_SERIAL_LINK
Don Gagne's avatar
Don Gagne committed
899 900
void LinkManager::_activeLinkCheck(void)
{
901
    SerialLink* link = NULL;
Don Gagne's avatar
Don Gagne committed
902 903 904
    bool found = false;

    if (_activeLinkCheckList.count() != 0) {
905
        link = _activeLinkCheckList.takeFirst();
Don Gagne's avatar
Don Gagne committed
906 907 908 909 910 911 912 913 914 915
        if (_links.contains(link) && link->isConnected()) {
            // 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;
                }
            }
916 917
        } else {
            link = NULL;
Don Gagne's avatar
Don Gagne committed
918 919 920 921 922 923 924
        }
    }

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

925 926 927
    if (!found && link) {
        // See if we can get an NSH prompt on this link
        bool foundNSHPrompt = false;
928
        link->writeBytesSafe("\r", 1);
929 930 931 932 933 934 935 936 937 938 939
        QSignalSpy spy(link, SIGNAL(bytesReceived(LinkInterface*, QByteArray)));
        if (spy.wait(100)) {
            QList<QVariant> arguments = spy.takeFirst();
            if (arguments[1].value<QByteArray>().contains("nsh>")) {
                foundNSHPrompt = true;
            }
        }

        qgcApp()->showMessage(foundNSHPrompt ?
                                  QStringLiteral("Please check to make sure you have an SD Card inserted in your Vehicle and try again.") :
                                  QStringLiteral("Your Vehicle is not responding. If this continues shutdown QGroundControl, restart the Vehicle letting it boot completely, then start QGroundControl."));
Don Gagne's avatar
Don Gagne committed
940 941
    }
}
Don Gagne's avatar
Don Gagne committed
942
#endif