LinkManager.cc 31.7 KB
Newer Older
pixhawk's avatar
pixhawk committed
1
/*=====================================================================
lm's avatar
lm committed
2 3 4

QGroundControl Open Source Ground Control Station

5
(c) 2009, 2015 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
lm's avatar
lm committed
6 7 8 9

This file is part of the QGROUNDCONTROL project

    QGROUNDCONTROL is free software: you can redistribute it and/or modify
pixhawk's avatar
pixhawk committed
10 11 12
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
lm's avatar
lm committed
13 14

    QGROUNDCONTROL is distributed in the hope that it will be useful,
pixhawk's avatar
pixhawk committed
15 16 17
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
lm's avatar
lm committed
18

pixhawk's avatar
pixhawk committed
19
    You should have received a copy of the GNU General Public License
lm's avatar
lm committed
20 21
    along with QGROUNDCONTROL. If not, see <http://www.gnu.org/licenses/>.

pixhawk's avatar
pixhawk committed
22
======================================================================*/
23

pixhawk's avatar
pixhawk committed
24 25 26 27 28 29 30 31 32 33
/**
 * @file
 *   @brief Brief Description
 *
 *   @author Lorenz Meier <mavteam@student.ethz.ch>
 *
 */

#include <QList>
#include <QApplication>
34
#include <QDebug>
35
#include <QSignalSpy>
dogmaphobic's avatar
dogmaphobic committed
36 37

#ifndef __ios__
Don Gagne's avatar
Don Gagne committed
38
#include "QGCSerialPortInfo.h"
dogmaphobic's avatar
dogmaphobic committed
39
#endif
40

41
#include "LinkManager.h"
42
#include "QGCApplication.h"
Don Gagne's avatar
Don Gagne committed
43 44
#include "UDPLink.h"
#include "TCPLink.h"
dogmaphobic's avatar
dogmaphobic committed
45
#ifdef QGC_ENABLE_BLUETOOTH
dogmaphobic's avatar
dogmaphobic committed
46 47
#include "BluetoothLink.h"
#endif
48

Don Gagne's avatar
Don Gagne committed
49 50 51 52
#ifndef __mobile__
    #include "GPSManager.h"
#endif

Don Gagne's avatar
Don Gagne committed
53
QGC_LOGGING_CATEGORY(LinkManagerLog, "LinkManagerLog")
Don Gagne's avatar
Don Gagne committed
54 55 56 57 58 59 60
QGC_LOGGING_CATEGORY(LinkManagerVerboseLog, "LinkManagerVerboseLog")

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";
Don Gagne's avatar
Don Gagne committed
61
const char* LinkManager::_autoconnectRTKGPSKey =    "AutoconnectRTKGPS";
Don Gagne's avatar
Don Gagne committed
62
const char* LinkManager::_defaultUPDLinkName =      "Default UDP Link";
63

64 65 66 67 68 69 70 71
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

72 73
LinkManager::LinkManager(QGCApplication* app)
    : QGCTool(app)
74 75 76
    , _configUpdateSuspended(false)
    , _configurationsLoaded(false)
    , _connectionsSuspended(false)
77
    , _mavlinkChannelsUsedBitMask(1)    // We never use channel 0 to avoid sequence numbering problems
78
    , _mavlinkProtocol(NULL)
Don Gagne's avatar
Don Gagne committed
79 80 81 82
    , _autoconnectUDP(true)
    , _autoconnectPixhawk(true)
    , _autoconnect3DRRadio(true)
    , _autoconnectPX4Flow(true)
Don Gagne's avatar
Don Gagne committed
83
    , _autoconnectRTKGPS(true)
