From a8fb35cc494b8caf8482beb4957cb27212b99ec5 Mon Sep 17 00:00:00 2001 From: Alex Spataru Date: Sat, 27 Feb 2021 01:50:42 -0500 Subject: [PATCH] Let Serial Studio act as a MQTT publisher (WIP) --- Serial-Studio.pro | 10 +- assets/assets.qrc | 3 +- assets/qml/SetupPanes/MQTTPublisher.qml | 269 ++++++++++++++++++++++++ assets/qml/Windows/Setup.qml | 31 +++ src/IO/Console.cpp | 61 +++++- src/IO/DataSources/Network.h | 21 +- src/MQTT/Publisher.cpp | 230 ++++++++++++++++++++ src/MQTT/Publisher.h | 140 ++++++++++++ src/Misc/ModuleManager.cpp | 5 + src/UI/WidgetProvider.cpp | 2 +- 10 files changed, 750 insertions(+), 22 deletions(-) create mode 100644 assets/qml/SetupPanes/MQTTPublisher.qml create mode 100644 src/MQTT/Publisher.cpp create mode 100644 src/MQTT/Publisher.h diff --git a/Serial-Studio.pro b/Serial-Studio.pro index ba82d399..7d147d99 100644 --- a/Serial-Studio.pro +++ b/Serial-Studio.pro @@ -121,12 +121,8 @@ RESOURCES += \ assets/assets.qrc DISTFILES += \ - assets/qml/PlatformDependent/DecentMenuItem.qml \ - assets/qml/PlatformDependent/Menubar.qml \ - assets/qml/PlatformDependent/MenubarMacOS.qml \ - assets/qml/SetupPanes/Network.qml \ - assets/qml/SetupPanes/Serial.qml \ - assets/qml/SetupPanes/Settings.qml \ + assets/qml/PlatformDependent/*.qml \ + assets/qml/SetupPanes/*.qml \ assets/qml/Widgets/*.qml \ assets/qml/Windows/*.qml \ assets/qml/SetupPanes/*.qml \ @@ -151,6 +147,7 @@ HEADERS += \ src/JSON/FrameInfo.h \ src/JSON/Generator.h \ src/JSON/Group.h \ + src/MQTT/Publisher.h \ src/Misc/ModuleManager.h \ src/Misc/TimerEvents.h \ src/Misc/Translator.h \ @@ -172,6 +169,7 @@ SOURCES += \ src/JSON/FrameInfo.cpp \ src/JSON/Generator.cpp \ src/JSON/Group.cpp \ + src/MQTT/Publisher.cpp \ src/Misc/ModuleManager.cpp \ src/Misc/TimerEvents.cpp \ src/Misc/Translator.cpp \ diff --git a/assets/assets.qrc b/assets/assets.qrc index b7ba377d..be141b04 100644 --- a/assets/assets.qrc +++ b/assets/assets.qrc @@ -66,7 +66,7 @@ qml/Widgets/MapDelegate.qml qml/Widgets/Window.qml qml/Windows/About.qml - qml/Windows/Acknowledgements.qml + qml/Windows/Acknowledgements.qml qml/Windows/Console.qml qml/Windows/CsvPlayer.qml qml/Windows/DataGrid.qml @@ -94,5 +94,6 @@ qml/PlatformDependent/MenubarMacOS.qml qml/PlatformDependent/Menubar.qml qml/PlatformDependent/DecentMenuItem.qml + qml/SetupPanes/MQTTPublisher.qml diff --git a/assets/qml/SetupPanes/MQTTPublisher.qml b/assets/qml/SetupPanes/MQTTPublisher.qml new file mode 100644 index 00000000..a59dd87a --- /dev/null +++ b/assets/qml/SetupPanes/MQTTPublisher.qml @@ -0,0 +1,269 @@ +/* + * Copyright (c) 2020-2021 Alex Spataru + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 + +Control { + id: root + implicitHeight: layout.implicitHeight + app.spacing * 2 + + // + // Aliases + // + property alias host: _host.text + property alias port: _port.text + property alias topic: _topic.text + property alias user: _user.text + property alias password: _password.text + property alias dnsAddress: _addrLookup.text + + // + // Layout + // + ColumnLayout { + id: layout + anchors.fill: parent + anchors.margins: app.spacing + + GridLayout { + columns: 2 + Layout.fillWidth: true + rowSpacing: app.spacing + columnSpacing: app.spacing + + // + // MQTT version + // + Label { + text: qsTr("Version") + ":" + } ComboBox { + Layout.fillWidth: true + model: Cpp_MQTT_Publisher.mqttVersions + currentIndex: Cpp_MQTT_Publisher.mqttVersion + onCurrentIndexChanged: { + if (Cpp_MQTT_Publisher.mqttVersion !== currentIndex) + Cpp_MQTT_Publisher.mqttVersion = currentIndex + } + } + + // + // Host + // + Label { + opacity: enabled ? 1 : 0.5 + enabled: !Cpp_MQTT_Publisher.isConnectedToHost + Behavior on opacity {NumberAnimation{}} + text: qsTr("Host") + ":" + } TextField { + id: _host + Layout.fillWidth: true + text: Cpp_MQTT_Publisher.host + placeholderText: Cpp_MQTT_Publisher.defaultHost + onTextChanged: { + if (Cpp_MQTT_Publisher.host !== text) + Cpp_MQTT_Publisher.host = text + } + + opacity: enabled ? 1 : 0.5 + enabled: !Cpp_MQTT_Publisher.isConnectedToHost + Behavior on opacity {NumberAnimation{}} + } + + // + // Port + // + Label { + opacity: enabled ? 1 : 0.5 + enabled: !Cpp_MQTT_Publisher.isConnectedToHost + Behavior on opacity {NumberAnimation{}} + text: qsTr("Port") + ":" + } TextField { + id: _port + Layout.fillWidth: true + text: Cpp_MQTT_Publisher.port + placeholderText: Cpp_MQTT_Publisher.defaultPort + onTextChanged: { + if (Cpp_MQTT_Publisher.port !== text) + Cpp_MQTT_Publisher.port = text + } + + validator: IntValidator { + bottom: 0 + top: 65535 + } + + opacity: enabled ? 1 : 0.5 + enabled: !Cpp_MQTT_Publisher.isConnectedToHost + Behavior on opacity {NumberAnimation{}} + } + + // + // Topic + // + Label { + opacity: enabled ? 1 : 0.5 + enabled: !Cpp_MQTT_Publisher.isConnectedToHost + Behavior on opacity {NumberAnimation{}} + text: qsTr("Topic") + ":" + } TextField { + id: _topic + Layout.fillWidth: true + text: Cpp_MQTT_Publisher.topic + placeholderText: qsTr("MQTT topic") + onTextChanged: { + if (Cpp_MQTT_Publisher.topic !== text) + Cpp_MQTT_Publisher.topic = text + } + + opacity: enabled ? 1 : 0.5 + enabled: !Cpp_MQTT_Publisher.isConnectedToHost + Behavior on opacity {NumberAnimation{}} + } + + // + // Username + // + Label { + opacity: enabled ? 1 : 0.5 + enabled: !Cpp_MQTT_Publisher.isConnectedToHost + Behavior on opacity {NumberAnimation{}} + text: qsTr("User") + ":" + } TextField { + id: _user + Layout.fillWidth: true + text: Cpp_MQTT_Publisher.username + placeholderText: qsTr("MQTT username") + onTextChanged: { + if (Cpp_MQTT_Publisher.username !== text) + Cpp_MQTT_Publisher.username = text + } + + opacity: enabled ? 1 : 0.5 + enabled: !Cpp_MQTT_Publisher.isConnectedToHost + Behavior on opacity {NumberAnimation{}} + } + + // + // Password + // + Label { + opacity: enabled ? 1 : 0.5 + enabled: !Cpp_MQTT_Publisher.isConnectedToHost + Behavior on opacity {NumberAnimation{}} + text: qsTr("Password") + ":" + } TextField { + id: _password + Layout.fillWidth: true + text: Cpp_MQTT_Publisher.password + placeholderText: qsTr("MQTT password") + onTextChanged: { + if (Cpp_MQTT_Publisher.password !== text) + Cpp_MQTT_Publisher.password = text + } + + opacity: enabled ? 1 : 0.5 + enabled: !Cpp_MQTT_Publisher.isConnectedToHost + Behavior on opacity {NumberAnimation{}} + } + } + + // + // Spacer + // + Item { + Layout.fillHeight: true + Layout.minimumHeight: app.spacing + } + + // + // Address lookup + // + Label { + text: qsTr("DNS lookup") + ": " + opacity: enabled ? 1 : 0.5 + enabled: !Cpp_MQTT_Publisher.isConnectedToHost + Behavior on opacity {NumberAnimation{}} + } RowLayout { + Layout.fillWidth: true + spacing: app.spacing / 2 + + TextField { + id: _addrLookup + Layout.fillWidth: true + opacity: enabled ? 1 : 0.5 + Layout.alignment: Qt.AlignVCenter + onAccepted: Cpp_MQTT_Publisher.lookup(text) + placeholderText: qsTr("Enter address (e.g. google.com)") + enabled: !Cpp_MQTT_Publisher.isConnectedToHost && + !Cpp_MQTT_Publisher.lookupActive + + Behavior on opacity {NumberAnimation{}} + } + + Button { + icon.color: palette.text + opacity: enabled ? 1 : 0.5 + Layout.maximumWidth: height + Layout.alignment: Qt.AlignVCenter + icon.source: "qrc:/icons/search.svg" + onClicked: Cpp_MQTT_Publisher.lookup(_addrLookup.text) + enabled: _addrLookup.text.length > 0 && + !Cpp_MQTT_Publisher.isConnectedToHost && + !Cpp_MQTT_Publisher.lookupActive + + Behavior on opacity {NumberAnimation{}} + } + } + + // + // Spacer + // + Item { + Layout.fillHeight: true + } + + // + // Connect/disconnect button + // + Button { + icon.width: 24 + icon.height: 24 + font.bold: true + Layout.fillWidth: true + icon.color: palette.buttonText + checked: Cpp_MQTT_Publisher.isConnectedToHost + onClicked: Cpp_MQTT_Publisher.toggleConnection() + palette.buttonText: checked ? "#d72d60" : "#2eed5c" + text: (checked ? qsTr("Disconnect") : qsTr("Connect")) + " " + icon.source: checked ? "qrc:/icons/disconnect.svg" : "qrc:/icons/connect.svg" + } + + // + // Spacer + // + Item { + Layout.fillHeight: true + } + } +} diff --git a/assets/qml/Windows/Setup.qml b/assets/qml/Windows/Setup.qml index 5a4473c2..1c74db17 100644 --- a/assets/qml/Windows/Setup.qml +++ b/assets/qml/Windows/Setup.qml @@ -65,6 +65,16 @@ Control { property alias socketType: network.socketType property alias addressLookup: network.addressLookup + // + // MQTT settings + // + property alias mqttHost: mqttPublisher.host + property alias mqttPort: mqttPublisher.port + property alias mqttUser: mqttPublisher.user + property alias mqttTopic: mqttPublisher.topic + property alias mqttPassword: mqttPublisher.password + property alias mqttDnsAddress: mqttPublisher.dnsAddress + // // App settings // @@ -204,6 +214,12 @@ Control { width: implicitWidth + 2 * app.spacing } + TabButton { + text: qsTr("MQTT") + height: tab.height + 3 + width: implicitWidth + 2 * app.spacing + } + TabButton { text: qsTr("Settings") height: tab.height + 3 @@ -238,7 +254,14 @@ Control { stack.implicitHeight = network.implicitHeight break case 2: + stack.implicitHeight = mqttPublisher.implicitHeight + break + case 3: stack.implicitHeight = settings.implicitHeight + break + default: + stack.implicitHeight = 0 + break } } @@ -258,6 +281,14 @@ Control { } } + SetupPanes.MQTTPublisher { + id: mqttPublisher + background: TextField { + enabled: false + palette.base: "#16232a" + } + } + SetupPanes.Settings { id: settings background: TextField { diff --git a/src/IO/Console.cpp b/src/IO/Console.cpp index b58ae520..0b941008 100644 --- a/src/IO/Console.cpp +++ b/src/IO/Console.cpp @@ -38,6 +38,48 @@ using namespace IO; static Console *INSTANCE = nullptr; +/** + * Generates a hexdump of the given data + */ +static QString HexDump(const char *data, size_t size) +{ + char str[4096] = ""; + char ascii[17]; + + size_t i, j; + ascii[16] = '\0'; + for (i = 0; i < size; ++i) + { + sprintf(str + strlen(str), "%02X ", static_cast(data[i])); + + if (data[i] >= ' ' && data[i] <= '~') + ascii[i % 16] = data[i]; + else + ascii[i % 16] = '.'; + + if ((i + 1) % 8 == 0 || i + 1 == size) + { + sprintf(str + strlen(str), " "); + if ((i + 1) % 16 == 0) + sprintf(str + strlen(str), "| %s \n", ascii); + + else if (i + 1 == size) + { + ascii[(i + 1) % 16] = '\0'; + + if ((i + 1) % 16 <= 8) + sprintf(str + strlen(str), " "); + for (j = (i + 1) % 16; j < 16; ++j) + sprintf(str + strlen(str), " "); + + sprintf(str + strlen(str), "| %s \n", ascii); + } + } + } + + return QString(str); +} + /** * Constructor function */ @@ -610,19 +652,18 @@ QString Console::plainTextStr(const QByteArray &data) */ QString Console::hexadecimalStr(const QByteArray &data) { - // Convert to hex string with spaces between bytes + // Convert data to string with dump every ~80 chars QString str; - QString hex = QString::fromUtf8(data.toHex()); - for (int i = 0; i < hex.length(); ++i) + const int characters = 80; + for (int i = 0; i < data.length(); ++i) { - str.append(hex.at(i)); - if ((i + 1) % 2 == 0 && i > 0) - str.append(" "); - } + QByteArray line; + for (int j = 0; j < qMin(characters, data.length() - i); ++j) + line.append(data.at(i + j)); - // Add new line & carriage returns - str.replace("0a", "0a\r"); - str.replace("0d", "0d\n"); + str.append(HexDump(line.data(), line.size())); + str.append("\n"); + } // Return string return str; diff --git a/src/IO/DataSources/Network.h b/src/IO/DataSources/Network.h index e2437fcb..18426ec1 100644 --- a/src/IO/DataSources/Network.h +++ b/src/IO/DataSources/Network.h @@ -1,10 +1,23 @@ /* - * Copyright (C) MATUSICA S.A. de C.V. - All Rights Reserved + * Copyright (c) 2020-2021 Alex Spataru * - * Unauthorized copying of this file, via any medium is strictly prohibited. - * Proprietary and confidential. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: * - * Written by Alex Spataru , February 2021 + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ #ifndef IO_DATA_SOURCES_NETWORK_H diff --git a/src/MQTT/Publisher.cpp b/src/MQTT/Publisher.cpp new file mode 100644 index 00000000..40c53e76 --- /dev/null +++ b/src/MQTT/Publisher.cpp @@ -0,0 +1,230 @@ +/* + * Copyright (c) 2020-2021 Alex Spataru + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "Publisher.h" + +#include +#include + +using namespace MQTT; + +static Publisher *INSTANCE = nullptr; + +Publisher::Publisher() +{ + m_lookupActive = false; + + connect(&m_client, &QMQTT::Client::connected, this, &Publisher::connectedChanged); + connect(&m_client, &QMQTT::Client::disconnected, this, &Publisher::connectedChanged); + connect(&m_client, &QMQTT::Client::error, this, &Publisher::onError); + + setPort(defaultPort()); + setHost(defaultHost()); +} + +Publisher::~Publisher() +{ + disconnectFromHost(); +} + +Publisher *Publisher::getInstance() +{ + if (!INSTANCE) + INSTANCE = new Publisher; + + return INSTANCE; +} + +quint16 Publisher::port() const +{ + return m_client.port(); +} + +QString Publisher::topic() const +{ + return m_topic; +} + +int Publisher::mqttVersion() const +{ + switch (m_client.version()) + { + case QMQTT::V3_1_0: + return 0; + break; + case QMQTT::V3_1_1: + return 1; + break; + default: + return -1; + break; + } +} + +QString Publisher::username() const +{ + return m_client.username(); +} + +QString Publisher::password() const +{ + return QString::fromUtf8(m_client.password()); +} + +QString Publisher::host() const +{ + return m_client.host().toString(); +} + +bool Publisher::lookupActive() const +{ + return m_lookupActive; +} + +bool Publisher::isConnectedToHost() const +{ + return m_client.isConnectedToHost(); +} + +QStringList Publisher::mqttVersions() const +{ + return QStringList { "MQTT 3.1.0", "MQTT 3.1.1" }; +} + +void Publisher::connectToHost() +{ + m_client.connectToHost(); +} + +/** + * Connects/disconnects the application from the current MQTT broker. This function is + * used as a convenience for the connect/disconnect button. + */ +void Publisher::toggleConnection() +{ + if (isConnectedToHost()) + disconnectFromHost(); + else + connectToHost(); +} + +void Publisher::disconnectFromHost() +{ + m_client.disconnectFromHost(); +} + +/** + * Performs a DNS lookup for the given @a host name + */ +void Publisher::lookup(const QString &host) +{ + m_lookupActive = true; + emit lookupActiveChanged(); + QHostInfo::lookupHost(host.simplified(), this, &Publisher::lookupFinished); +} + +void Publisher::setPort(const quint16 port) +{ + m_client.setPort(port); + emit portChanged(); +} + +void Publisher::setHost(const QString &host) +{ + m_client.setHost(QHostAddress(host)); + emit hostChanged(); +} + +void Publisher::setTopic(const QString &topic) +{ + m_topic = topic; + emit topicChanged(); +} + +void Publisher::setUsername(const QString &username) +{ + m_client.setUsername(username); + emit usernameChanged(); +} + +void Publisher::setPassword(const QString &password) +{ + m_client.setPassword(password.toUtf8()); + emit passwordChanged(); +} + +void Publisher::setMqttVersion(const int versionIndex) +{ + switch (versionIndex) + { + case 0: + m_client.setVersion(QMQTT::V3_1_0); + break; + case 1: + m_client.setVersion(QMQTT::V3_1_1); + break; + default: + break; + } + + emit mqttVersionChanged(); +} + +void Publisher::sendData() +{ + // Sort JFI list from oldest to most recent + JFI_SortList(&m_jfiList); + + // Clear JFI list +} + +/** + * Sets the host IP address when the lookup finishes. + * If the lookup fails, the error code/string shall be shown to the user in a messagebox. + */ +void Publisher::lookupFinished(const QHostInfo &info) +{ + m_lookupActive = false; + emit lookupActiveChanged(); + + if (info.error() == QHostInfo::NoError) + { + auto addresses = info.addresses(); + if (addresses.count() >= 1) + { + setHost(addresses.first().toString()); + return; + } + } + + Misc::Utilities::showMessageBox(tr("IP address lookup error"), info.errorString()); +} + +void Publisher::onError(const QMQTT::ClientError error) +{ + qDebug() << error; +} + +void Publisher::registerJsonFrame(const JFI_Object &frameInfo) +{ + m_jfiList.append(frameInfo); +} diff --git a/src/MQTT/Publisher.h b/src/MQTT/Publisher.h new file mode 100644 index 00000000..bdb84c09 --- /dev/null +++ b/src/MQTT/Publisher.h @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2020-2021 Alex Spataru + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef MQTT_PUBLISHER_H +#define MQTT_PUBLISHER_H + +#include +#include +#include +#include + +#include +#include + +#include + +namespace MQTT +{ +class Publisher : public QObject +{ + // clang-format off + Q_OBJECT + Q_PROPERTY(quint16 port + READ port + WRITE setPort + NOTIFY portChanged) + Q_PROPERTY(QString host + READ host + WRITE setHost + NOTIFY hostChanged) + Q_PROPERTY(QString topic + READ topic + WRITE setTopic + NOTIFY topicChanged) + Q_PROPERTY(int mqttVersion + READ mqttVersion + WRITE setMqttVersion + NOTIFY mqttVersionChanged) + Q_PROPERTY(QString username + READ username + WRITE setUsername + NOTIFY usernameChanged) + Q_PROPERTY(QString password + READ password + WRITE setPassword + NOTIFY passwordChanged) + Q_PROPERTY(bool isConnectedToHost + READ isConnectedToHost + NOTIFY connectedChanged) + Q_PROPERTY(bool lookupActive + READ lookupActive + NOTIFY lookupActiveChanged) + Q_PROPERTY(QStringList mqttVersions + READ mqttVersions + CONSTANT) + Q_PROPERTY(quint16 defaultPort + READ defaultPort + CONSTANT) + Q_PROPERTY(QString defaultHost + READ defaultHost + CONSTANT) + // clang-format on + +signals: + void portChanged(); + void hostChanged(); + void topicChanged(); + void usernameChanged(); + void passwordChanged(); + void connectedChanged(); + void mqttVersionChanged(); + void lookupActiveChanged(); + +public: + static Publisher *getInstance(); + + quint16 port() const; + QString host() const; + QString topic() const; + int mqttVersion() const; + QString username() const; + QString password() const; + bool lookupActive() const; + bool isConnectedToHost() const; + QStringList mqttVersions() const; + + quint16 defaultPort() const { return 1883; } + + QString defaultHost() const { return "127.0.0.1"; } + +public slots: + void connectToHost(); + void toggleConnection(); + void disconnectFromHost(); + void lookup(const QString &host); + void setPort(const quint16 port); + void setHost(const QString &host); + void setTopic(const QString &topic); + void setUsername(const QString &username); + void setPassword(const QString &password); + void setMqttVersion(const int versionIndex); + +private: + Publisher(); + ~Publisher(); + +private slots: + void sendData(); + void lookupFinished(const QHostInfo &info); + void onError(const QMQTT::ClientError error); + void registerJsonFrame(const JFI_Object &frameInfo); + +private: + QString m_topic; + bool m_lookupActive; + QMQTT::Client m_client; + QList m_jfiList; +}; +} + +#endif diff --git a/src/Misc/ModuleManager.cpp b/src/Misc/ModuleManager.cpp index 9031f14f..bfafe749 100644 --- a/src/Misc/ModuleManager.cpp +++ b/src/Misc/ModuleManager.cpp @@ -48,6 +48,8 @@ #include #include +#include + #include #include #include @@ -148,6 +150,7 @@ void ModuleManager::initializeQmlInterface() auto ioNetwork = IO::DataSources::Network::getInstance(); auto jsonGenerator = JSON::Generator::getInstance(); auto utilities = Misc::Utilities::getInstance(); + auto mqttPublisher = MQTT::Publisher::getInstance(); LOG_INFO() << "Finished initializing C++ modules"; // Retranslate the QML interface automagically @@ -170,6 +173,7 @@ void ModuleManager::initializeQmlInterface() c->setContextProperty("Cpp_IO_Serial", ioSerial); c->setContextProperty("Cpp_IO_Network", ioNetwork); c->setContextProperty("Cpp_JSON_Generator", jsonGenerator); + c->setContextProperty("Cpp_MQTT_Publisher", mqttPublisher); // Register app info with QML c->setContextProperty("Cpp_AppName", qApp->applicationName()); @@ -207,6 +211,7 @@ void ModuleManager::stopOperations() CSV::Player::getInstance()->closeFile(); IO::Manager::getInstance()->disconnectDevice(); Misc::TimerEvents::getInstance()->stopTimers(); + MQTT::Publisher::getInstance()->disconnectFromHost(); LOG_INFO() << "Application modules stopped"; } diff --git a/src/UI/WidgetProvider.cpp b/src/UI/WidgetProvider.cpp index 7f030e24..147c2584 100644 --- a/src/UI/WidgetProvider.cpp +++ b/src/UI/WidgetProvider.cpp @@ -419,7 +419,7 @@ void WidgetProvider::updateModels() // Check if frame is valid if (!DataProvider::getInstance()->latestFrame()->isValid()) - return; + return; // Update groups m_mapGroups = getWidgetGroup("map");