From 4488da5de3d162c99edf5eca808050cb75c57901 Mon Sep 17 00:00:00 2001 From: Richard Genoud Date: Sun, 22 Dec 2019 19:57:17 +0100 Subject: [PATCH] Thermostat: 1st thermostat implementation --- soft/thermostat/Thermostat.conf | 4 +- soft/thermostat/inc/mainwindow.h | 11 +-- soft/thermostat/inc/settings.h | 10 +++ soft/thermostat/inc/zoneitem.h | 13 ++-- soft/thermostat/src/mainwindow.cpp | 148 +++++++++++++++++++++++++++++++++---- soft/thermostat/src/mqttclient.cpp | 22 +++--- soft/thermostat/src/settings.cpp | 18 ++++- soft/thermostat/src/zoneitem.cpp | 44 +++++------ 8 files changed, 201 insertions(+), 69 deletions(-) diff --git a/soft/thermostat/Thermostat.conf b/soft/thermostat/Thermostat.conf index 522e0a5..5a3cf34 100644 --- a/soft/thermostat/Thermostat.conf +++ b/soft/thermostat/Thermostat.conf @@ -1,6 +1,8 @@ # This file should be in: # $HOME/.config/Sorico/Thermostat.conf +state=auto + [broker] address=192.168.1.2 port=1883 @@ -12,7 +14,7 @@ port=1883 1\name=Bedroom 1\sensor_topic=sensors/chambre_so/xiaomi 1\default_temperature=21 -1\temperature_schedule\1\temperature=21 +1\temperature_schedule\1\temperature=18 1\temperature_schedule\1\days_of_week=127 1\temperature_schedule\1\start_time=10:00 1\temperature_schedule\1\end_time=18:30 diff --git a/soft/thermostat/inc/mainwindow.h b/soft/thermostat/inc/mainwindow.h index 0a699c1..7a83303 100644 --- a/soft/thermostat/inc/mainwindow.h +++ b/soft/thermostat/inc/mainwindow.h @@ -18,13 +18,6 @@ #include "zoneitem.h" #include "settings.h" -enum power_states { - OFF, - ON, - AUTO, - MAX_POWER_STATES -}; - class MainWindow : public QMainWindow { Q_OBJECT @@ -38,8 +31,10 @@ private: QPushButton m_state_btn; QVector m_zones; QTimer *m_timer; - enum power_states m_pwr_state; void update_state_btn(enum power_states st); + double get_target_temperature(int room_idx); + bool get_heater_order(int room_idx); + void apply_automatic_state(void); private slots: void temperature_slot(int idx, double val); diff --git a/soft/thermostat/inc/settings.h b/soft/thermostat/inc/settings.h index 648a4c7..40645d3 100644 --- a/soft/thermostat/inc/settings.h +++ b/soft/thermostat/inc/settings.h @@ -14,6 +14,15 @@ #include #define MAX_NB_ZONES 4 +#define FORCE_OFF (-256) +#define FORCE_ON (99) + +enum power_states { + OFF, + ON, + AUTO, + MAX_POWER_STATES +}; struct Heater { QString ctrl_topic; @@ -49,6 +58,7 @@ public: QVector m_rooms; struct Brocker m_broker; + enum power_states m_state; private: explicit Settings(QObject *parent = Q_NULLPTR); diff --git a/soft/thermostat/inc/zoneitem.h b/soft/thermostat/inc/zoneitem.h index 1338941..9977f5f 100644 --- a/soft/thermostat/inc/zoneitem.h +++ b/soft/thermostat/inc/zoneitem.h @@ -21,21 +21,18 @@ class ZoneItem : public QWidget public: explicit ZoneItem(const QString &zoneName, QWidget *parent = Q_NULLPTR); ~ZoneItem(); + void refresh(void); - void set_temperature_value(double val); - void set_target_temperature(double val); - void set_hygro_value(double val); + double m_temperature_value; + double m_hygro_value; + double m_target_temperature; + bool m_heating_on; private: QPushButton m_zoneNameBtn; QPushButton m_temperatureBtn; QPushButton m_hygroBtn; - void refresh(void); - - double m_temperature_value; - double m_hygro_value; - double m_target_temperature; }; #endif // ZONEITEM_H diff --git a/soft/thermostat/src/mainwindow.cpp b/soft/thermostat/src/mainwindow.cpp index 0b26155..1690475 100644 --- a/soft/thermostat/src/mainwindow.cpp +++ b/soft/thermostat/src/mainwindow.cpp @@ -9,6 +9,8 @@ #include #include #include +#include +#include #include #include @@ -26,6 +28,7 @@ MainWindow::MainWindow(QWidget *parent) : for (int i = 0; i < s->nbZones(); i++) { zone = new ZoneItem(s->m_rooms.at(i).name, this); + zone->m_target_temperature = get_target_temperature(i); m_zones << zone; } @@ -36,7 +39,7 @@ MainWindow::MainWindow(QWidget *parent) : m_state_btn.setText(tr("Auto")); - if (s->nbZones() < 4) { + if (s->nbZones() < MAX_NB_ZONES) { // TODO qDebug() << "bad configuration" ; } else { @@ -47,8 +50,7 @@ MainWindow::MainWindow(QWidget *parent) : mainLayout->addWidget(m_zones.at(3), 1, 1); mainLayout->addWidget(&m_state_btn, 1, 2); } - m_pwr_state = OFF; - update_state_btn(m_pwr_state); + update_state_btn(s->m_state); QWidget *mainWidget = new QWidget; mainWidget->setLayout(mainLayout); @@ -75,6 +77,7 @@ MainWindow::MainWindow(QWidget *parent) : this, SLOT(availability_slot(int, bool))); connect(this, SIGNAL(setAllHeatersOn(bool)), m_mqtt, SLOT(allHeatersOn(bool))); + connect(m_mqtt, SIGNAL(connected(void)), this, SLOT(apply_order_to_heaters(void))); connect(&m_state_btn, SIGNAL(clicked()), this, SLOT(change_state())); @@ -83,7 +86,7 @@ MainWindow::MainWindow(QWidget *parent) : */ m_timer = new QTimer(this); connect(m_timer, SIGNAL(timeout()), this, SLOT(apply_order_to_heaters())); - m_timer->start(600000); + m_timer->start(60000); } MainWindow::~MainWindow() @@ -108,39 +111,150 @@ void MainWindow::update_state_btn(enum power_states st) } } +double MainWindow::get_target_temperature(int room_idx) +{ + Settings *s = Settings::getInstance(); + const struct Room *r = NULL; + const struct Program *p = NULL; + double target = FORCE_OFF; + QTime now = QTime::currentTime(); + uint8_t dow = QDate::currentDate().dayOfWeek(); + + if ((room_idx < 0) || (room_idx >= MAX_NB_ZONES)) { + goto out; + } + + r = &(s->m_rooms.at(room_idx)); + target = r->default_temperature; + if (!dow) { + goto out; + } + + dow <<= 1; + for (int i = 0; i < r->progs.count(); i++) { + p = &(r->progs.at(i)); + if (dow & p->DoW) { + if (p->start_time == p->end_time) { + continue; + } + + /* + * Ex: from 10h to 18h + */ + if (p->start_time < p->end_time) { + if ((now >= p->start_time) && (now < p->end_time)) { + target = p->temperature; + } + } else { + /* + * Ex: from 22h to 6h + */ + if (now >= p->start_time) { + target = p->temperature; + } else { + if (now < p->end_time) { + target = p->temperature; + } + } + } + } + } + +out: + return target; +} + +bool MainWindow::get_heater_order(int room_idx) +{ + bool should_heat = false; + + if ((room_idx < 0) || (room_idx >= MAX_NB_ZONES)) { + goto out; + } + + if (m_zones.at(room_idx)->m_temperature_value == FORCE_OFF) { + goto out; + } + + /* TODO: make it smarter */ + if (qFabs(get_target_temperature(room_idx) + - m_zones.at(room_idx)->m_temperature_value) < 0.3) { + /* + * Stay like before + */ + should_heat = m_zones.at(room_idx)->m_heating_on; + } else { + + if (get_target_temperature(room_idx) > + m_zones.at(room_idx)->m_temperature_value) { + should_heat = true; + } + } + +out: + return should_heat; +} + +void MainWindow::apply_automatic_state(void) +{ + Settings *s = Settings::getInstance(); + int i; + + qDebug() << "apply auto state"; + for (i = 0; i < s->m_rooms.count(); i++) { + const struct Room *r = &(s->m_rooms.at(i)); + bool should_heat = get_heater_order(i); + qDebug() << "room " << r->name; + for (int j = 0; j < r->heaters.count(); j++) { + const struct Heater *h = &(r->heaters.at(j)); + qDebug() << "heater " << h->ctrl_topic; + /* TODO: check if connected */ + m_mqtt->publish_msg(h->ctrl_topic, + should_heat ? "on" : "off"); + } + } +} + void MainWindow::change_state(void) { - switch (m_pwr_state) { + Settings *s = Settings::getInstance(); + + switch (s->m_state) { case OFF: - m_pwr_state = ON; + s->m_state = ON; break; case ON: - m_pwr_state = AUTO; + s->m_state = AUTO; break; case AUTO: /* fall through */ default: - m_pwr_state = OFF; + s->m_state = OFF; break; } - update_state_btn(m_pwr_state); + update_state_btn(s->m_state); apply_order_to_heaters(); } void MainWindow::apply_order_to_heaters(void) { - switch (m_pwr_state) { + Settings *s = Settings::getInstance(); + + switch (s->m_state) { case ON: + qDebug() << "emit ALL_ON order"; emit setAllHeatersOn(true); break; case AUTO: - /* TODO */ + qDebug() << "apply AUTO state"; + apply_automatic_state(); break; case OFF: /* fall */ default: + qDebug() << "emit ALL_OFF order"; emit setAllHeatersOn(false); break; } @@ -152,7 +266,8 @@ void MainWindow::temperature_slot(int idx, double val) qDebug() << "temperature idx:" << idx << " val: " << val << endl; if (idx < s->nbZones()) { - m_zones.at(idx)->set_temperature_value(val); + m_zones.at(idx)->m_temperature_value = val; + m_zones.at(idx)->refresh(); } } @@ -162,16 +277,19 @@ void MainWindow::hygro_slot(int idx, double val) qDebug() << "hygro idx:" << idx << " val: " << val << endl; if (idx < s->nbZones()) { - m_zones.at(idx)->set_hygro_value(val); + m_zones.at(idx)-> m_hygro_value = val; + m_zones.at(idx)->refresh(); } } + void MainWindow::availability_slot(int idx, bool ok) { Settings *s = Settings::getInstance(); if ((idx < s->nbZones()) && !ok) { - m_zones.at(idx)->set_hygro_value(0); - m_zones.at(idx)->set_temperature_value(0); + m_zones.at(idx)->m_hygro_value = 0; + m_zones.at(idx)->m_temperature_value = 0; + m_zones.at(idx)->refresh(); } } diff --git a/soft/thermostat/src/mqttclient.cpp b/soft/thermostat/src/mqttclient.cpp index 52c866a..0e11615 100644 --- a/soft/thermostat/src/mqttclient.cpp +++ b/soft/thermostat/src/mqttclient.cpp @@ -38,18 +38,18 @@ MQTTClient::~MQTTClient() { void MQTTClient::onError(const QMQTT::ClientError error) { - qDebug() << "error" << error << endl; + qDebug() << "error" << error; } void MQTTClient::onConnected() { - qDebug() << "connected" << endl; + qDebug() << "connected"; this->subscribe("sensors/#", 1); } void MQTTClient::onSubscribed(const QString& topic) { - qDebug() << "subscribed " << topic << endl; + qDebug() << "subscribed " << topic; } void MQTTClient::onReceived(const QMQTT::Message& message) @@ -59,33 +59,33 @@ void MQTTClient::onReceived(const QMQTT::Message& message) QJsonValue val; qDebug() << "publish received: \"" << QString::fromUtf8(message.payload()) - << "\"" << " from: " << message.topic() << endl; + << "\"" << " from: " << message.topic(); for (int i = 0; i < s->nbZones(); ++i) { if (s->m_rooms.at(i).sensor_topic == message.topic()) { - qDebug() << "this is for us !" << endl; + qDebug() << "this is for us !"; sensorData = QJsonDocument::fromJson(message.payload()); if (!sensorData.isObject()) { - qWarning() << "malformed JSON data" << endl; + qWarning() << "malformed JSON data"; goto out; } QJsonObject obj(sensorData.object()); val = obj["temperature"]; if (val.isDouble()) { emit new_temperature(i, val.toDouble()); - qDebug() << val.toDouble() << endl; + qDebug() << val.toDouble(); } val = obj["humidity"]; if (val.isDouble()) { emit new_hygro(i, val.toDouble()); - qDebug() << val.toDouble() << endl; + qDebug() << val.toDouble(); } val = obj["battery"]; if (val.isDouble()) { emit new_battery(i, val.toDouble()); - qDebug() << val.toDouble() << endl; + qDebug() << val.toDouble(); } break; @@ -102,12 +102,12 @@ out: void MQTTClient::onPublished(const QMQTT::Message& message, quint16 msgid) { - qDebug() << "published" << msgid << " payload: " << message.topic() << endl; + qDebug() << "published" << msgid << " topic: " << message.topic() << "payload" << QString::fromUtf8(message.payload()); } void MQTTClient::onDisconnected(void) { - qDebug() << "disconnected" << endl; + qDebug() << "disconnected"; } void MQTTClient::publish_msg(const QString& topic, const QString& payload) diff --git a/soft/thermostat/src/settings.cpp b/soft/thermostat/src/settings.cpp index 15ae12d..85e1365 100644 --- a/soft/thermostat/src/settings.cpp +++ b/soft/thermostat/src/settings.cpp @@ -50,6 +50,16 @@ Settings::Settings(QObject *parent) : QSettings(parent) struct Program program; int nb_rooms; + m_state = OFF; + + QString st = this->value("state", "off").toString(); + if (st.compare(QString("on"), Qt::CaseInsensitive) == 0) { + m_state = ON; + } + if (st.compare(QString("auto"), Qt::CaseInsensitive) == 0) { + m_state = AUTO; + } + nb_rooms = this->beginReadArray("rooms"); if (nb_rooms == 0) { this->endArray(); @@ -79,6 +89,7 @@ Settings::Settings(QObject *parent) : QSettings(parent) room.default_temperature = this->value("default_temperature", 20).toDouble(); size = this->beginReadArray("temperature_schedule"); for (int j = 0; j < size; ++j) { + /* TODO: check for overlapping schedules ? */ this->setArrayIndex(j); program.temperature = this->value("temperature", 20).toDouble(); program.DoW = this->value("days_of_week", 127).toInt(); @@ -91,6 +102,8 @@ Settings::Settings(QObject *parent) : QSettings(parent) } this->endArray(); m_rooms << room; + room.progs.clear(); + room.heaters.clear(); } this->endArray(); m_broker.address = this->value("broker/address", "localhost").toString(); @@ -99,6 +112,7 @@ Settings::Settings(QObject *parent) : QSettings(parent) void Settings::set_rooms_default_config(void) { + this->setValue("state", "auto"); this->beginWriteArray("rooms"); for (int i = 0; i < MAX_NB_ZONES; i++) { @@ -126,12 +140,12 @@ void Settings::set_rooms_default_config(void) this->setArrayIndex(3); this->beginWriteArray("temperature_schedule"); this->setArrayIndex(0); - this->setValue("temperature", 99); // Force On + this->setValue("temperature", FORCE_ON); this->setValue("days_of_week", 31); this->setValue("start_time", QTime(6,30).toString("H:m")); this->setValue("end_time", QTime(7,0).toString("H:m")); this->setArrayIndex(1); - this->setValue("temperature", 99); // Force On + this->setValue("temperature", FORCE_ON); this->setValue("days_of_week", 96); this->setValue("start_time", QTime(7,30).toString("H:m")); this->setValue("end_time", QTime(8,0).toString("H:m")); diff --git a/soft/thermostat/src/zoneitem.cpp b/soft/thermostat/src/zoneitem.cpp index f162d01..6d0750b 100644 --- a/soft/thermostat/src/zoneitem.cpp +++ b/soft/thermostat/src/zoneitem.cpp @@ -12,6 +12,7 @@ #include #include +#include "settings.h" #include "zoneitem.h" ZoneItem::ZoneItem(const QString &zoneName, QWidget *parent) : @@ -22,9 +23,9 @@ ZoneItem::ZoneItem(const QString &zoneName, QWidget *parent) : m_temperatureBtn.setFlat(true); m_hygroBtn.setFlat(true); - m_temperature_value = 0; - m_hygro_value = 0; - m_target_temperature = 0; + m_temperature_value = FORCE_OFF; + m_hygro_value = FORCE_OFF; + m_target_temperature = FORCE_OFF; /* * Layout for the left part of the window @@ -45,34 +46,29 @@ void ZoneItem::refresh(void) { QString text; - text += QString::number(m_temperature_value); + if (m_temperature_value == FORCE_OFF) + text += QString("-"); + else + text += QString::number(m_temperature_value); text += QString("°C / "); - text += QString::number(m_target_temperature); + + if (m_target_temperature == FORCE_OFF) { + text += QString("-"); + } else if (m_target_temperature == FORCE_ON) { + text += QString("+"); + } else { + text += QString::number(m_target_temperature); + } text += QString("°C"); m_temperatureBtn.setText(text); - text = QString::number(m_hygro_value); + if (m_hygro_value == FORCE_OFF) + text = QString("-"); + else + text = QString::number(m_hygro_value); text += QString("%h"); m_hygroBtn.setText(text); } -void ZoneItem::set_temperature_value(double val) -{ - m_temperature_value = val; - refresh(); -} - -void ZoneItem::set_target_temperature(double val) -{ - m_target_temperature = val; - refresh(); -} - -void ZoneItem::set_hygro_value(double val) -{ - m_hygro_value = val; - refresh(); -} - /* vim: set tabstop=8 shiftwidth=8 softtabstop=0 noexpandtab: */