LinkManager.cc 31.6 KB
Newer Older
1 2
/****************************************************************************
 *
Gus Grubba's avatar
Gus Grubba committed
3
 * (c) 2009-2020 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
4 5 6 7 8
 *
 * QGroundControl is licensed according to the terms in the file
 * COPYING.md in the root of the source code directory.
 *
 ****************************************************************************/
lm's avatar
lm committed
9

pixhawk's avatar
pixhawk committed
10 11
#include <QList>
#include <QApplication>
12
#include <QDebug>
13
#include <QSignalSpy>
dogmaphobic's avatar
dogmaphobic committed
14

15 16
#include <memory>

Gus Grubba's avatar
Gus Grubba committed
17
#ifndef NO_SERIAL_LINK
Don Gagne's avatar
Don Gagne committed
18
#include "QGCSerialPortInfo.h"
dogmaphobic's avatar
dogmaphobic committed
19
#endif
20

21
#include "LinkManager.h"
22
#include "QGCApplication.h"
Don Gagne's avatar
Don Gagne committed
23 24
#include "UDPLink.h"
#include "TCPLink.h"
25
#include "SettingsManager.h"
26
#include "LogReplayLink.h"
dogmaphobic's avatar
dogmaphobic committed
27
#ifdef QGC_ENABLE_BLUETOOTH
dogmaphobic's avatar
dogmaphobic committed
28 29
#include "BluetoothLink.h"
#endif
30

Don Gagne's avatar
Don Gagne committed
31
#ifndef __mobile__
DonLakeFlyer's avatar
DonLakeFlyer committed
32
#include "GPSManager.h"
33
#include "PositionManager.h"
Don Gagne's avatar
Don Gagne committed
34 35
#endif

36 37 38 39
#ifdef QT_DEBUG
#include "MockLink.h"
#endif

Don Gagne's avatar
Don Gagne committed
40
QGC_LOGGING_CATEGORY(LinkManagerLog, "LinkManagerLog")
Don Gagne's avatar
Don Gagne committed
41 42
QGC_LOGGING_CATEGORY(LinkManagerVerboseLog, "LinkManagerVerboseLog")

43
const char* LinkManager::_defaultUDPLinkName =       "UDP Link (AutoConnect)";
44
const char* LinkManager::_mavlinkForwardingLinkName =       "MAVLink Forwarding Link";
45

46 47 48 49 50 51 52 53
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

54 55
LinkManager::LinkManager(QGCApplication* app, QGCToolbox* toolbox)
    : QGCTool(app, toolbox)
56 57 58
    , _configUpdateSuspended(false)
    , _configurationsLoaded(false)
    , _connectionsSuspended(false)
59
    , _mavlinkChannelsUsedBitMask(1)    // We never use channel 0 to avoid sequence numbering problems
60 61
    , _autoConnectSettings(nullptr)
    , _mavlinkProtocol(nullptr)
62 63
    #ifndef __mobile__
    #ifndef NO_SERIAL_LINK
64
    , _nmeaPort(nullptr)
65 66
    #endif
    #endif
pixhawk's avatar
pixhawk committed
67
{
Don Gagne's avatar
Don Gagne committed
68 69 70
    qmlRegisterUncreatableType<LinkManager>         ("QGroundControl", 1, 0, "LinkManager",         "Reference only");
    qmlRegisterUncreatableType<LinkConfiguration>   ("QGroundControl", 1, 0, "LinkConfiguration",   "Reference only");
    qmlRegisterUncreatableType<LinkInterface>       ("QGroundControl", 1, 0, "LinkInterface",       "Reference only");
pixhawk's avatar
pixhawk committed
71 72 73 74
}

LinkManager::~LinkManager()
{
75
#ifndef __mobile__
76
#ifndef NO_SERIAL_LINK
77
    delete _nmeaPort;
78
#endif
79
#endif
pixhawk's avatar
pixhawk committed
80 81
}

82 83
void LinkManager::setToolbox(QGCToolbox *toolbox)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
84
    QGCTool::setToolbox(toolbox);
85

DonLakeFlyer's avatar
DonLakeFlyer committed
86 87
    _autoConnectSettings = toolbox->settingsManager()->autoConnectSettings();
    _mavlinkProtocol = _toolbox->mavlinkProtocol();
88

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

92 93
}

Don Gagne's avatar
Don Gagne committed
94 95 96
// This should only be used by Qml code
void LinkManager::createConnectedLink(LinkConfiguration* config)
{
97 98 99 100
    for(int i = 0; i < _rgLinkConfigs.count(); i++) {
        SharedLinkConfigurationPtr& sharedConfig = _rgLinkConfigs[i];
        if (sharedConfig.get() == config)
            createConnectedLink(sharedConfig);
Don Gagne's avatar
Don Gagne committed
101 102 103
    }
}

