LinkManager.cc 30.3 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();
pixhawk's avatar
pixhawk committed
90 91 92 93
}

LinkManager::~LinkManager()
{
Don Gagne's avatar
Don Gagne committed
94 95
    if (anyActiveLinks()) {
        qWarning() << "Why are there still active links?";
96
    }
pixhawk's avatar
pixhawk committed
97 98
}

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

   _mavlinkProtocol = _toolbox->mavlinkProtocol();
Don Gagne's avatar
Don Gagne committed
104
   connect(_mavlinkProtocol, &MAVLinkProtocol::vehicleHeartbeatInfo, this, &LinkManager::_vehicleHeartbeatInfo);
105

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

109 110
}

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

Don Gagne's avatar
Don Gagne committed
153
LinkInterface* LinkManager::createConnectedLink(const QString& name)
154 155 156
{
    Q_ASSERT(name.isEmpty() == false);
    for(int i = 0; i < _linkConfigurations.count(); i++) {
Don Gagne's avatar
Don Gagne committed
157
        LinkConfiguration* conf = _linkConfigurations.value<LinkConfiguration*>(i);
158
        if(conf && conf->name() == name)
Don Gagne's avatar
Don Gagne committed
159
            return createConnectedLink(conf);
160 161 162 163
    }
    return NULL;
}

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

Don Gagne's avatar
Don Gagne committed
171 172 173
    if (!link) {
        return;
    }
174

Don Gagne's avatar
Don Gagne committed
175
    if (!_links.contains(link)) {
176 177 178
        // Find a mavlink channel to use for this link
        for (int i=0; i<32; i++) {
            if (!(_mavlinkChannelsUsedBitMask && 1 << i)) {
179
                mavlink_reset_channel_status(i);
180 181 182 183 184
                link->_setMavlinkChannel(i);
                _mavlinkChannelsUsedBitMask |= i << i;
                break;
            }
        }
185

Don Gagne's avatar
Don Gagne committed
186
        _links.append(link);
187 188
        emit newLink(link);
    }
189

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

193
    _mavlinkProtocol->resetMetadataForLink(link);
194

195
    connect(link, &LinkInterface::connected,    this, &LinkManager::_linkConnected);
196
    connect(link, &LinkInterface::disconnected, this, &LinkManager::_linkDisconnected);
197
}
pixhawk's avatar
pixhawk committed
198

Don Gagne's avatar
Don Gagne committed
199
void LinkManager::disconnectAll(void)
pixhawk's avatar
pixhawk committed
200
{
Don Gagne's avatar
Don Gagne committed
201 202
    // 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
203
        disconnectLink(_links.value<LinkInterface*>(i));
204
    }
pixhawk's avatar
pixhawk committed
205 206 207 208
}

bool LinkManager::connectLink(LinkInterface* link)
{
209
    Q_ASSERT(link);
210

211 212 213 214
    if (_connectionsSuspendedMsg()) {
        return false;
    }

Don Gagne's avatar
Don Gagne committed
215 216
    bool previousAnyConnectedLinks = anyConnectedLinks();

217
    if (link->_connect()) {
Don Gagne's avatar
Don Gagne committed
218 219 220
        if (!previousAnyConnectedLinks) {
            emit anyConnectedLinksChanged(true);
        }
221 222 223 224
        return true;
    } else {
        return false;
    }
pixhawk's avatar
pixhawk committed
225 226
}

Don Gagne's avatar
Don Gagne committed
227
void LinkManager::disconnectLink(LinkInterface* link)
pixhawk's avatar
pixhawk committed
228
{
229
    Q_ASSERT(link);
Don Gagne's avatar
Don Gagne committed
230

Don Gagne's avatar
Don Gagne committed
231 232
    link->_disconnect();
    LinkConfiguration* config = link->getLinkConfiguration();
233 234 235 236
    if (config) {
        if (_autoconnectConfigurations.contains(config)) {
            config->setLink(NULL);
        }
237
    }
Don Gagne's avatar
Don Gagne committed
238
    _deleteLink(link);
239 240 241 242
    if (_autoconnectConfigurations.contains(config)) {
        _autoconnectConfigurations.removeOne(config);
        delete config;
    }
pixhawk's avatar
pixhawk committed
243 244
}

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

    if (!link) {
        return;
    }
255

256 257
    // Free up the mavlink channel associated with this link
    _mavlinkChannelsUsedBitMask &= ~(1 << link->getMavlinkChannel());
258

