LinkManager.cc 30.5 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(0)
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
    connect(link, &LinkInterface::connected,    this, &LinkManager::_linkConnected);
218
    connect(link, &LinkInterface::disconnected, this, &LinkManager::_linkDisconnected);
219
}
pixhawk's avatar
pixhawk committed
220

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

bool LinkManager::connectLink(LinkInterface* link)
{
231
    Q_ASSERT(link);
232

233 234 235 236
    if (_connectionsSuspendedMsg()) {
        return false;
    }

237
    return link->_connect();
pixhawk's avatar
pixhawk committed
238 239
}

Don Gagne's avatar
Don Gagne committed
240
void LinkManager::disconnectLink(LinkInterface* link)
pixhawk's avatar
pixhawk committed
241
{
242
    Q_ASSERT(link);
Don Gagne's avatar
Don Gagne committed
243

Don Gagne's avatar
Don Gagne committed
244 245
    link->_disconnect();
    LinkConfiguration* config = link->getLinkConfiguration();
246 247 248 249
    if (config) {
        if (_autoconnectConfigurations.contains(config)) {
            config->setLink(NULL);
        }
250
    }
Don Gagne's avatar
Don Gagne committed
251
    _deleteLink(link);
252
    if (_autoconnectConfigurations.contains(config)) {
253
        qCDebug(LinkManagerLog) << "Removing disconnected autoconnect config" << config->name();
254 255 256
        _autoconnectConfigurations.removeOne(config);
        delete config;
    }
pixhawk's avatar
pixhawk committed
257 258
}

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

    if (!link) {
        return;
    }
269

270 271
    // Free up the mavlink channel associated with this link
    _mavlinkChannelsUsedBitMask &= ~(1 << link->getMavlinkChannel());
272

Don Gagne's avatar
Don Gagne committed
273 274
    _links.removeOne(link);
    delete link;
275

Don Gagne's avatar
Don Gagne committed
276
    // Emit removal of link
277
    emit linkDeleted(link);
pixhawk's avatar
pixhawk committed
278 279
}

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

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

299 300 301 302 303 304 305 306 307
void LinkManager::_linkConnected(void)
{
    emit linkConnected((LinkInterface*)sender());
}

void LinkManager::_linkDisconnected(void)
{
    emit linkDisconnected((LinkInterface*)sender());
}
308 309 310 311 312 313 314 315 316 317

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

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

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

    if(linksChanged) {
428
        emit linkConfigurationsChanged();
429 430
    }
    // Enable automatic Serial PX4/3DR Radio hunting
431 432 433
    _configurationsLoaded = true;
}

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

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

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

Don Gagne's avatar
Don Gagne committed
454
void LinkManager::_updateAutoConnectLinks(void)
455
{
Don Gagne's avatar
Don Gagne committed
456
    if (_connectionsSuspended || qgcApp()->runningUnitTests()) {
457 458
        return;
    }
Don Gagne's avatar
Don Gagne committed
459

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

479
#ifndef __ios__
dogmaphobic's avatar
dogmaphobic committed
480
    QStringList currentPorts;
Don Gagne's avatar
Don Gagne committed
481
    QList<QGCSerialPortInfo> portList = QGCSerialPortInfo::availablePorts();
Don Gagne's avatar
Don Gagne committed
482

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

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

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

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

517
                _autoconnectWaitList.remove(portInfo.systemLocation());
518

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

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

dogmaphobic's avatar
dogmaphobic committed
561 562
    // 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
563 564 565 566
    for (int i=0; i<_autoconnectConfigurations.count(); i++) {
        SerialConfiguration* linkConfig = _autoconnectConfigurations.value<SerialConfiguration*>(i);
        if (linkConfig) {
            if (!currentPorts.contains(linkConfig->portName())) {
567 568 569 570 571 572 573 574 575
                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
576
                }
577
                _confToDelete.append(linkConfig);
dogmaphobic's avatar
dogmaphobic committed
578
            }
Don Gagne's avatar
Don Gagne committed
579 580
        } else {
            qWarning() << "Internal error";
dogmaphobic's avatar
dogmaphobic committed
581 582
        }
    }
Don Gagne's avatar
Don Gagne committed
583

Don Gagne's avatar
Don Gagne committed
584
    // Now remove all configs that are gone
