SerialLink.cc 15.6 KB
Newer Older
pixhawk's avatar
pixhawk committed
1 2 3 4 5 6 7 8 9 10 11 12
/*=====================================================================
======================================================================*/
/**
 * @file
 *   @brief Cross-platform support for serial ports
 *
 *   @author Lorenz Meier <mavteam@student.ethz.ch>
 *
 */

#include <QTimer>
#include <QDebug>
13
#include <QSettings>
pixhawk's avatar
pixhawk committed
14
#include <QMutexLocker>
dogmaphobic's avatar
dogmaphobic committed
15 16 17 18

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

pixhawk's avatar
pixhawk committed
22
#include "SerialLink.h"
23
#include "QGC.h"
24
#include "MG.h"
25
#include "QGCLoggingCategory.h"
Don Gagne's avatar
Don Gagne committed
26
#include "QGCApplication.h"
27
#include "QGCSerialPortInfo.h"
28

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

31 32
static QStringList kSupportedBaudRates;

33 34 35 36 37 38 39 40 41
SerialLink::SerialLink(SerialConfiguration* config)
{
    _bytesRead = 0;
    _port     = Q_NULLPTR;
    _stopp    = false;
    _reqReset = false;
    Q_ASSERT(config != NULL);
    _config = config;
    _config->setLink(this);
Bill Bonney's avatar
Bill Bonney committed
42

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

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

SerialLink::~SerialLink()
{
56 57
    // Disconnect link from configuration
    _config->setLink(NULL);
58
    _disconnect();
59 60
    if(_port) delete _port;
    _port = NULL;
61 62
}

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

85 86
void SerialLink::writeBytes(const char* data, qint64 size)
{
87
    if(_port && _port->isOpen()) {
88 89
        _logOutputDataRate(size, QDateTime::currentMSecsSinceEpoch());
        _port->write(data, size);
90 91
    } else {
        // Error occured
92
        _emitLinkError(tr("Could not send data - link %1 is disconnected!").arg(getName()));
pixhawk's avatar
pixhawk committed
93 94 95 96 97 98 99 100 101
    }
}

/**
 * @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
 **/
102 103
void SerialLink::readBytes()
{
104
    if(_port && _port->isOpen()) {
105 106
        const qint64 maxLength = 2048;
        char data[maxLength];
107 108
        _dataMutex.lock();
        qint64 numBytes = _port->bytesAvailable();
109

110
        if (numBytes > 0) {
pixhawk's avatar
pixhawk committed
111 112 113
            /* Read as much data in buffer as possible without overflow */
            if(maxLength < numBytes) numBytes = maxLength;

114
            _logInputDataRate(numBytes, QDateTime::currentMSecsSinceEpoch());
115

116
            _port->read(data, numBytes);
pixhawk's avatar
pixhawk committed
117 118 119
            QByteArray b(data, numBytes);
            emit bytesReceived(this, b);
        }
120
        _dataMutex.unlock();
pixhawk's avatar
pixhawk committed
121 122 123 124 125 126 127 128
    }
}

/**
 * @brief Disconnect the connection.
 *
 * @return True if connection has been disconnected, false if connection couldn't be disconnected.
 **/
Don Gagne's avatar
Don Gagne committed
129
void SerialLink::_disconnect(void)
130
{
131 132 133 134
    if (_port) {
        _port->close();
        delete _port;
        _port = NULL;
135
    }
136

dogmaphobic's avatar
dogmaphobic committed
137
#ifdef __android__
138
    qgcApp()->toolbox()->linkManager()->suspendConfigurationUpdates(false);
dogmaphobic's avatar
dogmaphobic committed
139
#endif
pixhawk's avatar
pixhawk committed
140 141 142 143 144 145 146
}

/**
 * @brief Connect the connection.
 *
 * @return True if connection has been established, false if connection couldn't be established.
 **/
147
bool SerialLink::_connect(void)
148
{
149
    qCDebug(SerialLinkLog) << "CONNECT CALLED";
150 151

    _disconnect();
152

dogmaphobic's avatar
dogmaphobic committed
153
#ifdef __android__
154
    qgcApp()->toolbox()->linkManager()->suspendConfigurationUpdates(true);
155
#endif
156

dogmaphobic's avatar
dogmaphobic committed
157 158 159 160 161 162 163 164 165 166
    // 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;
    }
167
    return true;
pixhawk's avatar
pixhawk committed
168 169 170
}

