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
#include "MG.h"
21
#include "QGCLoggingCategory.h"
22

23
QGC_LOGGING_CATEGORY(SerialLinkLog, "SerialLinkLog")
pixhawk's avatar
pixhawk committed
24

25 26 27 28 29 30 31 32 33
SerialLink::SerialLink(SerialConfiguration* config)
{
    _bytesRead = 0;
    _port     = Q_NULLPTR;
    _stopp    = false;
    _reqReset = false;
    Q_ASSERT(config != NULL);
    _config = config;
    _config->setLink(this);
34 35 36
    // 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
37
    // Set unique ID and add link to the list of links
38
    _id = getNextLinkId();
Bill Bonney's avatar
Bill Bonney committed
39

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

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

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

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

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

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

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

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

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

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

            // 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.
141 142
            QMutexLocker dataRateLocker(&dataRateMutex);
            logDataRateToBuffer(outDataWriteAmounts, outDataWriteTimes, &outDataIndex, numWritten, QDateTime::currentMSecsSinceEpoch());
143 144
        }

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

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

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

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

172 173
        if (bytes != _bytesRead) { // i.e things are good and data is being read.
            bytes = _bytesRead;
174 175
            msecs = QDateTime::currentMSecsSinceEpoch();
        }
176 177
        else {
            if (QDateTime::currentMSecsSinceEpoch() - msecs > timeout) {
178 179
                //It's been 10 seconds since the last data came in. Reset and try again
                msecs = QDateTime::currentMSecsSinceEpoch();
180
                if (msecs - initialmsecs > 25000) {
181 182 183 184 185 186 187
                    //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;
                }
188 189
            }
        }
190
        QGC::SLEEP::msleep(SerialLink::poll_interval);
191
    } // end of forever
192

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

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

/**
 * @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
 **/
220 221
void SerialLink::readBytes()
{
222
    if(_port && _port->isOpen()) {
223 224
        const qint64 maxLength = 2048;
        char data[maxLength];
225 226
        _dataMutex.lock();
        qint64 numBytes = _port->bytesAvailable();
227

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

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

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

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

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

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

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

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

326 327
    // 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
328 329
    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
330

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

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

335
    // After the bootloader times out, it still can take a second or so for the Pixhawk USB driver to come up and make
336 337 338
    // 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)) {
339
            qCDebug(SerialLinkLog) << "Port open failed, retrying";
340 341 342 343
            QGC::SLEEP::msleep(500);
        } else {
            break;
        }
344
    }
345 346 347 348
    if (!_port->isOpen() ) {
        emit communicationUpdate(getName(),"Error opening port: " + _port->errorString());
        _port->close();
        return false; // couldn't open serial port
349 350
    }

351
    qCDebug(SerialLinkLog) << "Configuring port";
352 353 354 355 356
    _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()));
357

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

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

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

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

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

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

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

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

404 405 406 407
/**
  * This function maps baud rate constants to numerical equivalents.
  * It relies on the mapping given in qportsettings.h from the QSerialPort library.
  */
408
qint64 SerialLink::getConnectionSpeed() const
409
{
Bill Bonney's avatar
Bill Bonney committed
410
    int baudRate;
411 412
    if (_port) {
        baudRate = _port->baudRate();
Bill Bonney's avatar
Bill Bonney committed
413
    } else {
414
        baudRate = _config->baud();
Bill Bonney's avatar
Bill Bonney committed
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 446
    }
    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
447 448 449 450
    }
    return dataRate;
}

451
void SerialLink::_resetConfiguration()
452
{
453 454 455 456 457 458 459 460 461
    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) {
462
        qCDebug(SerialLinkLog) << "Reconfiguring port";
463 464
        emit updateLink(this);
    }
pixhawk's avatar
pixhawk committed
465 466
}

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

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

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

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

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

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

505
void SerialConfiguration::copyFrom(LinkConfiguration *source)
506
{
507 508 509 510 511 512 513 514 515
    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();
516 517
}

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

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

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

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

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

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

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

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

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

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