Drahtloser Wi-Fi-programmierbarer Raumthermostat mit Luftqualitätsmonitor und anderen nützlichen Funktionen

Das autonome Heizsystem meiner Wohnung verfügt über einen handelsüblichen kabellosen Raumthermostat. Das System funktioniert natürlich auch ohne: Der Thermostat wurde gekauft, um den Gasverbrauch zu senken und den Komfort zu erhöhen.


Die Sache ist sehr nützlich, aber meiner Meinung nach etwas veraltet. Es wurde beschlossen, etwas Ähnliches wie einen gekauften Thermostat zusammenzubauen, um zunächst eine bequemere Einrichtung und Internetverbindung im Thermostatlayout zu ermöglichen.


Was als Ergebnis passiert ist - lesen Sie weiter. Ich hoffe, dass das Projekt neben mir für andere interessant sein wird.


Bekanntschaft


Merkmale und Spezifikationen:


  • Die Verbindung zwischen den Knoten des Thermostats erfolgt über Luft mit einer Hochfrequenz.
  • Tagsüber hält der Thermostat drei Temperatursollwerte konstant.
  • Die Thermostateinstellungen (Arbeitsprogramm, Grenzluftparameter usw.) werden über WLAN aus dem Formular im Browser ferngesteuert.
  • Der Thermostat verfügt über die Funktion eines Luftqualitätsmonitors zur Messung von Temperatur, Kohlendioxid und Luftfeuchtigkeit.
  • Der Thermostat ist mit einer Echtzeituhr mit Uhrensynchronisation mit dem genauen Zeitserver über das Internet ausgestattet.
  • Der Thermostat wird über die Schnittstelle der mobilen Blynk-Anwendung gesteuert. Darüber hinaus akzeptiert und zeigt die Blynk-App die Ergebnisse von Temperatur-, CO2- und Luftfeuchtigkeitsmessungen an.
  • Der Thermostat geht automatisch aus, wenn kein WLAN vorhanden ist.
  • Nachrichten werden vom Thermostat an die E-Mail gesendet, wenn die Temperatur, der CO 2 -Gehalt oder die Luftfeuchtigkeit außerhalb der Schwellenwerte liegen.
  • Im Thermostat ist es zusätzlich zur Temperatur möglich, den Rest der gemessenen Luftparameter innerhalb der angegebenen Grenzen zu halten.
  • Am Ende der Heizperiode muss der Thermostat nicht ausgeblendet werden: Der Luftqualitätsmonitor bleibt in Betrieb und sendet Nachrichten an Post und Uhr.

Der Thermostat besteht aus zwei Geräten. Im ersten Gerät wird ein Steuersignal für das Heizgerät oder das Heizsystem erzeugt und an das zweite Gerät übertragen. Nennen wir dieses Gerät einen Analysator. Das zweite Gerät empfängt das Signal, entschlüsselt es und steuert die Wärmequelle - sei es ein Schütz. Die Verbindung zwischen dem Analysator und dem Schütz ist auf der Funkfrequenz drahtlos.



Montage


Für die Montage des Geräts benötigen Sie Komponenten, deren Liste und deren geschätzte Kosten zu den Preisen der AliExpress- Website in der Tabelle aufgeführt sind.


KomponentePreis, $
Analysator
Wi-Fi-Karte NodeMCU CP2102 ESP82662,53
Temperatur- und Feuchtigkeitssensor DHT222,34
CO2-Gehaltssensor MH Z-1918.50
RTC DS3231 Uhr1,00
Bildschirm OLED LCD blau 0,96 "I2C 128x641,95
HF-Modul 433MHz, Sender (Kit-Preis: Sender, Empfänger)0,99
4-Kanal-Logikpegelwandler 3.3V-5V (Logical Layer Converter)0,28
Spannungsstabilisator LM7805 (10 Stk.)0,79
AC100-240V 50 / 60Hz DC12V 2A Adapter10.70
Entwicklungsplatine (Glasfaser), Kontakte usw.2.00
Schütz
Arduino Pro Mini 5V Modul1.45
HF-Modul 433 MHz (Empfänger)- -
2-Kanal-Relaismodul0,98
Adapter AC-DC HLK-PM014.29
Entwicklungsplatine (Glasfaser), Kontakte usw.2.00
Insgesamt (ungefähr):50