/**
171
 * @brief This function is called indirectly by the _connect() call.
pixhawk's avatar
pixhawk committed
172
 *
173
 * The _connect() function starts the thread and indirectly calls this method.
pixhawk's avatar
pixhawk committed
174 175
 *
 * @return True if the connection could be established, false otherwise
176
 * @see _connect() For the right function to establish the connection.
pixhawk's avatar
pixhawk committed
177
 **/
178
bool SerialLink::_hardwareConnect(QString &type)
179
{
180
    if (_port) {
181
        qCDebug(SerialLinkLog) << "SerialLink:" << QString::number((long)this, 16) << "closing port";
182
        _port->close();
183
        QGC::SLEEP::usleep(50000);
184 185
        delete _port;
        _port = NULL;
186
    }
pixhawk's avatar
pixhawk committed
187

188
    qCDebug(SerialLinkLog) << "SerialLink: hardwareConnect to " << _config->portName();
189

190
    // If we are in the Pixhawk bootloader code wait for it to timeout
191
    if (_isBootloader()) {
192
        qCDebug(SerialLinkLog) << "Not connecting to a bootloader, waiting for 2nd chance";
193 194 195
        const unsigned retry_limit = 12;
        unsigned retries;
        for (retries = 0; retries < retry_limit; retries++) {
196 197
            if (!_isBootloader()) {
                QGC::SLEEP::msleep(500);
198 199 200 201 202 203 204
                break;
            }
            QGC::SLEEP::msleep(500);
        }
        // Check limit
        if (retries == retry_limit) {
            // bail out
205
            qWarning() << "Timeout waiting for something other than booloader";
206 207 208 209
            return false;
        }
    }

210 211 212
    _port = new QSerialPort(_config->portName());
    if (!_port) {
        emit communicationUpdate(getName(),"Error opening port: " + _config->portName());
Bill Bonney's avatar
Bill Bonney committed
213
        return false; // couldn't create serial port.
214
    }
Bill Bonney's avatar
Bill Bonney committed
215

216
    QObject::connect(_port, SIGNAL(error(QSerialPort::SerialPortError)), this, SLOT(linkError(QSerialPort::SerialPortError)));
217
    QObject::connect(_port, &QIODevice::readyRead, this, &SerialLink::_readBytes);
Bill Bonney's avatar
Bill Bonney committed
218

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

221
    // TODO This needs a bit of TLC still...
222

223
    // After the bootloader times out, it still can take a second or so for the Pixhawk USB driver to come up and make
224
    // the port available for open. So we retry a few times to wait for it.
dogmaphobic's avatar
dogmaphobic committed
225 226 227
#ifdef __android__
    _port->open(QIODevice::ReadWrite);
#else
228 229
    for (int openRetries = 0; openRetries < 4; openRetries++) {
        if (!_port->open(QIODevice::ReadWrite)) {
230
            qCDebug(SerialLinkLog) << "Port open failed, retrying";
231 232 233 234
            QGC::SLEEP::msleep(500);
        } else {
            break;
        }
235
    }
dogmaphobic's avatar
dogmaphobic committed
236
#endif
237 238 239
    if (!_port->isOpen() ) {
        emit communicationUpdate(getName(),"Error opening port: " + _port->errorString());
        _port->close();
240 241
        delete _port;
        _port = NULL;
242
        return false; // couldn't open serial port
243 244
    }

245
    qCDebug(SerialLinkLog) << "Configuring port";
246 247 248 249 250
    _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()));
251

252
    emit communicationUpdate(getName(), "Opened port!");
Bill Bonney's avatar
Bill Bonney committed
253
    emit connected();
254

255
    qCDebug(SerialLinkLog) << "CONNECTING LINK: " << __FILE__ << __LINE__ << "type:" << type << "with settings" << _config->portName()
