SerialLink.cc 18.6 KB
Newer Older
pixhawk's avatar
pixhawk committed
1 2 3 4 5 6 7 8 9 10 11 12
/*=====================================================================
======================================================================*/
/**
 * @file
 *   @brief Cross-platform support for serial ports
 *
 *   @author Lorenz Meier <mavteam@student.ethz.ch>
 *
 */

#include <QTimer>
#include <QDebug>
13
#include <QSettings>
pixhawk's avatar
pixhawk committed
14
#include <QMutexLocker>
15 16
#include <QSerialPort>
#include <QSerialPortInfo>
17

pixhawk's avatar
pixhawk committed
18
#include "SerialLink.h"
19
#include "QGC.h"
20 21 22
#include "MG.h"

Q_LOGGING_CATEGORY(SerialLinkLog, "SerialLinkLog")
pixhawk's avatar
pixhawk committed
23

24 25 26 27 28 29 30 31 32
SerialLink::SerialLink(SerialConfiguration* config)
{
    _bytesRead = 0;
    _port     = Q_NULLPTR;
    _stopp    = false;
    _reqReset = false;
    Q_ASSERT(config != NULL);
    _config = config;
    _config->setLink(this);
33 34 35
    // We're doing it wrong - because the Qt folks got the API wrong:
    // http://blog.qt.digia.com/blog/2010/06/17/youre-doing-it-wrong/
    moveToThread(this);
pixhawk's avatar
pixhawk committed
36
    // Set unique ID and add link to the list of links
37
    _id = getNextLinkId();
Bill Bonney's avatar
Bill Bonney committed
38

39
    qCDebug(SerialLinkLog) << "Create SerialLink " << config->portName() << config->baud() << config->flowControl()
40
             << config->parity() << config->dataBits() << config->stopBits();
41
    qCDebug(SerialLinkLog) << "portName: " << config->portName();
pixhawk's avatar
pixhawk committed
42
}
43

44 45
void SerialLink::requestReset()
{
46 47
    QMutexLocker locker(&this->_stoppMutex);
    _reqReset = true;
48
}
pixhawk's avatar
pixhawk committed
49 50 51

SerialLink::~SerialLink()
{
52 53
    // Disconnect link from configuration
    _config->setLink(NULL);
54
    _disconnect();
55 56
    if(_port) delete _port;
    _port = NULL;
Lorenz Meier's avatar
Lorenz Meier committed
57 58 59 60
    // Tell the thread to exit
    quit();
    // Wait for it to exit
    wait();
61 62
}

63
bool SerialLink::_isBootloader()
64
{
65
    QList<QSerialPortInfo> portList =  QSerialPortInfo::availablePorts();
66 67 68 69 70
    if( portList.count() == 0){
        return false;
    }
    foreach (const QSerialPortInfo &info, portList)
    {
71 72 73 74 75 76 77 78
        qCDebug(SerialLinkLog) << "PortName    : " << info.portName() << "Description : " << info.description();
        qCDebug(SerialLinkLog) << "Manufacturer: " << info.manufacturer();
        if (info.portName().trimmed() == _config->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;
79 80 81 82 83 84
       }
    }
    // Not found
    return false;
}

85 86 87 88 89 90
/**
 * @brief Runs the thread
 *
 **/
