From 510af52e97e069570ffac713886eb35a5cc116af Mon Sep 17 00:00:00 2001 From: Juny Date: Wed, 14 Feb 2024 18:16:42 -0300 Subject: [PATCH] Initial backend with MQTT support --- CMakeLists.txt | 6 ++-- src/Backend.cpp | 62 +++++++++++++++++++++++++++++++++++ src/Backend.hpp | 61 ++++++++++++++++++++++++++++++++++ src/Gui/App.qml | 16 +++++---- src/Gui/Login/BrokerInput.qml | 36 ++++++++++++-------- src/Gui/Login/Login.qml | 2 +- src/Gui/Settings/Settings.qml | 2 +- src/Gui/Splash.qml | 28 +++++++++++++++- src/main.cpp | 4 ++- 9 files changed, 190 insertions(+), 27 deletions(-) create mode 100644 src/Backend.cpp create mode 100644 src/Backend.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 27d6f33..ff9a0c7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,18 +11,20 @@ set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) set(CMAKE_AUTOUIC ON) -find_package(Qt6 COMPONENTS Widgets Qml QuickControls2 REQUIRED) +find_package(Qt6 COMPONENTS Widgets Qml QuickControls2 Mqtt REQUIRED) add_executable(${PROJECT} src/main.cpp src/FileIO.cpp + src/Backend.cpp src/resources.qrc src/Gui/Style/resources.qrc) target_link_libraries(${PROJECT} Qt::Widgets Qt::Qml - Qt::QuickControls2) + Qt::QuickControls2 + Qt::Mqtt) if(CMAKE_BUILD_TYPE STREQUAL "Release") set_property(TARGET ${PROJECT} PROPERTY WIN32_EXECUTABLE true) diff --git a/src/Backend.cpp b/src/Backend.cpp new file mode 100644 index 0000000..2556057 --- /dev/null +++ b/src/Backend.cpp @@ -0,0 +1,62 @@ +/** + * @file Backend.cpp + * @author Juny + * @brief Implementation of backend module for QML + * @version 0.1 + * @date 2024-02-13 + * + * @copyright Copyright (c) 2024 + * + */ + +#include "Backend.hpp" + +void Backend::registerTypes(const char *uri) { + qmlRegisterType(uri, 0, 1, "Backend"); +} +Backend::Backend(QObject *parent) : QObject(parent) { + this->m_client = new QMqttClient(this); + + connect(this->m_client, &QMqttClient::hostnameChanged, this, + &Backend::brokerHostChanged); + connect(this->m_client, &QMqttClient::portChanged, this, + &Backend::brokerPortChanged); + connect(this->m_client, &QMqttClient::stateChanged, this, + &Backend::brokerStateChanged); + connect(this->m_client, &QMqttClient::stateChanged, this, + &Backend::onBrokerStateChanged); +} + +void Backend::connectToBroker() { this->m_client->connectToHost(); } +void Backend::disconnectFromBroker() { this->m_client->disconnectFromHost(); } + +const QString Backend::brokerHost() const { return this->m_client->hostname(); } +void Backend::setBrokerHost(const QString &host) { + this->m_client->setHostname(host); +} + +int Backend::brokerPort() const { return this->m_client->port(); } +void Backend::setBrokerPort(int port) { + if (port < 0 || port > std::numeric_limits::max()) { + qWarning() << "Invalid port: " << port; + return; + } + + this->m_client->setPort(static_cast(port)); +} + +const QMqttClient::ClientState Backend::brokerState() const { + return this->m_client->state(); +} +void Backend::setBrokerState(const QMqttClient::ClientState &state) { + this->m_client->setState(state); +} + +void Backend::onBrokerStateChanged(const QMqttClient::ClientState &state) { + qWarning() << "State change" << state; + + if (state != QMqttClient::Connected) + return; + + // TODO: subscribe +} \ No newline at end of file diff --git a/src/Backend.hpp b/src/Backend.hpp new file mode 100644 index 0000000..284284a --- /dev/null +++ b/src/Backend.hpp @@ -0,0 +1,61 @@ +/** + * @file Backend.hpp + * @author Juny + * @brief Backend module for QML + * @version 0.1 + * @date 2024-02-13 + * + * @copyright Copyright (c) 2024 + * + */ + +#pragma once + +#include +#include +#include + +class Backend : public QObject { + Q_OBJECT + Q_PROPERTY(QString brokerHost READ brokerHost WRITE setBrokerHost NOTIFY + brokerHostChanged) + Q_PROPERTY(int brokerPort READ brokerPort WRITE setBrokerPort NOTIFY + brokerPortChanged) + Q_PROPERTY(QMqttClient::ClientState brokerState READ brokerState WRITE + setBrokerState NOTIFY brokerStateChanged) + QML_NAMED_ELEMENT(Backend) + +public: + explicit Backend(QObject *parent = nullptr); + + static void registerTypes(const char *uri); + static QObject *singletonProvider(QQmlEngine *engine, QJSEngine *); + + // QML functions + + Q_INVOKABLE void connectToBroker(); + Q_INVOKABLE void disconnectFromBroker(); + + // QML Properties + + const QString brokerHost() const; + void setBrokerHost(const QString &host); + + int brokerPort() const; + void setBrokerPort(int port); + + const QMqttClient::ClientState brokerState() const; + void setBrokerState(const QMqttClient::ClientState &state); + +signals: + void brokerHostChanged(); + void brokerPortChanged(); + void brokerStateChanged(); + +private slots: + void onBrokerStateChanged(const QMqttClient::ClientState &state); + +private: + Q_DISABLE_COPY(Backend) + QMqttClient *m_client; +}; \ No newline at end of file diff --git a/src/Gui/App.qml b/src/Gui/App.qml index d7e615d..ab8fc36 100644 --- a/src/Gui/App.qml +++ b/src/Gui/App.qml @@ -4,12 +4,12 @@ import QtQuick import "Settings" -QtObject { +import QSpec.Backend + +Backend { id: app property string brokerURL: Settings._json.brokerURL || "" - property string brokerHost - property string brokerPort property string brokerUsername: Settings._json.brokerUsername || "" property string brokerPassword: Settings._json.brokerPassword || "" @@ -19,11 +19,13 @@ QtObject { brokerPort = brokerURL.split(":")[1] || 1883 } - function saveBroker(url, user, pass) { - Settings._json.brokerURL = url - Settings._json.brokerUsername = user - Settings._json.brokerPassword = pass + function tryConnect(url, user, pass) { + brokerURL = Settings._json.brokerURL = url + brokerUsername = Settings._json.brokerUsername = user + brokerPassword = Settings._json.brokerPassword = pass Settings.save() + + connectToBroker() } function setup() { diff --git a/src/Gui/Login/BrokerInput.qml b/src/Gui/Login/BrokerInput.qml index bcb89ff..836035e 100644 --- a/src/Gui/Login/BrokerInput.qml +++ b/src/Gui/Login/BrokerInput.qml @@ -11,6 +11,8 @@ ColumnLayout { signal tryConnect(url: string, username: string, password: string) + Component.onCompleted: checkUrl(brokerURL.tfText) + Timer { id: focusTimer running: true @@ -40,19 +42,7 @@ ColumnLayout { tfText: App.brokerURL || "" onTfAccepted: _tryConnect() - - onTfChanged: (text) => { - let url; - - try { - url = new URL(`https://${text}`) - } catch (_) { - btn.enabled = false - return - } - - btn.enabled = true - } + onTfChanged: (text) => checkUrl(text) } } @@ -84,7 +74,7 @@ ColumnLayout { id: btn text: qsTr("Connect") Layout.fillWidth: true - // enabled: false + enabled: false onClicked: _tryConnect() } @@ -92,4 +82,22 @@ ColumnLayout { function _tryConnect() { tryConnect(brokerURL.tfText, brokerUsername.tfText, brokerPassword.tfText) } + + function checkUrl(text) { + let url; + + if (text == "") { + btn.enabled = false + return + } + + try { + url = new URL(`https://${text}`) + } catch (_) { + btn.enabled = false + return + } + + btn.enabled = true + } } \ No newline at end of file diff --git a/src/Gui/Login/Login.qml b/src/Gui/Login/Login.qml index b9568fe..46c6556 100644 --- a/src/Gui/Login/Login.qml +++ b/src/Gui/Login/Login.qml @@ -20,7 +20,7 @@ PStackView { anchors.centerIn: parent onTryConnect: (url, user, pass) => { - App.saveBroker(url, user, pass) + App.tryConnect(url, user, pass) login.push(loadInstance) } } diff --git a/src/Gui/Settings/Settings.qml b/src/Gui/Settings/Settings.qml index 200171d..ce04ca0 100644 --- a/src/Gui/Settings/Settings.qml +++ b/src/Gui/Settings/Settings.qml @@ -3,7 +3,7 @@ pragma Singleton import QtQuick import QtCore -import QSpec +import QSpec.FileIO QtObject { property string configDir: StandardPaths.writableLocation(StandardPaths.AppConfigLocation) diff --git a/src/Gui/Splash.qml b/src/Gui/Splash.qml index 53766ff..2c5322c 100644 --- a/src/Gui/Splash.qml +++ b/src/Gui/Splash.qml @@ -16,6 +16,11 @@ Window { Component.onCompleted: { App.setup() loadingTimer.running = true + + App.brokerStateChanged.connect(() => { + if (App.brokerState == 2) + connectTimer.running = true + }) } Timer { @@ -23,11 +28,21 @@ Window { running: false repeat: false - interval: 500 + interval: 750 onTriggered: loader.source = "qrc:/Login/Login.qml" } + Timer { + id: connectTimer + + running: false + repeat: false + interval: 750 + + onTriggered: loader.sourceComponent = mainPane + } + Loader { id: loader anchors.fill: parent @@ -55,4 +70,15 @@ Window { } } } + + Component { + id: mainPane + + PBackground { + PText { + anchors.centerIn: parent + text: qsTr("YAAAAY I AM CONNECTED") + } + } + } } \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index fcc6cfd..d7f78b6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,6 +19,7 @@ #include #include +#include "Backend.hpp" #include "FileIO.hpp" int main(int argc, char *argv[]) { @@ -35,7 +36,8 @@ int main(int argc, char *argv[]) { QObject::connect(engine, &QQmlEngine::quit, &QApplication::quit); - FileIO::registerTypes("QSpec"); + FileIO::registerTypes("QSpec.FileIO"); + Backend::registerTypes("QSpec.Backend"); engine->addImportPath("qrc:/");