Беспроводной программируемый по Wi-Fi комнатный термостат с монитором качества воздуха и другими полезными функциями
20 февраля 2019, 10:39
В системе автономного отопления моей квартиры работает выпускаемый серийно беспроводной комнатный термостат. Система, конечно, функционирует и без него: термостат был приобретен для экономии расхода газа и повышения комфорта.

Вещь очень полезная, но, на мой взгляд, несколько морально устаревшая. Было решено собрать нечто похожее на купленный термостат, добавив для начала в макет термостата более удобную настройку и подключение к Интернету.

Что в результате получилось – читайте дальше. Надеюсь, кроме меня проект будет интересен другим.

Знакомство

Возможности и характеристики:

  • Связь между узлами термостата осуществляется по воздуху на радиочастоте.
  • В течение суток термостат поддерживает постоянными три заданные значения температуры.
  • Настройки термостата (программа работы, граничные параметры воздуха, другие) задаются дистанционно через Wi-Fi с формы в браузере.
  • В термостат включена функция монитора качества воздуха с измерением температуры, уровня содержания углекислого газа и влажности воздуха.
  • Термостат укомплектован часами реального времени с синхронизацией часов с сервером точного времени через Интернет.
  • Управление термостатом осуществляется с интерфейса мобильного приложения Blynk. Кроме того, приложение Blynk принимает и отображает результаты измерения температуры, содержание СО2 и влажности воздуха.
  • Термостат автоматически переходит в автономный режим работы при отсутствии Wi-Fi.
  • С термостата отправляются сообщения на е-мейл, если температура, содержание СО2 или влажность воздуха находятся за пределами пороговых значений.
  • В термостате кроме температуры есть возможность поддержания в заданных пределах остальных измеряемых параметров воздуха.
  • По окончании отопительного сезона термостат не придется прятать: останутся в работе монитор качества воздуха с отправкой сообщений на почту и часы.

Термостат состоит из двух устройств. В первом устройстве формируется и передается на второе устройство сигнал управления нагревательным прибором или системой, назовем это устройство анализатором. Второе устройство, принимает сигнал, дешифрирует его и управляет источником тепла – пусть это будет контактор. Связь между анализатором и контактором беспроводная, на радиочастоте.

Сборка

Для сборки устройства понадобятся компоненты, перечень которых и их ориентировочная стоимость по ценам сайта AliExpress приведена в таблице.

Компонент Цена, $
анализатор
Wi-Fi плата NodeMCU CP2102 ESP8266 2,53
Датчик температуры и влажности DHT22 2,34
Датчик содержания СО2 MH Z-19 18,50
Часы RTC DS3231 1,00
Экран OLED LCD синий 0.96" I2C 128x64 1,95
RF модуль 433MHz, передатчик (цена комплекта: передатчик, приемник) 0,99
4-канальный преобразователь логических уровней 3,3В-5В (Logical Layer Converter) 0,28
Стабилизатор напряжения LM7805 (10 шт.) 0,79
Адаптер AC100-240V 50/60Hz DC12V 2A 10,70
Макетная плата (стеклотекстолит), контакты и др. 2,00
контактор
Модуль Arduino Pro Mini 5V 1,45
RF модуль 433MHz (приемник)    -
2-канальный модуль реле 0,98
Адаптер AC-DC HLK-PM01 4,29
Макетная плата (стеклотекстолит), контакты и др. 2,00
Всего: 49,80

Если планируется собирать термостат с минимальными габаритами, то нужно заменить 4-канальный преобразователь логических уровней на 2-канальный и 2-канальный модуль реле на 1-канальный.

Оба устройства собраны на стеклотекстолитовых макетных платах. Монтаж – навесной. Модули установлены на панельки, собранные из "гребенок" контактов. Такой подход имеет ряд преимуществ: компоненты легко демонтируются, легко меняется монтаж под новую версию скетча и, наконец, в корпусе самоделки не видно каким способом он выполнен.

Антенны у передатчика и приемника – это провод длиной 17,3 см. Повышенная мощность передатчика и простейшие антенны обеспечивают надежную связь в пределах квартиры.

Анализатор

Мозг анализатора – контроллер ESP8266 на плате модуля NodeMCU CP2102. Он принимает сигналы с датчиков и формирует сигналы управления передатчиком и экраном.

При установке датчика DHT22 на плате, измеренная температура на 1,5…2°С выше реальной (даже без корпуса!). Поэтому следует размещать датчик температуры подальше от элементов с большим тепловыделением LM7805 и NodeMCU CP2102. Кроме того, было бы неплохо установить стабилизатор напряжения LM7805 на радиатор и однозначно необходимо обеспечить хорошую конвекцию воздуха в корпусе для понижения температуры и уменьшения ошибки ее измерений. Другой вариант избавиться от ошибки — вынести датчик DHT22 за объем корпуса – этот вариант проще и я выбрал его.

