SerialLink.cc 19.4 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>
pixhawk's avatar
pixhawk committed
17
#include "SerialLink.h"
18
#include "QGC.h"
pixhawk's avatar
pixhawk committed
19 20
#include <MG.h>

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

36 37 38
    qDebug() << "Create SerialLink " << config->portName() << config->baud() << config->flowControl()
             << config->parity() << config->dataBits() << config->stopBits();
    qDebug() << "portName: " << config->portName();
pixhawk's avatar
pixhawk committed
39
}
40

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

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

60
bool SerialLink::_isBootloader()
61
{
62
    QList<QSerialPortInfo> portList =  QSerialPortInfo::availablePorts();
63 64 65 66 67
    if( portList.count() == 0){
        return false;
    }
    foreach (const QSerialPortInfo &info, portList)
    {
68
        // XXX debug statements will be removed once we have 100% stable link reports
69 70 71
//        qDebug() << "PortName    : " << info.portName()
//                 << "Description : " << info.description();
//        qDebug() << "Manufacturer: " << info.manufacturer();
72 73 74 75 76
       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"))) {
//         qDebug() << "BOOTLOADER FOUND";
77 78 79 80 81 82 83
           return true;
       }
    }
    // 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
        // TODO This needs a bit of TLC still...
118
        // If there are too many errors on this link, disconnect.
119
        if (isConnected() && (linkErrorCount > 150)) {
120
            qDebug() << "linkErrorCount too high: re-connecting!";
121
            linkErrorCount = 0;
122
            emit communicationUpdate(getName(), tr("Link timeout, not receiving any data, attempting reconnect"));
123 124 125 126
            if (_port) {
                _port->close();
                delete _port;
                _port = NULL;
127 128 129 130
            }
            QGC::SLEEP::msleep(500);
            unsigned tries = 0;
            const unsigned tries_max = 15;
131
            while (!_hardwareConnect(_type) && tries < tries_max) {
132 133 134 135 136 137 138
                tries++;
                QGC::SLEEP::msleep(500);
            }
            // Give up
            if (tries == tries_max) {
                break;
            }
139 140
        }

141
        // Write all our buffered data out the serial port.
142 143 144 145 146 147
        if (_transmitBuffer.count() > 0) {
            _writeMutex.lock();
            int numWritten = _port->write(_transmitBuffer);
            bool txSuccess = _port->flush();
            txSuccess |= _port->waitForBytesWritten(10);
            if (!txSuccess || (numWritten != _transmitBuffer.count())) {
148
                linkErrorCount++;
149
                qDebug() << "TX Error! written:" << txSuccess << "wrote" << numWritten << ", asked for " << _transmitBuffer.count() << "bytes";
150 151
            }
            else {
152 153

                // Since we were successful, reset out error counter.
154 155
                linkErrorCount = 0;
            }
156 157

            // Now that we transmit all of the data in the transmit buffer, flush it.
158 159
            _transmitBuffer = _transmitBuffer.remove(0, numWritten);
            _writeMutex.unlock();
160 161 162

            // 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.
163 164
            QMutexLocker dataRateLocker(&dataRateMutex);
            logDataRateToBuffer(outDataWriteAmounts, outDataWriteTimes, &outDataIndex, numWritten, QDateTime::currentMSecsSinceEpoch());
165 166
        }

167 168
        //wait n msecs for data to be ready
        //[TODO][BB] lower to SerialLink::poll_interval?
169 170
        _dataMutex.lock();
        bool success = _port->waitForReadyRead(20);
171

172
        if (success) {
173 174 175 176
            QByteArray readData = _port->readAll();
            while (_port->waitForReadyRead(10))
                readData += _port->readAll();
            _dataMutex.unlock();
177 178 179
            if (readData.length() > 0) {
                emit bytesReceived(this, readData);

180
                // Log this data reception for this timestep
181 182
                QMutexLocker dataRateLocker(&dataRateMutex);
                logDataRateToBuffer(inDataWriteAmounts, inDataWriteTimes, &inDataIndex, readData.length(), QDateTime::currentMSecsSinceEpoch());
183 184

                // Track the total amount of data read.
185
                _bytesRead += readData.length();
186
                linkErrorCount = 0;
187
            }
188 189
        }
        else {
190
            _dataMutex.unlock();
191
            linkErrorCount++;
192
        }
193

194 195
        if (bytes != _bytesRead) { // i.e things are good and data is being read.
            bytes = _bytesRead;
196 197
            msecs = QDateTime::currentMSecsSinceEpoch();
        }
