LinkManager.cc 32.1 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
                    break;
Henry Zhang's avatar
Henry Zhang committed
552 553 554 555 556 557
                case QGCSerialPortInfo::BoardTypeMINDPXFMUV2:
                    if (_autoconnectPixhawk) {
                        pSerialConfig = new SerialConfiguration(QString("MindPX on %1").arg(portInfo.portName().trimmed()));
                        pSerialConfig->setUsbDirect(true);
                    }
                    break;
Don Gagne's avatar
Don Gagne committed
558
                case QGCSerialPortInfo::BoardTypePX4Flow:
Don Gagne's avatar
Don Gagne committed
559 560 561
                    if (_autoconnectPX4Flow) {
                        pSerialConfig = new SerialConfiguration(QString("PX4Flow on %1").arg(portInfo.portName().trimmed()));
                    }
Don Gagne's avatar
Don Gagne committed
562
                    break;
Don Gagne's avatar
Don Gagne committed
563
                case QGCSerialPortInfo::BoardTypeSikRadio:
Don Gagne's avatar
Don Gagne committed
564
                    if (_autoconnect3DRRadio) {
565
                        pSerialConfig = new SerialConfiguration(QString("SiK Radio on %1").arg(portInfo.portName().trimmed()));
Don Gagne's avatar
Don Gagne committed
566 567
                    }
                    break;
Don Gagne's avatar
Don Gagne committed
568 569 570 571 572 573 574 575
#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
576 577
                default:
                    qWarning() << "Internal error";
Don Gagne's avatar
Don Gagne committed
578
                    continue;
dogmaphobic's avatar
dogmaphobic committed
579
                }
Don Gagne's avatar
Don Gagne committed
580

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

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

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

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

Don Gagne's avatar
Don Gagne committed
634
bool LinkManager::_setAutoconnectWorker(bool& currentAutoconnect, bool newAutoconnect, const char* autoconnectKey)
Don Gagne's avatar
Don Gagne committed
635
{
Don Gagne's avatar
Don Gagne committed
636
    if (currentAutoconnect != newAutoconnect) {
Don Gagne's avatar
Don Gagne committed
637 638 639
        QSettings settings;

        settings.beginGroup(_settingsGroup);
Don Gagne's avatar
Don Gagne committed
640 641 642 643 644 645 646
        settings.setValue(autoconnectKey, newAutoconnect);
        currentAutoconnect = newAutoconnect;
        return true;
    }

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

Don Gagne's avatar
Don Gagne committed
648 649 650
void LinkManager::setAutoconnectUDP(bool autoconnect)
{
    if (_setAutoconnectWorker(_autoconnectUDP, autoconnect, _autoconnectUDPKey)) {
Don Gagne's avatar
Don Gagne committed
651
        emit autoconnectUDPChanged(autoconnect);
652
    }
Don Gagne's avatar
Don Gagne committed
653 654 655 656
}

void LinkManager::setAutoconnectPixhawk(bool autoconnect)
{
Don Gagne's avatar
Don Gagne committed
657
    if (_setAutoconnectWorker(_autoconnectPixhawk, autoconnect, _autoconnectPixhawkKey)) {
Don Gagne's avatar
Don Gagne committed
658 659 660 661 662 663
        emit autoconnectPixhawkChanged(autoconnect);
    }
}

void LinkManager::setAutoconnect3DRRadio(bool autoconnect)
{
Don Gagne's avatar
Don Gagne committed
664
    if (_setAutoconnectWorker(_autoconnect3DRRadio, autoconnect, _autoconnect3DRRadioKey)) {
Don Gagne's avatar
Don Gagne committed
665 666 667 668 669 670
        emit autoconnect3DRRadioChanged(autoconnect);
    }
}

void LinkManager::setAutoconnectPX4Flow(bool autoconnect)
{
Don Gagne's avatar
Don Gagne committed
671
    if (_setAutoconnectWorker(_autoconnectPX4Flow, autoconnect, _autoconnectPX4FlowKey)) {
Don Gagne's avatar
Don Gagne committed
672 673
        emit autoconnectPX4FlowChanged(autoconnect);
    }
Don Gagne's avatar
Don Gagne committed
674
}
Don Gagne's avatar
Don Gagne committed
675

Don Gagne's avatar
Don Gagne committed
676 677 678 679 680
void LinkManager::setAutoconnectRTKGPS(bool autoconnect)
{
    if (_setAutoconnectWorker(_autoconnectRTKGPS, autoconnect, _autoconnectRTKGPSKey)) {
        emit autoconnectRTKGPSChanged(autoconnect);
    }
681
}
682 683 684 685 686 687 688 689 690 691 692 693

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
694
#ifdef QGC_ENABLE_BLUETOOTH
dogmaphobic's avatar
dogmaphobic committed
695 696
        list += "Bluetooth";
#endif
dogmaphobic's avatar
dogmaphobic committed
697
#ifdef QT_DEBUG
698
        list += "Mock Link";
dogmaphobic's avatar
dogmaphobic committed
699 700
#endif
#ifndef __mobile__
701
        list += "Log Replay";
dogmaphobic's avatar
dogmaphobic committed
702
#endif
dogmaphobic's avatar
dogmaphobic committed
703
        Q_ASSERT(list.size() == (int)LinkConfiguration::TypeLast);
704 705 706 707
    }
    return list;
}

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

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

QStringList LinkManager::serialPorts(void)
{
    if(!_commPortList.size())
    {
        _updateSerialPorts();
    }
738 739 740 741 742 743 744 745 746 747 748 749
    return _commPortList;
}

QStringList LinkManager::serialBaudRates(void)
{
#ifdef __ios__
    QStringList foo;
    return foo;
#else
    return SerialConfiguration::supportedBaudRates();
#endif
}
750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775

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
776
#ifndef __ios__
777 778
    if((LinkConfiguration::LinkType)type == LinkConfiguration::TypeSerial)
        _updateSerialPorts();
dogmaphobic's avatar
dogmaphobic committed
779
#endif
780 781 782 783 784 785
    return LinkConfiguration::createSettings(type, name);
}

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

870 871 872 873
bool LinkManager::isAutoconnectLink(LinkInterface* link)
{
    return _autoconnectConfigurations.contains(link->getLinkConfiguration());
}
dogmaphobic's avatar
dogmaphobic committed
874 875 876 877 878

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

Don Gagne's avatar
Don Gagne committed
880
#ifndef __ios__
Don Gagne's avatar
Don Gagne committed
881 882
void LinkManager::_activeLinkCheck(void)
{
883
    SerialLink* link = NULL;
Don Gagne's avatar
Don Gagne committed
884 885 886
    bool found = false;

    if (_activeLinkCheckList.count() != 0) {
887
        link = _activeLinkCheckList.takeFirst();
Don Gagne's avatar
Don Gagne committed
888 889 890 891 892 893 894 895 896 897
        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;
                }
            }
898 899
        } else {
            link = NULL;
Don Gagne's avatar
Don Gagne committed
900 901 902 903 904 905 906
        }
    }

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

907 908 909
    if (!found && link) {
        // See if we can get an NSH prompt on this link
        bool foundNSHPrompt = false;
910
        link->writeBytesSafe("\r", 1);
911 912 913 914 915 916 917 918 919 920 921
        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
922 923
    }
}
Don Gagne's avatar
Don Gagne committed
924
#endif