Thermostat pour piloter jusqu'à 4 radiateurs avec fil pilote
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

308 lines
6.4 KiB

  1. // SPDX-License-Identifier: GPL-3.0-or-later
  2. /*
  3. * Qt mutizone MQTT thermostat
  4. *
  5. * Copyright (C) 2019 Richard Genoud
  6. *
  7. */
  8. #include <QtWidgets>
  9. #include <QPushButton>
  10. #include <QTime>
  11. #include <QDate>
  12. #include <QtMath>
  13. #include <QLocale>
  14. #include <QVector>
  15. #include "mqttclient.h"
  16. #include "zoneitem.h"
  17. #include "settings.h"
  18. #include "mainwindow.h"
  19. MainWindow::MainWindow(QWidget *parent) :
  20. QMainWindow(parent)
  21. {
  22. ZoneItem *zone;
  23. Settings *s = Settings::getInstance();
  24. for (int i = 0; i < s->nbZones(); i++) {
  25. zone = new ZoneItem(s->m_rooms.at(i).name, this);
  26. zone->m_target_temperature = get_target_temperature(i);
  27. m_zones << zone;
  28. }
  29. /*
  30. * Sensors-related layout
  31. */
  32. QGridLayout *mainLayout = new QGridLayout;
  33. m_state_btn.setText(tr("Auto"));
  34. if (s->nbZones() < MAX_NB_ZONES) {
  35. // TODO
  36. qDebug() << "bad configuration" ;
  37. } else {
  38. mainLayout->addWidget(m_zones.at(0), 0, 0);
  39. mainLayout->addWidget(m_zones.at(1), 0, 1);
  40. mainLayout->addWidget(new QPushButton(tr("Menu")), 0, 2);
  41. mainLayout->addWidget(m_zones.at(2), 1, 0);
  42. mainLayout->addWidget(m_zones.at(3), 1, 1);
  43. mainLayout->addWidget(&m_state_btn, 1, 2);
  44. }
  45. update_state_btn(s->m_state);
  46. QWidget *mainWidget = new QWidget;
  47. mainWidget->setLayout(mainLayout);
  48. /*
  49. * Top widget
  50. */
  51. setCentralWidget(mainWidget);
  52. setWindowTitle(tr("Sorico's thermostat"));
  53. /*
  54. * MQTT client
  55. */
  56. m_mqtt = new MQTTClient(QHostAddress(s->m_broker.address), s->m_broker.port, NULL);
  57. m_mqtt->connectToHost();
  58. connect(m_mqtt, SIGNAL(new_temperature(int, double)),
  59. this, SLOT(temperature_slot(int, double)));
  60. connect(m_mqtt, SIGNAL(new_hygro(int, double)),
  61. this, SLOT(hygro_slot(int, double)));
  62. connect(m_mqtt, SIGNAL(new_availability(int, bool)),
  63. this, SLOT(availability_slot(int, bool)));
  64. connect(this, SIGNAL(setAllHeatersOn(bool)), m_mqtt, SLOT(allHeatersOn(bool)));
  65. connect(m_mqtt, SIGNAL(connected(void)), this, SLOT(apply_order_to_heaters(void)));
  66. connect(&m_state_btn, SIGNAL(clicked()), this, SLOT(change_state()));
  67. /*
  68. * Heater timer
  69. */
  70. m_timer = new QTimer(this);
  71. connect(m_timer, SIGNAL(timeout()), this, SLOT(apply_order_to_heaters()));
  72. m_timer->start(60000);
  73. }
  74. MainWindow::~MainWindow()
  75. {
  76. }
  77. void MainWindow::update_state_btn(enum power_states st)
  78. {
  79. switch (st) {
  80. case OFF:
  81. m_state_btn.setText(tr("Force off"));
  82. break;
  83. case ON:
  84. m_state_btn.setText(tr("Force on"));
  85. break;
  86. case AUTO:
  87. m_state_btn.setText(tr("Auto"));
  88. break;
  89. default:
  90. m_state_btn.setText(tr("Error"));
  91. break;
  92. }
  93. }
  94. double MainWindow::get_target_temperature(int room_idx)
  95. {
  96. Settings *s = Settings::getInstance();
  97. const struct Room *r = NULL;
  98. const struct Program *p = NULL;
  99. double target = FORCE_OFF;
  100. QTime now = QTime::currentTime();
  101. uint8_t dow = QDate::currentDate().dayOfWeek();
  102. if ((room_idx < 0) || (room_idx >= MAX_NB_ZONES)) {
  103. goto out;
  104. }
  105. r = &(s->m_rooms.at(room_idx));
  106. target = r->default_temperature;
  107. if (!dow) {
  108. goto out;
  109. }
  110. dow <<= 1;
  111. for (int i = 0; i < r->progs.count(); i++) {
  112. p = &(r->progs.at(i));
  113. if (dow & p->DoW) {
  114. if (p->start_time == p->end_time) {
  115. continue;
  116. }
  117. /*
  118. * Ex: from 10h to 18h
  119. */
  120. if (p->start_time < p->end_time) {
  121. if ((now >= p->start_time) && (now < p->end_time)) {
  122. target = p->temperature;
  123. }
  124. } else {
  125. /*
  126. * Ex: from 22h to 6h
  127. */
  128. if (now >= p->start_time) {
  129. target = p->temperature;
  130. } else {
  131. if (now < p->end_time) {
  132. target = p->temperature;
  133. }
  134. }
  135. }
  136. }
  137. }
  138. out:
  139. return target;
  140. }
  141. bool MainWindow::get_heater_order(int room_idx)
  142. {
  143. bool should_heat = false;
  144. if ((room_idx < 0) || (room_idx >= MAX_NB_ZONES)) {
  145. goto out;
  146. }
  147. if (m_zones.at(room_idx)->m_temperature_value == FORCE_OFF) {
  148. goto out;
  149. }
  150. if (!m_zones.at(room_idx)->m_available) {
  151. goto out;
  152. }
  153. /* TODO: make it smarter */
  154. if (qFabs(get_target_temperature(room_idx)
  155. - m_zones.at(room_idx)->m_temperature_value) < 0.3) {
  156. /*
  157. * Stay like before
  158. */
  159. should_heat = m_zones.at(room_idx)->m_heating_on;
  160. } else {
  161. if (get_target_temperature(room_idx) >
  162. m_zones.at(room_idx)->m_temperature_value) {
  163. should_heat = true;
  164. }
  165. }
  166. out:
  167. return should_heat;
  168. }
  169. void MainWindow::apply_automatic_state(void)
  170. {
  171. Settings *s = Settings::getInstance();
  172. int i;
  173. qDebug() << "apply auto state";
  174. for (i = 0; i < s->m_rooms.count(); i++) {
  175. const struct Room *r = &(s->m_rooms.at(i));
  176. m_zones.at(i)->m_heating_on = get_heater_order(i);
  177. qDebug() << "room " << r->name;
  178. for (int j = 0; j < r->heaters.count(); j++) {
  179. const struct Heater *h = &(r->heaters.at(j));
  180. qDebug() << "heater " << h->ctrl_topic;
  181. /* TODO: check if connected */
  182. m_mqtt->publish_msg(h->ctrl_topic,
  183. m_zones.at(i)->m_heating_on ? "on" : "off");
  184. }
  185. }
  186. }
  187. void MainWindow::change_state(void)
  188. {
  189. Settings *s = Settings::getInstance();
  190. switch (s->m_state) {
  191. case OFF:
  192. s->m_state = ON;
  193. break;
  194. case ON:
  195. s->m_state = AUTO;
  196. break;
  197. case AUTO:
  198. /* fall through */
  199. default:
  200. s->m_state = OFF;
  201. break;
  202. }
  203. update_state_btn(s->m_state);
  204. apply_order_to_heaters();
  205. }
  206. void MainWindow::apply_order_to_heaters(void)
  207. {
  208. Settings *s = Settings::getInstance();
  209. switch (s->m_state) {
  210. case ON:
  211. qDebug() << "emit ALL_ON order";
  212. emit setAllHeatersOn(true);
  213. break;
  214. case AUTO:
  215. qDebug() << "apply AUTO state";
  216. apply_automatic_state();
  217. break;
  218. case OFF:
  219. /* fall */
  220. default:
  221. qDebug() << "emit ALL_OFF order";
  222. emit setAllHeatersOn(false);
  223. break;
  224. }
  225. }
  226. void MainWindow::temperature_slot(int idx, double val)
  227. {
  228. static bool temp_received[MAX_NB_ZONES];
  229. Settings *s = Settings::getInstance();
  230. qDebug() << "temperature idx:" << idx << " val: " << val << endl;
  231. if (idx < s->nbZones()) {
  232. m_zones.at(idx)->m_temperature_value = val;
  233. m_zones.at(idx)->refresh();
  234. if (!temp_received[idx]) {
  235. temp_received[idx] = true;
  236. apply_order_to_heaters();
  237. }
  238. }
  239. }
  240. void MainWindow::hygro_slot(int idx, double val)
  241. {
  242. Settings *s = Settings::getInstance();
  243. qDebug() << "hygro idx:" << idx << " val: " << val << endl;
  244. if (idx < s->nbZones()) {
  245. m_zones.at(idx)-> m_hygro_value = val;
  246. m_zones.at(idx)->refresh();
  247. }
  248. }
  249. void MainWindow::availability_slot(int idx, bool ok)
  250. {
  251. Settings *s = Settings::getInstance();
  252. if (idx < s->nbZones()) {
  253. m_zones.at(idx)->m_available = ok;
  254. if (!ok) {
  255. m_zones.at(idx)->m_hygro_value = 0;
  256. m_zones.at(idx)->m_temperature_value = 0;
  257. m_zones.at(idx)->refresh();
  258. }
  259. }
  260. }
  261. /* vim: set tabstop=8 shiftwidth=8 softtabstop=0 noexpandtab: */