LinkManager.cc 29.4 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>
dogmaphobic's avatar
dogmaphobic committed
35 36

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

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

Don Gagne's avatar
Don Gagne committed
48
QGC_LOGGING_CATEGORY(LinkManagerLog, "LinkManagerLog")
Don Gagne's avatar
Don Gagne committed
49 50 51 52 53 54 55
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
56
const char* LinkManager::_defaultUPDLinkName =      "Default UDP Link";
57

58 59 60 61 62 63 64 65
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

66 67
LinkManager::LinkManager(QGCApplication* app)
    : QGCTool(app)
68 69 70
    , _configUpdateSuspended(false)
    , _configurationsLoaded(false)
    , _connectionsSuspended(false)
71
    , _mavlinkChannelsUsedBitMask(0)
72
    , _mavlinkProtocol(NULL)
Don Gagne's avatar
Don Gagne committed
73 74 75 76 77
    , _autoconnectUDP(true)
    , _autoconnectPixhawk(true)
    , _autoconnect3DRRadio(true)
    , _autoconnectPX4Flow(true)

pixhawk's avatar
pixhawk committed
78
{
Don Gagne's avatar
Don Gagne committed
79 80 81 82 83
    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;
84

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

    _activeLinkCheckTimer.setInterval(_activeLinkCheckTimeoutMSecs);
    _activeLinkCheckTimer.setSingleShot(false);
    connect(&_activeLinkCheckTimer, &QTimer::timeout, this, &LinkManager::_activeLinkCheck);
pixhawk's avatar
pixhawk committed
94 95 96 97
}

LinkManager::~LinkManager()
{
98

pixhawk's avatar
pixhawk committed
99 100
}

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

   _mavlinkProtocol = _toolbox->mavlinkProtocol();

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

110 111
}

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

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

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

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

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

190 191
        // Find a mavlink channel to use for this link
        for (int i=0; i<32; i++) {
192
            if (!(_mavlinkChannelsUsedBitMask & 1 << i)) {
193
                mavlink_reset_channel_status(i);
194 195
                link->_setMavlinkChannel(i);
                _mavlinkChannelsUsedBitMask |= i << i;
196
                channelSet = true;
197 198 199
                break;
            }
        }
200

201 202 203 204
        if (!channelSet) {
            qWarning() << "Ran out of mavlink channels";
        }

Don Gagne's avatar
Don Gagne committed
205
        _links.append(link);
206 207
        emit newLink(link);
    }
208

Don Gagne's avatar
Don Gagne committed
209 210
    connect(link, &LinkInterface::communicationError,   _app,               &QGCApplication::criticalMessageBoxOnMainThread);
    connect(link, &LinkInterface::bytesReceived,        _mavlinkProtocol,   &MAVLinkProtocol::receiveBytes);
211

212
    _mavlinkProtocol->resetMetadataForLink(link);
213

214
    connect(link, &LinkInterface::connected,    this, &LinkManager::_linkConnected);
215
    connect(link, &LinkInterface::disconnected, this, &LinkManager::_linkDisconnected);
216
}
pixhawk's avatar
pixhawk committed
217

Don Gagne's avatar
Don Gagne committed
218
void LinkManager::disconnectAll(void)
pixhawk's avatar
pixhawk committed
219
{
Don Gagne's avatar
Don Gagne committed
220 221
    // 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
222
        disconnectLink(_links.value<LinkInterface*>(i));
223
    }
pixhawk's avatar
pixhawk committed
224 225 226 227
}

bool LinkManager::connectLink(LinkInterface* link)
{
228
    Q_ASSERT(link);
229

230 231 232 233
    if (_connectionsSuspendedMsg()) {
        return false;
    }

234
    return link->_connect();
pixhawk's avatar
pixhawk committed
235 236
}

Don Gagne's avatar
Don Gagne committed
237
void LinkManager::disconnectLink(LinkInterface* link)
pixhawk's avatar
pixhawk committed
238
{
239
    Q_ASSERT(link);
Don Gagne's avatar
Don Gagne committed
240

Don Gagne's avatar
Don Gagne committed
241 242
    link->_disconnect();
    LinkConfiguration* config = link->getLinkConfiguration();
243 244 245 246
    if (config) {
        if (_autoconnectConfigurations.contains(config)) {
            config->setLink(NULL);
        }
247
    }
Don Gagne's avatar
Don Gagne committed
248
    _deleteLink(link);
249 250 251 252
    if (_autoconnectConfigurations.contains(config)) {
        _autoconnectConfigurations.removeOne(config);
        delete config;
    }
pixhawk's avatar
pixhawk committed
253 254
}