104
bool LinkManager::createConnectedLink(SharedLinkConfigurationPtr& config, bool isPX4Flow)
105
{
106
    LinkInterface* link = nullptr;
107

108
    switch(config->type()) {
Gus Grubba's avatar
Gus Grubba committed
109
#ifndef NO_SERIAL_LINK
DonLakeFlyer's avatar
DonLakeFlyer committed
110
    case LinkConfiguration::TypeSerial:
111
        link = new SerialLink(config, isPX4Flow);
Don Gagne's avatar
Don Gagne committed
112
        break;
DonLakeFlyer's avatar
DonLakeFlyer committed
113 114
#else
    Q_UNUSED(isPX4Flow)
dogmaphobic's avatar
dogmaphobic committed
115
#endif
DonLakeFlyer's avatar
DonLakeFlyer committed
116
    case LinkConfiguration::TypeUdp:
117
        link = new UDPLink(config);
DonLakeFlyer's avatar
DonLakeFlyer committed
118 119
        break;
    case LinkConfiguration::TypeTcp:
120
        link = new TCPLink(config);
DonLakeFlyer's avatar
DonLakeFlyer committed
121
        break;
dogmaphobic's avatar
dogmaphobic committed
122
#ifdef QGC_ENABLE_BLUETOOTH
DonLakeFlyer's avatar
DonLakeFlyer committed
123
    case LinkConfiguration::TypeBluetooth:
124
        link = new BluetoothLink(config);
DonLakeFlyer's avatar
DonLakeFlyer committed
125
        break;
dogmaphobic's avatar
dogmaphobic committed
126
#endif
DonLakeFlyer's avatar
DonLakeFlyer committed
127
    case LinkConfiguration::TypeLogReplay:
128
        link = new LogReplayLink(config);
DonLakeFlyer's avatar
DonLakeFlyer committed
129
        break;
130
#ifdef QT_DEBUG
DonLakeFlyer's avatar
DonLakeFlyer committed
131
    case LinkConfiguration::TypeMock:
132
        link = new MockLink(config);
DonLakeFlyer's avatar
DonLakeFlyer committed
133
        break;
134
#endif
DonLakeFlyer's avatar
DonLakeFlyer committed
135 136
    case LinkConfiguration::TypeLast:
        break;
137 138
    }

139
    QScopedPointer<LinkInterface> scopedLink(link);
140

141
    if (link) {
142

143 144 145 146
        int mavlinkChannel = _reserveMavlinkChannel();
        if (mavlinkChannel != 0) {
            link->_setMavlinkChannel(mavlinkChannel);
        } else {
147
            qWarning() << "Ran out of mavlink channels";
148
            return false;
149 150
        }

151 152 153
        SharedLinkInterfacePtr sharedLink(link);
        _rgLinks.append(sharedLink);
        config->setLink(sharedLink);
154

155 156 157 158
        connect(link, &LinkInterface::communicationError,  _app,                &QGCApplication::criticalMessageBoxOnMainThread);
        connect(link, &LinkInterface::bytesReceived,       _mavlinkProtocol,    &MAVLinkProtocol::receiveBytes);
        connect(link, &LinkInterface::bytesSent,           _mavlinkProtocol,    &MAVLinkProtocol::logSentBytes);
        connect(link, &LinkInterface::disconnected,        this,                &LinkManager::_linkDisconnected);
159

160 161
        _mavlinkProtocol->resetMetadataForLink(link);
        _mavlinkProtocol->setVersion(_mavlinkProtocol->getCurrentVersion());
pixhawk's avatar
pixhawk committed
162

163 164 165
        if (!link->_connect()) {
            return false;
        }
166
    }
167 168

    return scopedLink.take() ? true : false;
pixhawk's avatar
pixhawk committed
169 170
}

171
SharedLinkInterfacePtr LinkManager::mavlinkForwardingLink()
pixhawk's avatar
pixhawk committed
172
{
173 174 175 176 177
    for (int i = 0; i < _rgLinks.count(); i++) {
        SharedLinkConfigurationPtr linkConfig = _rgLinks[i]->linkConfiguration();
        if (linkConfig->type() == LinkConfiguration::TypeUdp && linkConfig->name() == _mavlinkForwardingLinkName) {
            SharedLinkInterfacePtr& link = _rgLinks[i];
            return link;
DonLakeFlyer's avatar
DonLakeFlyer committed
178
        }
179
    }
180 181

    return nullptr;
pixhawk's avatar
pixhawk committed
182 183
}

184
void LinkManager::disconnectAll(void)
pixhawk's avatar
pixhawk committed
185
{
186
    QList<SharedLinkInterfacePtr> links = _rgLinks;
Don Gagne's avatar
Don Gagne committed
187

188 189
    for (const SharedLinkInterfacePtr& sharedLink: links) {
        sharedLink->disconnect();
190
    }
pixhawk's avatar
pixhawk committed
191 192
}

193
void LinkManager::_linkDisconnected(void)
194
{
195
    LinkInterface* link = qobject_cast<LinkInterface*>(sender());
Don Gagne's avatar
Don Gagne committed
196

197
    if (!link || !containsLink(link)) {
Don Gagne's avatar
Don Gagne committed
198 199
        return;
    }
200

201
    _freeMavlinkChannel(link->mavlinkChannel());
202 203 204 205 206
    for (int i=0; i<_rgLinks.count(); i++) {
        if (_rgLinks[i].get() == link) {
            qCDebug(LinkManagerLog) << "LinkManager::_linkDisconnected" <<_rgLinks[i]->getName() << _rgLinks[i].use_count();
            _rgLinks.removeAt(i);
            return;
207 208
        }
    }
pixhawk's avatar
pixhawk committed
209 210
}