Wenn Sie einen Thermostat mit minimalen Abmessungen zusammenbauen möchten, müssen Sie den 4-Kanal-Logikpegelwandler durch ein 2-Kanal- und ein 2-Kanal-Relaismodul durch ein 1-Kanal-Relaismodul ersetzen.


Beide Geräte sind auf Glasfaser-Prototyping-Platinen montiert. Montage - montiert. Die Module werden auf den Paneelen installiert und aus dem "Kamm" der Kontakte zusammengesetzt. Dieser Ansatz hat mehrere Vorteile: Die Komponenten lassen sich leicht zerlegen, die Installation für eine neue Version der Skizze kann leicht geändert werden, und schließlich ist im selbstgebauten Körper nicht sichtbar, wie sie hergestellt wurde.


Die Antennen von Sender und Empfänger sind 17,3 cm lang. Die erhöhte Leistung des Senders und die einfachsten Antennen sorgen für eine zuverlässige Kommunikation innerhalb der Wohnung.


Analysator



Das Gehirn des Analysators ist der ESP8266-Controller auf der Platine des NodeMCU CP2102-Moduls. Es empfängt Signale von Sensoren und erzeugt Steuersignale für den Sender und den Bildschirm.



Bei der Installation des DHT22-Sensors auf der Platine ist die gemessene Temperatur 1,5 ... 2 ° C höher als die tatsächliche (auch ohne Gehäuse!). Stellen Sie den Temperatursensor daher nicht in der Nähe von Elementen mit hoher Wärmeableitung LM7805 und NodeMCU CP2102 auf. Darüber hinaus wäre es schön, den Spannungsregler LM7805 am Kühler anzubringen, und es ist auf jeden Fall erforderlich, eine gute Luftkonvektion sicherzustellen, um die Temperatur zu senken und den Messfehler zu verringern. Eine andere Möglichkeit, den Fehler zu beseitigen, besteht darin, den DHT22-Sensor über das Körpervolumen hinaus zu bewegen - diese Option ist einfacher und ich habe sie gewählt.


Im Internet gibt es viele Beschwerden über die geringe Genauigkeit des DHT22. Heute gibt es eine Alternative: modernere Temperatur- und Feuchtigkeitssensoren HTU21D, Si7021, SHT21.


Eine 12-V-Gleichspannung vom AC / DC-Adapter wird dem Analysator zugeführt. Ferner erzeugt der Konstantspannungsstabilisator LM7805 eine Spannung von 5 V. Die Versorgungsspannung des Senders beträgt 12V. Beim Testen des Geräts, wenn sich der Analysator und das Schütz in der Nähe des Desktops befinden, kann der Analysator über den USB-Anschluss des Computers mit Strom versorgt werden, indem das NodeMCU CP2102-Modul mit einem Standard-USB-Kabel - microUSB - mit Spannung versorgt wird. Die Versorgungsspannung der NodeMCU CP2102 und MH Z-19 beträgt 5 V, die Stromversorgung der verbleibenden Knoten der Schaltung (3,3 V) bildet den Stabilisator des NodeMCU CP2102-Moduls.


