SerialLink.cc 14.6 KB
Newer Older
1
/****************************************************************************
pixhawk's avatar
pixhawk committed
2
 *
3
 * (c) 2009-2020 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
pixhawk's avatar
pixhawk committed
4
 *
5 6 7 8
 * QGroundControl is licensed according to the terms in the file
 * COPYING.md in the root of the source code directory.
 *
 ****************************************************************************/
pixhawk's avatar
pixhawk committed
9 10 11

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

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

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

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

30 31
static QStringList kSupportedBaudRates;

32
SerialLink::SerialLink(SharedLinkConfigurationPtr& config, bool isPX4Flow)
33
    : LinkInterface(config, isPX4Flow)
34
    , _serialConfig(qobject_cast<SerialConfiguration*>(config.get()))
35
{
36
    qCDebug(SerialLinkLog) << "Create SerialLink portName:baud:flowControl:parity:dataButs:stopBits" << _serialConfig->portName() << _serialConfig->baud() << _serialConfig->flowControl()
DonLakeFlyer's avatar
DonLakeFlyer committed
37
                           << _serialConfig->parity() << _serialConfig->dataBits() << _serialConfig->stopBits();
38
}
pixhawk's avatar
pixhawk committed
39 40 41

SerialLink::~SerialLink()
{
42
    disconnect();
43 44
}

45
bool SerialLink::_isBootloader()
46
{
47
    QList<QSerialPortInfo> portList = QSerialPortInfo::availablePorts();
48 49 50
    if( portList.count() == 0){
        return false;
    }
51
    for (const QSerialPortInfo &info: portList)
52
    {
53 54
        qCDebug(SerialLinkLog) << "PortName    : " << info.portName() << "Description : " << info.description();
        qCDebug(SerialLinkLog) << "Manufacturer: " << info.manufacturer();
55
        if (info.portName().trimmed() == _serialConfig->portName() &&
56 57 58 59 60
                (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
61
        }
62 63 64 65 66
    }
    // Not found
    return false;
}

67
void SerialLink::_writeBytes(const QByteArray data)
68
{
69
    if(_port && _port->isOpen()) {
70
        emit bytesSent(this, data);
71
        _port->write(data);
72
    } else {
Ricardo de Almeida Gonzaga's avatar
Ricardo de Almeida Gonzaga committed
73
        // Error occurred
74
        qWarning() << "Serial port not writeable";
75
        _emitLinkError(tr("Could not send data - link %1 is disconnected!").arg(_config->name()));
pixhawk's avatar
pixhawk committed
76 77 78
    }
}

79
void SerialLink::disconnect(void)
80
{
81
    if (_port) {
82 83
        // This prevents stale signals from calling the link after it has been deleted
        QObject::disconnect(_port, &QIODevice::readyRead, this, &SerialLink::_readBytes);
84
        _port->close();
85
        _port->deleteLater();
86 87
        _port = nullptr;
        emit disconnected();
88
    }
89

dogmaphobic's avatar
dogmaphobic committed
90
#ifdef __android__
91
    qgcApp()->toolbox()->linkManager()->suspendConfigurationUpdates(false);
dogmaphobic's avatar
dogmaphobic committed
92
#endif
pixhawk's avatar
pixhawk committed
93 94
}

95
bool SerialLink::_connect(void)
96
{
97
    qCDebug(SerialLinkLog) << "CONNECT CALLED";
98

99 100 101 102
    if (_port) {
        qCWarning(SerialLinkLog) << "connect called while already connected";
        return true;
    }
103

dogmaphobic's avatar
dogmaphobic committed
104
#ifdef __android__
105
    qgcApp()->toolbox()->linkManager()->suspendConfigurationUpdates(true);
106
#endif
107

108 109 110
    QSerialPort::SerialPortError    error;
    QString                         errorString;

dogmaphobic's avatar
dogmaphobic committed
111
    // Initialize the connection
112
    if (!_hardwareConnect(error, errorString)) {
113
        if (_config->isAutoConnect()) {
114 115 116 117 118
            // 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
119
        }
120

121
        _emitLinkError(tr("Error connecting: Could not create port. %1").arg(errorString));
dogmaphobic's avatar
dogmaphobic committed
122 123
        return false;
    }
124
    return true;
pixhawk's avatar
pixhawk committed
125 126
}

127 128 129 130 131
/// 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)
132
{
133
    if (_port) {
134
        qCDebug(SerialLinkLog) << "SerialLink:" << QString::number((qulonglong)this, 16) << "closing port";
135
        _port->close();
136 137 138 139 140 141

        // 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);
        }
142
        delete _port;
143
        _port = nullptr;
144
    }
pixhawk's avatar
pixhawk committed
145

146
    qCDebug(SerialLinkLog) << "SerialLink: hardwareConnect to " << _serialConfig->portName();
147

148
    // If we are in the Pixhawk bootloader code wait for it to timeout
149
    if (_isBootloader()) {
150
        qCDebug(SerialLinkLog) << "Not connecting to a bootloader, waiting for 2nd chance";
151 152
        const unsigned retry_limit = 12;
        unsigned retries;
153

154
        for (retries = 0; retries < retry_limit; retries++) {
155
            if (!_isBootloader()) {
156 157 158 159 160
                // 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);
                }
161 162
                break;
            }
163 164 165 166 167 168

            // 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);
            }
169 170 171 172
        }
        // Check limit
        if (retries == retry_limit) {
            // bail out
173
            qWarning() << "Timeout waiting for something other than booloader";
174 175 176 177
            return false;
        }
    }

