Skip to content
Snippets Groups Projects
LinkManager.cc 35.4 KiB
Newer Older
  • Learn to ignore specific revisions
  • /****************************************************************************
     *
     *   (c) 2009-2016 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
     *
     * QGroundControl is licensed according to the terms in the file
     * COPYING.md in the root of the source code directory.
     *
     ****************************************************************************/
    
    pixhawk's avatar
    pixhawk committed
    #include <QList>
    #include <QApplication>
    
    #include <QSignalSpy>
    
    Gus Grubba's avatar
    Gus Grubba committed
    #ifndef NO_SERIAL_LINK
    
    Don Gagne's avatar
    Don Gagne committed
    #include "QGCSerialPortInfo.h"
    
    dogmaphobic's avatar
    dogmaphobic committed
    #endif
    
    #include "LinkManager.h"
    
    #include "QGCApplication.h"
    
    Don Gagne's avatar
    Don Gagne committed
    #include "UDPLink.h"
    #include "TCPLink.h"
    
    #include "SettingsManager.h"
    
    dogmaphobic's avatar
    dogmaphobic committed
    #ifdef QGC_ENABLE_BLUETOOTH
    
    dogmaphobic's avatar
    dogmaphobic committed
    #include "BluetoothLink.h"
    #endif
    
    Don Gagne's avatar
    Don Gagne committed
    #ifndef __mobile__
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    #include "GPSManager.h"
    
    #include "PositionManager.h"
    
    Don Gagne's avatar
    Don Gagne committed
    #endif
    
    
    Don Gagne's avatar
    Don Gagne committed
    QGC_LOGGING_CATEGORY(LinkManagerLog, "LinkManagerLog")
    
    Don Gagne's avatar
    Don Gagne committed
    QGC_LOGGING_CATEGORY(LinkManagerVerboseLog, "LinkManagerVerboseLog")
    
    
    Don Gagne's avatar
    Don Gagne committed
    const char* LinkManager::_defaultUPDLinkName =       "UDP Link (AutoConnect)";
    
    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
    
    
    LinkManager::LinkManager(QGCApplication* app, QGCToolbox* toolbox)
        : QGCTool(app, toolbox)
    
        , _configUpdateSuspended(false)
        , _configurationsLoaded(false)
        , _connectionsSuspended(false)
    
        , _mavlinkChannelsUsedBitMask(1)    // We never use channel 0 to avoid sequence numbering problems
    
        , _autoConnectSettings(NULL)
    
        , _mavlinkProtocol(NULL)
    
    #ifndef __mobile__
    
        , _nmeaPort(NULL)
    
    pixhawk's avatar
    pixhawk committed
    {
    
    Don Gagne's avatar
    Don Gagne committed
        qmlRegisterUncreatableType<LinkManager>         ("QGroundControl", 1, 0, "LinkManager",         "Reference only");
        qmlRegisterUncreatableType<LinkConfiguration>   ("QGroundControl", 1, 0, "LinkConfiguration",   "Reference only");
        qmlRegisterUncreatableType<LinkInterface>       ("QGroundControl", 1, 0, "LinkInterface",       "Reference only");
    
    
    Gus Grubba's avatar
    Gus Grubba committed
    #ifndef NO_SERIAL_LINK
    
    Don Gagne's avatar
    Don Gagne committed
        _activeLinkCheckTimer.setInterval(_activeLinkCheckTimeoutMSecs);
        _activeLinkCheckTimer.setSingleShot(false);
        connect(&_activeLinkCheckTimer, &QTimer::timeout, this, &LinkManager::_activeLinkCheck);
    
    Don Gagne's avatar
    Don Gagne committed
    #endif
    
    pixhawk's avatar
    pixhawk committed
    }
    
    LinkManager::~LinkManager()
    {
    
    #ifndef __mobile__
    
        delete _nmeaPort;
    
    pixhawk's avatar
    pixhawk committed
    }
    
    
    void LinkManager::setToolbox(QGCToolbox *toolbox)
    {
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        QGCTool::setToolbox(toolbox);
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        _autoConnectSettings = toolbox->settingsManager()->autoConnectSettings();
        _mavlinkProtocol = _toolbox->mavlinkProtocol();
    
        connect(_mavlinkProtocol, &MAVLinkProtocol::messageReceived, this, &LinkManager::_mavlinkMessageReceived);
    
    Don Gagne's avatar
    Don Gagne committed
        connect(&_portListTimer, &QTimer::timeout, this, &LinkManager::_updateAutoConnectLinks);
    
        _portListTimer.start(_autoconnectUpdateTimerMSecs); // timeout must be long enough to get past bootloader on second pass
    
    Don Gagne's avatar
    Don Gagne committed
    // This should only be used by Qml code
    void LinkManager::createConnectedLink(LinkConfiguration* config)
    {
        for(int i = 0; i < _sharedConfigurations.count(); i++) {
            SharedLinkConfigurationPointer& sharedConf = _sharedConfigurations[i];
            if (sharedConf->name() == config->name())
                createConnectedLink(sharedConf);
        }
    }
    
    
    LinkInterface* LinkManager::createConnectedLink(SharedLinkConfigurationPointer& config, bool isPX4Flow)
    
        if (!config) {
            qWarning() << "LinkManager::createConnectedLink called with NULL config";
            return NULL;
        }
    
    
        LinkInterface* pLink = NULL;
        switch(config->type()) {
    
    Gus Grubba's avatar
    Gus Grubba committed
    #ifndef NO_SERIAL_LINK
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        case LinkConfiguration::TypeSerial:
        {
            SerialConfiguration* serialConfig = dynamic_cast<SerialConfiguration*>(config.data());
            if (serialConfig) {
    
                pLink = new SerialLink(config, isPX4Flow);
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
                if (serialConfig->usbDirect()) {
                    _activeLinkCheckList.append((SerialLink*)pLink);
                    if (!_activeLinkCheckTimer.isActive()) {
                        _activeLinkCheckTimer.start();
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        }
    
    Don Gagne's avatar
    Don Gagne committed
            break;
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    #else
        Q_UNUSED(isPX4Flow)
    
    dogmaphobic's avatar
    dogmaphobic committed
    #endif
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        case LinkConfiguration::TypeUdp:
            pLink = new UDPLink(config);
            break;
        case LinkConfiguration::TypeTcp:
            pLink = new TCPLink(config);
            break;
    
    dogmaphobic's avatar
    dogmaphobic committed
    #ifdef QGC_ENABLE_BLUETOOTH
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        case LinkConfiguration::TypeBluetooth:
            pLink = new BluetoothLink(config);
            break;
    
    dogmaphobic's avatar
    dogmaphobic committed
    #endif
    
    dogmaphobic's avatar
    dogmaphobic committed
    #ifndef __mobile__
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        case LinkConfiguration::TypeLogReplay:
            pLink = new LogReplayLink(config);
            break;
    
    dogmaphobic's avatar
    dogmaphobic committed
    #endif
    
    #ifdef QT_DEBUG
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        case LinkConfiguration::TypeMock:
            pLink = new MockLink(config);
            break;
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        case LinkConfiguration::TypeLast:
        default:
            break;
    
            _addLink(pLink);
            connectLink(pLink);
    
    Don Gagne's avatar
    Don Gagne committed
    LinkInterface* LinkManager::createConnectedLink(const QString& name)
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        if (name.isEmpty()) {
            qWarning() << "Internal error";
        } else {
            for(int i = 0; i < _sharedConfigurations.count(); i++) {
                SharedLinkConfigurationPointer& conf = _sharedConfigurations[i];
                if (conf->name() == name) {
                    return createConnectedLink(conf);
                }
            }
    
    void LinkManager::_addLink(LinkInterface* link)
    
    pixhawk's avatar
    pixhawk committed
    {
    
    Don Gagne's avatar
    Don Gagne committed
        if (thread() != QThread::currentThread()) {
    
            qWarning() << "_addLink called from incorrect thread";
    
    Don Gagne's avatar
    Don Gagne committed
            return;
        }
    
    Don Gagne's avatar
    Don Gagne committed
        if (!link) {
            return;
        }
    
        if (!containsLink(link)) {
    
            int mavlinkChannel = _reserveMavlinkChannel();
            if (mavlinkChannel != 0) {
                link->_setMavlinkChannel(mavlinkChannel);
            } else {
    
                qWarning() << "Ran out of mavlink channels";
    
                return;
    
            _sharedLinks.append(SharedLinkInterfacePointer(link));
    
    Don Gagne's avatar
    Don Gagne committed
        connect(link, &LinkInterface::communicationError,   _app,               &QGCApplication::criticalMessageBoxOnMainThread);
        connect(link, &LinkInterface::bytesReceived,        _mavlinkProtocol,   &MAVLinkProtocol::receiveBytes);
    
        _mavlinkProtocol->resetMetadataForLink(link);
    
        _mavlinkProtocol->setVersion(_mavlinkProtocol->getCurrentVersion());
    
        connect(link, &LinkInterface::connected,            this, &LinkManager::_linkConnected);
        connect(link, &LinkInterface::disconnected,         this, &LinkManager::_linkDisconnected);
    
        // This connection is queued since it will cloe the link. So we want the link emitter to return otherwise we would
        // close the link our from under itself.
        connect(link, &LinkInterface::connectionRemoved,    this, &LinkManager::_linkConnectionRemoved, Qt::QueuedConnection);
    
    pixhawk's avatar
    pixhawk committed
    
    
    Don Gagne's avatar
    Don Gagne committed
    void LinkManager::disconnectAll(void)
    
    pixhawk's avatar
    pixhawk committed
    {
    
    Don Gagne's avatar
    Don Gagne committed
        // Walk list in reverse order to preserve indices during delete
    
        for (int i=_sharedLinks.count()-1; i>=0; i--) {
            disconnectLink(_sharedLinks[i].data());
    
    pixhawk's avatar
    pixhawk committed
    }
    
    bool LinkManager::connectLink(LinkInterface* link)
    {
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        if (link) {
            if (_connectionsSuspendedMsg()) {
                return false;
            }
            return link->_connect();
        } else {
            qWarning() << "Internal error";
    
    pixhawk's avatar
    pixhawk committed
    }
    
    
    Don Gagne's avatar
    Don Gagne committed
    void LinkManager::disconnectLink(LinkInterface* link)
    
    pixhawk's avatar
    pixhawk committed
    {
    
        if (!link || !containsLink(link)) {
    
            return;
        }
    
    Don Gagne's avatar
    Don Gagne committed
    
    
    Don Gagne's avatar
    Don Gagne committed
        link->_disconnect();
    
    Don Gagne's avatar
    Don Gagne committed
        LinkConfiguration* config = link->getLinkConfiguration();
    
        for (int i=0; i<_sharedAutoconnectConfigurations.count(); i++) {
            if (_sharedAutoconnectConfigurations[i].data() == config) {
                qCDebug(LinkManagerLog) << "Removing disconnected autoconnect config" << config->name();
                _sharedAutoconnectConfigurations.removeAt(i);
                break;
    
    Don Gagne's avatar
    Don Gagne committed
        _deleteLink(link);
    
    pixhawk's avatar
    pixhawk committed
    }
    
    
    void LinkManager::_deleteLink(LinkInterface* link)
    
    Don Gagne's avatar
    Don Gagne committed
        if (thread() != QThread::currentThread()) {
            qWarning() << "_deleteLink called from incorrect thread";
            return;
        }
    
        if (!link) {
            return;
        }
    
        // Free up the mavlink channel associated with this link
    
        _freeMavlinkChannel(link->mavlinkChannel());
    
        for (int i=0; i<_sharedLinks.count(); i++) {
            if (_sharedLinks[i].data() == link) {
                _sharedLinks.removeAt(i);
                break;
            }
        }
    
    Don Gagne's avatar
    Don Gagne committed
        // Emit removal of link
    
        emit linkDeleted(link);
    
    pixhawk's avatar
    pixhawk committed
    }
    
    
    SharedLinkInterfacePointer LinkManager::sharedLinkInterfacePointerForLink(LinkInterface* link)
    {
        for (int i=0; i<_sharedLinks.count(); i++) {
            if (_sharedLinks[i].data() == link) {
                return _sharedLinks[i];
            }
        }
    
        qWarning() << "LinkManager::sharedLinkInterfaceForLink returning NULL";
        return SharedLinkInterfacePointer(NULL);
    }
    
    
    /// @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) {
    
            qgcApp()->showMessage(tr("Connect not allowed: %1").arg(_connectionsSuspendedReason));
    
            return true;
        } else {
            return false;
        }
    }
    
    void LinkManager::setConnectionsSuspended(QString reason)
    {
        _connectionsSuspended = true;
        _connectionsSuspendedReason = reason;
    }
    
    void LinkManager::_linkConnected(void)
    {
        emit linkConnected((LinkInterface*)sender());
    }
    
    void LinkManager::_linkDisconnected(void)
    {
        emit linkDisconnected((LinkInterface*)sender());
    }
    
    void LinkManager::_linkConnectionRemoved(LinkInterface* link)
    {
        // Link has been removed from system, disconnect it automatically
        disconnectLink(link);
    }
    
    
    void LinkManager::suspendConfigurationUpdates(bool suspend)
    {
        _configUpdateSuspended = suspend;
    }
    
    void LinkManager::saveLinkConfigurationList()
    {
        QSettings settings;
        settings.remove(LinkConfiguration::settingsRoot());
    
        int trueCount = 0;
    
        for (int i = 0; i < _sharedConfigurations.count(); i++) {
            SharedLinkConfigurationPointer linkConfig = _sharedConfigurations[i];
    
    Don Gagne's avatar
    Don Gagne committed
            if (linkConfig) {
    
                if (!linkConfig->isDynamic()) {
    
    Don Gagne's avatar
    Don Gagne committed
                    QString root = LinkConfiguration::settingsRoot();
    
                    root += QString("/Link%1").arg(trueCount++);
    
    Don Gagne's avatar
    Don Gagne committed
                    settings.setValue(root + "/name", linkConfig->name());
                    settings.setValue(root + "/type", linkConfig->type());
    
                    settings.setValue(root + "/auto", linkConfig->isAutoConnect());
    
                    settings.setValue(root + "/high_latency", linkConfig->isHighLatency());
    
    Don Gagne's avatar
    Don Gagne committed
                    // Have the instance save its own values
                    linkConfig->saveSettings(settings, root);
                }
            } else {
    
                qWarning() << "Internal error for link configuration in LinkManager";
    
    dogmaphobic's avatar
    dogmaphobic committed
            }
    
    dogmaphobic's avatar
    dogmaphobic committed
        QString root(LinkConfiguration::settingsRoot());
    
        settings.setValue(root + "/count", trueCount);
        emit linkConfigurationsChanged();
    
        bool linksChanged = false;
    
        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();
    
                    if((LinkConfiguration::LinkType)type < LinkConfiguration::TypeLast) {
    
                        if(settings.contains(root + "/name")) {
                            QString name = settings.value(root + "/name").toString();
                            if(!name.isEmpty()) {
                                LinkConfiguration* pLink = NULL;
    
                                bool autoConnect = settings.value(root + "/auto").toBool();
    
                                bool highLatency = settings.value(root + "/high_latency").toBool();
    
                                switch((LinkConfiguration::LinkType)type) {
    
    Gus Grubba's avatar
    Gus Grubba committed
    #ifndef NO_SERIAL_LINK
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
                                case LinkConfiguration::TypeSerial:
                                    pLink = (LinkConfiguration*)new SerialConfiguration(name);
                                    break;
    
    dogmaphobic's avatar
    dogmaphobic committed
    #endif
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
                                case LinkConfiguration::TypeUdp:
                                    pLink = (LinkConfiguration*)new UDPConfiguration(name);
                                    break;
                                case LinkConfiguration::TypeTcp:
                                    pLink = (LinkConfiguration*)new TCPConfiguration(name);
                                    break;
    
    dogmaphobic's avatar
    dogmaphobic committed
    #ifdef QGC_ENABLE_BLUETOOTH
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
                                case LinkConfiguration::TypeBluetooth:
                                    pLink = (LinkConfiguration*)new BluetoothConfiguration(name);
                                    break;
    
    dogmaphobic's avatar
    dogmaphobic committed
    #endif
    
    dogmaphobic's avatar
    dogmaphobic committed
    #ifndef __mobile__
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
                                case LinkConfiguration::TypeLogReplay:
                                    pLink = (LinkConfiguration*)new LogReplayLinkConfiguration(name);
                                    break;
    
    dogmaphobic's avatar
    dogmaphobic committed
    #endif
    
    #ifdef QT_DEBUG
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
                                case LinkConfiguration::TypeMock:
                                    pLink = (LinkConfiguration*)new MockConfiguration(name);
                                    break;
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
                                default:
                                case LinkConfiguration::TypeLast:
                                    break;
    
                                    //-- Have the instance load its own values
                                    pLink->setAutoConnect(autoConnect);
    
                                    pLink->setHighLatency(highLatency);
    
                                    addConfiguration(pLink);
    
                                    linksChanged = true;
    
                                qWarning() << "Link Configuration" << root << "has an empty name." ;
    
                            qWarning() << "Link Configuration" << root << "has no name." ;
    
                        qWarning() << "Link Configuration" << root << "an invalid type: " << type;
    
                    qWarning() << "Link Configuration" << root << "has no type." ;
    
    
        if(linksChanged) {
    
            emit linkConfigurationsChanged();
    
        }
        // Enable automatic Serial PX4/3DR Radio hunting
    
    Gus Grubba's avatar
    Gus Grubba committed
    #ifndef NO_SERIAL_LINK
    
    Don Gagne's avatar
    Don Gagne committed
    SerialConfiguration* LinkManager::_autoconnectConfigurationsContainsPort(const QString& portName)
    
    Don Gagne's avatar
    Don Gagne committed
    
    
        for (int i=0; i<_sharedAutoconnectConfigurations.count(); i++) {
            SerialConfiguration* serialConfig = qobject_cast<SerialConfiguration*>(_sharedAutoconnectConfigurations[i].data());
    
    Don Gagne's avatar
    Don Gagne committed
    
    
            if (serialConfig) {
                if (serialConfig->portName() == searchPort) {
                    return serialConfig;
    
    Don Gagne's avatar
    Don Gagne committed
            } else {
                qWarning() << "Internal error";
    
    dogmaphobic's avatar
    dogmaphobic committed
    #endif
    
    Don Gagne's avatar
    Don Gagne committed
    void LinkManager::_updateAutoConnectLinks(void)
    
    Don Gagne's avatar
    Don Gagne committed
        if (_connectionsSuspended || qgcApp()->runningUnitTests()) {
    
    Don Gagne's avatar
    Don Gagne committed
    
    
    Don Gagne's avatar
    Don Gagne committed
        // Re-add UDP if we need to
        bool foundUDP = false;
    
        for (int i=0; i<_sharedLinks.count(); i++) {
            LinkConfiguration* linkConfig = _sharedLinks[i]->getLinkConfiguration();
    
    Don Gagne's avatar
    Don Gagne committed
            if (linkConfig->type() == LinkConfiguration::TypeUdp && linkConfig->name() == _defaultUPDLinkName) {
                foundUDP = true;
                break;
            }
        }
    
        if (!foundUDP && _autoConnectSettings->autoConnectUDP()->rawValue().toBool()) {
    
    Don Gagne's avatar
    Don Gagne committed
            qCDebug(LinkManagerLog) << "New auto-connect UDP port added";
    
            // Default UDPConfiguration is set up for autoconnect
    
    Don Gagne's avatar
    Don Gagne committed
            UDPConfiguration* udpConfig = new UDPConfiguration(_defaultUPDLinkName);
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
            udpConfig->setDynamic(true);
    
            SharedLinkConfigurationPointer config = addConfiguration(udpConfig);
            createConnectedLink(config);
    
            emit linkConfigurationsChanged();
    
    Gus Grubba's avatar
    Gus Grubba committed
    #ifndef NO_SERIAL_LINK
    
    dogmaphobic's avatar
    dogmaphobic committed
        QStringList currentPorts;
    
        QList<QGCSerialPortInfo> portList;
    
    #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.
    
        if (!_sharedAutoconnectConfigurations.count()) {
    
            portList = QGCSerialPortInfo::availablePorts();
        }
    
    #else
        portList = QGCSerialPortInfo::availablePorts();
    
    Don Gagne's avatar
    Don Gagne committed
    
    
    Don Gagne's avatar
    Don Gagne committed
        foreach (QGCSerialPortInfo portInfo, portList) {
    
    Don Gagne's avatar
    Don Gagne committed
            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
            // Save port name
            currentPorts << portInfo.systemLocation();
    
            QGCSerialPortInfo::BoardType_t boardType;
            QString boardName;
    
    #ifndef __mobile__
    
            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();
                    newPort->setBaudRate(_nmeaBaud);
                    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();
                    _nmeaPort->setBaudRate(_nmeaBaud);
                    qCDebug(LinkManagerLog) << "Configuring nmea baudrate" << _nmeaBaud;
                }
    
            } else
    #endif
            if (portInfo.getBoardInfo(boardType, boardName)) {
    
    Don Gagne's avatar
    Don Gagne committed
                if (portInfo.isBootloader()) {
                    // Don't connect to bootloader
    
                    qCDebug(LinkManagerLog) << "Waiting for bootloader to finish" << portInfo.systemLocation();
    
    Don Gagne's avatar
    Don Gagne committed
                    continue;
                }
    
                if (_autoconnectConfigurationsContainsPort(portInfo.systemLocation()) || _autoConnectRTKPort == 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[portInfo.systemLocation()] = 1;
                } else if (++_autoconnectWaitList[portInfo.systemLocation()] * _autoconnectUpdateTimerMSecs > _autoconnectConnectDelayMSecs) {
    
    Don Gagne's avatar
    Don Gagne committed
                    SerialConfiguration* pSerialConfig = NULL;
    
    
                    _autoconnectWaitList.remove(portInfo.systemLocation());
    
    Don Gagne's avatar
    Don Gagne committed
                    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;
    
    Don Gagne's avatar
    Don Gagne committed
                    case QGCSerialPortInfo::BoardTypePX4Flow:
    
                        if (_autoConnectSettings->autoConnectPX4Flow()->rawValue().toBool()) {
    
                            pSerialConfig = new SerialConfiguration(tr("%1 on %2 (AutoConnect)").arg(boardName).arg(portInfo.portName().trimmed()));
    
    Don Gagne's avatar
    Don Gagne committed
                        }
    
    Don Gagne's avatar
    Don Gagne committed
                        break;
    
                    case QGCSerialPortInfo::BoardTypeSiKRadio:
    
                        if (_autoConnectSettings->autoConnectSiKRadio()->rawValue().toBool()) {
    
                            pSerialConfig = new SerialConfiguration(tr("%1 on %2 (AutoConnect)").arg(boardName).arg(portInfo.portName().trimmed()));
    
    Don Gagne's avatar
    Don Gagne committed
                        }
                        break;
    
                    case QGCSerialPortInfo::BoardTypeOpenPilot:
    
                        if (_autoConnectSettings->autoConnectLibrePilot()->rawValue().toBool()) {
    
                            pSerialConfig = new SerialConfiguration(tr("%1 on %2 (AutoConnect)").arg(boardName).arg(portInfo.portName().trimmed()));
    
    Don Gagne's avatar
    Don Gagne committed
    #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);
    
    Don Gagne's avatar
    Don Gagne committed
                        }
                        break;
    #endif
    
    Don Gagne's avatar
    Don Gagne committed
                    default:
                        qWarning() << "Internal error";
    
    Don Gagne's avatar
    Don Gagne committed
                        continue;
    
    dogmaphobic's avatar
    dogmaphobic committed
                    }
    
    Don Gagne's avatar
    Don Gagne committed
                    if (pSerialConfig) {
                        qCDebug(LinkManagerLog) << "New auto-connect port added: " << pSerialConfig->name() << portInfo.systemLocation();
    
                        pSerialConfig->setBaud(boardType == QGCSerialPortInfo::BoardTypeSiKRadio ? 57600 : 115200);
    
    Don Gagne's avatar
    Don Gagne committed
                        pSerialConfig->setDynamic(true);
                        pSerialConfig->setPortName(portInfo.systemLocation());
    
                        _sharedAutoconnectConfigurations.append(SharedLinkConfigurationPointer(pSerialConfig));
    
                        createConnectedLink(_sharedAutoconnectConfigurations.last(), boardType == QGCSerialPortInfo::BoardTypePX4Flow);
    
    Don Gagne's avatar
    Don Gagne committed
                    }
    
    #ifndef __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. The means we must rely on
        // the port disconnecting itself when the radio is pulled to signal communication list as opposed to automatically
        // closing the Link.
    
    
    dogmaphobic's avatar
    dogmaphobic committed
        // Now we go through the current configuration list and make sure any dynamic config has gone away
        QList<LinkConfiguration*>  _confToDelete;
    
        for (int i=0; i<_sharedAutoconnectConfigurations.count(); i++) {
            SerialConfiguration* serialConfig = qobject_cast<SerialConfiguration*>(_sharedAutoconnectConfigurations[i].data());
            if (serialConfig) {
                if (!currentPorts.contains(serialConfig->portName())) {
                    if (serialConfig->link()) {
                        if (serialConfig->link()->isConnected()) {
                            if (serialConfig->link()->active()) {
    
                                // We don't remove links which are still connected which have been active with a vehicle on them
                                // even though at this point the cable may have been pulled. Instead we wait for the user to
                                // Disconnect. Once the user disconnects, the link will be removed.
                                continue;
                            }
                        }
    
                    _confToDelete.append(serialConfig);
    
    dogmaphobic's avatar
    dogmaphobic committed
                }
    
    Don Gagne's avatar
    Don Gagne committed
            } else {
                qWarning() << "Internal error";
    
    Don Gagne's avatar
    Don Gagne committed
    
    
    Don Gagne's avatar
    Don Gagne committed
        // Now remove all configs that are gone
    
    Don Gagne's avatar
    Don Gagne committed
        foreach (LinkConfiguration* pDeleteConfig, _confToDelete) {
    
    Don Gagne's avatar
    Don Gagne committed
            qCDebug(LinkManagerLog) << "Removing unused autoconnect config" << pDeleteConfig->name();
    
    Don Gagne's avatar
    Don Gagne committed
            if (pDeleteConfig->link()) {
                disconnectLink(pDeleteConfig->link());
            }
    
            for (int i=0; i<_sharedAutoconnectConfigurations.count(); i++) {
                if (_sharedAutoconnectConfigurations[i].data() == pDeleteConfig) {
                    _sharedAutoconnectConfigurations.removeAt(i);
                    break;
                }
            }
    
    
        // Check for RTK GPS connection gone
    
    #if !defined(__mobile__)
    
        if (!_autoConnectRTKPort.isEmpty() && !currentPorts.contains(_autoConnectRTKPort)) {
            qCDebug(LinkManagerLog) << "RTK GPS disconnected" << _autoConnectRTKPort;
            _toolbox->gpsManager()->disconnectGPS();
            _autoConnectRTKPort.clear();
        }
    
    Gus Grubba's avatar
    Gus Grubba committed
    #endif // NO_SERIAL_LINK
    
    Don Gagne's avatar
    Don Gagne committed
    void LinkManager::shutdown(void)
    {
    
        setConnectionsSuspended(tr("Shutdown"));
    
    Don Gagne's avatar
    Don Gagne committed
        disconnectAll();
    
    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
    #ifndef NO_SERIAL_LINK
    
            list += "Serial";
    #endif
            list += "UDP";
            list += "TCP";
    
    dogmaphobic's avatar
    dogmaphobic committed
    #ifdef QGC_ENABLE_BLUETOOTH
    
    dogmaphobic's avatar
    dogmaphobic committed
            list += "Bluetooth";
    #endif
    
    dogmaphobic's avatar
    dogmaphobic committed
    #ifdef QT_DEBUG
    
            list += "Mock Link";
    
    dogmaphobic's avatar
    dogmaphobic committed
    #endif
    #ifndef __mobile__
    
            list += "Log Replay";
    
    dogmaphobic's avatar
    dogmaphobic committed
    #endif
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
            if (list.size() != (int)LinkConfiguration::TypeLast) {
                qWarning() << "Internal error";
            }
    
    void LinkManager::_updateSerialPorts()
    
        _commPortList.clear();
        _commPortDisplayList.clear();
    
    Gus Grubba's avatar
    Gus Grubba committed
    #ifndef NO_SERIAL_LINK
    
        QList<QSerialPortInfo> portList = QSerialPortInfo::availablePorts();
        foreach (const QSerialPortInfo &info, portList)
    
            QString port = info.systemLocation().trimmed();
            _commPortList += port;
            _commPortDisplayList += SerialConfiguration::cleanPortDisplayname(port);
    
    }
    
    QStringList LinkManager::serialPortStrings(void)
    {
        if(!_commPortDisplayList.size())
        {
            _updateSerialPorts();
        }
        return _commPortDisplayList;
    }
    
    QStringList LinkManager::serialPorts(void)
    {
        if(!_commPortList.size())
        {
            _updateSerialPorts();
        }
    
        return _commPortList;
    }
    
    QStringList LinkManager::serialBaudRates(void)
    {
    
    Gus Grubba's avatar
    Gus Grubba committed
    #ifdef NO_SERIAL_LINK
    
        QStringList foo;
        return foo;
    #else
        return SerialConfiguration::supportedBaudRates();
    #endif
    }
    
    
    bool LinkManager::endConfigurationEditing(LinkConfiguration* config, LinkConfiguration* editedConfig)
    {
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        if (config && editedConfig) {
            _fixUnnamed(editedConfig);
            config->copyFrom(editedConfig);
            saveLinkConfigurationList();
            // Tell link about changes (if any)
            config->updateSettings();
            // Discard temporary duplicate
            delete editedConfig;
        } else {
            qWarning() << "Internal error";
        }
    
        return true;
    }
    
    bool LinkManager::endCreateConfiguration(LinkConfiguration* config)
    {
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        if (config) {
            _fixUnnamed(config);
            addConfiguration(config);
            saveLinkConfigurationList();
        } else {
            qWarning() << "Internal error";
        }
    
        return true;
    }
    
    LinkConfiguration* LinkManager::createConfiguration(int type, const QString& name)
    {
    
    Gus Grubba's avatar
    Gus Grubba committed
    #ifndef NO_SERIAL_LINK
    
        if((LinkConfiguration::LinkType)type == LinkConfiguration::TypeSerial)
            _updateSerialPorts();
    
    dogmaphobic's avatar
    dogmaphobic committed
    #endif
    
        return LinkConfiguration::createSettings(type, name);
    }
    
    LinkConfiguration* LinkManager::startConfigurationEditing(LinkConfiguration* config)
    {
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        if (config) {
    
    Gus Grubba's avatar
    Gus Grubba committed
    #ifndef NO_SERIAL_LINK
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
            if(config->type() == LinkConfiguration::TypeSerial)
                _updateSerialPorts();
    
    dogmaphobic's avatar
    dogmaphobic committed
    #endif
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
            return LinkConfiguration::duplicateSettings(config);
        } else {
            qWarning() << "Internal error";
            return NULL;
        }
    
    }
    
    
    void LinkManager::_fixUnnamed(LinkConfiguration* config)
    {
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        if (config) {
            //-- Check for "Unnamed"
            if (config->name() == "Unnamed") {
                switch(config->type()) {
    
    Gus Grubba's avatar
    Gus Grubba committed
    #ifndef NO_SERIAL_LINK
    
                case LinkConfiguration::TypeSerial: {
                    QString tname = dynamic_cast<SerialConfiguration*>(config)->portName();
    
    #ifdef Q_OS_WIN
    
                    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
                }
    
    #endif
                case LinkConfiguration::TypeUdp:
                    config->setName(
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
                                QString("UDP Link on Port %1").arg(dynamic_cast<UDPConfiguration*>(config)->localPort()));
    
                    break;
                case LinkConfiguration::TypeTcp: {
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
                    TCPConfiguration* tconfig = dynamic_cast<TCPConfiguration*>(config);
                    if(tconfig) {
                        config->setName(
                                    QString("TCP Link %1:%2").arg(tconfig->address().toString()).arg((int)tconfig->port()));
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
                }
    
    dogmaphobic's avatar
    dogmaphobic committed
    #ifdef QGC_ENABLE_BLUETOOTH
    
    dogmaphobic's avatar
    dogmaphobic committed
                case LinkConfiguration::TypeBluetooth: {
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
                    BluetoothConfiguration* tconfig = dynamic_cast<BluetoothConfiguration*>(config);
                    if(tconfig) {
                        config->setName(QString("%1 (Bluetooth Device)").arg(tconfig->device().name));
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
                }
    
    dogmaphobic's avatar
    dogmaphobic committed
                    break;
    #endif
    
    dogmaphobic's avatar
    dogmaphobic committed
    #ifndef __mobile__
    
                case LinkConfiguration::TypeLogReplay: {
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
                    LogReplayLinkConfiguration* tconfig = dynamic_cast<LogReplayLinkConfiguration*>(config);
                    if(tconfig) {
                        config->setName(QString("Log Replay %1").arg(tconfig->logFilenameShort()));
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
                }
    
    dogmaphobic's avatar
    dogmaphobic committed
    #endif
    
    #ifdef QT_DEBUG
                case LinkConfiguration::TypeMock:
                    config->setName(
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
                                QString("Mock Link"));
    
                    break;
    #endif
                case LinkConfiguration::TypeLast:
                default:
                    break;
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
                }
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        } else {
            qWarning() << "Internal error";
    
        }
    }
    
    void LinkManager::removeConfiguration(LinkConfiguration* config)
    {
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        if (config) {
            LinkInterface* iface = config->link();
            if(iface) {
                disconnectLink(iface);
            }
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
            _removeConfiguration(config);
            saveLinkConfigurationList();
        } else {
            qWarning() << "Internal error";
        }
    
    bool LinkManager::isAutoconnectLink(LinkInterface* link)
    {
    
        for (int i=0; i<_sharedAutoconnectConfigurations.count(); i++) {
            if (_sharedAutoconnectConfigurations[i].data() == link->getLinkConfiguration()) {
                return true;
            }
        }
        return false;
    
    dogmaphobic's avatar
    dogmaphobic committed
    
    bool LinkManager::isBluetoothAvailable(void)
    {
        return qgcApp()->isBluetoothAvailable();
    }
    
    Gus Grubba's avatar
    Gus Grubba committed
    #ifndef NO_SERIAL_LINK
    
    Don Gagne's avatar
    Don Gagne committed
    void LinkManager::_activeLinkCheck(void)
    {
    
        SerialLink* link = NULL;
    
    Don Gagne's avatar
    Don Gagne committed
        bool found = false;
    
        if (_activeLinkCheckList.count() != 0) {
    
            link = _activeLinkCheckList.takeFirst();
    
            if (containsLink(link) && link->isConnected()) {
    
    Don Gagne's avatar
    Don Gagne committed
                // Make sure there is a vehicle on the link
                QmlObjectListModel* vehicles = _toolbox->multiVehicleManager()->vehicles();
                for (int i=0; i<vehicles->count(); i++) {
                    Vehicle* vehicle = qobject_cast<Vehicle*>(vehicles->get(i));
                    if (vehicle->containsLink(link)) {
                        found = true;
                        break;
                    }
                }
    
            } else {
                link = NULL;
    
    Don Gagne's avatar
    Don Gagne committed
            }
        }
    
        if (_activeLinkCheckList.count() == 0) {
            _activeLinkCheckTimer.stop();
        }
    
    
        if (!found && link) {
            // See if we can get an NSH prompt on this link
            bool foundNSHPrompt = false;
    
            link->writeBytesSafe("\r", 1);
    
            QSignalSpy spy(link, SIGNAL(bytesReceived(LinkInterface*, QByteArray)));
            if (spy.wait(100)) {
                QList<QVariant> arguments = spy.takeFirst();
                if (arguments[1].value<QByteArray>().contains("nsh>")) {
                    foundNSHPrompt = true;
                }
            }
    
            qgcApp()->showMessage(foundNSHPrompt ?
    
                                      tr("Please check to make sure you have an SD Card inserted in your Vehicle and try again.") :
                                      tr("Your Vehicle is not responding. If this continues, shutdown %1, restart the Vehicle letting it boot completely, then start %1.").arg(qgcApp()->applicationName()));
    
    Don Gagne's avatar
    Don Gagne committed
    #endif
    
    
    bool LinkManager::containsLink(LinkInterface* link)
    {
        for (int i=0; i<_sharedLinks.count(); i++) {
            if (_sharedLinks[i].data() == link) {
                return true;
            }
        }
        return false;
    }
    
    SharedLinkConfigurationPointer LinkManager::addConfiguration(LinkConfiguration* config)
    {
        _qmlConfigurations.append(config);
        _sharedConfigurations.append(SharedLinkConfigurationPointer(config));
        return _sharedConfigurations.last();
    }
    
    void LinkManager::_removeConfiguration(LinkConfiguration* config)
    {
        _qmlConfigurations.removeOne(config);
    
        for (int i=0; i<_sharedConfigurations.count(); i++) {
            if (_sharedConfigurations[i].data() == config) {
                _sharedConfigurations.removeAt(i);
                return;
            }
        }
    
        qWarning() << "LinkManager::_removeConfiguration called with unknown config";
    }
    
    QList<LinkInterface*> LinkManager::links(void)
    {
        QList<LinkInterface*> rawLinks;
    
        for (int i=0; i<_sharedLinks.count(); i++) {
            rawLinks.append(_sharedLinks[i].data());
        }
    
        return rawLinks;
    }
    
    
    void LinkManager::startAutoConnectedLinks(void)
    {
        SharedLinkConfigurationPointer conf;
    
        for(int i = 0; i < _sharedConfigurations.count(); i++) {
            conf = _sharedConfigurations[i];
            if (conf->isAutoConnect())
                createConnectedLink(conf);
        }
    }
    
    
    int LinkManager::_reserveMavlinkChannel(void)
    {
        // Find a mavlink channel to use for this link, Channel 0 is reserved for internal use.
        for (int mavlinkChannel=1; mavlinkChannel<32; mavlinkChannel++) {
            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;
            }