void SerialLink::run()
{
pixhawk's avatar
pixhawk committed
91
    // Initialize the connection
92 93
    if (!_hardwareConnect(_type)) {
        // Need to error out here.
94
        QString err("Could not create port.");
95 96
        if (_port) {
            err = _port->errorString();
97
        }
98
        _emitLinkError("Error connecting: " + err);
99
        return;
100
    }
pixhawk's avatar
pixhawk committed
101

102 103
    qint64  msecs = QDateTime::currentMSecsSinceEpoch();
    qint64  initialmsecs = QDateTime::currentMSecsSinceEpoch();
104
    quint64 bytes = 0;
105
    qint64  timeout = 5000;
106
    int linkErrorCount = 0;
107

108
    // Qt way to make clear what a while(1) loop does
109
    forever {
110
        {
111 112 113
            QMutexLocker locker(&this->_stoppMutex);
            if (_stopp) {
                _stopp = false;
114 115
                break; // exit the thread
            }
116
        }
117

118
        // Write all our buffered data out the serial port.
119 120 121 122 123 124
        if (_transmitBuffer.count() > 0) {
            _writeMutex.lock();
            int numWritten = _port->write(_transmitBuffer);
            bool txSuccess = _port->flush();
            txSuccess |= _port->waitForBytesWritten(10);
            if (!txSuccess || (numWritten != _transmitBuffer.count())) {
125
                linkErrorCount++;
126
                qCDebug(SerialLinkLog) << "TX Error! written:" << txSuccess << "wrote" << numWritten << ", asked for " << _transmitBuffer.count() << "bytes";
127 128
            }
            else {
129 130

                // Since we were successful, reset out error counter.
131 132
                linkErrorCount = 0;
            }
133 134

            // Now that we transmit all of the data in the transmit buffer, flush it.
135 136
            _transmitBuffer = _transmitBuffer.remove(0, numWritten);
            _writeMutex.unlock();
137 138 139

            // Log this written data for this timestep. If this value ends up being 0 due to
            // write() failing, that's what we want as well.
140 141
            QMutexLocker dataRateLocker(&dataRateMutex);
            logDataRateToBuffer(outDataWriteAmounts, outDataWriteTimes, &outDataIndex, numWritten, QDateTime::currentMSecsSinceEpoch());
142 143
        }

144 145
        //wait n msecs for data to be ready
        //[TODO][BB] lower to SerialLink::poll_interval?
146 147
        _dataMutex.lock();
        bool success = _port->waitForReadyRead(20);
148

149
        if (success) {
150 151 152 153
            QByteArray readData = _port->readAll();
            while (_port->waitForReadyRead(10))
                readData += _port->readAll();
            _dataMutex.unlock();
154 155 156
            if (readData.length() > 0) {
                emit bytesReceived(this, readData);

157
                // Log this data reception for this timestep
158 159
                QMutexLocker dataRateLocker(&dataRateMutex);
                logDataRateToBuffer(inDataWriteAmounts, inDataWriteTimes, &inDataIndex, readData.length(), QDateTime::currentMSecsSinceEpoch());
160 161

                // Track the total amount of data read.
162
                _bytesRead += readData.length();
163
                linkErrorCount = 0;
164
            }
165 166
        }
        else {
167
            _dataMutex.unlock();
168
            linkErrorCount++;
169
        }
170

171 172
        if (bytes != _bytesRead) { // i.e things are good and data is being read.
            bytes = _bytesRead;
173 174
            msecs = QDateTime::currentMSecsSinceEpoch();
        }
175 176
        else {
            if (QDateTime::currentMSecsSinceEpoch() - msecs > timeout) {
177 178
                //It's been 10 seconds since the last data came in. Reset and try again
                msecs = QDateTime::currentMSecsSinceEpoch();
179
                if (msecs - initialmsecs > 25000) {
180 181 182 183 184 185 186
                    //After initial 25 seconds, timeouts are increased to 30 seconds.
                    //This prevents temporary silences from things like calibration commands
                    //from screwing things up. In all reality, timeouts should be enabled/disabled
                    //for events like that on a case by case basis.
                    //TODO ^^
                    timeout = 30000;
                }
187 188
            }
        }
189
        QGC::SLEEP::msleep(SerialLink::poll_interval);
190
    } // end of forever
191

192
    if (_port) {
193
        qCDebug(SerialLinkLog) << "Closing Port #" << __LINE__ << _port->portName();
194 195 196
        _port->close();
        delete _port;
        _port = NULL;
197
    }
pixhawk's avatar
pixhawk committed
198 199
}

200 201
void SerialLink::writeBytes(const char* data, qint64 size)
{
202
    if(_port && _port->isOpen()) {
203
        QByteArray byteArray(data, size);
204 205 206
        _writeMutex.lock();
        _transmitBuffer.append(byteArray);
        _writeMutex.unlock();
207 208
    } else {
        // Error occured
209
        _emitLinkError(tr("Could not send data - link %1 is disconnected!").arg(getName()));
pixhawk's avatar
pixhawk committed
210 211 212 213 214 215 216 217 218
    }
}

/**
 * @brief Read a number of bytes from the interface.
 *
 * @param data Pointer to the data byte array to write the bytes to
 * @param maxLength The maximum number of bytes to write
 **/
219 220
void SerialLink::readBytes()
{
221
    if(_port && _port->isOpen()) {
222 223
        const qint64 maxLength = 2048;
        char data[maxLength];
224 225
        _dataMutex.lock();
        qint64 numBytes = _port->bytesAvailable();
226

227
        if(numBytes > 0) {
pixhawk's avatar
pixhawk committed
228 229 230
            /* Read as much data in buffer as possible without overflow */
            if(maxLength < numBytes) numBytes = maxLength;

231
            _port->read(data, numBytes);
pixhawk's avatar
pixhawk committed
232 233 234
            QByteArray b(data, numBytes);
            emit bytesReceived(this, b);
        }
235
        _dataMutex.unlock();
pixhawk's avatar
pixhawk committed
236 237 238 239 240 241 242 243
    }
}

