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
23SerialConfiguration::SerialConfiguration(const SerialConfiguration *source, QObject *parent)
24 : LinkConfiguration(source, parent)
25{
26 qCDebug(SerialLinkLog) << this;
27
28 SerialConfiguration::copyFrom(source);
29}
30
31SerialConfiguration::~SerialConfiguration()
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 const QString portDisplayName = cleanPortDisplayName(portName);
49 setPortDisplayName(portDisplayName);
50}
51
52void SerialConfiguration::copyFrom(const LinkConfiguration *source)
53{
54 LinkConfiguration::copyFrom(source);
55
56 const SerialConfiguration* serialSource = qobject_cast<const SerialConfiguration*>(source);
57
58 setBaud(serialSource->baud());
59 setDataBits(serialSource->dataBits());
60 setFlowControl(serialSource->flowControl());
61 setStopBits(serialSource->stopBits());
62 setParity(serialSource->parity());
63 setPortName(serialSource->portName());
64 setPortDisplayName(serialSource->portDisplayName());
65 setUsbDirect(serialSource->usbDirect());
66 setdtrForceLow(serialSource->dtrForceLow());
67}
68
69void SerialConfiguration::loadSettings(QSettings &settings, const QString &root)
70{
71 settings.beginGroup(root);
72
73 setBaud(settings.value("baud", _baud).toInt());
74 setDataBits(static_cast<QSerialPort::DataBits>(settings.value("dataBits", _dataBits).toInt()));
75 setFlowControl(static_cast<QSerialPort::FlowControl>(settings.value("flowControl", _flowControl).toInt()));
76 setStopBits(static_cast<QSerialPort::StopBits>(settings.value("stopBits", _stopBits).toInt()));
77 setParity(static_cast<QSerialPort::Parity>(settings.value("parity", _parity).toInt()));
78 setPortName(settings.value("portName", _portName).toString());
79 setPortDisplayName(settings.value("portDisplayName", _portDisplayName).toString());
80 setdtrForceLow(settings.value("dtrForceLow", _dtrForceLow).toBool());
81
82 settings.endGroup();
83}
84
85void SerialConfiguration::saveSettings(QSettings &settings, const QString &root) const
86{
87 settings.beginGroup(root);
88
89 settings.setValue("baud", _baud);
90 settings.setValue("dataBits", _dataBits);
91 settings.setValue("flowControl", _flowControl);
92 settings.setValue("stopBits", _stopBits);
93 settings.setValue("parity", _parity);
94 settings.setValue("portName", _portName);
95 settings.setValue("portDisplayName", _portDisplayName);
96 settings.setValue("dtrForceLow", _dtrForceLow);
97
98 settings.endGroup();
99}
100
101QStringList SerialConfiguration::supportedBaudRates()
102{
103 static const QSet<qint32> kDefaultSupportedBaudRates = {
104#ifdef Q_OS_UNIX
105 50,
106 75,
107#endif
108 110,
109#ifdef Q_OS_UNIX
110 150,
111 200,
112 134,
113#endif
114 300,
115 600,
116 1200,
117#ifdef Q_OS_UNIX
118 1800,
119#endif
120 2400,
121 4800,
122 9600,
123#ifdef Q_OS_WIN
124 14400,
125#endif
126 19200,
127 38400,
128#ifdef Q_OS_WIN
129 56000,
130#endif
131 57600,
132 115200,
133#ifdef Q_OS_WIN
134 128000,
135#endif
136 230400,
137#ifdef Q_OS_WIN
138 256000,
139#endif
140 460800,
141 500000,
142#ifdef Q_OS_LINUX
143 576000,
144#endif
145 921600,
146 };
147
148 const QList<qint32> activeSupportedBaudRates = QSerialPortInfo::standardBaudRates();
149
150 QSet<qint32> mergedBaudRateSet(kDefaultSupportedBaudRates.constBegin(), kDefaultSupportedBaudRates.constEnd());
151 (void) mergedBaudRateSet.unite(QSet<qint32>(activeSupportedBaudRates.constBegin(), activeSupportedBaudRates.constEnd()));
152
153 QList<qint32> mergedBaudRateList = mergedBaudRateSet.values();
154 std::sort(mergedBaudRateList.begin(), mergedBaudRateList.end());
155
156 QStringList supportBaudRateStrings{};
157 supportBaudRateStrings.reserve(mergedBaudRateList.size());
158 for (const qint32 rate : std::as_const(mergedBaudRateList)) {
159 supportBaudRateStrings.append(QString::number(rate));
160 }
161
162 return supportBaudRateStrings;
163}
164
165QString SerialConfiguration::cleanPortDisplayName(const QString &name)
166{
167 const QList<QSerialPortInfo> availablePorts = QSerialPortInfo::availablePorts();
168 for (const QSerialPortInfo &portInfo : availablePorts) {
169 if (portInfo.systemLocation() == name) {
170 return portInfo.portName();
171 }
172 }
173
174 return QString();
175}
176
177/*===========================================================================*/
178
179SerialWorker::SerialWorker(const SerialConfiguration *config, QObject *parent)
180 : QObject(parent)
181 , _serialConfig(config)
182{
183 qCDebug(SerialLinkLog) << this;
184
185 (void) qRegisterMetaType<QSerialPort::SerialPortError>("QSerialPort::SerialPortError");
186}
187
189{
191
192 qCDebug(SerialLinkLog) << this;
193}
194
196{
197 return (_port && _port->isOpen());
198}
199
201{
202 if (!_port) {
203 _port = new QSerialPort(this);
204 }
205
206 if (!_timer) {
207 _timer = new QTimer(this);
208 }
209
210 (void) connect(_port, &QSerialPort::aboutToClose, this, &SerialWorker::_onPortDisconnected);
211 (void) connect(_port, &QSerialPort::readyRead, this, &SerialWorker::_onPortReadyRead);
212 (void) connect(_port, &QSerialPort::errorOccurred, this, &SerialWorker::_onPortErrorOccurred);
213
214 /* if (SerialLinkLog().isDebugEnabled()) {
215 (void) connect(_port, &QSerialPort::bytesWritten, this, &SerialWorker::_onPortBytesWritten);
216 } */
217
218 (void) connect(_timer, &QTimer::timeout, this, &SerialWorker::_checkPortAvailability);
219}
220
222{
223 if (isConnected()) {
224 qCWarning(SerialLinkLog) << "Already connected to" << _port->portName();
225 return;
226 }
227
228 _port->setPortName(_serialConfig->portName());
229
230 const QGCSerialPortInfo portInfo(*_port);
231 if (portInfo.isBootloader()) {
232 qCWarning(SerialLinkLog) << "Not connecting to bootloader" << _port->portName();
233 emit errorOccurred(tr("Not connecting to a bootloader"));
234 _onPortDisconnected();
235 return;
236 }
237
238 _errorEmitted = false;
239
240 qCDebug(SerialLinkLog) << "Attempting to open port" << _port->portName();
241 if (!_port->open(QIODevice::ReadWrite)) {
242 qCWarning(SerialLinkLog) << "Opening port" << _port->portName() << "failed:" << _port->errorString();
243
244 // If auto-connect is enabled, we don't want to emit an error for PermissionError from devices already in use
245 if (!_errorEmitted && (!_serialConfig->isAutoConnect() || _port->error() != QSerialPort::PermissionError)) {
246 emit errorOccurred(tr("Could not open port: %1").arg(_port->errorString()));
247 _errorEmitted = true;
248 }
249
250 _onPortDisconnected();
251
252 return;
253 }
254
255 _onPortConnected();
256}
257
259{
260 if (!isConnected()) {
261 qCDebug(SerialLinkLog) << "Already disconnected from port:" << _port->portName();
262 return;
263 }
264
265 qCDebug(SerialLinkLog) << "Attempting to close port:" << _port->portName();
266
267 _port->close();
268}
269
270void SerialWorker::writeData(const QByteArray &data)
271{
272 if (data.isEmpty()) {
273 emit errorOccurred(tr("Data to Send is Empty"));
274 return;
275 }
276
277 if (!isConnected()) {
278 emit errorOccurred(tr("Port is not Connected"));
279 return;
280 }
281
282 if (!_port->isWritable()) {
283 emit errorOccurred(tr("Port is not Writable"));
284 return;
285 }
286
287 qint64 totalBytesWritten = 0;
288 while (totalBytesWritten < data.size()) {
289 const qint64 bytesWritten = _port->write(data.constData() + totalBytesWritten, data.size() - totalBytesWritten);
290 if (bytesWritten == -1) {
291 emit errorOccurred(tr("Could Not Send Data - Write Failed: %1").arg(_port->errorString()));
292 return;
293 } else if (bytesWritten == 0) {
294 emit errorOccurred(tr("Could Not Send Data - Write Returned 0 Bytes"));
295 return;
296 }
297 totalBytesWritten += bytesWritten;
298 }
299
300 const QByteArray sent = data.first(totalBytesWritten);
301 emit dataSent(sent);
302}
303
304void SerialWorker::_onPortConnected()
305{
306 qCDebug(SerialLinkLog) << "Port connected:" << _port->portName();
307
308 _port->setDataTerminalReady(_serialConfig->dtrForceLow() ? false : true);
309 _port->setBaudRate(_serialConfig->baud());
310 _port->setDataBits(static_cast<QSerialPort::DataBits>(_serialConfig->dataBits()));
311 _port->setFlowControl(static_cast<QSerialPort::FlowControl>(_serialConfig->flowControl()));
312 _port->setStopBits(static_cast<QSerialPort::StopBits>(_serialConfig->stopBits()));
313 _port->setParity(static_cast<QSerialPort::Parity>(_serialConfig->parity()));
314
315 if (_timer) {
316 _timer->start(CONNECT_TIMEOUT_MS);
317 }
318
319 _errorEmitted = false;
320 emit connected();
321}
322
323void SerialWorker::_onPortDisconnected()
324{
325 qCDebug(SerialLinkLog) << "Port disconnected:" << _port->portName();
326
327 if (_timer) {
328 _timer->stop();
329 }
330
331 _errorEmitted = false;
332 emit disconnected();
333}
334
335void SerialWorker::_onPortReadyRead()
336{
337 const QByteArray data = _port->readAll();
338 if (!data.isEmpty()) {
339 // qCDebug(SerialLinkLog) << data.size();
340 emit dataReceived(data);
341 }
342}
343
344void SerialWorker::_onPortBytesWritten(qint64 bytes) const
345{
346 qCDebug(SerialLinkLog) << _port->portName() << "Wrote" << bytes << "bytes";
347}
348
349void SerialWorker::_onPortErrorOccurred(QSerialPort::SerialPortError portError)
350{
351 switch (portError) {
353 qCDebug(SerialLinkLog) << "About to open port" << _port->portName();
354 return;
356 // We get this when a usb cable is unplugged - close port to allow reconnection
357 qCDebug(SerialLinkLog) << "Resource error (likely USB disconnect):" << _port->errorString();
358 _port->close();
359 return;
361 if (_serialConfig->isAutoConnect()) {
362 return;
363 }
364 break;
365 default:
366 break;
367 }
368
369 const QString errorString = _port->errorString();
370 qCWarning(SerialLinkLog) << "Port error:" << portError << errorString;
371
372 if (!_errorEmitted) {
374 _errorEmitted = true;
375 }
376}
377
378void SerialWorker::_checkPortAvailability()
379{
380 if (!isConnected()) {
381 return;
382 }
383
384 bool portExists = false;
385 const auto availablePorts = QSerialPortInfo::availablePorts();
386 for (const QSerialPortInfo &info : availablePorts) {
387 if (info.portName() == _serialConfig->portDisplayName()) {
388 portExists = true;
389 break;
390 }
391 }
392
393 if (!portExists) {
394 _port->close();
395 }
396}
397
398/*===========================================================================*/
399
401 : LinkInterface(config, parent)
402 , _serialConfig(qobject_cast<const SerialConfiguration*>(config.get()))
403 , _worker(new SerialWorker(_serialConfig))
404 , _workerThread(new QThread(this))
405{
406 qCDebug(SerialLinkLog) << this;
407
408 _workerThread->setObjectName(QStringLiteral("Serial_%1").arg(_serialConfig->name()));
409
410 (void) _worker->moveToThread(_workerThread);
411
412 (void) connect(_workerThread, &QThread::started, _worker, &SerialWorker::setupPort);
413 (void) connect(_workerThread, &QThread::finished, _worker, &QObject::deleteLater);
414
415 (void) connect(_worker, &SerialWorker::connected, this, &SerialLink::_onConnected, Qt::QueuedConnection);
416 (void) connect(_worker, &SerialWorker::disconnected, this, &SerialLink::_onDisconnected, Qt::QueuedConnection);
417 (void) connect(_worker, &SerialWorker::dataReceived, this, &SerialLink::_onDataReceived, Qt::QueuedConnection);
418 (void) connect(_worker, &SerialWorker::dataSent, this, &SerialLink::_onDataSent, Qt::QueuedConnection);
419 (void) connect(_worker, &SerialWorker::errorOccurred, this, &SerialLink::_onErrorOccurred, Qt::QueuedConnection);
420
421 _workerThread->start();
422}
423
425{
426 if (isConnected()) {
427 (void) QMetaObject::invokeMethod(_worker, "disconnectFromPort", Qt::BlockingQueuedConnection);
428 _onDisconnected();
429 }
430
431 _workerThread->quit();
432 if (!_workerThread->wait(DISCONNECT_TIMEOUT_MS)) {
433 qCWarning(SerialLinkLog) << "Failed to wait for Serial Thread to close";
434 }
435
436 qCDebug(SerialLinkLog) << this;
437}
438
440{
441 return _worker && _worker->isConnected();
442}
443
444bool SerialLink::_connect()
445{
446 return QMetaObject::invokeMethod(_worker, "connectToPort", Qt::QueuedConnection);
447}
448
450{
451 if (isConnected()) {
452 (void) QMetaObject::invokeMethod(_worker, "disconnectFromPort", Qt::QueuedConnection);
453 }
454}
455
456void SerialLink::_onConnected()
457{
458 _disconnectedEmitted = false;
459 emit connected();
460}
461
462void SerialLink::_onDisconnected()
463{
464 if (!_disconnectedEmitted.exchange(true)) {
465 emit disconnected();
466 }
467}
468
469void SerialLink::_onErrorOccurred(const QString &errorString)
470{
471 qCWarning(SerialLinkLog) << "Communication error:" << errorString;
472 emit communicationError(tr("Serial Link Error"), tr("Link %1: (Port: %2) %3").arg(_serialConfig->name(), _serialConfig->portName(), errorString));
473}
474
475void SerialLink::_onDataReceived(const QByteArray &data)
476{
477 emit bytesReceived(this, data);
478}
479
480void SerialLink::_onDataSent(const QByteArray &data)
481{
482 emit bytesSent(this, data);
483}
484
485void SerialLink::_writeBytes(const QByteArray &data)
486{
487 (void) QMetaObject::invokeMethod(_worker, "writeData", Qt::QueuedConnection, Q_ARG(QByteArray, data));
488}
std::shared_ptr< LinkConfiguration > SharedLinkConfigurationPtr
QString errorString
#define QGC_LOGGING_CATEGORY(name, categoryStr)
Interface holding link specific settings.
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()
Provides information about existing serial ports.
static QList< QSerialPortInfo > availablePorts()
static QList< qint32 > standardBaudRates()
Provides functions to access serial ports.
Definition qserialport.h:17
bool setDataTerminalReady(bool set)
bool open(OpenMode mode) override
void close() override
bool setStopBits(StopBits stopBits)
void setPortName(const QString &name)
void errorOccurred(QSerialPort::SerialPortError error)
SerialPortError error() const
the error status of the serial port
QString portName() const
bool setBaudRate(qint32 baudRate, Directions directions=AllDirections)
bool setDataBits(DataBits dataBits)
bool setParity(Parity parity)
bool setFlowControl(FlowControl flowControl)
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