Don Gagne's avatar
Don Gagne committed
259 260
    _links.removeOne(link);
    delete link;
261

Don Gagne's avatar
Don Gagne committed
262
    // Emit removal of link
263
    emit linkDeleted(link);
pixhawk's avatar
pixhawk committed
264 265
}

266 267 268 269 270
/// @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) {
271
        qgcApp()->showMessage(QString("Connect not allowed: %1").arg(_connectionsSuspendedReason));
272 273 274 275 276 277 278 279 280 281 282 283
        return true;
    } else {
        return false;
    }
}

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

285 286 287 288 289 290 291 292 293
void LinkManager::_linkConnected(void)
{
    emit linkConnected((LinkInterface*)sender());
}

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

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

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

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

    if(linksChanged) {
414
        emit linkConfigurationsChanged();
415 416
    }
    // Enable automatic Serial PX4/3DR Radio hunting
417 418 419
    _configurationsLoaded = true;
}

dogmaphobic's avatar
dogmaphobic committed
420
#ifndef __ios__
Don Gagne's avatar
Don Gagne committed
421
SerialConfiguration* LinkManager::_autoconnectConfigurationsContainsPort(const QString& portName)
422 423
{
    QString searchPort = portName.trimmed();
Don Gagne's avatar
Don Gagne committed
424 425 426 427 428 429 430

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

        if (linkConfig) {
            if (linkConfig->portName() == searchPort) {
                return linkConfig;
431
            }
Don Gagne's avatar
Don Gagne committed
432 433
        } else {
            qWarning() << "Internal error";
434 435 436 437
        }
    }
    return NULL;
}
dogmaphobic's avatar
dogmaphobic committed
438
#endif
439

Don Gagne's avatar
Don Gagne committed
440
void LinkManager::_updateAutoConnectLinks(void)
441
{
Don Gagne's avatar
Don Gagne committed
442
    if (_connectionsSuspended || qgcApp()->runningUnitTests()) {
443 444
        return;
    }
Don Gagne's avatar
Don Gagne committed
445

Don Gagne's avatar
Don Gagne committed
446 447 448 449 450 451 452 453 454 455
    // 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;
        }
    }
    if (!foundUDP) {
Don Gagne's avatar
Don Gagne committed
456
        qCDebug(LinkManagerLog) << "New auto-connect UDP port added";
Don Gagne's avatar
Don Gagne committed
457 458 459
        UDPConfiguration* udpConfig = new UDPConfiguration(_defaultUPDLinkName);
        udpConfig->setLocalPort(QGC_UDP_LOCAL_PORT);
        udpConfig->setDynamic(true);
460
        _linkConfigurations.append(udpConfig);
Don Gagne's avatar
Don Gagne committed
461
        createConnectedLink(udpConfig);
462
        emit linkConfigurationsChanged();
Don Gagne's avatar
Don Gagne committed
463 464
    }

465
#ifndef __ios__
dogmaphobic's avatar
dogmaphobic committed
466
    QStringList currentPorts;
Don Gagne's avatar
Don Gagne committed
467
    QList<QGCSerialPortInfo> portList = QGCSerialPortInfo::availablePorts();
Don Gagne's avatar
Don Gagne committed
468

469
    // Iterate Comm Ports
