SerialLink.cc 14.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"
Don Gagne's avatar
Don Gagne committed
28
#include "QGCApplication.h"
29

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

32 33 34 35 36 37 38 39 40
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
41

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

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

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

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

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

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

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

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

/**
 * @brief Disconnect the connection.
 *
 * @return True if connection has been disconnected, false if connection couldn't be disconnected.
 **/
128
bool SerialLink::_disconnect(void)
129
{
130 131 132 133
    if (_port) {
        _port->close();
        delete _port;
        _port = NULL;
134
    }
135
    
dogmaphobic's avatar
dogmaphobic committed
136
#ifdef __android__
137
    qgcApp()->toolbox()->linkManager()->suspendConfigurationUpdates(false);
dogmaphobic's avatar
dogmaphobic committed
138
#endif
139
    return true;
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 152

    _disconnect();
    
dogmaphobic's avatar
dogmaphobic committed
153
#ifdef __android__
154
    qgcApp()->toolbox()->linkManager()->suspendConfigurationUpdates(true);
155 156
#endif
    
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 217
    // 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
218 219
    QObject::connect(_port, SIGNAL(aboutToClose()), this, SLOT(_rerouteDisconnected()));
    QObject::connect(_port, SIGNAL(error(QSerialPort::SerialPortError)), this, SLOT(linkError(QSerialPort::SerialPortError)));
220
    QObject::connect(_port, &QIODevice::readyRead, this, &SerialLink::_readBytes);
Bill Bonney's avatar
Bill Bonney committed
221

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

224
    // TODO This needs a bit of TLC still...
225

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

248
    qCDebug(SerialLinkLog) << "Configuring port";
249 250 251 252 253
    _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()));
254

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

258
    qCDebug(SerialLinkLog) << "CONNECTING LINK: " << __FILE__ << __LINE__ << "type:" << type << "with settings" << _config->portName()
259
             << _config->baud() << _config->dataBits() << _config->parity() << _config->stopBits();
260

Bill Bonney's avatar
Bill Bonney committed
261
    return true; // successful connection
pixhawk's avatar
pixhawk committed
262
}
263

264 265 266 267 268 269 270 271 272 273 274 275
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);
    }
}

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

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

296
    if (_port) {
297
        isConnected = _port->isOpen();
lm's avatar
lm committed
298
    }
299 300
    
    return isConnected;
pixhawk's avatar
pixhawk committed
301 302
}

303
QString SerialLink::getName() const
pixhawk's avatar
pixhawk committed
304
{
305
    return _config->portName();
pixhawk's avatar
pixhawk committed
306 307
}

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

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

366
void SerialLink::_rerouteDisconnected(void)
367
{
368
    emit disconnected();
pixhawk's avatar
pixhawk committed
369 370
}

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

378
LinkConfiguration* SerialLink::getLinkConfiguration()
379
{
380
    return _config;
pixhawk's avatar
pixhawk committed
381 382
}

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

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

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

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

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

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

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

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

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

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

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

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

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