198 199
        else {
            if (QDateTime::currentMSecsSinceEpoch() - msecs > timeout) {
200 201
                //It's been 10 seconds since the last data came in. Reset and try again
                msecs = QDateTime::currentMSecsSinceEpoch();
202
                if (msecs - initialmsecs > 25000) {
203 204 205 206 207 208 209
                    //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;
                }
210 211
            }
        }
212
        QGC::SLEEP::msleep(SerialLink::poll_interval);
213
    } // end of forever
214

215 216 217 218 219
    if (_port) {
        qDebug() << "Closing Port #" << __LINE__ << _port->portName();
        _port->close();
        delete _port;
        _port = NULL;
220
    }
pixhawk's avatar
pixhawk committed
221 222
}

223 224
void SerialLink::writeBytes(const char* data, qint64 size)
{
225
    if(_port && _port->isOpen()) {
226
        QByteArray byteArray(data, size);
227 228 229
        _writeMutex.lock();
        _transmitBuffer.append(byteArray);
        _writeMutex.unlock();
230 231
    } else {
        // Error occured
232
        _emitLinkError(tr("Could not send data - link %1 is disconnected!").arg(getName()));
pixhawk's avatar
pixhawk committed
233 234 235 236 237 238 239 240 241
    }
}

/**
 * @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
 **/
242 243
void SerialLink::readBytes()
{
244
    if(_port && _port->isOpen()) {
245 246
        const qint64 maxLength = 2048;
        char data[maxLength];
247 248
        _dataMutex.lock();
        qint64 numBytes = _port->bytesAvailable();
249

250
        if(numBytes > 0) {
pixhawk's avatar
pixhawk committed
251 252 253
            /* Read as much data in buffer as possible without overflow */
            if(maxLength < numBytes) numBytes = maxLength;

254
            _port->read(data, numBytes);
pixhawk's avatar
pixhawk committed
255 256 257
            QByteArray b(data, numBytes);
            emit bytesReceived(this, b);
        }
258
        _dataMutex.unlock();
pixhawk's avatar
pixhawk committed
259 260 261 262 263 264 265 266
    }
}

/**
 * @brief Disconnect the connection.
 *
 * @return True if connection has been disconnected, false if connection couldn't be disconnected.
 **/
267
bool SerialLink::_disconnect(void)
268
{
Bill Bonney's avatar
Bill Bonney committed
269
    if (isRunning())
270 271
    {
        {
272 273
            QMutexLocker locker(&_stoppMutex);
            _stopp = true;
274
        }
Bill Bonney's avatar
Bill Bonney committed
275
        wait(); // This will terminate the thread and close the serial port
276 277
        return true;
    }
278 279
    _transmitBuffer.clear(); //clear the output buffer to avoid sending garbage at next connect
    qDebug() << "Already disconnected";
280
    return true;
pixhawk's avatar
pixhawk committed
281 282 283 284 285 286 287
}

/**
 * @brief Connect the connection.
 *
 * @return True if connection has been established, false if connection couldn't be established.
 **/
288
bool SerialLink::_connect(void)
289
{
290
    qDebug() << "CONNECT CALLED";
Bill Bonney's avatar
Bill Bonney committed
291
    if (isRunning())
292
        _disconnect();
293
    {
294 295
        QMutexLocker locker(&this->_stoppMutex);
        _stopp = false;
296
    }
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) {
Bill Bonney's avatar
Bill Bonney committed
312
        qDebug() << "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
    qDebug() << "SerialLink: hardwareConnect to " << _config->portName();
320

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

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

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

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

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

356 357 358 359 360 361 362 363 364
    // After the bootloader times out, it still can take a second or so for the USB driver to come up and make
    // 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)) {
            qDebug() << "Port open failed, retrying";
            QGC::SLEEP::msleep(500);
        } else {
            break;
        }
365
    }
366 367 368 369
    if (!_port->isOpen() ) {
        emit communicationUpdate(getName(),"Error opening port: " + _port->errorString());
        _port->close();
        return false; // couldn't open serial port
370 371
    }

372 373 374 375 376 377
    qDebug() << "Configuring port";
    _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()));
378

379
    emit communicationUpdate(getName(), "Opened port!");
Bill Bonney's avatar
Bill Bonney committed
380
    emit connected();
381

382 383
    qDebug() << "CONNECTING LINK: " << __FILE__ << __LINE__ << "type:" << type << "with settings" << _config->portName()
             << _config->baud() << _config->dataBits() << _config->parity() << _config->stopBits();
384

Bill Bonney's avatar
Bill Bonney committed
385
    return true; // successful connection
pixhawk's avatar
pixhawk committed
386
}
387

