Termostat ruang Wi-Fi nirkabel yang dapat diprogram dengan monitor kualitas udara dan fitur berguna lainnya

Sistem pemanas otonom apartemen saya memiliki termostat ruang nirkabel yang tersedia secara komersial. Sistem, tentu saja, berfungsi tanpa itu: termostat dibeli untuk menghemat konsumsi gas dan meningkatkan kenyamanan.


Hal ini sangat berguna, tetapi, menurut saya, agak ketinggalan jaman. Diputuskan untuk merakit sesuatu yang mirip dengan termostat yang dibeli, menambahkan, untuk permulaan, konfigurasi yang lebih nyaman dan koneksi Internet ke tata letak termostat.


Apa yang terjadi sebagai hasilnya - baca terus. Saya berharap, selain saya, proyek ini akan menarik bagi orang lain.


Kenalan


Fitur dan spesifikasi:


  • Koneksi antara node termostat dilakukan melalui udara pada frekuensi radio.
  • Pada siang hari, termostat mempertahankan suhu konstan tiga setpoint.
  • Pengaturan termostat (program kerja, parameter batas udara, lainnya) diatur dari jarak jauh melalui Wi-Fi dari formulir di browser.
  • Thermostat termasuk fungsi monitor kualitas udara dengan pengukuran suhu, tingkat karbon dioksida dan kelembaban udara.
  • Thermostat dilengkapi dengan jam waktu nyata dengan sinkronisasi jam dengan server waktu yang tepat melalui Internet.
  • Thermostat dikendalikan dari antarmuka aplikasi seluler Blynk. Selain itu, aplikasi Blynk menerima dan menampilkan hasil pengukuran suhu, CO2, dan kelembaban udara.
  • Thermostat secara otomatis menjadi offline jika tidak ada Wi-Fi.
  • Pesan dikirim dari termostat ke email jika suhu, konten CO 2 atau kelembaban udara di luar nilai ambang batas.
  • Dalam termostat, selain suhu, dimungkinkan untuk mempertahankan sisa parameter udara yang diukur dalam batas yang ditentukan.
  • Pada akhir musim pemanasan, termostat tidak perlu disembunyikan: monitor kualitas udara akan tetap beroperasi dengan mengirim pesan ke pos dan jam.

Thermostat terdiri dari dua perangkat. Pada perangkat pertama, sinyal kontrol untuk perangkat pemanas atau sistem pemanas dihasilkan dan dikirim ke perangkat kedua, mari kita sebut perangkat ini sebagai alat analisis. Perangkat kedua, menerima sinyal, mendekripsi dan mengontrol sumber panas - biarkan itu menjadi kontaktor. Koneksi antara penganalisis dan kontaktor nirkabel, pada frekuensi radio.



Majelis


Untuk merakit perangkat, Anda akan memerlukan komponen, daftar yang dan perkiraan biayanya dengan harga situs web AliExpress ditunjukkan pada tabel.


KomponenHarga, $
penganalisa
Papan Wi-Fi NodeMCU CP2102 ESP82662,53
Sensor suhu dan kelembaban DHT222,34
Sensor konten CO2 MH Z-1918.50
RTC DS3231 Watch1,00
Layar OLED LCD biru 0,96 "I2C 128x641.95
Modul RF 433MHz, pemancar (harga kit: pemancar, penerima)0,99
Konverter tingkat logika 4-saluran 3.3V-5V (Konverter Lapisan Logical)0,28
Stabilizer tegangan LM7805 (10 pcs.)0,79
AC100-240V 50 / 60Hz DC12V 2A adaptor10.70
Papan pengembangan (fiberglass), kontak, dll.2,00
kontaktor
Modul Arduino Pro Mini 5V1.45
Modul RF 433MHz (penerima)-
Modul relai 2-saluran0,98
Adaptor AC-DC HLK-PM014.29
Papan pengembangan (fiberglass), kontak, dll.2,00
Total (kurang-lebih):50

Jika Anda berencana untuk memasang termostat dengan dimensi minimal, maka Anda perlu mengganti konverter level logika 4-channel dengan modul relai 2-channel dan 2-channel dengan yang 1-channel.


Kedua perangkat dirakit pada papan prototyping fiberglass. Pemasangan - dipasang. Modul dipasang pada panel, dirakit dari "sisir" kontak. Pendekatan ini memiliki beberapa keunggulan: komponen-komponennya mudah dibongkar, pemasangan sketsa versi baru mudah diubah, dan akhirnya, di badan buatan sendiri tidak terlihat bagaimana dibuat.


Antena pemancar dan penerima adalah kabel sepanjang 17,3 cm. Daya pemancar meningkat dan antena paling sederhana memberikan komunikasi yang andal di dalam apartemen.


Penganalisa



Otak penganalisa adalah pengontrol ESP8266 di papan modul NodeMCU CP2102. Ini menerima sinyal dari sensor dan menghasilkan sinyal kontrol untuk pemancar dan layar.