256
             << _config->baud() << _config->dataBits() << _config->parity() << _config->stopBits();
257

Bill Bonney's avatar
Bill Bonney committed
258
    return true; // successful connection
pixhawk's avatar
pixhawk committed
259
}
260

261 262 263 264 265 266 267 268 269 270 271 272
void SerialLink::_readBytes(void)
{
    qint64 byteCount = _port->bytesAvailable();
    if (byteCount)
    {
        QByteArray buffer;
        buffer.resize(byteCount);
        _port->read(buffer.data(), buffer.size());
        emit bytesReceived(this, buffer);
    }
}

273
void SerialLink::linkError(QSerialPort::SerialPortError error)
274
{
275
    if (error != QSerialPort::NoError) {
276 277 278 279
        // 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.
280
        //qCDebug(SerialLinkLog) << "SerialLink::linkError" << error;
281
    }
282 283
}

pixhawk's avatar
pixhawk committed
284 285 286 287 288
/**
 * @brief Check if connection is active.
 *
 * @return True if link is connected, false otherwise.
 **/
289
bool SerialLink::isConnected() const
290
{
291
    bool isConnected = false;
Bill Bonney's avatar
Bill Bonney committed
292

293
    if (_port) {
294
        isConnected = _port->isOpen();
lm's avatar
lm committed
295
    }
296

297
    return isConnected;
pixhawk's avatar
pixhawk committed
298 299
}

300
QString SerialLink::getName() const
pixhawk's avatar
pixhawk committed
301
{
302
    return _config->portName();
pixhawk's avatar
pixhawk committed
303 304
}

305 306 307 308
/**
  * This function maps baud rate constants to numerical equivalents.
  * It relies on the mapping given in qportsettings.h from the QSerialPort library.
  */
309
qint64 SerialLink::getConnectionSpeed() const
310
{
Bill Bonney's avatar
Bill Bonney committed
311
    int baudRate;
312 313
    if (_port) {
        baudRate = _port->baudRate();
Bill Bonney's avatar
Bill Bonney committed
314
    } else {
315
        baudRate = _config->baud();
Bill Bonney's avatar
Bill Bonney committed
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347
    }
    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
348 349 350 351
    }
    return dataRate;
}

352
void SerialLink::_resetConfiguration()
353
{
354
    if (_port) {
Don Gagne's avatar
Don Gagne committed
355 356 357 358 359
        _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()));
360
    }
pixhawk's avatar
pixhawk committed
361 362
}

363
void SerialLink::_emitLinkError(const QString& errorMsg)
364
{
365
    QString msg("Error on link %1. %2");
366
    qDebug() << errorMsg;
367
    emit communicationError(tr("Link Error"), msg.arg(getName()).arg(errorMsg));
pixhawk's avatar
pixhawk committed
368 369
}

370
LinkConfiguration* SerialLink::getLinkConfiguration()
371
{
372
    return _config;
pixhawk's avatar
pixhawk committed
373 374
}

375 376 377 378 379 380 381 382 383
bool SerialLink::requiresUSBMavlinkStart(void) const
{
    if (_port) {
        return QGCSerialPortInfo(*_port).boardTypePixhawk();
    } else {
        return false;
    }
}

384 385
//--------------------------------------------------------------------------
//-- SerialConfiguration
pixhawk's avatar
pixhawk committed
386

387
SerialConfiguration::SerialConfiguration(const QString& name) : LinkConfiguration(name)
388
{
389 390 391 392 393
    _baud       = 57600;
    _flowControl= QSerialPort::NoFlowControl;
    _parity     = QSerialPort::NoParity;
    _dataBits   = 8;
    _stopBits   = 1;
pixhawk's avatar
pixhawk committed
394 395
}

396
SerialConfiguration::SerialConfiguration(SerialConfiguration* copy) : LinkConfiguration(copy)
397
{
398 399 400 401 402 403
    _baud       = copy->baud();
    _flowControl= copy->flowControl();
    _parity     = copy->parity();
    _dataBits   = copy->dataBits();
    _stopBits   = copy->stopBits();
    _portName   = copy->portName();
pixhawk's avatar
pixhawk committed
404 405
}