255
void LinkManager::_deleteLink(LinkInterface* link)
256
{
Don Gagne's avatar
Don Gagne committed
257 258 259 260 261 262 263 264
    if (thread() != QThread::currentThread()) {
        qWarning() << "_deleteLink called from incorrect thread";
        return;
    }

    if (!link) {
        return;
    }
265

266 267
    // Free up the mavlink channel associated with this link
    _mavlinkChannelsUsedBitMask &= ~(1 << link->getMavlinkChannel());
268

Don Gagne's avatar
Don Gagne committed
269 270
    _links.removeOne(link);
    delete link;
271

Don Gagne's avatar
Don Gagne committed
272
    // Emit removal of link
273
    emit linkDeleted(link);
pixhawk's avatar
pixhawk committed
274 275
}

276 277 278 279 280
/// @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) {
281
        qgcApp()->showMessage(QString("Connect not allowed: %1").arg(_connectionsSuspendedReason));
282 283 284 285 286 287 288 289 290 291 292 293
        return true;
    } else {
        return false;
    }
}

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

295 296 297 298 299 300 301 302 303
void LinkManager::_linkConnected(void)
{
    emit linkConnected((LinkInterface*)sender());
}

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

void LinkManager::suspendConfigurationUpdates(bool suspend)
{
    _configUpdateSuspended = suspend;
}

void LinkManager::saveLinkConfigurationList()
{
    QSettings settings;
    settings.remove(LinkConfiguration::settingsRoot());
314 315
    int trueCount = 0;
    for (int i = 0; i < _linkConfigurations.count(); i++) {
Don Gagne's avatar
Don Gagne committed
316 317 318 319 320
        LinkConfiguration* linkConfig = _linkConfigurations.value<LinkConfiguration*>(i);
        if (linkConfig) {
            if(!linkConfig->isDynamic())
            {
                QString root = LinkConfiguration::settingsRoot();
321
                root += QString("/Link%1").arg(trueCount++);
Don Gagne's avatar
Don Gagne committed
322 323
                settings.setValue(root + "/name", linkConfig->name());
                settings.setValue(root + "/type", linkConfig->type());
324
                settings.setValue(root + "/auto", linkConfig->isAutoConnect());
Don Gagne's avatar
Don Gagne committed
325 326 327 328 329
                // Have the instance save its own values
                linkConfig->saveSettings(settings, root);
            }
        } else {
            qWarning() << "Internal error";
dogmaphobic's avatar
dogmaphobic committed
330
        }
331
    }
dogmaphobic's avatar
dogmaphobic committed
332
    QString root(LinkConfiguration::settingsRoot());
333 334
    settings.setValue(root + "/count", trueCount);
    emit linkConfigurationsChanged();
335 336 337 338
}

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

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

dogmaphobic's avatar
dogmaphobic committed
430
#ifndef __ios__
Don Gagne's avatar
Don Gagne committed
431
SerialConfiguration* LinkManager::_autoconnectConfigurationsContainsPort(const QString& portName)
432 433
{
    QString searchPort = portName.trimmed();
Don Gagne's avatar
Don Gagne committed
434 435 436 437 438 439 440

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

        if (linkConfig) {
            if (linkConfig->portName() == searchPort) {
                return linkConfig;
441
            }
Don Gagne's avatar
Don Gagne committed
442 443
        } else {
            qWarning() << "Internal error";
444 445 446 447
        }
    }
    return NULL;
}
dogmaphobic's avatar
dogmaphobic committed
448
#endif
449

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

Don Gagne's avatar
Don Gagne committed
456 457 458 459 460 461 462 463 464
    // 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
