You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
344 lines
8.5 KiB
344 lines
8.5 KiB
/**************************************************************************** |
|
* |
|
* (c) 2009-2020 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org> |
|
* |
|
* QGroundControl is licensed according to the terms in the file |
|
* COPYING.md in the root of the source code directory. |
|
* |
|
****************************************************************************/ |
|
|
|
|
|
#include <QTimer> |
|
#include <QList> |
|
#include <QDebug> |
|
#include <QMutexLocker> |
|
#include <iostream> |
|
#include "TCPLink.h" |
|
#include "LinkManager.h" |
|
#include "QGC.h" |
|
#include <QHostInfo> |
|
#include <QSignalSpy> |
|
|
|
/// @file |
|
/// @brief TCP link type for SITL support |
|
/// |
|
/// @author Don Gagne <don@thegagnes.com> |
|
|
|
TCPLink::TCPLink(SharedLinkConfigurationPointer& config) |
|
: LinkInterface(config) |
|
, _tcpConfig(qobject_cast<TCPConfiguration*>(config.data())) |
|
, _socket(nullptr) |
|
, _socketIsConnected(false) |
|
{ |
|
Q_ASSERT(_tcpConfig); |
|
moveToThread(this); |
|
} |
|
|
|
TCPLink::~TCPLink() |
|
{ |
|
_disconnect(); |
|
// Tell the thread to exit |
|
quit(); |
|
// Wait for it to exit |
|
wait(); |
|
} |
|
|
|
void TCPLink::run() |
|
{ |
|
_hardwareConnect(); |
|
exec(); |
|
} |
|
|
|
#ifdef TCPLINK_READWRITE_DEBUG |
|
void TCPLink::_writeDebugBytes(const QByteArray data) |
|
{ |
|
QString bytes; |
|
QString ascii; |
|
for (int i=0, size = data.size(); i<size; i++) |
|
{ |
|
unsigned char v = data[i]; |
|
bytes.append(QString::asprintf("%02x ", v)); |
|
if (data[i] > 31 && data[i] < 127) |
|
{ |
|
ascii.append(data[i]); |
|
} |
|
else |
|
{ |
|
ascii.append(219); |
|
} |
|
} |
|
qDebug() << "Sent" << size << "bytes to" << _tcpConfig->address().toString() << ":" << _tcpConfig->port() << "data:"; |
|
qDebug() << bytes; |
|
qDebug() << "ASCII:" << ascii; |
|
} |
|
#endif |
|
|
|
void TCPLink::_writeBytes(const QByteArray data) |
|
{ |
|
#ifdef TCPLINK_READWRITE_DEBUG |
|
_writeDebugBytes(data); |
|
#endif |
|
|
|
if (_socket) { |
|
_socket->write(data); |
|
emit bytesSent(this, data); |
|
_logOutputDataRate(data.size(), QDateTime::currentMSecsSinceEpoch()); |
|
} |
|
} |
|
|
|
/** |
|
* @brief Read a number of bytes from the interface. |
|
* |
|
* @param data Pointer to the data byte array to write the bytes to |
|
* @param maxLength The maximum number of bytes to write |
|
**/ |
|
void TCPLink::readBytes() |
|
{ |
|
if (_socket) { |
|
qint64 byteCount = _socket->bytesAvailable(); |
|
if (byteCount) |
|
{ |
|
QByteArray buffer; |
|
buffer.resize(byteCount); |
|
_socket->read(buffer.data(), buffer.size()); |
|
emit bytesReceived(this, buffer); |
|
_logInputDataRate(byteCount, QDateTime::currentMSecsSinceEpoch()); |
|
#ifdef TCPLINK_READWRITE_DEBUG |
|
writeDebugBytes(buffer.data(), buffer.size()); |
|
#endif |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* @brief Disconnect the connection. |
|
* |
|
* @return True if connection has been disconnected, false if connection couldn't be disconnected. |
|
**/ |
|
void TCPLink::_disconnect(void) |
|
{ |
|
quit(); |
|
wait(); |
|
if (_socket) { |
|
_socketIsConnected = false; |
|
_socket->disconnectFromHost(); // Disconnect tcp |
|
_socket->waitForDisconnected(); |
|
_socket->deleteLater(); // Make sure delete happens on correct thread |
|
_socket = nullptr; |
|
emit disconnected(); |
|
} |
|
} |
|
|
|
/** |
|
* @brief Connect the connection. |
|
* |
|
* @return True if connection has been established, false if connection couldn't be established. |
|
**/ |
|
bool TCPLink::_connect(void) |
|
{ |
|
if (isRunning()) |
|
{ |
|
quit(); |
|
wait(); |
|
} |
|
start(HighPriority); |
|
return true; |
|
} |
|
|
|
bool TCPLink::_hardwareConnect() |
|
{ |
|
Q_ASSERT(_socket == nullptr); |
|
_socket = new QTcpSocket(); |
|
|
|
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) |
|
QSignalSpy errorSpy(_socket, static_cast<void (QTcpSocket::*)(QAbstractSocket::SocketError)>(&QTcpSocket::error)); |
|
#else |
|
QSignalSpy errorSpy(_socket, &QAbstractSocket::errorOccurred); |
|
#endif |
|
|
|
_socket->connectToHost(_tcpConfig->address(), _tcpConfig->port()); |
|
QObject::connect(_socket, &QTcpSocket::readyRead, this, &TCPLink::readBytes); |
|
|
|
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) |
|
QObject::connect(_socket,static_cast<void (QTcpSocket::*)(QAbstractSocket::SocketError)>(&QTcpSocket::error), |
|
this, &TCPLink::_socketError); |
|
#else |
|
QObject::connect(_socket, &QAbstractSocket::errorOccurred, this, &TCPLink::_socketError); |
|
#endif |
|
|
|
// Give the socket a second to connect to the other side otherwise error out |
|
if (!_socket->waitForConnected(1000)) |
|
{ |
|
// Whether a failed connection emits an error signal or not is platform specific. |
|
// So in cases where it is not emitted, we emit one ourselves. |
|
if (errorSpy.count() == 0) { |
|
emit communicationError(tr("Link Error"), tr("Error on link %1. Connection failed").arg(getName())); |
|
} |
|
delete _socket; |
|
_socket = nullptr; |
|
return false; |
|
} |
|
_socketIsConnected = true; |
|
emit connected(); |
|
return true; |
|
} |
|
|
|
void TCPLink::_socketError(QAbstractSocket::SocketError socketError) |
|
{ |
|
Q_UNUSED(socketError); |
|
emit communicationError(tr("Link Error"), tr("Error on link %1. Error on socket: %2.").arg(getName()).arg(_socket->errorString())); |
|
} |
|
|
|
/** |
|
* @brief Check if connection is active. |
|
* |
|
* @return True if link is connected, false otherwise. |
|
**/ |
|
bool TCPLink::isConnected() const |
|
{ |
|
return _socketIsConnected; |
|
} |
|
|
|
QString TCPLink::getName() const |
|
{ |
|
return _tcpConfig->name(); |
|
} |
|
|
|
qint64 TCPLink::getConnectionSpeed() const |
|
{ |
|
return 54000000; // 54 Mbit |
|
} |
|
|
|
qint64 TCPLink::getCurrentInDataRate() const |
|
{ |
|
return 0; |
|
} |
|
|
|
qint64 TCPLink::getCurrentOutDataRate() const |
|
{ |
|
return 0; |
|
} |
|
|
|
void TCPLink::waitForBytesWritten(int msecs) |
|
{ |
|
Q_ASSERT(_socket); |
|
_socket->waitForBytesWritten(msecs); |
|
} |
|
|
|
void TCPLink::waitForReadyRead(int msecs) |
|
{ |
|
Q_ASSERT(_socket); |
|
_socket->waitForReadyRead(msecs); |
|
} |
|
|
|
void TCPLink::_restartConnection() |
|
{ |
|
if(this->isConnected()) |
|
{ |
|
_disconnect(); |
|
_connect(); |
|
} |
|
} |
|
|
|
//-------------------------------------------------------------------------- |
|
//-- TCPConfiguration |
|
|
|
static bool is_ip(const QString& address) |
|
{ |
|
int a,b,c,d; |
|
if (sscanf(address.toStdString().c_str(), "%d.%d.%d.%d", &a, &b, &c, &d) != 4 |
|
&& strcmp("::1", address.toStdString().c_str())) { |
|
return false; |
|
} else { |
|
return true; |
|
} |
|
} |
|
|
|
static QString get_ip_address(const QString& address) |
|
{ |
|
if(is_ip(address)) |
|
return address; |
|
// Need to look it up |
|
QHostInfo info = QHostInfo::fromName(address); |
|
if (info.error() == QHostInfo::NoError) |
|
{ |
|
QList<QHostAddress> hostAddresses = info.addresses(); |
|
for (int i = 0; i < hostAddresses.size(); i++) |
|
{ |
|
// Exclude all IPv6 addresses |
|
if (!hostAddresses.at(i).toString().contains(":")) |
|
{ |
|
return hostAddresses.at(i).toString(); |
|
} |
|
} |
|
} |
|
return {}; |
|
} |
|
|
|
TCPConfiguration::TCPConfiguration(const QString& name) : LinkConfiguration(name) |
|
{ |
|
_port = QGC_TCP_PORT; |
|
_address = QHostAddress::Any; |
|
} |
|
|
|
TCPConfiguration::TCPConfiguration(TCPConfiguration* source) : LinkConfiguration(source) |
|
{ |
|
_port = source->port(); |
|
_address = source->address(); |
|
} |
|
|
|
void TCPConfiguration::copyFrom(LinkConfiguration *source) |
|
{ |
|
LinkConfiguration::copyFrom(source); |
|
auto* usource = qobject_cast<TCPConfiguration*>(source); |
|
Q_ASSERT(usource != nullptr); |
|
_port = usource->port(); |
|
_address = usource->address(); |
|
} |
|
|
|
void TCPConfiguration::setPort(quint16 port) |
|
{ |
|
_port = port; |
|
} |
|
|
|
void TCPConfiguration::setAddress(const QHostAddress& address) |
|
{ |
|
_address = address; |
|
} |
|
|
|
void TCPConfiguration::setHost(const QString host) |
|
{ |
|
QString ipAdd = get_ip_address(host); |
|
if(ipAdd.isEmpty()) { |
|
qWarning() << "TCP:" << "Could not resolve host:" << host; |
|
} else { |
|
_address = QHostAddress(ipAdd); |
|
} |
|
} |
|
|
|
void TCPConfiguration::saveSettings(QSettings& settings, const QString& root) |
|
{ |
|
settings.beginGroup(root); |
|
settings.setValue("port", (int)_port); |
|
settings.setValue("host", address().toString()); |
|
settings.endGroup(); |
|
} |
|
|
|
void TCPConfiguration::loadSettings(QSettings& settings, const QString& root) |
|
{ |
|
settings.beginGroup(root); |
|
_port = (quint16)settings.value("port", QGC_TCP_PORT).toUInt(); |
|
QString address = settings.value("host", _address.toString()).toString(); |
|
_address = QHostAddress(address); |
|
settings.endGroup(); |
|
} |
|
|
|
void TCPConfiguration::updateSettings() |
|
{ |
|
if(_link) { |
|
auto* ulink = qobject_cast<TCPLink*>(_link); |
|
if(ulink) { |
|
ulink->_restartConnection(); |
|
} |
|
} |
|
}
|
|
|