На анализатор подается постоянное напряжение 12В от адаптера AC/DC. Далее стабилизатор постоянного напряжения LM7805 формирует напряжение 5В. Напряжение питания передатчика — 12В. При тестировании устройства, когда анализатор и контактор находятся рядом на рабочем столе, питание анализатора можно организовать с USB-порта компьютера, подав напряжение на модуль NodeMCU CP2102 стандартным кабелем USB – microUSB. Напряжение питания NodeMCU CP2102 и MH Z-19 – 5В, питание остальных узлов схемы (3,3В) формирует стабилизатор модуля NodeMCU CP2102.

Датчик температуры и влажности DHT22 подключен к выводу D6 модуля NodeMCU CP2102. Часы DC3231 и дисплей 0.96" подключены к ESP8266 (на модуле NodeMCU CP2102) через двухпроводный интерфейс I2C, а выводы Tx, Rx датчика содержания СО2 MH Z-19 подключены к выводам Rx, Tx ESP8266 соответственно. Сигнал на передатчик поступает с NodeMCU CP2102 через преобразователь логических уровней, который преобразует сигнал с NodeMCU CP2102 с амплитудой около 3,3В в сигнал, амплитуда которого близка к напряжению питания передатчика 12В.

Если в модуле часов вы используете батарейку вместо аккумулятора, то не забудьте разорвать цепь заряда аккумулятора, иначе батарейка вздуется через несколько недель работы под напряжением. С автономным питанием часов точность хода 2 сек/год вам обеспечена.

Скетч анализатора для загрузки в ESP8266 находится под спойлером.