pixhawk's avatar
pixhawk committed
84
{
Don Gagne's avatar
Don Gagne committed
85 86 87 88 89
    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;
90

Don Gagne's avatar
Don Gagne committed
91 92 93 94 95
    settings.beginGroup(_settingsGroup);
    _autoconnectUDP =       settings.value(_autoconnectUDPKey, true).toBool();
    _autoconnectPixhawk =   settings.value(_autoconnectPixhawkKey, true).toBool();
    _autoconnect3DRRadio =  settings.value(_autoconnect3DRRadioKey, true).toBool();
    _autoconnectPX4Flow =   settings.value(_autoconnectPX4FlowKey, true).toBool();
Don Gagne's avatar
Don Gagne committed
96
    _autoconnectRTKGPS =    settings.value(_autoconnectRTKGPSKey, true).toBool();
Don Gagne's avatar
Don Gagne committed
97

Don Gagne's avatar
Don Gagne committed
98
#ifndef __ios__
Don Gagne's avatar
Don Gagne committed
99 100 101
    _activeLinkCheckTimer.setInterval(_activeLinkCheckTimeoutMSecs);
    _activeLinkCheckTimer.setSingleShot(false);
    connect(&_activeLinkCheckTimer, &QTimer::timeout, this, &LinkManager::_activeLinkCheck);
Don Gagne's avatar
Don Gagne committed
102
#endif
pixhawk's avatar
pixhawk committed
103 104 105 106
}

LinkManager::~LinkManager()
{
107

pixhawk's avatar
pixhawk committed
108 109
}

110 111 112 113 114 115
void LinkManager::setToolbox(QGCToolbox *toolbox)
{
   QGCTool::setToolbox(toolbox);

   _mavlinkProtocol = _toolbox->mavlinkProtocol();

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

119 120
}

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

Don Gagne's avatar
Don Gagne committed
174
LinkInterface* LinkManager::createConnectedLink(const QString& name)
175 176 177
{
    Q_ASSERT(name.isEmpty() == false);
    for(int i = 0; i < _linkConfigurations.count(); i++) {
Don Gagne's avatar
Don Gagne committed
178
        LinkConfiguration* conf = _linkConfigurations.value<LinkConfiguration*>(i);
179
        if(conf && conf->name() == name)
Don Gagne's avatar
Don Gagne committed
180
            return createConnectedLink(conf);
181 182 183 184
    }
    return NULL;
}

185
void LinkManager::_addLink(LinkInterface* link)
pixhawk's avatar
pixhawk committed
186
{
Don Gagne's avatar
Don Gagne committed
187 188 189 190
    if (thread() != QThread::currentThread()) {
        qWarning() << "_deleteLink called from incorrect thread";
        return;
    }
191

Don Gagne's avatar
Don Gagne committed
192 193 194
    if (!link) {
        return;
    }
195

Don Gagne's avatar
Don Gagne committed
196
    if (!_links.contains(link)) {
197 198
        bool channelSet = false;

199 200
        // Find a mavlink channel to use for this link
        for (int i=0; i<32; i++) {
201
            if (!(_mavlinkChannelsUsedBitMask & 1 << i)) {
202
                mavlink_reset_channel_status(i);
203 204
                link->_setMavlinkChannel(i);
                _mavlinkChannelsUsedBitMask |= i << i;
205
                channelSet = true;
206 207 208
                break;
            }
        }
209

210 211 212 213
        if (!channelSet) {
            qWarning() << "Ran out of mavlink channels";
        }

Don Gagne's avatar
Don Gagne committed
214
        _links.append(link);
215 216
        emit newLink(link);
    }
217

Don Gagne's avatar
Don Gagne committed
218 219
    connect(link, &LinkInterface::communicationError,   _app,               &QGCApplication::criticalMessageBoxOnMainThread);
    connect(link, &LinkInterface::bytesReceived,        _mavlinkProtocol,   &MAVLinkProtocol::receiveBytes);
220

221
    _mavlinkProtocol->resetMetadataForLink(link);
222

223 224 225 226 227 228
    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);
229
}
pixhawk's avatar
pixhawk committed
230

Don Gagne's avatar
Don Gagne committed
231
void LinkManager::disconnectAll(void)
pixhawk's avatar
pixhawk committed
232
{
Don Gagne's avatar
Don Gagne committed
233 234
    // 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
235
        disconnectLink(_links.value<LinkInterface*>(i));
236
    }
pixhawk's avatar
pixhawk committed
237 238 239 240
}

bool LinkManager::connectLink(LinkInterface* link)
{
241
    Q_ASSERT(link);
242

243 244 245 246
    if (_connectionsSuspendedMsg()) {
        return false;
    }

247
    return link->_connect();
pixhawk's avatar
pixhawk committed
248 249
}

