LinkManager.cc 28.7 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 41
#include "LinkManager.h"
#include "MainWindow.h"
Don Gagne's avatar
Don Gagne committed
42
#include "QGCMessageBox.h"
43
#include "QGCApplication.h"
44
#include "QGCApplication.h"
Don Gagne's avatar
Don Gagne committed
45 46
#include "UDPLink.h"
#include "TCPLink.h"
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
LinkManager::LinkManager(QGCApplication* app)
    : QGCTool(app)
60 61 62
    , _configUpdateSuspended(false)
    , _configurationsLoaded(false)
    , _connectionsSuspended(false)
63
    , _mavlinkChannelsUsedBitMask(0)
64
    , _mavlinkProtocol(NULL)
Don Gagne's avatar
Don Gagne committed
65 66 67 68 69
    , _autoconnectUDP(true)
    , _autoconnectPixhawk(true)
    , _autoconnect3DRRadio(true)
    , _autoconnectPX4Flow(true)

pixhawk's avatar
pixhawk committed
70
{
Don Gagne's avatar
Don Gagne committed
71 72 73 74 75
    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;
76

Don Gagne's avatar
Don Gagne committed
77 78 79 80 81
    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
82 83 84 85
}

LinkManager::~LinkManager()
{
Don Gagne's avatar
Don Gagne committed
86 87
    if (anyActiveLinks()) {
        qWarning() << "Why are there still active links?";
88
    }
pixhawk's avatar
pixhawk committed
89 90
}

91 92 93 94 95
void LinkManager::setToolbox(QGCToolbox *toolbox)
{
   QGCTool::setToolbox(toolbox);

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

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

101 102
}

Don Gagne's avatar
Don Gagne committed
103
LinkInterface* LinkManager::createConnectedLink(LinkConfiguration* config)
104 105 106 107
{
    Q_ASSERT(config);
    LinkInterface* pLink = NULL;
    switch(config->type()) {
dogmaphobic's avatar
dogmaphobic committed
108
#ifndef __ios__
109 110 111
        case LinkConfiguration::TypeSerial:
            pLink = new SerialLink(dynamic_cast<SerialConfiguration*>(config));
            break;
dogmaphobic's avatar
dogmaphobic committed
112
#endif
113 114 115
        case LinkConfiguration::TypeUdp:
            pLink = new UDPLink(dynamic_cast<UDPConfiguration*>(config));
            break;
116 117 118
        case LinkConfiguration::TypeTcp:
            pLink = new TCPLink(dynamic_cast<TCPConfiguration*>(config));
            break;
Don Gagne's avatar
Don Gagne committed
119 120 121
        case LinkConfiguration::TypeLogReplay:
            pLink = new LogReplayLink(dynamic_cast<LogReplayLinkConfiguration*>(config));
            break;
122
#ifdef QT_DEBUG
123 124 125
        case LinkConfiguration::TypeMock:
            pLink = new MockLink(dynamic_cast<MockConfiguration*>(config));
            break;
126
#endif
127 128 129
        case LinkConfiguration::TypeLast:
        default:
            break;
130 131
    }
    if(pLink) {
132 133
        _addLink(pLink);
        connectLink(pLink);
134 135 136 137
    }
    return pLink;
}

Don Gagne's avatar
Don Gagne committed
138
LinkInterface* LinkManager::createConnectedLink(const QString& name)
139 140 141
{
    Q_ASSERT(name.isEmpty() == false);
    for(int i = 0; i < _linkConfigurations.count(); i++) {
Don Gagne's avatar
Don Gagne committed
142
        LinkConfiguration* conf = _linkConfigurations.value<LinkConfiguration*>(i);
143
        if(conf && conf->name() == name)
Don Gagne's avatar
Don Gagne committed
144
            return createConnectedLink(conf);
145 146 147 148
    }
    return NULL;
}