465
    if (!foundUDP && _autoconnectUDP) {
Don Gagne's avatar
Don Gagne committed
466
        qCDebug(LinkManagerLog) << "New auto-connect UDP port added";
Don Gagne's avatar
Don Gagne committed
467 468 469
        UDPConfiguration* udpConfig = new UDPConfiguration(_defaultUPDLinkName);
        udpConfig->setLocalPort(QGC_UDP_LOCAL_PORT);
        udpConfig->setDynamic(true);
470
        _linkConfigurations.append(udpConfig);
Don Gagne's avatar
Don Gagne committed
471
        createConnectedLink(udpConfig);
472
        emit linkConfigurationsChanged();
Don Gagne's avatar
Don Gagne committed
473 474
    }

475
#ifndef __ios__
dogmaphobic's avatar
dogmaphobic committed
476
    QStringList currentPorts;
Don Gagne's avatar
Don Gagne committed
477
    QList<QGCSerialPortInfo> portList = QGCSerialPortInfo::availablePorts();
Don Gagne's avatar
Don Gagne committed
478

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

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

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

502 503 504 505 506 507 508
            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();
509 510
                _autoconnectWaitList[portInfo.systemLocation()] = 1;
            } else if (++_autoconnectWaitList[portInfo.systemLocation()] * _autoconnectUpdateTimerMSecs > _autoconnectConnectDelayMSecs) {
Don Gagne's avatar
Don Gagne committed
511 512
                SerialConfiguration* pSerialConfig = NULL;

513
                _autoconnectWaitList.remove(portInfo.systemLocation());
514

Don Gagne's avatar
Don Gagne committed
515 516 517
                switch (boardType) {
                case QGCSerialPortInfo::BoardTypePX4FMUV1:
                case QGCSerialPortInfo::BoardTypePX4FMUV2:
518
                case QGCSerialPortInfo::BoardTypePX4FMUV4:
Don Gagne's avatar
Don Gagne committed
519 520
                    if (_autoconnectPixhawk) {
                        pSerialConfig = new SerialConfiguration(QString("Pixhawk on %1").arg(portInfo.portName().trimmed()));
521
                        pSerialConfig->setUsbDirect(true);
Don Gagne's avatar
Don Gagne committed
522
                    }
Don Gagne's avatar
Don Gagne committed
523 524
                    break;
                case QGCSerialPortInfo::BoardTypeAeroCore:
Don Gagne's avatar
Don Gagne committed
525 526
                    if (_autoconnectPixhawk) {
                        pSerialConfig = new SerialConfiguration(QString("AeroCore on %1").arg(portInfo.portName().trimmed()));
527
                        pSerialConfig->setUsbDirect(true);
Don Gagne's avatar
Don Gagne committed
528
                    }
Don Gagne's avatar
Don Gagne committed
529 530
                    break;
                case QGCSerialPortInfo::BoardTypePX4Flow:
Don Gagne's avatar
Don Gagne committed
531 532 533
                    if (_autoconnectPX4Flow) {
                        pSerialConfig = new SerialConfiguration(QString("PX4Flow on %1").arg(portInfo.portName().trimmed()));
                    }
Don Gagne's avatar
Don Gagne committed
534 535
                    break;
                case QGCSerialPortInfo::BoardType3drRadio:
Don Gagne's avatar
Don Gagne committed
536 537 538 539
                    if (_autoconnect3DRRadio) {
                        pSerialConfig = new SerialConfiguration(QString("3DR Radio on %1").arg(portInfo.portName().trimmed()));
                    }
                    break;
Don Gagne's avatar
Don Gagne committed
540 541
                default:
                    qWarning() << "Internal error";
Don Gagne's avatar
Don Gagne committed
542
                    continue;
dogmaphobic's avatar
dogmaphobic committed
543
                }
Don Gagne's avatar
Don Gagne committed
544

Don Gagne's avatar
Don Gagne committed
545 546 547 548 549 550
                if (pSerialConfig) {
                    qCDebug(LinkManagerLog) << "New auto-connect port added: " << pSerialConfig->name() << portInfo.systemLocation();
                    pSerialConfig->setBaud(boardType == QGCSerialPortInfo::BoardType3drRadio ? 57600 : 115200);
                    pSerialConfig->setDynamic(true);
                    pSerialConfig->setPortName(portInfo.systemLocation());
                    _autoconnectConfigurations.append(pSerialConfig);
Don Gagne's avatar
Don Gagne committed
551
                    createConnectedLink(pSerialConfig);
Don Gagne's avatar
Don Gagne committed
552
                }
dogmaphobic's avatar
dogmaphobic committed
553 554 555
            }
        }
    }