178
    _port = new QSerialPort(_serialConfig->portName(), this);
Bill Bonney's avatar
Bill Bonney committed
179

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

183
    // After the bootloader times out, it still can take a second or so for the Pixhawk USB driver to come up and make
184
    // the port available for open. So we retry a few times to wait for it.
dogmaphobic's avatar
dogmaphobic committed
185 186 187
#ifdef __android__
    _port->open(QIODevice::ReadWrite);
#else
188 189 190

    // Try to open the port three times
    for (int openRetries = 0; openRetries < 3; openRetries++) {
191
        if (!_port->open(QIODevice::ReadWrite)) {
192
            qCDebug(SerialLinkLog) << "Port open failed, retrying";
193 194 195 196 197 198
            // 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);
199 200 201
        } else {
            break;
        }
202
    }
dogmaphobic's avatar
dogmaphobic committed
203
#endif
204
    if (!_port->isOpen() ) {
205
        qDebug() << "open failed" << _port->errorString() << _port->error() << _config->name() << "autconnect:" << _config->isAutoConnect();
206 207
        error = _port->error();
        errorString = _port->errorString();
208
        _port->close();
209
        delete _port;
210
        _port = nullptr;
211
        return false; // couldn't open serial port
212 213
    }

214 215
    _port->setDataTerminalReady(true);

216
    qCDebug(SerialLinkLog) << "Configuring port";
217 218 219 220 221
    _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()));
222

Bill Bonney's avatar
Bill Bonney committed
223
    emit connected();
224

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

Bill Bonney's avatar
Bill Bonney committed
228
    return true; // successful connection
pixhawk's avatar
pixhawk committed
229
}
230

231 232
void SerialLink::_readBytes(void)
{
233 234 235 236 237 238 239 240 241 242 243
    if (_port && _port->isOpen()) {
        qint64 byteCount = _port->bytesAvailable();
        if (byteCount) {
            QByteArray buffer;
            buffer.resize(byteCount);
            _port->read(buffer.data(), buffer.size());
            emit bytesReceived(this, buffer);
        }
    } else {
        // Error occurred
        qWarning() << "Serial port not readable";
244
        _emitLinkError(tr("Could not read data - link %1 is disconnected!").arg(_config->name()));
245 246 247
    }
}

248
void SerialLink::linkError(QSerialPort::SerialPortError error)
249
{
250 251 252 253
    switch (error) {
    case QSerialPort::NoError:
        break;
    case QSerialPort::ResourceError:
254 255
        // This indicates the hardware was pulled from the computer. For example usb cable unplugged.
        _connectionRemoved();
256 257
        break;
    default:
258 259 260 261
        // 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.
262
        //qCDebug(SerialLinkLog) << "SerialLink::linkError" << error;
263
        break;
264
    }
265 266
}

267
bool SerialLink::isConnected() const
268
{
269
    bool isConnected = false;
Bill Bonney's avatar
Bill Bonney committed
270

271
    if (_port) {
272
        isConnected = _port->isOpen();
lm's avatar
lm committed
273
    }
274

275
    return isConnected;
pixhawk's avatar
pixhawk committed
276 277
}

278
void SerialLink::_emitLinkError(const QString& errorMsg)
279
{
280
    QString msg("Error on link %1. %2");
281
    qDebug() << errorMsg;
282
    emit communicationError(tr("Link Error"), msg.arg(_config->name()).arg(errorMsg));
pixhawk's avatar
pixhawk committed
283 284
}

285 286
//--------------------------------------------------------------------------
//-- SerialConfiguration
pixhawk's avatar
pixhawk committed
287

288
SerialConfiguration::SerialConfiguration(const QString& name) : LinkConfiguration(name)
289
{
290 291 292 293 294
    _baud       = 57600;
    _flowControl= QSerialPort::NoFlowControl;
    _parity     = QSerialPort::NoParity;
    _dataBits   = 8;
    _stopBits   = 1;
295
    _usbDirect  = false;
pixhawk's avatar
pixhawk committed
296 297
}

