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

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

67 68
LinkManager::LinkManager(QGCApplication* app)
    : QGCTool(app)
69 70 71
    , _configUpdateSuspended(false)
    , _configurationsLoaded(false)
    , _connectionsSuspended(false)
72
    , _mavlinkChannelsUsedBitMask(1)    // We never use channel 0 to avoid sequence numbering problems
73
    , _mavlinkProtocol(NULL)
Don Gagne's avatar
Don Gagne committed
74 75 76 77 78
    , _autoconnectUDP(true)
    , _autoconnectPixhawk(true)
    , _autoconnect3DRRadio(true)
    , _autoconnectPX4Flow(true)

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

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

Don Gagne's avatar
Don Gagne committed
92
#ifndef __ios__
Don Gagne's avatar
Don Gagne committed
93 94 95
    _activeLinkCheckTimer.setInterval(_activeLinkCheckTimeoutMSecs);
    _activeLinkCheckTimer.setSingleShot(false);
    connect(&_activeLinkCheckTimer, &QTimer::timeout, this, &LinkManager::_activeLinkCheck);
Don Gagne's avatar
Don Gagne committed
96
#endif
pixhawk's avatar
pixhawk committed
97 98 99 100
}

LinkManager::~LinkManager()
{
101

pixhawk's avatar
pixhawk committed
102 103
}

104 105 106 107 108 109
void LinkManager::setToolbox(QGCToolbox *toolbox)
{
   QGCTool::setToolbox(toolbox);

   _mavlinkProtocol = _toolbox->mavlinkProtocol();

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

113 114
}

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

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

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

Don Gagne's avatar
Don Gagne committed
186 187 188
    if (!link) {
        return;
    }
189

Don Gagne's avatar
Don Gagne committed
190
    if (!_links.contains(link)) {
191 192
        bool channelSet = false;

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

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

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

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

215
    _mavlinkProtocol->resetMetadataForLink(link);
216

217 218 219 220 221 222
    connect(link, &LinkInterface::connected,            this, &LinkManager::_linkConnected);
    connect(link, &LinkInterface::disconnected,         this, &LinkManager::_linkDisconnected);

    // This connection is queued since it will cloe the link. So we want the link emitter to return otherwise we would
    // close the link our from under itself.
    connect(link, &LinkInterface::connectionRemoved,    this, &LinkManager::_linkConnectionRemoved, Qt::QueuedConnection);
223
}
pixhawk's avatar
pixhawk committed
224

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

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

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

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

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

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

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

    if (!link) {
        return;
    }
275

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

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

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