Don Gagne's avatar
Don Gagne committed
585
    foreach (LinkConfiguration* pDeleteConfig, _confToDelete) {
Don Gagne's avatar
Don Gagne committed
586
        qCDebug(LinkManagerLog) << "Removing unused autoconnect config" << pDeleteConfig->name();
Don Gagne's avatar
Don Gagne committed
587
        _autoconnectConfigurations.removeOne(pDeleteConfig);
588 589 590
        if (pDeleteConfig->link()) {
            disconnectLink(pDeleteConfig->link());
        }
Don Gagne's avatar
Don Gagne committed
591
        delete pDeleteConfig;
592
    }
593
#endif // __ios__
594 595
}

Don Gagne's avatar
Don Gagne committed
596 597 598
void LinkManager::shutdown(void)
{
    setConnectionsSuspended("Shutdown");
Don Gagne's avatar
Don Gagne committed
599
    disconnectAll();
Don Gagne's avatar
Don Gagne committed
600 601 602 603 604 605 606 607 608 609 610 611
}

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

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

        _autoconnectUDP = autoconnect;
        emit autoconnectUDPChanged(autoconnect);
612
    }
Don Gagne's avatar
Don Gagne committed
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 644 645 646 647 648 649 650 651 652 653 654
}

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

655
}
656 657 658 659 660 661 662 663 664 665 666 667

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
668
#ifdef QGC_ENABLE_BLUETOOTH
dogmaphobic's avatar
dogmaphobic committed
669 670
        list += "Bluetooth";
#endif
dogmaphobic's avatar
dogmaphobic committed
671
#ifdef QT_DEBUG
672
        list += "Mock Link";
dogmaphobic's avatar
dogmaphobic committed
673 674
#endif
#ifndef __mobile__
675
        list += "Log Replay";
dogmaphobic's avatar
dogmaphobic committed
676
#endif
dogmaphobic's avatar
dogmaphobic committed
677
        Q_ASSERT(list.size() == (int)LinkConfiguration::TypeLast);
678 679 680 681
    }
    return list;
}

682
void LinkManager::_updateSerialPorts()
683
{
684 685
    _commPortList.clear();
    _commPortDisplayList.clear();
686
#ifndef __ios__
687 688
    QList<QSerialPortInfo> portList = QSerialPortInfo::availablePorts();
    foreach (const QSerialPortInfo &info, portList)
689
    {
690 691 692
        QString port = info.systemLocation().trimmed();
        _commPortList += port;
        _commPortDisplayList += SerialConfiguration::cleanPortDisplayname(port);
693 694
    }
#endif
695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711
}

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

QStringList LinkManager::serialPorts(void)
{
    if(!_commPortList.size())
    {
        _updateSerialPorts();
    }
712 713 714 715 716 717 718 719 720 721 722 723
    return _commPortList;
}

QStringList LinkManager::serialBaudRates(void)
{
#ifdef __ios__
    QStringList foo;
    return foo;
#else
    return SerialConfiguration::supportedBaudRates();
#endif
}
724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749

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
750
#ifndef __ios__
751 752
    if((LinkConfiguration::LinkType)type == LinkConfiguration::TypeSerial)
        _updateSerialPorts();
dogmaphobic's avatar
dogmaphobic committed
753
#endif
754 755 756 757 758 759
    return LinkConfiguration::createSettings(type, name);
}

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

844 845 846 847
bool LinkManager::isAutoconnectLink(LinkInterface* link)
{
    return _autoconnectConfigurations.contains(link->getLinkConfiguration());
}
dogmaphobic's avatar
dogmaphobic committed
848 849 850 851 852

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

Don Gagne's avatar
Don Gagne committed
854
#ifndef __ios__
Don Gagne's avatar
Don Gagne committed
855 856
void LinkManager::_activeLinkCheck(void)
{
857
    SerialLink* link = NULL;
Don Gagne's avatar
Don Gagne committed
858 859 860
    bool found = false;

    if (_activeLinkCheckList.count() != 0) {
861
        link = _activeLinkCheckList.takeFirst();
Don Gagne's avatar
Don Gagne committed
862 863 864 865 866 867 868 869 870 871
        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;
                }
            }
872 873
        } else {
            link = NULL;
Don Gagne's avatar
Don Gagne committed
874 875 876 877 878 879 880
        }
    }

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

881 882 883 884 885 886 887 888 889 890 891 892 893 894 895
    if (!found && link) {
        // See if we can get an NSH prompt on this link
        bool foundNSHPrompt = false;
        link->writeBytes("\r", 1);
        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
896 897
    }
}
Don Gagne's avatar
Don Gagne committed
898
#endif