SerialLink.cc 17.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>
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
SerialLink::SerialLink(SharedLinkConfigurationPointer& config)
    : LinkInterface(config)
    , _port(NULL)
    , _bytesRead(0)
    , _stopp(false)
    , _reqReset(false)
    , _serialConfig(qobject_cast<SerialConfiguration*>(config.data()))
40
{
DonLakeFlyer's avatar
DonLakeFlyer committed
41 42 43 44
    if (!_serialConfig) {
        qWarning() << "Internal error";
        return;
    }
45 46

    qCDebug(SerialLinkLog) << "Create SerialLink " << _serialConfig->portName() << _serialConfig->baud() << _serialConfig->flowControl()
DonLakeFlyer's avatar
DonLakeFlyer committed
47
                           << _serialConfig->parity() << _serialConfig->dataBits() << _serialConfig->stopBits();
48
    qCDebug(SerialLinkLog) << "portName: " << _serialConfig->portName();
pixhawk's avatar
pixhawk committed
49
}
50

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

SerialLink::~SerialLink()
{
59
    _disconnect();
60 61 62
    if (_port) {
        delete _port;
    }
63
    _port = NULL;
64 65
}

66
bool SerialLink::_isBootloader()
67
{
68
    QList<QSerialPortInfo> portList = QSerialPortInfo::availablePorts();
69 70 71 72 73
    if( portList.count() == 0){
        return false;
    }
    foreach (const QSerialPortInfo &info, portList)
    {
74 75
        qCDebug(SerialLinkLog) << "PortName    : " << info.portName() << "Description : " << info.description();
        qCDebug(SerialLinkLog) << "Manufacturer: " << info.manufacturer();
76
        if (info.portName().trimmed() == _serialConfig->portName() &&
77 78 79 80 81
                (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;
DonLakeFlyer's avatar
DonLakeFlyer committed
82
        }
83 84 85 86 87
    }
    // Not found
    return false;
}

88
void SerialLink::_writeBytes(const QByteArray data)
89
{
90
    if(_port && _port->isOpen()) {
91 92
        _logOutputDataRate(data.size(), QDateTime::currentMSecsSinceEpoch());
        _port->write(data);
93
    } else {
Ricardo de Almeida Gonzaga's avatar
Ricardo de Almeida Gonzaga committed
94
        // Error occurred
95
        _emitLinkError(tr("Could not send data - link %1 is disconnected!").arg(getName()));
pixhawk's avatar
pixhawk committed
96 97 98 99 100 101 102 103
    }
}

/**
 * @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
104
void SerialLink::_disconnect(void)
105
{
106 107 108 109
    if (_port) {
        _port->close();
        delete _port;
        _port = NULL;
110
    }
111

dogmaphobic's avatar
dogmaphobic committed
112
#ifdef __android__
113
    qgcApp()->toolbox()->linkManager()->suspendConfigurationUpdates(false);
dogmaphobic's avatar
dogmaphobic committed
114
#endif
pixhawk's avatar
pixhawk committed
115 116 117 118 119 120 121
}

/**
 * @brief Connect the connection.
 *
 * @return True if connection has been established, false if connection couldn't be established.
 **/
122
bool SerialLink::_connect(void)
123
{
124
    qCDebug(SerialLinkLog) << "CONNECT CALLED";
125 126

    _disconnect();
127

dogmaphobic's avatar
dogmaphobic committed
128
#ifdef __android__
129
    qgcApp()->toolbox()->linkManager()->suspendConfigurationUpdates(true);
130
#endif
131

132 133 134
    QSerialPort::SerialPortError    error;
    QString                         errorString;

dogmaphobic's avatar
dogmaphobic committed
135
    // Initialize the connection
136 137 138 139 140 141 142
    if (!_hardwareConnect(error, errorString)) {
        if (qgcApp()->toolbox()->linkManager()->isAutoconnectLink(this)) {
            // Be careful with spitting out open error related to trying to open a busy port using autoconnect
            if (error == QSerialPort::PermissionError) {
                // Device already open, ignore and fail connect
                return false;
            }
dogmaphobic's avatar
dogmaphobic committed
143
        }
144

145
        _emitLinkError(tr("Error connecting: Could not create port. %1").arg(errorString));
dogmaphobic's avatar
dogmaphobic committed
146 147
        return false;
    }
148
    return true;
pixhawk's avatar
pixhawk committed
149 150
}

151 152 153 154 155
/// Performs the actual hardware port connection.
///     @param[out] error if failed
///     @param[out] error string if failed
/// @return success/fail
bool SerialLink::_hardwareConnect(QSerialPort::SerialPortError& error, QString& errorString)
156
{
157
    if (_port) {
158
        qCDebug(SerialLinkLog) << "SerialLink:" << QString::number((long)this, 16) << "closing port";
159
        _port->close();
160 161 162 163 164 165

        // Wait 50 ms while continuing to run the event queue
        for (unsigned i = 0; i < 10; i++) {
            QGC::SLEEP::usleep(5000);
            qgcApp()->processEvents(QEventLoop::ExcludeUserInputEvents);
        }
166 167
        delete _port;
        _port = NULL;
168
    }
pixhawk's avatar
pixhawk committed
169

170
    qCDebug(SerialLinkLog) << "SerialLink: hardwareConnect to " << _serialConfig->portName();
171

172
    // If we are in the Pixhawk bootloader code wait for it to timeout
173
    if (_isBootloader()) {
174
        qCDebug(SerialLinkLog) << "Not connecting to a bootloader, waiting for 2nd chance";
175 176
        const unsigned retry_limit = 12;
        unsigned retries;
177

178
        for (retries = 0; retries < retry_limit; retries++) {
179
            if (!_isBootloader()) {
180 181 182 183 184
                // Wait 500 ms while continuing to run the event loop
                for (unsigned i = 0; i < 100; i++) {
                    QGC::SLEEP::msleep(5);
                    qgcApp()->processEvents(QEventLoop::ExcludeUserInputEvents);
                }
185 186
                break;
            }
187 188 189 190 191 192

            // Wait 500 ms while continuing to run the event loop
            for (unsigned i = 0; i < 100; i++) {
                QGC::SLEEP::msleep(5);
                qgcApp()->processEvents(QEventLoop::ExcludeUserInputEvents);
            }
193 194 195 196
        }
        // Check limit
        if (retries == retry_limit) {
            // bail out
197
            qWarning() << "Timeout waiting for something other than booloader";
198 199 200 201
            return false;
        }
    }

202
    _port = new QSerialPort(_serialConfig->portName());
Bill Bonney's avatar
Bill Bonney committed
203

204 205
    QObject::connect(_port, static_cast<void (QSerialPort::*)(QSerialPort::SerialPortError)>(&QSerialPort::error),
                     this, &SerialLink::linkError);
206
    QObject::connect(_port, &QIODevice::readyRead, this, &SerialLink::_readBytes);
Bill Bonney's avatar
Bill Bonney committed
207

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

210
    // TODO This needs a bit of TLC still...
211

212
    // After the bootloader times out, it still can take a second or so for the Pixhawk USB driver to come up and make
213
    // the port available for open. So we retry a few times to wait for it.
dogmaphobic's avatar
dogmaphobic committed
214 215 216
#ifdef __android__
    _port->open(QIODevice::ReadWrite);
#else
217 218 219

    // Try to open the port three times
    for (int openRetries = 0; openRetries < 3; openRetries++) {
220
        if (!_port->open(QIODevice::ReadWrite)) {
221
            qCDebug(SerialLinkLog) << "Port open failed, retrying";
222 223 224 225 226 227
            // Wait 250 ms while continuing to run the event loop
            for (unsigned i = 0; i < 50; i++) {
                QGC::SLEEP::msleep(5);
                qgcApp()->processEvents(QEventLoop::ExcludeUserInputEvents);
            }
            qgcApp()->processEvents(QEventLoop::ExcludeUserInputEvents);
228 229 230
        } else {
            break;
        }
231
    }
dogmaphobic's avatar
dogmaphobic committed
232
#endif
233
    if (!_port->isOpen() ) {
234 235 236
        qDebug() << "open failed" << _port->errorString() << _port->error() << getName() << qgcApp()->toolbox()->linkManager()->isAutoconnectLink(this);
        error = _port->error();
        errorString = _port->errorString();
237
        emit communicationUpdate(getName(), tr("Error opening port: %1").arg(_port->errorString()));
238
        _port->close();
239 240
        delete _port;
        _port = NULL;
241
        return false; // couldn't open serial port
242 243
    }

244 245
    _port->setDataTerminalReady(true);

246
    qCDebug(SerialLinkLog) << "Configuring port";
247 248 249 250 251
    _port->setBaudRate     (_serialConfig->baud());
    _port->setDataBits     (static_cast<QSerialPort::DataBits>     (_serialConfig->dataBits()));
    _port->setFlowControl  (static_cast<QSerialPort::FlowControl>  (_serialConfig->flowControl()));
    _port->setStopBits     (static_cast<QSerialPort::StopBits>     (_serialConfig->stopBits()));
    _port->setParity       (static_cast<QSerialPort::Parity>       (_serialConfig->parity()));
252

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

256
    qCDebug(SerialLinkLog) << "Connection SeriaLink: " << "with settings" << _serialConfig->portName()
DonLakeFlyer's avatar
DonLakeFlyer committed
257
                           << _serialConfig->baud() << _serialConfig->dataBits() << _serialConfig->parity() << _serialConfig->stopBits();
258

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

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

273
void SerialLink::linkError(QSerialPort::SerialPortError error)
274
{
275 276 277 278 279 280 281
    switch (error) {
    case QSerialPort::NoError:
        break;
    case QSerialPort::ResourceError:
        emit connectionRemoved(this);
        break;
    default:
282 283 284 285
        // 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.
286
        //qCDebug(SerialLinkLog) << "SerialLink::linkError" << error;
287
        break;
288
    }
289 290
}

pixhawk's avatar
pixhawk committed
291 292 293 294 295
/**
 * @brief Check if connection is active.
 *
 * @return True if link is connected, false otherwise.
 **/
296
bool SerialLink::isConnected() const
297
{
298
    bool isConnected = false;
Bill Bonney's avatar
Bill Bonney committed
299

300
    if (_port) {
301
        isConnected = _port->isOpen();
lm's avatar
lm committed
302
    }
303

304
    return isConnected;
pixhawk's avatar
pixhawk committed
305 306
}

307
QString SerialLink::getName() const
pixhawk's avatar
pixhawk committed
308
{
309
    return _serialConfig->portName();
pixhawk's avatar
pixhawk committed
310 311
}

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

359
void SerialLink::_resetConfiguration()
360
{
361
    if (_port) {
362 363 364 365 366
        _port->setBaudRate      (_serialConfig->baud());
        _port->setDataBits      (static_cast<QSerialPort::DataBits>    (_serialConfig->dataBits()));
        _port->setFlowControl   (static_cast<QSerialPort::FlowControl> (_serialConfig->flowControl()));
        _port->setStopBits      (static_cast<QSerialPort::StopBits>    (_serialConfig->stopBits()));
        _port->setParity        (static_cast<QSerialPort::Parity>      (_serialConfig->parity()));
367
    }
pixhawk's avatar
pixhawk committed
368 369
}

370
void SerialLink::_emitLinkError(const QString& errorMsg)
371
{
372
    QString msg("Error on link %1. %2");
373
    qDebug() << errorMsg;
374
    emit communicationError(tr("Link Error"), msg.arg(getName()).arg(errorMsg));
pixhawk's avatar
pixhawk committed
375 376
}

377 378
//--------------------------------------------------------------------------
//-- SerialConfiguration
pixhawk's avatar
pixhawk committed
379

380
SerialConfiguration::SerialConfiguration(const QString& name) : LinkConfiguration(name)
381
{
382 383 384 385 386
    _baud       = 57600;
    _flowControl= QSerialPort::NoFlowControl;
    _parity     = QSerialPort::NoParity;
    _dataBits   = 8;
    _stopBits   = 1;
387
    _usbDirect  = false;
pixhawk's avatar
pixhawk committed
388 389
}

390
SerialConfiguration::SerialConfiguration(SerialConfiguration* copy) : LinkConfiguration(copy)
391
{
392 393 394 395 396 397 398
    _baud               = copy->baud();
    _flowControl        = copy->flowControl();
    _parity             = copy->parity();
    _dataBits           = copy->dataBits();
    _stopBits           = copy->stopBits();
    _portName           = copy->portName();
    _portDisplayName    = copy->portDisplayName();
399
    _usbDirect          = copy->_usbDirect;
pixhawk's avatar
pixhawk committed
400 401
}

402
void SerialConfiguration::copyFrom(LinkConfiguration *source)
403
{
404 405
    LinkConfiguration::copyFrom(source);
    SerialConfiguration* ssource = dynamic_cast<SerialConfiguration*>(source);
DonLakeFlyer's avatar
DonLakeFlyer committed
406 407 408 409 410 411 412 413 414 415 416 417
    if (ssource) {
        _baud               = ssource->baud();
        _flowControl        = ssource->flowControl();
        _parity             = ssource->parity();
        _dataBits           = ssource->dataBits();
        _stopBits           = ssource->stopBits();
        _portName           = ssource->portName();
        _portDisplayName    = ssource->portDisplayName();
        _usbDirect          = ssource->_usbDirect;
    } else {
        qWarning() << "Internal error";
    }
418 419
}

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

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

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

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

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

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

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

465 466 467
QString SerialConfiguration::cleanPortDisplayname(const QString name)
{
    QString pname = name.trimmed();
468
#ifdef Q_OS_WIN
469 470 471 472 473 474 475 476
    pname.replace("\\\\.\\", "");
#else
    pname.replace("/dev/cu.", "");
    pname.replace("/dev/", "");
#endif
    return pname;
}

477
void SerialConfiguration::saveSettings(QSettings& settings, const QString& root)
478
{
479
    settings.beginGroup(root);
480 481 482 483 484 485 486
    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.setValue("portDisplayName",_portDisplayName);
487
    settings.endGroup();
488
}
489

490
void SerialConfiguration::loadSettings(QSettings& settings, const QString& root)
491
{
492
    settings.beginGroup(root);
493 494 495 496 497 498 499
    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();
    if(settings.contains("portDisplayName"))_portDisplayName= settings.value("portDisplayName").toString();
500
    settings.endGroup();
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 543 544 545 546 547 548 549 550 551 552

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";
    kSupportedBaudRates << "500000";
553
#if defined(Q_OS_LINUX)
554 555 556 557 558
    kSupportedBaudRates << "576000";
#endif
    kSupportedBaudRates << "921600";
}

559 560 561 562 563 564 565
void SerialConfiguration::setUsbDirect(bool usbDirect)
{
    if (_usbDirect != usbDirect) {
        _usbDirect = usbDirect;
        emit usbDirectChanged(_usbDirect);
    }
}