SerialLink.cc 18.9 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>
dogmaphobic's avatar
dogmaphobic committed
15 16 17 18 19

#ifdef __android__
#include "qserialport.h"
#include "qserialportinfo.h"
#else
20 21
#include <QSerialPort>
#include <QSerialPortInfo>
dogmaphobic's avatar
dogmaphobic committed
22
#endif
23

pixhawk's avatar
pixhawk committed
24
#include "SerialLink.h"
25
#include "QGC.h"
26
#include "MG.h"
27
#include "QGCLoggingCategory.h"
28

29
QGC_LOGGING_CATEGORY(SerialLinkLog, "SerialLinkLog")
pixhawk's avatar
pixhawk committed
30

31 32 33 34 35 36 37 38 39
SerialLink::SerialLink(SerialConfiguration* config)
{
    _bytesRead = 0;
    _port     = Q_NULLPTR;
    _stopp    = false;
    _reqReset = false;
    Q_ASSERT(config != NULL);
    _config = config;
    _config->setLink(this);
40 41 42
    // 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
43

44
    qCDebug(SerialLinkLog) << "Create SerialLink " << config->portName() << config->baud() << config->flowControl()
45
             << config->parity() << config->dataBits() << config->stopBits();
46
    qCDebug(SerialLinkLog) << "portName: " << config->portName();
pixhawk's avatar
pixhawk committed
47
}
48

49 50
void SerialLink::requestReset()
{
51 52
    QMutexLocker locker(&this->_stoppMutex);
    _reqReset = true;
53
}
pixhawk's avatar
pixhawk committed
54 55 56

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

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

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

109 110
    qint64  msecs = QDateTime::currentMSecsSinceEpoch();
    qint64  initialmsecs = QDateTime::currentMSecsSinceEpoch();
111
    quint64 bytes = 0;
112
    qint64  timeout = 5000;
113
    int linkErrorCount = 0;
114

115
    // Qt way to make clear what a while(1) loop does
116
    forever {
117
        {
118 119 120
            QMutexLocker locker(&this->_stoppMutex);
            if (_stopp) {
                _stopp = false;
121 122
                break; // exit the thread
            }
123
        }
124

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

                // Since we were successful, reset out error counter.
138 139
                linkErrorCount = 0;
            }
140 141

            // Now that we transmit all of the data in the transmit buffer, flush it.
142 143
            _transmitBuffer = _transmitBuffer.remove(0, numWritten);
            _writeMutex.unlock();
144 145 146

            // 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.
147 148
            QMutexLocker dataRateLocker(&dataRateMutex);
            logDataRateToBuffer(outDataWriteAmounts, outDataWriteTimes, &outDataIndex, numWritten, QDateTime::currentMSecsSinceEpoch());
149 150
        }

151 152
        //wait n msecs for data to be ready
        //[TODO][BB] lower to SerialLink::poll_interval?
153 154
        _dataMutex.lock();
        bool success = _port->waitForReadyRead(20);
155

156
        if (success) {
157 158 159 160
            QByteArray readData = _port->readAll();
            while (_port->waitForReadyRead(10))
                readData += _port->readAll();
            _dataMutex.unlock();
161 162 163
            if (readData.length() > 0) {
                emit bytesReceived(this, readData);

164
                // Log this data reception for this timestep
165 166
                QMutexLocker dataRateLocker(&dataRateMutex);
                logDataRateToBuffer(inDataWriteAmounts, inDataWriteTimes, &inDataIndex, readData.length(), QDateTime::currentMSecsSinceEpoch());
167 168

                // Track the total amount of data read.
169
                _bytesRead += readData.length();
170
                linkErrorCount = 0;
171
            }
172 173
        }
        else {
174
            _dataMutex.unlock();
175
            linkErrorCount++;
176
        }
177

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

199
    if (_port) {
200
        qCDebug(SerialLinkLog) << "Closing Port #" << __LINE__ << _port->portName();
201 202 203
        _port->close();
        delete _port;
        _port = NULL;
204
    }
pixhawk's avatar
pixhawk committed
205 206
}

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