211
SharedLinkInterfacePtr LinkManager::sharedLinkInterfacePointerForLink(LinkInterface* link)
212
{
213 214 215
    for (int i=0; i<_rgLinks.count(); i++) {
        if (_rgLinks[i].get() == link) {
            return _rgLinks[i];
216 217 218
        }
    }

219
    qWarning() << "LinkManager::sharedLinkInterfaceForLink returning nullptr";
220
    return SharedLinkInterfacePtr(nullptr);
221 222
}

223 224 225 226 227
/// @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) {
228
        qgcApp()->showAppMessage(tr("Connect not allowed: %1").arg(_connectionsSuspendedReason));
229 230 231 232 233 234 235 236 237 238 239
        return true;
    } else {
        return false;
    }
}

void LinkManager::setConnectionsSuspended(QString reason)
{
    _connectionsSuspended = true;
    _connectionsSuspendedReason = reason;
}
240

241 242 243 244 245 246 247 248 249
void LinkManager::suspendConfigurationUpdates(bool suspend)
{
    _configUpdateSuspended = suspend;
}

void LinkManager::saveLinkConfigurationList()
{
    QSettings settings;
    settings.remove(LinkConfiguration::settingsRoot());
250
    int trueCount = 0;
251 252
    for (int i = 0; i < _rgLinkConfigs.count(); i++) {
        SharedLinkConfigurationPtr linkConfig = _rgLinkConfigs[i];
Don Gagne's avatar
Don Gagne committed
253
        if (linkConfig) {
254
            if (!linkConfig->isDynamic()) {
Don Gagne's avatar
Don Gagne committed
255
                QString root = LinkConfiguration::settingsRoot();
256
                root += QString("/Link%1").arg(trueCount++);
Don Gagne's avatar
Don Gagne committed
257 258
                settings.setValue(root + "/name", linkConfig->name());
                settings.setValue(root + "/type", linkConfig->type());
259
                settings.setValue(root + "/auto", linkConfig->isAutoConnect());
260
                settings.setValue(root + "/high_latency", linkConfig->isHighLatency());
Don Gagne's avatar
Don Gagne committed
261 262 263 264
                // Have the instance save its own values
                linkConfig->saveSettings(settings, root);
            }
        } else {
265
            qWarning() << "Internal error for link configuration in LinkManager";
dogmaphobic's avatar
dogmaphobic committed
266
        }
267
    }
dogmaphobic's avatar
dogmaphobic committed
268
    QString root(LinkConfiguration::settingsRoot());
269
    settings.setValue(root + "/count", trueCount);
270 271 272 273 274 275 276 277 278 279 280 281 282
}

void LinkManager::loadLinkConfigurationList()
{
    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")) {
283 284
                LinkConfiguration::LinkType type = static_cast<LinkConfiguration::LinkType>(settings.value(root + "/type").toInt());
                if(type < LinkConfiguration::TypeLast) {
285 286 287
                    if(settings.contains(root + "/name")) {
                        QString name = settings.value(root + "/name").toString();
                        if(!name.isEmpty()) {
288
                            LinkConfiguration* link = nullptr;
289
                            bool autoConnect = settings.value(root + "/auto").toBool();
290
                            bool highLatency = settings.value(root + "/high_latency").toBool();
291

292
                            switch(type) {
Gus Grubba's avatar
Gus Grubba committed
293
#ifndef NO_SERIAL_LINK
DonLakeFlyer's avatar
DonLakeFlyer committed
294
                            case LinkConfiguration::TypeSerial:
295
                                link = new SerialConfiguration(name);
DonLakeFlyer's avatar
DonLakeFlyer committed
296
                                break;
dogmaphobic's avatar
dogmaphobic committed
297
#endif
DonLakeFlyer's avatar
DonLakeFlyer committed
298
                            case LinkConfiguration::TypeUdp:
299
                                link = new UDPConfiguration(name);
DonLakeFlyer's avatar
DonLakeFlyer committed
300 301
                                break;
                            case LinkConfiguration::TypeTcp:
302
                                link = new TCPConfiguration(name);
DonLakeFlyer's avatar
DonLakeFlyer committed
303
                                break;
dogmaphobic's avatar
dogmaphobic committed
304
#ifdef QGC_ENABLE_BLUETOOTH
DonLakeFlyer's avatar
DonLakeFlyer committed
305
                            case LinkConfiguration::TypeBluetooth:
306
                                link = new BluetoothConfiguration(name);
DonLakeFlyer's avatar
DonLakeFlyer committed
307
                                break;
dogmaphobic's avatar
dogmaphobic committed
308
#endif
DonLakeFlyer's avatar
DonLakeFlyer committed
309
                            case LinkConfiguration::TypeLogReplay:
310
                                link = new LogReplayLinkConfiguration(name);
DonLakeFlyer's avatar
DonLakeFlyer committed
311
                                break;
312
#ifdef QT_DEBUG
DonLakeFlyer's avatar
DonLakeFlyer committed
313
                            case LinkConfiguration::TypeMock:
314
                                link = new MockConfiguration(name);
315
                                break;
316
#endif
DonLakeFlyer's avatar
DonLakeFlyer committed
317 318
                            case LinkConfiguration::TypeLast:
                                break;
319
                            }
320
                            if(link) {
321
                                //-- Have the instance load its own values
322 323 324 325
                                link->setAutoConnect(autoConnect);
                                link->setHighLatency(highLatency);
                                link->loadSettings(settings, root);
                                addConfiguration(link);
326 327
                            }
                        } else {
328
                            qWarning() << "Link Configuration" << root << "has an empty name." ;
329 330
                        }
                    } else {
331
                        qWarning() << "Link Configuration" << root << "has no name." ;
332 333
                    }
                } else {
334
                    qWarning() << "Link Configuration" << root << "an invalid type: " << type;
335 336
                }
            } else {
337
                qWarning() << "Link Configuration" << root << "has no type." ;
338 339 340
            }
        }
    }