/**
 * @brief Disconnect the connection.
 *
 * @return True if connection has been disconnected, false if connection couldn't be disconnected.
 **/
244
bool SerialLink::_disconnect(void)
245
{
Bill Bonney's avatar
Bill Bonney committed
246
    if (isRunning())
247 248
    {
        {
249 250
            QMutexLocker locker(&_stoppMutex);
            _stopp = true;
251
        }
Bill Bonney's avatar
Bill Bonney committed
252
        wait(); // This will terminate the thread and close the serial port
253 254
        return true;
    }
255
    _transmitBuffer.clear(); //clear the output buffer to avoid sending garbage at next connect
256
    qCDebug(SerialLinkLog) << "Already disconnected";
257
    return true;
pixhawk's avatar
pixhawk committed
258 259 260 261 262 263 264
}

/**
 * @brief Connect the connection.
 *
 * @return True if connection has been established, false if connection couldn't be established.
 **/
265
bool SerialLink::_connect(void)
266
{
267
    qCDebug(SerialLinkLog) << "CONNECT CALLED";
Bill Bonney's avatar
Bill Bonney committed
268
    if (isRunning())
269
        _disconnect();
270
    {
271 272
        QMutexLocker locker(&this->_stoppMutex);
        _stopp = false;
273
    }
274
    start(HighPriority);
275
    return true;
pixhawk's avatar
pixhawk committed
276 277 278
}

/**
279
 * @brief This function is called indirectly by the _connect() call.
pixhawk's avatar
pixhawk committed
280
 *
281
 * The _connect() function starts the thread and indirectly calls this method.
pixhawk's avatar
pixhawk committed
282 283
 *
 * @return True if the connection could be established, false otherwise
284
 * @see _connect() For the right function to establish the connection.
pixhawk's avatar
pixhawk committed
285
 **/
286
bool SerialLink::_hardwareConnect(QString &type)
287
{
288
    if (_port) {
289
        qCDebug(SerialLinkLog) << "SerialLink:" << QString::number((long)this, 16) << "closing port";
290
        _port->close();
291
        QGC::SLEEP::usleep(50000);
292 293
        delete _port;
        _port = NULL;
294
    }
pixhawk's avatar
pixhawk committed
295

296
    qCDebug(SerialLinkLog) << "SerialLink: hardwareConnect to " << _config->portName();
297

298
    // If we are in the Pixhawk bootloader code wait for it to timeout
299
    if (_isBootloader()) {
300
        qCDebug(SerialLinkLog) << "Not connecting to a bootloader, waiting for 2nd chance";
301 302 303
        const unsigned retry_limit = 12;
        unsigned retries;
        for (retries = 0; retries < retry_limit; retries++) {
304 305
            if (!_isBootloader()) {
                QGC::SLEEP::msleep(500);
306 307 308 309 310 311 312
                break;
            }
            QGC::SLEEP::msleep(500);
        }
        // Check limit
        if (retries == retry_limit) {
            // bail out
313
            qWarning() << "Timeout waiting for something other than booloader";
314 315 316 317
            return false;
        }
    }

318 319 320
    _port = new QSerialPort(_config->portName());
    if (!_port) {
        emit communicationUpdate(getName(),"Error opening port: " + _config->portName());
Bill Bonney's avatar
Bill Bonney committed
321
        return false; // couldn't create serial port.
322
    }
323
    _port->moveToThread(this);
Bill Bonney's avatar
Bill Bonney committed
324

325 326
    // We need to catch this signal and then emit disconnected. You can't connect
    // signal to signal otherwise disonnected will have the wrong QObject::Sender
327 328
    QObject::connect(_port, SIGNAL(aboutToClose()), this, SLOT(_rerouteDisconnected()));
    QObject::connect(_port, SIGNAL(error(QSerialPort::SerialPortError)), this, SLOT(linkError(QSerialPort::SerialPortError)));
Bill Bonney's avatar
Bill Bonney committed
329

330
    //  port->setCommTimeouts(QSerialPort::CtScheme_NonBlockingRead);
pixhawk's avatar
pixhawk committed
331

332
    // TODO This needs a bit of TLC still...
333

334
    // After the bootloader times out, it still can take a second or so for the Pixhawk USB driver to come up and make
335 336 337
    // the port available for open. So we retry a few times to wait for it.
    for (int openRetries = 0; openRetries < 4; openRetries++) {
        if (!_port->open(QIODevice::ReadWrite)) {
338
            qCDebug(SerialLinkLog) << "Port open failed, retrying";
339 340 341 342
            QGC::SLEEP::msleep(500);
        } else {
            break;
        }
343
    }
344 345 346 347
    if (!_port->isOpen() ) {
        emit communicationUpdate(getName(),"Error opening port: " + _port->errorString());
        _port->close();
        return false; // couldn't open serial port
348 349
    }

350
    qCDebug(SerialLinkLog) << "Configuring port";
351 352 353 354 355
    _port->setBaudRate     (_config->baud());
    _port->setDataBits     (static_cast<QSerialPort::DataBits>     (_config->dataBits()));
    _port->setFlowControl  (static_cast<QSerialPort::FlowControl>  (_config->flowControl()));
    _port->setStopBits     (static_cast<QSerialPort::StopBits>     (_config->stopBits()));
    _port->setParity       (static_cast<QSerialPort::Parity>       (_config->parity()));
356

357
    emit communicationUpdate(getName(), "Opened port!");
Bill Bonney's avatar
Bill Bonney committed
358
    emit connected();
359

360
    qCDebug(SerialLinkLog) << "CONNECTING LINK: " << __FILE__ << __LINE__ << "type:" << type << "with settings" << _config->portName()
361
             << _config->baud() << _config->dataBits() << _config->parity() << _config->stopBits();
362

Bill Bonney's avatar
Bill Bonney committed
363
    return true; // successful connection
pixhawk's avatar
pixhawk committed
364
}
365