Don Gagne's avatar
Don Gagne committed
250
void LinkManager::disconnectLink(LinkInterface* link)
pixhawk's avatar
pixhawk committed
251
{
252 253 254
    if (!link || !_links.contains(link)) {
        return;
    }
Don Gagne's avatar
Don Gagne committed
255

Don Gagne's avatar
Don Gagne committed
256 257
    link->_disconnect();
    LinkConfiguration* config = link->getLinkConfiguration();
258 259 260 261
    if (config) {
        if (_autoconnectConfigurations.contains(config)) {
            config->setLink(NULL);
        }
262
    }
Don Gagne's avatar
Don Gagne committed
263
    _deleteLink(link);
264
    if (_autoconnectConfigurations.contains(config)) {
265
        qCDebug(LinkManagerLog) << "Removing disconnected autoconnect config" << config->name();
266 267 268
        _autoconnectConfigurations.removeOne(config);
        delete config;
    }
pixhawk's avatar
pixhawk committed
269 270
}

271
void LinkManager::_deleteLink(LinkInterface* link)
272
{
Don Gagne's avatar
Don Gagne committed
273 274 275 276 277 278 279 280
    if (thread() != QThread::currentThread()) {
        qWarning() << "_deleteLink called from incorrect thread";
        return;
    }

    if (!link) {
        return;
    }
281

282 283
    // Free up the mavlink channel associated with this link
    _mavlinkChannelsUsedBitMask &= ~(1 << link->getMavlinkChannel());
284

Don Gagne's avatar
Don Gagne committed
285 286
    _links.removeOne(link);
    delete link;
287

Don Gagne's avatar
Don Gagne committed
288
    // Emit removal of link
289
    emit linkDeleted(link);
pixhawk's avatar
pixhawk committed
290 291
}

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

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

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

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

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

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

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

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

    if(linksChanged) {
446
        emit linkConfigurationsChanged();
447 448
    }
    // Enable automatic Serial PX4/3DR Radio hunting
449 450 451
    _configurationsLoaded = true;
}

dogmaphobic's avatar
dogmaphobic committed
452
#ifndef __ios__
Don Gagne's avatar
Don Gagne committed
453
SerialConfiguration* LinkManager::_autoconnectConfigurationsContainsPort(const QString& portName)
454 455
{
    QString searchPort = portName.trimmed();
Don Gagne's avatar
Don Gagne committed
456 457 458 459 460 461 462

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

        if (linkConfig) {
            if (linkConfig->portName() == searchPort) {
                return linkConfig;
463
            }
Don Gagne's avatar
Don Gagne committed
464 465
        } else {
            qWarning() << "Internal error";
466 467 468 469
        }
    }
    return NULL;
}
dogmaphobic's avatar
dogmaphobic committed
470
#endif
471