341 342

    // Enable automatic Serial PX4/3DR Radio hunting
343 344 345
    _configurationsLoaded = true;
}

Gus Grubba's avatar
Gus Grubba committed
346
#ifndef NO_SERIAL_LINK
347
bool LinkManager::_portAlreadyConnected(const QString& portName)
348 349
{
    QString searchPort = portName.trimmed();
Don Gagne's avatar
Don Gagne committed
350

351 352 353
    for (int i=0; i<_rgLinks.count(); i++) {
        SharedLinkConfigurationPtr linkConfig = _rgLinks[i]->linkConfiguration();
        SerialConfiguration* serialConfig = qobject_cast<SerialConfiguration*>(linkConfig.get());
354 355
        if (serialConfig) {
            if (serialConfig->portName() == searchPort) {
356
                return true;
357 358 359
            }
        }
    }
360
    return false;
361
}
dogmaphobic's avatar
dogmaphobic committed
362
#endif
363

364
void LinkManager::_addUDPAutoConnectLink(void)
365
{
366 367 368 369 370 371 372 373 374
    if (_autoConnectSettings->autoConnectUDP()->rawValue().toBool()) {
        bool foundUDP = false;

        for (int i = 0; i < _rgLinks.count(); i++) {
            SharedLinkConfigurationPtr linkConfig = _rgLinks[i]->linkConfiguration();
            if (linkConfig->type() == LinkConfiguration::TypeUdp && linkConfig->name() == _defaultUDPLinkName) {
                foundUDP = true;
                break;
            }
Don Gagne's avatar
Don Gagne committed
375
        }
376

377 378 379 380 381 382 383
        if (!foundUDP) {
            qCDebug(LinkManagerLog) << "New auto-connect UDP port added";
            //-- Default UDPConfiguration is set up for autoconnect
            UDPConfiguration* udpConfig = new UDPConfiguration(_defaultUDPLinkName);
            udpConfig->setDynamic(true);
            SharedLinkConfigurationPtr config = addConfiguration(udpConfig);
            createConnectedLink(config);
384 385
        }
    }
386
}
387

388 389 390 391
void LinkManager::_addMAVLinkForwardingLink(void)
{
    if (_toolbox->settingsManager()->appSettings()->forwardMavlink()->rawValue().toBool()) {
        bool foundMAVLinkForwardingLink = false;
392

393 394 395 396 397 398 399 400
        for (int i=0; i<_rgLinks.count(); i++) {
            SharedLinkConfigurationPtr linkConfig = _rgLinks[i]->linkConfiguration();
            if (linkConfig->type() == LinkConfiguration::TypeUdp && linkConfig->name() == _mavlinkForwardingLinkName) {
                foundMAVLinkForwardingLink = true;
                // TODO: should we check if the host/port matches the mavlinkForwardHostName setting and update if it does not match?
                break;
            }
        }
401

402 403
        if (!foundMAVLinkForwardingLink) {
            qCDebug(LinkManagerLog) << "New MAVLink forwarding port added";
404

405 406
            UDPConfiguration* udpConfig = new UDPConfiguration(_mavlinkForwardingLinkName);
            udpConfig->setDynamic(true);
407

408 409 410 411 412 413
            QString hostName = _toolbox->settingsManager()->appSettings()->forwardMavlinkHostName()->rawValue().toString();
            udpConfig->addHost(hostName);

            SharedLinkConfigurationPtr config = addConfiguration(udpConfig);
            createConnectedLink(config);
        }
414
    }
415 416 417 418 419 420 421 422 423 424
}