366
void SerialLink::linkError(QSerialPort::SerialPortError error)
367
{
368
    if (error != QSerialPort::NoError) {
369 370 371 372
        // 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.
373
        //qCDebug(SerialLinkLog) << "SerialLink::linkError" << error;
374
    }
375 376
}

pixhawk's avatar
pixhawk committed
377 378 379 380 381
/**
 * @brief Check if connection is active.
 *
 * @return True if link is connected, false otherwise.
 **/
382
bool SerialLink::isConnected() const
383
{
384
    bool isConnected = false;
Bill Bonney's avatar
Bill Bonney committed
385

386
    if (_port) {
387
        isConnected = _port->isOpen();
lm's avatar
lm committed
388
    }
389 390
    
    return isConnected;
pixhawk's avatar
pixhawk committed
391 392
}

393
int SerialLink::getId() const
pixhawk's avatar
pixhawk committed
394
{
395
    return _id;
pixhawk's avatar
pixhawk committed
396 397
}

398
QString SerialLink::getName() const
pixhawk's avatar
pixhawk committed
399
{
400
    return _config->portName();
pixhawk's avatar
pixhawk committed
401 402
}

403 404 405 406
/**
  * This function maps baud rate constants to numerical equivalents.
  * It relies on the mapping given in qportsettings.h from the QSerialPort library.
  */
407
qint64 SerialLink::getConnectionSpeed() const
408
{
Bill Bonney's avatar
Bill Bonney committed
409
    int baudRate;
410 411
    if (_port) {
        baudRate = _port->baudRate();
Bill Bonney's avatar
Bill Bonney committed
412
    } else {
413
        baudRate = _config->baud();
Bill Bonney's avatar
Bill Bonney committed
414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445
    }
    qint64 dataRate;
    switch (baudRate)
    {
        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
446 447 448 449
    }
    return dataRate;
}

450
void SerialLink::_resetConfiguration()
451
{
452 453 454 455 456 457 458 459 460
    bool somethingChanged = false;
    if (_port) {
        somethingChanged = _port->setBaudRate     (_config->baud());
        somethingChanged |= _port->setDataBits    (static_cast<QSerialPort::DataBits>    (_config->dataBits()));
        somethingChanged |= _port->setFlowControl (static_cast<QSerialPort::FlowControl> (_config->flowControl()));
        somethingChanged |= _port->setStopBits    (static_cast<QSerialPort::StopBits>    (_config->stopBits()));
        somethingChanged |= _port->setParity      (static_cast<QSerialPort::Parity>      (_config->parity()));
    }
    if(somethingChanged) {
461
        qCDebug(SerialLinkLog) << "Reconfiguring port";
462 463
        emit updateLink(this);
    }
pixhawk's avatar
pixhawk committed
464 465
}

