LinkManager.cc 32.1 KB
Newer Older
1 2
/****************************************************************************
 *
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 44
const char* LinkManager::_defaultUDPLinkName =       "UDP Link (AutoConnect)";
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 202 203 204
    disconnect(link, &LinkInterface::communicationError,  _app,                &QGCApplication::criticalMessageBoxOnMainThread);
    disconnect(link, &LinkInterface::bytesReceived,       _mavlinkProtocol,    &MAVLinkProtocol::receiveBytes);
    disconnect(link, &LinkInterface::bytesSent,           _mavlinkProtocol,    &MAVLinkProtocol::logSentBytes);
    disconnect(link, &LinkInterface::disconnected,        this,                &LinkManager::_linkDisconnected);
205

206 207 208 209 210 211
    _freeMavlinkChannel(link->mavlinkChannel());
    for (int i=0; i<_rgLinks.count(); i++) {
        if (_rgLinks[i].get() == link) {
            qCDebug(LinkManagerLog) << "LinkManager::_linkDisconnected" << _rgLinks[i]->linkConfiguration()->name() << _rgLinks[i].use_count();
            _rgLinks.removeAt(i);
            return;
212 213
        }
    }
pixhawk's avatar
pixhawk committed
214 215
}

216
SharedLinkInterfacePtr LinkManager::sharedLinkInterfacePointerForLink(LinkInterface* link)
217
{
218 219 220
    for (int i=0; i<_rgLinks.count(); i++) {
        if (_rgLinks[i].get() == link) {
            return _rgLinks[i];
221 222 223
        }
    }

224
    qWarning() << "LinkManager::sharedLinkInterfaceForLink returning nullptr";
225
    return SharedLinkInterfacePtr(nullptr);
226 227
}

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

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

246 247 248 249 250 251 252 253 254
void LinkManager::suspendConfigurationUpdates(bool suspend)
{
    _configUpdateSuspended = suspend;
}

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

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")) {
288 289
                LinkConfiguration::LinkType type = static_cast<LinkConfiguration::LinkType>(settings.value(root + "/type").toInt());
                if(type < LinkConfiguration::TypeLast) {
290 291 292
                    if(settings.contains(root + "/name")) {
                        QString name = settings.value(root + "/name").toString();
                        if(!name.isEmpty()) {
293
                            LinkConfiguration* link = nullptr;
294
                            bool autoConnect = settings.value(root + "/auto").toBool();
295
                            bool highLatency = settings.value(root + "/high_latency").toBool();
296

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

    // Enable automatic Serial PX4/3DR Radio hunting
348 349 350
    _configurationsLoaded = true;
}

Gus Grubba's avatar
Gus Grubba committed
351
#ifndef NO_SERIAL_LINK
352
bool LinkManager::_portAlreadyConnected(const QString& portName)
353 354
{
    QString searchPort = portName.trimmed();
Don Gagne's avatar
Don Gagne committed
355

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

369
void LinkManager::_addUDPAutoConnectLink(void)
370
{
371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389
    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;
            }
        }

        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);
        }
390
    }
391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417
}

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

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

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

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

            QString hostName = _toolbox->settingsManager()->appSettings()->forwardMavlinkHostName()->rawValue().toString();
            udpConfig->addHost(hostName);

            SharedLinkConfigurationPtr config = addConfiguration(udpConfig);
            createConnectedLink(config);
Don Gagne's avatar
Don Gagne committed
418 419
        }
    }
420 421 422 423 424 425
}

void LinkManager::_updateAutoConnectLinks(void)
{
    if (_connectionsSuspended || qgcApp()->runningUnitTests()) {
        return;
Don Gagne's avatar
Don Gagne committed
426
    }
427 428 429 430

    _addUDPAutoConnectLink();
    _addMAVLinkForwardingLink();

431
#ifndef __mobile__
432
#ifndef NO_SERIAL_LINK
433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452
    // 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
453
#endif
Don Gagne's avatar
Don Gagne committed
454

Gus Grubba's avatar
Gus Grubba committed
455
#ifndef NO_SERIAL_LINK
456 457
    QStringList                 currentPorts;
    QList<QGCSerialPortInfo>    portList;
458 459 460 461
#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.
462
    if (!_isSerialPortConnected()) {
463 464
        portList = QGCSerialPortInfo::availablePorts();
    }
465 466 467
    else {
        qDebug() << "Skipping serial port list";
    }
468 469
#else
    portList = QGCSerialPortInfo::availablePorts();
470
#endif
Don Gagne's avatar
Don Gagne committed
471

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

486 487
        QGCSerialPortInfo::BoardType_t boardType;
        QString boardName;
Don Gagne's avatar
Don Gagne committed
488

489
#ifndef NO_SERIAL_LINK
490
#ifndef __mobile__
491
        // check to see if nmea gps is configured for current Serial port, if so, set it up to connect
492 493 494 495 496 497
        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();
498
                newPort->setBaudRate(static_cast<qint32>(_nmeaBaud));
499 500 501 502 503 504 505 506 507
                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();
508
                _nmeaPort->setBaudRate(static_cast<qint32>(_nmeaBaud));
509 510
                qCDebug(LinkManagerLog) << "Configuring nmea baudrate" << _nmeaBaud;
            }
511
        } else
512
#endif
513
#endif
514 515 516 517
            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
518
                    continue;
dogmaphobic's avatar
dogmaphobic committed
519
                }
520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540
                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()));
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 569 570 571 572 573
                        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);
574
                    }
Don Gagne's avatar
Don Gagne committed
575
                }
576
            }
577
    }
578

579
#ifndef __mobile__
580 581 582 583 584 585
    // Check for RTK GPS connection gone
    if (!_autoConnectRTKPort.isEmpty() && !currentPorts.contains(_autoConnectRTKPort)) {
        qCDebug(LinkManagerLog) << "RTK GPS disconnected" << _autoConnectRTKPort;
        _toolbox->gpsManager()->disconnectGPS();
        _autoConnectRTKPort.clear();
    }
586
#endif
587

Gus Grubba's avatar
Gus Grubba committed
588
#endif // NO_SERIAL_LINK
589 590
}

Don Gagne's avatar
Don Gagne committed
591 592
void LinkManager::shutdown(void)
{
593
    setConnectionsSuspended(tr("Shutdown"));
Don Gagne's avatar
Don Gagne committed
594
    disconnectAll();
595 596 597 598 599

    // 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
600 601
}

602 603 604 605 606 607
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
608
#ifndef NO_SERIAL_LINK
609 610 611 612
        list += tr("Serial");
#endif
        list += tr("UDP");
        list += tr("TCP");
dogmaphobic's avatar
dogmaphobic committed
613
#ifdef QGC_ENABLE_BLUETOOTH
dogmaphobic's avatar
dogmaphobic committed
614 615
        list += "Bluetooth";
#endif
dogmaphobic's avatar
dogmaphobic committed
616
#ifdef QT_DEBUG
617
        list += tr("Mock Link");
dogmaphobic's avatar
dogmaphobic committed
618 619
#endif
#ifndef __mobile__
620
        list += tr("Log Replay");
dogmaphobic's avatar
dogmaphobic committed
621
#endif
622
        if (list.size() != static_cast<int>(LinkConfiguration::TypeLast)) {
DonLakeFlyer's avatar
DonLakeFlyer committed
623 624
            qWarning() << "Internal error";
        }
625 626 627 628
    }
    return list;
}

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

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

QStringList LinkManager::serialPorts(void)
{
    if(!_commPortList.size())
    {
        _updateSerialPorts();
    }
659 660 661 662 663
    return _commPortList;
}

QStringList LinkManager::serialBaudRates(void)
{
Gus Grubba's avatar
Gus Grubba committed
664
#ifdef NO_SERIAL_LINK
665 666 667 668 669 670
    QStringList foo;
    return foo;
#else
    return SerialConfiguration::supportedBaudRates();
#endif
}
671 672 673

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

bool LinkManager::endCreateConfiguration(LinkConfiguration* config)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
689 690 691 692 693 694 695
    if (config) {
        _fixUnnamed(config);
        addConfiguration(config);
        saveLinkConfigurationList();
    } else {
        qWarning() << "Internal error";
    }
696 697 698 699 700
    return true;
}

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

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

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

void LinkManager::removeConfiguration(LinkConfiguration* config)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
785
    if (config) {
786 787 788
        LinkInterface* link = config->link();
        if (link) {
            link->disconnect();
DonLakeFlyer's avatar
DonLakeFlyer committed
789
        }
790

DonLakeFlyer's avatar
DonLakeFlyer committed
791 792 793 794 795
        _removeConfiguration(config);
        saveLinkConfigurationList();
    } else {
        qWarning() << "Internal error";
    }
796
}
797

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

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

815 816
bool LinkManager::containsLink(LinkInterface* link)
{
817 818
    for (int i=0; i<_rgLinks.count(); i++) {
        if (_rgLinks[i].get() == link) {
819 820 821 822 823 824
            return true;
        }
    }
    return false;
}

825
SharedLinkConfigurationPtr LinkManager::addConfiguration(LinkConfiguration* config)
826 827
{
    _qmlConfigurations.append(config);
828 829
    _rgLinkConfigs.append(SharedLinkConfigurationPtr(config));
    return _rgLinkConfigs.last();
830
}
831 832 833

void LinkManager::startAutoConnectedLinks(void)
{
834 835 836
    SharedLinkConfigurationPtr conf;
    for(int i = 0; i < _rgLinkConfigs.count(); i++) {
        conf = _rgLinkConfigs[i];
837 838 839 840
        if (conf->isAutoConnect())
            createConnectedLink(conf);
    }
}
841 842 843 844

int LinkManager::_reserveMavlinkChannel(void)
{
    // Find a mavlink channel to use for this link, Channel 0 is reserved for internal use.
845
    for (uint8_t mavlinkChannel = 1; mavlinkChannel < MAVLINK_COMM_NUM_BUFFERS; mavlinkChannel++) {
846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861
        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);
}
862

863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885
LogReplayLink* LinkManager::startLogReplay(const QString& logFile)
{
    LogReplayLinkConfiguration* linkConfig = new LogReplayLinkConfiguration(tr("Log Replay"));
    linkConfig->setLogFilename(logFile);
    linkConfig->setName(linkConfig->logFilenameShort());

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