скетч анализатора/* * Беспроводной программируемый по Wi-Fi комнатный термостат с монитором качества воздуха и другими полезными функциями (анализатор) */ #include #include #include //https://github.com/esp8266/Arduino // Wifi Manager #include #include #include //https://github.com/tzapu/WiFiManager //e-mail #include //https://github.com/esp8266/Arduino/blob/master/libraries/ESP8266WiFi/src/ESP8266WiFiMulti.h #include ESP8266WiFiMulti WiFiMulti; char address[64] {"e-mail"}; //e-mail, address // HTTP requests #include // OTA updates #include // Blynk #include // Debounce #include //https://github.com/thomasfredericks/Bounce2 // JSON #include //https://github.com/bblanchon/ArduinoJson //clock #include #include #include #include #include //https://github.com/Makuna/Rtc RtcDS3231 Rtc(Wire); #define countof(a) (sizeof(a) / sizeof(a[0])) //timer #include SimpleTimer timer; // ссылка на таймер unsigned int timerCO2; //период опроса MH-Z19 unsigned int timerBl; //период отправки данных на Blynk unsigned int timerMail; //период отправки сообщений на емейл // GPIO Defines #define I2C_SDA 4 // D2 - OLED #define I2C_SCL 5 // D1 - OLED #define DHTPIN 12 //D6 cp2102 // Humidity/Temperature #include #define DHTTYPE DHT22 // DHT 22 DHT dht(DHTPIN, DHTTYPE); #define mySerial Serial // Use U8g2 for i2c OLED Lib #include #include U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, I2C_SCL, I2C_SDA, U8X8_PIN_NONE); byte x {0}; byte y {0}; // Blynk token char blynk_token[33] {"Blynk token"}; //Transmitter #include RCSwitch transmitter = RCSwitch(); unsigned long TimeTransmitMax; // переменная для хранения точки отсчета времени передачи сигнала ВКЛ/ВЫКЛ передатчиком // Setup Wifi connection WiFiManager wifiManager; // Network credentials String ssid {"am-5108"}; String pass {"vb" + String(ESP.getFlashChipId())}; //flag for saving data bool shouldSaveConfig = false; //переменные float t {-100}; //температура int h {-1}; //влажность int co2 {-1}; //содержание co2 float Chs = 0.2; //чуствительность (гистерезис) термостата по температуре (диапазон: 0.1(большая тепловая инерция) - 0.4 (малая тепловая инерция)) char Tmx[]{"25.0"}, Hmn[]{"35"}, Cmx[]{"1000"}, tZ[]{"2.0"}; //пороговые значения t, h и co2, час. пояс float Cmax, Tmax, Hmin, tZone; char Temperature0[]{"20.0"}, Temperature1[]{"22.0"}, Temperature2[]{"19.0"};//температура стабилизации термостата во временных интервалах float TemperaturePoint0, TemperaturePoint1, TemperaturePoint2, TemperaturePoint1Mn, TemperaturePoint2Mn, TemperaturePoint1Pl, TemperaturePoint2Pl; float TemperaturePointA0 = 21.0; //температура стабилизации термостата в автономном режиме char Hour1[]{"6"}, Hour2[]{"22"}; //временные точки термостата, час float HourPoint1, HourPoint2; float MinPoint1 = 0, MinPoint2 = 0; int n, j, m; //счетчик часов, минут int progr = 0; //счетчик программ работы термостата во времени суток int timeSummerWinter = 0; // летнее(1)/зимнее(0) время int a = 1; //режим работы термостата: 1 - онлайн, 2 - автономный bool buttonBlynk = true; //признак ВКЛ(true)/ВЫКЛ(falce) виртуальной кнопки V(10) Blynk //NTP, clock uint8_t hh,mm,ss; //containers for current time char time_r[9]; char date_r[12]; // NTP Servers: //static const char ntpServerName[] = "us.pool.ntp.org"; static const char ntpServerName[] = "time.nist.gov"; WiFiUDP Udp; unsigned int localPort = 2390; // local port to listen for UDP packets time_t getNtpTime(); void digitalClockDisplay(); void printDigits(int digits); void sendNTPpacket(IPAddress &address); void digitalClockDisplay() { // digital clock display of the time Serial.print(hour()); printDigits(minute()); printDigits(second()); Serial.print(" "); Serial.print(day()); Serial.print("."); Serial.print(month()); Serial.print("."); Serial.print(year()); Serial.println(); } void printDigits(int digits) { // utility for digital clock display: prints preceding colon and leading 0 Serial.print(":"); if (digits 0) ; // discard any previously received packets Serial.println("Transmit NTP Request"); // get a random server from the pool WiFi.hostByName(ntpServerName, ntpServerIP); Serial.print(ntpServerName); Serial.print(": "); Serial.println(ntpServerIP); sendNTPpacket(ntpServerIP); uint32_t beginWait = millis(); while (millis() - beginWait = NTP_PACKET_SIZE) { Serial.println("Receive NTP Response"); Udp.read(packetBuffer, NTP_PACKET_SIZE); // read packet into the buffer unsigned long secsSince1900; // convert four bytes starting at location 40 to a long integer secsSince1900 = (unsigned long)packetBuffer[40] compiled) { Serial.println("RTC is newer than compile time. (this is expected)"); } else if (now == compiled) { Serial.println("RTC is the same as compile time! (not expected but all is fine)"); } // never assume the Rtc was last configured by you, so // just clear them to your needed state Rtc.Enable32kHzPin(false); Rtc.SetSquareWavePin(DS3231SquareWavePin_ModeNone); } } void synchronClock() { Rtc.Begin(); wifiManager.autoConnect(ssid.c_str(), pass.c_str()); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(" "); Serial.print("IP number assigned by DHCP is "); Serial.println(WiFi.localIP()); Serial.println("Starting UDP"); Udp.begin(localPort); Serial.print("Local port: "); Serial.println(Udp.localPort()); Serial.println("waiting for sync"); setSyncProvider(getNtpTime); if(timeStatus() != timeNotSet){ digitalClockDisplay(); Serial.println("here is another way to set rtc"); time_t t = now(); char date_0[12]; snprintf_P(date_0, countof(date_0), PSTR("%s %02u %04u"), monthShortStr(month(t)), day(t), year(t)); Serial.println(date_0); char time_0[9]; snprintf_P(time_0, countof(time_0), PSTR("%02u:%02u:%02u"), hour(t), minute(t), second(t)); Serial.println(time_0); Serial.println("Now its time to set up rtc"); RtcDateTime compiled = RtcDateTime(date_0, time_0); Serial.println(""); if (!Rtc.IsDateTimeValid()) { // Common Cuases: // 1) first time you ran and the device wasn't running yet // 2) the battery on the device is low or even missing Serial.println("RTC lost confidence in the DateTime!"); // following line sets the RTC to the date & time this sketch was compiled // it will also reset the valid flag internally unless the Rtc device is // having an issue } Rtc.SetDateTime(compiled); RtcDateTime now = Rtc.GetDateTime(); if (now compiled) { Serial.println("RTC is newer than compile time. (this is expected)"); } else if (now == compiled) { Serial.println("RTC is the same as compile time! (not expected but all is fine)"); } // never assume the Rtc was last configured by you, so // just clear them to your needed state Rtc.Enable32kHzPin(false); Rtc.SetSquareWavePin(DS3231SquareWavePin_ModeNone); } } void Clock(){ RtcDateTime now = Rtc.GetDateTime(); //Print RTC time to Serial Monitor hh = now.Hour(); mm = now.Minute(); ss = now.Second(); sprintf(date_r, "%d.%d.%d", now.Day(), now.Month(), now.Year()); if (mm -1) and (t1 -1) and (h1 "; Tmx_p = onl2 + Tmx_i_m; char Tmx_p_m [16]; Tmx_p.toCharArray(Tmx_p_m, 16); String Cmx_i; Cmx_i = String(Cmx); char Cmx_i_m [16]; Cmx_i.toCharArray(Cmx_i_m, 16); u8g2.clearBuffer(); String Cmx_p; String onl3 = "OnL CO2>"; Cmx_p = onl3 + Cmx_i_m; char Cmx_p_m [16]; Cmx_p.toCharArray(Cmx_p_m, 16); String Hmn_i; Hmn_i = String(Hmn); char Hmn_i_m [16]; Hmn_i.toCharArray(Hmn_i_m, 16); u8g2.clearBuffer(); String Hmn_p; String onl4 = "OnLine H=HourPoint1) and (hh Cmax) u8g2.drawStr(x, y, Cmx_p_m); else if (h -100) and (t -1) and (h -1) and (co2 -1) and (h -1) and (co2 "; Tmx_p = onl2 + Tmx_i_m; char Tmx_p_m [16]; Tmx_p.toCharArray(Tmx_p_m, 16); String Cmx_i; Cmx_i = String(Cmx); char Cmx_i_m [16]; Cmx_i.toCharArray(Cmx_i_m, 16); u8g2.clearBuffer(); String Cmx_p; String onl3 = "OnL CO2>"; Cmx_p = onl3 + Cmx_i_m; char Cmx_p_m [16]; Cmx_p.toCharArray(Cmx_p_m, 16); String Hmn_i; Hmn_i = String(Hmn); char Hmn_i_m [16]; Hmn_i.toCharArray(Hmn_i_m, 16); u8g2.clearBuffer(); String Hmn_p; String onl4 = "OffBL H=HourPoint1) and (hh Cmax) u8g2.drawStr(x, y, Cmx_p_m); else if (h -100) and (t -1) and (h -1) and (co2 1024) { Serial.println("Config file size is too large"); return false; } // Allocate a buffer to store contents of the file. std::unique_ptr buf(new char[size]); // We don't use String here because ArduinoJson library requires the input // buffer to be mutable. If you don't use ArduinoJson, you may as well // use configFile.readString instead. configFile.readBytes(buf.get(), size); StaticJsonBuffer jsonBuffer; JsonObject &json = jsonBuffer.parseObject(buf.get()); if (!json.success()) { Serial.println("Failed to parse config file"); return false; } // Save parameters strcpy(blynk_token, json["blynk_token"]); strcpy(address, json["address"]); strcpy(Tmx, json["Tmx"]); strcpy(Cmx, json["Cmx"]); strcpy(Temperature0, json["Temperature0"]); strcpy(Temperature1, json["Temperature1"]); strcpy(Temperature2, json["Temperature2"]); strcpy(Hmn, json["Hmn"]); strcpy(Hour1, json["Hour1"]); strcpy(Hour2, json["Hour2"]); strcpy(tZ, json["tZ"]); } void configModeCallback (WiFiManager *wifiManager) { String url {"http://192.168.4.1"}; printString("Connect to WiFi:"); printString("net: " + ssid); printString("pw: "+ pass); printString("Open browser:"); printString(url); printString("to setup device"); drawConnectionDetails(ssid, pass, url); } void setupWiFi() { //set config save notify callback wifiManager.setSaveConfigCallback(saveConfigCallback); // Custom parameters WiFiManagerParameter custom_tZ("tZ", "Time Zone", tZ, 5); wifiManager.addParameter(&custom_tZ); WiFiManagerParameter custom_Temperature0("Temperature0", "Temperature 0", Temperature0, 5); wifiManager.addParameter(&custom_Temperature0); WiFiManagerParameter custom_Hour1("Hour1", "Hour 1", Hour1, 5); wifiManager.addParameter(&custom_Hour1); WiFiManagerParameter custom_Temperature1("Temperature1", "Temperature 1", Temperature1, 5); wifiManager.addParameter(&custom_Temperature1); WiFiManagerParameter custom_Hour2("Hour2", "Hour 2", Hour2, 5); wifiManager.addParameter(&custom_Hour2); WiFiManagerParameter custom_Temperature2("Temperature2", "Temperature 2", Temperature2, 5); wifiManager.addParameter(&custom_Temperature2); WiFiManagerParameter custom_Cmx("Cmx", "Cmax", Cmx, 7); wifiManager.addParameter(&custom_Cmx); WiFiManagerParameter custom_Hmn("Hmn", "Hmin", Hmn, 5); wifiManager.addParameter(&custom_Hmn); WiFiManagerParameter custom_Tmx("Tmx", "Tmax", Tmx,5); wifiManager.addParameter(&custom_Tmx); WiFiManagerParameter custom_address("address", "E-mail", address, 64); wifiManager.addParameter(&custom_address); WiFiManagerParameter custom_blynk_token("blynk_token", "Blynk Token", blynk_token, 34); wifiManager.addParameter(&custom_blynk_token); wifiManager.setAPCallback(configModeCallback); wifiManager.setTimeout(180); if (!wifiManager.autoConnect(ssid.c_str(), pass.c_str())) { a++; Serial.println("mode OffLINE :("); loadConfigS(); synchronClockA(); } //save the custom parameters to FS if (shouldSaveConfig) { Serial.println("saving config"); DynamicJsonBuffer jsonBuffer; JsonObject &json = jsonBuffer.createObject(); json["blynk_token"] = custom_blynk_token.getValue(); json["address"] = custom_address.getValue(); json["Tmx"] = custom_Tmx.getValue(); json["Cmx"] = custom_Cmx.getValue(); json["Temperature0"] = custom_Temperature0.getValue(); json["Temperature1"] = custom_Temperature1.getValue(); json["Temperature2"] = custom_Temperature2.getValue(); json["Hmn"] = custom_Hmn.getValue(); json["Hour1"] = custom_Hour1.getValue(); json["Hour2"] = custom_Hour2.getValue(); json["tZ"] = custom_tZ.getValue(); File configFile = SPIFFS.open("/config.json", "w"); if (!configFile) { Serial.println("failed to open config file for writing"); } json.printTo(Serial); json.printTo(configFile); configFile.close(); //end save } //if you get here you have connected to the WiFi Serial.println("WiFi connected"); Serial.print("IP address: "); Serial.println(WiFi.localIP()); } BLYNK_WRITE(V10) { if (param.asInt() == 1) { buttonBlynk = true; Blynk.virtualWrite(V10, HIGH); drawBoot("Thermo ON"); } else { buttonBlynk = false; Blynk.virtualWrite(V10, LOW); drawBoot("Thermo OFF"); } } void mailer() { // wait for WiFi connection if((WiFiMulti.run() == WL_CONNECTED)) { HTTPClient http; Serial.print("[HTTP] begin...\n"); http.begin("http://skorovoda.in.ua/php/aqm42.php?mymail="+String(address)+"&t="+String(t) +"&h="+String(h)+"&co2="+String(co2)+"&ID="+String(ESP.getChipId())); Serial.print("[HTTP] GET...\n"); // start connection and send HTTP header int httpCode = http.GET(); // httpCode will be negative on error if(httpCode > 0) { // HTTP header has been send and Server response header has been handled Serial.printf("[HTTP] GET... code: %d\n", httpCode); // file found at server if(httpCode == HTTP_CODE_OK) { String payload = http.getString(); Serial.println(payload); } } else { Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str()); } http.end(); } } void HystTemperatureA() { float TemperaturePointA0Mn, TemperaturePointA0Pl; TemperaturePointA0Mn = TemperaturePointA0-Chs; TemperaturePointA0Pl = TemperaturePointA0+Chs; if (t 120000){ TimeTransmitMax = millis(); transmitter.send(B11111110, 8); Serial.println ("t 120000) { TimeTransmitMax = millis(); transmitter.send(B10000000, 8); Serial.println ("t>TemperaturePointA0Mn Thermostat OFF"); } if (t 120000){ TimeTransmitMax = millis(); transmitter.send(B11111110, 8); Serial.println ("t 120000) { TimeTransmitMax = millis(); transmitter.send(B10000000, 8); Serial.println ("t>TemperaturePointA0Pl Thermostat OFF"); } } void HystTemperature() { float TemperaturePoint0Mn, TemperaturePoint0Pl; TemperaturePoint0Mn = TemperaturePoint0-Chs; TemperaturePoint0Pl = TemperaturePoint0+Chs; if (t 120000){ TimeTransmitMax = millis(); transmitter.send(B11111110, 8); Serial.println ("t 120000) { TimeTransmitMax = millis(); transmitter.send(B10000000, 8); Serial.println ("t>TemperaturePoint0Mn Thermostat OFF"); } if (t 120000){ TimeTransmitMax = millis(); transmitter.send(B11111110, 8); Serial.println ("t 120000) { TimeTransmitMax = millis(); transmitter.send(B10000000, 8); Serial.println ("t>TemperaturePoint0Pl Thermostat OFF"); } } void TransmitterA(){ transmitter.send(B10101010, 8); //B10101010 - признак работающего передатчика HystTemperatureA(); } void Transmitter(){ transmitter.send(B10101010, 8); //B10101010 - признак работающего передатчика if (n>=24) n = 0; if (m>=60) m = 0; progr = 0; if ((hh >= HourPoint1) and (hh = MinPoint1) progr = 1; if (mm = HourPoint2) { progr = 2; if (mm >= MinPoint2) progr = 2; } if (buttonBlynk==true) { Serial.println ("BLynk: Термостат ВКЛ"); if (progr == 0) { TemperaturePoint0 = TemperaturePoint0; HystTemperature(); Serial.println ("Термостатирование: t = " + String(TemperaturePoint0)); } else if (progr == 1) { TemperaturePoint0 = TemperaturePoint1; HystTemperature(); Serial.println ("Термостатирование: t = " + String(TemperaturePoint0)); } else if (progr == 2){ TemperaturePoint0 = TemperaturePoint2; HystTemperature(); Serial.println ("Термостатирование: t = " + String(TemperaturePoint0)); } } else { transmitter.send(B10000000, 8); Serial.println ("BLynk: Термостат ВЫКЛ"); } if (co2 > Cmax) { transmitter.send(B11111101, 8); Serial.println("co2 > Cmax"); } else transmitter.send(B00000010, 8); if (h Tmax) { transmitter.send(B11110111, 8); Serial.println("t > Tmax"); } else transmitter.send(B00001000, 8); } void connectBlynk(){ if(String(blynk_token)== "Blynk token"){ drawBoot("OFFBLYNK!"); delay (3000); } else { drawBoot("Connect. Blynk"); Serial.println("Connecting to blynk..."); while (Blynk.connect() == false) { delay(500); Serial.println("Connecting to blynk..."); } } } void setup() { // factoryReset(); //форматирование RAM mySerial.begin(9600); Serial.begin(115200); transmitter.enableTransmit(2); u8g2.begin(); // инициализация экрана drawBoot("Loading..."); // инициализация файловой системы if (!SPIFFS.begin()) { Serial.println("Failed to mount file system"); ESP.reset(); } // загрузка параметров drawBoot("Connect. WiFi"); setupWiFi(); timerCO2 = timer.setInterval(15000, readCO2); buttonBlynk = true; if(a == 1){ // Load config drawBoot("Load Config"); if (!loadConfig()) { Serial.println("Failed to load config"); factoryReset(); } else { Serial.println("Config loaded"); } Blynk.config(address); Serial.print("e-mail: "); Serial.println(address); Blynk.config(Tmx); Serial.print("T max: "); Serial.println(Tmx); Blynk.config(Cmx); Serial.print("CO2 max: "); Serial.println(Cmx); Blynk.config(Temperature0); Serial.print("Temperature 0: "); Serial.println(Temperature0); Blynk.config(Temperature1); Serial.print("Temperature1: "); Serial.println(Temperature1); Blynk.config(Temperature2); Serial.print("Temperature2: "); Serial.println(Temperature2); Blynk.config(Hmn); Serial.print("H min: "); Serial.println(Hmn); Blynk.config(Hour1); Serial.print("Hour 1: "); Serial.println(Hour1); Blynk.config(Hour2); Serial.print("Hour 2: "); Serial.println(Hour2); Blynk.config(tZ); Serial.print("Time Zone: "); Serial.println(tZ); Blynk.config(blynk_token, "blynk-cloud.com", 8442); Serial.print("token: " ); Serial.println(blynk_token); //преобразование char в float Tmax = atof (Tmx); Cmax = atof (Cmx); TemperaturePoint0 = atof (Temperature0); TemperaturePoint1 = atof (Temperature1); TemperaturePoint2 = atof (Temperature2); Hmin = atof (Hmn); HourPoint1 = atof (Hour1); HourPoint2 = atof (Hour2); tZone = atof (tZ); //синхронизация часов drawBoot("Clock synchr."); synchronClock(); //периодичность вызова функций timerCO2 = timer.setInterval(15000, readCO2); timerBl = timer.setInterval(5000, sendToBlynk); connectBlynk(); // подключение до Blynk Blynk.virtualWrite(V10, HIGH); //установка кнопки V10 в состояние ВКЛ buttonBlynk = true; } } void loop(){ if (a == 2) { Serial.println(":( OffLINE"); timer.run(); Clock(); sendMeasurements(); TransmitterA(); drawOff(); delay(1000); } else if (a == 1) { Serial.println(":) OnLINE"); timer.run(); Clock(); Blynk.run(); BLYNK_WRITE(V10); Transmitter(); sendMeasurements(); if(String(blynk_token) == "Blynk token") drawOffBlynk(); else drawOn(); if (j>=24) j =0; if (hh == j){ if ((mm==30) and ((ss Tmax) or (co2 > Cmax) or (h

