// SPDX-License-Identifier: GPL-3.0-or-later /* * Qt mutizone MQTT thermostat * * Copyright (C) 2019 Richard Genoud * */ #include #include #include #include #include #include #include #include #include #include "boost_dlg.h" #include "menu_dlg.h" #include "holiday_dlg.h" #include "mqttclient.h" #include "zoneitem.h" #include "settings.h" #include "mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { ZoneItem *zone; m_boost = NULL; m_menu = NULL; m_end_holiday = QDate::currentDate();; Settings *s = Settings::getInstance(); for (int i = 0; i < s->nbZones(); i++) { zone = new ZoneItem(s->m_rooms.at(i).name, this); zone->setProperty("idx", i); zone->m_target_temperature = get_target_temperature(i); connect(zone, SIGNAL(clicked()), this, SLOT(show_boost())); m_zones << zone; } /* * Sensors-related layout */ QGridLayout *mainLayout = new QGridLayout; m_state_btn.setText(tr("Auto")); QPushButton *menu_btn = new QPushButton(tr("Menu")); if (s->nbZones() < MAX_NB_ZONES) { // TODO qDebug() << "bad configuration" ; } else { mainLayout->addWidget(m_zones.at(0), 0, 0); mainLayout->addWidget(m_zones.at(1), 0, 1); mainLayout->addWidget(menu_btn, 0, 2); mainLayout->addWidget(m_zones.at(2), 1, 0); mainLayout->addWidget(m_zones.at(3), 1, 1); mainLayout->addWidget(&m_state_btn, 1, 2); } update_state_btn(s->m_state); QSizePolicy *szPolicy = new QSizePolicy(QSizePolicy::Minimum, QSizePolicy::MinimumExpanding, QSizePolicy::PushButton); QFont font = menu_btn->font(); font.setPointSize(18); menu_btn->setFont(font); m_state_btn.setFont(font); menu_btn->setSizePolicy(*szPolicy); m_state_btn.setSizePolicy(*szPolicy); QWidget *mainWidget = new QWidget; mainWidget->setLayout(mainLayout); m_central_widget.addWidget(mainWidget); /* * Top widget */ setCentralWidget(&m_central_widget); setWindowTitle(tr("Sorico's thermostat")); /* * MQTT client */ m_mqtt = new MQTTClient(s->m_broker.address, s->m_broker.port, NULL); m_mqtt->connectToHost(); connect(m_mqtt, SIGNAL(new_temperature(int, double)), this, SLOT(temperature_slot(int, double))); connect(m_mqtt, SIGNAL(new_hygro(int, double)), this, SLOT(hygro_slot(int, double))); connect(m_mqtt, SIGNAL(new_availability(int, bool)), 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())); connect(menu_btn, SIGNAL(clicked()), this, SLOT(show_menu())); /* * Heater timer */ m_timer = new QTimer(this); connect(m_timer, SIGNAL(timeout()), this, SLOT(apply_order_to_heaters())); m_timer->start(60000); } MainWindow::~MainWindow() { } void MainWindow::boost_slot(int idx, struct boost_data &data) { if (idx == -1) { /* * user has hit cancel */ goto out; } qDebug() << "room " << m_zones.at(idx)->m_name << " new BOOST " << data.temperature << " end: " << data.end_date; m_zones.at(idx)->m_boost = data; apply_order_to_heaters(); out: show_main(); } void MainWindow::show_boost(void) { QObject* obj = sender(); QVariant v; v = obj->property("idx"); if (!v.isValid()) return; int idx = v.toInt(); if ((idx < 0) || (idx >= MAX_NB_ZONES)) return; m_boost = new BoostDlg(idx, m_zones.at(idx)->m_name, m_zones.at(idx)->m_target_temperature, NULL); connect(m_boost, SIGNAL(boost_changed(int, struct boost_data &)), this, SLOT(boost_slot(int, struct boost_data &))); m_central_widget.addWidget(m_boost); m_central_widget.setCurrentWidget(m_boost); } void MainWindow::do_show_holiday_dlg(void) { HolidayDlg *holiday_dlg = new HolidayDlg(this); m_central_widget.addWidget(holiday_dlg); m_central_widget.setCurrentWidget(holiday_dlg); connect(holiday_dlg, SIGNAL(close_holiday_dlg(void)), this, SLOT(do_close_holiday_dlg(void))); connect(holiday_dlg, SIGNAL(holiday_mode(QDate)), this, SLOT(set_holiday_mode(QDate))); } void MainWindow::do_close_holiday_dlg(void) { QWidget *current = m_central_widget.currentWidget(); if (current == NULL) return; m_central_widget.removeWidget(current); delete current; } void MainWindow::set_holiday_mode(QDate end_date) { m_end_holiday = end_date; apply_order_to_heaters(); } void MainWindow::show_menu(void) { m_menu = new MenuDlg(NULL); connect(m_menu, SIGNAL(closed(void)), this, SLOT(show_main(void))); connect(m_menu, SIGNAL(show_holiday_dlg(void)), this, SLOT(do_show_holiday_dlg(void))); m_central_widget.addWidget(m_menu); m_central_widget.setCurrentWidget(m_menu); } void MainWindow::show_main(void) { QWidget *current = m_central_widget.currentWidget(); if (current == NULL) goto out; if (m_central_widget.count() < 1) { /* WTF ?! */ return; } if (m_central_widget.count() == 1) { /* nothing to do */ return; } if (current == m_boost) { m_boost = NULL; } if (current == m_menu) { m_menu = NULL; } m_central_widget.removeWidget(current); delete current; out: m_central_widget.setCurrentIndex(0); } void MainWindow::update_state_btn(enum power_states st) { switch (st) { case OFF: m_state_btn.setText(tr("Force off")); break; case ON: m_state_btn.setText(tr("Force on")); break; case AUTO: m_state_btn.setText(tr("Auto")); break; default: m_state_btn.setText(tr("Error")); break; } } double MainWindow::get_target_temperature(int room_idx) { Settings *s = Settings::getInstance(); const struct Room *r = NULL; const struct Program *p = NULL; const struct boost_data *b = NULL; double target = FORCE_OFF; QDateTime now = QDateTime::currentDateTime(); 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; } /* * Check if there's a boost programmation */ if (room_idx < m_zones.count()) { b = &(m_zones.at(room_idx)->m_boost); if (b->end_date.isValid() && (b->end_date > now)) { /* BOOST is active */ target = b->temperature; goto out; } } /* * dayOfWeek() returns 1 for monday, 7 for sunday. * so dow -= 1 gives 0 for monday, 6 for sunday. * And (1 << dow) gives the day of week as a bit field */ dow -= 1; for (int i = 0; i < r->progs.count(); i++) { p = &(r->progs.at(i)); if ((1 << 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.time() >= p->start_time) && (now.time() < p->end_time)) { target = p->temperature; } } else { /* * Ex: from 22h to 6h */ if (now.time() >= p->start_time) { target = p->temperature; } else { if (now.time() < 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; } if (!m_zones.at(room_idx)->m_available) { goto out; } /* TODO: make it smarter */ if (qFabs(get_target_temperature(room_idx) - m_zones.at(room_idx)->m_temperature_value) < 0.1) { /* * 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)); m_zones.at(i)->m_heating_on = get_heater_order(i); m_zones.at(i)->m_target_temperature = get_target_temperature(i); m_zones.at(i)->refresh(); 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, m_zones.at(i)->m_heating_on ? "1" : "0"); } } } void MainWindow::change_state(void) { Settings *s = Settings::getInstance(); switch (s->m_state) { case OFF: s->m_state = ON; break; case ON: s->m_state = AUTO; break; case AUTO: /* fall through */ default: s->m_state = OFF; break; } update_state_btn(s->m_state); apply_order_to_heaters(); } void MainWindow::apply_order_to_heaters(void) { Settings *s = Settings::getInstance(); if (m_end_holiday > QDate::currentDate()) { qDebug() << "Holiday mode => emit ALL_OFF order"; emit setAllHeatersOn(false); } switch (s->m_state) { case ON: qDebug() << "emit ALL_ON order"; emit setAllHeatersOn(true); break; case AUTO: qDebug() << "apply AUTO state"; apply_automatic_state(); break; case OFF: /* fall */ default: qDebug() << "emit ALL_OFF order"; emit setAllHeatersOn(false); break; } } void MainWindow::temperature_slot(int idx, double val) { static bool temp_received[MAX_NB_ZONES]; Settings *s = Settings::getInstance(); qDebug() << "temperature idx:" << idx << " val: " << val << endl; if (idx < s->nbZones()) { m_zones.at(idx)->m_temperature_value = val; m_zones.at(idx)->refresh(); if (!temp_received[idx]) { temp_received[idx] = true; apply_order_to_heaters(); } } } void MainWindow::hygro_slot(int idx, double val) { Settings *s = Settings::getInstance(); qDebug() << "hygro idx:" << idx << " val: " << val << endl; if (idx < s->nbZones()) { 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()) { m_zones.at(idx)->m_available = ok; if (!ok) { m_zones.at(idx)->m_hygro_value = 0; m_zones.at(idx)->m_temperature_value = 0; m_zones.at(idx)->refresh(); } } } /* vim: set tabstop=8 shiftwidth=8 softtabstop=0 noexpandtab: */