Saat memasang sensor DHT22 di papan, suhu yang diukur adalah 1,5 ... 2 ° C lebih tinggi dari yang sebenarnya (bahkan tanpa kasing!). Oleh karena itu, Anda harus meletakkan sensor suhu dari elemen dengan disipasi panas tinggi LM7805 dan NodeMCU CP2102. Selain itu, akan lebih baik untuk menginstal regulator tegangan LM7805 pada radiator dan sudah pasti diperlukan untuk memastikan konveksi udara yang baik dalam hal ini untuk menurunkan suhu dan mengurangi kesalahan pengukurannya. Pilihan lain untuk menghilangkan kesalahan adalah memindahkan sensor DHT22 di luar volume tubuh - opsi ini lebih sederhana dan saya memilihnya.


Ada banyak keluhan di Internet tentang rendahnya akurasi DHT22. Hari ini ada alternatif: sensor suhu dan kelembaban yang lebih modern HTU21D, Si7021, SHT21.


Tegangan DC 12V dari adaptor AC / DC diberikan ke alat analisa. Selanjutnya, stabilisator tegangan konstan LM7805 menghasilkan tegangan 5V. Tegangan catu daya pemancar adalah 12V. Saat menguji perangkat, ketika alat analisis dan kontaktor berada di dekat desktop, alat analisis dapat diaktifkan dari port USB komputer dengan memasok tegangan ke modul NodeMCU CP2102 dengan kabel USB standar - microUSB. Tegangan suplai NodeMCU CP2102 dan MH Z-19 adalah 5V, catu daya ke node yang tersisa dari rangkaian (3.3V) membentuk penstabil modul NodeMCU CP2102.


Sensor suhu dan kelembaban DHT22 terhubung ke terminal D6 dari modul NodeMCU CP2102. Jam DC3231 dan layar 0,96 "terhubung ke ESP8266 (pada modul NodeMCU CP2102) melalui antarmuka dua-kawat I2C, dan pin Tx, Rx dari sensor konten CO2 MH Z-19 masing-masing terhubung ke pin Rx, Tx ESP8266, masing-masing. Sinyal ditransmisikan ke pemancar dari NodeMC2 melalui CP2 level logis, yang mengubah sinyal dari NodeMCU CP2102 dengan amplitudo sekitar 3,3V menjadi sinyal yang amplitudonya dekat dengan tegangan suplai pemancar 12V.


Jika Anda menggunakan baterai alih-alih baterai dalam modul arloji, jangan lupa memutus sirkuit pengisian daya baterai, jika tidak, baterai akan membengkak setelah beberapa minggu bekerja di bawah tegangan. Dengan keakuratan jam berdaya otomatis 2 detik / tahun, Anda dijamin.


Sketsa analisa untuk memuat dalam ESP8266 berada di bawah spoiler.


