diff --git a/custom-example/qgroundcontrol.qrc b/custom-example/qgroundcontrol.qrc index 0f249c9d16d..f93644c9a20 100644 --- a/custom-example/qgroundcontrol.qrc +++ b/custom-example/qgroundcontrol.qrc @@ -31,7 +31,7 @@ ../src/FlightMap/MapItems/MapLineArrow.qml ../src/FlightMap/MapItems/SplitIndicator.qml ../src/UI/preferences/ADSBServerSettings.qml - ../src/AirLink/AirLinkSettings.qml + ../src/Comms/AirLink/AirLinkSettings.qml ../src/AnalyzeView/AnalyzeView.qml ../src/UI/AppSettings.qml ../src/UI/preferences/BluetoothSettings.qml diff --git a/qgroundcontrol.qrc b/qgroundcontrol.qrc index 61516930996..a8a9488e16d 100644 --- a/qgroundcontrol.qrc +++ b/qgroundcontrol.qrc @@ -31,7 +31,7 @@ src/FlightMap/MapItems/MapLineArrow.qml src/FlightMap/MapItems/SplitIndicator.qml src/UI/preferences/ADSBServerSettings.qml - src/AirLink/AirLinkSettings.qml + src/Comms/AirLink/AirLinkSettings.qml src/AnalyzeView/AnalyzeView.qml src/UI/AppSettings.qml src/UI/preferences/BluetoothSettings.qml diff --git a/src/AirLink/AirLinkManager.cc b/src/AirLink/AirLinkManager.cc deleted file mode 100644 index 76c38895810..00000000000 --- a/src/AirLink/AirLinkManager.cc +++ /dev/null @@ -1,111 +0,0 @@ -/**************************************************************************** - * - * (c) 2009-2024 QGROUNDCONTROL PROJECT - * - * QGroundControl is licensed according to the terms in the file - * COPYING.md in the root of the source code directory. - * - ****************************************************************************/ - -#include "AirLinkManager.h" -#include "SettingsManager.h" - -#include -#include -#include -#include -#include -#include - -const QString AirLinkManager::airlinkHost = "air-link.space"; - -AirLinkManager::AirLinkManager(QGCApplication* app, QGCToolbox* toolbox) - : QGCTool(app, toolbox) -{ -} - -AirLinkManager::~AirLinkManager() -{ -} - -void AirLinkManager::setToolbox(QGCToolbox* toolbox) -{ - QGCTool::setToolbox(toolbox); -} - -QStringList AirLinkManager::droneList() const -{ - return _vehiclesFromServer.keys(); -} - -void AirLinkManager::updateDroneList(const QString &login, const QString &pass) -{ - connectToAirLinkServer(login, pass); -} - -bool AirLinkManager::isOnline(const QString &drone) -{ - if (!_vehiclesFromServer.contains(drone)) { - return false; - } else { - return _vehiclesFromServer[drone]; - } -} - -void AirLinkManager::connectToAirLinkServer(const QString &login, const QString &pass) -{ - QNetworkAccessManager *mngr = new QNetworkAccessManager(this); - - const QUrl url("https://air-link.space/api/gs/getModems"); - QNetworkRequest request(url); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - - QJsonObject obj; - obj["login"] = login; - obj["password"] = pass; - QJsonDocument doc(obj); - QByteArray data = doc.toJson(); - - _reply = mngr->post(request, data); - - QObject::connect(_reply, &QNetworkReply::finished, [this](){ - _processReplyAirlinkServer(*_reply); - _reply->deleteLater(); - }); - - mngr = nullptr; - delete mngr; -} - -void AirLinkManager::updateCredentials(const QString &login, const QString &pass) -{ - _toolbox->settingsManager()->appSettings()->loginAirLink()->setRawValue(login); - _toolbox->settingsManager()->appSettings()->passAirLink()->setRawValue(pass); -} - -void AirLinkManager::_parseAnswer(const QByteArray &ba) -{ - _vehiclesFromServer.clear(); - for (const auto &arr : QJsonDocument::fromJson(ba)["modems"].toArray()) { - QString droneModem = arr.toObject()["name"].toString(); - bool isOnline = arr.toObject()["isOnline"].toBool(); - _vehiclesFromServer[droneModem] = isOnline; - } - emit droneListChanged(); -} - -void AirLinkManager::_processReplyAirlinkServer(QNetworkReply &reply) -{ - QByteArray ba = reply.readAll(); - - if (reply.error() == QNetworkReply::NoError) { - if (!QJsonDocument::fromJson(ba)["modems"].toArray().isEmpty()) { - _parseAnswer(ba); - } else { - qDebug() << "No airlink modems in answer"; - } - } else { - qDebug() << "Airlink auth - network error"; - } -} - diff --git a/src/AirLink/AirLinkManager.h b/src/AirLink/AirLinkManager.h deleted file mode 100644 index 0b18dcf1abf..00000000000 --- a/src/AirLink/AirLinkManager.h +++ /dev/null @@ -1,52 +0,0 @@ -/**************************************************************************** - * - * (c) 2009-2024 QGROUNDCONTROL PROJECT - * - * QGroundControl is licensed according to the terms in the file - * COPYING.md in the root of the source code directory. - * - ****************************************************************************/ - -#pragma once - -#include "QGCToolbox.h" - -#include - -class AppSettings; -class QGCApplication; -class LinkInterface; -class QNetworkReply; - -//----------------------------------------------------------------------------- -class AirLinkManager : public QGCTool -{ - Q_OBJECT - -public: - Q_PROPERTY(QStringList droneList READ droneList NOTIFY droneListChanged) - Q_INVOKABLE void updateDroneList(const QString &login, const QString &pass); - Q_INVOKABLE bool isOnline(const QString &drone); - Q_INVOKABLE void connectToAirLinkServer(const QString &login, const QString &pass); - Q_INVOKABLE void updateCredentials(const QString &login, const QString &pass); - - explicit AirLinkManager(QGCApplication* app, QGCToolbox* toolbox); - ~AirLinkManager() override; - - void setToolbox (QGCToolbox* toolbox) override; - QStringList droneList() const; - - static const QString airlinkHost; - static constexpr int airlinkPort = 10000; - -signals: - void droneListChanged(); - -private: - void _parseAnswer (const QByteArray &ba); - void _processReplyAirlinkServer (QNetworkReply &reply); - -private: - QMap _vehiclesFromServer; - QNetworkReply* _reply; -}; diff --git a/src/AirLink/AirlinkLink.cc b/src/AirLink/AirlinkLink.cc deleted file mode 100644 index 6d6ffff151f..00000000000 --- a/src/AirLink/AirlinkLink.cc +++ /dev/null @@ -1,200 +0,0 @@ -/**************************************************************************** - * - * (c) 2009-2024 QGROUNDCONTROL PROJECT - * - * QGroundControl is licensed according to the terms in the file - * COPYING.md in the root of the source code directory. - * - ****************************************************************************/ - -#include "AirlinkLink.h" -#include "AirLinkManager.h" -#include "QGCApplication.h" -#include "AppSettings.h" -#include "SettingsManager.h" -#include "MAVLinkProtocol.h" - -#include -#include -#include - - -AirlinkConfiguration::AirlinkConfiguration(const QString &name) : UDPConfiguration(name) -{ - -} - -AirlinkConfiguration::AirlinkConfiguration(AirlinkConfiguration *source) : UDPConfiguration(source) -{ - _copyFrom(source); -} - -AirlinkConfiguration::~AirlinkConfiguration() -{ -} - -void AirlinkConfiguration::setUsername(QString username) -{ - _username = username; -} - -void AirlinkConfiguration::setPassword(QString password) -{ - _password = password; -} - -void AirlinkConfiguration::setModemName(QString modemName) -{ - _modemName = modemName; -} - -void AirlinkConfiguration::loadSettings(QSettings &settings, const QString &root) -{ - AppSettings *appSettings = qgcApp()->toolbox()->settingsManager()->appSettings(); - settings.beginGroup(root); - _username = settings.value(_usernameSettingsKey, appSettings->loginAirLink()->rawValueString()).toString(); - _password = settings.value(_passwordSettingsKey, appSettings->passAirLink()->rawValueString()).toString(); - _modemName = settings.value(_modemNameSettingsKey).toString(); - settings.endGroup(); -} - -void AirlinkConfiguration::saveSettings(QSettings &settings, const QString &root) -{ - settings.beginGroup(root); - settings.setValue(_usernameSettingsKey, _username); - settings.setValue(_passwordSettingsKey, _password); - settings.setValue(_modemNameSettingsKey, _modemName); - settings.endGroup(); -} - -void AirlinkConfiguration::copyFrom(LinkConfiguration *source) -{ - LinkConfiguration::copyFrom(source); - auto* udpSource = qobject_cast(source); - if (udpSource) { - UDPConfiguration::copyFrom(source); - } - _copyFrom(source); -} - -void AirlinkConfiguration::_copyFrom(LinkConfiguration *source) -{ - auto* airlinkSource = qobject_cast(source); - if (airlinkSource) { - _username = airlinkSource->username(); - _password = airlinkSource->password(); - _modemName = airlinkSource->modemName(); - } else { - qWarning() << "Internal error: cannot read AirlinkConfiguration from given source"; - } -} - - -AirlinkLink::AirlinkLink(SharedLinkConfigurationPtr &config) : UDPLink(config) -{ - _configureUdpSettings(); -} - -AirlinkLink::~AirlinkLink() -{ -} - -// bool AirlinkLink::isConnected() const -// { -// return UDPLink::isConnected(); -// } - -void AirlinkLink::disconnect() -{ - _setConnectFlag(false); - UDPLink::disconnect(); -} - -// void AirlinkLink::run() -// { -// UDPLink::run(); -// } - -bool AirlinkLink::_connect() -{ - start(NormalPriority); - QTimer *pendingTimer = new QTimer; - connect(pendingTimer, &QTimer::timeout, [this, pendingTimer] { - pendingTimer->setInterval(3000); - if (_stillConnecting()) { - qDebug() << "Connecting..."; - _sendLoginMsgToAirLink(); - } else { - qDebug() << "Stopping..."; - pendingTimer->stop(); - pendingTimer->deleteLater(); - } - }); - MAVLinkProtocol *mavlink = qgcApp()->toolbox()->mavlinkProtocol(); - auto conn = std::make_shared(); - *conn = connect(mavlink, &MAVLinkProtocol::messageReceived, [this, conn] (LinkInterface* linkSrc, mavlink_message_t message) { - if (this != linkSrc || message.msgid != MAVLINK_MSG_ID_AIRLINK_AUTH_RESPONSE) { - return; - } - mavlink_airlink_auth_response_t responseMsg; - mavlink_msg_airlink_auth_response_decode(&message, &responseMsg); - int answer = responseMsg.resp_type; - if (answer != AIRLINK_AUTH_RESPONSE_TYPE::AIRLINK_AUTH_OK) { - qDebug() << "Airlink auth failed"; - return; - } - qDebug() << "Connected successfully"; - QObject::disconnect(*conn); - _setConnectFlag(false); - }); - _setConnectFlag(true); - pendingTimer->start(0); - return true; -} - -void AirlinkLink::_configureUdpSettings() -{ - quint16 availablePort = 14550; - QUdpSocket udpSocket; - while (!udpSocket.bind(QHostAddress::LocalHost, availablePort)) - availablePort++; - UDPConfiguration* udpConfig = dynamic_cast(UDPLink::m_config.get()); - udpConfig->addHost(AirLinkManager::airlinkHost, AirLinkManager::airlinkPort); - udpConfig->setLocalPort(availablePort); - udpConfig->setDynamic(false); -} - -void AirlinkLink::_sendLoginMsgToAirLink() -{ - __mavlink_airlink_auth_t auth; - uint8_t buffer[MAVLINK_MAX_PACKET_LEN]; - mavlink_message_t mavmsg; - AirlinkConfiguration* config = dynamic_cast(m_config.get()); - QString login = config->modemName(); ///< Connect not to account but to specific modem - QString pass = config->password(); - - memset(&auth.login, 0, sizeof(auth.login)); - memset(&auth.password, 0, sizeof(auth.password)); - strcpy(auth.login, login.toUtf8().constData()); - strcpy(auth.password, pass.toUtf8().constData()); - - mavlink_msg_airlink_auth_pack(0, 0, &mavmsg, auth.login, auth.password); - uint16_t len = mavlink_msg_to_send_buffer(buffer, &mavmsg); - if (!_stillConnecting()) { - qDebug() << "Force exit from connection"; - return; - } - writeBytesThreadSafe((const char *)buffer, len); -} - -bool AirlinkLink::_stillConnecting() -{ - QMutexLocker locker(&_mutex); - return _needToConnect; -} - -void AirlinkLink::_setConnectFlag(bool connect) -{ - QMutexLocker locker(&_mutex); - _needToConnect = connect; -} diff --git a/src/AirLink/AirlinkLink.h b/src/AirLink/AirlinkLink.h deleted file mode 100644 index 2291fbcedaa..00000000000 --- a/src/AirLink/AirlinkLink.h +++ /dev/null @@ -1,89 +0,0 @@ -/**************************************************************************** - * - * (c) 2009-2024 QGROUNDCONTROL PROJECT - * - * QGroundControl is licensed according to the terms in the file - * COPYING.md in the root of the source code directory. - * - ****************************************************************************/ - -#pragma once - -#include - -#include "UDPLink.h" - -class AirlinkConfiguration : public UDPConfiguration -{ - Q_OBJECT -public: - Q_PROPERTY(QString username READ username WRITE setUsername NOTIFY usernameChanged) - Q_PROPERTY(QString password READ password WRITE setPassword NOTIFY passwordChanged) - Q_PROPERTY(QString modemName READ modemName WRITE setModemName NOTIFY modemNameChanged) - - AirlinkConfiguration(const QString& name); - AirlinkConfiguration(AirlinkConfiguration* source); - ~AirlinkConfiguration(); - - QString username () const { return _username; } - QString password () const { return _password; } - QString modemName() const { return _modemName; } - - void setUsername (QString username); - void setPassword (QString password); - void setModemName (QString modemName); - - /// LinkConfiguration overrides - LinkType type (void) override { return LinkConfiguration::Airlink; } - void loadSettings (QSettings& settings, const QString& root) override; - void saveSettings (QSettings& settings, const QString& root) override; - QString settingsURL (void) override { return "AirLinkSettings.qml"; } - QString settingsTitle (void) override { return tr("Airlink Link Settings"); } - void copyFrom (LinkConfiguration* source) override; - - -signals: - void usernameChanged (void); - void passwordChanged (void); - void modemNameChanged (void); - -private: - void _copyFrom (LinkConfiguration *source); - - - QString _username; - QString _password; - QString _modemName; - - const QString _usernameSettingsKey = "username"; - const QString _passwordSettingsKey = "password"; - const QString _modemNameSettingsKey = "modemName"; -}; - -class AirlinkLink : public UDPLink -{ - Q_OBJECT -public: - AirlinkLink(SharedLinkConfigurationPtr& config); - virtual ~AirlinkLink(); - - /// LinkInterface overrides - // bool isConnected(void) const override; - void disconnect (void) override; - - /// QThread overrides - // void run(void) override; - -private: - /// LinkInterface overrides - bool _connect(void) override; - - void _configureUdpSettings(); - void _sendLoginMsgToAirLink(); - bool _stillConnecting(); - void _setConnectFlag(bool connect); - - QMutex _mutex; - /// Access this varible only with _mutex locked - bool _needToConnect {false}; -}; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 93b769f7eea..f712e3393db 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -23,7 +23,6 @@ if(QGC_UTM_ADAPTER) endif() add_subdirectory(ADSB) -add_subdirectory(AirLink) add_subdirectory(AnalyzeView) add_subdirectory(API) add_subdirectory(Audio) @@ -68,7 +67,6 @@ target_link_libraries(QGC Qt6::Bluetooth API ADSB - AirLink AnalyzeView Audio AutoPilotPlugins diff --git a/src/Comms/AirLink/AirLinkLink.cc b/src/Comms/AirLink/AirLinkLink.cc new file mode 100644 index 00000000000..c4a93891e8d --- /dev/null +++ b/src/Comms/AirLink/AirLinkLink.cc @@ -0,0 +1,216 @@ +/**************************************************************************** + * + * (c) 2009-2024 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#include "AirLinkLink.h" +#include "QGCApplication.h" +#include "AppSettings.h" +#include "SettingsManager.h" +#include "MAVLinkProtocol.h" +#include "QGCLoggingCategory.h" + +#include +#include +#include + +QGC_LOGGING_CATEGORY(AirLinkLinkLog, "qgc.AirLink.AirLinklink"); + +AirLinkLink::AirLinkLink(SharedLinkConfigurationPtr &config) + : UDPLink(config) + , _AirLinkConfig(qobject_cast(config.get())) +{ + // qCDebug(AirLinkLinkLog) << Q_FUNC_INFO << this; + + _configureUdpSettings(); +} + +AirLinkLink::~AirLinkLink() +{ + // qCDebug(AirLinkLinkLog) << Q_FUNC_INFO << this; +} + +void AirLinkLink::disconnect() +{ + _setConnectFlag(false); + UDPLink::disconnect(); +} + +bool AirLinkLink::_connect() +{ + start(NormalPriority); + + QTimer *const pendingTimer = new QTimer(this); + (void) connect(pendingTimer, &QTimer::timeout, this, [this, pendingTimer] { + pendingTimer->setInterval(3000); + if (_stillConnecting()) { + qCDebug(AirLinkLinkLog) << "Connecting..."; + _sendLoginMsgToAirLink(); + } else { + qCDebug(AirLinkLinkLog) << "Stopping..."; + pendingTimer->stop(); + pendingTimer->deleteLater(); + } + }); + + MAVLinkProtocol *const mavlink = qgcApp()->toolbox()->mavlinkProtocol(); + auto conn = std::make_shared(); + *conn = connect(mavlink, &MAVLinkProtocol::messageReceived, this, [this, conn] (LinkInterface* linkSrc, mavlink_message_t message) { + if (this != linkSrc || message.msgid != MAVLINK_MSG_ID_AIRLINK_AUTH_RESPONSE) { + return; + } + + mavlink_airlink_auth_response_t responseMsg; + mavlink_msg_airlink_auth_response_decode(&message, &responseMsg); + const int answer = responseMsg.resp_type; + if (answer != AIRLINK_AUTH_RESPONSE_TYPE::AIRLINK_AUTH_OK) { + qCDebug(AirLinkLinkLog) << "AirLink auth failed"; + return; + } + + qCDebug(AirLinkLinkLog) << "Connected successfully"; + QObject::disconnect(*conn); + _setConnectFlag(false); + }); + + _setConnectFlag(true); + pendingTimer->start(0); + + return true; +} + +void AirLinkLink::_configureUdpSettings() +{ + quint16 availablePort = 14550; + QUdpSocket udpSocket; + while (!udpSocket.bind(QHostAddress::LocalHost, availablePort)) { + availablePort++; + } + + UDPConfiguration *const udpConfig = dynamic_cast(UDPLink::_config.get()); + udpConfig->addHost(_airLinkHost, _airLinkPort); + udpConfig->setLocalPort(availablePort); + udpConfig->setDynamic(false); +} + +void AirLinkLink::_sendLoginMsgToAirLink() +{ + mavlink_airlink_auth_t auth; + uint8_t buffer[MAVLINK_MAX_PACKET_LEN]; + mavlink_message_t mavmsg; + const QString login = _AirLinkConfig->modemName(); ///< Connect not to account but to specific modem + const QString pass = _AirLinkConfig->password(); + + memset(&auth.login, 0, sizeof(auth.login)); + memset(&auth.password, 0, sizeof(auth.password)); + strcpy(auth.login, login.toUtf8().constData()); + strcpy(auth.password, pass.toUtf8().constData()); + + (void) mavlink_msg_airlink_auth_pack(0, 0, &mavmsg, auth.login, auth.password); + const uint16_t len = mavlink_msg_to_send_buffer(buffer, &mavmsg); + if (!_stillConnecting()) { + qCDebug(AirLinkLinkLog) << "Force exit from connection"; + return; + } + + writeBytesThreadSafe((const char *)buffer, len); +} + +bool AirLinkLink::_stillConnecting() +{ + QMutexLocker locker(&_mutex); + return _needToConnect; +} + +void AirLinkLink::_setConnectFlag(bool connect) +{ + QMutexLocker locker(&_mutex); + _needToConnect = connect; +} + +AirLinkConfiguration::AirLinkConfiguration(const QString &name) + : UDPConfiguration(name) +{ + // qCDebug(AirLinkLinkLog) << Q_FUNC_INFO << this; +} + +AirLinkConfiguration::AirLinkConfiguration(const AirLinkConfiguration *source) + : UDPConfiguration(source) +{ + // qCDebug(AirLinkLinkLog) << Q_FUNC_INFO << this; + + _copyFrom(source); +} + +AirLinkConfiguration::~AirLinkConfiguration() +{ + // qCDebug(AirLinkLinkLog) << Q_FUNC_INFO << this; +} + +void AirLinkConfiguration::setUsername(const QString &username) +{ + if (username != _username) { + _username = username; + emit usernameChanged(); + } +} + +void AirLinkConfiguration::setPassword(const QString &password) +{ + if (password != _password) { + _password = password; + emit passwordChanged(); + } +} + +void AirLinkConfiguration::setModemName(const QString &modemName) +{ + if (modemName != _modemName) { + _modemName = modemName; + modemNameChanged(); + } +} + +void AirLinkConfiguration::loadSettings(QSettings &settings, const QString &root) +{ + AppSettings *const appSettings = qgcApp()->toolbox()->settingsManager()->appSettings(); + settings.beginGroup(root); + _username = settings.value(_usernameSettingsKey, appSettings->loginAirLink()->rawValueString()).toString(); + _password = settings.value(_passwordSettingsKey, appSettings->passAirLink()->rawValueString()).toString(); + _modemName = settings.value(_modemNameSettingsKey).toString(); + settings.endGroup(); +} + +void AirLinkConfiguration::saveSettings(QSettings &settings, const QString &root) +{ + settings.beginGroup(root); + settings.setValue(_usernameSettingsKey, _username); + settings.setValue(_passwordSettingsKey, _password); + settings.setValue(_modemNameSettingsKey, _modemName); + settings.endGroup(); +} + +void AirLinkConfiguration::copyFrom(const LinkConfiguration *source) +{ + const UDPConfiguration *const udpSource = qobject_cast(source); + if (udpSource) { + UDPConfiguration::copyFrom(source); + } + _copyFrom(source); +} + +void AirLinkConfiguration::_copyFrom(const LinkConfiguration *source) +{ + const AirLinkConfiguration *const AirLinkSource = qobject_cast(source); + if (AirLinkSource) { + _username = AirLinkSource->username(); + _password = AirLinkSource->password(); + _modemName = AirLinkSource->modemName(); + } else { + qCWarning(AirLinkLinkLog) << "Internal error: cannot read AirLinkConfiguration from given source"; + } +} diff --git a/src/Comms/AirLink/AirLinkLink.h b/src/Comms/AirLink/AirLinkLink.h new file mode 100644 index 00000000000..f5380132599 --- /dev/null +++ b/src/Comms/AirLink/AirLinkLink.h @@ -0,0 +1,87 @@ +/**************************************************************************** + * + * (c) 2009-2024 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#pragma once + +#include +#include + +#include "UDPLink.h" + +Q_DECLARE_LOGGING_CATEGORY(AirLinkLinkLog) + +class AirLinkConfiguration : public UDPConfiguration +{ + Q_OBJECT + + Q_PROPERTY(QString username READ username WRITE setUsername NOTIFY usernameChanged) + Q_PROPERTY(QString password READ password WRITE setPassword NOTIFY passwordChanged) + Q_PROPERTY(QString modemName READ modemName WRITE setModemName NOTIFY modemNameChanged) +public: + AirLinkConfiguration(const QString &name); + AirLinkConfiguration(const AirLinkConfiguration *source); + ~AirLinkConfiguration(); + + QString username() const { return _username; } + QString password() const { return _password; } + QString modemName() const { return _modemName; } + + void setUsername(const QString &username); + void setPassword(const QString &password); + void setModemName(const QString &modemName); + + LinkType type() const override { return LinkConfiguration::AirLink; } + void loadSettings(QSettings& settings, const QString& root) override; + void saveSettings(QSettings& settings, const QString& root) override; + QString settingsURL() override { return "AirLinkSettings.qml"; } + QString settingsTitle() override { return tr("AirLink Link Settings"); } + void copyFrom(const LinkConfiguration *source) override; + +signals: + void usernameChanged(); + void passwordChanged(); + void modemNameChanged(); + +private: + void _copyFrom(const LinkConfiguration *source); + + QString _username; + QString _password; + QString _modemName; + + const QString _usernameSettingsKey = QStringLiteral("username"); + const QString _passwordSettingsKey = QStringLiteral("password"); + const QString _modemNameSettingsKey = QStringLiteral("modemName"); +}; + +class AirLinkLink : public UDPLink +{ + Q_OBJECT + +public: + AirLinkLink(SharedLinkConfigurationPtr &config); + ~AirLinkLink(); + + void disconnect() override; + +private: + bool _connect() override; + + void _configureUdpSettings(); + void _sendLoginMsgToAirLink(); + bool _stillConnecting(); + void _setConnectFlag(bool connect); + + const AirLinkConfiguration *_AirLinkConfig = nullptr; + QMutex _mutex; + bool _needToConnect = false; + + static constexpr const char *_airLinkHost = "air-link.space"; + static constexpr int _airLinkPort = 10000; +}; diff --git a/src/Comms/AirLink/AirLinkManager.cc b/src/Comms/AirLink/AirLinkManager.cc new file mode 100644 index 00000000000..2ca336791e8 --- /dev/null +++ b/src/Comms/AirLink/AirLinkManager.cc @@ -0,0 +1,107 @@ +/**************************************************************************** + * + * (c) 2009-2024 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#include "AirLinkManager.h" +#include "QGCApplication.h" +#include "QGCToolbox.h" +#include "SettingsManager.h" +#include "QGCLoggingCategory.h" + +#include +#include +#include +#include +#include + +QGC_LOGGING_CATEGORY(AirLinkManagerLog, "qgc.airlink.airlinkmanager"); + +Q_APPLICATION_STATIC(AirLinkManager, _airLinkManager); + +AirLinkManager::AirLinkManager(QObject *parent) + : QObject(parent) + , _mngr(new QNetworkAccessManager(this)) +{ + // qCDebug(AirLinkManagerLog) << Q_FUNC_INFO << this; +} + +AirLinkManager::~AirLinkManager() +{ + // qCDebug(AirLinkManagerLog) << Q_FUNC_INFO << this; +} + +AirLinkManager *AirLinkManager::instance() +{ + return _airLinkManager(); +} + +bool AirLinkManager::isOnline(const QString &drone) +{ + if (!_vehiclesFromServer.contains(drone)) { + return false; + } else { + return _vehiclesFromServer[drone]; + } +} + +void AirLinkManager::updateCredentials(const QString &login, const QString &pass) +{ + qgcApp()->toolbox()->settingsManager()->appSettings()->loginAirLink()->setRawValue(login); + qgcApp()->toolbox()->settingsManager()->appSettings()->passAirLink()->setRawValue(pass); +} + +void AirLinkManager::_connectToAirLinkServer(const QString &login, const QString &pass) +{ + const QUrl url("https://air-link.space/api/gs/getModems"); + QNetworkRequest request(url); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + + QJsonObject obj; + obj["login"] = login; + obj["password"] = pass; + QJsonDocument doc(obj); + const QByteArray data = doc.toJson(); + + QNetworkReply *const reply = _mngr->post(request, data); + (void) QObject::connect(reply, &QNetworkReply::finished, this, &AirLinkManager::_processReplyAirlinkServer); +} + +void AirLinkManager::_processReplyAirlinkServer() +{ + QNetworkReply* const reply = qobject_cast(sender()); + if (!reply) { + return; + } + reply->deleteLater(); + + if (reply->error() != QNetworkReply::NoError) { + qCDebug(AirLinkManagerLog) << "Airlink auth - network error"; + return; + } + + const QByteArray ba = reply->readAll(); + + if (!QJsonDocument::fromJson(ba)["modems"].toArray().isEmpty()) { + _parseAnswer(ba); + } else { + qCDebug(AirLinkManagerLog) << "No airlink modems in answer"; + } +} + +void AirLinkManager::_parseAnswer(const QByteArray &ba) +{ + _vehiclesFromServer.clear(); + + for (const auto &arr : QJsonDocument::fromJson(ba)["modems"].toArray()) { + const QString droneModem = arr.toObject()["name"].toString(); + const bool isOnline = arr.toObject()["isOnline"].toBool(); + _vehiclesFromServer[droneModem] = isOnline; + } + + emit droneListChanged(); +} diff --git a/src/Comms/AirLink/AirLinkManager.h b/src/Comms/AirLink/AirLinkManager.h new file mode 100644 index 00000000000..b125de011d6 --- /dev/null +++ b/src/Comms/AirLink/AirLinkManager.h @@ -0,0 +1,55 @@ +/**************************************************************************** + * + * (c) 2009-2024 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#pragma once + +#include +#include +#include +#include + +class QNetworkAccessManager; + +Q_DECLARE_LOGGING_CATEGORY(AirLinkManagerLog) + +class AirLinkManager : public QObject +{ + Q_OBJECT + // QML_ELEMENT + // QML_SINGLETON + + Q_PROPERTY(QStringList droneList READ droneList NOTIFY droneListChanged) + +public: + explicit AirLinkManager(QObject *parent = nullptr); + ~AirLinkManager(); + + /// Gets the singleton instance of AirLinkManager. + /// @return The singleton instance. + static AirLinkManager *instance(); + + Q_INVOKABLE void updateDroneList(const QString &login, const QString &pass) { _connectToAirLinkServer(login, pass); } + Q_INVOKABLE bool isOnline(const QString &drone); + Q_INVOKABLE void updateCredentials(const QString &login, const QString &pass); + + QStringList droneList() const { return _vehiclesFromServer.keys(); } + +signals: + void droneListChanged(); + +private slots: + void _processReplyAirlinkServer(); + +private: + void _connectToAirLinkServer(const QString &login, const QString &pass); + void _parseAnswer(const QByteArray &ba); + + QNetworkAccessManager *_mngr = nullptr; + QMap _vehiclesFromServer; +}; diff --git a/src/AirLink/AirLinkSettings.qml b/src/Comms/AirLink/AirLinkSettings.qml similarity index 68% rename from src/AirLink/AirLinkSettings.qml rename to src/Comms/AirLink/AirLinkSettings.qml index 2b676f32e10..f660ef652c7 100644 --- a/src/AirLink/AirLinkSettings.qml +++ b/src/Comms/AirLink/AirLinkSettings.qml @@ -8,28 +8,23 @@ ****************************************************************************/ -import QtMultimedia import QtQuick import QtQuick.Controls -import QtQuick.Dialogs import QtQuick.Layouts -import QtLocation -import QtPositioning import QGroundControl import QGroundControl.Controllers import QGroundControl.Controls import QGroundControl.FactControls import QGroundControl.FactSystem -import QGroundControl.Palette import QGroundControl.ScreenTools import QGroundControl.SettingsManager ColumnLayout { spacing: _rowSpacing - property Fact _loginFact: QGroundControl.settingsManager.appSettings.loginAirLink - property Fact _passFact: QGroundControl.settingsManager.appSettings.passAirLink + property Fact _loginFact: QGroundControl.settingsManager.appSettings.loginAirLink + property Fact _passFact: QGroundControl.settingsManager.appSettings.passAirLink function saveSettings() { // No need @@ -44,10 +39,7 @@ ColumnLayout { columnSpacing: _colSpacing rowSpacing: _rowSpacing - QGCLabel { - text: qsTr("Login:") - } - + QGCLabel { text: qsTr("Login:") } QGCTextField { id: loginField text: _loginFact.rawValue @@ -56,11 +48,7 @@ ColumnLayout { onTextChanged: subEditConfig.username = loginField.text } - - QGCLabel { - text: qsTr("Password:") - } - + QGCLabel { text: qsTr("Password:") } QGCTextField { id: passwordField text: _passFact.rawValue @@ -70,40 +58,41 @@ ColumnLayout { onTextChanged: subEditConfig.password = passwordField.text } } + QGCLabel { text: "Forgot Your AirLink Password?" font.underline: true Layout.columnSpan: 2 MouseArea { - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: Qt.openUrlExternally("https://air-link.space/forgot-pass") + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: Qt.openUrlExternally("https://air-link.space/forgot-pass") } } RowLayout { spacing: _colSpacing + QGCLabel { - wrapMode: Text.WordWrap - text: qsTr("Don't have an account?") + wrapMode: Text.WordWrap + text: qsTr("Don't have an account?") } + QGCLabel { - font.underline: true - wrapMode: Text.WordWrap - text: qsTr("Register") + font.underline: true + wrapMode: Text.WordWrap + text: qsTr("Register") MouseArea { - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: Qt.openUrlExternally("https://air-link.space/registration") + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: Qt.openUrlExternally("https://air-link.space/registration") } } } - QGCLabel { - text: qsTr("List of available devices") - } + QGCLabel { text: qsTr("List of available devices") } RowLayout { QGCComboBox { @@ -113,6 +102,7 @@ ColumnLayout { subEditConfig.modemName = QGroundControl.airlinkManager.droneList[index] updateConnectionName(subEditConfig.modemName) } + Connections { target: QGroundControl.airlinkManager // model update does not trigger onActivated, so we catch first element manually @@ -124,8 +114,9 @@ ColumnLayout { } } } + QGCButton { - text: qsTr("Refresh") + text: qsTr("Refresh") onClicked: { QGroundControl.airlinkManager.updateDroneList(loginField.text, passwordField.text) refreshHint.visible = false @@ -135,10 +126,10 @@ ColumnLayout { } QGCLabel { - id: refreshHint - Layout.fillWidth: true - font.pointSize: ScreenTools.smallFontPointSize - wrapMode: Text.WordWrap - text: qsTr("Click \"Refresh\" to authorize") + id: refreshHint + Layout.fillWidth: true + font.pointSize: ScreenTools.smallFontPointSize + wrapMode: Text.WordWrap + text: qsTr("Click \"Refresh\" to authorize") } } diff --git a/src/AirLink/CMakeLists.txt b/src/Comms/AirLink/CMakeLists.txt similarity index 78% rename from src/AirLink/CMakeLists.txt rename to src/Comms/AirLink/CMakeLists.txt index d77a0e6a492..aee8e678825 100644 --- a/src/AirLink/CMakeLists.txt +++ b/src/Comms/AirLink/CMakeLists.txt @@ -4,29 +4,26 @@ qt_add_library(AirLink STATIC) option(QGC_AIRLINK_DISABLED "Enable airlink" ON) if(NOT QGC_AIRLINK_DISABLED) - find_package(Qt6 COMPONENTS Network REQUIRED) + find_package(Qt6 REQUIRED COMPONENTS Network QmlIntegration) target_sources(AirLink PRIVATE - AirlinkLink.cc - AirlinkLink.h + AirLinkLink.cc + AirLinkLink.h AirLinkManager.cc AirLinkManager.h ) - add_custom_target(AirLinkQml - SOURCES - AirLinkSettings.qml - ) - target_link_libraries(AirLink PRIVATE Qt6::Network + QGC Settings + Utilities PUBLIC Qt6::Core + Qt6::QmlIntegration Comms - QGC ) target_include_directories(AirLink PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) @@ -39,23 +36,21 @@ if(NOT QGC_AIRLINK_DISABLED) # OUTPUT_TARGETS AirLink_targets # IMPORT_PATH ${QT_QML_OUTPUT_DIRECTORY} # IMPORTS - # QtMultimedia # QtQuick # QtQuick.Controls - # QtQuick.Dialogs # QtQuick.Layouts - # QtLocation - # QtPositioning # QGroundControl # QGroundControl.Controllers # QGroundControl.Controls # QGroundControl.FactControls # QGroundControl.FactSystem - # QGroundControl.Palette # QGroundControl.ScreenTools # QGroundControl.SettingsManager # ) + + # cmake_print_variables(AirLink_targets) + # target_link_libraries(AirLink PRIVATE AirLinkplugin) else() target_compile_definitions(AirLink PUBLIC QGC_AIRLINK_DISABLED) endif() diff --git a/src/Comms/CMakeLists.txt b/src/Comms/CMakeLists.txt index 05da3a7f48a..643d3973fa3 100644 --- a/src/Comms/CMakeLists.txt +++ b/src/Comms/CMakeLists.txt @@ -1,3 +1,4 @@ +add_subdirectory(AirLink) add_subdirectory(MockLink) find_package(Qt6 REQUIRED COMPONENTS Core Network Qml Test Widgets) @@ -23,13 +24,13 @@ target_link_libraries(Comms PRIVATE Qt6::Qml Qt6::Test - AirLink MockLink Settings Vehicle PUBLIC Qt6::Core Qt6::Network + AirLink MAVLink QGC QmlControls diff --git a/src/Comms/LinkConfiguration.cc b/src/Comms/LinkConfiguration.cc index cfb4d46c62b..d65137c5951 100644 --- a/src/Comms/LinkConfiguration.cc +++ b/src/Comms/LinkConfiguration.cc @@ -21,7 +21,7 @@ #include "MockLink.h" #endif #ifndef QGC_AIRLINK_DISABLED -#include "AirlinkLink.h" +#include "AirLinkLink.h" #endif LinkConfiguration::LinkConfiguration(const QString &name, QObject *parent) @@ -90,8 +90,8 @@ LinkConfiguration *LinkConfiguration::createSettings(int type, const QString &na break; #endif #ifndef QGC_AIRLINK_DISABLED - case Airlink: - config = new AirlinkConfiguration(name); + case AirLink: + config = new AirLinkConfiguration(name); break; #endif case TypeLast: @@ -132,8 +132,8 @@ LinkConfiguration *LinkConfiguration::duplicateSettings(const LinkConfiguration break; #endif #ifndef QGC_AIRLINK_DISABLED - case Airlink: - dupe = new AirlinkConfiguration(qobject_cast(source)); + case AirLink: + dupe = new AirLinkConfiguration(qobject_cast(source)); break; #endif case TypeLast: diff --git a/src/Comms/LinkConfiguration.h b/src/Comms/LinkConfiguration.h index 0d644b6b65e..ef90c37e8e7 100644 --- a/src/Comms/LinkConfiguration.h +++ b/src/Comms/LinkConfiguration.h @@ -79,7 +79,7 @@ class LinkConfiguration : public QObject TypeMock, ///< Mock Link for Unitesting #endif #ifndef QGC_AIRLINK_DISABLED - Airlink, + AirLink, #endif TypeLogReplay, TypeLast // Last type value (type >= TypeLast == invalid) diff --git a/src/Comms/LinkManager.cc b/src/Comms/LinkManager.cc index 790913f68d1..9261faad172 100644 --- a/src/Comms/LinkManager.cc +++ b/src/Comms/LinkManager.cc @@ -36,7 +36,7 @@ #endif #ifndef QGC_AIRLINK_DISABLED -#include "AirlinkLink.h" +#include "AirLinkLink.h" #endif #ifdef QGC_ZEROCONF_ENABLED @@ -140,8 +140,8 @@ bool LinkManager::createConnectedLink(SharedLinkConfigurationPtr &config) break; #endif #ifndef QGC_AIRLINK_DISABLED - case LinkConfiguration::Airlink: - link = std::make_shared(config); + case LinkConfiguration::AirLink: + link = std::make_shared(config); break; #endif case LinkConfiguration::TypeLast: @@ -344,8 +344,8 @@ void LinkManager::loadLinkConfigurationList() break; #endif #ifndef QGC_AIRLINK_DISABLED - case LinkConfiguration::Airlink: - link = new AirlinkConfiguration(name); + case LinkConfiguration::AirLink: + link = new AirLinkConfiguration(name); break; #endif case LinkConfiguration::TypeLast: diff --git a/src/QGCToolbox.cc b/src/QGCToolbox.cc index 1614d88c802..63a67404b4e 100644 --- a/src/QGCToolbox.cc +++ b/src/QGCToolbox.cc @@ -19,18 +19,14 @@ #include "QGCCorePlugin.h" #include "SettingsManager.h" #include "QGCApplication.h" -#ifndef QGC_AIRLINK_DISABLED -#include "AirLinkManager.h" +#ifdef QGC_UTM_ADAPTER +#include "UTMSPManager.h" #endif #if defined(QGC_CUSTOM_BUILD) #include CUSTOMHEADER #endif -#ifdef QGC_UTM_ADAPTER -#include "UTMSPManager.h" -#endif - QGCToolbox::QGCToolbox(QGCApplication* app) : QObject(app) { @@ -46,11 +42,7 @@ QGCToolbox::QGCToolbox(QGCApplication* app) _multiVehicleManager = new MultiVehicleManager (app, this); _qgcPositionManager = new QGCPositionManager (app, this); _videoManager = new VideoManager (app, this); - _mavlinkLogManager = new MAVLinkLogManager (app, this); -#ifndef QGC_AIRLINK_DISABLED - _airlinkManager = new AirLinkManager (app, this); -#endif #ifdef QGC_UTM_ADAPTER _utmspManager = new UTMSPManager (app, this); #endif @@ -70,9 +62,6 @@ void QGCToolbox::setChildToolboxes(void) _qgcPositionManager->setToolbox(this); _videoManager->setToolbox(this); _mavlinkLogManager->setToolbox(this); -#ifndef QGC_AIRLINK_DISABLED - _airlinkManager->setToolbox(this); -#endif #ifdef QGC_UTM_ADAPTER _utmspManager->setToolbox(this); #endif diff --git a/src/QGCToolbox.h b/src/QGCToolbox.h index acef3379d6a..1b0c60d55dc 100644 --- a/src/QGCToolbox.h +++ b/src/QGCToolbox.h @@ -24,9 +24,6 @@ class VideoManager; class MAVLinkLogManager; class QGCCorePlugin; class SettingsManager; -#ifndef QGC_AIRLINK_DISABLED -class AirLinkManager; -#endif #ifdef QGC_UTM_ADAPTER class UTMSPManager; #endif @@ -48,9 +45,6 @@ class QGCToolbox : public QObject { MAVLinkLogManager* mavlinkLogManager () { return _mavlinkLogManager; } QGCCorePlugin* corePlugin () { return _corePlugin; } SettingsManager* settingsManager () { return _settingsManager; } -#ifndef QGC_AIRLINK_DISABLED - AirLinkManager* airlinkManager () { return _airlinkManager; } -#endif #ifdef QGC_UTM_ADAPTER UTMSPManager* utmspManager () { return _utmspManager; } #endif @@ -69,12 +63,8 @@ class QGCToolbox : public QObject { MAVLinkLogManager* _mavlinkLogManager = nullptr; QGCCorePlugin* _corePlugin = nullptr; SettingsManager* _settingsManager = nullptr; -#ifndef QGC_AIRLINK_DISABLED - AirLinkManager* _airlinkManager = nullptr; -#endif - #ifdef QGC_UTM_ADAPTER - UTMSPManager* _utmspManager = nullptr; + UTMSPManager* _utmspManager = nullptr; #endif friend class QGCApplication; }; diff --git a/src/QmlControls/QGroundControlQmlGlobal.cc b/src/QmlControls/QGroundControlQmlGlobal.cc index 89d4238f310..3cbcf021c6e 100644 --- a/src/QmlControls/QGroundControlQmlGlobal.cc +++ b/src/QmlControls/QGroundControlQmlGlobal.cc @@ -25,6 +25,9 @@ #ifdef QT_DEBUG #include "MockLink.h" #endif +#ifndef QGC_AIRLINK_DISABLED +#include "AirLinkManager.h" +#endif #include #include @@ -36,6 +39,9 @@ QGroundControlQmlGlobal::QGroundControlQmlGlobal(QGCApplication* app, QGCToolbox : QGCTool(app, toolbox) , _mapEngineManager(QGCMapEngineManager::instance()) , _adsbVehicleManager(ADSBVehicleManager::instance()) +#ifndef QGC_AIRLINK_DISABLED + , _airlinkManager(AirLinkManager::instance()) +#endif { // We clear the parent on this object since we run into shutdown problems caused by hybrid qml app. Instead we let it leak on shutdown. // setParent(nullptr); @@ -89,9 +95,6 @@ void QGroundControlQmlGlobal::setToolbox(QGCToolbox* toolbox) _gpsRtkFactGroup = GPSManager::instance()->gpsRtk()->gpsRtkFactGroup(); #endif _globalPalette = new QGCPalette(this); -#ifndef QGC_AIRLINK_DISABLED - _airlinkManager = toolbox->airlinkManager(); -#endif #ifdef QGC_UTM_ADAPTER _utmspManager = toolbox->utmspManager(); #endif diff --git a/src/QmlControls/QGroundControlQmlGlobal.h b/src/QmlControls/QGroundControlQmlGlobal.h index e9e2b41d39c..8c4121a8d76 100644 --- a/src/QmlControls/QGroundControlQmlGlobal.h +++ b/src/QmlControls/QGroundControlQmlGlobal.h @@ -185,11 +185,11 @@ class QGroundControlQmlGlobal : public QGCTool static QGeoCoordinate flightMapPosition () { return _coord; } static double flightMapZoom () { return _zoom; } - AirLinkManager* airlinkManager () { return _airlinkManager; } #ifndef QGC_AIRLINK_DISABLED + AirLinkManager* airlinkManager () { return _airlinkManager; } bool airlinkSupported () { return true; } #else - bool airlinkSupported () { return false; } + bool airlinkSupported () { return false; } #endif #ifdef QGC_UTM_ADAPTER @@ -257,10 +257,14 @@ class QGroundControlQmlGlobal : public QGCTool void skipSetupPageChanged (); private: + QGCMapEngineManager* _mapEngineManager = nullptr; + ADSBVehicleManager* _adsbVehicleManager = nullptr; +#ifndef QGC_AIRLINK_DISABLED + AirLinkManager* _airlinkManager = nullptr; +#endif double _flightMapInitialZoom = 17.0; LinkManager* _linkManager = nullptr; MultiVehicleManager* _multiVehicleManager = nullptr; - QGCMapEngineManager* _mapEngineManager = nullptr; QGCPositionManager* _qgcPositionManager = nullptr; MissionCommandTree* _missionCommandTree = nullptr; VideoManager* _videoManager = nullptr; @@ -271,8 +275,6 @@ class QGroundControlQmlGlobal : public QGCTool #ifndef NO_SERIAL_LINK FactGroup* _gpsRtkFactGroup = nullptr; #endif - AirLinkManager* _airlinkManager = nullptr; - ADSBVehicleManager* _adsbVehicleManager = nullptr; QGCPalette* _globalPalette = nullptr; QmlUnitsConversion _unitsConversion; #ifdef QGC_UTM_ADAPTER