Don Gagne's avatar
Don Gagne committed
472
void LinkManager::_updateAutoConnectLinks(void)
473
{
Don Gagne's avatar
Don Gagne committed
474
    if (_connectionsSuspended || qgcApp()->runningUnitTests()) {
475 476
        return;
    }
Don Gagne's avatar
Don Gagne committed
477

Don Gagne's avatar
Don Gagne committed
478 479 480 481 482 483 484 485 486
    // 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
487
    if (!foundUDP && _autoconnectUDP) {
Don Gagne's avatar
Don Gagne committed
488
        qCDebug(LinkManagerLog) << "New auto-connect UDP port added";
Don Gagne's avatar
Don Gagne committed
489 490 491
        UDPConfiguration* udpConfig = new UDPConfiguration(_defaultUPDLinkName);
        udpConfig->setLocalPort(QGC_UDP_LOCAL_PORT);
        udpConfig->setDynamic(true);
492
        _linkConfigurations.append(udpConfig);
Don Gagne's avatar
Don Gagne committed
493
        createConnectedLink(udpConfig);
494
        emit linkConfigurationsChanged();
Don Gagne's avatar
Don Gagne committed
495 496
    }

497
#ifndef __ios__
dogmaphobic's avatar
dogmaphobic committed
498
    QStringList currentPorts;
Don Gagne's avatar
Don Gagne committed
499
    QList<QGCSerialPortInfo> portList = QGCSerialPortInfo::availablePorts();
Don Gagne's avatar
Don Gagne committed
500

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

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

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

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

535
                _autoconnectWaitList.remove(portInfo.systemLocation());
536

Don Gagne's avatar
Don Gagne committed
537 538 539
                switch (boardType) {
                case QGCSerialPortInfo::BoardTypePX4FMUV1:
                case QGCSerialPortInfo::BoardTypePX4FMUV2:
540
                case QGCSerialPortInfo::BoardTypePX4FMUV4:
Don Gagne's avatar
Don Gagne committed
541 542
                    if (_autoconnectPixhawk) {
                        pSerialConfig = new SerialConfiguration(QString("Pixhawk 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 546
                    break;
                case QGCSerialPortInfo::BoardTypeAeroCore:
Don Gagne's avatar
Don Gagne committed
547 548
                    if (_autoconnectPixhawk) {
                        pSerialConfig = new SerialConfiguration(QString("AeroCore on %1").arg(portInfo.portName().trimmed()));
549
                        pSerialConfig->setUsbDirect(true);
Don Gagne's avatar
Don Gagne committed
550
                    }
Don Gagne's avatar
Don Gagne committed
551 552
                    break;
                case QGCSerialPortInfo::BoardTypePX4Flow:
Don Gagne's avatar
Don Gagne committed
553 554 555
                    if (_autoconnectPX4Flow) {
                        pSerialConfig = new SerialConfiguration(QString("PX4Flow on %1").arg(portInfo.portName().trimmed()));
                    }
Don Gagne's avatar
Don Gagne committed
556
                    break;
Don Gagne's avatar
Don Gagne committed
557
                case QGCSerialPortInfo::BoardTypeSikRadio:
Don Gagne's avatar
Don Gagne committed
558
                    if (_autoconnect3DRRadio) {
559
                        pSerialConfig = new SerialConfiguration(QString("SiK Radio on %1").arg(portInfo.portName().trimmed()));
Don Gagne's avatar
Don Gagne committed
560 561
                    }
                    break;
Don Gagne's avatar
Don Gagne committed
562 563 564 565 566 567 568 569
#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
570 571
                default:
                    qWarning() << "Internal error";
Don Gagne's avatar
Don Gagne committed
572
                    continue;
dogmaphobic's avatar
dogmaphobic committed
573
                }
Don Gagne's avatar
Don Gagne committed
574

Don Gagne's avatar
Don Gagne committed
575 576
                if (pSerialConfig) {
                    qCDebug(LinkManagerLog) << "New auto-connect port added: " << pSerialConfig->name() << portInfo.systemLocation();
Don Gagne's avatar
Don Gagne committed
577
                    pSerialConfig->setBaud(boardType == QGCSerialPortInfo::BoardTypeSikRadio ? 57600 : 115200);
Don Gagne's avatar
Don Gagne committed
578 579 580
                    pSerialConfig->setDynamic(true);
                    pSerialConfig->setPortName(portInfo.systemLocation());
                    _autoconnectConfigurations.append(pSerialConfig);
Don Gagne's avatar
Don Gagne committed
581
                    createConnectedLink(pSerialConfig);
Don Gagne's avatar
Don Gagne committed
582
                }
dogmaphobic's avatar
dogmaphobic committed
583 584 585
            }
        }
    }
Don Gagne's avatar
Don Gagne committed
586

dogmaphobic's avatar
dogmaphobic committed
587 588
    // 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
589 590 591 592
    for (int i=0; i<_autoconnectConfigurations.count(); i++) {
        SerialConfiguration* linkConfig = _autoconnectConfigurations.value<SerialConfiguration*>(i);
        if (linkConfig) {
            if (!currentPorts.contains(linkConfig->portName())) {
593 594 595 596 597 598 599 600 601
                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
602
                }
603
                _confToDelete.append(linkConfig);
dogmaphobic's avatar
dogmaphobic committed
604
            }
Don Gagne's avatar
Don Gagne committed
605 606
        } else {
            qWarning() << "Internal error";
dogmaphobic's avatar
dogmaphobic committed
607 608
        }
    }
Don Gagne's avatar
Don Gagne committed
609

Don Gagne's avatar
Don Gagne committed
610
    // Now remove all configs that are gone
Don Gagne's avatar
Don Gagne committed
611
    foreach (LinkConfiguration* pDeleteConfig, _confToDelete) {
Don Gagne's avatar
Don Gagne committed
612
        qCDebug(LinkManagerLog) << "Removing unused autoconnect config" << pDeleteConfig->name();
Don Gagne's avatar
Don Gagne committed
613
        _autoconnectConfigurations.removeOne(pDeleteConfig);
614 615 616
        if (pDeleteConfig->link()) {
            disconnectLink(pDeleteConfig->link());
        }
Don Gagne's avatar
Don Gagne committed
617
        delete pDeleteConfig;
618
    }