sketsa analisa
/* *    Wi-Fi           () */ #include <FS.h> #include <Arduino.h> #include <ESP8266WiFi.h> //https://github.com/esp8266/Arduino // Wifi Manager #include <DNSServer.h> #include <ESP8266WebServer.h> #include <WiFiManager.h> //https://github.com/tzapu/WiFiManager //e-mail #include <ESP8266WiFiMulti.h> //https://github.com/esp8266/Arduino/blob/master/libraries/ESP8266WiFi/src/ESP8266WiFiMulti.h #include <ESP8266HTTPClient.h> ESP8266WiFiMulti WiFiMulti; char address[64] {"e-mail"}; //e-mail, address // HTTP requests #include <ESP8266HTTPClient.h> // OTA updates #include <ESP8266httpUpdate.h> // Blynk #include <BlynkSimpleEsp8266.h> // Debounce #include <Bounce2.h> //https://github.com/thomasfredericks/Bounce2 // JSON #include <ArduinoJson.h> //https://github.com/bblanchon/ArduinoJson //clock #include <pgmspace.h> #include <TimeLib.h> #include <WiFiUdp.h> #include <Wire.h> #include <RtcDS3231.h> //https://github.com/Makuna/Rtc RtcDS3231<TwoWire> Rtc(Wire); #define countof(a) (sizeof(a) / sizeof(a[0])) //timer #include <SimpleTimer.h> 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 <DHT.h> #define DHTTYPE DHT22 // DHT 22 DHT dht(DHTPIN, DHTTYPE); #define mySerial Serial // Use U8g2 for i2c OLED Lib #include <SPI.h> #include <U8g2lib.h> 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.h> 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 < 10) Serial.print('0'); Serial.print(digits); } //NTP code const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets time_t getNtpTime() { int tZoneI; tZoneI = (int)tZone; IPAddress ntpServerIP; // NTP server's ip address while (Udp.parsePacket() > 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 < 1500) { int size = Udp.parsePacket(); if (size >= 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] << 24; secsSince1900 |= (unsigned long)packetBuffer[41] << 16; secsSince1900 |= (unsigned long)packetBuffer[42] << 8; secsSince1900 |= (unsigned long)packetBuffer[43]; return secsSince1900 - 2208988800UL + tZoneI * SECS_PER_HOUR + timeSummerWinter * SECS_PER_HOUR; //tZoneI } } Serial.println("No NTP Response (:-()"); return 0; // return 0 if unable to get the time } // send an NTP request to the time server at the given address void sendNTPpacket(IPAddress &address) { // set all bytes in the buffer to 0 memset(packetBuffer, 0, NTP_PACKET_SIZE); // Initialize values needed to form NTP request // (see URL above for details on the packets) packetBuffer[0] = 0b11100011; // LI, Version, Mode packetBuffer[1] = 0; // Stratum, or type of clock packetBuffer[2] = 6; // Polling Interval packetBuffer[3] = 0xEC; // Peer Clock Precision // 8 bytes of zero for Root Delay & Root Dispersion packetBuffer[12] = 49; packetBuffer[13] = 0x4E; packetBuffer[14] = 49; packetBuffer[15] = 52; // all NTP fields have been given values, now // you can send a packet requesting a timestamp: Udp.beginPacket(address, 123); //NTP requests are to port 123 Udp.write(packetBuffer, NTP_PACKET_SIZE); Udp.endPacket(); } void synchronClockA() { WiFiManager wifiManager; Rtc.Begin(); 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); // printDateTime(compiled); 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 older than compile time! (Updating DateTime)"); Rtc.SetDateTime(compiled); } else 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 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 older than compile time! (Updating DateTime)"); Rtc.SetDateTime(compiled); } else 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 < 10) sprintf(time_r, "%d:0%d", hh, mm); else sprintf(time_r, "%d:%d", hh, mm); Serial.println(date_r); Serial.println(time_r); } //callback notifying the need to save config void saveConfigCallback() { Serial.println("Should save config"); shouldSaveConfig = true; } void factoryReset() { Serial.println("Resetting to factory settings"); wifiManager.resetSettings(); SPIFFS.format(); ESP.reset(); } void printString(String str) { Serial.println(str); } void readCO2() { static byte cmd[9] = {0xFF,0x01,0x86,0x00,0x00,0x00,0x00,0x00,0x79}; //  byte response[9]; byte crc = 0; while (mySerial.available())mySerial.read(); //  UART   memset(response, 0, 9);//   mySerial.write(cmd,9);//    CO2 mySerial.readBytes(response, 9);// 9    //   crc = 0; for (int i = 1; i <= 7; i++) { crc += response[i]; } crc = ((~crc)+1); { // CRC if ( !(response[0] == 0xFF && response[1] == 0x86 && response[8] == crc) ) { Serial.println("CRC error"); } else { //  CO2 co2 = (((unsigned int) response[2])<<8) + response[3]; Serial.println("CO2: " + String(co2) + "ppm"); } } } void sendMeasurements() { float t1 {-100}; int h1 {-1}, i; // Temperature t1 = dht.readTemperature(); if ((t1 > -1) and (t1 < 100)) t = t1; Serial.println("T: " + String(t) + "C"); // Humidity h1 = dht.readHumidity(); if ((h1 > -1) and (h1 < 100)) h = h1; Serial.println("H: " + String(h) + "%"); // CO2 readCO2(); } void sendToBlynk(){ Blynk.virtualWrite(V1, t); Blynk.virtualWrite(V2, h); Blynk.virtualWrite(V3, co2); Blynk.virtualWrite(V4, TemperaturePoint0); } void noData() { u8g2.setFont(u8g2_font_9x18_mf); x = 48; y = 40; u8g2.drawStr(x, y, "***"); } void drawOn() { float TemperatureP0; char Online_ch[]{" Online"}; TemperatureP0 = TemperaturePoint0 - Chs; dtostrf(TemperatureP0, 4, 1, Temperature0); // float  char String Temperature0_i; Temperature0_i = String(Temperature0); char Temperature0_i_m [16]; Temperature0_i.toCharArray(Temperature0_i_m, 16); u8g2.clearBuffer(); String Temperature0_p; String onl1 = "OnLine T<"; Temperature0_p = onl1 + Temperature0_i_m; char Temperature0_p_m [16]; Temperature0_p.toCharArray(Temperature0_p_m, 16); String Tmx_i; Tmx_i = String(Tmx); char Tmx_i_m [16]; Tmx_i.toCharArray(Tmx_i_m, 16); u8g2.clearBuffer(); String Tmx_p; String onl2 = "OnLine T>"; 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<"; Hmn_p = onl4 + Hmn_i_m; char Hmn_p_m [16]; Hmn_p.toCharArray(Hmn_p_m, 16); //string 3 u8g2.setFont(u8g2_font_9x18_mf); x = 0; y = 64; u8g2.drawStr(x, y, Online_ch); if ((hh>=HourPoint1) and (hh<=HourPoint2) and (t<TemperatureP0)) u8g2.drawStr(x, y, Temperature0_p_m); else if (t > Tmax) u8g2.drawStr(x, y, Tmx_p_m); else if (co2 > Cmax) u8g2.drawStr(x, y, Cmx_p_m); else if (h < Hmin) u8g2.drawStr(x, y, Hmn_p_m); switch((millis() / 100) % 4) { // Temperature case 0: { String info_t; String paramT; String tmpr = "T("; String grad = "C):"; const char degree {176}; paramT = tmpr + degree + grad; char paramT_m [12]; paramT.toCharArray(paramT_m, 12); info_t = String(t); char info_t_m [12]; info_t.toCharArray(info_t_m, 5); //string 1 u8g2.setFont(u8g2_font_9x18_mf); x = 16; y = u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, paramT_m); //string 2 if ((t > -100) and (t < 100)) { u8g2.setFont(u8g2_font_inb24_mf); x = (128 - u8g2.getStrWidth(info_t_m))/2; y = y + 2 + u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, info_t_m); } else noData(); } break; //Humidity case 1: { String info_h; info_h = String(h); char info_h_m [12]; info_h.toCharArray(info_h_m, 12); //string 1 u8g2.setFont(u8g2_font_9x18_mf); x = 16; y = u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, "H(%):"); //string 2 if ((h > -1) and (h < 100)){ u8g2.setFont(u8g2_font_inb24_mf); x = (128 - u8g2.getStrWidth(info_h_m))/2; y = y + 2 + u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, info_h_m); } else noData(); } break; //CO2 case 2: { String info_co2; info_co2 = String(co2); char info_co2_m [12]; info_co2.toCharArray(info_co2_m, 12); //string 1 u8g2.setFont(u8g2_font_9x18_mf); x = 8; y = u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, "CO2(ppm):"); //string 2 if ((co2 > -1) and (co2 <= 2000)) { u8g2.setFont(u8g2_font_inb24_mf); x = (128 - u8g2.getStrWidth(info_co2_m))/2; y = y + 2 + u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, info_co2_m); } else noData(); } break; //time, date case 3: { //string 1 u8g2.setFont(u8g2_font_9x18_mf); x = (128 - u8g2.getStrWidth(date_r))/2; y = u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, date_r); //string 2 u8g2.setFont(u8g2_font_inb24_mf); x = (128 - u8g2.getStrWidth(time_r))/2; y = y + 2 + u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, time_r); } break; } u8g2.sendBuffer(); } void drawOff() { float TemperatureP0A; char OffLine_ch[]{"Offline Tst=21"}; TemperatureP0A = TemperaturePointA0 - Chs; // dtostrf(TemperatureP0A, 4, 1, TemperaturePointA0); // float  char String TemperaturePointA0_i; TemperaturePointA0_i = String(TemperaturePointA0); char TemperaturePointA0_i_m [16]; TemperaturePointA0_i.toCharArray(TemperaturePointA0_i_m, 16); u8g2.clearBuffer(); String TemperaturePointA0_p; String onl1 = "Offline T<"; TemperaturePointA0_p = onl1 + TemperaturePointA0_i_m; char TemperaturePointA0_p_m [16]; TemperaturePointA0_p.toCharArray(TemperaturePointA0_p_m, 16); //string 3 u8g2.setFont(u8g2_font_9x18_mf); x = 0; y = 64; u8g2.drawStr(x, y, OffLine_ch); if (t<TemperatureP0A) u8g2.drawStr(x, y, TemperaturePointA0_p_m); switch((millis() / 100) % 4) { // Temperature case 0: { String info_t; String paramT; String tmpr = "T("; String grad = "C):"; const char degree {176}; paramT = tmpr + degree + grad; char paramT_m [12]; paramT.toCharArray(paramT_m, 12); info_t = String(t); char info_t_m [12]; info_t.toCharArray(info_t_m, 5); //string 1 u8g2.setFont(u8g2_font_9x18_mf); x = 16; y = u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, paramT_m); //string 2 if ((t > -100) and (t < 100)) { u8g2.setFont(u8g2_font_inb24_mf); x = (128 - u8g2.getStrWidth(info_t_m))/2; y = y + 2 + u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, info_t_m); } else noData(); } break; //Humidity case 1: { String info_h; info_h = String(h); char info_h_m [12]; info_h.toCharArray(info_h_m, 12); //string 1 u8g2.setFont(u8g2_font_9x18_mf); x = 16; y = u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, "H(%):"); //string 2 if ((h > -1) and (h < 100)){ u8g2.setFont(u8g2_font_inb24_mf); x = (128 - u8g2.getStrWidth(info_h_m))/2; y = y + 2 + u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, info_h_m); } else noData(); } break; //CO2 case 2: { String info_co2; info_co2 = String(co2); char info_co2_m [12]; info_co2.toCharArray(info_co2_m, 12); //string 1 u8g2.setFont(u8g2_font_9x18_mf); x = 8; y = u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, "CO2(ppm):"); //string 2 if ((co2 > -1) and (co2 <= 2000)) { u8g2.setFont(u8g2_font_inb24_mf); x = (128 - u8g2.getStrWidth(info_co2_m))/2; y = y + 2 + u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, info_co2_m); } else noData(); } break; //time, date case 3: { //string 1 u8g2.setFont(u8g2_font_9x18_mf); x = (128 - u8g2.getStrWidth(date_r))/2; y = u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, date_r); //string 2 u8g2.setFont(u8g2_font_inb24_mf); x = (128 - u8g2.getStrWidth(time_r))/2; y = y + 2 + u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, time_r); } break; } u8g2.sendBuffer(); } void drawOffBlynk() { float TemperatureP0; char OffBlynk_ch[]{" OffBlynk"}; TemperatureP0 = TemperaturePoint0 - Chs; dtostrf(TemperatureP0, 4, 1, Temperature0); // float  char String Temperature0_i; Temperature0_i = String(Temperature0); char Temperature0_i_m [16]; Temperature0_i.toCharArray(Temperature0_i_m, 16); u8g2.clearBuffer(); String Temperature0_p; String onl1 = "OffBL T<"; Temperature0_p = onl1 + Temperature0_i_m; char Temperature0_p_m [16]; Temperature0_p.toCharArray(Temperature0_p_m, 16); String Tmx_i; Tmx_i = String(Tmx); char Tmx_i_m [16]; Tmx_i.toCharArray(Tmx_i_m, 16); u8g2.clearBuffer(); String Tmx_p; String onl2 = "OffBL T>"; 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<"; Hmn_p = onl4 + Hmn_i_m; char Hmn_p_m [16]; Hmn_p.toCharArray(Hmn_p_m, 16); //string 3 u8g2.setFont(u8g2_font_9x18_mf); x = 0; y = 64; u8g2.drawStr(x, y, OffBlynk_ch); if ((hh>=HourPoint1) and (hh<=HourPoint2) and (t<TemperatureP0)) u8g2.drawStr(x, y, Temperature0_p_m); else if (t > Tmax) u8g2.drawStr(x, y, Tmx_p_m); else if (co2 > Cmax) u8g2.drawStr(x, y, Cmx_p_m); else if (h < Hmin) u8g2.drawStr(x, y, Hmn_p_m); switch((millis() / 100) % 4) { // Temperature case 0: { String info_t; String paramT; String tmpr = "T("; String grad = "C):"; const char degree {176}; paramT = tmpr + degree + grad; char paramT_m [12]; paramT.toCharArray(paramT_m, 12); info_t = String(t); char info_t_m [12]; info_t.toCharArray(info_t_m, 5); //string 1 u8g2.setFont(u8g2_font_9x18_mf); x = 16; y = u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, paramT_m); //string 2 if ((t > -100) and (t < 100)) { u8g2.setFont(u8g2_font_inb24_mf); x = (128 - u8g2.getStrWidth(info_t_m))/2; y = y + 2 + u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, info_t_m); } else noData(); } break; //Humidity case 1: { String info_h; info_h = String(h); char info_h_m [12]; info_h.toCharArray(info_h_m, 12); //string 1 u8g2.setFont(u8g2_font_9x18_mf); x = 16; y = u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, "H(%):"); //string 2 if ((h > -1) and (h < 100)){ u8g2.setFont(u8g2_font_inb24_mf); x = (128 - u8g2.getStrWidth(info_h_m))/2; y = y + 2 + u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, info_h_m); } else noData(); } break; //CO2 case 2: { String info_co2; info_co2 = String(co2); char info_co2_m [12]; info_co2.toCharArray(info_co2_m, 12); //string 1 u8g2.setFont(u8g2_font_9x18_mf); x = 8; y = u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, "CO2(ppm):"); //string 2 if ((co2 > -1) and (co2 <= 2000)) { u8g2.setFont(u8g2_font_inb24_mf); x = (128 - u8g2.getStrWidth(info_co2_m))/2; y = y + 2 + u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, info_co2_m); } else noData(); } break; //time, date case 3: { //string 1 u8g2.setFont(u8g2_font_9x18_mf); x = (128 - u8g2.getStrWidth(date_r))/2; y = u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, date_r); //string 2 u8g2.setFont(u8g2_font_inb24_mf); x = (128 - u8g2.getStrWidth(time_r))/2; y = y + 2 + u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, time_r); } break; } u8g2.sendBuffer(); } void drawBoot(String msg = "Loading...") { u8g2.clearBuffer(); u8g2.setFont(u8g2_font_9x18_mf); x = (128 - u8g2.getStrWidth(msg.c_str())) / 2; y = 32 + u8g2.getAscent() / 2; u8g2.drawStr(x, y, msg.c_str()); u8g2.sendBuffer(); } void drawConnectionDetails(String ssid, String pass, String url) { String msg {""}; u8g2.clearBuffer(); msg = "Connect to WiFi:"; u8g2.setFont(u8g2_font_7x13_mf); x = (128 - u8g2.getStrWidth(msg.c_str())) / 2; y = u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, msg.c_str()); msg = "net: " + ssid; x = (128 - u8g2.getStrWidth(msg.c_str())) / 2; y = y + 1 + u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, msg.c_str()); msg = "pw: "+ pass; x = (128 - u8g2.getStrWidth(msg.c_str())) / 2; y = y + 1 + u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, msg.c_str()); msg = "Open browser:"; x = (128 - u8g2.getStrWidth(msg.c_str())) / 2; y = y + 1 + u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, msg.c_str()); // URL // u8g2.setFont(u8g2_font_6x12_mf); x = (128 - u8g2.getStrWidth(url.c_str())) / 2; y = y + 1 + u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, url.c_str()); u8g2.sendBuffer(); } bool loadConfigS(){ 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 ); } bool loadConfig() { Serial.println("Load config..."); File configFile = SPIFFS.open("/config.json", "r"); if (!configFile) { Serial.println("Failed to open config file"); return false; } size_t size = configFile.size(); if (size > 1024) { Serial.println("Config file size is too large"); return false; } // Allocate a buffer to store contents of the file. std::unique_ptr<char[]> 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<200> 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<TemperaturePointA0Mn) { if (millis() - TimeTransmitMax > 120000){ TimeTransmitMax = millis(); transmitter.send(B11111110, 8); Serial.println ("t<TemperaturePointA0Mn Thermostat ON"); } } else if (millis() - TimeTransmitMax > 120000) { TimeTransmitMax = millis(); transmitter.send(B10000000, 8); Serial.println ("t>TemperaturePointA0Mn Thermostat OFF"); } if (t<TemperaturePointA0Pl) { if (millis() - TimeTransmitMax > 120000){ TimeTransmitMax = millis(); transmitter.send(B11111110, 8); Serial.println ("t<TemperaturePointA0Pl Thermostat ON"); } } else if (millis() - TimeTransmitMax > 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<TemperaturePoint0Mn) { if (millis() - TimeTransmitMax > 120000){ TimeTransmitMax = millis(); transmitter.send(B11111110, 8); Serial.println ("t<TemperaturePoint0Mn Thermostat ON"); } } else if (millis() - TimeTransmitMax > 120000) { TimeTransmitMax = millis(); transmitter.send(B10000000, 8); Serial.println ("t>TemperaturePoint0Mn Thermostat OFF"); } if (t<TemperaturePoint0Pl) { if (millis() - TimeTransmitMax > 120000){ TimeTransmitMax = millis(); transmitter.send(B11111110, 8); Serial.println ("t<TemperaturePoint0Pl Thermostat ON"); } } else if (millis() - TimeTransmitMax > 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 < HourPoint2)){ progr = 1; if (mm >= MinPoint1) progr = 1; if (mm < MinPoint2) progr = 1; } else if (hh >= 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 < Hmin) { transmitter.send(B11111011, 8); Serial.println("h < Hmin"); } else transmitter.send(B00000100, 8); if (t > 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<30) )){ if ((t > Tmax) or (co2 > Cmax) or (h < Hmin) or ((progr == 0) and (t<(TemperaturePoint0-1.0)) or ((progr == 1) and (t<(TemperaturePoint1-1.0)) or ((progr == 2) and (t<(TemperaturePoint2-1.0)))))) mailer(); } } j++; } } 