149
void LinkManager::_addLink(LinkInterface* link)
pixhawk's avatar
pixhawk committed
150
{
Don Gagne's avatar
Don Gagne committed
151 152 153 154
    if (thread() != QThread::currentThread()) {
        qWarning() << "_deleteLink called from incorrect thread";
        return;
    }
155

Don Gagne's avatar
Don Gagne committed
156 157 158
    if (!link) {
        return;
    }
159

Don Gagne's avatar
Don Gagne committed
160
    if (!_links.contains(link)) {
161 162 163
        // Find a mavlink channel to use for this link
        for (int i=0; i<32; i++) {
            if (!(_mavlinkChannelsUsedBitMask && 1 << i)) {
164
                mavlink_reset_channel_status(i);
165 166 167 168 169
                link->_setMavlinkChannel(i);
                _mavlinkChannelsUsedBitMask |= i << i;
                break;
            }
        }
170

Don Gagne's avatar
Don Gagne committed
171
        _links.append(link);
172 173
        emit newLink(link);
    }
174

175 176
    // MainWindow may be around when doing things like running unit tests
    if (MainWindow::instance()) {
177
        connect(link, &LinkInterface::communicationError, _app, &QGCApplication::criticalMessageBoxOnMainThread);
178
    }
179

180 181 182 183
    connect(link, &LinkInterface::bytesReceived,    _mavlinkProtocol, &MAVLinkProtocol::receiveBytes);
    connect(link, &LinkInterface::connected,        _mavlinkProtocol, &MAVLinkProtocol::linkConnected);
    connect(link, &LinkInterface::disconnected,     _mavlinkProtocol, &MAVLinkProtocol::linkDisconnected);
    _mavlinkProtocol->resetMetadataForLink(link);
184

185
    connect(link, &LinkInterface::connected,    this, &LinkManager::_linkConnected);
186
    connect(link, &LinkInterface::disconnected, this, &LinkManager::_linkDisconnected);
187
}
pixhawk's avatar
pixhawk committed
188

Don Gagne's avatar
Don Gagne committed
189
void LinkManager::disconnectAll(void)
pixhawk's avatar
pixhawk committed
190
{
Don Gagne's avatar
Don Gagne committed
191 192
    // 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
193
        disconnectLink(_links.value<LinkInterface*>(i));
194
    }
pixhawk's avatar
pixhawk committed
195 196 197 198
}

bool LinkManager::connectLink(LinkInterface* link)
{
199
    Q_ASSERT(link);
200

201 202 203 204
    if (_connectionsSuspendedMsg()) {
        return false;
    }

Don Gagne's avatar
Don Gagne committed
205 206
    bool previousAnyConnectedLinks = anyConnectedLinks();

207
    if (link->_connect()) {
Don Gagne's avatar
Don Gagne committed
208 209 210
        if (!previousAnyConnectedLinks) {
            emit anyConnectedLinksChanged(true);
        }
211 212 213 214
        return true;
    } else {
        return false;
    }
pixhawk's avatar
pixhawk committed
215 216
}

Don Gagne's avatar
Don Gagne committed
217
void LinkManager::disconnectLink(LinkInterface* link)
pixhawk's avatar
pixhawk committed
218
{
219
    Q_ASSERT(link);
Don Gagne's avatar
Don Gagne committed
220

Don Gagne's avatar
Don Gagne committed
221 222 223 224
    link->_disconnect();
    LinkConfiguration* config = link->getLinkConfiguration();
    if(config) {
        config->setLink(NULL);
225
    }
Don Gagne's avatar
Don Gagne committed
226
    _deleteLink(link);
pixhawk's avatar
pixhawk committed
227 228
}

229
void LinkManager::_deleteLink(LinkInterface* link)
230
{
Don Gagne's avatar
Don Gagne committed
231 232 233 234 235 236 237 238
    if (thread() != QThread::currentThread()) {
        qWarning() << "_deleteLink called from incorrect thread";
        return;
    }

    if (!link) {
        return;
    }
239

240 241
    // Free up the mavlink channel associated with this link
    _mavlinkChannelsUsedBitMask &= ~(1 << link->getMavlinkChannel());
242

Don Gagne's avatar
Don Gagne committed
243 244
    _links.removeOne(link);
    delete link;
245

Don Gagne's avatar
Don Gagne committed
246
    // Emit removal of link
247
    emit linkDeleted(link);
pixhawk's avatar
pixhawk committed
248 249
}