Сообщения на е-мейл отправляются php-скриптом. Скрипт загружен на мой почтовый сервер. Он понадобится, если планируется отправка сообщений с другого ресурса.

php-скрипт

Контактор

Управление в контакторе осуществляет модуль Arduino Pro Mini. Он принимает сигнал с RF приемника и вырабатывает сигналы превышения пороговых значений параметров воздуха.

Напряжение питания всех узлов контактора 5В поступает с адаптера AC/DC HLK-PM01.

Сигналы с выводов контроллера 6 (h >Hmin), 5 (co2 > CO2max), 3 (t > Tmax) можно использовать для организации автоматического увлажнения, принудительной вентиляции или кондиционирования воздуха. Преимущество заключается в том, что отпадает необходимость в прокладке кабеля для передачи сигнала управления с датчика на ту или иную систему – достаточно разместить контактор неподалеку от одного из концов провода питания или управления системой.

Я, например, планирую кроме управления котлом отопления подключить к контактору еще и кухонную вытяжку — котел и вытяжка расположены рядом.

Скетч контактора для загрузки в Arduino Pro Mini — под спойлером.

скетч контактора```

/*

* Беспроводной программируемый по Wi-Fi комнатный термостат с монитором качества воздуха и другими полезными функциями (контактор)

*/

#include //https://github.com/sui77/rc-switch

RCSwitch mySwitch = RCSwitch();

void setup() {

pinMode(13, OUTPUT);

pinMode(3, OUTPUT);

pinMode(4, OUTPUT);

pinMode(5, OUTPUT);

pinMode(6, OUTPUT);

digitalWrite(3, HIGH);

digitalWrite(4, HIGH);

digitalWrite(5, HIGH);

digitalWrite(6, HIGH);

digitalWrite(13, LOW);

mySwitch.enableReceive(0);

}

void loop() {

if( mySwitch.available() ){

int value = mySwitch.getReceivedValue();

//t Cmax

if(value == B11111101) digitalWrite(5, LOW);

else if (value == B00000010) digitalWrite(5, HIGH);

//h Tmax

if(value == B11110111)digitalWrite(3, LOW);

else if (value == B00001000) digitalWrite(3, HIGH);

//светодиод D13 Arduino — указывает на наличие связи передатчик-приемник (мигает — связь есть)

if(value == B10101010) digitalWrite(13, HIGH); // B10101010 — код включенного передатчика, генерируется в анализаторе без условий

else digitalWrite(13, LOW);

mySwitch.resetAvailable();

}

}

```