Jika setidaknya salah satu parameter udara berada di luar ambang batas yang diprogram, maka perangkat mengirim surat ke email setengah jam setiap jam:



Pesan ke email dikirim melalui skrip php. Skrip diunggah ke server email saya. Ini akan diperlukan jika Anda berencana untuk mengirim pesan dari sumber lain.


skrip php
 <?php //  - http://skorovoda.in.ua/php/aqm42.php?mymail=my_login@my.site.net&t=22.2&h=55&co2=666 $EMAIL=0; $TEMPER=0; $vlaga=0; $carbon=0; $device=0; $EMAIL=$_GET["mymail"]; $device=$_GET["ID"]; echo $EMAIL; $TEMPER=$_GET["t"]; $vlaga=$_GET["h"]; $carbon=$_GET["co2"]; $mdate = date("H:i dmy"); echo <<<END <p>: $TEMPER °<p> <p>: $vlaga %<p> <p>  : $carbon ppm<p> <p>--------------------<p> <p> №: $device<p> END; echo <<<END <p>$mdate</p> END; mail($EMAIL, "Air Quality Monitor " .$device. " v.051018","       №" .$device. " .        (,     )      . === : ".$TEMPER."°C === ".": ".$vlaga."% === "."  : ".$carbon." ppm === "." ! === , : ".$mdate,"From: my_sensors@air-monitor.info \n") ?> 


