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