Don Gagne's avatar
Don Gagne committed
556

dogmaphobic's avatar
dogmaphobic committed
557 558
    // 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
559 560 561 562
    for (int i=0; i<_autoconnectConfigurations.count(); i++) {
        SerialConfiguration* linkConfig = _autoconnectConfigurations.value<SerialConfiguration*>(i);
        if (linkConfig) {
            if (!currentPorts.contains(linkConfig->portName())) {
Don Gagne's avatar
Don Gagne committed
563 564 565 566 567 568 569
                // We don't remove links which are still connected even though at this point the cable may
                // have been pulled. This is due to the fact that whether a serial port goes away from the
                // list when the cable is pulled is OS dependant. By not disconnecting in this case, we keep
                // things working the same across all OS.
                if (!linkConfig->link() || !linkConfig->link()->isConnected()) {
                    _confToDelete.append(linkConfig);
                }
dogmaphobic's avatar
dogmaphobic committed
570
            }
Don Gagne's avatar
Don Gagne committed
571 572
        } else {
            qWarning() << "Internal error";
dogmaphobic's avatar
dogmaphobic committed
573 574
        }
    }
Don Gagne's avatar
Don Gagne committed
575

Don Gagne's avatar
Don Gagne committed
576
    // Now remove all configs that are gone
Don Gagne's avatar
Don Gagne committed
577
    foreach (LinkConfiguration* pDeleteConfig, _confToDelete) {
Don Gagne's avatar
Don Gagne committed
578
        qCDebug(LinkManagerLog) << "Removing unused autoconnect config" << pDeleteConfig->name();
Don Gagne's avatar
Don Gagne committed
579 580
        _autoconnectConfigurations.removeOne(pDeleteConfig);
        delete pDeleteConfig;
581
    }
582
#endif // __ios__
583 584
}

Don Gagne's avatar
Don Gagne committed
585 586 587
void LinkManager::shutdown(void)
{
    setConnectionsSuspended("Shutdown");
Don Gagne's avatar
Don Gagne committed
588
    disconnectAll();
Don Gagne's avatar
Don Gagne committed
589 590 591 592 593 594 595 596 597 598 599 600
}

void LinkManager::setAutoconnectUDP(bool autoconnect)
{
    if (_autoconnectUDP != autoconnect) {
        QSettings settings;

        settings.beginGroup(_settingsGroup);
        settings.setValue(_autoconnectUDPKey, autoconnect);

        _autoconnectUDP = autoconnect;
        emit autoconnectUDPChanged(autoconnect);
601
    }
Don Gagne's avatar
Don Gagne committed
602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643
}

void LinkManager::setAutoconnectPixhawk(bool autoconnect)
{
    if (_autoconnectPixhawk != autoconnect) {
        QSettings settings;

        settings.beginGroup(_settingsGroup);
        settings.setValue(_autoconnectPixhawkKey, autoconnect);

        _autoconnectPixhawk = autoconnect;
        emit autoconnectPixhawkChanged(autoconnect);
    }

}

void LinkManager::setAutoconnect3DRRadio(bool autoconnect)
{
    if (_autoconnect3DRRadio != autoconnect) {
        QSettings settings;

        settings.beginGroup(_settingsGroup);
        settings.setValue(_autoconnect3DRRadioKey, autoconnect);

        _autoconnect3DRRadio = autoconnect;
        emit autoconnect3DRRadioChanged(autoconnect);
    }

}

void LinkManager::setAutoconnectPX4Flow(bool autoconnect)
{
    if (_autoconnectPX4Flow != autoconnect) {
        QSettings settings;

        settings.beginGroup(_settingsGroup);
        settings.setValue(_autoconnectPX4FlowKey, autoconnect);

        _autoconnectPX4Flow = autoconnect;
        emit autoconnectPX4FlowChanged(autoconnect);
    }

644
}
645 646 647 648 649 650 651 652 653 654 655 656

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
657
#ifdef QGC_ENABLE_BLUETOOTH
dogmaphobic's avatar
dogmaphobic committed
658 659
        list += "Bluetooth";
#endif
dogmaphobic's avatar
dogmaphobic committed
660
#ifdef QT_DEBUG
661
        list += "Mock Link";