Kontaktor



Kontrol pada kontaktor dilakukan oleh modul Arduino Pro Mini. Ini menerima sinyal dari penerima RF dan menghasilkan sinyal untuk melebihi nilai ambang batas parameter udara.



Tegangan suplai semua node kontaktor 5V berasal dari adaptor AC / DC HLK-PM01.


Sinyal dari output controller 6 (h> Hmin), 5 (co2> CO2max), 3 (t> Tmax) dapat digunakan untuk mengatur pelembapan otomatis, ventilasi paksa atau pendingin udara. Keuntungannya adalah tidak perlu meletakkan kabel untuk mentransmisikan sinyal kontrol dari sensor ke sistem tertentu - cukup tempatkan kontaktor di dekat salah satu ujung kabel daya atau kabel kontrol sistem.


Sebagai contoh, selain mengendalikan boiler pemanas, saya berencana untuk menghubungkan tudung kompor ke kontaktor - ketel dan tutup kompor terletak di dekatnya.


Sketsa kontaktor untuk memuat di Arduino Pro Mini - di bawah spoiler.


sketsa kontaktor atau
 /* *    Wi-Fi           () */ #include <RCSwitch.h> //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 < Tmin if(value == B11111110) digitalWrite(4, LOW); else if (value == B10000000) digitalWrite(4, HIGH); //co2 > Cmax if(value == B11111101) digitalWrite(5, LOW); else if (value == B00000010) digitalWrite(5, HIGH); //h < Hmin if(value == B11111011) digitalWrite(6, LOW); else if (value == B00000100) digitalWrite(6, HIGH); //t > 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(); } } 