/**
 * @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
 **/
226 227
void SerialLink::readBytes()
{
228
    if(_port && _port->isOpen()) {
229 230
        const qint64 maxLength = 2048;
        char data[maxLength];
231 232
        _dataMutex.lock();
        qint64 numBytes = _port->bytesAvailable();
233

234
        if(numBytes > 0) {
pixhawk's avatar
pixhawk committed
235 236 237
            /* Read as much data in buffer as possible without overflow */
            if(maxLength < numBytes) numBytes = maxLength;

238
            _port->read(data, numBytes);
pixhawk's avatar
pixhawk committed
239 240 241
            QByteArray b(data, numBytes);
            emit bytesReceived(this, b);
        }
242
        _dataMutex.unlock();
pixhawk's avatar
pixhawk committed
243 244 245 246 247 248 249 250
    }
}

/**
 * @brief Disconnect the connection.
 *
 * @return True if connection has been disconnected, false if connection couldn't be disconnected.
 **/
251
bool SerialLink::_disconnect(void)
252
{
Bill Bonney's avatar
Bill Bonney committed
253
    if (isRunning())
254 255
    {
        {
256 257
            QMutexLocker locker(&_stoppMutex);
            _stopp = true;
258
        }
Bill Bonney's avatar
Bill Bonney committed
259
        wait(); // This will terminate the thread and close the serial port
dogmaphobic's avatar
dogmaphobic committed
260 261 262
    } else {
        _transmitBuffer.clear(); //clear the output buffer to avoid sending garbage at next connect
        qCDebug(SerialLinkLog) << "Already disconnected";
263
    }
dogmaphobic's avatar
dogmaphobic committed
264 265 266
#ifdef __android__
    LinkManager::instance()->suspendConfigurationUpdates(false);
#endif
267
    return true;
pixhawk's avatar
pixhawk committed
268 269 270 271 272 273 274
}

/**
 * @brief Connect the connection.
 *
 * @return True if connection has been established, false if connection couldn't be established.
 **/
275
bool SerialLink::_connect(void)
276
{
277
    qCDebug(SerialLinkLog) << "CONNECT CALLED";
Bill Bonney's avatar
Bill Bonney committed
278
    if (isRunning())
279
        _disconnect();
280
    {
281 282
        QMutexLocker locker(&this->_stoppMutex);
        _stopp = false;
283
    }
dogmaphobic's avatar
dogmaphobic committed
284 285 286 287 288 289 290 291 292 293 294 295 296
#ifdef __android__
    LinkManager::instance()->suspendConfigurationUpdates(true);
    // Initialize the connection
    if (!_hardwareConnect(_type)) {
        // Need to error out here.
        QString err("Could not create port.");
        if (_port) {
            err = _port->errorString();
        }
        _emitLinkError("Error connecting: " + err);
        return false;
    }
#endif
297
    start(HighPriority);
298
    return true;
pixhawk's avatar
pixhawk committed
299 300 301
}

/**
302
 * @brief This function is called indirectly by the _connect() call.
pixhawk's avatar
pixhawk committed
303
 *
304
 * The _connect() function starts the thread and indirectly calls this method.
pixhawk's avatar
pixhawk committed
305 306
 *
 * @return True if the connection could be established, false otherwise
307
 * @see _connect() For the right function to establish the connection.
pixhawk's avatar
pixhawk committed
308
 **/