250 251 252 253 254
/// @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) {
Don Gagne's avatar
Don Gagne committed
255 256
        QGCMessageBox::information(tr("Connect not allowed"),
                                   tr("Connect not allowed: %1").arg(_connectionsSuspendedReason));
257 258 259 260 261 262 263 264 265 266 267 268
        return true;
    } else {
        return false;
    }
}

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

270 271 272 273 274 275 276 277 278
void LinkManager::_linkConnected(void)
{
    emit linkConnected((LinkInterface*)sender());
}

void LinkManager::_linkDisconnected(void)
{
    emit linkDisconnected((LinkInterface*)sender());
}
279 280 281 282 283 284 285 286 287 288

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

void LinkManager::saveLinkConfigurationList()
{
    QSettings settings;
    settings.remove(LinkConfiguration::settingsRoot());
289 290
    int trueCount = 0;
    for (int i = 0; i < _linkConfigurations.count(); i++) {
Don Gagne's avatar
Don Gagne committed
291 292 293 294 295
        LinkConfiguration* linkConfig = _linkConfigurations.value<LinkConfiguration*>(i);
        if (linkConfig) {
            if(!linkConfig->isDynamic())
            {
                QString root = LinkConfiguration::settingsRoot();
296
                root += QString("/Link%1").arg(trueCount++);
Don Gagne's avatar
Don Gagne committed
297 298
                settings.setValue(root + "/name", linkConfig->name());
                settings.setValue(root + "/type", linkConfig->type());
299
                settings.setValue(root + "/auto", linkConfig->isAutoConnect());
Don Gagne's avatar
Don Gagne committed
300 301 302 303 304
                // Have the instance save its own values
                linkConfig->saveSettings(settings, root);
            }
        } else {
            qWarning() << "Internal error";
dogmaphobic's avatar
dogmaphobic committed
305
        }
306
    }
dogmaphobic's avatar
dogmaphobic committed
307
    QString root(LinkConfiguration::settingsRoot());
308 309
    settings.setValue(root + "/count", trueCount);
    emit linkConfigurationsChanged();
310 311 312 313
}

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

    if(linksChanged) {
390
        emit linkConfigurationsChanged();
391 392
    }
    // Enable automatic Serial PX4/3DR Radio hunting
393 394 395
    _configurationsLoaded = true;
}

dogmaphobic's avatar
dogmaphobic committed
396
#ifndef __ios__
Don Gagne's avatar
Don Gagne committed
397
SerialConfiguration* LinkManager::_autoconnectConfigurationsContainsPort(const QString& portName)
398 399
{
    QString searchPort = portName.trimmed();
Don Gagne's avatar
Don Gagne committed
400 401 402 403 404 405 406

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

        if (linkConfig) {
            if (linkConfig->portName() == searchPort) {
                return linkConfig;
407
            }
Don Gagne's avatar
Don Gagne committed
408 409
        } else {
            qWarning() << "Internal error";
410 411 412 413
        }
    }
    return NULL;
}
dogmaphobic's avatar
dogmaphobic committed
414
#endif
415

Don Gagne's avatar
Don Gagne committed
416
void LinkManager::_updateAutoConnectLinks(void)
417
{
Don Gagne's avatar
Don Gagne committed
418
    if (_connectionsSuspended || qgcApp()->runningUnitTests()) {
419 420
        return;
    }
Don Gagne's avatar
Don Gagne committed
421

Don Gagne's avatar
Don Gagne committed
422 423 424 425 426 427 428 429 430 431
    // 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
432
        qCDebug(LinkManagerLog) << "New auto-connect UDP port added";
Don Gagne's avatar
Don Gagne committed
433 434 435
        UDPConfiguration* udpConfig = new UDPConfiguration(_defaultUPDLinkName);
        udpConfig->setLocalPort(QGC_UDP_LOCAL_PORT);
        udpConfig->setDynamic(true);
436
        _linkConfigurations.append(udpConfig);
Don Gagne's avatar
Don Gagne committed
437
        createConnectedLink(udpConfig);
438
        emit linkConfigurationsChanged();
Don Gagne's avatar
Don Gagne committed
439 440
    }