Der Temperatur- und Feuchtigkeitssensor DHT22 ist an die D6-Klemme des NodeMCU CP2102-Moduls angeschlossen. Der DC3231-Takt und das 0,96-Zoll-Display sind über die I2C-Zweidrahtschnittstelle mit dem ESP8266 (auf dem NodeMCU CP2102-Modul) verbunden, und die Tx-, Rx-Pins des CO2-Inhaltssensors MH Z-19 sind mit den Rx- bzw. Tx-ESP8266-Pins verbunden logische Pegel, die das Signal von der NodeMCU CP2102 mit einer Amplitude von etwa 3,3 V in ein Signal umwandeln, dessen Amplitude nahe an der Versorgungsspannung des 12-V-Senders liegt.


Wenn Sie im Uhrenmodul eine Batterie anstelle einer Batterie verwenden, vergessen Sie nicht, den Batterieladekreis zu unterbrechen, da sonst die Batterie nach einigen Wochen unter Spannung anschwillt. Mit einer Taktgenauigkeit von 2 Sekunden pro Jahr sind Sie garantiert.


Die Analysatorskizze zum Laden in den ESP8266 befindet sich unter dem Spoiler.


Analysatorskizze
/* *    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++; } } 

Wenn mindestens einer der Luftparameter außerhalb der programmierten Schwellenwerte liegt, sendet das Gerät jede halbe Stunde einen Brief an die E-Mail:



Nachrichten an E-Mail werden per PHP-Skript gesendet. Das Skript wird auf meinen Mailserver hochgeladen. Dies ist erforderlich, wenn Sie Nachrichten von einer anderen Ressource senden möchten.


PHP-Skript
 <?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") ?> 


Schütz



Die Steuerung im Schütz erfolgt über das Arduino Pro Mini-Modul. Es empfängt ein Signal vom HF-Empfänger und erzeugt Signale zum Überschreiten der Schwellenwerte von Luftparametern.



Die Versorgungsspannung aller 5-V-Schützknoten stammt vom AC / DC-Adapter HLK-PM01.


Die Signale von den Klemmen der Steuerung 6 (h> Hmin), 5 (co2> CO2max), 3 (t> Tmax) können verwendet werden, um eine automatische Befeuchtung, Zwangsbelüftung oder Klimaanlage zu organisieren. Der Vorteil ist, dass kein Kabel für die Übertragung des Steuersignals vom Sensor zu einem bestimmten System verlegt werden muss. Platzieren Sie das Schütz einfach in der Nähe eines der Enden der Strom- oder Systemsteuerungskabel.


Zum Beispiel plane ich, zusätzlich zur Steuerung des Heizkessels eine Dunstabzugshaube an das Schütz anzuschließen - der Kessel und die Dunstabzugshaube befinden sich in der Nähe.


Schützskizze zum Laden in Arduino Pro Mini - unter dem Spoiler.


Schützskizze
 /* *    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(); } } 


Starten des Thermostats zur Arbeit


Es ist Zeit, den Thermostat einzuschalten.


Schritt 1:


Schalten Sie zuerst den Analysator ein.



Zuerst müssen Sie Geduld haben und, ohne etwas zu tun, 3 Minuten warten. Der Thermostat wechselt automatisch in den Offline-Modus - ohne über WLAN eine Verbindung zu Ihrem Heimnetzwerk und dem Internet herzustellen. Nach 3 Minuten blinkt auf dem Analysatorbildschirm in drei Zeilen alles, was der Thermostat einschaltet.



Die ersten beiden Zeilen auf dem Bildschirm erfordern keine Kommentare. Die dritte Zeile enthält die Thermostat-Betriebsart ( Offline, Online oder OffBlynk ) und Informationen zum Überschreiten der festgelegten Schwellenwerte der Luftparameter . Beispiel: Offline-CO2> 1000 - Der Thermostat arbeitet im Standalone-Modus und der gemessene CO 2 -Gehalt ist höher als der eingestellte Schwellenwert von 1000 ppm.


Eine Offline-Uhr zeigt die falsche Zeit an. Sie sind noch nicht mit dem genauen Zeitserver synchronisiert und die Zeitzone wurde noch nicht eingegeben - dies ist der nächste Schritt.


Im autonomen Modus wird die Temperatur der Thermostatisierung tagsüber auf 21 ° C eingestellt.


Schritt 2:


Nachdem Sie den Offline-Modus beherrschen, schalten Sie den AC / DC-Analysatoradapter aus und wieder ein. Auf dem Bildschirm wird eine vertraute Meldung angezeigt, an die wir uns in drei Minuten des Wartens auf den Offline-Modus gewöhnt haben.


Das Gerät hat den Zugangspunkt am-5108 angehoben. Wir finden diesen Punkt in der Liste der verfügbaren Netzwerke und stellen eine Verbindung her, das Passwort wird auf dem Bildschirm angezeigt. Öffnen Sie dann die Browserseite http://192.168.4.1.



Klicken Sie auf die Schaltfläche WLAN konfigurieren (kein Scan) . Eine Seite mit dem Formular für die Thermostateinstellungen wird geöffnet:



Das gleiche Formular mit leeren Feldern und Kommentaren:



Geben Sie in der Form den Namen und das Passwort Ihres Heimnetzwerks, den BLynk-Identifikationsschlüssel und die E-Mail-Adresse an. Ändern Sie die Standardzeitzone, Zeit (Stunden) und Temperatur für Zeitpunkte sowie Schwellenwerte für Temperatur, Luftfeuchtigkeit und CO 2 -Gehalt.


Der Tag ist durch zwei Zeitpunkte in drei Zeitbereiche unterteilt - der erste: von 00 Stunden 00 Minuten bis Punkt 1 ( Stunde 1, Minute 1 ), der zweite: von Punkt 1 ( Stunde 1, Minute 1 ) bis Punkt 2 ( Stunde 2, Minute 2 ) und der dritte: von Punkt 2 ( Stunde 2, Minute 2 ) bis 00 Stunden 00 Minuten . Es gibt keine Felder für die Eingabe von Minuten im Formular. Minuten für Punkte 1,2 können in der Skizze geändert werden (Variablen MinPoint1, MinPoint2 ). In jedem der drei Zeitbereiche können Sie Ihre eigene Temperaturregelungstemperatur einstellen - Temperatur 0, Temperatur 1 und Temperatur 2 . Wenn Sie die gleiche Temperatur den ganzen Tag über konstant halten möchten, setzen Sie den Wert einfach auf Temperatur 0 und lassen Sie die Felder für die Punkte 1.2 leer.


Lassen Sie sich bei der Auswahl von Schwellenwerten für Luftparameter von Indikatoren leiten, die ich im Internet gefunden habe:


  1. Angenehme Nachttemperatur im Schlaf 19 ... 21 ° C, tagsüber - 22 ... 23 ° C.
  2. Die optimale relative Luftfeuchtigkeit in der kalten Jahreszeit wird als Luftfeuchtigkeit von 30 ... 45% und in der warmen Jahreszeit von 30 ... 60% angesehen. Indikatoren für die maximale maximale Luftfeuchtigkeit: Im Winter sollten sie 60% und im Sommer 65% nicht überschreiten.
  3. Der maximale Kohlendioxidgehalt in den Räumen sollte 1000 ppm nicht überschreiten. Das empfohlene Niveau für Schlafzimmer, Kinderzimmer - nicht mehr als 600 ppm. Die Marke von 1400 ppm ist die Grenze des zulässigen CO 2 -Gehalts im Raum. Wenn mehr vorhanden ist, wird die Luftqualität als niedrig angesehen.

Standardmäßig wird das tägliche Temperaturregelungsprogramm (tagsüber - hohe Temperatur, nachts - niedrige Temperatur) unter der Annahme eingestellt, dass tagsüber einer der Mieter im Raum ist und beispielsweise zu Hause arbeitet. Das Programm kann leicht an Ihre Realität angepasst werden.


Das E-Mail- Feld kann leer gelassen werden. Dann geht die Möglichkeit verloren, E-Mails über den Austritt von Luftparametern über die Schwellenwerte hinaus zu erhalten. Ohne den eingegebenen Blynk- Schlüssel ist es unmöglich, den Thermostat zu steuern und Informationen über die Luftparameter aus der Ferne zu erhalten. Der Thermostat geht jedoch nicht „verloren“. Wenn die Felder mit den Grenzwerten der Luftparameter leer bleiben, bleibt nur eine Funktion dahinter: Thermostat.


Und noch etwas. Bitte geben Sie alle Zahlen im Format von Gleitkommavariablen ein. Anschließend erfolgt die Konvertierung in das gewünschte Format in der Skizze. Ausnahme: Zeitpunkte 1.2 (Stunde) - Ganzzahlformat.


Nach dem Speichern der Einstellungen im ESP8266-Speicher (Schaltfläche Speichern) stellt der Analysator eine Verbindung zum Netzwerk her und nimmt den Betrieb auf.


Wenn Sie einen Fehler machen (es passiert!) Oder die Einstellungen ändern möchten, müssen Sie die Skizze erneut zweimal in den ESP8266 laden. Das erste Mal - mit der Zeile FactoryReset (), die in Setup'e nicht kommentiert ist; und der zweite auskommentiert, dann Schritt 2 wiederholen.


Schritt 3:


Jetzt können Sie das Schütz einschalten.


Bei stabiler Funkverbindung zwischen dem Analysator und dem Schütz blinkt die D13-LED auf der Arduino-Platine mit einer Frequenz von ca. 1 Hz.


Wenn das Schütz vom Analysegerät den Befehl erhält, das Heizgerät oder das Heizsystem einzuschalten, werden die normalerweise geöffneten Relaiskontakte geschlossen und die entsprechende LED am Relaismodul leuchtet auf.


Wenn es kein Problem mit dem Leerlaufschütz gibt, schließen wir ein Heizgerät oder eine Elektronik des Heizsystems an. Das Heizgerät sollte mit einem Draht eines bestimmten Abschnitts verbunden werden. Der spezifische Indikator zur Berechnung des Querschnitts eines Kupferdrahtes beträgt 5 A / mm 2 .



Schritt 4:


Es ist Zeit, die Blynk-App auf Ihrem Smartphone zu starten. Es gibt viele Informationen über die Blynk-Anwendung im Internet - es macht keinen Sinn, sie zu wiederholen.


Variablen für Blynk (nicht in der Analysatorskizze zu suchen): Temperatur - V1 , Luftfeuchtigkeit - V2 , CO 2 -Gehalt - V3 , Temperaturregelungstemperatur - V4 , virtuelle Taste - V10 .


Auf meinem Smartphone sieht die Blynk'a-Oberfläche (Sie können sie ändern) folgendermaßen aus:



Die Grafik zeigt die gemessene Temperatur (weiß), Thermostattemperatur (gelb), das Zeitintervall beträgt 24 Stunden. Die Variablen Feuchtigkeit und CO 2 -Gehalt werden im Diagramm nicht angezeigt, da zwei zusätzliche Skalen das Diagrammfeld, in dem die Kurven selbst berücksichtigt werden können, stark einschränken.


Das Signal von der virtuellen THERMOSTAT- Taste wird nur beim Drücken der Taste erzeugt. Wenn eine Taste auf dem Analysatorbildschirm gedrückt wird, wird die Meldung Thermo AUS! oder Thermo EIN! - abhängig vom vorherigen Status der Schaltfläche. Diese Meldung ist beim Testen des Thermostats relevant.


Der folgende Screenshot zeigt den Prozess des Erhitzens eines 2 kW / h-Heizlüfters mit einer Fläche von etwa 5 Quadratmetern und einer Anfangstemperatur von 16 ° C. Hier ist die Temperatur (gelb), Luftfeuchtigkeit (blau) und der Gehalt an CO 2 (rot).



Die Zahnfeuchtigkeitskurve, die mit der Temperatursäge in der Grafik synchronisiert ist, ist eine weitere Bestätigung der bekannten Tatsache, dass ein offenes Heizelement die Luft trocknet, und die Spitzen auf der CO 2 -Gehaltskurve sind ein Beweis für meine kurzfristigen Besuche im Raum.


Jetzt testen wir den Betrieb des Benachrichtigungssystems per E-Mail. Geben Sie die auskommentierte Zeile mit der http-Adresse aus dem PHP-Skript-Code in die Adressleiste des Browsers ein. Wenn Sie nicht vergessen haben, Ihre E-Mail-Adresse in den Einstellungen und im Browserfenster anzugeben - Informationen wie in der Abbildung unten -, gibt es höchstwahrscheinlich keine Probleme beim Empfang von Benachrichtigungen. Der Test ist besonders nützlich, wenn Sie ein PHP-Skript von meinem Server auf einen anderen übertragen.



Absichten


In Zukunft habe ich vor, an der Verbesserung des Thermostats zu arbeiten (wie man so sagt, der Perfektion sind keine Grenzen gesetzt!)


Aufgaben - viel:


  • Ergänzen Sie den Thermostat mit einem Temperatursensor mit drahtloser Verbindung zur Messung der Außentemperatur.
  • Ersetzen Sie das HF-Sender-Empfänger-Paar durch ein anderes Paar mit einer längeren Kommunikationsreichweite und einer Versorgungsspannung von nicht mehr als 3 V. Idealerweise möchte ich während der Heizperiode einen Analysator zusammenbauen, der mit zwei AA-Batterien betrieben wird.
  • Vermeiden Sie die manuelle Formatierung des ESP8266-Speichers vor jeder Änderung der Thermostateinstellungen, indem Sie die Skizze neu laden.
  • Verlängern Sie den programmierbaren Thermostatzyklus von täglich auf wöchentlich.
  • Ersetzen Sie den monochromen Bildschirm durch Farbe und höhere Auflösung. Auf diese Weise können alle Informationen über den Thermostatbetrieb in einem Frame und die Ausgabe von Luftparametern über die festgelegten Grenzen hinaus angezeigt werden - durch eine Farbänderung.
  • Dann beschäftigen Sie sich mit Leiterplatten und einem ansehnlichen Erscheinungsbild des Thermostats.

Was kann noch verbessert werden? Akzeptierte Vorschläge, Kommentare. Ich höre konstruktive Kritik.


Schlussfolgerungen


  • Dank der Internetverbindung hat sich die Thermostatfunktionalität erheblich erweitert. Zusätzlich zur Hauptfunktion werden eine Reihe weiterer Funktionen implementiert: vom Senden von Warnungen per E-Mail bis zur automatischen Aufrechterhaltung der Raumluftqualität.
  • Im Thermostat ist eine neue Qualität aufgetaucht: Er kann über das Internet gesteuert werden.
  • Die Leichtigkeit, mit der der Thermostat programmiert wird, ist erfreulich: Sie müssen nur das Formular auf der Browserseite ausfüllen.
  • Jetzt können Sie persönliche Daten im Speicher des Thermostats speichern, wie dies beispielsweise in Routern der Fall ist.

Achtung!
Der Autor ist nicht verantwortlich für ein mögliches Negativ bei der Wiederholung des Projekts. Sie sind für alles verantwortlich, was Sie tun.


PS 1. Das Modell aus dem Projekt ersetzte würdig den alten Thermostat, da er in der vierten Heizperiode gelegentlich „vergaß“, das Heizsystem ein- und auszuschalten.
2. Über Lösungsansätze für einige der oben aufgeführten Aufgaben kann ich mich in meinen anderen Artikeln über Habré kennenlernen:



Meine Lesezeichen zu einem Thema von Habr


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


All Articles