From 052c1059a163ea32a37f59a6bb86aed109591d13 Mon Sep 17 00:00:00 2001 From: Bas Schouten Date: Tue, 31 Jan 2023 03:50:02 +0100 Subject: [PATCH] First, extremely rudimentary version of a Qt thermostat interface. --- .gitignore | 74 +++++++++++++++++++++++++++++ .gitmodules | 3 ++ CMakeLists.txt | 50 ++++++++++++++++++++ main.cpp | 19 ++++++++ main.qml | 56 ++++++++++++++++++++++ mqttinterface.cpp | 115 ++++++++++++++++++++++++++++++++++++++++++++++ mqttinterface.h | 45 ++++++++++++++++++ paho.mqtt.c | 1 + 8 files changed, 363 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 CMakeLists.txt create mode 100644 main.cpp create mode 100644 main.qml create mode 100644 mqttinterface.cpp create mode 100644 mqttinterface.h create mode 160000 paho.mqtt.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4a0b530 --- /dev/null +++ b/.gitignore @@ -0,0 +1,74 @@ +# This file is used to ignore files which are generated +# ---------------------------------------------------------------------------- + +*~ +*.autosave +*.a +*.core +*.moc +*.o +*.obj +*.orig +*.rej +*.so +*.so.* +*_pch.h.cpp +*_resource.rc +*.qm +.#* +*.*# +core +!core/ +tags +.DS_Store +.directory +*.debug +Makefile* +*.prl +*.app +moc_*.cpp +ui_*.h +qrc_*.cpp +Thumbs.db +*.res +*.rc +/.qmake.cache +/.qmake.stash + +# qtcreator generated files +*.pro.user* +CMakeLists.txt.user* + +# xemacs temporary files +*.flc + +# Vim temporary files +.*.swp + +# Visual Studio generated files +*.ib_pdb_index +*.idb +*.ilk +*.pdb +*.sln +*.suo +*.vcproj +*vcproj.*.*.user +*.ncb +*.sdf +*.opensdf +*.vcxproj +*vcxproj.* + +# MinGW generated files +*.Debug +*.Release + +# Python byte code +*.pyc + +# Binaries +# -------- +*.dll +*.exe + diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..e0e40ec --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "paho.mqtt.c"] + path = paho.mqtt.c + url = https://github.com/eclipse/paho.mqtt.c diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..ff02d94 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,50 @@ +cmake_minimum_required(VERSION 3.16) + +project(qthomecontrol VERSION 0.1 LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(Qt6 6.2 COMPONENTS Quick REQUIRED) + +if (UNIX) + add_subdirectory ("paho.mqtt.c") +endif(UNIX) + +qt_add_executable(appqthomecontrol + main.cpp + mqttinterface.cpp + mqttinterface.h +) + +qt_add_qml_module(appqthomecontrol + URI qthomecontrol + VERSION 1.0 + QML_FILES main.qml + SOURCES + mqttinterface.h mqttinterface.cpp +) + +set_target_properties(appqthomecontrol PROPERTIES + MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com + MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} + MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} + MACOSX_BUNDLE TRUE + WIN32_EXECUTABLE TRUE +) + +target_include_directories (appqthomecontrol PRIVATE paho.mqtt.c/src) + +target_link_libraries(appqthomecontrol + PRIVATE Qt6::Quick) + +if (WIN32) + target_link_libraries (appqthomecontrol PRIVATE ${CMAKE_SOURCE_DIR}/paho.mqtt.c/src/Release/paho-mqtt3a.lib ${CMAKE_SOURCE_DIR}/paho.mqtt.c/src/Release/paho-mqtt3c.lib) +endif (WIN32) +if (UNIX) + target_link_libraries (appqthomecontrol PRIVATE paho-mqtt3a paho-mqtt3c) +endif(UNIX) + +install(TARGETS appqthomecontrol + BUNDLE DESTINATION . + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..e4ddce2 --- /dev/null +++ b/main.cpp @@ -0,0 +1,19 @@ +#include +#include + + +int main(int argc, char *argv[]) +{ + QGuiApplication app(argc, argv); + + QQmlApplicationEngine engine; + const QUrl url(u"qrc:/qthomecontrol/main.qml"_qs); + QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, + &app, [url](QObject *obj, const QUrl &objUrl) { + if (!obj && url == objUrl) + QCoreApplication::exit(-1); + }, Qt::QueuedConnection); + engine.load(url); + + return app.exec(); +} diff --git a/main.qml b/main.qml new file mode 100644 index 0000000..b5b938d --- /dev/null +++ b/main.qml @@ -0,0 +1,56 @@ +import QtQuick +import QtQuick.Controls 6.3 +import QtQuick.Layouts 6.3 +import qthomecontrol + +Window { + width: 720 + height: 720 + visible: true + color: "#000000" + title: qsTr("Hello World") + + MQTTInterface { + id: mqttinterface + } + + Slider { + id: slider + x: 13 + y: 534 + width: 695 + height: 178 + live: true + antialiasing: false + topPadding: 0 + orientation: Qt.Horizontal + snapMode: RangeSlider.SnapOnRelease + stepSize: 1 + to: 100 + value: 0 + } + + Text { + id: text1 + x: 206 + y: 114 + width: 309 + height: 129 + color: "#ffffff" + text: mqttinterface.bedroomTemperature.toFixed(1) + " °C" + font.pixelSize: 92 + horizontalAlignment: Text.AlignHCenter + } + + Text { + id: text2 + x: 206 + y: 285 + width: 309 + height: 129 + color: "#ffffff" + text: mqttinterface.bedroomHumidity.toFixed(0) + " %" + font.pixelSize: 92 + horizontalAlignment: Text.AlignHCenter + } +} diff --git a/mqttinterface.cpp b/mqttinterface.cpp new file mode 100644 index 0000000..cb8b81d --- /dev/null +++ b/mqttinterface.cpp @@ -0,0 +1,115 @@ +#include "mqttinterface.h" + +using namespace std; + +void delivered(void* context, MQTTClient_deliveryToken dt) +{ + MQTTInterface* thermostat = static_cast(context); + //thermostat->DeliverToken(dt); +} + +int msgarrvd(void* context, char* topicName, int topicLen, MQTTClient_message* message) +{ + MQTTInterface* thermostat = static_cast(context); + + if (string(topicName) == "bedroom/thermostat/temperature/raw") { + float temperature = stof(string((char*)message->payload)); + thermostat->updateBedroomTemperature(temperature); + } else if (string(topicName) == "bedroom/thermostat/humidity/raw") { + float humidity = stof(string((char*)message->payload)); + thermostat->updateBedroomHumidity(humidity); + } + + MQTTClient_freeMessage(&message); + MQTTClient_free(topicName); + + + return 1; +} + +void connlost(void* context, char* cause) +{ + MQTTInterface* thermostat = static_cast(context); + + printf("\nConnection lost\n"); + printf(" cause: %s\n", cause); + + //thermostat->ShouldReconnect(); +} + +#define QOS 1 + +MQTTInterface::MQTTInterface(QObject *parent) + : QObject{parent} +{ + MQTTClient_message pubmsg = MQTTClient_message_initializer; + MQTTClient_deliveryToken token; + + int rc; + MQTTClient_create(&mClient, "tcp://10.0.1.213:1883", "bedroom-thermostat-fe", + MQTTCLIENT_PERSISTENCE_NONE, NULL); + + if ((rc = MQTTClient_setCallbacks(mClient, this, connlost, msgarrvd, delivered)) != MQTTCLIENT_SUCCESS) + { + printf("Failed to set callbacks, return code %d\n", rc); + return; + } + + connectToServer(); + + char cbuf[256]; + sprintf(cbuf, "%.1f", 15.3); + pubmsg.payload = (void*)cbuf; + pubmsg.payloadlen = strlen(cbuf); + pubmsg.qos = QOS; + pubmsg.retained = 0; + MQTTClient_publishMessage(mClient, "newtest/test", &pubmsg, &token); +} + +void +MQTTInterface::updateBedroomTemperature(float aTemperature) +{ + { + unique_lock lk(mDataMutex); + mBedroomTemperature = aTemperature; + } + + emit bedroomTemperatureChanged(); +} + +void +MQTTInterface::updateBedroomHumidity(float aHumidity) +{ + { + unique_lock lk(mDataMutex); + mBedroomHumidity = aHumidity; + } + + emit bedroomHumidityChanged(); +} + +void +MQTTInterface::connectToServer() +{ + int rc = 0; + MQTTClient_disconnect(mClient, 10000); + + MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer; + conn_opts.keepAliveInterval = 20; + conn_opts.cleansession = 1; + + if ((rc = MQTTClient_connect(mClient, &conn_opts)) != MQTTCLIENT_SUCCESS) + { + printf("Failed to connect, return code %d\n", rc); + return; + } + + if ((rc = MQTTClient_subscribe(mClient, "bedroom/thermostat/temperature/raw", QOS)) != MQTTCLIENT_SUCCESS) + { + printf("Failed to subscribe, return code %d\n", rc); + } + if ((rc = MQTTClient_subscribe(mClient, "bedroom/thermostat/humidity/raw", QOS)) != MQTTCLIENT_SUCCESS) + { + printf("Failed to subscribe, return code %d\n", rc); + } +} diff --git a/mqttinterface.h b/mqttinterface.h new file mode 100644 index 0000000..1b54a9a --- /dev/null +++ b/mqttinterface.h @@ -0,0 +1,45 @@ +#ifndef MQTTINTERFACE_H +#define MQTTINTERFACE_H + +#include +#include +#include + +#include "MQTTClient.h" + +class MQTTInterface : public QObject +{ + Q_OBJECT + Q_PROPERTY(qreal bedroomTemperature READ bedroomTemperature NOTIFY bedroomTemperatureChanged); + Q_PROPERTY(qreal bedroomHumidity READ bedroomHumidity NOTIFY bedroomHumidityChanged); + QML_ELEMENT + +public: + explicit MQTTInterface(QObject *parent = nullptr); + + qreal bedroomTemperature() { + std::unique_lock lk(mDataMutex); + return mBedroomTemperature; + } + qreal bedroomHumidity() { + std::unique_lock lk(mDataMutex); + return mBedroomHumidity; + } + + void updateBedroomTemperature(float aTemperature); + void updateBedroomHumidity(float aHumidity); + +signals: + void bedroomTemperatureChanged(); + void bedroomHumidityChanged(); + +private: + void connectToServer(); + + qreal mBedroomTemperature = 10; + qreal mBedroomHumidity = 50; + MQTTClient mClient; + std::mutex mDataMutex; +}; + +#endif // MQTTINTERFACE_H diff --git a/paho.mqtt.c b/paho.mqtt.c new file mode 160000 index 0000000..f7799da --- /dev/null +++ b/paho.mqtt.c @@ -0,0 +1 @@ +Subproject commit f7799da95e347bbc930b201b52a1173ebbad45a7 -- 2.47.3