441
#ifndef __ios__
dogmaphobic's avatar
dogmaphobic committed
442
    QStringList currentPorts;
Don Gagne's avatar
Don Gagne committed
443
    QList<QGCSerialPortInfo> portList = QGCSerialPortInfo::availablePorts();
Don Gagne's avatar
Don Gagne committed
444

445
    // Iterate Comm Ports
Don Gagne's avatar
Don Gagne committed
446
    foreach (QGCSerialPortInfo portInfo, portList) {
Don Gagne's avatar
Don Gagne committed
447 448 449 450 451 452 453 454 455
        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
456 457
        // Save port name
        currentPorts << portInfo.systemLocation();
Don Gagne's avatar
Don Gagne committed
458 459 460 461 462 463

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

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

468 469 470 471 472 473 474 475 476
            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();
                _autoconnectWaitList.append(portInfo.systemLocation());
            } else {
Don Gagne's avatar
Don Gagne committed
477 478
                SerialConfiguration* pSerialConfig = NULL;

479 480
                _autoconnectWaitList.removeOne(portInfo.systemLocation());

Don Gagne's avatar
Don Gagne committed
481 482 483
                switch (boardType) {
                case QGCSerialPortInfo::BoardTypePX4FMUV1:
                case QGCSerialPortInfo::BoardTypePX4FMUV2:
Don Gagne's avatar
Don Gagne committed
484 485 486
                    if (_autoconnectPixhawk) {
                        pSerialConfig = new SerialConfiguration(QString("Pixhawk on %1").arg(portInfo.portName().trimmed()));
                    }
Don Gagne's avatar
Don Gagne committed
487 488
                    break;
                case QGCSerialPortInfo::BoardTypeAeroCore:
Don Gagne's avatar
Don Gagne committed
489 490 491
                    if (_autoconnectPixhawk) {
                        pSerialConfig = new SerialConfiguration(QString("AeroCore on %1").arg(portInfo.portName().trimmed()));
                    }
Don Gagne's avatar
Don Gagne committed
492 493
                    break;
                case QGCSerialPortInfo::BoardTypePX4Flow:
Don Gagne's avatar
Don Gagne committed
494 495 496
                    if (_autoconnectPX4Flow) {
                        pSerialConfig = new SerialConfiguration(QString("PX4Flow on %1").arg(portInfo.portName().trimmed()));
                    }
Don Gagne's avatar
Don Gagne committed
497 498
                    break;
                case QGCSerialPortInfo::BoardType3drRadio:
Don Gagne's avatar
Don Gagne committed
499 500 501 502
                    if (_autoconnect3DRRadio) {
                        pSerialConfig = new SerialConfiguration(QString("3DR Radio on %1").arg(portInfo.portName().trimmed()));
                    }
                    break;
Don Gagne's avatar
Don Gagne committed
503 504
                default:
                    qWarning() << "Internal error";
Don Gagne's avatar
Don Gagne committed
505
                    continue;
dogmaphobic's avatar
dogmaphobic committed
506
                }
Don Gagne's avatar
Don Gagne committed
507

Don Gagne's avatar
Don Gagne committed
508 509 510 511 512 513
                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
514
                    createConnectedLink(pSerialConfig);
Don Gagne's avatar
Don Gagne committed
515
                }
dogmaphobic's avatar
dogmaphobic committed
516 517 518
            }
        }
    }
Don Gagne's avatar
Don Gagne committed
519

dogmaphobic's avatar
dogmaphobic committed
520 521
    // 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
522 523 524 525
    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
526 527 528 529 530 531 532
                // 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
