Skip to content
Snippets Groups Projects
SerialLink.cc 17.4 KiB
Newer Older
  • Learn to ignore specific revisions
  • pixhawk's avatar
    pixhawk committed
    /*=====================================================================
    ======================================================================*/
    /**
     * @file
     *   @brief Cross-platform support for serial ports
     *
     *   @author Lorenz Meier <mavteam@student.ethz.ch>
     *
     */
    
    #include <QTimer>
    #include <QDebug>
    
    #include <QSettings>
    
    pixhawk's avatar
    pixhawk committed
    #include <QMutexLocker>
    
    dogmaphobic's avatar
    dogmaphobic committed
    
    #ifdef __android__
    #include "qserialport.h"
    #else
    
    #include <QSerialPort>
    
    dogmaphobic's avatar
    dogmaphobic committed
    #endif
    
    pixhawk's avatar
    pixhawk committed
    #include "SerialLink.h"
    
    #include "QGC.h"
    
    #include "QGCLoggingCategory.h"
    
    Don Gagne's avatar
    Don Gagne committed
    #include "QGCApplication.h"
    
    #include "QGCSerialPortInfo.h"
    
    QGC_LOGGING_CATEGORY(SerialLinkLog, "SerialLinkLog")
    
    pixhawk's avatar
    pixhawk committed
    
    
    static QStringList kSupportedBaudRates;
    
    
    SerialLink::SerialLink(SharedLinkConfigurationPointer& config)
        : LinkInterface(config)
        , _port(NULL)
        , _bytesRead(0)
        , _stopp(false)
        , _reqReset(false)
        , _serialConfig(qobject_cast<SerialConfiguration*>(config.data()))
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        if (!_serialConfig) {
            qWarning() << "Internal error";
            return;
        }
    
    
        qCDebug(SerialLinkLog) << "Create SerialLink " << _serialConfig->portName() << _serialConfig->baud() << _serialConfig->flowControl()
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
                               << _serialConfig->parity() << _serialConfig->dataBits() << _serialConfig->stopBits();
    
        qCDebug(SerialLinkLog) << "portName: " << _serialConfig->portName();
    
    pixhawk's avatar
    pixhawk committed
    }
    
        QMutexLocker locker(&this->_stoppMutex);
        _reqReset = true;
    
    pixhawk's avatar
    pixhawk committed
    
    SerialLink::~SerialLink()
    {
    
        QList<QSerialPortInfo> portList = QSerialPortInfo::availablePorts();
    
        if( portList.count() == 0){
            return false;
        }
        foreach (const QSerialPortInfo &info, portList)
        {
    
            qCDebug(SerialLinkLog) << "PortName    : " << info.portName() << "Description : " << info.description();
            qCDebug(SerialLinkLog) << "Manufacturer: " << info.manufacturer();
    
            if (info.portName().trimmed() == _serialConfig->portName() &&
    
                    (info.description().toLower().contains("bootloader") ||
                     info.description().toLower().contains("px4 bl") ||
                     info.description().toLower().contains("px4 fmu v1.6"))) {
                qCDebug(SerialLinkLog) << "BOOTLOADER FOUND";
                return true;
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
            }
    
    void SerialLink::_writeBytes(const QByteArray data)
    
            _logOutputDataRate(data.size(), QDateTime::currentMSecsSinceEpoch());
            _port->write(data);
    
    Ricardo de Almeida Gonzaga's avatar
    Ricardo de Almeida Gonzaga committed
            // Error occurred
    
            qWarning() << "Serial port not writeable";
    
            _emitLinkError(tr("Could not send data - link %1 is disconnected!").arg(getName()));
    
    pixhawk's avatar
    pixhawk committed
        }
    }
    
    /**
     * @brief Disconnect the connection.
     *
     * @return True if connection has been disconnected, false if connection couldn't be disconnected.
     **/
    
    Don Gagne's avatar
    Don Gagne committed
    void SerialLink::_disconnect(void)
    
        if (_port) {
            _port->close();
    
            _port->deleteLater();
    
            _port = NULL;
    
    dogmaphobic's avatar
    dogmaphobic committed
    #ifdef __android__
    
        qgcApp()->toolbox()->linkManager()->suspendConfigurationUpdates(false);
    
    dogmaphobic's avatar
    dogmaphobic committed
    #endif
    
    pixhawk's avatar
    pixhawk committed
    }
    
    /**
     * @brief Connect the connection.
     *
     * @return True if connection has been established, false if connection couldn't be established.
     **/
    
    bool SerialLink::_connect(void)
    
        qCDebug(SerialLinkLog) << "CONNECT CALLED";
    
    
        _disconnect();
    
    dogmaphobic's avatar
    dogmaphobic committed
    #ifdef __android__
    
        qgcApp()->toolbox()->linkManager()->suspendConfigurationUpdates(true);
    
    #endif
    
        QSerialPort::SerialPortError    error;
        QString                         errorString;
    
    
    dogmaphobic's avatar
    dogmaphobic committed
        // Initialize the connection
    
        if (!_hardwareConnect(error, errorString)) {
            if (qgcApp()->toolbox()->linkManager()->isAutoconnectLink(this)) {
                // Be careful with spitting out open error related to trying to open a busy port using autoconnect
                if (error == QSerialPort::PermissionError) {
                    // Device already open, ignore and fail connect
                    return false;
                }
    
            _emitLinkError(tr("Error connecting: Could not create port. %1").arg(errorString));
    
    dogmaphobic's avatar
    dogmaphobic committed
            return false;
        }
    
        return true;
    
    pixhawk's avatar
    pixhawk committed
    }
    
    
    /// Performs the actual hardware port connection.
    ///     @param[out] error if failed
    ///     @param[out] error string if failed
    /// @return success/fail
    bool SerialLink::_hardwareConnect(QSerialPort::SerialPortError& error, QString& errorString)
    
            qCDebug(SerialLinkLog) << "SerialLink:" << QString::number((long)this, 16) << "closing port";
    
    
            // Wait 50 ms while continuing to run the event queue
            for (unsigned i = 0; i < 10; i++) {
                QGC::SLEEP::usleep(5000);
                qgcApp()->processEvents(QEventLoop::ExcludeUserInputEvents);
            }
    
    pixhawk's avatar
    pixhawk committed
    
    
        qCDebug(SerialLinkLog) << "SerialLink: hardwareConnect to " << _serialConfig->portName();
    
        // If we are in the Pixhawk bootloader code wait for it to timeout
    
            qCDebug(SerialLinkLog) << "Not connecting to a bootloader, waiting for 2nd chance";
    
            const unsigned retry_limit = 12;
            unsigned retries;
    
            for (retries = 0; retries < retry_limit; retries++) {
    
                    // Wait 500 ms while continuing to run the event loop
                    for (unsigned i = 0; i < 100; i++) {
                        QGC::SLEEP::msleep(5);
                        qgcApp()->processEvents(QEventLoop::ExcludeUserInputEvents);
                    }
    
    
                // Wait 500 ms while continuing to run the event loop
                for (unsigned i = 0; i < 100; i++) {
                    QGC::SLEEP::msleep(5);
                    qgcApp()->processEvents(QEventLoop::ExcludeUserInputEvents);
                }
    
            }
            // Check limit
            if (retries == retry_limit) {
                // bail out
    
                qWarning() << "Timeout waiting for something other than booloader";
    
        _port = new QSerialPort(_serialConfig->portName(), this);
    
    Bill Bonney's avatar
    Bill Bonney committed
    
    
        QObject::connect(_port, static_cast<void (QSerialPort::*)(QSerialPort::SerialPortError)>(&QSerialPort::error),
                         this, &SerialLink::linkError);
    
        QObject::connect(_port, &QIODevice::readyRead, this, &SerialLink::_readBytes);
    
    Bill Bonney's avatar
    Bill Bonney committed
    
    
        //  port->setCommTimeouts(QSerialPort::CtScheme_NonBlockingRead);
    
    pixhawk's avatar
    pixhawk committed
    
    
        // TODO This needs a bit of TLC still...
    
        // After the bootloader times out, it still can take a second or so for the Pixhawk USB driver to come up and make
    
        // the port available for open. So we retry a few times to wait for it.
    
    dogmaphobic's avatar
    dogmaphobic committed
    #ifdef __android__
        _port->open(QIODevice::ReadWrite);
    #else
    
    
        // Try to open the port three times
        for (int openRetries = 0; openRetries < 3; openRetries++) {
    
            if (!_port->open(QIODevice::ReadWrite)) {
    
                qCDebug(SerialLinkLog) << "Port open failed, retrying";
    
                // Wait 250 ms while continuing to run the event loop
                for (unsigned i = 0; i < 50; i++) {
                    QGC::SLEEP::msleep(5);
                    qgcApp()->processEvents(QEventLoop::ExcludeUserInputEvents);
                }
                qgcApp()->processEvents(QEventLoop::ExcludeUserInputEvents);
    
    dogmaphobic's avatar
    dogmaphobic committed
    #endif
    
            qDebug() << "open failed" << _port->errorString() << _port->error() << getName() << qgcApp()->toolbox()->linkManager()->isAutoconnectLink(this);
            error = _port->error();
            errorString = _port->errorString();
    
            emit communicationUpdate(getName(), tr("Error opening port: %1").arg(_port->errorString()));
    
            delete _port;
            _port = NULL;
    
            return false; // couldn't open serial port
    
        _port->setDataTerminalReady(true);
    
    
        qCDebug(SerialLinkLog) << "Configuring port";
    
        _port->setBaudRate     (_serialConfig->baud());
        _port->setDataBits     (static_cast<QSerialPort::DataBits>     (_serialConfig->dataBits()));
        _port->setFlowControl  (static_cast<QSerialPort::FlowControl>  (_serialConfig->flowControl()));
        _port->setStopBits     (static_cast<QSerialPort::StopBits>     (_serialConfig->stopBits()));
        _port->setParity       (static_cast<QSerialPort::Parity>       (_serialConfig->parity()));
    
        emit communicationUpdate(getName(), "Opened port!");
    
    Bill Bonney's avatar
    Bill Bonney committed
        emit connected();
    
        qCDebug(SerialLinkLog) << "Connection SeriaLink: " << "with settings" << _serialConfig->portName()
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
                               << _serialConfig->baud() << _serialConfig->dataBits() << _serialConfig->parity() << _serialConfig->stopBits();
    
    Bill Bonney's avatar
    Bill Bonney committed
        return true; // successful connection
    
    pixhawk's avatar
    pixhawk committed
    }
    
    void SerialLink::_readBytes(void)
    {
    
        if (_port && _port->isOpen()) {
            qint64 byteCount = _port->bytesAvailable();
            if (byteCount) {
                QByteArray buffer;
                buffer.resize(byteCount);
                _port->read(buffer.data(), buffer.size());
                emit bytesReceived(this, buffer);
            }
        } else {
            // Error occurred
            qWarning() << "Serial port not readable";
            _emitLinkError(tr("Could not read data - link %1 is disconnected!").arg(getName()));
    
    void SerialLink::linkError(QSerialPort::SerialPortError error)
    
        switch (error) {
        case QSerialPort::NoError:
            break;
        case QSerialPort::ResourceError:
            emit connectionRemoved(this);
            break;
        default:
    
            // You can use the following qDebug output as needed during development. Make sure to comment it back out
            // when you are done. The reason for this is that this signal is very noisy. For example if you try to
            // connect to a PixHawk before it is ready to accept the connection it will output a continuous stream
            // of errors until the Pixhawk responds.
    
            //qCDebug(SerialLinkLog) << "SerialLink::linkError" << error;
    
            break;
    
    pixhawk's avatar
    pixhawk committed
    /**
     * @brief Check if connection is active.
     *
     * @return True if link is connected, false otherwise.
     **/
    
    bool SerialLink::isConnected() const
    
        bool isConnected = false;
    
    Bill Bonney's avatar
    Bill Bonney committed
    
    
            isConnected = _port->isOpen();
    
    lm's avatar
    lm committed
        }
    
        return isConnected;
    
    pixhawk's avatar
    pixhawk committed
    }
    
    
    QString SerialLink::getName() const
    
    pixhawk's avatar
    pixhawk committed
    {
    
        return _serialConfig->name();
    
    pixhawk's avatar
    pixhawk committed
    }
    
    
    /**
      * This function maps baud rate constants to numerical equivalents.
      * It relies on the mapping given in qportsettings.h from the QSerialPort library.
      */
    
    qint64 SerialLink::getConnectionSpeed() const
    
    Bill Bonney's avatar
    Bill Bonney committed
        int baudRate;
    
    Bill Bonney's avatar
    Bill Bonney committed
        } else {
    
            baudRate = _serialConfig->baud();
    
    Bill Bonney's avatar
    Bill Bonney committed
        }
        qint64 dataRate;
        switch (baudRate)
        {
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        case QSerialPort::Baud1200:
            dataRate = 1200;
            break;
        case QSerialPort::Baud2400:
            dataRate = 2400;
            break;
        case QSerialPort::Baud4800:
            dataRate = 4800;
            break;
        case QSerialPort::Baud9600:
            dataRate = 9600;
            break;
        case QSerialPort::Baud19200:
            dataRate = 19200;
            break;
        case QSerialPort::Baud38400:
            dataRate = 38400;
            break;
        case QSerialPort::Baud57600:
            dataRate = 57600;
            break;
        case QSerialPort::Baud115200:
            dataRate = 115200;
            break;
            // Otherwise do nothing.
        default:
            dataRate = -1;
            break;
    
    pixhawk's avatar
    pixhawk committed
        }
        return dataRate;
    }
    
    
            _port->setBaudRate      (_serialConfig->baud());
            _port->setDataBits      (static_cast<QSerialPort::DataBits>    (_serialConfig->dataBits()));
            _port->setFlowControl   (static_cast<QSerialPort::FlowControl> (_serialConfig->flowControl()));
            _port->setStopBits      (static_cast<QSerialPort::StopBits>    (_serialConfig->stopBits()));
            _port->setParity        (static_cast<QSerialPort::Parity>      (_serialConfig->parity()));
    
    pixhawk's avatar
    pixhawk committed
    }
    
    
    void SerialLink::_emitLinkError(const QString& errorMsg)
    
        qDebug() << errorMsg;
    
        emit communicationError(tr("Link Error"), msg.arg(getName()).arg(errorMsg));
    
    pixhawk's avatar
    pixhawk committed
    }
    
    
    //--------------------------------------------------------------------------
    //-- SerialConfiguration
    
    pixhawk's avatar
    pixhawk committed
    
    
    SerialConfiguration::SerialConfiguration(const QString& name) : LinkConfiguration(name)
    
        _baud       = 57600;
        _flowControl= QSerialPort::NoFlowControl;
        _parity     = QSerialPort::NoParity;
        _dataBits   = 8;
        _stopBits   = 1;
    
        _usbDirect  = false;
    
    pixhawk's avatar
    pixhawk committed
    }
    
    
    SerialConfiguration::SerialConfiguration(SerialConfiguration* copy) : LinkConfiguration(copy)
    
        _baud               = copy->baud();
        _flowControl        = copy->flowControl();
        _parity             = copy->parity();
        _dataBits           = copy->dataBits();
        _stopBits           = copy->stopBits();
        _portName           = copy->portName();
        _portDisplayName    = copy->portDisplayName();
    
        _usbDirect          = copy->_usbDirect;
    
    pixhawk's avatar
    pixhawk committed
    }
    
    
    void SerialConfiguration::copyFrom(LinkConfiguration *source)
    
        LinkConfiguration::copyFrom(source);
        SerialConfiguration* ssource = dynamic_cast<SerialConfiguration*>(source);
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        if (ssource) {
            _baud               = ssource->baud();
            _flowControl        = ssource->flowControl();
            _parity             = ssource->parity();
            _dataBits           = ssource->dataBits();
            _stopBits           = ssource->stopBits();
            _portName           = ssource->portName();
            _portDisplayName    = ssource->portDisplayName();
            _usbDirect          = ssource->_usbDirect;
        } else {
            qWarning() << "Internal error";
        }
    
    void SerialConfiguration::updateSettings()
    
        if(_link) {
            SerialLink* serialLink = dynamic_cast<SerialLink*>(_link);
            if(serialLink) {
                serialLink->_resetConfiguration();
            }
    
    void SerialConfiguration::setBaud(int baud)
    
    pixhawk's avatar
    pixhawk committed
    {
    
    pixhawk's avatar
    pixhawk committed
    }
    
    
    void SerialConfiguration::setDataBits(int databits)
    
    pixhawk's avatar
    pixhawk committed
    {
    
    pixhawk's avatar
    pixhawk committed
    }
    
    
    void SerialConfiguration::setFlowControl(int flowControl)
    
    pixhawk's avatar
    pixhawk committed
    {
    
    pixhawk's avatar
    pixhawk committed
    }
    
    
    void SerialConfiguration::setStopBits(int stopBits)
    
    pixhawk's avatar
    pixhawk committed
    }
    
    
    void SerialConfiguration::setParity(int parity)
    
    pixhawk's avatar
    pixhawk committed
    }
    
    
    void SerialConfiguration::setPortName(const QString& portName)
    
        // No effect on a running connection
        QString pname = portName.trimmed();
        if (!pname.isEmpty() && pname != _portName) {
            _portName = pname;
    
            _portDisplayName = cleanPortDisplayname(pname);
    
    QString SerialConfiguration::cleanPortDisplayname(const QString name)
    {
        QString pname = name.trimmed();
    
    #ifdef Q_OS_WIN
    
        pname.replace("\\\\.\\", "");
    #else
        pname.replace("/dev/cu.", "");
        pname.replace("/dev/", "");
    #endif
        return pname;
    }
    
    
    void SerialConfiguration::saveSettings(QSettings& settings, const QString& root)
    
        settings.setValue("baud",           _baud);
        settings.setValue("dataBits",       _dataBits);
        settings.setValue("flowControl",    _flowControl);
        settings.setValue("stopBits",       _stopBits);
        settings.setValue("parity",         _parity);
        settings.setValue("portName",       _portName);
        settings.setValue("portDisplayName",_portDisplayName);
    
    void SerialConfiguration::loadSettings(QSettings& settings, const QString& root)
    
        if(settings.contains("baud"))           _baud           = settings.value("baud").toInt();
        if(settings.contains("dataBits"))       _dataBits       = settings.value("dataBits").toInt();
        if(settings.contains("flowControl"))    _flowControl    = settings.value("flowControl").toInt();
        if(settings.contains("stopBits"))       _stopBits       = settings.value("stopBits").toInt();
        if(settings.contains("parity"))         _parity         = settings.value("parity").toInt();
        if(settings.contains("portName"))       _portName       = settings.value("portName").toString();
        if(settings.contains("portDisplayName"))_portDisplayName= settings.value("portDisplayName").toString();
    
    
    QStringList SerialConfiguration::supportedBaudRates()
    {
        if(!kSupportedBaudRates.size())
            _initBaudRates();
        return kSupportedBaudRates;
    }
    
    void SerialConfiguration::_initBaudRates()
    {
        kSupportedBaudRates.clear();
    #if USE_ANCIENT_RATES
    #if defined(Q_OS_UNIX) || defined(Q_OS_LINUX) || defined(Q_OS_DARWIN)
        kSupportedBaudRates << "50";
        kSupportedBaudRates << "75";
    #endif
        kSupportedBaudRates << "110";
    #if defined(Q_OS_UNIX) || defined(Q_OS_LINUX) || defined(Q_OS_DARWIN)
        kSupportedBaudRates << "134";
        kSupportedBaudRates << "150";
        kSupportedBaudRates << "200";
    #endif
        kSupportedBaudRates << "300";
        kSupportedBaudRates << "600";
        kSupportedBaudRates << "1200";
    #if defined(Q_OS_UNIX) || defined(Q_OS_LINUX) || defined(Q_OS_DARWIN)
        kSupportedBaudRates << "1800";
    #endif
    #endif
        kSupportedBaudRates << "2400";
        kSupportedBaudRates << "4800";
        kSupportedBaudRates << "9600";
    #if defined(Q_OS_WIN)
        kSupportedBaudRates << "14400";
    #endif
        kSupportedBaudRates << "19200";
        kSupportedBaudRates << "38400";
    #if defined(Q_OS_WIN)
        kSupportedBaudRates << "56000";
    #endif
        kSupportedBaudRates << "57600";
        kSupportedBaudRates << "115200";
    #if defined(Q_OS_WIN)
        kSupportedBaudRates << "128000";
    #endif
        kSupportedBaudRates << "230400";
    #if defined(Q_OS_WIN)
        kSupportedBaudRates << "256000";
    #endif
        kSupportedBaudRates << "460800";
        kSupportedBaudRates << "500000";
    
    #if defined(Q_OS_LINUX)
    
        kSupportedBaudRates << "576000";
    #endif
        kSupportedBaudRates << "921600";
    }
    
    
    void SerialConfiguration::setUsbDirect(bool usbDirect)
    {
        if (_usbDirect != usbDirect) {
            _usbDirect = usbDirect;
            emit usbDirectChanged(_usbDirect);
        }
    }