286 287 288 289 290
/// @brief If all new connections should be suspended a message is displayed to the user and true
///         is returned;
bool LinkManager::_connectionsSuspendedMsg(void)
{
    if (_connectionsSuspended) {
291
        qgcApp()->showMessage(QString("Connect not allowed: %1").arg(_connectionsSuspendedReason));
292 293 294 295 296 297 298 299 300 301 302 303
        return true;
    } else {
        return false;
    }
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

495
    // Iterate Comm Ports
Don Gagne's avatar
Don Gagne committed
496
    foreach (QGCSerialPortInfo portInfo, portList) {
Don Gagne's avatar
Don Gagne committed
497 498 499 500 501 502 503 504 505
        qCDebug(LinkManagerVerboseLog) << "-----------------------------------------------------";
        qCDebug(LinkManagerVerboseLog) << "portName:          " << portInfo.portName();
        qCDebug(LinkManagerVerboseLog) << "systemLocation:    " << portInfo.systemLocation();
        qCDebug(LinkManagerVerboseLog) << "description:       " << portInfo.description();
        qCDebug(LinkManagerVerboseLog) << "manufacturer:      " << portInfo.manufacturer();
        qCDebug(LinkManagerVerboseLog) << "serialNumber:      " << portInfo.serialNumber();
        qCDebug(LinkManagerVerboseLog) << "vendorIdentifier:  " << portInfo.vendorIdentifier();
        qCDebug(LinkManagerVerboseLog) << "productIdentifier: " << portInfo.productIdentifier();

dogmaphobic's avatar
dogmaphobic committed
506 507
        // Save port name
        currentPorts << portInfo.systemLocation();
Don Gagne's avatar
Don Gagne committed
508 509 510 511 512 513

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

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

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

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

Don Gagne's avatar
Don Gagne committed
531 532 533
                switch (boardType) {
                case QGCSerialPortInfo::BoardTypePX4FMUV1:
                case QGCSerialPortInfo::BoardTypePX4FMUV2:
534
                case QGCSerialPortInfo::BoardTypePX4FMUV4:
Don Gagne's avatar
Don Gagne committed
535 536
                    if (_autoconnectPixhawk) {
                        pSerialConfig = new SerialConfiguration(QString("Pixhawk on %1").arg(portInfo.portName().trimmed()));
537
                        pSerialConfig->setUsbDirect(true);
Don Gagne's avatar
Don Gagne committed
538
                    }
Don Gagne's avatar
Don Gagne committed
539 540
                    break;
                case QGCSerialPortInfo::BoardTypeAeroCore:
Don Gagne's avatar
Don Gagne committed
541 542
                    if (_autoconnectPixhawk) {
                        pSerialConfig = new SerialConfiguration(QString("AeroCore on %1").arg(portInfo.portName().trimmed()));
543
                        pSerialConfig->setUsbDirect(true);
Don Gagne's avatar
Don Gagne committed
544
                    }
Don Gagne's avatar
Don Gagne committed
545 546
                    break;
                case QGCSerialPortInfo::BoardTypePX4Flow:
Don Gagne's avatar
Don Gagne committed
547 548 549
                    if (_autoconnectPX4Flow) {
                        pSerialConfig = new SerialConfiguration(QString("PX4Flow on %1").arg(portInfo.portName().trimmed()));
                    }
Don Gagne's avatar
Don Gagne committed
550
                    break;
Don Gagne's avatar
Don Gagne committed
551
                case QGCSerialPortInfo::BoardTypeSikRadio:
Don Gagne's avatar
Don Gagne committed
552
                    if (_autoconnect3DRRadio) {
553
                        pSerialConfig = new SerialConfiguration(QString("SiK Radio on %1").arg(portInfo.portName().trimmed()));
Don Gagne's avatar
Don Gagne committed
554 555
                    }
                    break;
Don Gagne's avatar
Don Gagne committed
556 557
                default:
                    qWarning() << "Internal error";
Don Gagne's avatar
Don Gagne committed
558
                    continue;
dogmaphobic's avatar
dogmaphobic committed
559
                }
Don Gagne's avatar
Don Gagne committed
560

Don Gagne's avatar
Don Gagne committed
561 562
                if (pSerialConfig) {
                    qCDebug(LinkManagerLog) << "New auto-connect port added: " << pSerialConfig->name() << portInfo.systemLocation();
Don Gagne's avatar
Don Gagne committed
563
                    pSerialConfig->setBaud(boardType == QGCSerialPortInfo::BoardTypeSikRadio ? 57600 : 115200);
Don Gagne's avatar
Don Gagne committed
564 565 566
                    pSerialConfig->setDynamic(true);
                    pSerialConfig->setPortName(portInfo.systemLocation());
                    _autoconnectConfigurations.append(pSerialConfig);
Don Gagne's avatar
Don Gagne committed
567
                    createConnectedLink(pSerialConfig);
Don Gagne's avatar
Don Gagne committed
568
                }
dogmaphobic's avatar
dogmaphobic committed
569 570 571
            }
        }
    }
Don Gagne's avatar
Don Gagne committed
572

dogmaphobic's avatar
dogmaphobic committed
573 574
    // 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
575 576 577 578
    for (int i=0; i<_autoconnectConfigurations.count(); i++) {
        SerialConfiguration* linkConfig = _autoconnectConfigurations.value<SerialConfiguration*>(i);
        if (linkConfig) {
            if (!currentPorts.contains(linkConfig->portName())) {
579 580 581 582 583 584 585 586 587
                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
588
                }
589
                _confToDelete.append(linkConfig);
dogmaphobic's avatar
dogmaphobic committed
590
            }
Don Gagne's avatar
Don Gagne committed
591 592
        } else {
            qWarning() << "Internal error";
dogmaphobic's avatar
dogmaphobic committed
593 594
        }
    }
Don Gagne's avatar
Don Gagne committed
595

Don Gagne's avatar
Don Gagne committed
596
    // Now remove all configs that are gone
