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