void LinkManager::_updateAutoConnectLinks(void)
{
    if (_connectionsSuspended || qgcApp()->runningUnitTests()) {
        return;
    }

    _addUDPAutoConnectLink();
    _addMAVLinkForwardingLink();
425

426
#ifndef __mobile__
427
#ifndef NO_SERIAL_LINK
428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447
    // check to see if nmea gps is configured for UDP input, if so, set it up to connect
    if (_autoConnectSettings->autoConnectNmeaPort()->cookedValueString() == "UDP Port") {
        if (_nmeaSocket.localPort() != _autoConnectSettings->nmeaUdpPort()->rawValue().toUInt()
                || _nmeaSocket.state() != UdpIODevice::BoundState) {
            qCDebug(LinkManagerLog) << "Changing port for UDP NMEA stream";
            _nmeaSocket.close();
            _nmeaSocket.bind(QHostAddress::AnyIPv4, _autoConnectSettings->nmeaUdpPort()->rawValue().toUInt());
            _toolbox->qgcPositionManager()->setNmeaSourceDevice(&_nmeaSocket);
        }
        //close serial port
        if (_nmeaPort) {
            _nmeaPort->close();
            delete _nmeaPort;
            _nmeaPort = nullptr;
            _nmeaDeviceName = "";
        }
    } else {
        _nmeaSocket.close();
    }
#endif
448
#endif
Don Gagne's avatar
Don Gagne committed
449

Gus Grubba's avatar
Gus Grubba committed
450
#ifndef NO_SERIAL_LINK
451 452
    QStringList                 currentPorts;
    QList<QGCSerialPortInfo>    portList;
453 454 455 456
#ifdef __android__
    // Android builds only support a single serial connection. Repeatedly calling availablePorts after that one serial
    // port is connected leaks file handles due to a bug somewhere in android serial code. In order to work around that
    // bug after we connect the first serial port we stop probing for additional ports.
457
    if (!_isSerialPortConnected()) {
458 459
        portList = QGCSerialPortInfo::availablePorts();
    }
460 461 462
    else {
        qDebug() << "Skipping serial port list";
    }
463 464
#else
    portList = QGCSerialPortInfo::availablePorts();
465
#endif
Don Gagne's avatar
Don Gagne committed
466

467
    // Iterate Comm Ports
468
    for (const QGCSerialPortInfo& portInfo: portList) {
Don Gagne's avatar
Don Gagne committed
469 470 471 472 473 474 475 476 477
        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
478 479
        // Save port name
        currentPorts << portInfo.systemLocation();
Don Gagne's avatar
Don Gagne committed
480

481 482
        QGCSerialPortInfo::BoardType_t boardType;
        QString boardName;
Don Gagne's avatar
Don Gagne committed
483

484
#ifndef NO_SERIAL_LINK
485
#ifndef __mobile__
486
        // check to see if nmea gps is configured for current Serial port, if so, set it up to connect
487 488 489 490 491 492
        if (portInfo.systemLocation().trimmed() == _autoConnectSettings->autoConnectNmeaPort()->cookedValueString()) {
            if (portInfo.systemLocation().trimmed() != _nmeaDeviceName) {
                _nmeaDeviceName = portInfo.systemLocation().trimmed();
                qCDebug(LinkManagerLog) << "Configuring nmea port" << _nmeaDeviceName;
                QSerialPort* newPort = new QSerialPort(portInfo);
                _nmeaBaud = _autoConnectSettings->autoConnectNmeaBaud()->cookedValue().toUInt();
493
                newPort->setBaudRate(static_cast<qint32>(_nmeaBaud));
494 495 496 497 498 499 500 501 502
                qCDebug(LinkManagerLog) << "Configuring nmea baudrate" << _nmeaBaud;
                // This will stop polling old device if previously set
                _toolbox->qgcPositionManager()->setNmeaSourceDevice(newPort);
                if (_nmeaPort) {
                    delete _nmeaPort;
                }
                _nmeaPort = newPort;
            } else if (_autoConnectSettings->autoConnectNmeaBaud()->cookedValue().toUInt() != _nmeaBaud) {
                _nmeaBaud = _autoConnectSettings->autoConnectNmeaBaud()->cookedValue().toUInt();
503
                _nmeaPort->setBaudRate(static_cast<qint32>(_nmeaBaud));
504 505
                qCDebug(LinkManagerLog) << "Configuring nmea baudrate" << _nmeaBaud;
            }
506
        } else
507
#endif
508
#endif
509 510 511 512
            if (portInfo.getBoardInfo(boardType, boardName)) {
                if (portInfo.isBootloader()) {
                    // Don't connect to bootloader
                    qCDebug(LinkManagerLog) << "Waiting for bootloader to finish" << portInfo.systemLocation();
Don Gagne's avatar
Don Gagne committed
513
                    continue;
dogmaphobic's avatar
dogmaphobic committed
514
                }
515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535
                if (_portAlreadyConnected(portInfo.systemLocation()) || _autoConnectRTKPort == portInfo.systemLocation()) {
                    qCDebug(LinkManagerVerboseLog) << "Skipping existing autoconnect" << portInfo.systemLocation();
                } else if (!_autoconnectPortWaitList.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();
                    _autoconnectPortWaitList[portInfo.systemLocation()] = 1;
                } else if (++_autoconnectPortWaitList[portInfo.systemLocation()] * _autoconnectUpdateTimerMSecs > _autoconnectConnectDelayMSecs) {
                    SerialConfiguration* pSerialConfig = nullptr;
                    _autoconnectPortWaitList.remove(portInfo.systemLocation());
                    switch (boardType) {
                    case QGCSerialPortInfo::BoardTypePixhawk:
                        if (_autoConnectSettings->autoConnectPixhawk()->rawValue().toBool()) {
                            pSerialConfig = new SerialConfiguration(tr("%1 on %2 (AutoConnect)").arg(boardName).arg(portInfo.portName().trimmed()));
                            pSerialConfig->setUsbDirect(true);
                        }
                        break;
                    case QGCSerialPortInfo::BoardTypePX4Flow:
                        if (_autoConnectSettings->autoConnectPX4Flow()->rawValue().toBool()) {
                            pSerialConfig = new SerialConfiguration(tr("%1 on %2 (AutoConnect)").arg(boardName).arg(portInfo.portName().trimmed()));
536
                        }
537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568
                        break;
                    case QGCSerialPortInfo::BoardTypeSiKRadio:
                        if (_autoConnectSettings->autoConnectSiKRadio()->rawValue().toBool()) {
                            pSerialConfig = new SerialConfiguration(tr("%1 on %2 (AutoConnect)").arg(boardName).arg(portInfo.portName().trimmed()));
                        }
                        break;
                    case QGCSerialPortInfo::BoardTypeOpenPilot:
                        if (_autoConnectSettings->autoConnectLibrePilot()->rawValue().toBool()) {
                            pSerialConfig = new SerialConfiguration(tr("%1 on %2 (AutoConnect)").arg(boardName).arg(portInfo.portName().trimmed()));
                        }
                        break;
#ifndef __mobile__
                    case QGCSerialPortInfo::BoardTypeRTKGPS:
                        if (_autoConnectSettings->autoConnectRTKGPS()->rawValue().toBool() && !_toolbox->gpsManager()->connected()) {
                            qCDebug(LinkManagerLog) << "RTK GPS auto-connected" << portInfo.portName().trimmed();
                            _autoConnectRTKPort = portInfo.systemLocation();
                            _toolbox->gpsManager()->connectGPS(portInfo.systemLocation(), boardName);
                        }
                        break;
#endif
                    default:
                        qWarning() << "Internal error";
                        continue;
                    }
                    if (pSerialConfig) {
                        qCDebug(LinkManagerLog) << "New auto-connect port added: " << pSerialConfig->name() << portInfo.systemLocation();
                        pSerialConfig->setBaud      (boardType == QGCSerialPortInfo::BoardTypeSiKRadio ? 57600 : 115200);
                        pSerialConfig->setDynamic   (true);
                        pSerialConfig->setPortName  (portInfo.systemLocation());

                        SharedLinkConfigurationPtr  sharedConfig(pSerialConfig);
                        createConnectedLink(sharedConfig, boardType == QGCSerialPortInfo::BoardTypePX4Flow);
569
                    }
Don Gagne's avatar
Don Gagne committed
570
                }
571
            }
572
    }
573

574
#ifndef __mobile__
575 576 577 578 579 580
    // Check for RTK GPS connection gone
    if (!_autoConnectRTKPort.isEmpty() && !currentPorts.contains(_autoConnectRTKPort)) {
        qCDebug(LinkManagerLog) << "RTK GPS disconnected" << _autoConnectRTKPort;
        _toolbox->gpsManager()->disconnectGPS();
        _autoConnectRTKPort.clear();
    }
581
#endif
582

Gus Grubba's avatar
Gus Grubba committed
583
#endif // NO_SERIAL_LINK
584 585
}