Mulai termostat bekerja


Saatnya untuk menyalakan termostat.


Langkah 1:


Pertama, nyalakan alat analisa.



Pertama, Anda harus memiliki kesabaran dan, tanpa melakukan apa pun, tunggu 3 menit. Thermostat akan secara otomatis masuk ke mode offline - tanpa terhubung melalui Wi-Fi ke jaringan rumah Anda dan Internet. Setelah 3 menit, pada layar analyzer dalam tiga baris, semua yang dihidupkan oleh termostat akan berkedip.



Dua baris pertama di layar tidak memerlukan komentar. Baris ketiga berisi mode pengoperasian termostat ( Offline, Online, atau OffBlynk ) dan informasi tentang melampaui nilai ambang batas yang ditetapkan dari parameter udara. Misalnya, Offline CO2> 1000 - termostat beroperasi dalam mode mandiri, dan konten CO 2 yang diukur lebih tinggi dari nilai ambang batas yang ditetapkan 1000 ppm.


Arloji offline akan menunjukkan waktu yang salah. Mereka belum disinkronkan dengan server waktu yang tepat, dan zona waktu belum dimasukkan - ini adalah langkah berikutnya.


Dalam mode otonom, suhu termostasi diatur pada 21 ° C di siang hari.


Langkah 2:


