SerialLink.cc 14.1 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 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);
Bill Bonney's avatar
Bill Bonney committed
40

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

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

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

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

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

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

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

112 113
            _logInputDataRate(numBytes, QDateTime::currentMSecsSinceEpoch());
            
114
            _port->read(data, numBytes);
pixhawk's avatar
pixhawk committed
115 116 117
            QByteArray b(data, numBytes);
            emit bytesReceived(this, b);
        }
118
        _dataMutex.unlock();
pixhawk's avatar
pixhawk committed
119 120 121 122 123 124 125 126
    }
}

/**
 * @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
127
void SerialLink::_disconnect(void)
128
{
129 130 131 132
    if (_port) {
        _port->close();
        delete _port;
        _port = NULL;
133
    }
134
    
dogmaphobic's avatar
dogmaphobic committed
135
#ifdef __android__
136
    qgcApp()->toolbox()->linkManager()->suspendConfigurationUpdates(false);
dogmaphobic's avatar
dogmaphobic committed
137
#endif
pixhawk's avatar
pixhawk committed
138 139 140 141 142 143 144
}

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

    _disconnect();
    
dogmaphobic's avatar
dogmaphobic committed
151
#ifdef __android__
152
    qgcApp()->toolbox()->linkManager()->suspendConfigurationUpdates(true);
153 154
#endif
    
dogmaphobic's avatar
dogmaphobic committed
155 156 157 158 159 160 161 162 163 164
    // 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;
    }
165
    return true;
pixhawk's avatar
pixhawk committed
166 167 168
}

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

186
    qCDebug(SerialLinkLog) << "SerialLink: hardwareConnect to " << _config->portName();
187

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

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

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

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

219
    // TODO This needs a bit of TLC still...
220

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

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

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

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

Bill Bonney's avatar
Bill Bonney committed
256
    return true; // successful connection
pixhawk's avatar
pixhawk committed
257
}
258

259 260 261 262 263 264 265 266 267 268 269 270
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);
    }
}

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

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

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

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

303 304 305 306
/**
  * This function maps baud rate constants to numerical equivalents.
  * It relies on the mapping given in qportsettings.h from the QSerialPort library.
  */
307
qint64 SerialLink::getConnectionSpeed() const
308
{
Bill Bonney's avatar
Bill Bonney committed
309
    int baudRate;
310 311
    if (_port) {
        baudRate = _port->baudRate();
Bill Bonney's avatar
Bill Bonney committed
312
    } else {
313
        baudRate = _config->baud();
Bill Bonney's avatar
Bill Bonney committed
314 315 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
    }
    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
346 347 348 349
    }
    return dataRate;
}

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

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

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

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

382 383
//--------------------------------------------------------------------------
//-- SerialConfiguration
pixhawk's avatar
pixhawk committed
384

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

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

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

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

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

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

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

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

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

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

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

473
void SerialConfiguration::loadSettings(QSettings& settings, const QString& root)
474
{
475 476 477 478 479 480 481 482
    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();
483
}