309
bool SerialLink::_hardwareConnect(QString &type)
310
{
311
    if (_port) {
312
        qCDebug(SerialLinkLog) << "SerialLink:" << QString::number((long)this, 16) << "closing port";
313
        _port->close();
314
        QGC::SLEEP::usleep(50000);
315 316
        delete _port;
        _port = NULL;
317
    }
pixhawk's avatar
pixhawk committed
318

319
    qCDebug(SerialLinkLog) << "SerialLink: hardwareConnect to " << _config->portName();
320

321
    // If we are in the Pixhawk bootloader code wait for it to timeout
322
    if (_isBootloader()) {
323
        qCDebug(SerialLinkLog) << "Not connecting to a bootloader, waiting for 2nd chance";
324 325 326
        const unsigned retry_limit = 12;
        unsigned retries;
        for (retries = 0; retries < retry_limit; retries++) {
327 328
            if (!_isBootloader()) {
                QGC::SLEEP::msleep(500);
329 330 331 332 333 334 335
                break;
            }
            QGC::SLEEP::msleep(500);
        }
        // Check limit
        if (retries == retry_limit) {
            // bail out
336
            qWarning() << "Timeout waiting for something other than booloader";
337 338 339 340
            return false;
        }
    }

341 342 343
    _port = new QSerialPort(_config->portName());
    if (!_port) {
        emit communicationUpdate(getName(),"Error opening port: " + _config->portName());
Bill Bonney's avatar
Bill Bonney committed
344
        return false; // couldn't create serial port.
345
    }
346
    _port->moveToThread(this);
Bill Bonney's avatar
Bill Bonney committed
347

348 349
    // 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
350 351
    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
352

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

355
    // TODO This needs a bit of TLC still...
356

357
    // After the bootloader times out, it still can take a second or so for the Pixhawk USB driver to come up and make
358
    // the port available for open. So we retry a few times to wait for it.
dogmaphobic's avatar
dogmaphobic committed
359 360 361
#ifdef __android__
    _port->open(QIODevice::ReadWrite);
#else
362 363
    for (int openRetries = 0; openRetries < 4; openRetries++) {
        if (!_port->open(QIODevice::ReadWrite)) {
364
            qCDebug(SerialLinkLog) << "Port open failed, retrying";
365 366 367 368
            QGC::SLEEP::msleep(500);
        } else {
            break;
        }
369
    }
dogmaphobic's avatar
dogmaphobic committed
370
#endif
371 372 373 374
    if (!_port->isOpen() ) {
        emit communicationUpdate(getName(),"Error opening port: " + _port->errorString());
        _port->close();
        return false; // couldn't open serial port
375 376
    }

377
    qCDebug(SerialLinkLog) << "Configuring port";
378 379 380 381 382
    _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()));
383

384
    emit communicationUpdate(getName(), "Opened port!");
Bill Bonney's avatar
Bill Bonney committed
385
    emit connected();
386

387
    qCDebug(SerialLinkLog) << "CONNECTING LINK: " << __FILE__ << __LINE__ << "type:" << type << "with settings" << _config->portName()
388
             << _config->baud() << _config->dataBits() << _config->parity() << _config->stopBits();
389

Bill Bonney's avatar
Bill Bonney committed
390
    return true; // successful connection
pixhawk's avatar
pixhawk committed
391
}
392

393
void SerialLink::linkError(QSerialPort::SerialPortError error)
394
{
395
    if (error != QSerialPort::NoError) {
396 397 398 399
        // 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.
400
        //qCDebug(SerialLinkLog) << "SerialLink::linkError" << error;
401
    }
402 403
}

pixhawk's avatar
pixhawk committed
404 405 406 407 408
/**
 * @brief Check if connection is active.
 *
 * @return True if link is connected, false otherwise.
 **/
409
bool SerialLink::isConnected() const
410
{
411
    bool isConnected = false;
Bill Bonney's avatar
Bill Bonney committed
412

413
    if (_port) {
414
        isConnected = _port->isOpen();
lm's avatar
lm committed
415
    }
416 417
    
    return isConnected;
pixhawk's avatar
pixhawk committed
418 419
}

420
QString SerialLink::getName() const
pixhawk's avatar
pixhawk committed
421
{
422
    return _config->portName();
pixhawk's avatar
pixhawk committed
423 424
}

425 426 427 428
/**
  * This function maps baud rate constants to numerical equivalents.
  * It relies on the mapping given in qportsettings.h from the QSerialPort library.
  */
429
qint64 SerialLink::getConnectionSpeed() const
430
{
Bill Bonney's avatar
Bill Bonney committed
431
    int baudRate;
432 433
    if (_port) {
        baudRate = _port->baudRate();
Bill Bonney's avatar
Bill Bonney committed
434
    } else {
435
        baudRate = _config->baud();
Bill Bonney's avatar
Bill Bonney committed
436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467
    }
    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
468 469 470 471
    }
    return dataRate;
}