388
void SerialLink::linkError(QSerialPort::SerialPortError error)
389
{
390
    if (error != QSerialPort::NoError) {
391 392 393 394 395
        // 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.
        //qDebug() << "SerialLink::linkError" << error;
396
    }
397 398
}

pixhawk's avatar
pixhawk committed
399 400 401 402 403
/**
 * @brief Check if connection is active.
 *
 * @return True if link is connected, false otherwise.
 **/
404
bool SerialLink::isConnected() const
405
{
Bill Bonney's avatar
Bill Bonney committed
406

407 408
    if (_port) {
        bool isConnected = _port->isOpen();
409 410
//        qDebug() << "SerialLink #" << __LINE__ << ":"<<  m_port->portName()
//                 << " isConnected =" << QString::number(isConnected);
Bill Bonney's avatar
Bill Bonney committed
411
        return isConnected;
412
    } else {
413 414
//        qDebug() << "SerialLink #" << __LINE__ << ":" <<  m_portName
//                 << " isConnected = NULL";
lm's avatar
lm committed
415 416
        return false;
    }
pixhawk's avatar
pixhawk committed
417 418
}

419
int SerialLink::getId() const
pixhawk's avatar
pixhawk committed
420
{
421
    return _id;
pixhawk's avatar
pixhawk committed
422 423
}

424
QString SerialLink::getName() const
pixhawk's avatar
pixhawk committed
425
{
426
    return _config->portName();
pixhawk's avatar
pixhawk committed
427 428
}

429 430 431 432
/**
  * This function maps baud rate constants to numerical equivalents.
  * It relies on the mapping given in qportsettings.h from the QSerialPort library.
  */
433
qint64 SerialLink::getConnectionSpeed() const
434
{
Bill Bonney's avatar
Bill Bonney committed
435
    int baudRate;
436 437
    if (_port) {
        baudRate = _port->baudRate();
Bill Bonney's avatar
Bill Bonney committed
438
    } else {
439
        baudRate = _config->baud();
Bill Bonney's avatar
Bill Bonney committed
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 468 469 470 471
    }
    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
472 473 474 475
    }
    return dataRate;
}

476
void SerialLink::_resetConfiguration()
477
{
478 479 480 481 482 483 484 485 486 487 488 489
    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) {
        qDebug() << "Reconfiguring port";
        emit updateLink(this);
    }
pixhawk's avatar
pixhawk committed
490 491
}

492
void SerialLink::_rerouteDisconnected(void)
493
{
494
    emit disconnected();
pixhawk's avatar
pixhawk committed
495 496
}

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

503
LinkConfiguration* SerialLink::getLinkConfiguration()
504
{
505
    return _config;
pixhawk's avatar
pixhawk committed
506 507
}

508 509
//--------------------------------------------------------------------------
//-- SerialConfiguration
pixhawk's avatar
pixhawk committed
510

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

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

530
void SerialConfiguration::copyFrom(LinkConfiguration *source)
531
{
532 533 534 535 536 537 538 539 540
    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();
541 542
}

543
void SerialConfiguration::updateSettings()
544
{
545 546 547 548 549
    if(_link) {
        SerialLink* serialLink = dynamic_cast<SerialLink*>(_link);
        if(serialLink) {
            serialLink->_resetConfiguration();
        }
550
    }
551 552
}

553
void SerialConfiguration::setBaud(int baud)
pixhawk's avatar
pixhawk committed
554
{
555
    _baud = baud;
pixhawk's avatar
pixhawk committed
556 557
}

558
void SerialConfiguration::setDataBits(int databits)
pixhawk's avatar
pixhawk committed
559
{
560
    _dataBits = databits;
pixhawk's avatar
pixhawk committed
561 562
}

563
void SerialConfiguration::setFlowControl(int flowControl)
pixhawk's avatar
pixhawk committed
564
{
565
    _flowControl = flowControl;
pixhawk's avatar
pixhawk committed
566 567
}

568
void SerialConfiguration::setStopBits(int stopBits)
569
{
570
    _stopBits = stopBits;
pixhawk's avatar
pixhawk committed
571 572
}

573
void SerialConfiguration::setParity(int parity)
574
{
575
    _parity = parity;
pixhawk's avatar
pixhawk committed
576 577
}

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

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

599
void SerialConfiguration::loadSettings(QSettings& settings, const QString& root)
600
{
601 602 603 604 605 606 607 608
    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();
609
}
610

611
QList<QString> SerialConfiguration::getCurrentPorts()
612
{
613 614 615 616 617 618 619
    QList<QString> ports;
    QList<QSerialPortInfo> portList =  QSerialPortInfo::availablePorts();
    foreach (const QSerialPortInfo &info, portList)
    {
        ports.append(info.portName());
    }
    return ports;
620
}