Don Gagne's avatar
Don Gagne committed
586 587
void LinkManager::shutdown(void)
{
588
    setConnectionsSuspended(tr("Shutdown"));
Don Gagne's avatar
Don Gagne committed
589
    disconnectAll();
590 591 592 593 594

    // Wait for all the vehicles to go away to ensure an orderly shutdown and deletion of all objects
    while (_toolbox->multiVehicleManager()->vehicles()->count()) {
        qgcApp()->processEvents(QEventLoop::ExcludeUserInputEvents);
    }
Don Gagne's avatar
Don Gagne committed
595 596
}

597 598 599 600 601 602
QStringList LinkManager::linkTypeStrings(void) const
{
    //-- Must follow same order as enum LinkType in LinkConfiguration.h
    static QStringList list;
    if(!list.size())
    {
Gus Grubba's avatar
Gus Grubba committed
603
#ifndef NO_SERIAL_LINK
604 605 606 607
        list += tr("Serial");
#endif
        list += tr("UDP");
        list += tr("TCP");
dogmaphobic's avatar
dogmaphobic committed
608
#ifdef QGC_ENABLE_BLUETOOTH
dogmaphobic's avatar
dogmaphobic committed
609 610
        list += "Bluetooth";
#endif
dogmaphobic's avatar
dogmaphobic committed
611
#ifdef QT_DEBUG
612
        list += tr("Mock Link");
dogmaphobic's avatar
dogmaphobic committed
613 614
#endif
#ifndef __mobile__
615
        list += tr("Log Replay");
dogmaphobic's avatar
dogmaphobic committed
616
#endif
617
        if (list.size() != static_cast<int>(LinkConfiguration::TypeLast)) {
DonLakeFlyer's avatar
DonLakeFlyer committed
618 619
            qWarning() << "Internal error";
        }
620 621 622 623
    }
    return list;
}