Запуск термостата в работу

Пришло время включить термостат.

Шаг 1:

Сначала включим анализатор.

Вначале надо набраться терпения и, ничего не предпринимая, выждать 3 минуты. Термостат автоматически перейдет в автономный режим работы – без подключения по Wi-Fi к домашней сети и Интернету. Через 3 минуты на экране анализатора в трех строках начнет мелькать все, что ворочает термостат.

Первые две строки на экране не требуют комментариев. В третьей строке – режим работы термостата ( Offline, Online или OffBlynk) и информация о выходе за пределы установленных пороговых значений параметров воздуха. Например, Offline CO2>1000 — термостат работает в автономном режиме, а измеренное содержание СО 2 выше заданного порогового значения 1000 ppm.

Часы в автономном режиме будут показывать неправильное время. Они еще не синхронизированы с сервером точного времени, а также не выполнен ввод часового пояса – это в следующем шаге.

В автономном режиме установлена температура термостатирования 21°С на протяжении суток.

Шаг 2:

Освоившись с автономным режимом, выключим и снова включим адаптер AC/DC анализатора. На экране появится знакомое сообщение, к которому успели привыкнуть за три минуты ожидания автономного режима.

Устройство подняло точку доступа am-5108. Найдем эту точку в списке доступных сетей и подключимся к ней, пароль – на экране. Затем откроем в браузере страницу http://192.168.4.1.