533
            }
Don Gagne's avatar
Don Gagne committed
534 535
        } else {
            qWarning() << "Internal error";
dogmaphobic's avatar
dogmaphobic committed
536 537
        }
    }
Don Gagne's avatar
Don Gagne committed
538

Don Gagne's avatar
Don Gagne committed
539
    // Now remove all configs that are gone
Don Gagne's avatar
Don Gagne committed
540
    foreach (LinkConfiguration* pDeleteConfig, _confToDelete) {
Don Gagne's avatar
Don Gagne committed
541
        qCDebug(LinkManagerLog) << "Removing unused autoconnect config" << pDeleteConfig->name();
Don Gagne's avatar
Don Gagne committed
542 543
        _autoconnectConfigurations.removeOne(pDeleteConfig);
        delete pDeleteConfig;
544
    }
545
#endif // __ios__
546 547
}

Don Gagne's avatar
Don Gagne committed
548 549 550 551 552 553 554 555 556 557 558 559 560 561 562
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)
563 564
{
    bool found = false;
Don Gagne's avatar
Don Gagne committed
565 566 567 568
    for (int i=0; i<_links.count(); i++) {
        LinkInterface* link = _links.value<LinkInterface*>(i);

        if (link && link->active()) {
569 570 571 572 573 574 575
            found = true;
            break;
        }
    }
    return found;
}

Don Gagne's avatar
Don Gagne committed
576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606
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()) {
            _app->showToolBarMessage(QString("Warning: A vehicle is using the same system id as QGroundControl: %1").arg(vehicleId));
        }

        QSettings settings;
        bool mavlinkVersionCheck = settings.value("VERSION_CHECK_ENABLED", true).toBool();
        if (mavlinkVersionCheck && vehicleMavlinkVersion != MAVLINK_VERSION) {
            _ignoreVehicleIds += vehicleId;
            _app->showToolBarMessage(QString("The MAVLink protocol version on vehicle #%1 and QGroundControl differ! "
                                                 "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);
607
        }
Don Gagne's avatar
Don Gagne committed
608 609 610 611 612 613
    }
}

void LinkManager::shutdown(void)
{
    setConnectionsSuspended("Shutdown");
Don Gagne's avatar
Don Gagne committed
614
    disconnectAll();
Don Gagne's avatar
Don Gagne committed
615 616 617 618 619 620 621 622 623 624 625 626
}

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

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

        _autoconnectUDP = autoconnect;
        emit autoconnectUDPChanged(autoconnect);
627
    }
Don Gagne's avatar
Don Gagne committed
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 667 668 669
}

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

670
}
671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688

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";
        list += "Mock Link";
        list += "Log Replay";
    }
    return list;
}

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

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

QStringList LinkManager::serialPorts(void)
{
    if(!_commPortList.size())
    {
        _updateSerialPorts();
    }
719 720 721 722 723 724 725 726 727 728 729 730
    return _commPortList;
}

QStringList LinkManager::serialBaudRates(void)
{
#ifdef __ios__
    QStringList foo;
    return foo;
#else
    return SerialConfiguration::supportedBaudRates();
#endif
}
731 732 733 734 735 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 762 763 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 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834

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)
{
    if((LinkConfiguration::LinkType)type == LinkConfiguration::TypeSerial)
        _updateSerialPorts();
    return LinkConfiguration::createSettings(type, name);
}

LinkConfiguration* LinkManager::startConfigurationEditing(LinkConfiguration* config)
{
    Q_ASSERT(config != NULL);
    if(config->type() == LinkConfiguration::TypeSerial)
        _updateSerialPorts();
    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();
#ifdef Q_OS_WIN32
                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;
            case LinkConfiguration::TypeLogReplay: {
                LogReplayLinkConfiguration* tconfig = dynamic_cast<LogReplayLinkConfiguration*>(config);
                if(tconfig) {
                    config->setName(QString("Log Replay %1").arg(tconfig->logFilenameShort()));
                }
            }
                break;
#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();
}