QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
SerialLink.cc
Go to the documentation of this file.
1#include "SerialLink.h"
3#include "QGCSerialPortInfo.h"
4#include <QtCore/QSettings>
5#include <QtCore/QThread>
6#include <QtCore/QTimer>
7
8QGC_LOGGING_CATEGORY(SerialLinkLog, "Comms.SerialLink")
9
10namespace {
11 constexpr int CONNECT_TIMEOUT_MS = 1000;
12 constexpr int DISCONNECT_TIMEOUT_MS = 3000;
13}
14
15/*===========================================================================*/
16
17SerialConfiguration::SerialConfiguration(const QString &name, QObject *parent)
18 : LinkConfiguration(name, parent)
19{
20 qCDebug(SerialLinkLog) << this;
21}
22
24 : LinkConfiguration(source, parent)
25{
26 qCDebug(SerialLinkLog) << this;
27
29}
30
32{
33 qCDebug(SerialLinkLog) << this;
34}
35
36void SerialConfiguration::setPortName(const QString &name)
37{
38 const QString portName = name.trimmed();
39 if (portName.isEmpty()) {
40 return;
41 }
42
43 if (portName != _portName) {
44 _portName = portName;
45 emit portNameChanged();
46 }
47
48 // Only update the display name if the port is currently available. Otherwise keep
49 // the existing (e.g. persisted) display name rather than clearing it.
51 if (!portDisplayName.isEmpty()) {
53 }
54}
55
57{
59
60 const SerialConfiguration* serialSource = qobject_cast<const SerialConfiguration*>(source);
61
62 setBaud(serialSource->baud());
63 setDataBits(serialSource->dataBits());
64 setFlowControl(serialSource->flowControl());
65 setStopBits(serialSource->stopBits());
66 setParity(serialSource->parity());
67 setPortName(serialSource->portName());
68 setPortDisplayName(serialSource->portDisplayName());
69 setUsbDirect(serialSource->usbDirect());
70 setdtrForceLow(serialSource->dtrForceLow());
71}
72
73void SerialConfiguration::loadSettings(QSettings &settings, const QString &root)
74{
75 settings.beginGroup(root);
76
77 setBaud(settings.value("baud", _baud).toInt());
78 setDataBits(static_cast<QSerialPort::DataBits>(settings.value("dataBits", _dataBits).toInt()));
79 setFlowControl(static_cast<QSerialPort::FlowControl>(settings.value("flowControl", _flowControl).toInt()));
80 setStopBits(static_cast<QSerialPort::StopBits>(settings.value("stopBits", _stopBits).toInt()));
81 setParity(static_cast<QSerialPort::Parity>(settings.value("parity", _parity).toInt()));
82 // Load the saved display name first as a fallback; setPortName() recomputes a
83 // fresh display name which takes precedence when the device is present.
84 setPortDisplayName(settings.value("portDisplayName", _portDisplayName).toString());
85 setPortName(settings.value("portName", _portName).toString());
86 setdtrForceLow(settings.value("dtrForceLow", _dtrForceLow).toBool());
87
88 settings.endGroup();
89}
90
91void SerialConfiguration::saveSettings(QSettings &settings, const QString &root) const
92{
93 settings.beginGroup(root);
94
95 settings.setValue("baud", _baud);
96 settings.setValue("dataBits", _dataBits);
97 settings.setValue("flowControl", _flowControl);
98 settings.setValue("stopBits", _stopBits);
99 settings.setValue("parity", _parity);
100 settings.setValue("portName", _portName);
101 settings.setValue("portDisplayName", _portDisplayName);
102 settings.setValue("dtrForceLow", _dtrForceLow);
103
104 settings.endGroup();
105}
106
108{
109 static const QSet<qint32> kDefaultSupportedBaudRates = {
110#ifdef Q_OS_UNIX
111 50,
112 75,
113#endif
114 110,
115#ifdef Q_OS_UNIX
116 150,
117 200,
118 134,
119#endif
120 300,
121 600,
122 1200,
123#ifdef Q_OS_UNIX
124 1800,
125#endif
126 2400,
127 4800,
128 9600,
129#ifdef Q_OS_WIN
130 14400,
131#endif
132 19200,
133 38400,
134#ifdef Q_OS_WIN
135 56000,
136#endif
137 57600,
138 115200,
139#ifdef Q_OS_WIN
140 128000,
141#endif
142 230400,
143#ifdef Q_OS_WIN
144 256000,
145#endif
146 460800,
147 500000,
148#ifdef Q_OS_LINUX
149 576000,
150#endif
151 921600,
152 };
153
154 const QList<qint32> activeSupportedBaudRates = QSerialPortInfo::standardBaudRates();
155
156 QSet<qint32> mergedBaudRateSet(kDefaultSupportedBaudRates.constBegin(), kDefaultSupportedBaudRates.constEnd());
157 (void) mergedBaudRateSet.unite(QSet<qint32>(activeSupportedBaudRates.constBegin(), activeSupportedBaudRates.constEnd()));
158
159 QList<qint32> mergedBaudRateList = mergedBaudRateSet.values();
160 std::sort(mergedBaudRateList.begin(), mergedBaudRateList.end());
161
162 QStringList supportBaudRateStrings{};
163 supportBaudRateStrings.reserve(mergedBaudRateList.size());
164 for (const qint32 rate : std::as_const(mergedBaudRateList)) {
165 supportBaudRateStrings.append(QString::number(rate));
166 }
167
168 return supportBaudRateStrings;
169}
170
172{
173 const QList<QSerialPortInfo> availablePorts = QSerialPortInfo::availablePorts();
174 for (const QSerialPortInfo &portInfo : availablePorts) {
175 if (portInfo.systemLocation() == name) {
176#ifdef Q_OS_ANDROID
177 // Android port names (bus/usb/001/003) aren't human-readable. Prefer the USB
178 // description/manufacturer, with the port name appended to keep entries unique.
179 QString displayName;
180 if (!portInfo.description().isEmpty()) {
181 displayName = portInfo.description();
182 } else if (!portInfo.manufacturer().isEmpty()) {
183 displayName = portInfo.manufacturer();
184 }
185 if (!displayName.isEmpty()) {
186 return QStringLiteral("%1 (%2)").arg(displayName, portInfo.portName());
187 }
188#endif
189 return portInfo.portName();
190 }
191 }
192
193 return QString();
194}
195
196/*===========================================================================*/
197
199 : QObject(parent)
200 , _serialConfig(config)
201{
202 qCDebug(SerialLinkLog) << this;
203
204 (void) qRegisterMetaType<QSerialPort::SerialPortError>("QSerialPort::SerialPortError");
205}
206
208{
210
211 qCDebug(SerialLinkLog) << this;
212}
213
215{
216 return (_port && _port->isOpen());
217}
218
220{
221 if (!_port) {
222 _port = new QSerialPort(this);
223 }
224
225 if (!_timer) {
226 _timer = new QTimer(this);
227 }
228
229 (void) connect(_port, &QSerialPort::aboutToClose, this, &SerialWorker::_onPortDisconnected);
230 (void) connect(_port, &QSerialPort::readyRead, this, &SerialWorker::_onPortReadyRead);
231 (void) connect(_port, &QSerialPort::errorOccurred, this, &SerialWorker::_onPortErrorOccurred);
232
233 /* if (SerialLinkLog().isDebugEnabled()) {
234 (void) connect(_port, &QSerialPort::bytesWritten, this, &SerialWorker::_onPortBytesWritten);
235 } */
236
237 (void) connect(_timer, &QTimer::timeout, this, &SerialWorker::_checkPortAvailability);
238}
239
241{
242 if (isConnected()) {
243 qCWarning(SerialLinkLog) << "Already connected to" << _port->portName();
244 return;
245 }
246
247 _port->setPortName(_serialConfig->portName());
248
249 const QGCSerialPortInfo portInfo(*_port);
250 if (portInfo.isBootloader()) {
251 qCWarning(SerialLinkLog) << "Not connecting to bootloader" << _port->portName();
252 emit errorOccurred(tr("Not connecting to a bootloader"));
253 _onPortDisconnected();
254 return;
255 }
256
257 _errorEmitted = false;
258
259 qCDebug(SerialLinkLog) << "Attempting to open port" << _port->portName();
260 if (!_port->open(QIODevice::ReadWrite)) {
261 qCWarning(SerialLinkLog) << "Opening port" << _port->portName() << "failed:" << _port->errorString();
262
263 // If auto-connect is enabled, we don't want to emit an error for PermissionError from devices already in use
264 if (!_errorEmitted && (!_serialConfig->isAutoConnect() || _port->error() != QSerialPort::PermissionError)) {
265 emit errorOccurred(tr("Could not open port: %1").arg(_port->errorString()));
266 _errorEmitted = true;
267 }
268
269 _onPortDisconnected();
270
271 return;
272 }
273
274 _onPortConnected();
275}
276
278{
279 if (!isConnected()) {
280 qCDebug(SerialLinkLog) << "Already disconnected from port:" << _port->portName();
281 return;
282 }
283
284 qCDebug(SerialLinkLog) << "Attempting to close port:" << _port->portName();
285
286 _port->close();
287}
288
289void SerialWorker::writeData(const QByteArray &data)
290{
291 if (data.isEmpty()) {
292 emit errorOccurred(tr("Data to Send is Empty"));
293 return;
294 }
295
296 if (!isConnected()) {
297 emit errorOccurred(tr("Port is not Connected"));
298 return;
299 }
300
301 if (!_port->isWritable()) {
302 emit errorOccurred(tr("Port is not Writable"));
303 return;
304 }
305
306 qint64 totalBytesWritten = 0;
307 while (totalBytesWritten < data.size()) {
308 const qint64 bytesWritten = _port->write(data.constData() + totalBytesWritten, data.size() - totalBytesWritten);
309 if (bytesWritten == -1) {
310 emit errorOccurred(tr("Could Not Send Data - Write Failed: %1").arg(_port->errorString()));
311 return;
312 } else if (bytesWritten == 0) {
313 emit errorOccurred(tr("Could Not Send Data - Write Returned 0 Bytes"));
314 return;
315 }
316 totalBytesWritten += bytesWritten;
317 }
318
319 const QByteArray sent = data.first(totalBytesWritten);
320 emit dataSent(sent);
321}
322
323void SerialWorker::_onPortConnected()
324{
325 qCDebug(SerialLinkLog) << "Port connected:" << _port->portName();
326
327 _port->setDataTerminalReady(_serialConfig->dtrForceLow() ? false : true);
328 _port->setBaudRate(_serialConfig->baud());
329 _port->setDataBits(static_cast<QSerialPort::DataBits>(_serialConfig->dataBits()));
330 _port->setFlowControl(static_cast<QSerialPort::FlowControl>(_serialConfig->flowControl()));
331 _port->setStopBits(static_cast<QSerialPort::StopBits>(_serialConfig->stopBits()));
332 _port->setParity(static_cast<QSerialPort::Parity>(_serialConfig->parity()));
333
334 if (_timer) {
335 _timer->start(CONNECT_TIMEOUT_MS);
336 }
337
338 _errorEmitted = false;
339 emit connected();
340}
341
342void SerialWorker::_onPortDisconnected()
343{
344 qCDebug(SerialLinkLog) << "Port disconnected:" << _port->portName();
345
346 if (_timer) {
347 _timer->stop();
348 }
349
350 _errorEmitted = false;
351 emit disconnected();
352}
353
354void SerialWorker::_onPortReadyRead()
355{
356 const QByteArray data = _port->readAll();
357 if (!data.isEmpty()) {
358 // qCDebug(SerialLinkLog) << data.size();
359 emit dataReceived(data);
360 }
361}
362
363void SerialWorker::_onPortBytesWritten(qint64 bytes) const
364{
365 qCDebug(SerialLinkLog) << _port->portName() << "Wrote" << bytes << "bytes";
366}
367
368void SerialWorker::_onPortErrorOccurred(QSerialPort::SerialPortError portError)
369{
370 switch (portError) {
372 qCDebug(SerialLinkLog) << "About to open port" << _port->portName();
373 return;
375 // We get this when a usb cable is unplugged - close port to allow reconnection
376 qCDebug(SerialLinkLog) << "Resource error (likely USB disconnect):" << _port->errorString();
377 _port->close();
378 return;
380 if (_serialConfig->isAutoConnect()) {
381 return;
382 }
383 break;
384 default:
385 break;
386 }
387
388 const QString errorString = _port->errorString();
389 qCWarning(SerialLinkLog) << "Port error:" << portError << errorString;
390
391 if (!_errorEmitted) {
393 _errorEmitted = true;
394 }
395}
396
397void SerialWorker::_checkPortAvailability()
398{
399 if (!isConnected()) {
400 return;
401 }
402
403 bool portExists = false;
404 const QString configuredPort = _serialConfig->portName();
405 const auto availablePorts = QSerialPortInfo::availablePorts();
406 for (const QSerialPortInfo &info : availablePorts) {
407 // Compare against the real port identity, not the human-readable display
408 // name, which may be a USB description string (e.g. on Android).
409 if ((info.systemLocation() == configuredPort) || (info.portName() == configuredPort)) {
410 portExists = true;
411 break;
412 }
413 }
414
415 if (!portExists) {
416 _port->close();
417 }
418}
419
420/*===========================================================================*/
421
423 : LinkInterface(config, parent)
424 , _serialConfig(qobject_cast<const SerialConfiguration*>(config.get()))
425 , _worker(new SerialWorker(_serialConfig))
426 , _workerThread(new QThread(this))
427{
428 qCDebug(SerialLinkLog) << this;
429
430 _workerThread->setObjectName(QStringLiteral("Serial_%1").arg(_serialConfig->name()));
431
432 (void) _worker->moveToThread(_workerThread);
433
434 (void) connect(_workerThread, &QThread::started, _worker, &SerialWorker::setupPort);
435 (void) connect(_workerThread, &QThread::finished, _worker, &QObject::deleteLater);
436
437 (void) connect(_worker, &SerialWorker::connected, this, &SerialLink::_onConnected, Qt::QueuedConnection);
438 (void) connect(_worker, &SerialWorker::disconnected, this, &SerialLink::_onDisconnected, Qt::QueuedConnection);
439 (void) connect(_worker, &SerialWorker::dataReceived, this, &SerialLink::_onDataReceived, Qt::QueuedConnection);
440 (void) connect(_worker, &SerialWorker::dataSent, this, &SerialLink::_onDataSent, Qt::QueuedConnection);
441 (void) connect(_worker, &SerialWorker::errorOccurred, this, &SerialLink::_onErrorOccurred, Qt::QueuedConnection);
442
443 _workerThread->start();
444}
445
447{
448 if (isConnected()) {
449 (void) QMetaObject::invokeMethod(_worker, "disconnectFromPort", Qt::BlockingQueuedConnection);
450 _onDisconnected();
451 }
452
453 _workerThread->quit();
454 if (!_workerThread->wait(DISCONNECT_TIMEOUT_MS)) {
455 qCWarning(SerialLinkLog) << "Failed to wait for Serial Thread to close";
456 }
457
458 qCDebug(SerialLinkLog) << this;
459}
460
462{
463 return _worker && _worker->isConnected();
464}
465
466bool SerialLink::_connect()
467{
468 return QMetaObject::invokeMethod(_worker, "connectToPort", Qt::QueuedConnection);
469}
470
472{
473 if (isConnected()) {
474 (void) QMetaObject::invokeMethod(_worker, "disconnectFromPort", Qt::QueuedConnection);
475 }
476}
477
478void SerialLink::_onConnected()
479{
480 _disconnectedEmitted = false;
481 emit connected();
482}
483
484void SerialLink::_onDisconnected()
485{
486 if (!_disconnectedEmitted.exchange(true)) {
487 emit disconnected();
488 }
489}
490
491void SerialLink::_onErrorOccurred(const QString &errorString)
492{
493 qCWarning(SerialLinkLog) << "Communication error:" << errorString;
494 emit communicationError(tr("Serial Link Error"), tr("Link %1: (Port: %2) %3").arg(_serialConfig->name(), _serialConfig->portName(), errorString));
495}
496
497void SerialLink::_onDataReceived(const QByteArray &data)
498{
499 emit bytesReceived(this, data);
500}
501
502void SerialLink::_onDataSent(const QByteArray &data)
503{
504 emit bytesSent(this, data);
505}
506
507void SerialLink::_writeBytes(const QByteArray &data)
508{
509 (void) QMetaObject::invokeMethod(_worker, "writeData", Qt::QueuedConnection, Q_ARG(QByteArray, data));
510}
Config config
std::shared_ptr< LinkConfiguration > SharedLinkConfigurationPtr
QString errorString
#define QGC_LOGGING_CATEGORY(name, categoryStr)
Interface holding link specific settings.
virtual void copyFrom(const LinkConfiguration *source)
bool isAutoConnect() const
QString name() const
The link interface defines the interface for all links used to communicate with the ground station ap...
void bytesReceived(LinkInterface *link, const QByteArray &data)
void disconnected()
void communicationError(const QString &title, const QString &error)
void bytesSent(LinkInterface *link, const QByteArray &data)
void connected()
QGC's version of Qt QSerialPortInfo. It provides additional information about board types that QGC ca...
Provides information about existing serial ports.
static QList< QSerialPortInfo > availablePorts()
Returns a list of available serial ports on the system.
static QList< qint32 > standardBaudRates()
Returns a list of available standard baud rates supported by the target platform.
Provides functions to access serial ports.
Definition qserialport.h:17
bool setDataTerminalReady(bool set)
bool open(OpenMode mode) override
\reimp
void close() override
\reimp
bool setStopBits(StopBits stopBits)
void setPortName(const QString &name)
Sets the name of the serial port.
void errorOccurred(QSerialPort::SerialPortError error)
DataBits
This enum describes the number of data bits used.
Definition qserialport.h:60
Parity
This enum describes the parity scheme used.
Definition qserialport.h:69
SerialPortError error() const
the error status of the serial port
SerialPortError
This enum describes the errors that may be contained by the QSerialPort::error property.
QString portName() const
Returns the name set by setPort() or passed to the QSerialPort constructor.
bool setBaudRate(qint32 baudRate, Directions directions=AllDirections)
StopBits
This enum describes the number of stop bits used.
Definition qserialport.h:79
bool setDataBits(DataBits dataBits)
FlowControl
This enum describes the flow control used.
Definition qserialport.h:87
bool setParity(Parity parity)
bool setFlowControl(FlowControl flowControl)
void setParity(QSerialPort::Parity parity)
Definition SerialLink.h:58
QSerialPort::FlowControl flowControl() const
Definition SerialLink.h:51
void loadSettings(QSettings &settings, const QString &root) override
Definition SerialLink.cc:73
QSerialPort::StopBits stopBits() const
Definition SerialLink.h:54
void saveSettings(QSettings &settings, const QString &root) const override
Definition SerialLink.cc:91
void setStopBits(QSerialPort::StopBits stopBits)
Definition SerialLink.h:55
void setPortDisplayName(const QString &portDisplayName)
Definition SerialLink.h:64
qint32 baud() const
Definition SerialLink.h:45
void setBaud(qint32 baud)
Definition SerialLink.h:46
SerialConfiguration(const QString &name, QObject *parent=nullptr)
Definition SerialLink.cc:17
void setDataBits(QSerialPort::DataBits databits)
Definition SerialLink.h:49
void setdtrForceLow(bool dtrForceLow)
Definition SerialLink.h:70
bool usbDirect() const
Definition SerialLink.h:66
static QStringList supportedBaudRates()
QString portName() const
Definition SerialLink.h:60
QString portDisplayName() const
Definition SerialLink.h:63
QSerialPort::Parity parity() const
Definition SerialLink.h:57
void setUsbDirect(bool usbDirect)
Definition SerialLink.h:67
QSerialPort::DataBits dataBits() const
Definition SerialLink.h:48
bool dtrForceLow() const
Definition SerialLink.h:69
void copyFrom(const LinkConfiguration *source) override
Definition SerialLink.cc:56
static QString cleanPortDisplayName(const QString &name)
void setFlowControl(QSerialPort::FlowControl flowControl)
Definition SerialLink.h:52
void setPortName(const QString &name)
Definition SerialLink.cc:36
virtual ~SerialConfiguration()
Definition SerialLink.cc:31
void connectToPort()
void dataReceived(const QByteArray &data)
void disconnectFromPort()
void connected()
SerialWorker(const SerialConfiguration *config, QObject *parent=nullptr)
void dataSent(const QByteArray &data)
void disconnected()
void writeData(const QByteArray &data)
void setupPort()
void errorOccurred(const QString &errorString)
bool isConnected() const