Нажмем кнопку Configure WiFi (No Scan). Откроется страница с формой настроек термостата:

Эта же форма с незаполненными полями и комментариями:

Укажем в форме имя и пароль своей домашней сети, ключ идентификации BLynk, электронную почту. Изменим заданные по умолчанию часовой пояс, время (часы) и температуру для временных точек, а также пороговые значения температуры, влажности и содержания СО2.

Сутки двумя временными точками разбиты на три временных диапазона — первый: с 00 час 00 мин до точки 1 ( Hour 1, Minute 1), второй: с точки 1 ( Hour 1, Minute 1) до точки 2 ( Hour 2, Minute 2) и третий: с точки 2 ( Hour 2, Minute 2) до 00 час 00 мин. Полей для ввода минут на форме нет, минуты для точек 1,2 можно изменить в скетче (переменные MinPoint1, MinPoint2). В каждом из трех временных диапазонов можно задать свою температуру термостатирования — Temperature 0, Temperature 1 и Temperature 2. Если планируется поддерживать постоянной одну и ту же температуру в течение суток, то достаточно задать значение Temperature 0, а поля для точек 1,2 оставить пустыми.

При выборе пороговых значений можно ориентироваться на показатели, которые можно найти в Интернете:

  1. Комфортная температура ночью во время сна 19…21°С, днем — 22…23°С.
  2. Оптимальной относительной влажностью в холодное время года считается влажность 30…45%, а в теплое – 30…60%. Предельные максимальные показатели влажности: зимой она не должна превышать 60%, а летом – 65%.
  3. Максимальный уровень содержания углекислого газа в помещениях не должен превышать 1000 ppm. Рекомендованный уровень для спален – не более 600 ppm. Отметка 1400 ppm – предел допустимого содержания СО2 в помещении. Если его больше, то качество воздуха считается низким.