406
void SerialConfiguration::copyFrom(LinkConfiguration *source)
407
{
408 409 410 411 412 413 414 415 416
    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();
417 418
}

419
void SerialConfiguration::updateSettings()
420
{
421 422 423 424 425
    if(_link) {
        SerialLink* serialLink = dynamic_cast<SerialLink*>(_link);
        if(serialLink) {
            serialLink->_resetConfiguration();
        }
426
    }
427 428
}

429
void SerialConfiguration::setBaud(int baud)
pixhawk's avatar
pixhawk committed
430
{
431
    _baud = baud;
pixhawk's avatar
pixhawk committed
432 433
}

434
void SerialConfiguration::setDataBits(int databits)
pixhawk's avatar
pixhawk committed
435
{
436
    _dataBits = databits;
pixhawk's avatar
pixhawk committed
437 438
}

439
void SerialConfiguration::setFlowControl(int flowControl)
pixhawk's avatar
pixhawk committed
440
{
441
    _flowControl = flowControl;
pixhawk's avatar
pixhawk committed
442 443
}

444
void SerialConfiguration::setStopBits(int stopBits)
445
{
446
    _stopBits = stopBits;
pixhawk's avatar
pixhawk committed
447 448
}

449
void SerialConfiguration::setParity(int parity)
450
{
451
    _parity = parity;
pixhawk's avatar
pixhawk committed
452 453
}

454
void SerialConfiguration::setPortName(const QString& portName)
455
{
456 457 458 459
    // No effect on a running connection
    QString pname = portName.trimmed();
    if (!pname.isEmpty() && pname != _portName) {
        _portName = pname;
460 461 462
    }
}

463
void SerialConfiguration::saveSettings(QSettings& settings, const QString& root)
464
{
465 466 467 468 469 470 471 472
    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();
473
}
474

475
void SerialConfiguration::loadSettings(QSettings& settings, const QString& root)
476
{
477 478 479 480 481 482 483 484
    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();
485
}
486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542

QStringList SerialConfiguration::supportedBaudRates()
{
    if(!kSupportedBaudRates.size())
        _initBaudRates();
    return kSupportedBaudRates;
}

void SerialConfiguration::_initBaudRates()
{
    kSupportedBaudRates.clear();
#if USE_ANCIENT_RATES
#if defined(Q_OS_UNIX) || defined(Q_OS_LINUX) || defined(Q_OS_DARWIN)
    kSupportedBaudRates << "50";
    kSupportedBaudRates << "75";
#endif
    kSupportedBaudRates << "110";
#if defined(Q_OS_UNIX) || defined(Q_OS_LINUX) || defined(Q_OS_DARWIN)
    kSupportedBaudRates << "134";
    kSupportedBaudRates << "150";
    kSupportedBaudRates << "200";
#endif
    kSupportedBaudRates << "300";
    kSupportedBaudRates << "600";
    kSupportedBaudRates << "1200";
#if defined(Q_OS_UNIX) || defined(Q_OS_LINUX) || defined(Q_OS_DARWIN)
    kSupportedBaudRates << "1800";
#endif
#endif
    kSupportedBaudRates << "2400";
    kSupportedBaudRates << "4800";
    kSupportedBaudRates << "9600";
#if defined(Q_OS_WIN)
    kSupportedBaudRates << "14400";
#endif
    kSupportedBaudRates << "19200";
    kSupportedBaudRates << "38400";
#if defined(Q_OS_WIN)
    kSupportedBaudRates << "56000";
#endif
    kSupportedBaudRates << "57600";
    kSupportedBaudRates << "115200";
#if defined(Q_OS_WIN)
    kSupportedBaudRates << "128000";
#endif
    kSupportedBaudRates << "230400";
#if defined(Q_OS_WIN)
    kSupportedBaudRates << "256000";
#endif
    kSupportedBaudRates << "460800";
#if defined(Q_OS_LINUX)
    kSupportedBaudRates << "500000";
    kSupportedBaudRates << "576000";
#endif
    kSupportedBaudRates << "921600";
}