SerialLink.cc 18.3 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()
{
pixhawk's avatar
pixhawk committed
96
    // Initialize the connection
97 98
    if (!_hardwareConnect(_type)) {
        // Need to error out here.
99
        QString err("Could not create port.");
100 101
        if (_port) {
            err = _port->errorString();
102
        }
103
        _emitLinkError("Error connecting: " + err);
104
        return;
105
    }
pixhawk's avatar
pixhawk committed
106

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

301
    qCDebug(SerialLinkLog) << "SerialLink: hardwareConnect to " << _config->portName();
302

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

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

330 331
    // 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
332 333
    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
334

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

337
    // TODO This needs a bit of TLC still...
338

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

355
    qCDebug(SerialLinkLog) << "Configuring port";
356 357 358 359 360
    _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()));
361

362
    emit communicationUpdate(getName(), "Opened port!");
Bill Bonney's avatar
Bill Bonney committed
363
    emit connected();
364

365
    qCDebug(SerialLinkLog) << "CONNECTING LINK: " << __FILE__ << __LINE__ << "type:" << type << "with settings" << _config->portName()
366
             << _config->baud() << _config->dataBits() << _config->parity() << _config->stopBits();
367

Bill Bonney's avatar
Bill Bonney committed
368
    return true; // successful connection
pixhawk's avatar
pixhawk committed
369
}
370

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

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

391
    if (_port) {
392
        isConnected = _port->isOpen();
lm's avatar
lm committed
393
    }
394 395
    
    return isConnected;
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
}