По умолчанию суточная программа термостатирования (днем – высокая температура, ночью – низкая) задана из предположения, что днем кто-то из жильцов находится в помещении, например, работает на дому. Программу легко изменить под свои реалии.

Поле e-mail можно не заполнять. Тогда предоставленная возможность получать письма на электронную почту о выходе параметров воздуха за пороговые значения будет утрачена. Без введенного ключа Blynk'а – невозможно управлять термостатом и получать информацию о параметрах воздуха на удалении. Впрочем, термостат не "растеряется", если останутся незаполненными поля с предельными значениями параметров воздуха, тогда за ним останется только одна функция: термостатирование.

После сохранения настроек в памяти ESP8266 (кнопка Save), анализатор подключится к сети и начнет работу.

Если ошиблись (бывает!) или решили изменить настройки, снова придется дважды загрузить скетч в ESP8266. Первый раз – с раскомментированной в Setup'e строкой factoryReset(); а второй — с закомментированной, затем повторить шаг 2.

Шаг 3:

Теперь можно включить контактор.

При устойчивой радиосвязи между анализатором и контактором – светодиод D13 на плате Arduino мигает с частотой около 1Гц.

Если контактор принял с анализатора команду на включение обогревательного прибора или отопительной системы — замкнутся нормально разомкнутые контакты реле и загорится соответствующий ему светодиод на модуле реле.

