SerialLink.cc 18.2 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);
Bill Bonney's avatar
Bill Bonney committed
37

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

324 325
    // 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
326 327
    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
328

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

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

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

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

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

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

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

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

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

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

392
QString SerialLink::getName() const
pixhawk's avatar
pixhawk committed
393
{
394
    return _config->portName();
pixhawk's avatar
pixhawk committed
395 396
}

397 398 399 400
/**
  * This function maps baud rate constants to numerical equivalents.
  * It relies on the mapping given in qportsettings.h from the QSerialPort library.
  */
401
qint64 SerialLink::getConnectionSpeed() const
402
{
Bill Bonney's avatar
Bill Bonney committed
403
    int baudRate;
404 405
    if (_port) {
        baudRate = _port->baudRate();
Bill Bonney's avatar
Bill Bonney committed
406
    } else {
407
        baudRate = _config->baud();
Bill Bonney's avatar
Bill Bonney committed
408 409 410 411 412 413 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
    }
    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
440 441 442 443
    }
    return dataRate;
}

444
void SerialLink::_resetConfiguration()
445
{
446 447 448 449 450 451 452 453 454
    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) {
455
        qCDebug(SerialLinkLog) << "Reconfiguring port";
456 457
        emit updateLink(this);
    }
pixhawk's avatar
pixhawk committed
458 459
}

460
void SerialLink::_rerouteDisconnected(void)
461
{
462
    emit disconnected();
pixhawk's avatar
pixhawk committed
463 464
}

465
void SerialLink::_emitLinkError(const QString& errorMsg)
466
{
467 468
    QString msg("Error on link %1. %2");
    emit communicationError(tr("Link Error"), msg.arg(getName()).arg(errorMsg));
pixhawk's avatar
pixhawk committed
469 470
}

471
LinkConfiguration* SerialLink::getLinkConfiguration()
472
{
473
    return _config;
pixhawk's avatar
pixhawk committed
474 475
}

476 477
//--------------------------------------------------------------------------
//-- SerialConfiguration
pixhawk's avatar
pixhawk committed
478

479
SerialConfiguration::SerialConfiguration(const QString& name) : LinkConfiguration(name)
480
{
481 482 483 484 485
    _baud       = 57600;
    _flowControl= QSerialPort::NoFlowControl;
    _parity     = QSerialPort::NoParity;
    _dataBits   = 8;
    _stopBits   = 1;
pixhawk's avatar
pixhawk committed
486 487
}

488
SerialConfiguration::SerialConfiguration(SerialConfiguration* copy) : LinkConfiguration(copy)
489
{
490 491 492 493 494 495
    _baud       = copy->baud();
    _flowControl= copy->flowControl();
    _parity     = copy->parity();
    _dataBits   = copy->dataBits();
    _stopBits   = copy->stopBits();
    _portName   = copy->portName();
pixhawk's avatar
pixhawk committed
496 497
}

498
void SerialConfiguration::copyFrom(LinkConfiguration *source)
499
{
500 501 502 503 504 505 506 507 508
    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();
509 510
}

511
void SerialConfiguration::updateSettings()
512
{
513 514 515 516 517
    if(_link) {
        SerialLink* serialLink = dynamic_cast<SerialLink*>(_link);
        if(serialLink) {
            serialLink->_resetConfiguration();
        }
518
    }
519 520
}

521
void SerialConfiguration::setBaud(int baud)
pixhawk's avatar
pixhawk committed
522
{
523
    _baud = baud;
pixhawk's avatar
pixhawk committed
524 525
}

526
void SerialConfiguration::setDataBits(int databits)
pixhawk's avatar
pixhawk committed
527
{
528
    _dataBits = databits;
pixhawk's avatar
pixhawk committed
529 530
}

531
void SerialConfiguration::setFlowControl(int flowControl)
pixhawk's avatar
pixhawk committed
532
{
533
    _flowControl = flowControl;
pixhawk's avatar
pixhawk committed
534 535
}

536
void SerialConfiguration::setStopBits(int stopBits)
537
{
538
    _stopBits = stopBits;
pixhawk's avatar
pixhawk committed
539 540
}

541
void SerialConfiguration::setParity(int parity)
542
{
543
    _parity = parity;
pixhawk's avatar
pixhawk committed
544 545
}

546
void SerialConfiguration::setPortName(const QString& portName)
547
{
548 549 550 551
    // No effect on a running connection
    QString pname = portName.trimmed();
    if (!pname.isEmpty() && pname != _portName) {
        _portName = pname;
552 553 554
    }
}

555
void SerialConfiguration::saveSettings(QSettings& settings, const QString& root)
556
{
557 558 559 560 561 562 563 564
    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();
565
}
566

567
void SerialConfiguration::loadSettings(QSettings& settings, const QString& root)
568
{
569 570 571 572 573 574 575 576
    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();
577
}