624
void LinkManager::_updateSerialPorts()
625
{
626 627
    _commPortList.clear();
    _commPortDisplayList.clear();
Gus Grubba's avatar
Gus Grubba committed
628
#ifndef NO_SERIAL_LINK
629
    QList<QSerialPortInfo> portList = QSerialPortInfo::availablePorts();
630
    for (const QSerialPortInfo &info: portList)
631
    {
632 633 634
        QString port = info.systemLocation().trimmed();
        _commPortList += port;
        _commPortDisplayList += SerialConfiguration::cleanPortDisplayname(port);
635 636
    }
#endif
637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653
}

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

QStringList LinkManager::serialPorts(void)
{
    if(!_commPortList.size())
    {
        _updateSerialPorts();
    }
654 655 656 657 658
    return _commPortList;
}

QStringList LinkManager::serialBaudRates(void)
{
Gus Grubba's avatar
Gus Grubba committed
659
#ifdef NO_SERIAL_LINK
660 661 662 663 664 665
    QStringList foo;
    return foo;
#else
    return SerialConfiguration::supportedBaudRates();
#endif
}
666 667 668

bool LinkManager::endConfigurationEditing(LinkConfiguration* config, LinkConfiguration* editedConfig)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
669 670 671 672
    if (config && editedConfig) {
        _fixUnnamed(editedConfig);
        config->copyFrom(editedConfig);
        saveLinkConfigurationList();
673
        emit config->nameChanged(config->name());
DonLakeFlyer's avatar
DonLakeFlyer committed
674 675 676 677 678
        // Discard temporary duplicate
        delete editedConfig;
    } else {
        qWarning() << "Internal error";
    }
679 680 681 682 683
    return true;
}

bool LinkManager::endCreateConfiguration(LinkConfiguration* config)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
684 685 686 687 688 689 690
    if (config) {
        _fixUnnamed(config);
        addConfiguration(config);
        saveLinkConfigurationList();
    } else {
        qWarning() << "Internal error";
    }
691 692 693 694 695
    return true;
}

LinkConfiguration* LinkManager::createConfiguration(int type, const QString& name)
{
Gus Grubba's avatar
Gus Grubba committed
696
#ifndef NO_SERIAL_LINK
697
    if(static_cast<LinkConfiguration::LinkType>(type) == LinkConfiguration::TypeSerial)
698
        _updateSerialPorts();
dogmaphobic's avatar
dogmaphobic committed
699
#endif
700 701 702 703 704
    return LinkConfiguration::createSettings(type, name);
}

LinkConfiguration* LinkManager::startConfigurationEditing(LinkConfiguration* config)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
705
    if (config) {
Gus Grubba's avatar
Gus Grubba committed
706
#ifndef NO_SERIAL_LINK
DonLakeFlyer's avatar
DonLakeFlyer committed
707 708
        if(config->type() == LinkConfiguration::TypeSerial)
            _updateSerialPorts();
dogmaphobic's avatar
dogmaphobic committed
709
#endif
DonLakeFlyer's avatar
DonLakeFlyer committed
710 711 712
        return LinkConfiguration::duplicateSettings(config);
    } else {
        qWarning() << "Internal error";
713
        return nullptr;
DonLakeFlyer's avatar
DonLakeFlyer committed
714
    }
715 716 717 718
}

void LinkManager::_fixUnnamed(LinkConfiguration* config)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
719 720 721 722
    if (config) {
        //-- Check for "Unnamed"
        if (config->name() == "Unnamed") {
            switch(config->type()) {
Gus Grubba's avatar
Gus Grubba committed
723
#ifndef NO_SERIAL_LINK
724 725
            case LinkConfiguration::TypeSerial: {
                QString tname = dynamic_cast<SerialConfiguration*>(config)->portName();
726
#ifdef Q_OS_WIN
727 728 729 730 731 732 733
                tname.replace("\\\\.\\", "");
#else
                tname.replace("/dev/cu.", "");
                tname.replace("/dev/", "");
#endif
                config->setName(QString("Serial Device on %1").arg(tname));
                break;
DonLakeFlyer's avatar
DonLakeFlyer committed
734
            }
735 736 737
#endif
            case LinkConfiguration::TypeUdp:
                config->setName(
738
                            QString("UDP Link on Port %1").arg(dynamic_cast<UDPConfiguration*>(config)->localPort()));
739 740
                break;
            case LinkConfiguration::TypeTcp: {
DonLakeFlyer's avatar
DonLakeFlyer committed
741 742 743
                TCPConfiguration* tconfig = dynamic_cast<TCPConfiguration*>(config);
                if(tconfig) {
                    config->setName(
744
                                QString("TCP Link %1:%2").arg(tconfig->address().toString()).arg(static_cast<int>(tconfig->port())));
745
                }
DonLakeFlyer's avatar
DonLakeFlyer committed
746
            }
747
                break;
dogmaphobic's avatar
dogmaphobic committed
748
#ifdef QGC_ENABLE_BLUETOOTH
dogmaphobic's avatar
dogmaphobic committed
749
            case LinkConfiguration::TypeBluetooth: {
DonLakeFlyer's avatar
DonLakeFlyer committed
750 751 752
                BluetoothConfiguration* tconfig = dynamic_cast<BluetoothConfiguration*>(config);
                if(tconfig) {
                    config->setName(QString("%1 (Bluetooth Device)").arg(tconfig->device().name));
dogmaphobic's avatar
dogmaphobic committed
753
                }
DonLakeFlyer's avatar
DonLakeFlyer committed
754
            }
dogmaphobic's avatar
dogmaphobic committed
755 756
                break;
#endif
757
            case LinkConfiguration::TypeLogReplay: {
DonLakeFlyer's avatar
DonLakeFlyer committed
758 759 760
                LogReplayLinkConfiguration* tconfig = dynamic_cast<LogReplayLinkConfiguration*>(config);
                if(tconfig) {
                    config->setName(QString("Log Replay %1").arg(tconfig->logFilenameShort()));
761
                }
DonLakeFlyer's avatar
DonLakeFlyer committed
762
            }
763 764 765
                break;
#ifdef QT_DEBUG
            case LinkConfiguration::TypeMock:
766
                config->setName(QString("Mock Link"));
767 768 769 770
                break;
#endif
            case LinkConfiguration::TypeLast:
                break;
DonLakeFlyer's avatar
DonLakeFlyer committed
771
            }
772
        }
DonLakeFlyer's avatar
DonLakeFlyer committed
773 774
    } else {
        qWarning() << "Internal error";
775 776 777 778 779
    }
}