Don Gagne's avatar
Don Gagne committed
470
    foreach (QGCSerialPortInfo portInfo, portList) {
Don Gagne's avatar
Don Gagne committed
471 472 473 474 475 476 477 478 479
        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
480 481
        // Save port name
        currentPorts << portInfo.systemLocation();
Don Gagne's avatar
Don Gagne committed
482 483 484 485 486 487

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

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

492 493 494 495 496 497 498
            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();
499 500
                _autoconnectWaitList[portInfo.systemLocation()] = 1;
            } else if (++_autoconnectWaitList[portInfo.systemLocation()] * _autoconnectUpdateTimerMSecs > _autoconnectConnectDelayMSecs) {
Don Gagne's avatar
Don Gagne committed
501 502
                SerialConfiguration* pSerialConfig = NULL;

503
                _autoconnectWaitList.remove(portInfo.systemLocation());
504

Don Gagne's avatar
Don Gagne committed
505 506 507
                switch (boardType) {
                case QGCSerialPortInfo::BoardTypePX4FMUV1:
                case QGCSerialPortInfo::BoardTypePX4FMUV2:
508
                case QGCSerialPortInfo::BoardTypePX4FMUV4:
Don Gagne's avatar
Don Gagne committed
509 510 511
                    if (_autoconnectPixhawk) {
                        pSerialConfig = new SerialConfiguration(QString("Pixhawk on %1").arg(portInfo.portName().trimmed()));
                    }
Don Gagne's avatar
Don Gagne committed
512 513
                    break;
                case QGCSerialPortInfo::BoardTypeAeroCore:
Don Gagne's avatar
Don Gagne committed
514 515 516
                    if (_autoconnectPixhawk) {
                        pSerialConfig = new SerialConfiguration(QString("AeroCore on %1").arg(portInfo.portName().trimmed()));
                    }
Don Gagne's avatar
Don Gagne committed
517 518
                    break;
                case QGCSerialPortInfo::BoardTypePX4Flow:
Don Gagne's avatar
Don Gagne committed
519 520 521
                    if (_autoconnectPX4Flow) {
                        pSerialConfig = new SerialConfiguration(QString("PX4Flow on %1").arg(portInfo.portName().trimmed()));
                    }
Don Gagne's avatar
Don Gagne committed
522 523
                    break;
                case QGCSerialPortInfo::BoardType3drRadio:
Don Gagne's avatar
Don Gagne committed
524 525 526 527
                    if (_autoconnect3DRRadio) {
                        pSerialConfig = new SerialConfiguration(QString("3DR Radio on %1").arg(portInfo.portName().trimmed()));
                    }
                    break;
Don Gagne's avatar
Don Gagne committed
528 529
                default:
                    qWarning() << "Internal error";
Don Gagne's avatar
Don Gagne committed
530
                    continue;
dogmaphobic's avatar
dogmaphobic committed
531
                }
Don Gagne's avatar
Don Gagne committed
532

Don Gagne's avatar
Don Gagne committed
533 534 535 536 537 538
                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
539
                    createConnectedLink(pSerialConfig);
Don Gagne's avatar
Don Gagne committed
540
                }
dogmaphobic's avatar
dogmaphobic committed
541 542 543
            }
        }
    }
Don Gagne's avatar
Don Gagne committed
544

dogmaphobic's avatar
dogmaphobic committed
545 546
    // 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
547 548 549 550
    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
551 552 553 554 555 556 557
                // 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
558
            }
Don Gagne's avatar
Don Gagne committed
559 560
        } else {
            qWarning() << "Internal error";
dogmaphobic's avatar
dogmaphobic committed
561 562
        }
    }
Don Gagne's avatar
Don Gagne committed
563

Don Gagne's avatar
Don Gagne committed
564
    // Now remove all configs that are gone
Don Gagne's avatar
Don Gagne committed
565
    foreach (LinkConfiguration* pDeleteConfig, _confToDelete) {
Don Gagne's avatar
Don Gagne committed
566
        qCDebug(LinkManagerLog) << "Removing unused autoconnect config" << pDeleteConfig->name();
Don Gagne's avatar
Don Gagne committed
567 568
        _autoconnectConfigurations.removeOne(pDeleteConfig);
        delete pDeleteConfig;
569
    }
570
#endif // __ios__
571 572
}

Don Gagne's avatar
Don Gagne committed
573 574 575 576 577 578 579 580 581 582 583 584 585 586 587
bool LinkManager::anyConnectedLinks(void)
{
    bool found = false;
    for (int i=0; i<_links.count(); i++) {
        LinkInterface* link = _links.value<LinkInterface*>(i);

        if (link && link->isConnected()) {
            found = true;
            break;
        }
    }
    return found;
}

bool LinkManager::anyActiveLinks(void)
588 589
{
    bool found = false;
Don Gagne's avatar
Don Gagne committed
590 591 592 593
    for (int i=0; i<_links.count(); i++) {
        LinkInterface* link = _links.value<LinkInterface*>(i);

        if (link && link->active()) {
594 595 596 597 598 599 600
            found = true;
            break;
        }
    }
    return found;
}