619
#endif // __ios__
620 621
}

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

Don Gagne's avatar
Don Gagne committed
628
bool LinkManager::_setAutoconnectWorker(bool& currentAutoconnect, bool newAutoconnect, const char* autoconnectKey)
Don Gagne's avatar
Don Gagne committed
629
{
Don Gagne's avatar
Don Gagne committed
630
    if (currentAutoconnect != newAutoconnect) {
Don Gagne's avatar
Don Gagne committed
631 632 633
        QSettings settings;

        settings.beginGroup(_settingsGroup);
Don Gagne's avatar
Don Gagne committed
634 635 636 637 638 639 640
        settings.setValue(autoconnectKey, newAutoconnect);
        currentAutoconnect = newAutoconnect;
        return true;
    }

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

Don Gagne's avatar
Don Gagne committed
642 643 644
void LinkManager::setAutoconnectUDP(bool autoconnect)
{
    if (_setAutoconnectWorker(_autoconnectUDP, autoconnect, _autoconnectUDPKey)) {
Don Gagne's avatar
Don Gagne committed
645
        emit autoconnectUDPChanged(autoconnect);
646
    }
Don Gagne's avatar
Don Gagne committed
647 648 649 650
}

void LinkManager::setAutoconnectPixhawk(bool autoconnect)
{
Don Gagne's avatar
Don Gagne committed
651
    if (_setAutoconnectWorker(_autoconnectPixhawk, autoconnect, _autoconnectPixhawkKey)) {
Don Gagne's avatar
Don Gagne committed
652 653 654 655 656 657
        emit autoconnectPixhawkChanged(autoconnect);
    }
}

void LinkManager::setAutoconnect3DRRadio(bool autoconnect)
{
Don Gagne's avatar
Don Gagne committed
658
    if (_setAutoconnectWorker(_autoconnect3DRRadio, autoconnect, _autoconnect3DRRadioKey)) {
Don Gagne's avatar
Don Gagne committed
659 660 661 662 663 664
        emit autoconnect3DRRadioChanged(autoconnect);
    }
}

void LinkManager::setAutoconnectPX4Flow(bool autoconnect)
{
Don Gagne's avatar
Don Gagne committed
665
    if (_setAutoconnectWorker(_autoconnectPX4Flow, autoconnect, _autoconnectPX4FlowKey)) {
Don Gagne's avatar
Don Gagne committed
666 667
        emit autoconnectPX4FlowChanged(autoconnect);
    }
Don Gagne's avatar
Don Gagne committed
668
}
Don Gagne's avatar
Don Gagne committed
669

Don Gagne's avatar
Don Gagne committed
670 671 672 673 674
void LinkManager::setAutoconnectRTKGPS(bool autoconnect)
{
    if (_setAutoconnectWorker(_autoconnectRTKGPS, autoconnect, _autoconnectRTKGPSKey)) {
        emit autoconnectRTKGPSChanged(autoconnect);
    }
675
}
676 677 678 679 680 681 682 683 684 685 686 687

QStringList LinkManager::linkTypeStrings(void) const
{
    //-- Must follow same order as enum LinkType in LinkConfiguration.h
    static QStringList list;
    if(!list.size())
    {
#ifndef __ios__
        list += "Serial";
#endif
        list += "UDP";
        list += "TCP";
dogmaphobic's avatar
dogmaphobic committed
688
#ifdef QGC_ENABLE_BLUETOOTH
dogmaphobic's avatar
dogmaphobic committed
689 690
        list += "Bluetooth";
#endif
dogmaphobic's avatar
dogmaphobic committed
691
#ifdef QT_DEBUG
692
        list += "Mock Link";
dogmaphobic's avatar
dogmaphobic committed
693 694
#endif
#ifndef __mobile__
695
        list += "Log Replay";
dogmaphobic's avatar
dogmaphobic committed
696
#endif
dogmaphobic's avatar
dogmaphobic committed
697
        Q_ASSERT(list.size() == (int)LinkConfiguration::TypeLast);
698 699 700 701
    }
    return list;
}

702
void LinkManager::_updateSerialPorts()
703
{
704 705
    _commPortList.clear();
    _commPortDisplayList.clear();
706
#ifndef __ios__
707 708
    QList<QSerialPortInfo> portList = QSerialPortInfo::availablePorts();
    foreach (const QSerialPortInfo &info, portList)
709
    {
710 711 712
        QString port = info.systemLocation().trimmed();
        _commPortList += port;
        _commPortDisplayList += SerialConfiguration::cleanPortDisplayname(port);
713 714
    }
#endif
715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731
}

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

QStringList LinkManager::serialPorts(void)
{
    if(!_commPortList.size())
    {
        _updateSerialPorts();
    }
732 733 734 735 736 737 738 739 740 741 742 743
    return _commPortList;
}

QStringList LinkManager::serialBaudRates(void)
{
#ifdef __ios__
    QStringList foo;
    return foo;
#else
    return SerialConfiguration::supportedBaudRates();
#endif
}
744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769

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)
{
dogmaphobic's avatar
dogmaphobic committed
770
#ifndef __ios__
771 772
    if((LinkConfiguration::LinkType)type == LinkConfiguration::TypeSerial)
        _updateSerialPorts();
dogmaphobic's avatar
dogmaphobic committed
773
#endif
774 775 776 777 778 779
    return LinkConfiguration::createSettings(type, name);
}

