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 |
Анализатор
Мозг анализатора – контроллер 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 оставить пустыми. При выборе пороговых значений можно ориентироваться на показатели, которые можно найти в Интернете:- Комфортная температура ночью во время сна 19…21°С, днем — 22…23°С.
- Оптимальной относительной влажностью в холодное время года считается влажность 30…45%, а в теплое – 30…60%. Предельные максимальные показатели влажности: зимой она не должна превышать 60%, а летом – 65%.
- Максимальный уровень содержания углекислого газа в помещениях не должен превышать 1000 ppm. Рекомендованный уровень для спален – не более 600 ppm. Отметка 1400 ppm – предел допустимого содержания СО2 в помещении. Если его больше, то качество воздуха считается низким.
Шаг 3:
Теперь можно включить контактор. При устойчивой радиосвязи между анализатором и контактором – светодиод D13 на плате Arduino мигает с частотой около 1Гц. Если контактор принял с анализатора команду на включение обогревательного прибора или отопительной системы — замкнутся нормально разомкнутые контакты реле и загорится соответствующий ему светодиод на модуле реле. Если нет проблем с "холостым ходом" контактора, то подключаем обогревательный прибор или электронику системы отопления. Обогревательный прибор следует подключать проводом определенного сечения. Удельный показатель для расчета сечения медного провода — 5 А/мм2.Шаг 4:
Пришло время запустить на смартфоне приложение Blynk. В Интернете много информации о приложении Blynk – нет смысла ее повторять. Переменные для Blynk (чтобы не искать их в скетче анализатора): температура — V1, влажность – V2, содержание СО2 – V3, температура термостатирования – V4, виртуальная кнопка — V10. На моем смартфоне интерфейс Blynk'a (его можно изменять) имеет вид: На графике – измеренная температура (белый), температура термостатирования (желтый), интервал времени – сутки. Переменные влажности и содержания СО2 на график не выведены, поскольку две дополнительные шкалы сильно ограничивают поле графика, где можно рассмотреть сами кривые. Сигнал с виртуальной кнопки ТЕРМОСТАТ формируется только в момент нажатия на кнопку. При нажатии на кнопку на экране анализатора мелькает сообщение Тhermo OFF! или Thermo ON! – в зависимости от предыдущего состояния кнопки. Это сообщение актуально при тестировании термостата. Скриншот ниже иллюстрирует процесс обогрева тепловентилятором мощностью 2 кВт/час помещения площадью около 5-ти квадратных метров с начальной температурой 16°С. Здесь — температура (желтый), влажность (синий) и содержание СО2 (красный). Синхронная с пилой температуры зубчатая кривая влажности на графике — еще одно подтверждение известному факту, что открытый ТЭН сушит воздух, а пики на кривой содержания СО2 – свидетельство моих кратковременных визитов в помещение. Теперь протестируем работу системы оповещений на е-мейл. Введем в адресную строку браузера закомментированную строку с http-адресом из кода php-скрипта. Если вы не забыли в настройках указать свой е-мейл, а в окне браузера — информация, как на картинке ниже, то проблем с приемом оповещений скорее всего не будет. Тест особенно полезен при переносе php-скрипта с моего сервера на другой.Намерения
В дальнейшем планирую поработать над усовершенствованием термостата (как говорят, совершенству нет предела!) Задач — уйма:- Дополнить термостат датчиком температуры с беспроводной связью для измерения температуры на улице.
- Заменить пару приемник-передатчик RF другой парой с большей дальностью связи при напряжении питания 3…5В. В идеале – хотелось бы собрать контактор с питанием от двух батареек АА на протяжении отопительного сезона.
- Уйти от ручного форматирования памяти ESP8266 перед каждым изменением настроек термостата через повторную загрузку скетча.
- Расширить программируемый цикл работы термостата с суточного до недельного.
- Заменить монохромный экран на цветной и с большим разрешением. Это позволит показывать всю информацию о работе термостата одним кадром, а выход параметров воздуха за пределы установленных границ – изменением цвета.
- Затем заняться печатными платами и презентабельным внешним видом термостата.
Выводы
- Благодаря подключению к Интернету, функционал термостата значительно расширился. Кроме основной функции, в нем реализован целый ряд других: от отправки оповещений на е-мейл — до возможности автоматического поддержания качества воздуха в помещении.
- В термостате появилось новое качество: им можно управлять через Интернет.
- Радует легкость, с которой программируется термостат: требуется лишь заполнить форму на странице браузера.
- Появилась возможность сохранять в памяти термостата персональные данные, как это делается, например, в роутерах.