Thermostat d'ambiance programmable sans fil Wi-Fi avec moniteur de qualité de l'air et autres fonctionnalités utiles

Le système de chauffage autonome de mon appartement dispose d'un thermostat d'ambiance sans fil disponible dans le commerce. Le système fonctionne bien sûr sans lui: le thermostat a été acheté pour économiser la consommation de gaz et augmenter le confort.


La chose est très utile, mais, à mon avis, quelque peu dépassée. Il a été décidé d'assembler quelque chose de similaire à un thermostat acheté, en ajoutant pour commencer une configuration et une connexion Internet plus pratiques dans la configuration du thermostat.


Ce qui s'est passé en conséquence - lisez la suite. J'espère qu'à côté de moi, le projet sera intéressant pour les autres.


Connaissance


Caractéristiques et spécifications:


  • La communication entre les nœuds du thermostat s'effectue par air à une fréquence radio.
  • Pendant la journée, le thermostat maintient trois points de consigne de température constants.
  • Les paramètres du thermostat (programme de travail, paramètres des limites de l'air, autres) sont définis à distance via Wi-Fi à partir du formulaire dans le navigateur.
  • Le thermostat comprend la fonction d'un moniteur de qualité de l'air avec mesure de la température, du niveau de dioxyde de carbone et de l'humidité de l'air.
  • Le thermostat est équipé d'une horloge en temps réel avec synchronisation d'horloge avec le serveur de temps exact via Internet.
  • Le thermostat est contrôlé depuis l'interface de l'application mobile Blynk. De plus, l'application Blynk accepte et affiche les résultats des mesures de température, de CO2 et d'humidité de l'air.
  • Le thermostat se déconnecte automatiquement en l'absence de Wi-Fi.
  • Des messages sont envoyés du thermostat à l'e-mail si la température, la teneur en CO 2 ou l'humidité de l'air sont en dehors des valeurs seuils.
  • Dans le thermostat, en plus de la température, il est possible de maintenir le reste des paramètres d'air mesurés dans les limites spécifiées.
  • À la fin de la saison de chauffage, le thermostat ne devra pas être caché: le moniteur de qualité de l'air restera en fonction avec l'envoi de messages à la poste et à l'horloge.

Le thermostat se compose de deux appareils. Dans le premier appareil, un signal de commande de l'appareil de chauffage ou du système de chauffage est généré et transmis au deuxième appareil, appelons cet appareil un analyseur. Le deuxième appareil reçoit le signal, le déchiffre et contrôle la source de chaleur - que ce soit un contacteur. La connexion entre l'analyseur et le contacteur est sans fil, sur la fréquence radio.



Assemblage


Pour assembler l'appareil, vous aurez besoin de composants, dont la liste et leur coût estimé aux prix du site AliExpress sont indiqués dans le tableau.


ComposantPrix, $
analyseur
Carte Wi-Fi NodeMCU CP2102 ESP82662,53
Capteur de température et d'humidité DHT222,34
Capteur de teneur en CO2 MH Z-1918,50
Montre RTC DS32311,00
Écran OLED LCD bleu 0.96 "I2C 128x641,95
Module RF 433 MHz, émetteur (prix du kit: émetteur, récepteur)0,99
Convertisseur de niveau logique 4 canaux 3,3 V-5 V (convertisseur de couche logique)0,28
Stabilisateur de tension LM7805 (10 pièces)0,79
Adaptateur AC100-240V 50 / 60Hz DC12V 2A10,70
Carte de développement (fibre de verre), contacts, etc.2,00
contacteur
Module Arduino Pro Mini 5V1,45
Module RF 433 MHz (récepteur)-
Module relais 2 canaux0,98
Adaptateur AC-DC HLK-PM014.29
Carte de développement (fibre de verre), contacts, etc.2,00
Total (environ):50

Si vous prévoyez d'assembler un thermostat aux dimensions minimales, vous devez remplacer le convertisseur de niveau logique à 4 canaux par un module de relais à 2 canaux et à 2 canaux par un module à 1 canal.


Les deux appareils sont assemblés sur des cartes de prototypage en fibre de verre. Montage - monté. Les modules sont installés sur les panneaux, assemblés à partir du "peigne" de contacts. Cette approche présente plusieurs avantages: les composants sont facilement démontables, l'installation pour une nouvelle version du croquis est facilement changée, et enfin, dans le corps fait maison, il n'est pas visible comment il est fait.


Les antennes de l'émetteur et du récepteur sont des fils de 17,3 cm de long. La puissance accrue de l'émetteur et les antennes les plus simples assurent une communication fiable dans l'appartement.


Analyseur



Le cerveau de l'analyseur est le contrôleur ESP8266 sur la carte du module NodeMCU CP2102. Il reçoit les signaux des capteurs et génère des signaux de contrôle pour l'émetteur et l'écran.



Lors de l'installation du capteur DHT22 sur la carte, la température mesurée est de 1,5 ... 2 ° C supérieure à la température réelle (même sans le boîtier!). Par conséquent, vous devez placer le capteur de température à l'écart des éléments à dissipation thermique élevée LM7805 et NodeMCU CP2102. De plus, il serait intéressant d'installer le régulateur de tension LM7805 sur le radiateur et il est absolument nécessaire d'assurer une bonne convection d'air dans le boîtier pour abaisser la température et réduire son erreur de mesure. Une autre option pour se débarrasser de l'erreur est de déplacer le capteur DHT22 au-delà du volume corporel - cette option est plus simple et je l'ai choisie.


Il existe de nombreuses plaintes sur Internet concernant la faible précision du DHT22. Il existe aujourd'hui une alternative: des capteurs de température et d'humidité plus modernes HTU21D, Si7021, SHT21.


Une tension CC 12 V de l'adaptateur CA / CC est fournie à l'analyseur. De plus, le stabilisateur de tension constante LM7805 génère une tension de 5V. La tension d'alimentation de l'émetteur est de 12V. Lors du test de l'appareil, lorsque l'analyseur et le contacteur sont près du bureau, l'analyseur peut être alimenté à partir du port USB de l'ordinateur en fournissant une tension au module NodeMCU CP2102 avec un câble USB standard - microUSB. La tension d'alimentation des NodeMCU CP2102 et MH Z-19 est de 5 V, l'alimentation des nœuds restants du circuit (3,3 V) constitue le stabilisateur du module NodeMCU CP2102.


Le capteur de température et d'humidité DHT22 est connecté à la borne D6 du module NodeMCU CP2102. L'horloge DC3231 et l'écran de 0,96 "sont connectés à l'ESP8266 (sur le module NodeMCU CP2102) via l'interface à deux fils I2C, et les broches Tx, Rx du capteur de teneur en CO2 MH Z-19 sont connectées aux broches Rx, Tx ESP8266, respectivement. Le signal est transmis à l'émetteur depuis le NodeMCU CP2102 via le convertisseur. niveaux logiques, qui convertit le signal du NodeMCU CP2102 avec une amplitude d'environ 3,3 V en un signal dont l'amplitude est proche de la tension d'alimentation de l'émetteur 12V.


Si vous utilisez une batterie au lieu d'une batterie dans le module de la montre, n'oubliez pas de rompre le circuit de charge de la batterie, sinon la batterie gonflera après quelques semaines de travail sous tension. Avec une précision d'horloge auto-alimentée de 2 secondes / an, vous êtes garanti.


L'esquisse de l'analyseur pour le chargement dans l'ESP8266 se trouve sous le becquet.


croquis de l'analyseur
/* *    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++; } } 

Si au moins l'un des paramètres d'air est en dehors des seuils programmés, l'appareil envoie une lettre au courrier électronique toutes les demi-heures:



Les messages envoyés par e-mail sont envoyés par script php. Le script est téléchargé sur mon serveur de messagerie. Il sera nécessaire si vous prévoyez d'envoyer des messages à partir d'une autre ressource.


script 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") ?> 


Contacteur



Le contrôle dans le contacteur est effectué par le module Arduino Pro Mini. Il reçoit un signal du récepteur RF et génère des signaux pour dépasser les valeurs de seuil des paramètres de l'air.



La tension d'alimentation de tous les nœuds de contacteurs 5 V provient de l'adaptateur HLK-PM01 AC / DC.


Les signaux des sorties du contrôleur 6 (h> Hmin), 5 (co2> CO2max), 3 (t> Tmax) peuvent être utilisés pour organiser l'humidification automatique, la ventilation forcée ou la climatisation. L'avantage est qu'il n'est pas nécessaire de poser un câble pour transmettre un signal de commande du capteur à un système particulier - il suffit de placer le contacteur près de l'une des extrémités des fils d'alimentation ou de commande du système.


Par exemple, en plus de contrôler la chaudière de chauffage, je prévois de connecter une hotte au contacteur - la chaudière et la hotte sont situées à proximité.


Croquis du contacteur pour le chargement dans Arduino Pro Mini - sous le becquet.


croquis de contacteur
 /* *    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(); } } 


Démarrage du thermostat pour fonctionner


Il est temps d'allumer le thermostat.


Étape 1:


Tout d'abord, allumez l'analyseur.



Vous devez d'abord faire preuve de patience et, sans rien faire, attendre 3 minutes. Le thermostat passera automatiquement en mode hors ligne - sans se connecter via Wi-Fi à votre réseau domestique et à Internet. Après 3 minutes, sur l'écran de l'analyseur en trois lignes, tout ce que le thermostat allume clignote.



Les deux premières lignes de l'écran ne nécessitent aucun commentaire. La troisième ligne contient le mode de fonctionnement du thermostat ( Offline, Online ou OffBlynk ) et des informations sur le dépassement des valeurs de seuil établies des paramètres de l'air. Par exemple, CO2 hors ligne> 1000 - le thermostat fonctionne en mode autonome et la teneur en CO 2 mesurée est supérieure à la valeur seuil définie de 1000 ppm.


Une montre hors ligne affichera le mauvais moment. Ils ne sont pas encore synchronisés avec le serveur horaire exact, et le fuseau horaire n'a pas encore été entré - c'est la prochaine étape.


En mode autonome, la température de thermostatisation est fixée à 21 ° C pendant la journée.


Étape 2:


Après avoir maîtrisé le mode hors ligne, éteignez et rallumez l'adaptateur d'analyseur AC / DC. Un message familier apparaîtra à l'écran, auquel nous nous sommes habitués en trois minutes d'attente pour le mode hors ligne.


L'appareil a soulevé le point d'accès am-5108. On retrouve ce point dans la liste des réseaux disponibles et on s'y connecte, le mot de passe est à l'écran. Ouvrez ensuite la page du navigateur http://192.168.4.1.



Cliquez sur le bouton Configure WiFi (No Scan) . Une page s'ouvrira avec le formulaire de réglages du thermostat:



Le même formulaire avec des champs vides et des commentaires:



Indiquez dans le formulaire le nom et le mot de passe de votre réseau domestique, clé d'identification BLynk, email. Modifiez le fuseau horaire, l'heure (heures) et la température par défaut pour les points horaires, ainsi que les valeurs de seuil pour la température, l'humidité et la teneur en CO 2 .


La journée est divisée par deux points dans trois plages horaires - la première: de 00 heure 00 min au point 1 ( heure 1, minute 1 ), la seconde: du point 1 ( heure 1, minute 1 ) au point 2 ( heure 2, minute 2 ) et le troisième: du point 2 ( heure 2, minute 2 ) à 00 heures 00 minutes . Il n'y a pas de champs pour entrer les minutes sur le formulaire, les minutes pour les points 1,2 peuvent être modifiées dans l'esquisse (variables MinPoint1, MinPoint2 ). Dans chacune des trois plages de temps, vous pouvez définir votre propre température de contrôle de la température - Température 0, Température 1 et Température 2 . Si vous prévoyez de maintenir la même température constante tout au long de la journée, définissez simplement la valeur sur Température 0 et laissez les champs des points 1.2 vides.


Lorsque vous choisissez des valeurs seuils pour les paramètres de l'air, laissez-vous guider par les indicateurs que j'ai trouvés sur Internet:


  1. Température confortable la nuit pendant le sommeil 19 ... 21 ° C, pendant la journée - 22 ... 23 ° C.
  2. L'humidité relative optimale pendant la saison froide est considérée comme une humidité de 30 ... 45%, et pendant la chaleur - 30 ... 60%. Indicateurs d'humidité maximale maximale: en hiver, elle ne doit pas dépasser 60%, et en été - 65%.
  3. Le niveau maximum de dioxyde de carbone dans les pièces ne doit pas dépasser 1000 ppm. Le niveau recommandé pour les chambres, les chambres d'enfants - pas plus de 600 ppm. La marque de 1400 ppm est la limite de la teneur en CO 2 admissible dans la pièce. S'il y en a plus, la qualité de l'air est considérée comme faible.

Par défaut, le programme de contrôle de la température quotidienne (pendant la journée - température élevée, pendant la nuit - faible) est défini sur l'hypothèse que pendant la journée, l'un des locataires est dans la pièce, par exemple, travaillant à domicile. Le programme est facile à changer pour s'adapter à vos réalités.


Le champ e-mail peut être laissé vide. Ensuite, la possibilité de recevoir des e-mails sur la sortie des paramètres d'air au-delà des valeurs de seuil sera perdue. Sans la clé Blynk entrée, il est impossible de contrôler le thermostat et de recevoir des informations sur les paramètres de l'air à distance. Cependant, le thermostat n'est pas «perdu», si les champs avec les valeurs limites des paramètres de l'air restent vides, alors il n'aura qu'une seule fonction: la thermostatisation.


Et encore une chose. Veuillez saisir tous les nombres au format des variables à virgule flottante, puis la conversion au format souhaité est effectuée dans l'esquisse. Exception: points de temps 1.2 (heure) - format entier.


Après avoir enregistré les paramètres dans la mémoire ESP8266 (bouton Enregistrer ), l'analyseur se connectera au réseau et commencera à fonctionner.


Si vous vous trompez (cela arrive!) Ou décidez de modifier les paramètres, vous devrez à nouveau charger l'esquisse dans l'ESP8266 deux fois. La première fois - avec la ligne factoryReset () non commentée dans Setup'e; et le second a commenté, puis répétez l'étape 2.


Étape 3:


Vous pouvez maintenant activer le contacteur.


Avec une communication radio stable entre l'analyseur et le contacteur, la LED D13 sur la carte Arduino clignote à une fréquence d'environ 1 Hz.


Si le contacteur reçoit une commande de l'analyseur pour allumer l'appareil de chauffage ou le système de chauffage, les contacts de relais normalement ouverts se ferment et la LED correspondante sur le module de relais s'allume.


S'il n'y a pas de problème avec le contacteur de ralenti, nous connectons un appareil de chauffage ou une électronique du système de chauffage. L'appareil de chauffage doit être connecté avec un fil d'une certaine section. L'indicateur spécifique pour le calcul de la section d'un fil de cuivre est de 5 A / mm 2 .



Étape 4:


Il est temps de lancer l'application Blynk sur votre smartphone. Il y a beaucoup d'informations sur l'application Blynk sur Internet - il est inutile de la répéter.


Variables pour Blynk (afin de ne pas les rechercher dans l'esquisse de l'analyseur): température - V1 , humidité - V2 , teneur en CO 2 - V3 , température de contrôle de la température - V4 , bouton virtuel - V10 .


Sur mon smartphone, l'interface Blynk'a (vous pouvez la changer) ressemble à:



Le graphique montre la température mesurée (blanc), la température de thermostatisation (jaune), l'intervalle de temps est de 24 heures. Les variables d'humidité et de teneur en CO 2 ne sont pas affichées sur le graphique, car deux échelles supplémentaires limitent considérablement le champ du graphique où les courbes elles-mêmes peuvent être prises en compte.


Le signal du bouton virtuel THERMOSTAT est généré uniquement lorsque le bouton est enfoncé. Lorsqu'une touche est appuyée sur l'écran de l'analyseur, le message Thermo OFF! ou Thermo ON! - en fonction de l'état précédent du bouton. Ce message est pertinent lors du test du thermostat.


La capture d'écran ci-dessous illustre le processus de chauffage d'un radiateur soufflant de 2 kW / heure d'une superficie d'environ 5 mètres carrés avec une température initiale de 16 ° C. Voici la température (jaune), l'humidité (bleu) et la teneur en CO 2 (rouge).



La courbe d'humidité dentée synchronisée avec la température observée sur le graphique est une autre confirmation du fait bien connu qu'un élément chauffant ouvert sèche l'air, et les pics sur la courbe de teneur en CO 2 sont la preuve de mes visites à court terme dans la pièce.


Nous testons maintenant le fonctionnement du système de notification par e-mail. Entrez la ligne commentée avec l'adresse http du code php-script dans la barre d'adresse du navigateur. Si vous n'avez pas oublié de spécifier votre e-mail dans les paramètres et dans la fenêtre du navigateur - informations, comme dans l'image ci-dessous, il n'y aura probablement aucun problème avec la réception des notifications. Le test est particulièrement utile lors du transfert d'un script php de mon serveur vers un autre.



Intentions


À l'avenir, je prévois de travailler à l'amélioration du thermostat (comme on dit, il n'y a pas de limite à la perfection!)


Tâches - beaucoup:


  • Complétez le thermostat avec un capteur de température avec connexion sans fil pour mesurer la température extérieure.
  • Remplacez la paire émetteur-récepteur RF par une autre paire avec une portée de communication plus longue avec une tension d'alimentation ne dépassant pas 3V. Idéalement, je voudrais assembler un analyseur alimenté par deux piles AA pendant la saison de chauffage.
  • Évitez le formatage manuel de la mémoire ESP8266 avant chaque modification des paramètres du thermostat en rechargeant l'esquisse.
  • Prolongez le cycle du thermostat programmable de quotidien à hebdomadaire.
  • Remplacez l'écran monochrome par des couleurs et une résolution plus élevée. Cela permettra d'afficher toutes les informations sur le fonctionnement du thermostat dans un seul cadre, et la sortie des paramètres de l'air au-delà des limites établies - par un changement de couleur.
  • Ensuite, traitez les cartes de circuits imprimés et l'apparence présentable du thermostat.

Quoi d'autre peut être amélioré? Suggestions et commentaires acceptés. J'écoute les critiques constructives.


Conclusions


  • Grâce à la connexion Internet, la fonctionnalité du thermostat s'est considérablement développée. En plus de la fonction principale, il en met en œuvre plusieurs autres: de l'envoi d'alertes par e-mail à la possibilité de maintenir automatiquement la qualité de l'air intérieur.
  • Une nouvelle qualité est apparue dans le thermostat: il peut être contrôlé via Internet.
  • La facilité avec laquelle le thermostat est programmé est agréable: il vous suffit de remplir le formulaire sur la page du navigateur.
  • Vous pouvez maintenant enregistrer des données personnelles dans la mémoire du thermostat, comme cela se fait, par exemple, dans les routeurs.

Attention!
L'auteur n'est pas responsable d'un éventuel négatif lors de la répétition du projet. Vous êtes responsable de tout ce que vous faites.


PS 1. Le modèle du projet a pris dignement la place de l'ancien thermostat, car lors de la quatrième saison de chauffage, il a parfois commencé à «oublier» d'allumer et d'éteindre le système de chauffage.
2. Concernant les approches dans la solution de certaines des tâches énumérées ci-dessus, il est possible de se familiariser avec mes autres articles sur Habré:



Mes signets sur un sujet de Habr


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


All Articles