Setelah menguasai mode offline, matikan dan hidupkan adaptor analisa AC / DC. Pesan yang familier akan muncul di layar, yang sudah biasa kita lakukan dalam tiga menit menunggu mode offline.


Perangkat menaikkan titik akses am-5108. Kami menemukan titik ini dalam daftar jaringan yang tersedia dan terhubung ke sana, kata sandi ada di layar. Kemudian buka halaman browser http://192.168.4.1.



Klik tombol Configure WiFi (No Scan) . Halaman akan terbuka dengan formulir pengaturan termostat:



Bentuk yang sama dengan bidang kosong dan komentar:



Tunjukkan dalam bentuk nama dan kata sandi jaringan rumah Anda, kunci identifikasi BLynk, email. Ubah zona waktu default, waktu (jam) dan suhu untuk titik waktu, serta nilai ambang batas untuk suhu, kelembaban dan konten CO 2 .


Hari ini dibagi oleh dua titik waktu menjadi tiga rentang waktu - yang pertama: dari 00 jam 00 menit ke titik 1 ( Jam 1, Menit 1 ), yang kedua: dari titik 1 ( Jam 1, Menit 1 ) ke titik 2 ( Jam 2, Menit 2 ) dan yang ketiga: dari poin 2 ( Jam 2, Menit 2 ) hingga 00 jam, 00 menit . Tidak ada bidang untuk memasukkan menit pada formulir, menit untuk poin 1,2 dapat diubah dalam sketsa (variabel MinPoint1, MinPoint2 ). Di masing-masing dari tiga rentang waktu, Anda dapat mengatur suhu kontrol suhu Anda sendiri - Suhu 0, Suhu 1 dan Suhu 2 . Jika Anda berencana untuk mempertahankan suhu yang sama konstan sepanjang hari, maka cukup atur nilainya ke Suhu 0 , dan biarkan bidang untuk poin 1.2 kosong.


Saat memilih nilai ambang batas untuk parameter udara, dipandu oleh indikator yang saya temukan di Internet:


  1. Suhu nyaman di malam hari saat tidur 19 ... 21 ° C, siang hari - 22 ... 23 ° C
  2. Kelembaban relatif optimal di musim dingin dianggap kelembaban 30 ... 45%, dan di hangat - 30 ... 60%. Indikator kelembaban maksimum maksimum: di musim dingin tidak boleh melebihi 60%, dan di musim panas - 65%.
  3. Level maksimum karbon dioksida di dalam kamar tidak boleh melebihi 1000 ppm. Tingkat yang disarankan untuk kamar tidur, kamar anak-anak - tidak lebih dari 600 ppm. Tanda 1400 ppm adalah batas konten CO 2 yang diizinkan di dalam ruangan. Jika ada lebih banyak, maka kualitas udara dianggap rendah.

Secara default, program kontrol suhu harian (siang hari - suhu tinggi, malam hari - rendah) ditetapkan dengan asumsi bahwa pada siang hari salah satu penyewa ada di dalam ruangan, misalnya, bekerja di rumah. Program ini mudah diubah agar sesuai dengan realitas Anda.


Bidang email dapat dibiarkan kosong. Maka kesempatan untuk menerima email tentang keluarnya parameter udara di luar nilai ambang akan hilang. Tanpa kunci Blynk yang dimasukkan, tidak mungkin mengontrol termostat dan menerima informasi tentang parameter udara di kejauhan. Namun, termostat tidak “hilang”, jika bidang dengan nilai pembatas parameter udara tetap kosong, maka hanya satu fungsi yang akan tetap berada di belakangnya: termostasi.


Dan satu hal lagi. Silakan masukkan semua angka dalam format variabel floating point, lalu konversi ke format yang diinginkan dilakukan dalam sketsa. Pengecualian: titik waktu 1.2 (jam) - format integer.


Setelah menyimpan pengaturan dalam memori ESP8266 (tombol Simpan ), alat analisa akan terhubung ke jaringan dan memulai operasi.


Jika Anda membuat kesalahan (itu terjadi!) Atau memutuskan untuk mengubah pengaturan, Anda lagi harus memuat sketsa ke ESP8266 dua kali. Pertama kali - dengan line factoryReset () dihapus komentarnya di Setup'e; dan yang kedua berkomentar, lalu ulangi langkah 2.


Langkah 3:


Sekarang Anda dapat menghidupkan kontaktor.


Dengan komunikasi radio yang stabil antara penganalisis dan kontaktor, LED D13 pada papan Arduino berkedip pada frekuensi sekitar 1 Hz.


Jika kontaktor menerima perintah dari penganalisis untuk menghidupkan perangkat pemanas atau sistem pemanas, kontak relai yang biasanya terbuka akan menutup dan LED yang sesuai pada modul relai akan menyala.