466
void SerialLink::_rerouteDisconnected(void)
467
{
468
    emit disconnected();
pixhawk's avatar
pixhawk committed
469 470
}

471
void SerialLink::_emitLinkError(const QString& errorMsg)
472
{
473 474
    QString msg("Error on link %1. %2");
    emit communicationError(tr("Link Error"), msg.arg(getName()).arg(errorMsg));
pixhawk's avatar
pixhawk committed
475 476
}

477
LinkConfiguration* SerialLink::getLinkConfiguration()
478
{
479
    return _config;
pixhawk's avatar
pixhawk committed
480 481
}

482 483
//--------------------------------------------------------------------------
//-- SerialConfiguration
pixhawk's avatar
pixhawk committed
484

485
SerialConfiguration::SerialConfiguration(const QString& name) : LinkConfiguration(name)
486
{
487 488 489 490 491
    _baud       = 57600;
    _flowControl= QSerialPort::NoFlowControl;
    _parity     = QSerialPort::NoParity;
    _dataBits   = 8;
    _stopBits   = 1;
pixhawk's avatar
pixhawk committed
492 493
}

494
SerialConfiguration::SerialConfiguration(SerialConfiguration* copy) : LinkConfiguration(copy)
495
{
496 497 498 499 500 501
    _baud       = copy->baud();
    _flowControl= copy->flowControl();
    _parity     = copy->parity();
    _dataBits   = copy->dataBits();
    _stopBits   = copy->stopBits();
    _portName   = copy->portName();
pixhawk's avatar
pixhawk committed
502 503
}

504
void SerialConfiguration::copyFrom(LinkConfiguration *source)
505
{
506 507 508 509 510 511 512 513 514
    LinkConfiguration::copyFrom(source);
    SerialConfiguration* ssource = dynamic_cast<SerialConfiguration*>(source);
    Q_ASSERT(ssource != NULL);
    _baud       = ssource->baud();
    _flowControl= ssource->flowControl();
    _parity     = ssource->parity();
    _dataBits   = ssource->dataBits();
    _stopBits   = ssource->stopBits();
    _portName   = ssource->portName();
515 516
}

517
void SerialConfiguration::updateSettings()
518
{
519 520 521 522 523
    if(_link) {
        SerialLink* serialLink = dynamic_cast<SerialLink*>(_link);
        if(serialLink) {
            serialLink->_resetConfiguration();
        }
524
    }
525 526
}

527
void SerialConfiguration::setBaud(int baud)
pixhawk's avatar
pixhawk committed
528
{
529
    _baud = baud;
pixhawk's avatar
pixhawk committed
530 531
}

532
void SerialConfiguration::setDataBits(int databits)
pixhawk's avatar
pixhawk committed
533
{
534
    _dataBits = databits;
pixhawk's avatar
pixhawk committed
535 536
}

537
void SerialConfiguration::setFlowControl(int flowControl)
pixhawk's avatar
pixhawk committed
538
{
539
    _flowControl = flowControl;
pixhawk's avatar
pixhawk committed
540 541
}

542
void SerialConfiguration::setStopBits(int stopBits)
543
{
544
    _stopBits = stopBits;
pixhawk's avatar
pixhawk committed
545 546
}

547
void SerialConfiguration::setParity(int parity)
548
{
549
    _parity = parity;
pixhawk's avatar
pixhawk committed
550 551
}

552
void SerialConfiguration::setPortName(const QString& portName)
553
{
554 555 556 557
    // No effect on a running connection
    QString pname = portName.trimmed();
    if (!pname.isEmpty() && pname != _portName) {
        _portName = pname;
558 559 560
    }
}

561
void SerialConfiguration::saveSettings(QSettings& settings, const QString& root)
562
{
563 564 565 566 567 568 569 570
    settings.beginGroup(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.endGroup();
571
}
572

573
void SerialConfiguration::loadSettings(QSettings& settings, const QString& root)
574
{
575 576 577 578 579 580 581 582
    settings.beginGroup(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();
    settings.endGroup();
583
}
584

585
QList<QString> SerialConfiguration::getCurrentPorts()
586
{
587 588 589 590 591 592 593
    QList<QString> ports;
    QList<QSerialPortInfo> portList =  QSerialPortInfo::availablePorts();
    foreach (const QSerialPortInfo &info, portList)
    {
        ports.append(info.portName());
    }
    return ports;
594
}