Don Gagne's avatar
Don Gagne committed
601 602 603 604 605 606 607 608 609 610 611
void LinkManager::_vehicleHeartbeatInfo(LinkInterface* link, int vehicleId, int vehicleMavlinkVersion, int vehicleFirmwareType, int vehicleType)
{
    if (!link->active() && !_ignoreVehicleIds.contains(vehicleId)) {
        qCDebug(LinkManagerLog) << "New heartbeat on link:vehicleId:vehicleMavlinkVersion:vehicleFirmwareType:vehicleType "
                                << link->getName()
                                << vehicleId
                                << vehicleMavlinkVersion
                                << vehicleFirmwareType
                                << vehicleType;

        if (vehicleId == _mavlinkProtocol->getSystemId()) {
612
            _app->showMessage(QString("Warning: A vehicle is using the same system id as QGroundControl: %1").arg(vehicleId));
Don Gagne's avatar
Don Gagne committed
613 614 615 616 617 618
        }

        QSettings settings;
        bool mavlinkVersionCheck = settings.value("VERSION_CHECK_ENABLED", true).toBool();
        if (mavlinkVersionCheck && vehicleMavlinkVersion != MAVLINK_VERSION) {
            _ignoreVehicleIds += vehicleId;
619
            _app->showMessage(QString("The MAVLink protocol version on vehicle #%1 and QGroundControl differ! "
Don Gagne's avatar
Don Gagne committed
620 621 622 623 624 625 626 627 628 629 630 631
                                                 "It is unsafe to use different MAVLink versions. "
                                                 "QGroundControl therefore refuses to connect to vehicle #%1, which sends MAVLink version %2 (QGroundControl uses version %3).").arg(vehicleId).arg(vehicleMavlinkVersion).arg(MAVLINK_VERSION));
            return;
        }

        bool previousAnyActiveLinks = anyActiveLinks();

        link->setActive(true);
        emit linkActive(link, vehicleId, vehicleFirmwareType, vehicleType);

        if (!previousAnyActiveLinks) {
            emit anyActiveLinksChanged(true);
632
        }
Don Gagne's avatar
Don Gagne committed
633 634 635 636 637 638
    }
}

void LinkManager::shutdown(void)
{
    setConnectionsSuspended("Shutdown");
Don Gagne's avatar
Don Gagne committed
639
    disconnectAll();
Don Gagne's avatar
Don Gagne committed
640 641 642 643 644 645 646 647 648 649 650 651
}

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

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

        _autoconnectUDP = autoconnect;
        emit autoconnectUDPChanged(autoconnect);
652
    }
Don Gagne's avatar
Don Gagne committed
653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694
}

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

695
}
696 697 698 699 700 701 702 703 704 705 706 707

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
708
#ifdef QGC_ENABLE_BLUETOOTH
dogmaphobic's avatar
dogmaphobic committed
709 710
        list += "Bluetooth";
#endif
dogmaphobic's avatar
dogmaphobic committed
711
#ifdef QT_DEBUG
712
        list += "Mock Link";
dogmaphobic's avatar
dogmaphobic committed
713 714
#endif
#ifndef __mobile__
715
        list += "Log Replay";
dogmaphobic's avatar
dogmaphobic committed
716
#endif
dogmaphobic's avatar
dogmaphobic committed
717
        Q_ASSERT(list.size() == (int)LinkConfiguration::TypeLast);
718 719 720 721
    }
    return list;
}

722
void LinkManager::_updateSerialPorts()
723
{
724 725
    _commPortList.clear();
    _commPortDisplayList.clear();
726
#ifndef __ios__
727 728
    QList<QSerialPortInfo> portList = QSerialPortInfo::availablePorts();
    foreach (const QSerialPortInfo &info, portList)
729
    {
730 731 732
        QString port = info.systemLocation().trimmed();
        _commPortList += port;
        _commPortDisplayList += SerialConfiguration::cleanPortDisplayname(port);
733 734
    }
#endif
735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751
}

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

QStringList LinkManager::serialPorts(void)
{
    if(!_commPortList.size())
    {
        _updateSerialPorts();
    }
752 753 754 755 756 757 758 759 760 761 762 763
    return _commPortList;
}

QStringList LinkManager::serialBaudRates(void)
{
#ifdef __ios__
    QStringList foo;
    return foo;
#else
    return SerialConfiguration::supportedBaudRates();
#endif
}
764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789

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
790
#ifndef __ios__
791 792
    if((LinkConfiguration::LinkType)type == LinkConfiguration::TypeSerial)
        _updateSerialPorts();
dogmaphobic's avatar
dogmaphobic committed
793
#endif
794 795 796 797 798 799
    return LinkConfiguration::createSettings(type, name);
}

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

884 885 886 887
bool LinkManager::isAutoconnectLink(LinkInterface* link)
{
    return _autoconnectConfigurations.contains(link->getLinkConfiguration());
}
dogmaphobic's avatar
dogmaphobic committed
888 889 890 891 892

bool LinkManager::isBluetoothAvailable(void)
{
    return qgcApp()->isBluetoothAvailable();
}