Если нет проблем с "холостым ходом" контактора, то подключаем обогревательный прибор или электронику системы отопления. Обогревательный прибор следует подключать проводом определенного сечения. Удельный показатель для расчета сечения медного провода — 5 А/мм2.

Шаг 4:

Пришло время запустить на смартфоне приложение Blynk. В Интернете много информации о приложении Blynk – нет смысла ее повторять.

Переменные для Blynk (чтобы не искать их в скетче анализатора): температура — V1, влажность – V2, содержание СО2V3, температура термостатирования – V4, виртуальная кнопка — V10.

На моем смартфоне интерфейс Blynk'a (его можно изменять) имеет вид:

На графике – измеренная температура (белый), температура термостатирования (желтый), интервал времени – сутки. Переменные влажности и содержания СО2 на график не выведены, поскольку две дополнительные шкалы сильно ограничивают поле графика, где можно рассмотреть сами кривые.

Сигнал с виртуальной кнопки ТЕРМОСТАТ формируется только в момент нажатия на кнопку. При нажатии на кнопку на экране анализатора мелькает сообщение Тhermo OFF! или Thermo ON! – в зависимости от предыдущего состояния кнопки. Это сообщение актуально при тестировании термостата.

Скриншот ниже иллюстрирует процесс обогрева тепловентилятором мощностью 2 кВт/час помещения площадью около 5-ти квадратных метров с начальной температурой 16°С. Здесь — температура (желтый), влажность (синий) и содержание СО2 (красный).

Синхронная с пилой температуры зубчатая кривая влажности на графике — еще одно подтверждение известному факту, что открытый ТЭН сушит воздух, а пики на кривой содержания СО2 – свидетельство моих кратковременных визитов в помещение.

Теперь протестируем работу системы оповещений на е-мейл. Введем в адресную строку браузера закомментированную строку с http-адресом из кода php-скрипта. Если вы не забыли в настройках указать свой е-мейл, а в окне браузера — информация, как на картинке ниже, то проблем с приемом оповещений скорее всего не будет. Тест особенно полезен при переносе php-скрипта с моего сервера на другой.

Намерения

В дальнейшем планирую поработать над усовершенствованием термостата (как говорят, совершенству нет предела!)

Задач — уйма:

  • Дополнить термостат датчиком температуры с беспроводной связью для измерения температуры на улице.
  • Заменить пару приемник-передатчик RF другой парой с большей дальностью связи при напряжении питания 3…5В. В идеале – хотелось бы собрать контактор с питанием от двух батареек АА на протяжении отопительного сезона.
  • Уйти от ручного форматирования памяти ESP8266 перед каждым изменением настроек термостата через повторную загрузку скетча.
  • Расширить программируемый цикл работы термостата с суточного до недельного.
  • Заменить монохромный экран на цветной и с большим разрешением. Это позволит показывать всю информацию о работе термостата одним кадром, а выход параметров воздуха за пределы установленных границ – изменением цвета.
  • Затем заняться печатными платами и презентабельным внешним видом термостата.

Что еще можно улучшить? Принимаются предложения, замечания. Прислушаюсь к конструктивной критике.

Выводы

  • Благодаря подключению к Интернету, функционал термостата значительно расширился. Кроме основной функции, в нем реализован целый ряд других: от отправки оповещений на е-мейл — до возможности автоматического поддержания качества воздуха в помещении.
  • В термостате появилось новое качество: им можно управлять через Интернет.
  • Радует легкость, с которой программируется термостат: требуется лишь заполнить форму на странице браузера.
  • Появилась возможность сохранять в памяти термостата персональные данные, как это делается, например, в роутерах.

Мои закладки по теме с Хабра

1. Wi-Fi термометр на ESP8266 + DS18B20 всего за 4$

2. Компактный монитор домашнего воздуха (CO2, температура, влажность, давление) с Wi-Fi и мобильным интерфейсом

3. Использование RF-модулей

4. Обзор инфракрасного датчика CO2 MH-Z19

5. Измеряем концентрацию CO2 в квартире с помощью MH-Z19

6. Практический опыт использования Blynk для датчика СО2. Часть 1

Тут же хочу выразить свою благодарность Сергею Сильнову (@kumekay). Он поделился со мной идеей ввода переменных в ESP8266 через Wi-Fi. Идею Сергей реализовал в устройстве, которое подробно описано в публикации "Компактный монитор домашнего воздуха (CO2, температура, влажность, давление) с Wi-Fi и мобильным интерфейсом". Боюсь, что подсказки Сергея этот проект не скоро завершился бы удачно.

P.S. Макет из проекта достойно занял место старого термостата, поскольку тот на четвертый отопительный сезон стал изредка "забывать" включать-выключать систему отопления.

Внимание!

Автор не несет ответственности за возможный негатив при повторении проекта. Вы отвечаете за все, что делаете.
habr.com
© ФГУП «ГосНИИПП», 1989-2024