void LinkManager::removeConfiguration(LinkConfiguration* config)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
780
    if (config) {
781 782 783
        LinkInterface* link = config->link();
        if (link) {
            link->disconnect();
DonLakeFlyer's avatar
DonLakeFlyer committed
784
        }
785

DonLakeFlyer's avatar
DonLakeFlyer committed
786 787 788 789 790
        _removeConfiguration(config);
        saveLinkConfigurationList();
    } else {
        qWarning() << "Internal error";
    }
791
}
792

793
void LinkManager::_removeConfiguration(LinkConfiguration* config)
794
{
795 796 797 798 799
    _qmlConfigurations.removeOne(config);
    for (int i=0; i<_rgLinkConfigs.count(); i++) {
        if (_rgLinkConfigs[i].get() == config) {
            _rgLinkConfigs.removeAt(i);
            return;
800 801
        }
    }
802
    qWarning() << "LinkManager::_removeConfiguration called with unknown config";
803
}
dogmaphobic's avatar
dogmaphobic committed
804 805 806 807 808

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

810 811
bool LinkManager::containsLink(LinkInterface* link)
{
812 813
    for (int i=0; i<_rgLinks.count(); i++) {
        if (_rgLinks[i].get() == link) {
814 815 816 817 818 819
            return true;
        }
    }
    return false;
}

820
SharedLinkConfigurationPtr LinkManager::addConfiguration(LinkConfiguration* config)
821 822
{
    _qmlConfigurations.append(config);
823 824
    _rgLinkConfigs.append(SharedLinkConfigurationPtr(config));
    return _rgLinkConfigs.last();
825
}
826 827 828

void LinkManager::startAutoConnectedLinks(void)
{
829 830 831
    SharedLinkConfigurationPtr conf;
    for(int i = 0; i < _rgLinkConfigs.count(); i++) {
        conf = _rgLinkConfigs[i];
832 833 834 835
        if (conf->isAutoConnect())
            createConnectedLink(conf);
    }
}
836 837 838 839

int LinkManager::_reserveMavlinkChannel(void)
{
    // Find a mavlink channel to use for this link, Channel 0 is reserved for internal use.
840
    for (uint8_t mavlinkChannel = 1; mavlinkChannel < MAVLINK_COMM_NUM_BUFFERS; mavlinkChannel++) {
841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856
        if (!(_mavlinkChannelsUsedBitMask & 1 << mavlinkChannel)) {
            mavlink_reset_channel_status(mavlinkChannel);
            // Start the channel on Mav 1 protocol
            mavlink_status_t* mavlinkStatus = mavlink_get_channel_status(mavlinkChannel);
            mavlinkStatus->flags |= MAVLINK_STATUS_FLAG_OUT_MAVLINK1;
            _mavlinkChannelsUsedBitMask |= 1 << mavlinkChannel;
            return mavlinkChannel;
        }
    }
    return 0;   // All channels reserved
}

void LinkManager::_freeMavlinkChannel(int channel)
{
    _mavlinkChannelsUsedBitMask &= ~(1 << channel);
}
857

858 859 860 861 862 863
LogReplayLink* LinkManager::startLogReplay(const QString& logFile)
{
    LogReplayLinkConfiguration* linkConfig = new LogReplayLinkConfiguration(tr("Log Replay"));
    linkConfig->setLogFilename(logFile);
    linkConfig->setName(linkConfig->logFilenameShort());

864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880
    SharedLinkConfigurationPtr sharedConfig = addConfiguration(linkConfig);
    if (createConnectedLink(sharedConfig)) {
        return qobject_cast<LogReplayLink*>(sharedConfig->link());
    } else {
        return nullptr;
    }
}

bool LinkManager::_isSerialPortConnected(void)
{
    for (SharedLinkInterfacePtr link: _rgLinks) {
        if (qobject_cast<SerialLink*>(link.get())) {
            return true;
        }
    }

    return false;
881
}