QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
SerialLink.cc
Go to the documentation of this file.
1#include "SerialLink.h"
2
3#include <QtCore/QSettings>
4#include <QtCore/QSignalBlocker>
5#include <QtCore/QThread>
6
7#include <algorithm>
8#include <utility>
9
10#include "QGCLoggingCategory.h"
11#include "SerialPortManager.h"
12
13QGC_LOGGING_CATEGORY(SerialLinkLog, "Comms.Serial.SerialLink")
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{
58 const SerialConfiguration* serialSource = qobject_cast<const SerialConfiguration*>(source);
59 if (!serialSource) {
60 qCWarning(SerialLinkLog) << "Invalid source configuration type";
61 return;
62 }
63
65
66 setBaud(serialSource->baud());
67 setDataBits(serialSource->dataBits());
68 setFlowControl(serialSource->flowControl());
69 setStopBits(serialSource->stopBits());
70 setParity(serialSource->parity());
71 setPortName(serialSource->portName());
72 setPortDisplayName(serialSource->portDisplayName());
73 setUsbDirect(serialSource->usbDirect());
74 setSikRadio(serialSource->sikRadio());
75 setdtrForceLow(serialSource->dtrForceLow());
76}
77
78void SerialConfiguration::loadSettings(QSettings &settings, const QString &root)
79{
80 settings.beginGroup(root);
81
82 setBaud(settings.value("baud", _baud).toInt());
83 setDataBits(static_cast<QSerialPort::DataBits>(settings.value("dataBits", _dataBits).toInt()));
84 setFlowControl(static_cast<QSerialPort::FlowControl>(settings.value("flowControl", _flowControl).toInt()));
85 setStopBits(static_cast<QSerialPort::StopBits>(settings.value("stopBits", _stopBits).toInt()));
86 setParity(static_cast<QSerialPort::Parity>(settings.value("parity", _parity).toInt()));
87 // Load the saved display name first as a fallback; setPortName() recomputes a
88 // fresh display name which takes precedence when the device is present.
89 setPortDisplayName(settings.value("portDisplayName", _portDisplayName).toString());
90 setPortName(settings.value("portName", _portName).toString());
91 setdtrForceLow(settings.value("dtrForceLow", _dtrForceLow).toBool());
92
93 settings.endGroup();
94}
95
96void SerialConfiguration::saveSettings(QSettings &settings, const QString &root) const
97{
98 settings.beginGroup(root);
99
100 settings.setValue("baud", _baud);
101 settings.setValue("dataBits", _dataBits);
102 settings.setValue("flowControl", _flowControl);
103 settings.setValue("stopBits", _stopBits);
104 settings.setValue("parity", _parity);
105 settings.setValue("portName", _portName);
106 settings.setValue("portDisplayName", _portDisplayName);
107 settings.setValue("dtrForceLow", _dtrForceLow);
108
109 settings.endGroup();
110}
111
116
118{
119 const auto ports = SerialPortManager::instance()->availablePorts();
120 const bool bootloader = std::any_of(ports.cbegin(), ports.cend(), [this](const auto& port) {
121 return (port.systemLocation == _portName || port.portName == _portName) && port.bootloader;
122 });
123 return {_portName, _baud, _dataBits, _flowControl, _stopBits, _parity, _dtrForceLow, isAutoConnect(), bootloader};
124}
125
127{
129}
130
131/*===========================================================================*/
132
134 QObject* parent)
135 : QObject(parent), _settings(std::move(settings)), _reservation(std::move(reservation))
136{
137 qCDebug(SerialLinkLog) << this;
138
139 (void) qRegisterMetaType<QSerialPort::SerialPortError>("QSerialPort::SerialPortError");
140}
141
143{
145
146 qCDebug(SerialLinkLog) << this;
147}
148
150{
151 // Called cross-thread from SerialLink; must not touch the thread-affine _port
152 return _isConnected;
153}
154
156{
157 if (_port) {
158 return;
159 }
160 _port = new QSerialPort(this);
161
162 (void) connect(_port, &QSerialPort::aboutToClose, this, &SerialWorker::_onPortDisconnected);
163 (void) connect(_port, &QSerialPort::readyRead, this, &SerialWorker::_onPortReadyRead);
164 (void) connect(_port, &QSerialPort::errorOccurred, this, &SerialWorker::_onPortErrorOccurred);
165
166 /* if (SerialLinkLog().isDebugEnabled()) {
167 (void) connect(_port, &QSerialPort::bytesWritten, this, &SerialWorker::_onPortBytesWritten);
168 } */
169}
170
172{
173 if (!_port) {
174 setupPort();
175 }
176 if (isConnected()) {
177 qCWarning(SerialLinkLog) << "Already connected to" << _port->portName();
178 return;
179 }
180
181 _port->setPortName(_settings.portName);
182
183 if (_settings.bootloader) {
184 qCWarning(SerialLinkLog) << "Not connecting to bootloader" << _port->portName();
185 emit errorOccurred(tr("Not connecting to a bootloader"));
186 _onPortDisconnected();
187 return;
188 }
189
190 _errorEmitted = false;
191
192 qCDebug(SerialLinkLog) << "Attempting to open port" << _port->portName();
193 if (!_port->open(QIODevice::ReadWrite)) {
194 const bool busyAutoConnect = _settings.autoConnect && (_port->error() == QSerialPort::PermissionError ||
196 if (busyAutoConnect) {
197 qCDebug(SerialLinkLog) << "Auto-connect port unavailable:" << _port->portName() << _port->errorString();
198 } else {
199 qCWarning(SerialLinkLog) << "Opening port" << _port->portName() << "failed:" << _port->errorString();
200 }
201
202 // Occupied or disappearing devices are normal races during automatic discovery.
203 if (!_errorEmitted && !busyAutoConnect) {
204 emit errorOccurred(tr("Could not open port: %1").arg(_port->errorString()));
205 _errorEmitted = true;
206 }
207
208 _onPortDisconnected();
209
210 return;
211 }
212
213 if (!_configurePort()) {
214 const QString error = _port->errorString();
215 emit errorOccurred(tr("Could not configure port: %1").arg(error));
216 _port->close();
217 return;
218 }
219 _onPortConnected();
220}
221
223{
224 if (!_port || !_port->isOpen()) {
225 return;
226 }
227
228 qCDebug(SerialLinkLog) << "Attempting to close port:" << _port->portName();
229
230 _port->close();
231}
232
233void SerialWorker::writeData(const QByteArray &data)
234{
235 if (data.isEmpty()) {
236 emit errorOccurred(tr("Data to Send is Empty"));
237 return;
238 }
239
240 if (!isConnected()) {
241 emit errorOccurred(tr("Port is not Connected"));
242 return;
243 }
244
245 if (!_port->isWritable()) {
246 emit errorOccurred(tr("Port is not Writable"));
247 return;
248 }
249
250 qint64 totalBytesWritten = 0;
251 while (totalBytesWritten < data.size()) {
252 const qint64 bytesWritten = _port->write(data.constData() + totalBytesWritten, data.size() - totalBytesWritten);
253 if (bytesWritten == -1) {
254 emit errorOccurred(tr("Could Not Send Data - Write Failed: %1").arg(_port->errorString()));
255 return;
256 } else if (bytesWritten == 0) {
257 emit errorOccurred(tr("Could Not Send Data - Write Returned 0 Bytes"));
258 return;
259 }
260 totalBytesWritten += bytesWritten;
261 }
262
263 const QByteArray sent = data.first(totalBytesWritten);
264 emit dataSent(sent);
265}
266
267void SerialWorker::_onPortConnected()
268{
269 qCDebug(SerialLinkLog) << "Port connected:" << _port->portName();
270
271 _isConnected = true;
272 _errorEmitted = false;
273 emit connected();
274}
275
276void SerialWorker::_onPortDisconnected()
277{
278 qCDebug(SerialLinkLog) << "Port disconnected:" << _port->portName();
279
280 _isConnected = false;
281 _errorEmitted = false;
282 emit disconnected();
283}
284
285void SerialWorker::_onPortReadyRead()
286{
287 const QByteArray data = _port->readAll();
288 if (!data.isEmpty()) {
289 // qCDebug(SerialLinkLog) << data.size();
290 emit dataReceived(data);
291 }
292}
293
294void SerialWorker::_onPortBytesWritten(qint64 bytes) const
295{
296 qCDebug(SerialLinkLog) << _port->portName() << "Wrote" << bytes << "bytes";
297}
298
299void SerialWorker::_onPortErrorOccurred(QSerialPort::SerialPortError portError)
300{
301 switch (portError) {
303 qCDebug(SerialLinkLog) << "About to open port" << _port->portName();
304 return;
306 // We get this when a usb cable is unplugged - close port to allow reconnection
307 qCDebug(SerialLinkLog) << "Resource error (likely USB disconnect):" << _port->errorString();
308 _port->close();
309 return;
312 if (_settings.autoConnect) {
313 return;
314 }
315 break;
316 default:
317 break;
318 }
319
320 const QString errorString = _port->errorString();
321 qCWarning(SerialLinkLog) << "Port error:" << portError << errorString;
322
323 if (!_errorEmitted) {
325 _errorEmitted = true;
326 }
327}
328
329void SerialWorker::checkPortAvailability(const QStringList& availablePorts)
330{
331 if (isConnected() && !availablePorts.contains(_settings.portName)) {
332 _port->close();
333 }
334}
335
336bool SerialWorker::_configurePort()
337{
338 // Report one setup failure; QSerialPort also emits errorOccurred synchronously.
339 const QSignalBlocker blocker(_port);
340 if (!_port->setBaudRate(_settings.baud) || !_port->setDataBits(_settings.dataBits) ||
341 !_port->setFlowControl(_settings.flowControl) || !_port->setStopBits(_settings.stopBits) ||
342 !_port->setParity(_settings.parity)) {
343 return false;
344 }
345 if (!_port->setDataTerminalReady(!_settings.dtrForceLow)) {
346 // PTYs and some USB adapters have no modem-control lines. Forcing DTR low is explicit and required.
347 if (_settings.dtrForceLow || _port->error() != QSerialPort::UnsupportedOperationError) {
348 return false;
349 }
350 qCDebug(SerialLinkLog) << "DTR is unavailable on" << _settings.portName;
351 _port->clearError();
352 }
353 // Some drivers (Android FTDI, Windows with no flow control) leave RTS low after open. A radio with RTS/CTS
354 // enabled sees CTS low and never transmits, so assert it explicitly. Fails harmlessly under HardwareControl,
355 // where the driver owns RTS.
356 if (!_port->setRequestToSend(true)) {
358 return false;
359 }
360 qCDebug(SerialLinkLog) << "RTS is unavailable on" << _settings.portName;
361 _port->clearError();
362 }
363 return true;
364}
365
366/*===========================================================================*/
367
369 QObject* parent)
370 : LinkInterface(config, parent),
371 _serialConfig(qobject_cast<const SerialConfiguration*>(config.get())),
372 _reservation(std::move(reservation)),
373 _worker(new SerialWorker(_serialConfig->connectionSettings(), _reservation)),
374 _workerThread(new QThread(this))
375{
376 qCDebug(SerialLinkLog) << this;
377
378 _workerThread->setObjectName(QStringLiteral("Serial_%1").arg(_serialConfig->name()));
379
380 (void) _worker->moveToThread(_workerThread);
381
382 (void) connect(_workerThread, &QThread::started, _worker, &SerialWorker::setupPort);
383 (void) connect(_workerThread, &QThread::finished, _worker, &QObject::deleteLater);
384
385 (void) connect(_worker, &SerialWorker::connected, this, &SerialLink::_onConnected, Qt::QueuedConnection);
386 (void) connect(_worker, &SerialWorker::disconnected, this, &SerialLink::_onDisconnected, Qt::QueuedConnection);
387 (void) connect(_worker, &SerialWorker::dataReceived, this, &SerialLink::_onDataReceived, Qt::QueuedConnection);
388 (void) connect(_worker, &SerialWorker::dataSent, this, &SerialLink::_onDataSent, Qt::QueuedConnection);
389 (void) connect(_worker, &SerialWorker::errorOccurred, this, &SerialLink::_onErrorOccurred, Qt::QueuedConnection);
390
392 &SerialWorker::checkPortAvailability, Qt::QueuedConnection);
393
394 _workerThread->start();
395}
396
398{
399 if (isConnected()) {
400 // Queued, not blocking: a wedged worker (e.g. QSerialPort::close() hung in a driver)
401 // would block here forever, before _shutdownWorkerThread can bound the wait.
402 // If quit() beats the queued call, ~SerialWorker still disconnects on thread finish.
403 (void) QMetaObject::invokeMethod(_worker, "disconnectFromPort", Qt::QueuedConnection);
404 _onDisconnected();
405 }
406
407 _shutdownWorkerThread(_workerThread, SerialLinkLog());
408
409 qCDebug(SerialLinkLog) << this;
410}
411
413{
414 return _worker && _worker->isConnected();
415}
416
417bool SerialLink::_connect()
418{
419 return QMetaObject::invokeMethod(_worker, "connectToPort", Qt::QueuedConnection);
420}
421
423{
424 if (isConnected()) {
425 (void) QMetaObject::invokeMethod(_worker, "disconnectFromPort", Qt::QueuedConnection);
426 }
427}
428
429void SerialLink::_onConnected()
430{
431 _disconnectedEmitted = false;
432 emit connected();
433}
434
435void SerialLink::_onDisconnected()
436{
437 if (!_disconnectedEmitted.exchange(true)) {
438 emit disconnected();
439 }
440}
441
442void SerialLink::_onErrorOccurred(const QString &errorString)
443{
444 qCWarning(SerialLinkLog) << "Communication error:" << errorString;
445 emit communicationError(tr("Serial Link Error"), tr("Link %1: (Port: %2) %3").arg(_serialConfig->name(), _serialConfig->portName(), errorString));
446}
447
448void SerialLink::_onDataReceived(const QByteArray &data)
449{
450 emit bytesReceived(this, data);
451}
452
453void SerialLink::_onDataSent(const QByteArray &data)
454{
455 emit bytesSent(this, data);
456}
457
458void SerialLink::_writeBytes(const QByteArray &data)
459{
460 (void) QMetaObject::invokeMethod(_worker, "writeData", Qt::QueuedConnection, Q_ARG(QByteArray, data));
461}
Config config
std::shared_ptr< LinkConfiguration > SharedLinkConfigurationPtr
QString errorString
Error error
#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()
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
bool setRequestToSend(bool set)
Parity
This enum describes the parity scheme used.
Definition qserialport.h:69
void clearError()
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.
@ UnsupportedOperationError
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:72
QSerialPort::FlowControl flowControl() const
Definition SerialLink.h:65
bool sikRadio() const
Port was identified as a SiK radio by USB VID/PID at auto-connect. Runtime only, not persisted.
Definition SerialLink.h:84
void loadSettings(QSettings &settings, const QString &root) override
Definition SerialLink.cc:78
QSerialPort::StopBits stopBits() const
Definition SerialLink.h:68
void saveSettings(QSettings &settings, const QString &root) const override
Definition SerialLink.cc:96
SerialConnectionSettings connectionSettings() const
void setStopBits(QSerialPort::StopBits stopBits)
Definition SerialLink.h:69
void setPortDisplayName(const QString &portDisplayName)
Definition SerialLink.h:78
qint32 baud() const
Definition SerialLink.h:59
void setBaud(qint32 baud)
Definition SerialLink.h:60
SerialConfiguration(const QString &name, QObject *parent=nullptr)
Definition SerialLink.cc:17
void setDataBits(QSerialPort::DataBits databits)
Definition SerialLink.h:63
void setdtrForceLow(bool dtrForceLow)
Definition SerialLink.h:88
bool usbDirect() const
Definition SerialLink.h:80
static QStringList supportedBaudRates()
QString portName() const
Definition SerialLink.h:74
QString portDisplayName() const
Definition SerialLink.h:77
QSerialPort::Parity parity() const
Definition SerialLink.h:71
void setUsbDirect(bool usbDirect)
Definition SerialLink.h:81
QSerialPort::DataBits dataBits() const
Definition SerialLink.h:62
bool dtrForceLow() const
Definition SerialLink.h:87
void copyFrom(const LinkConfiguration *source) override
Definition SerialLink.cc:56
static QString cleanPortDisplayName(const QString &name)
void setFlowControl(QSerialPort::FlowControl flowControl)
Definition SerialLink.h:66
void setSikRadio(bool sikRadio)
Definition SerialLink.h:85
void setPortName(const QString &name)
Definition SerialLink.cc:36
virtual ~SerialConfiguration()
Definition SerialLink.cc:31
static SerialPortManager * instance()
std::shared_ptr< const Reservation > ReservationPtr
static QStringList supportedBaudRates()
QList< Port > availablePorts()
QString displayName(const QString &systemLocation)
void portsEnumerated(const QStringList &availablePorts)
Emitted only after a fresh scan, never for a retained Android snapshot. Includes native names and pat...
void connectToPort()
void dataReceived(const QByteArray &data)
void disconnectFromPort()
void connected()
void checkPortAvailability(const QStringList &availablePorts)
void dataSent(const QByteArray &data)
void disconnected()
void writeData(const QByteArray &data)
void setupPort()
SerialWorker(SerialConnectionSettings settings, SerialPortManager::ReservationPtr reservation={}, QObject *parent=nullptr)
void errorOccurred(const QString &errorString)
bool isConnected() const
Captured on the application thread; changes take effect on the next connection.
Definition SerialLink.h:20
QSerialPort::StopBits stopBits
Definition SerialLink.h:25
QSerialPort::DataBits dataBits
Definition SerialLink.h:23
QSerialPort::FlowControl flowControl
Definition SerialLink.h:24
QSerialPort::Parity parity
Definition SerialLink.h:26