LinkConfiguration* LinkManager::startConfigurationEditing(LinkConfiguration* config)
{
    Q_ASSERT(config != NULL);
dogmaphobic's avatar
dogmaphobic committed
780
#ifndef __ios__
781 782
    if(config->type() == LinkConfiguration::TypeSerial)
        _updateSerialPorts();
dogmaphobic's avatar
dogmaphobic committed
783
#endif
784 785 786 787 788 789 790 791 792 793 794 795 796
    return LinkConfiguration::duplicateSettings(config);
}


void LinkManager::_fixUnnamed(LinkConfiguration* config)
{
    Q_ASSERT(config != NULL);
    //-- Check for "Unnamed"
    if (config->name() == "Unnamed") {
        switch(config->type()) {
#ifndef __ios__
            case LinkConfiguration::TypeSerial: {
                QString tname = dynamic_cast<SerialConfiguration*>(config)->portName();
797
#ifdef Q_OS_WIN
798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818
                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
819
#ifdef QGC_ENABLE_BLUETOOTH
dogmaphobic's avatar
dogmaphobic committed
820 821 822
            case LinkConfiguration::TypeBluetooth: {
                    BluetoothConfiguration* tconfig = dynamic_cast<BluetoothConfiguration*>(config);
                    if(tconfig) {
823
                        config->setName(QString("%1 (Bluetooth Device)").arg(tconfig->device().name));
dogmaphobic's avatar
dogmaphobic committed
824 825 826 827
                    }
                }
                break;
#endif
dogmaphobic's avatar
dogmaphobic committed
828
#ifndef __mobile__
829
            case LinkConfiguration::TypeLogReplay: {
dogmaphobic's avatar
dogmaphobic committed
830 831 832 833
                    LogReplayLinkConfiguration* tconfig = dynamic_cast<LogReplayLinkConfiguration*>(config);
                    if(tconfig) {
                        config->setName(QString("Log Replay %1").arg(tconfig->logFilenameShort()));
                    }
834 835
                }
                break;
dogmaphobic's avatar
dogmaphobic committed
836
#endif
837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862
#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();
}
863

864 865 866 867
bool LinkManager::isAutoconnectLink(LinkInterface* link)
{
    return _autoconnectConfigurations.contains(link->getLinkConfiguration());
}
dogmaphobic's avatar
dogmaphobic committed
868 869 870 871 872

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

Don Gagne's avatar
Don Gagne committed
874
#ifndef __ios__
Don Gagne's avatar
Don Gagne committed
875 876
void LinkManager::_activeLinkCheck(void)
{
877
    SerialLink* link = NULL;
Don Gagne's avatar
Don Gagne committed
878 879 880
    bool found = false;

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

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

901 902 903
    if (!found && link) {
        // See if we can get an NSH prompt on this link
        bool foundNSHPrompt = false;
904
        link->writeBytesSafe("\r", 1);
905 906 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 ?
                                  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
916 917
    }
}
Don Gagne's avatar
Don Gagne committed
918
#endif