From 05e86b1bdf52daf21682b7f82c13300edb1cc08c Mon Sep 17 00:00:00 2001 From: Bas Schouten Date: Tue, 23 Apr 2024 12:08:52 +0200 Subject: [PATCH] Update with new content for the office thermostat and use WebSockets directly to OpenHAB instead of MQTT. --- .gitmodules | 3 - CMakeLists.txt | 56 ++++-- Main.qml | 56 ------ MainBedroom.qml | 360 ++++++++++++++++++++++++++++++++++++++ MainOffice.qml | 376 ++++++++++++++++++++++++++++++++++++++++ backlightcontroller.cpp | 91 ++++++++++ backlightcontroller.h | 27 +++ fonts/DigitalDream.ttf | Bin 0 -> 29320 bytes fonts/digital-7.ttf | Bin 0 -> 34360 bytes images/energy.png | Bin 0 -> 704 bytes images/frontdoor.png | Bin 0 -> 329 bytes images/humidity.png | Bin 0 -> 1403 bytes images/light.png | Bin 0 -> 459 bytes images/moon.png | Bin 0 -> 550 bytes images/pressure.png | Bin 0 -> 901 bytes images/temperature.png | Bin 0 -> 349 bytes itemdefinitions.h | 15 ++ main.cpp | 19 +- mqttinterface.cpp | 115 ------------ mqttinterface.h | 45 ----- openhabinterface.cpp | 131 ++++++++++++++ openhabinterface.h | 102 +++++++++++ paho.mqtt.c | 1 - qtquickcontrols2.conf | 5 + resources.qrc | 5 + 25 files changed, 1168 insertions(+), 239 deletions(-) delete mode 100644 Main.qml create mode 100644 MainBedroom.qml create mode 100644 MainOffice.qml create mode 100644 backlightcontroller.cpp create mode 100644 backlightcontroller.h create mode 100644 fonts/DigitalDream.ttf create mode 100644 fonts/digital-7.ttf create mode 100644 images/energy.png create mode 100644 images/frontdoor.png create mode 100644 images/humidity.png create mode 100644 images/light.png create mode 100644 images/moon.png create mode 100644 images/pressure.png create mode 100644 images/temperature.png create mode 100644 itemdefinitions.h delete mode 100644 mqttinterface.cpp delete mode 100644 mqttinterface.h create mode 100644 openhabinterface.cpp create mode 100644 openhabinterface.h delete mode 160000 paho.mqtt.c create mode 100644 qtquickcontrols2.conf create mode 100644 resources.qrc diff --git a/.gitmodules b/.gitmodules index e0e40ec..e69de29 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "paho.mqtt.c"] - path = paho.mqtt.c - url = https://github.com/eclipse/paho.mqtt.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 11378e0..ba339e4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,26 +5,51 @@ project(qthomecontrol VERSION 0.1 LANGUAGES CXX) set(CMAKE_AUTOMOC ON) set(CMAKE_CXX_STANDARD_REQUIRED ON) -find_package(Qt6 6.5 COMPONENTS Quick REQUIRED) +find_package(Qt6 6.5 COMPONENTS Quick QuickControls2 WebSockets REQUIRED) qt_standard_project_setup(REQUIRES 6.5) -if (UNIX) - add_subdirectory ("paho.mqtt.c") -endif(UNIX) - qt_add_executable(appqthomecontrol main.cpp - mqttinterface.cpp - mqttinterface.h + openhabinterface.cpp + openhabinterface.h + backlightcontroller.h + backlightcontroller.cpp ) qt_add_qml_module(appqthomecontrol URI qthomecontrol VERSION 1.0 - QML_FILES Main.qml + QML_FILES MainBedroom.qml MainOffice.qml SOURCES - mqttinterface.h mqttinterface.cpp + openhabinterface.h openhabinterface.cpp + backlightcontroller.h backlightcontroller.cpp + itemdefinitions.h +) + +qt_add_resources(appqthomecontrol "configuration" + PREFIX "/" + FILES + qtquickcontrols2.conf +) + +qt_add_resources(appqthomecontrol "images" + PREFIX "/" + FILES + images/temperature.png + images/humidity.png + images/light.png + images/energy.png + images/frontdoor.png + images/moon.png + images/pressure.png +) + +qt_add_resources(appqthomecontrol "fonts" + PREFIX "/" + FILES + fonts/digital-7.ttf + ) set_target_properties(appqthomecontrol PROPERTIES @@ -35,17 +60,14 @@ set_target_properties(appqthomecontrol PROPERTIES 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) +target_link_libraries (appqthomecontrol PRIVATE Qt6::QuickControls2 Qt6::WebSockets) + +if (USE_PWM) + add_compile_definitions(USE_PWM) +endif(USE_PWM) install(TARGETS appqthomecontrol BUNDLE DESTINATION . diff --git a/Main.qml b/Main.qml deleted file mode 100644 index 7b52a7a..0000000 --- a/Main.qml +++ /dev/null @@ -1,56 +0,0 @@ -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("Home Control") - - 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/MainBedroom.qml b/MainBedroom.qml new file mode 100644 index 0000000..28526c2 --- /dev/null +++ b/MainBedroom.qml @@ -0,0 +1,360 @@ +import QtQuick +import QtQuick.Controls 6.3 +import QtQuick.Layouts 6.3 +import qthomecontrol + +Window { + width: 720 + height: 720 + visible: true + title: qsTr("Home Control") + visibility: "FullScreen" + color: Material.backgroundColor + + OpenHABInterface { + id: openHABInterface + } + + BackLightController { + id: backlightController + } + + FontLoader { id: digital; source: "qrc:/fonts/digital-7.ttf" } + + Label { + id: textTime + x: 620 + y: 10 + width: 90 + height: 50 + font.pixelSize: 36 + horizontalAlignment: Text.AlignRight + verticalAlignment: Text.AlignTop + } + + + Timer { + id: timer + interval: 1000 + repeat: true + running: true + + onTriggered: + { + textTime.text = Qt.formatTime(new Date(),"hh:mm") + } + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + propagateComposedEvents: true + onPositionChanged: { + backlightController.hadInteraction(); + mouse.accepted = false; + } + onClicked: { + backlightController.hadInteraction(); + mouse.accepted = false; + } + onPressed: { + backlightController.hadInteraction(); + mouse.accepted = false; + } + onReleased: mouse.accepted = false; + onDoubleClicked: mouse.accepted = false; + onPressAndHold: mouse.accepted = false; + z: 100 + } + + GroupBox { + id: groupBox + x: 15 + y: 10 + width: 483 + height: 430 + label: Label { + id: bedroomLabel + text: "Bedroom" + font.pixelSize: 32 + } + Slider { + id: slider + x: 245 + y: 83 + width: 190 + height: 25 + live: true + antialiasing: true + topPadding: 0 + bottomPadding: 0 + orientation: Qt.Horizontal + snapMode: RangeSlider.SnapOnRelease + stepSize: 1 + to: 100 + value: openHABInterface.bedroomDimmer + scale: 1.5 + onMoved: + { + openHABInterface.bedroomDimmer = slider.value + } + } + + Label { + id: textTemp + x: 50 + y: 0 + width: 100 + height: 60 + text: openHABInterface.bedroomTemperature.toFixed(1) + font.pixelSize: 52 + font.family: digital.font.family + horizontalAlignment: Text.AlignRight + verticalAlignment: Text.AlignBottom + } + Label { + id: unitTemp + x: 155 + y: 0 + width: 30 + height: 60 + text: "°C" + font.pixelSize: 32 + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignBottom + } + + Label { + id: textHumid + x: 50 + y: 60 + width: 100 + height: 60 + text: openHABInterface.bedroomHumidity.toFixed(0) + font.pixelSize: 52 + font.family: digital.font.family + horizontalAlignment: Text.AlignRight + verticalAlignment: Text.AlignBottom + } + Label { + id: unitHumid + x: 155 + y: 60 + width: 30 + height: 60 + text: "%" + font.pixelSize: 32 + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignBottom + } + + Label { + id: textLight + x: 250 + y: 0 + width: 100 + height: 60 + text: openHABInterface.bedroomDimmer.toFixed(0) + font.pixelSize: 52 + font.family: digital.font.family + horizontalAlignment: Text.AlignRight + verticalAlignment: Text.AlignBottom + } + Label { + id: unitDimmer + x: 357 + y: 0 + width: 30 + height: 60 + text: "%" + font.pixelSize: 32 + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignBottom + } + + Image { + id: tempImage + x: 10 + y: 0 + width: 54 + height: 60 + source: "qrc:/images/temperature.png" + fillMode: Image.PreserveAspectFit + } + Image { + id: humidImage + x: 10 + y: 60 + width: 54 + height: 60 + source: "qrc:/images/humidity.png" + fillMode: Image.PreserveAspectFit + } + Image { + id: lightImage + x: 235 + y: 0 + width: 54 + height: 60 + source: "qrc:/images/light.png" + fillMode: Image.PreserveAspectFit + } + Dial { + id: dialAC + x: 35 + y: 230 + inputMode: Dial.Vertical + scale: 1.5 + + from: 16 + value: openHABInterface.bedroomACSetPoint + to: 24 + stepSize: 0.5 + snapMode: Dial.SnapAlways + + onMoved: { + openHABInterface.bedroomACSetPoint = dialAC.value + } + + Label { + x: 0 + width: dialAC.width + y: 0 + height: dialAC.height + id: thermoSetLabel + text: dialAC.value.toFixed(1) + font.pixelSize: 32 + font.family: digital.font.family + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + } + Dial { + id: dialFloor + x: 50 + dialFloor.width * 1.5 + y: 230 + inputMode: Dial.Vertical + scale: 1.5 + + from: 16 + value: openHABInterface.bedroomFloorSetPoint + to: 24 + stepSize: 0.5 + snapMode: Dial.SnapAlways + + onMoved: { + openHABInterface.bedroomFloorSetPoint = dialFloor.value + } + + Label { + x: 0 + width: dialFloor.width + y: 0 + height: dialFloor.height + id: thermoFloorSetLabel + text: dialFloor.value.toFixed(1) + font.pixelSize: 32 + font.family: digital.font.family + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + } + + + Switch { + id: switchAC + x: 35 + width: dialAC.width + y: 143 + checked: openHABInterface.bedroomACToggle + scale: 1.5 + onToggled: + { + openHABInterface.bedroomACToggle = switchAC.checked + } + } + + } + + RoundButton { + id: allLightsOffButton + x: 610 + y: 610 + width: 100 + height: 100 + icon.source: "qrc:/images/moon.png" + icon.name: "night" + icon.width: 64 + icon.height: 64 + onClicked: + { + openHABInterface.signalItem("BedroomGoodnight") + } + } + + + Image { + id: outdoorTempImage + x: 10 + y: 590 + width: 54 + height: 60 + source: "qrc:/images/frontdoor.png" + fillMode: Image.PreserveAspectFit + } + Label { + id: outdoorTemp + x: 70 + y: 590 + width: 100 + height: 60 + text: openHABInterface.outdoorTemperature.toFixed(1) + font.pixelSize: 52 + font.family: digital.font.family + horizontalAlignment: Text.AlignRight + verticalAlignment: Text.AlignBottom + } + Label { + id: unitOutdoorTemp + x: 175 + y: 590 + width: 30 + height: 60 + text: "°C" + font.pixelSize: 32 + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignBottom + } + Image { + id: powerImage + x: 10 + y: 650 + width: 54 + height: 60 + source: "qrc:/images/energy.png" + fillMode: Image.PreserveAspectFit + } + Label { + id: textPower + x: 70 + y: 650 + width: 100 + height: 60 + text: openHABInterface.mainPowerUsage < 1000 ? openHABInterface.mainPowerUsage.toFixed(0) : (openHABInterface.mainPowerUsage / 1000).toFixed(2) + font.pixelSize: 52 + font.family: digital.font.family + horizontalAlignment: Text.AlignRight + verticalAlignment: Text.AlignBottom + } + Label { + id: unitPower + x: 175 + y: 650 + width: 30 + height: 60 + text: openHABInterface.mainPowerUsage < 1000 ? "W" : "kW" + font.pixelSize: 32 + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignBottom + } +} diff --git a/MainOffice.qml b/MainOffice.qml new file mode 100644 index 0000000..c77b76e --- /dev/null +++ b/MainOffice.qml @@ -0,0 +1,376 @@ +import QtQuick +import QtQuick.Controls 6.3 +import QtQuick.Layouts 6.3 +import qthomecontrol + +Window { + width: 720 + height: 720 + visible: true + title: qsTr("Home Control") + visibility: "FullScreen" + color: Material.backgroundColor + + OpenHABInterface { + id: openHABInterface + } + + BackLightController { + id: backlightController + } + + FontLoader { id: digital; source: "qrc:/fonts/digital-7.ttf" } + + Label { + id: textTime + x: 620 + y: 10 + width: 90 + height: 50 + font.pixelSize: 36 + horizontalAlignment: Text.AlignRight + verticalAlignment: Text.AlignTop + } + + + Timer { + id: timer + interval: 1000 + repeat: true + running: true + + onTriggered: + { + textTime.text = Qt.formatTime(new Date(),"hh:mm") + } + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + propagateComposedEvents: true + onPositionChanged: { + backlightController.hadInteraction(); + mouse.accepted = false; + } + onClicked: { + backlightController.hadInteraction(); + mouse.accepted = false; + } + onPressed: { + backlightController.hadInteraction(); + mouse.accepted = false; + } + onReleased: mouse.accepted = false; + onDoubleClicked: mouse.accepted = false; + onPressAndHold: mouse.accepted = false; + z: 100 + } + + GroupBox { + id: groupBox + x: 15 + y: 10 + width: 483 + height: 490 + label: Label { + id: officeLabel + text: "Office" + font.pixelSize: 32 + } + Slider { + id: slider + x: 245 + y: 83 + width: 190 + height: 25 + live: true + antialiasing: true + topPadding: 0 + bottomPadding: 0 + orientation: Qt.Horizontal + snapMode: RangeSlider.SnapOnRelease + stepSize: 1 + to: 100 + value: openHABInterface.officeDimmer + scale: 1.5 + onMoved: + { + openHABInterface.officeDimmer = slider.value + } + } + + Label { + id: textTemp + x: 50 + y: 0 + width: 100 + height: 60 + text: openHABInterface.officeTemperature.toFixed(1) + font.pixelSize: 52 + font.family: digital.font.family + horizontalAlignment: Text.AlignRight + verticalAlignment: Text.AlignBottom + } + Label { + id: unitTemp + x: 155 + y: 0 + width: 30 + height: 60 + text: "°C" + font.pixelSize: 32 + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignBottom + } + + Label { + id: textHumid + x: 50 + y: 60 + width: 100 + height: 60 + text: openHABInterface.officeHumidity.toFixed(0) + font.pixelSize: 52 + font.family: digital.font.family + horizontalAlignment: Text.AlignRight + verticalAlignment: Text.AlignBottom + } + Label { + id: unitHumid + x: 155 + y: 60 + width: 30 + height: 60 + text: "%" + font.pixelSize: 32 + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignBottom + } + + Label { + id: textPressure + x: 50 + y: 120 + width: 150 + height: 60 + text: openHABInterface.officePressure.toFixed(1) + font.pixelSize: 52 + font.family: digital.font.family + horizontalAlignment: Text.AlignRight + verticalAlignment: Text.AlignBottom + } + Label { + id: unitPressure + x: 205 + y: 120 + width: 30 + height: 60 + text: "hPa" + font.pixelSize: 32 + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignBottom + } + + Label { + id: textLight + x: 250 + y: 0 + width: 100 + height: 60 + text: openHABInterface.officeDimmer.toFixed(0) + font.pixelSize: 52 + font.family: digital.font.family + horizontalAlignment: Text.AlignRight + verticalAlignment: Text.AlignBottom + } + Label { + id: unitDimmer + x: 357 + y: 0 + width: 30 + height: 60 + text: "%" + font.pixelSize: 32 + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignBottom + } + + Image { + id: tempImage + x: 10 + y: 0 + width: 54 + height: 60 + source: "qrc:/images/temperature.png" + fillMode: Image.PreserveAspectFit + } + Image { + id: humidImage + x: 10 + y: 60 + width: 54 + height: 60 + source: "qrc:/images/humidity.png" + fillMode: Image.PreserveAspectFit + } + Image { + id: pressureImage + x: 10 + y: 120 + width: 54 + height: 60 + source: "qrc:/images/pressure.png" + fillMode: Image.PreserveAspectFit + } + Image { + id: lightImage + x: 235 + y: 0 + width: 54 + height: 60 + source: "qrc:/images/light.png" + fillMode: Image.PreserveAspectFit + } + Dial { + id: dialAC + x: 35 + y: 290 + inputMode: Dial.Vertical + scale: 1.5 + + from: 16 + value: openHABInterface.officeACSetPoint + to: 26 + stepSize: 0.5 + snapMode: Dial.SnapAlways + + onMoved: { + openHABInterface.officeACSetPoint = dialAC.value + } + + Label { + x: 0 + width: dialAC.width + y: 0 + height: dialAC.height + id: thermoSetLabel + text: dialAC.value.toFixed(1) + font.pixelSize: 32 + font.family: digital.font.family + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + } + Dial { + id: dialFloor + x: 50 + dialFloor.width * 1.5 + y: 290 + inputMode: Dial.Vertical + scale: 1.5 + + from: 16 + value: openHABInterface.officeFloorSetPoint + to: 24 + stepSize: 0.5 + snapMode: Dial.SnapAlways + + onMoved: { + openHABInterface.officeFloorSetPoint = dialFloor.value + } + + Label { + x: 0 + width: dialFloor.width + y: 0 + height: dialFloor.height + id: thermoFloorSetLabel + text: dialFloor.value.toFixed(1) + font.pixelSize: 32 + font.family: digital.font.family + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + } + + + Switch { + id: switchAC + x: 35 + width: dialAC.width + y: 203 + checked: openHABInterface.officeACToggle + scale: 1.5 + onToggled: + { + openHABInterface.officeACToggle = switchAC.checked + } + } + + } + + Image { + id: outdoorTempImage + x: 10 + y: 590 + width: 54 + height: 60 + source: "qrc:/images/frontdoor.png" + fillMode: Image.PreserveAspectFit + } + Label { + id: outdoorTemp + x: 70 + y: 590 + width: 100 + height: 60 + text: openHABInterface.outdoorTemperature.toFixed(1) + font.pixelSize: 52 + font.family: digital.font.family + horizontalAlignment: Text.AlignRight + verticalAlignment: Text.AlignBottom + } + Label { + id: unitOutdoorTemp + x: 175 + y: 590 + width: 30 + height: 60 + text: "°C" + font.pixelSize: 32 + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignBottom + } + Image { + id: powerImage + x: 10 + y: 650 + width: 54 + height: 60 + source: "qrc:/images/energy.png" + fillMode: Image.PreserveAspectFit + } + Label { + id: textPower + x: 70 + y: 650 + width: 100 + height: 60 + text: openHABInterface.mainPowerUsage < 1000 ? openHABInterface.mainPowerUsage.toFixed(0) : (openHABInterface.mainPowerUsage / 1000).toFixed(2) + font.pixelSize: 52 + font.family: digital.font.family + horizontalAlignment: Text.AlignRight + verticalAlignment: Text.AlignBottom + } + Label { + id: unitPower + x: 175 + y: 650 + width: 30 + height: 60 + text: openHABInterface.mainPowerUsage < 1000 ? "W" : "kW" + font.pixelSize: 32 + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignBottom + } +} diff --git a/backlightcontroller.cpp b/backlightcontroller.cpp new file mode 100644 index 0000000..3cb8f92 --- /dev/null +++ b/backlightcontroller.cpp @@ -0,0 +1,91 @@ +#include "backlightcontroller.h" +#ifdef USE_PWM +#define PWM_PERIOD "500000" +#define PWM_DUTYCYCLE_DIM "380000" +#define PWM_DUTYCYCLE_ON "100000" + +#include +#include +#include +#include +#include +#endif + +BackLightController::BackLightController(QObject *parent) + : QObject{parent} +{ +#ifdef USE_PWM + int fd; + + fd = open("/sys/class/pwm/pwmchip0/export", O_WRONLY); + if (-1 == fd) { + fprintf(stderr, "Failed to open export for writing.\n"); + } + write(fd, "0", 2); + close(fd); + + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + + fd = open("/sys/class/pwm/pwmchip0/pwm0/period", O_WRONLY); + if (-1 == fd) { + fprintf(stderr, "Failed to open PWM period for writing.\n"); + } + write(fd, PWM_PERIOD, strlen(PWM_PERIOD)); + close(fd); + + fd = open("/sys/class/pwm/pwmchip0/pwm0/duty_cycle", O_WRONLY); + if (-1 == fd) { + fprintf(stderr, "Failed to open PWM duty cycle for writing.\n"); + } + write(fd, PWM_DUTYCYCLE_DIM, strlen(PWM_DUTYCYCLE_DIM)); + close(fd); + + fd = open("/sys/class/pwm/pwmchip0/pwm0/enable", O_WRONLY); + if (-1 == fd) { + fprintf(stderr, "Failed to open PWM for writing.\n"); + } + write(fd, "1", 2); + close(fd); +#endif + updateBackLight(false); +} + +void +BackLightController::hadInteraction() +{ + updateBackLight(true); + mOffTimer.reset(new QTimer(this)); + mOffTimer->callOnTimeout(this, &BackLightController::backlightTimedOut); + mOffTimer->setSingleShot(true); + mOffTimer->start(10000); +} + +void +BackLightController::backlightTimedOut() +{ + updateBackLight(false); +} + +void +BackLightController::updateBackLight(bool aState) +{ + if (mBackLightIsHigh == aState) { + return; + } + +#ifdef USE_PWM + int fd; + fd = open("/sys/class/pwm/pwmchip0/pwm0/duty_cycle", O_WRONLY); + if (-1 == fd) { + fprintf(stderr, "Failed to open PWM duty cycle for writing.\n"); + } + write(fd, aState ? PWM_DUTYCYCLE_ON : PWM_DUTYCYCLE_DIM, aState ? strlen(PWM_DUTYCYCLE_ON) : strlen(PWM_DUTYCYCLE_DIM)); + close(fd); +#endif + if (aState) { + // turn on backlight + mBackLightIsHigh = true; + } else { + mBackLightIsHigh = false; + } +} diff --git a/backlightcontroller.h b/backlightcontroller.h new file mode 100644 index 0000000..19b1788 --- /dev/null +++ b/backlightcontroller.h @@ -0,0 +1,27 @@ +#ifndef BACKLIGHTCONTROLLER_H +#define BACKLIGHTCONTROLLER_H + +#include +#include +#include +#include + +class BackLightController : public QObject +{ + Q_OBJECT + QML_ELEMENT +public: + explicit BackLightController(QObject *parent = nullptr); + +public slots: + void hadInteraction(); + void backlightTimedOut(); + +private: + void updateBackLight(bool aState); + + bool mBackLightIsHigh = false; + QScopedPointer mOffTimer; +}; + +#endif // BACKLIGHTCONTROLLER_H diff --git a/fonts/DigitalDream.ttf b/fonts/DigitalDream.ttf new file mode 100644 index 0000000000000000000000000000000000000000..25ab5de5a3a75144e94852b67b4449ca69cb90cd GIT binary patch literal 29320 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 Hu8IEx0x4|* literal 0 HcmV?d00001 diff --git a/fonts/digital-7.ttf b/fonts/digital-7.ttf new file mode 100644 index 0000000000000000000000000000000000000000..5dbe6f908239d00a9f88c335df3efb9261ecc3cc GIT binary patch literal 34360 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 Vgr~3VDFwQl!PC{xWt~$(69DB|ghBuS literal 0 HcmV?d00001 diff --git a/images/humidity.png b/images/humidity.png new file mode 100644 index 0000000000000000000000000000000000000000..00f58df59a41ecc38d195780e2a89e1bdbc94cb1 GIT binary patch literal 1403 zcmV->1%&#EP)@*SwiR^U(9qGew6zm*+_<>7wY9alxVW^mw6wLh%*)Iccjgjt*c5Wvx3{;pwzd*- z+Y@rx#>U3Cx3|N?!?m@w5^>qNxw#W^+Y)lynw_GxwY9gmx45{tk*2+3m(P}}zwq$z z9)kZaedX`~6adIP?CtI8>gnI#-`CgI<>ltBtgRe>`5G4+n!W59eDIo?nv05z@Q9Gu z?B2h>!0_1GJci`b-tUm$sNLo7@DnCfl;~`u=kP5(@JUxB&Q~oeE)vU0Lep-0dV9d= z%(=L^Syx){bbj#0%7EUTywdFO>FHP3eekxsfv@Oyb$BX-?1F%TlD@`)0000obW%=J z00{W!|Nivg8TDEA`}rpQm~ppA^ndj#gvirq_HLY-Wcknk|NsC0|NsC0|NsC0|NbPS z*`@#h1N=!uK~z}7?N@1c+By)8!Aro#tRaM)_VqoP#48SVAS7fZCD4+lP5b}g zWGq7HIqzHNix)JPJ9iezdwYNFf3W>>?MTxVMc0mM<^7$#>sq1ZxxS6fcfEE&tM7ub z|FO_=QO4X>;h*VN%N*dGe3Rb`1Y@>x{i zKY{!9!G`+NL4b?vb4Vesy#N*~0g5n;M_!?`F(CN5xF$au;BkL)a!&Q0d6dBd$upXp zvtI8Ou0~Brpa84RPq2L@%aw>;7ATsNyUQN7Gr%s0DAZf{r*GHT|DIJY?We^8`Ap1P z3^0cKmZ%ssml3$Wz{C^%kr%$w8?44;%qX~;C<0h@hXKA|nc_HUs9#}B=tL2I^8*9i zz@s3BGDPqd6o5RUQPP3akV-)mo`4FMkjZ>G);t!$eWrzjFbufI8y16?&%=>RbI6j& z%wB}yhc05nz0;wf+; zaHdSB3jaJwLy(FC%i<|$ekvl4*UQgj57Lx~=x7~G08?m+(kRET^E|-(HI`Rt{7>xz zK))#E3h55HhAr9_Y{N|pNrnKxDxD3bEVi_i|E*Ze(jYR;1f`O7rH@H7Jy#QdE{L?9 z2?cqULGWiXH&G_NehOe!8lr*X&EzZt83r61DE4?@ahAxyjkGVw$>q^=ND6?!?N8p{ z>L)VPpylnm)lghB62J}Af@Q5Cn3=Jj$cXDO#;rV(xn3S$W(A-&=<*xO_tbwhcVgFG z%@g~q_nZrW&n4U1QvM?YMV`W+buJ;0ynmX@!%`-9b`%GGfU;%zLqGs$;yAOt^E1&8 ze#JcOxGw@o+p@lf;3{r}3~sFYRgpiAVf~1c5U`!tQC4Hs*NZ{`OXUtJTn)+C)r(fW z?Ug=l1%P9oFYZMjj(O^q5VjsPyKjrg+M*^p>ek49*eSl1#udZ|tlTcdAR@p002ov JPDHLkV1iQ|*FOLN literal 0 HcmV?d00001 diff --git a/images/light.png b/images/light.png new file mode 100644 index 0000000000000000000000000000000000000000..2e5b762dd49c26f3447c17ac4135c45ae890268a GIT binary patch literal 459 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I3?%1nZ+ru!JOX?|T-^(NDl01fpY;8I+#AF= z;fsQR;y?rx1d|VTMnYtvDo^@VmX`xXfZSWPq7^_h#Y%$wf*GU^3%)pW`@^rf-dZt= z>$#jdLn?Cjyn4F2oq>T-#?!?yB;xSfskem=De$;VWa69>6)pPzzyH;hk1eJyI@)-r!^!qmM{XAoy(|u3>`mO#PfnDd8?!2(I=09VQTfu{0x7Yl4 zOGwLziJI{EYvO#dr;)rKEf%gBTpQAT3?@eH4^WpXSe2=`p3R;4jut}(!y2XuY!AK* zhkRd9#pNzl5L178t9fALsr8cmngI<5D$l#HoQ~31XvDBl(4<^bpYcX^ujrGnViHYz}jHR&%zJO7;rFdOzXIL^a>Z_ z=dXN0|JQI@bTIC%KgqB!eE;=xG4qUe*E4JrvWq=r|L$UJKhH7=1HXoiHis(Ap4qJO b-LfwKe)QCp)?Y3GLz2PM)z4*}Q$iB}ybjO_ literal 0 HcmV?d00001 diff --git a/images/moon.png b/images/moon.png new file mode 100644 index 0000000000000000000000000000000000000000..bbe7893b82c21c34b0dbacbd16fc0d8725075c9b GIT binary patch literal 550 zcmV+>0@?kEP)(}$=lS)+ttY3*2>-0$=%h-@95g<^QxXamESMtJ;?%)KT1Y{P z)J0LmrJ#f0*e-&&bP!!r5ND;}?4TfSHn^P(tvCqve=+Gx(zN#8xAd*&m;25;@8-Tp zWX|j6{d}8|Yn^PH`}|5#S^ZM$-*I|fLzdH%I;=eoI2~eOFnT%%EO7-ofMqgzY8m)w zGQcB%A4ZQm1y~EMp#!Wiz=5ONrep`}ga)ea4%{KSZ~SCz z=*obG3>bN2PLUJ^}zi&?pl|)B*$3QH(?)2Okk54 zAi}1}j380MknCX>=rQOWB-GrAak+F*0-15&+=fSS;KJVUzpJ;WhPh7^g-|=5d=AQE zB7c7!+d0cj_Gn^1*aY}e^LpuJy1A7Q0rdZ0+^!ZP`fIbEMZMy?C>_mAG}%!qzs@SD oZ?p3>A{Vc^q86W;^L+0A0GBdYnI)D1;{X5v07*qoM6N<$g04OXGynhq literal 0 HcmV?d00001 diff --git a/images/pressure.png b/images/pressure.png new file mode 100644 index 0000000000000000000000000000000000000000..d91d298515baf171c259dcd398355dd62deafee3 GIT binary patch literal 901 zcmV;01A6?4P)FI}s zhuYfOCw&G|*`3+03vs}NBsYZ&45T946mTv(KupcTAp>eMMWC=307AklDo z3~_EB>N^r3+5<y_x8_QT+{>xEhvA-Wwm z&h)9Th=WWV!6yPeZ|k#Jz4s5DUYL*w$^=uwr{}X7Y!jgq(|-}vHBn2?7106Wd7|5h z-)O(JQiba@TXV<)gj0_b4=>s^f1|$EMuR7tnSjJY_^n5TlFf7E&+6L{4(;ZVhT*2; zNuGzHO*WfwK*?QE?lvTNmO0VzXtdJca9>{{YDgC#U%m^PU>*E+|8zw%maYvD;Eg;G z;~}xRFrkZT$^(&yLcm8WP4}cA8qkBrV}K9_o}w@jaS@XNOjZa3pFF-!A+*UhEvrOF zPtnrc16LD)c$3R~Xri71P;~(FNfLRuu*9L0mEsbU>=M&>BoTY)YXPRCrd`I+0Vm?o zOpXC2W9|0{5Gzff*BE#E339*RhhezgzEl%X_%WS6ea`O|lqrU%(=~d8^(8I7p1Gt=C5hJ-)xIdIQ(4D%%ldnbCF^aU zwzk;>JZP>G-F9=G+B=%TA5b{3t@i#LFmfM^WyBO7Y!4V|#8EmgfuOz4nXx+~&SJ>Q z00LvZ&!Vz7WG%D* b?&zm~im9Je4>2PK00000NkvXXu0mjfoE^3_ literal 0 HcmV?d00001 diff --git a/images/temperature.png b/images/temperature.png new file mode 100644 index 0000000000000000000000000000000000000000..2fddbbe4424e209a6249cce1b472d58d8c20431c GIT binary patch literal 349 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I3?%1nZ+ru!>;rs4T-^(NZa;c*>md+5zWw;g ztw&F8-@XmvK6rBT(bL5~TaTPN1LXey|Ns288@qc?Eeqap@xA_Z>QhoI;_Ccx^%5t*7Nw^|IY8? za(?Z5FePWr=R41hKgt~BC{#3MR`T%@*xFEh&5E6SZQQ}Yc~2Xz9saY*oyqe4C%+2v5lcAdBo9d?A6!uQ})P)FVdQ&MBb@0LNaT_5c6? literal 0 HcmV?d00001 diff --git a/itemdefinitions.h b/itemdefinitions.h new file mode 100644 index 0000000..756bae7 --- /dev/null +++ b/itemdefinitions.h @@ -0,0 +1,15 @@ +DEFINE_ITEM(bedroomTemperature, Bedroom_Thermostat_Temperature, float, temperature) +DEFINE_ITEM(bedroomHumidity, Bedroom_Thermostat_Humidity, float, humidity) +DEFINE_ITEM(bedroomDimmer, BedroomLight_Dimmer, float, dimmer) +DEFINE_ITEM(bedroomACToggle, ACDeviceSlaapkamer_Power, bool, toggle) +DEFINE_ITEM(bedroomACSetPoint, AC_Device__Slaapkamer_Set_Temperature, float, temperatureWithUnit) +DEFINE_ITEM(bedroomFloorSetPoint, Bedroom_Thermostat_Setpoint, float, temperature) +DEFINE_ITEM(officeTemperature, Office_Thermostat_Temperature, float, temperature) +DEFINE_ITEM(officeHumidity, Office_Thermostat_Humidity, float, humidityWithUnit) +DEFINE_ITEM(officePressure, Office_Thermostat_Pressure, float, pressure) +DEFINE_ITEM(officeDimmer, OfficeLight_Dimmer, float, dimmer) +DEFINE_ITEM(officeACToggle, ACDeviceOffice_Power, bool, toggle) +DEFINE_ITEM(officeACSetPoint, ACDeviceOffice_SetTemperature, float, temperatureWithUnit) +DEFINE_ITEM(officeFloorSetPoint, Office_Thermostat_Setpoint, float, temperature) +DEFINE_ITEM(mainPowerUsage, MainElectricityMeter_ActualPowerDelivery, float, power) +DEFINE_ITEM(outdoorTemperature, Nibe_BT1_Outdoor_Temperature_Sensor_Nibe_BT1_Outdoor_Temperature, float, temperature) diff --git a/main.cpp b/main.cpp index 6086143..099898f 100644 --- a/main.cpp +++ b/main.cpp @@ -1,16 +1,31 @@ #include #include - +#include +#include +#include int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); + //app.setOverrideCursor(QCursor(Qt::BlankCursor)); + + QString panelString("MainBedroom"); + if (argc >= 2) { + for (int i = 0; i < argc; i++) { + if (!strncasecmp(argv[i], "-p", 2)) { + if (!strncasecmp(argv[++i], "office", 6)) { + panelString = "MainOffice"; + } + } + } + } + QQmlApplicationEngine engine; QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed, &app, []() { QCoreApplication::exit(-1); }, Qt::QueuedConnection); - engine.loadFromModule("qthomecontrol", "Main"); + engine.loadFromModule("qthomecontrol", panelString); return app.exec(); } diff --git a/mqttinterface.cpp b/mqttinterface.cpp deleted file mode 100644 index 2958895..0000000 --- a/mqttinterface.cpp +++ /dev/null @@ -1,115 +0,0 @@ -#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.225: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 deleted file mode 100644 index 1b54a9a..0000000 --- a/mqttinterface.h +++ /dev/null @@ -1,45 +0,0 @@ -#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/openhabinterface.cpp b/openhabinterface.cpp new file mode 100644 index 0000000..c09cf03 --- /dev/null +++ b/openhabinterface.cpp @@ -0,0 +1,131 @@ +#include "openhabinterface.h" + +#include +#include + +using namespace std; + +QJsonObject createCommandMessage(const char* aItemName) { + QJsonObject jsonObject; + jsonObject["type"] = "ItemCommandEvent"; + jsonObject["topic"] = QString("openhab/items/") + aItemName + QString("/command"); + jsonObject["source"] = "BedroomHomeControl"; + return jsonObject; +} + +OpenHABInterface::OpenHABInterface(QObject *parent) + : QObject{parent} +{ +#define DEFINE_ITEM(name, itemName, ctype, type) \ + mItems.push_back(ItemDescription{ &m_##name, "openhab/items/"#itemName"/state", #itemName, std::bind(&OpenHABInterface::name##Changed, this), ItemType::type }); +#include "itemdefinitions.h" +#undef DEFINE_ITEM + + connect(&mOpenHABSocket, &QWebSocket::errorOccurred, this, &OpenHABInterface::socketError); + connect(&mOpenHABSocket, &QWebSocket::textMessageReceived, this, &OpenHABInterface::openHABMessageReceived); + connectToServer(); + + for (const ItemDescription& item : mItems) { + if (item.mType == ItemType::toggle) { + *reinterpret_cast(item.mValue) = false; + } else { + *reinterpret_cast(item.mValue) = 0; + } + } +} + +void +OpenHABInterface::socketError(QAbstractSocket::SocketError aError) +{ +} + +void +OpenHABInterface::openHABMessageReceived(const QString& aMessage) +{ + QJsonDocument receivedMessage = QJsonDocument::fromJson(aMessage.toUtf8().data()); + + if (!receivedMessage["topic"].isString()) { + return; + } + + const QString& topic = receivedMessage["topic"].toString(); + + for (const ItemDescription& item : mItems) { + if (topic == item.mOpenHABState) { + if (item.mType == ItemType::dimmer || item.mType == ItemType::temperature || item.mType == ItemType::humidity || item.mType == ItemType::pressure) { + QJsonDocument payload = QJsonDocument::fromJson(receivedMessage["payload"].toString().toUtf8().data()); + float value = payload["value"].toString().toFloat(); + *reinterpret_cast(item.mValue) = value; + emit item.mSignal(); + } else if (item.mType == ItemType::humidityWithUnit) { + QJsonDocument payload = QJsonDocument::fromJson(receivedMessage["payload"].toString().toUtf8().data()); + QString valueString = payload["value"].toString(); + valueString.chop(2); + float value = valueString.toFloat(); + *reinterpret_cast(item.mValue) = value; + emit item.mSignal(); + } else if (item.mType == ItemType::temperatureWithUnit) { + QJsonDocument payload = QJsonDocument::fromJson(receivedMessage["payload"].toString().toUtf8().data()); + QString valueString = payload["value"].toString(); + valueString.chop(3); + float value = valueString.toFloat(); + *reinterpret_cast(item.mValue) = value; + emit item.mSignal(); + } else if (item.mType == ItemType::power) { + QJsonDocument payload = QJsonDocument::fromJson(receivedMessage["payload"].toString().toUtf8().data()); + QString valueString = payload["value"].toString(); + valueString.chop(3); + float value = valueString.toFloat() * 1000; + *reinterpret_cast(item.mValue) = value; + emit item.mSignal(); + } else if (item.mType == ItemType::toggle) { + QJsonDocument payload = QJsonDocument::fromJson(receivedMessage["payload"].toString().toUtf8().data()); + *reinterpret_cast(item.mValue) = payload["value"].toString() == "ON"; + emit item.mSignal(); + } + } + } +} + +void +OpenHABInterface::signalItem(const QString& aItem) +{ + QJsonObject commandObject = createCommandMessage(aItem.toStdString().c_str()); + commandObject["payload"] = "{\"type\":\"OnOff\",\"value\":\"OFF\"}"; + QJsonDocument commandDocument; + commandDocument.setObject(commandObject); + mOpenHABSocket.sendTextMessage(QString::fromUtf8(commandDocument.toJson())); +} + +void +OpenHABInterface::writeValue(const char* aItemName, ItemType aType, float aValue) +{ + QJsonObject commandObject = createCommandMessage(aItemName); + + if (aType == ItemType::dimmer) { + commandObject["payload"] = "{\"type\":\"Percent\",\"value\":\"" + QString::number(aValue, 'f', 0) + "\"}"; + } else if (aType == ItemType::temperature) { + commandObject["payload"] = "{\"type\":\"Decimal\",\"value\":\"" + QString::number(aValue, 'f', 1) + "\"}"; + } else if (aType == ItemType::temperatureWithUnit) { + commandObject["payload"] = "{\"type\":\"Quantity\",\"value\":\"" + QString::number(aValue, 'f', 1) + " °C\"}"; + } + QJsonDocument commandDocument; + commandDocument.setObject(commandObject); + mOpenHABSocket.sendTextMessage(QString::fromUtf8(commandDocument.toJson())); +} + +void +OpenHABInterface::writeValue(const char* aItemName, ItemType aType, bool aValue) +{ + QJsonObject commandObject = createCommandMessage(aItemName); + commandObject["payload"] = "{\"type\":\"OnOff\",\"value\":\"" + QString(aValue ? "ON" : "OFF") + "\"}"; + QJsonDocument commandDocument; + commandDocument.setObject(commandObject); + mOpenHABSocket.sendTextMessage(QString::fromUtf8(commandDocument.toJson())); +} + +void +OpenHABInterface::connectToServer() +{ + mOpenHABSocket.open(QUrl("ws://10.0.1.225:8080/ws")); +} diff --git a/openhabinterface.h b/openhabinterface.h new file mode 100644 index 0000000..d3c0b75 --- /dev/null +++ b/openhabinterface.h @@ -0,0 +1,102 @@ +#ifndef OPENHABINTERFACE_H +#define OPENHABINTERFACE_H + +#include +#include +#include +#include +#include + +class OpenHABInterface : public QObject +{ + Q_OBJECT +#define DEFINE_ITEM(name, itemName, ctype, type) \ + Q_PROPERTY(ctype name READ name WRITE set##name NOTIFY name##Changed); +#include "itemdefinitions.h" +#undef DEFINE_ITEM + QML_ELEMENT + + enum class ItemType { + temperature, + temperatureWithUnit, + humidity, + humidityWithUnit, + dimmer, + toggle, + power, + pressure + }; + +public: + explicit OpenHABInterface(QObject *parent = nullptr); + +#define DEFINE_ITEM(name, itemName, ctype, type) \ + ctype name() { \ + std::unique_lock lk(mDataMutex); \ + return m_##name; \ + } \ + void set##name(ctype aValue) { \ + { \ + std::unique_lock lk(mDataMutex); \ + m_##name = aValue; \ + } \ +\ + writeValue(#itemName, ItemType::type, aValue); \ + emit name##Changed(); \ + } +#include "itemdefinitions.h" +#undef DEFINE_ITEM + +public slots: + void socketError(QAbstractSocket::SocketError aError); + void openHABMessageReceived(const QString& message); + void signalItem(const QString& item); + +signals: +// Ugh, Qt doesn't understand macros inside here. +//#define DEFINE_ITEM(name, itemName, type) \ +// void name##Changed(); +//#include "itemdefinitions.h" +//#undef DEFINE_ITEM + void bedroomTemperatureChanged(); + void bedroomHumidityChanged(); + void bedroomDimmerChanged(); + void bedroomACToggleChanged(); + void bedroomACSetPointChanged(); + void bedroomFloorSetPointChanged(); + void officeTemperatureChanged(); + void officeHumidityChanged(); + void officePressureChanged(); + void officeDimmerChanged(); + void officeACToggleChanged(); + void officeACSetPointChanged(); + void officeFloorSetPointChanged(); + void mainPowerUsageChanged(); + void outdoorTemperatureChanged(); + +private: + void connectToServer(); + + void writeValue(const char* aItemName, ItemType aItemType, float aValue); + void writeValue(const char* aItemName, ItemType aItemType, bool aValue); + + +#define DEFINE_ITEM(name, itemName, ctype, type) \ + ctype m_##name; +#include "itemdefinitions.h" +#undef DEFINE_ITEM + + struct ItemDescription { + void* mValue; + const char* mOpenHABState; + const char* mOpenHABCommand; + std::function mSignal; + ItemType mType; + }; + + std::mutex mDataMutex; + QWebSocket mOpenHABSocket; + std::vector mItems; +}; + +#endif // OPENHABINTERFACE_H diff --git a/paho.mqtt.c b/paho.mqtt.c deleted file mode 160000 index f7799da..0000000 --- a/paho.mqtt.c +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f7799da95e347bbc930b201b52a1173ebbad45a7 diff --git a/qtquickcontrols2.conf b/qtquickcontrols2.conf new file mode 100644 index 0000000..e1c0add --- /dev/null +++ b/qtquickcontrols2.conf @@ -0,0 +1,5 @@ +[Controls] +Style=Material + +[Material] +Theme=Dark diff --git a/resources.qrc b/resources.qrc new file mode 100644 index 0000000..4fbf17b --- /dev/null +++ b/resources.qrc @@ -0,0 +1,5 @@ + + + qtquickcontrols2.conf + + -- 2.47.3