298
SerialConfiguration::SerialConfiguration(SerialConfiguration* copy) : LinkConfiguration(copy)
299
{
300 301 302 303 304 305 306
    _baud               = copy->baud();
    _flowControl        = copy->flowControl();
    _parity             = copy->parity();
    _dataBits           = copy->dataBits();
    _stopBits           = copy->stopBits();
    _portName           = copy->portName();
    _portDisplayName    = copy->portDisplayName();
307
    _usbDirect          = copy->_usbDirect;
pixhawk's avatar
pixhawk committed
308 309
}

310
void SerialConfiguration::copyFrom(LinkConfiguration *source)
311
{
312
    LinkConfiguration::copyFrom(source);
313
    auto* ssource = qobject_cast<SerialConfiguration*>(source);
DonLakeFlyer's avatar
DonLakeFlyer committed
314 315 316 317 318 319 320 321 322 323 324 325
    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";
    }
326 327
}

328
void SerialConfiguration::setBaud(int baud)
pixhawk's avatar
pixhawk committed
329
{
330
    _baud = baud;
pixhawk's avatar
pixhawk committed
331 332
}

333
void SerialConfiguration::setDataBits(int databits)
pixhawk's avatar
pixhawk committed
334
{
335
    _dataBits = databits;
pixhawk's avatar
pixhawk committed
336 337
}

338
void SerialConfiguration::setFlowControl(int flowControl)
pixhawk's avatar
pixhawk committed
339
{
340
    _flowControl = flowControl;
pixhawk's avatar
pixhawk committed
341 342
}

343
void SerialConfiguration::setStopBits(int stopBits)
344
{
345
    _stopBits = stopBits;
pixhawk's avatar
pixhawk committed
346 347
}

348
void SerialConfiguration::setParity(int parity)
349
{
350
    _parity = parity;
pixhawk's avatar
pixhawk committed
351 352
}

353
void SerialConfiguration::setPortName(const QString& portName)
354
{
355 356 357 358
    // No effect on a running connection
    QString pname = portName.trimmed();
    if (!pname.isEmpty() && pname != _portName) {
        _portName = pname;
359
        _portDisplayName = cleanPortDisplayname(pname);
360 361 362
    }
}

363 364 365
QString SerialConfiguration::cleanPortDisplayname(const QString name)
{
    QString pname = name.trimmed();
366
#ifdef Q_OS_WIN
367 368 369 370 371 372 373 374
    pname.replace("\\\\.\\", "");
#else
    pname.replace("/dev/cu.", "");
    pname.replace("/dev/", "");
#endif
    return pname;
}

375
void SerialConfiguration::saveSettings(QSettings& settings, const QString& root)
376
{
377
    settings.beginGroup(root);
378 379 380 381 382 383 384
    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);
385
    settings.endGroup();
386
}
387

388
void SerialConfiguration::loadSettings(QSettings& settings, const QString& root)
389
{
390
    settings.beginGroup(root);
391 392 393 394 395 396 397
    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();
398
    settings.endGroup();
399
}
400 401 402 403 404 405 406 407 408 409 410

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

void SerialConfiguration::_initBaudRates()
{
    kSupportedBaudRates.clear();
411
    kSupportedBaudRates = QStringList({
412 413
#if USE_ANCIENT_RATES
#if defined(Q_OS_UNIX) || defined(Q_OS_LINUX) || defined(Q_OS_DARWIN)
414 415
        "50",
        "75",
416
#endif
417
        "110",
418
#if defined(Q_OS_UNIX) || defined(Q_OS_LINUX) || defined(Q_OS_DARWIN)
419 420 421
        "150",
        "200" ,
        "134"  ,
422
#endif
423 424 425
        "300",
        "600",
        "1200",
426
#if defined(Q_OS_UNIX) || defined(Q_OS_LINUX) || defined(Q_OS_DARWIN)
427
        "1800",
428 429
#endif
#endif
430 431 432
        "2400",
        "4800",
        "9600",
433
#if defined(Q_OS_WIN)
434
        "14400",
435
#endif
436 437
        "19200",
        "38400",
438
#if defined(Q_OS_WIN)
439
        "56000",
440
#endif
441 442
        "57600",
        "115200",
443
#if defined(Q_OS_WIN)
444
        "128000",
445
#endif
446
        "230400",
447
#if defined(Q_OS_WIN)
448
        "256000",
449
#endif
450 451
        "460800",
        "500000",
452
#if defined(Q_OS_LINUX)
453
        "576000",
454
#endif
455 456
        "921600",
    });
457 458
}

459 460 461 462 463 464 465
void SerialConfiguration::setUsbDirect(bool usbDirect)
{
    if (_usbDirect != usbDirect) {
        _usbDirect = usbDirect;
        emit usbDirectChanged(_usbDirect);
    }
}