Don Gagne's avatar
Don Gagne committed
597
    foreach (LinkConfiguration* pDeleteConfig, _confToDelete) {
Don Gagne's avatar
Don Gagne committed
598
        qCDebug(LinkManagerLog) << "Removing unused autoconnect config" << pDeleteConfig->name();
Don Gagne's avatar
Don Gagne committed
599
        _autoconnectConfigurations.removeOne(pDeleteConfig);
600 601 602
        if (pDeleteConfig->link()) {
            disconnectLink(pDeleteConfig->link());
        }
Don Gagne's avatar
Don Gagne committed
603
        delete pDeleteConfig;
604
    }
605
#endif // __ios__
606 607
}

Don Gagne's avatar
Don Gagne committed
608 609 610
void LinkManager::shutdown(void)
{
    setConnectionsSuspended("Shutdown");
Don Gagne's avatar
Don Gagne committed
611
    disconnectAll();
Don Gagne's avatar
Don Gagne committed
612 613 614 615 616 617 618 619 620 621 622 623
}

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

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

        _autoconnectUDP = autoconnect;
        emit autoconnectUDPChanged(autoconnect);
624
    }
Don Gagne's avatar
Don Gagne committed
625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666
}

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

667
}
668 669 670 671 672 673 674 675 676 677 678 679

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
680
#ifdef QGC_ENABLE_BLUETOOTH
dogmaphobic's avatar
dogmaphobic committed
681 682
        list += "Bluetooth";
#endif
dogmaphobic's avatar
dogmaphobic committed
683
#ifdef QT_DEBUG
684
        list += "Mock Link";
dogmaphobic's avatar
dogmaphobic committed
685 686
#endif
#ifndef __mobile__
687
        list += "Log Replay";
dogmaphobic's avatar
dogmaphobic committed
688
#endif
dogmaphobic's avatar
dogmaphobic committed
689
        Q_ASSERT(list.size() == (int)LinkConfiguration::TypeLast);
690 691 692 693
    }
    return list;
}

694
void LinkManager::_updateSerialPorts()
695
{
696 697
    _commPortList.clear();
    _commPortDisplayList.clear();
698
#ifndef __ios__
699 700
    QList<QSerialPortInfo> portList = QSerialPortInfo::availablePorts();
    foreach (const QSerialPortInfo &info, portList)
701
    {
702 703 704
        QString port = info.systemLocation().trimmed();
        _commPortList += port;
        _commPortDisplayList += SerialConfiguration::cleanPortDisplayname(port);
705 706
    }
#endif
707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723
}

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

QStringList LinkManager::serialPorts(void)
{
    if(!_commPortList.size())
    {
        _updateSerialPorts();
    }
724 725 726 727 728 729 730 731 732 733 734 735
    return _commPortList;
}

QStringList LinkManager::serialBaudRates(void)
{
#ifdef __ios__
    QStringList foo;
    return foo;
#else
    return SerialConfiguration::supportedBaudRates();
#endif
}
736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761

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
762
#ifndef __ios__
763 764
    if((LinkConfiguration::LinkType)type == LinkConfiguration::TypeSerial)
        _updateSerialPorts();
dogmaphobic's avatar
dogmaphobic committed
765
#endif
766 767 768 769 770 771
    return LinkConfiguration::createSettings(type, name);
}

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

856 857 858 859
bool LinkManager::isAutoconnectLink(LinkInterface* link)
{
    return _autoconnectConfigurations.contains(link->getLinkConfiguration());
}
dogmaphobic's avatar
dogmaphobic committed
860 861 862 863 864

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

Don Gagne's avatar
Don Gagne committed
866
#ifndef __ios__
Don Gagne's avatar
Don Gagne committed
867 868
void LinkManager::_activeLinkCheck(void)
{
869
    SerialLink* link = NULL;
Don Gagne's avatar
Don Gagne committed
870 871 872
    bool found = false;

    if (_activeLinkCheckList.count() != 0) {
873
        link = _activeLinkCheckList.takeFirst();
Don Gagne's avatar
Don Gagne committed
874 875 876 877 878 879 880 881 882 883
        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;
                }
            }
884 885
        } else {
            link = NULL;
Don Gagne's avatar
Don Gagne committed
886 887 888 889 890 891 892
        }
    }

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

893 894 895
    if (!found && link) {
        // See if we can get an NSH prompt on this link
        bool foundNSHPrompt = false;
896
        link->writeBytesSafe("\r", 1);
897 898 899 900 901 902 903 904 905 906 907
        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
908 909
    }
}
Don Gagne's avatar
Don Gagne committed
910
#endif