dogmaphobic's avatar
dogmaphobic committed
662 663
#endif
#ifndef __mobile__
664
        list += "Log Replay";
dogmaphobic's avatar
dogmaphobic committed
665
#endif
dogmaphobic's avatar
dogmaphobic committed
666
        Q_ASSERT(list.size() == (int)LinkConfiguration::TypeLast);
667 668 669 670
    }
    return list;
}

671
void LinkManager::_updateSerialPorts()
672
{
673 674
    _commPortList.clear();
    _commPortDisplayList.clear();
675
#ifndef __ios__
676 677
    QList<QSerialPortInfo> portList = QSerialPortInfo::availablePorts();
    foreach (const QSerialPortInfo &info, portList)
678
    {
679 680 681
        QString port = info.systemLocation().trimmed();
        _commPortList += port;
        _commPortDisplayList += SerialConfiguration::cleanPortDisplayname(port);
682 683
    }
#endif
684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700
}

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

QStringList LinkManager::serialPorts(void)
{
    if(!_commPortList.size())
    {
        _updateSerialPorts();
    }
701 702 703 704 705 706 707 708 709 710 711 712
    return _commPortList;
}

QStringList LinkManager::serialBaudRates(void)
{
#ifdef __ios__
    QStringList foo;
    return foo;
#else
    return SerialConfiguration::supportedBaudRates();
#endif
}
713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738

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
739
#ifndef __ios__
740 741
    if((LinkConfiguration::LinkType)type == LinkConfiguration::TypeSerial)
        _updateSerialPorts();
dogmaphobic's avatar
dogmaphobic committed
742
#endif
743 744 745 746 747 748
    return LinkConfiguration::createSettings(type, name);
}

LinkConfiguration* LinkManager::startConfigurationEditing(LinkConfiguration* config)
{
    Q_ASSERT(config != NULL);
dogmaphobic's avatar
dogmaphobic committed
749
#ifndef __ios__
750 751
    if(config->type() == LinkConfiguration::TypeSerial)
        _updateSerialPorts();
dogmaphobic's avatar
dogmaphobic committed
752
#endif
753 754 755 756 757 758 759 760 761 762 763 764 765
    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();
766
#ifdef Q_OS_WIN
767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787
                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
788
#ifdef QGC_ENABLE_BLUETOOTH
dogmaphobic's avatar
dogmaphobic committed
789 790 791
            case LinkConfiguration::TypeBluetooth: {
                    BluetoothConfiguration* tconfig = dynamic_cast<BluetoothConfiguration*>(config);
                    if(tconfig) {
792
                        config->setName(QString("%1 (Bluetooth Device)").arg(tconfig->device().name));
dogmaphobic's avatar
dogmaphobic committed
793 794 795 796
                    }
                }
                break;
#endif
dogmaphobic's avatar
dogmaphobic committed
797
#ifndef __mobile__
798
            case LinkConfiguration::TypeLogReplay: {
dogmaphobic's avatar
dogmaphobic committed
799 800 801 802
                    LogReplayLinkConfiguration* tconfig = dynamic_cast<LogReplayLinkConfiguration*>(config);
                    if(tconfig) {
                        config->setName(QString("Log Replay %1").arg(tconfig->logFilenameShort()));
                    }
803 804
                }
                break;
dogmaphobic's avatar
dogmaphobic committed
805
#endif
806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831
#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();
}
832

833 834 835 836
bool LinkManager::isAutoconnectLink(LinkInterface* link)
{
    return _autoconnectConfigurations.contains(link->getLinkConfiguration());
}
dogmaphobic's avatar
dogmaphobic committed
837 838 839 840 841

bool LinkManager::isBluetoothAvailable(void)
{
    return qgcApp()->isBluetoothAvailable();
}
Don Gagne's avatar
Don Gagne committed
842 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 869

void LinkManager::_activeLinkCheck(void)
{
    bool found = false;

    if (_activeLinkCheckList.count() != 0) {
        LinkInterface* link = _activeLinkCheckList.takeFirst();
        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;
                }
            }
        }
    }

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

    if (!found) {
        qgcApp()->showMessage("You have connected to a Vehicle which does not have an SD Card inserted. Please insert an SD card and try again.");
    }
}