QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
TCPLink.cc
Go to the documentation of this file.
1#include "TCPLink.h"
3#include "QGCNetworkHelper.h"
4
5#include <QtCore/QThread>
6#include <QtCore/QTimer>
7#include <QtNetwork/QHostInfo>
8#include <QtNetwork/QTcpSocket>
9
10QGC_LOGGING_CATEGORY(TCPLinkLog, "Comms.TCPLink")
11
12namespace {
13 constexpr int CONNECT_TIMEOUT_MS = 3000;
14 constexpr int TYPE_OF_SERVICE = 32; // Set ToS to priority for low delay
15}
16
17/*===========================================================================*/
18
19TCPConfiguration::TCPConfiguration(const QString &name, QObject *parent)
20 : LinkConfiguration(name, parent)
21{
22 qCDebug(TCPLinkLog) << this;
23}
24
26 : LinkConfiguration(copy, parent)
27 , _host(copy->host())
28 , _port(copy->port())
29{
30 qCDebug(TCPLinkLog) << this;
31}
32
34{
35 qCDebug(TCPLinkLog) << this;
36}
37
38void TCPConfiguration::setHost(const QString &host)
39{
40 const QString cleanHost = host.trimmed();
41 if (cleanHost != _host) {
42 _host = cleanHost;
43 emit hostChanged();
44 }
45}
46
47void TCPConfiguration::setPort(quint16 port)
48{
49 if (port != _port) {
50 _port = port;
51 emit portChanged();
52 }
53}
54
56{
57 const TCPConfiguration* tcpSource = qobject_cast<const TCPConfiguration*>(source);
58 if (!tcpSource) {
59 qCWarning(TCPLinkLog) << "Invalid source configuration type";
60 return;
61 }
62
64
65 setHost(tcpSource->host());
66 setPort(tcpSource->port());
67}
68
69void TCPConfiguration::loadSettings(QSettings &settings, const QString &root)
70{
71 settings.beginGroup(root);
72
73 setHost(settings.value(QStringLiteral("host"), host()).toString());
74 setPort(static_cast<quint16>(settings.value(QStringLiteral("port"), port()).toUInt()));
75
76 settings.endGroup();
77}
78
79void TCPConfiguration::saveSettings(QSettings &settings, const QString &root) const
80{
81 settings.beginGroup(root);
82
83 settings.setValue(QStringLiteral("host"), host());
84 settings.setValue(QStringLiteral("port"), port());
85
86 settings.endGroup();
87}
88
89/*===========================================================================*/
90
92 : QObject(parent)
93 , _config(config)
94{
95 qCDebug(TCPLinkLog) << this;
96}
97
99{
101
102 qCDebug(TCPLinkLog) << this;
103}
104
106{
107 // Called cross-thread from TCPLink; must not touch the thread-affine _socket
108 return _isConnected;
109}
110
112{
113 if (!_socket) {
114 _socket = new QTcpSocket(this);
115 }
116
117 _socket->setSocketOption(QAbstractSocket::LowDelayOption, 1);
118 _socket->setSocketOption(QAbstractSocket::KeepAliveOption, 1);
119 _socket->setSocketOption(QAbstractSocket::TypeOfServiceOption, TYPE_OF_SERVICE);
120
121 (void) connect(_socket, &QTcpSocket::connected, this, &TCPWorker::_onSocketConnected);
122 (void) connect(_socket, &QTcpSocket::disconnected, this, &TCPWorker::_onSocketDisconnected);
123 (void) connect(_socket, &QTcpSocket::readyRead, this, &TCPWorker::_onSocketReadyRead);
124 (void) connect(_socket, &QTcpSocket::errorOccurred, this, &TCPWorker::_onSocketErrorOccurred);
125
126 if (TCPLinkLog().isDebugEnabled()) {
127 // (void) connect(_socket, &QTcpSocket::bytesWritten, this, &TCPWorker::_onSocketBytesWritten);
128
129 (void) connect(_socket, &QTcpSocket::stateChanged, this, [](QTcpSocket::SocketState state) {
130 qCDebug(TCPLinkLog) << "TCP State Changed:" << state;
131 });
132
133 (void) connect(_socket, &QTcpSocket::hostFound, this, [this]() {
134 qCDebug(TCPLinkLog) << "TCP Host Found" << _socket->peerName() << _socket->peerAddress() << _socket->peerPort();
135 });
136 }
137}
138
140{
141 if (isConnected()) {
142 qCWarning(TCPLinkLog) << "Already connected to" << _config->host() << ":" << _config->port();
143 return;
144 }
145
146 if (_config->host().isEmpty()) {
147 emit errorOccurred(tr("Connection Failed: Host address is empty"));
148 return;
149 }
150
151 _errorEmitted = false;
152
153 qCDebug(TCPLinkLog) << "Attempting to connect to host:" << _config->host() << "port:" << _config->port();
154 _socket->connectToHost(_config->host(), _config->port());
155
156 if (!_socket->waitForConnected(CONNECT_TIMEOUT_MS)) {
157 qCWarning(TCPLinkLog) << "Connection to" << _config->host() << ":" << _config->port() << "failed:" << _socket->errorString();
158
159 if (!_errorEmitted.exchange(true)) {
160 emit errorOccurred(tr("Connection Failed: %1").arg(_socket->errorString()));
161 }
162
163 _onSocketDisconnected();
164 } else {
165 qCDebug(TCPLinkLog) << "Successfully connected to host:" << _config->host() << "port:" << _config->port();
166 }
167}
168
170{
171 if (!isConnected()) {
172 qCDebug(TCPLinkLog) << "Already disconnected from host:" << _config->host() << "port:" << _config->port();
173 return;
174 }
175
176 qCDebug(TCPLinkLog) << "Attempting to disconnect from host:" << _config->host() << "port:" << _config->port();
177
178 _socket->disconnectFromHost();
179
180 if (_socket->state() != QAbstractSocket::UnconnectedState) {
181 _socket->waitForDisconnected(1000);
182 }
183}
184
185void TCPWorker::writeData(const QByteArray &data)
186{
187 if (data.isEmpty()) {
188 emit errorOccurred(tr("Data to Send is Empty"));
189 return;
190 }
191
192 if (!isConnected()) {
193 emit errorOccurred(tr("Socket is not connected"));
194 return;
195 }
196
197 qint64 totalBytesWritten = 0;
198 while (totalBytesWritten < data.size()) {
199 const qint64 bytesWritten = _socket->write(data.constData() + totalBytesWritten, data.size() - totalBytesWritten);
200 if (bytesWritten == -1) {
201 emit errorOccurred(tr("Could Not Send Data - Write Failed: %1").arg(_socket->errorString()));
202 return;
203 } else if (bytesWritten == 0) {
204 emit errorOccurred(tr("Could Not Send Data - Write Returned 0 Bytes"));
205 return;
206 }
207 totalBytesWritten += bytesWritten;
208 }
209
210 emit dataSent(data.first(totalBytesWritten));
211}
212
213void TCPWorker::_onSocketConnected()
214{
215 qCDebug(TCPLinkLog) << "Socket connected:" << _config->host() << _config->port();
216 _isConnected = true;
217 _errorEmitted = false;
218 emit connected();
219}
220
221void TCPWorker::_onSocketDisconnected()
222{
223 qCDebug(TCPLinkLog) << "Socket disconnected:" << _config->host() << _config->port();
224 _isConnected = false;
225 _errorEmitted = false;
226 emit disconnected();
227}
228
229void TCPWorker::_onSocketReadyRead()
230{
231 const QByteArray data = _socket->readAll();
232 if (!data.isEmpty()) {
233 emit dataReceived(data);
234 }
235}
236
237void TCPWorker::_onSocketBytesWritten(qint64 bytes)
238{
239 qCDebug(TCPLinkLog) << _config->host() << "Wrote" << bytes << "bytes";
240}
241
242void TCPWorker::_onSocketErrorOccurred(QAbstractSocket::SocketError socketError)
243{
244 const QString errorString = _socket->errorString();
245 qCWarning(TCPLinkLog) << "Socket error:" << socketError << errorString;
246
247 if (!_errorEmitted.exchange(true)) {
249 }
250}
251
252/*===========================================================================*/
253
255 : LinkInterface(config, parent)
256 , _tcpConfig(qobject_cast<const TCPConfiguration*>(config.get()))
257 , _worker(new TCPWorker(_tcpConfig))
258 , _workerThread(new QThread(this))
259{
260 qCDebug(TCPLinkLog) << this;
261
262 _workerThread->setObjectName(QStringLiteral("TCP_%1").arg(_tcpConfig->name()));
263
264 _worker->moveToThread(_workerThread);
265
266 (void) connect(_workerThread, &QThread::started, _worker, &TCPWorker::setupSocket);
267 (void) connect(_workerThread, &QThread::finished, _worker, &QObject::deleteLater);
268
269 (void) connect(_worker, &TCPWorker::connected, this, &TCPLink::_onConnected, Qt::QueuedConnection);
270 (void) connect(_worker, &TCPWorker::disconnected, this, &TCPLink::_onDisconnected, Qt::QueuedConnection);
271 (void) connect(_worker, &TCPWorker::errorOccurred, this, &TCPLink::_onErrorOccurred, Qt::QueuedConnection);
272 (void) connect(_worker, &TCPWorker::dataReceived, this, &TCPLink::_onDataReceived, Qt::QueuedConnection);
273 (void) connect(_worker, &TCPWorker::dataSent, this, &TCPLink::_onDataSent, Qt::QueuedConnection);
274
275 _workerThread->start();
276}
277
279{
280 if (isConnected()) {
281 // Queued, not blocking: keeps a wedged worker from hanging the destructor before
282 // _shutdownWorkerThread can bound the wait. If quit() beats the queued call,
283 // ~TCPWorker still disconnects on thread finish.
284 (void) QMetaObject::invokeMethod(_worker, "disconnectFromHost", Qt::QueuedConnection);
285 _onDisconnected();
286 }
287
288 _shutdownWorkerThread(_workerThread, TCPLinkLog());
289
290 qCDebug(TCPLinkLog) << this;
291}
292
294{
295 return _worker && _worker->isConnected();
296}
297
298bool TCPLink::_connect()
299{
300 return QMetaObject::invokeMethod(_worker, "connectToHost", Qt::QueuedConnection);
301}
302
304{
305 if (isConnected()) {
306 (void) QMetaObject::invokeMethod(_worker, "disconnectFromHost", Qt::QueuedConnection);
307 }
308}
309
310void TCPLink::_onConnected()
311{
312 _disconnectedEmitted = false;
313 emit connected();
314}
315
316void TCPLink::_onDisconnected()
317{
318 if (!_disconnectedEmitted.exchange(true)) {
319 emit disconnected();
320 }
321}
322
323void TCPLink::_onErrorOccurred(const QString &errorString)
324{
325 qCWarning(TCPLinkLog) << "Communication error:" << errorString;
326 emit communicationError(tr("TCP Link Error"), tr("Link %1: (Host: %2 Port: %3) %4").arg(_tcpConfig->name(), _tcpConfig->host()).arg(_tcpConfig->port()).arg(errorString));
327}
328
329void TCPLink::_onDataReceived(const QByteArray &data)
330{
331 emit bytesReceived(this, data);
332}
333
334void TCPLink::_onDataSent(const QByteArray &data)
335{
336 emit bytesSent(this, data);
337}
338
339void TCPLink::_writeBytes(const QByteArray& bytes)
340{
341 (void) QMetaObject::invokeMethod(_worker, "writeData", Qt::QueuedConnection, Q_ARG(QByteArray, bytes));
342}
343
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)
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()
quint16 port() const
Definition TCPLink.h:38
~TCPConfiguration() override
Definition TCPLink.cc:33
void setPort(quint16 port)
Definition TCPLink.cc:47
void loadSettings(QSettings &settings, const QString &root) override
Definition TCPLink.cc:69
void setHost(const QString &host)
Definition TCPLink.cc:38
QString host() const
Definition TCPLink.h:36
void saveSettings(QSettings &settings, const QString &root) const override
Definition TCPLink.cc:79
TCPConfiguration(const QString &name, QObject *parent=nullptr)
Definition TCPLink.cc:19
void copyFrom(const LinkConfiguration *source) override
Definition TCPLink.cc:55
void setupSocket()
Definition TCPLink.cc:111
bool isConnected() const
Definition TCPLink.cc:105
~TCPWorker() override
Definition TCPLink.cc:98
void disconnectFromHost()
Definition TCPLink.cc:169
void errorOccurred(const QString &errorString)
void connectToHost()
Definition TCPLink.cc:139
void dataReceived(const QByteArray &data)
void dataSent(const QByteArray &data)
void writeData(const QByteArray &data)
Definition TCPLink.cc:185
TCPWorker(const TCPConfiguration *config, QObject *parent=nullptr)
Definition TCPLink.cc:91
void connected()
void disconnected()
bool isNetworkEthernet()
Check if current network connection is Ethernet.