-[submodule "paho.mqtt.c"]
- path = paho.mqtt.c
- url = https://github.com/eclipse/paho.mqtt.c
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
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 .
+++ /dev/null
-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
- }
-}
--- /dev/null
+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
+ }
+}
--- /dev/null
+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
+ }
+}
--- /dev/null
+#include "backlightcontroller.h"
+#ifdef USE_PWM
+#define PWM_PERIOD "500000"
+#define PWM_DUTYCYCLE_DIM "380000"
+#define PWM_DUTYCYCLE_ON "100000"
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <thread>
+#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;
+ }
+}
--- /dev/null
+#ifndef BACKLIGHTCONTROLLER_H
+#define BACKLIGHTCONTROLLER_H
+
+#include <QObject>
+#include <QQmlEngine>
+#include <QScopedPointer>
+#include <QTimer>
+
+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<QTimer> mOffTimer;
+};
+
+#endif // BACKLIGHTCONTROLLER_H
--- /dev/null
+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)
#include <QGuiApplication>
#include <QQmlApplicationEngine>
-
+#include <QQuickStyle>
+#include <QStyleHints>
+#include <QCursor>
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();
}
+++ /dev/null
-#include "mqttinterface.h"
-
-using namespace std;
-
-void delivered(void* context, MQTTClient_deliveryToken dt)
-{
- MQTTInterface* thermostat = static_cast<MQTTInterface*>(context);
- //thermostat->DeliverToken(dt);
-}
-
-int msgarrvd(void* context, char* topicName, int topicLen, MQTTClient_message* message)
-{
- MQTTInterface* thermostat = static_cast<MQTTInterface*>(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<MQTTInterface*>(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<mutex> lk(mDataMutex);
- mBedroomTemperature = aTemperature;
- }
-
- emit bedroomTemperatureChanged();
-}
-
-void
-MQTTInterface::updateBedroomHumidity(float aHumidity)
-{
- {
- unique_lock<mutex> 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);
- }
-}
+++ /dev/null
-#ifndef MQTTINTERFACE_H
-#define MQTTINTERFACE_H
-
-#include <QObject>
-#include <QtQml/qqmlregistration.h>
-#include <mutex>
-
-#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<std::mutex> lk(mDataMutex);
- return mBedroomTemperature;
- }
- qreal bedroomHumidity() {
- std::unique_lock<std::mutex> 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
--- /dev/null
+#include "openhabinterface.h"
+
+#include <QJsonDocument>
+#include <QJsonObject>
+
+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<bool*>(item.mValue) = false;
+ } else {
+ *reinterpret_cast<float*>(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<float*>(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<float*>(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<float*>(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<float*>(item.mValue) = value;
+ emit item.mSignal();
+ } else if (item.mType == ItemType::toggle) {
+ QJsonDocument payload = QJsonDocument::fromJson(receivedMessage["payload"].toString().toUtf8().data());
+ *reinterpret_cast<bool*>(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"));
+}
--- /dev/null
+#ifndef OPENHABINTERFACE_H
+#define OPENHABINTERFACE_H
+
+#include <QObject>
+#include <QtQml/qqmlregistration.h>
+#include <QWebSocket>
+#include <mutex>
+#include <vector>
+
+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<std::mutex> lk(mDataMutex); \
+ return m_##name; \
+ } \
+ void set##name(ctype aValue) { \
+ { \
+ std::unique_lock<std::mutex> 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<void()> mSignal;
+ ItemType mType;
+ };
+
+ std::mutex mDataMutex;
+ QWebSocket mOpenHABSocket;
+ std::vector<ItemDescription> mItems;
+};
+
+#endif // OPENHABINTERFACE_H
+++ /dev/null
-Subproject commit f7799da95e347bbc930b201b52a1173ebbad45a7
--- /dev/null
+[Controls]
+Style=Material
+
+[Material]
+Theme=Dark
--- /dev/null
+<RCC>
+ <qresource prefix="/">
+ <file>qtquickcontrols2.conf</file>
+ </qresource>
+</RCC>