Jika tidak ada masalah dengan kontaktor idle, maka kami menghubungkan perangkat pemanas atau elektronik dari sistem pemanas. Perangkat pemanas harus dihubungkan dengan kawat dari bagian tertentu. Indikator spesifik untuk menghitung penampang kawat tembaga adalah 5 A / mm 2 .



Langkah 4:


Saatnya untuk meluncurkan aplikasi Blynk di ponsel cerdas Anda. Ada banyak informasi tentang aplikasi Blynk di Internet - tidak ada gunanya mengulanginya.


Variabel untuk Blynk (bukan untuk mencari mereka dalam sketsa analisa): suhu - V1 , kelembaban - V2 , konten CO 2 - V3 , suhu kontrol suhu - V4 , tombol virtual - V10 .


Di ponsel cerdas saya, antarmuka Blynk'a (Anda dapat mengubahnya) terlihat seperti:



Grafik menunjukkan suhu yang diukur (putih), suhu termostasi (kuning), interval waktu 24 jam. Variabel kelembaban dan konten CO 2 tidak ditampilkan pada grafik, karena dua skala tambahan sangat membatasi bidang grafik di mana kurva itu sendiri dapat dipertimbangkan.


Sinyal dari tombol THERMOSTAT virtual dihasilkan hanya ketika tombol ditekan. Ketika sebuah tombol ditekan pada layar alat analisa, pesan Thermo OFF! atau Thermo ON! - tergantung pada kondisi tombol sebelumnya. Pesan ini relevan saat menguji termostat.


Tangkapan layar di bawah ini menggambarkan proses memanaskan pemanas kipas 2 kW / jam dengan luas sekitar 5 meter persegi dengan suhu awal 16 ° C. Berikut adalah suhu (kuning), kelembaban (biru) dan kandungan CO 2 (merah).



Kurva kelembaban dentate yang disinkronkan dengan suhu yang terlihat pada grafik adalah konfirmasi lain dari fakta terkenal bahwa elemen pemanas terbuka mengeringkan udara, dan puncak pada kurva konten CO 2 adalah bukti dari kunjungan jangka pendek saya ke ruangan.


Sekarang kami sedang menguji operasi sistem notifikasi melalui email. Masukkan baris komentar dengan alamat http dari kode skrip php di bilah alamat browser. Jika Anda tidak lupa menentukan email Anda di pengaturan, dan di jendela browser - informasi, seperti pada gambar di bawah ini, maka kemungkinan besar tidak akan ada masalah dengan menerima pemberitahuan. Tes ini sangat berguna ketika mentransfer skrip php dari server saya ke yang lain.



Niat


Di masa depan saya berencana untuk bekerja memperbaiki termostat (seperti yang mereka katakan, tidak ada batasan untuk kesempurnaan!)


Tugas - banyak:


  • Lengkapi termostat dengan sensor suhu dengan koneksi nirkabel untuk mengukur suhu di luar ruangan.
  • Ganti pasangan pemancar-penerima RF dengan pasangan lain dengan jangkauan komunikasi yang lebih panjang dengan tegangan suplai tidak lebih dari 3V. Idealnya, saya ingin merakit alat analisa yang ditenagai oleh dua baterai AA selama musim panas.
  • Hindari pemformatan manual memori ESP8266 sebelum setiap perubahan dalam pengaturan termostat dengan memuat ulang sketsa.
  • Perpanjang siklus termostat yang dapat diprogram dari harian ke mingguan.
  • Ganti layar monokrom dengan warna dan resolusi lebih tinggi. Ini akan memungkinkan untuk menunjukkan semua informasi tentang operasi termostat dalam satu bingkai, dan output parameter udara di luar batas yang ditetapkan - dengan perubahan warna.
  • Kemudian berurusan dengan papan sirkuit tercetak dan penampilan termostat yang rapi.

Apa lagi yang bisa diperbaiki? Saran, komentar yang diterima. Saya mendengarkan kritik yang membangun.


Kesimpulan


  • Berkat koneksi Internet, fungsi termostat telah berkembang secara signifikan. Selain fungsi utama, ini mengimplementasikan sejumlah fungsi lainnya: mulai dari mengirim peringatan melalui email hingga kemampuan untuk secara otomatis menjaga kualitas udara dalam ruangan.
  • Kualitas baru telah muncul di termostat: itu dapat dikontrol melalui Internet.
  • Kemudahan termostat diprogram menyenangkan: Anda hanya perlu mengisi formulir di halaman browser.
  • Sekarang Anda dapat menyimpan data pribadi dalam memori termostat, seperti yang dilakukan, misalnya di router.

Perhatian!
Penulis tidak bertanggung jawab atas kemungkinan negatif ketika mengulang proyek. Anda bertanggung jawab atas semua yang Anda lakukan.


PS 1. Model dari proyek layak menggantikan termostat lama, karena pada musim pemanasan keempat ia kadang-kadang mulai “lupa” untuk menghidupkan dan mematikan sistem pemanas.
2. Tentang pendekatan dalam solusi dari beberapa tugas yang tercantum di atas adalah mungkin untuk berkenalan di artikel saya yang lain tentang Habré:



Bookmark saya tentang suatu subjek dari Habr


Source: https://habr.com/ru/post/id440978/


All Articles