472
void SerialLink::_resetConfiguration()
473
{
474 475 476 477 478 479 480 481 482
    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) {
483
        qCDebug(SerialLinkLog) << "Reconfiguring port";
484 485
        emit updateLink(this);
    }
pixhawk's avatar
pixhawk committed
486 487
}

488
void SerialLink::_rerouteDisconnected(void)
489
{
490
    emit disconnected();
pixhawk's avatar
pixhawk committed
491 492
}

493
void SerialLink::_emitLinkError(const QString& errorMsg)
494
{
495 496
    QString msg("Error on link %1. %2");
    emit communicationError(tr("Link Error"), msg.arg(getName()).arg(errorMsg));
pixhawk's avatar
pixhawk committed
497 498
}

499
LinkConfiguration* SerialLink::getLinkConfiguration()
500
{
501
    return _config;
pixhawk's avatar
pixhawk committed
502 503
}

504 505
//--------------------------------------------------------------------------
//-- SerialConfiguration
pixhawk's avatar
pixhawk committed
506

507
SerialConfiguration::SerialConfiguration(const QString& name) : LinkConfiguration(name)
508
{
509 510 511 512 513
    _baud       = 57600;
    _flowControl= QSerialPort::NoFlowControl;
    _parity     = QSerialPort::NoParity;
    _dataBits   = 8;
    _stopBits   = 1;
pixhawk's avatar
pixhawk committed
514 515
}

516
SerialConfiguration::SerialConfiguration(SerialConfiguration* copy) : LinkConfiguration(copy)
517
{
518 519 520 521 522 523
    _baud       = copy->baud();
    _flowControl= copy->flowControl();
    _parity     = copy->parity();
    _dataBits   = copy->dataBits();
    _stopBits   = copy->stopBits();
    _portName   = copy->portName();
pixhawk's avatar
pixhawk committed
524 525
}

526
void SerialConfiguration::copyFrom(LinkConfiguration *source)
527
{
528 529 530 531 532 533 534 535 536
    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();
537 538
}

539
void SerialConfiguration::updateSettings()
540
{
541 542 543 544 545
    if(_link) {
        SerialLink* serialLink = dynamic_cast<SerialLink*>(_link);
        if(serialLink) {
            serialLink->_resetConfiguration();
        }
546
    }
547 548
}

549
void SerialConfiguration::setBaud(int baud)
pixhawk's avatar
pixhawk committed
550
{
551
    _baud = baud;
pixhawk's avatar
pixhawk committed
552 553
}

554
void SerialConfiguration::setDataBits(int databits)
pixhawk's avatar
pixhawk committed
555
{
556
    _dataBits = databits;
pixhawk's avatar
pixhawk committed
557 558
}

559
void SerialConfiguration::setFlowControl(int flowControl)
pixhawk's avatar
pixhawk committed
560
{
561
    _flowControl = flowControl;
pixhawk's avatar
pixhawk committed
562 563
}

564
void SerialConfiguration::setStopBits(int stopBits)
565
{
566
    _stopBits = stopBits;
pixhawk's avatar
pixhawk committed
567 568
}

569
void SerialConfiguration::setParity(int parity)
570
{
571
    _parity = parity;
pixhawk's avatar
pixhawk committed
572 573
}

574
void SerialConfiguration::setPortName(const QString& portName)
575
{
576 577 578 579
    // No effect on a running connection
    QString pname = portName.trimmed();
    if (!pname.isEmpty() && pname != _portName) {
        _portName = pname;
580 581 582
    }
}

583
void SerialConfiguration::saveSettings(QSettings& settings, const QString& root)
584
{
585 586 587 588 589 590 591 592
    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();
593
}
594

595
void SerialConfiguration::loadSettings(QSettings& settings, const QString& root)
596
{
597 598 599 600 601 602 603 604
    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();
605
}