خدمة الواي فاي اللاسلكية ترموستات غرفة قابلة للبرمجة مع شاشة مراقبة جودة الهواء وغيرها من الميزات المفيدة

يحتوي نظام التدفئة الذاتي في شقتي على ترموستات للغرفة اللاسلكية متاح تجاريًا. النظام ، بالطبع ، يعمل بدونه: تم شراء منظم الحرارة لتوفير استهلاك الغاز وزيادة الراحة.


الشيء مفيد للغاية ، ولكن ، في رأيي ، عفا عليه الزمن إلى حد ما. تقرر تجميع شيء مشابه لترموستات تم شراؤه ، مضيفًا أن تبدأ بإعداد واتصال إنترنت أكثر ملاءمة في تخطيط الترموستات.


ما حدث نتيجة لذلك - اقرأ على. آمل ، إلى جانبي ، أن يكون المشروع مثيراً للآخرين.


التعارف


الميزات والمواصفات:


  • يتم الاتصال بين العقد من الحرارة عن طريق الهواء على ترددات الراديو.
  • خلال اليوم ، يحافظ منظم الحرارة على ثلاث نقاط ثابتة.
  • يتم ضبط إعدادات الترموستات (برنامج العمل ، معلمات حدود الهواء ، وغيرها) عن بعد عبر Wi-Fi من النموذج في المستعرض.
  • يتضمن منظم الحرارة وظيفة جهاز مراقبة جودة الهواء مع قياس درجة الحرارة ، ومستوى ثاني أكسيد الكربون والرطوبة الهوائية.
  • ترموستات مجهز بساعة في الوقت الفعلي مع تزامن على مدار الساعة مع خادم التوقيت الدقيق عبر الإنترنت.
  • يتم التحكم في الترموستات من واجهة تطبيق Blynk للهاتف المحمول. بالإضافة إلى ذلك ، يقبل تطبيق Blynk ويعرض نتائج قياسات درجة الحرارة ، وثاني أكسيد الكربون ، ورطوبة الهواء.
  • يعمل منظم الحرارة تلقائيًا في وضع عدم الاتصال في غياب شبكة Wi-Fi.
  • يتم إرسال الرسائل من منظم الحرارة إلى البريد الإلكتروني إذا كانت درجة الحرارة أو محتوى ثاني أكسيد الكربون أو الرطوبة الجوية خارج القيم العتبة.
  • في الترموستات ، بالإضافة إلى درجة الحرارة ، من الممكن الحفاظ على ما تبقى من معلمات الهواء المقاسة ضمن الحدود المحددة.
  • في نهاية موسم التدفئة ، لن يتعين إخفاء منظم الحرارة: ستظل شاشة مراقبة جودة الهواء قيد التشغيل مع إرسال الرسائل إلى البريد وعلى مدار الساعة.

يتكون الحرارة من جهازين. في الجهاز الأول ، يتم إنشاء إشارة تحكم لجهاز التدفئة أو نظام التدفئة ويتم إرسالها إلى الجهاز الثاني ، دعنا نسمي هذا الجهاز محلل. يستقبل الجهاز الثاني الإشارة ويفك تشفيرها ويتحكم في مصدر الحرارة - فليكن موصلًا. الاتصال بين المحلل والقواطع لاسلكي ، على تردد الراديو.



الجمعية


لتجميع الجهاز ، ستحتاج إلى مكونات ، يتم عرض قائمة بها وتكلفتها المقدرة بأسعار موقع AliExpress في الجدول.


مكونالسعر ، $
محلل
لوحة Wi-Fi NodeMCU CP2102 ESP82662،53
استشعار درجة الحرارة والرطوبة DHT222،34
استشعار محتوى CO2 MH Z-1918.50
RTC DS3231 ووتش1.00
شاشة OLED LCD زرقاء 0.96 "I2C 128x641.95
وحدة الترددات اللاسلكية 433MHz ، الارسال (سعر طقم: الارسال ، المتلقي)0.99
محول مستوى المنطق 4 قنوات 3.3V-5V (محول الطبقة المنطقية)0.28
مثبت الجهد LM7805 (10 قطعة)0.79
AC100-240V 50 / 60HZ DC12V 2A محول10.70
لوحة التطوير (الألياف الزجاجية) ، جهات الاتصال ، إلخ.2.00
قواطع
اردوينو برو البسيطة 5 فولت وحدة1.45
وحدة RF 433MHz (مستقبل)-
وحدة التتابع 2-قناة0.98
محول AC-DC HLK-PM014.29
لوحة التطوير (الألياف الزجاجية) ، جهات الاتصال ، إلخ.2.00
المجموع (تقريبًا):50

إذا كنت تخطط لتجميع ترموستات مع أبعاد قليلة ، فأنت بحاجة إلى استبدال محول مستوى المنطق ذي 4 قنوات بوحدة مرحل ثنائية القناة وقناة واحدة.


يتم تجميع كلا الجهازين على ألواح الألياف الزجاجية النماذج. تصاعد-- شنت. يتم تثبيت الوحدات على اللوحات ، ويتم تجميعها من "مشط" من جهات الاتصال. هذا النهج له العديد من المزايا: يتم تفكيك المكونات بسهولة ، يتم بسهولة تغيير التثبيت لإصدار جديد من المخطط ، وأخيراً ، لا يمكن رؤيته في الهيكل المحلي الصنع.


يبلغ طول هوائيات المرسِل والمستقبِل سلكًا طوله 17.3 سم ، وتوفر قدرة المرسل المتزايدة وأبسط الهوائيات اتصالات موثوقة داخل الشقة.


محلل



المخ محلل هو وحدة تحكم ESP8266 على لوحة وحدة NodeMCU CP2102. يستقبل إشارات من أجهزة الاستشعار ويولد إشارات التحكم للمرسل والشاشة.



عند تثبيت جهاز استشعار DHT22 على اللوحة ، تكون درجة الحرارة المقاسة 1.5 ... 2 درجة مئوية أعلى من القيمة الحقيقية (حتى بدون الحالة!). لذلك ، يجب وضع مستشعر درجة الحرارة بعيدًا عن العناصر ذات تبديد الحرارة المرتفع LM7805 و NodeMCU CP2102. بالإضافة إلى ذلك ، سيكون من الجيد تثبيت مثبت الجهد LM7805 على الرادياتير ومن الضروري بالتأكيد ضمان حمل جيد للهواء في هذه الحالة لخفض درجة الحرارة وتقليل خطأ القياس. هناك خيار آخر للتخلص من الخطأ وهو نقل مستشعر DHT22 إلى ما وراء حجم الجسم - هذا الخيار أبسط واخترته.


هناك العديد من الشكاوى على الإنترنت حول دقة منخفضة من DHT22. اليوم هناك بديل: أجهزة استشعار درجة الحرارة والرطوبة أكثر حداثة HTU21D ، Si7021 ، SHT21.


يتم توفير الجهد DC 12V من محول AC / DC إلى محلل. علاوة على ذلك ، فإن مثبت الجهد الثابت LM7805 يولد جهد 5V. الجهد امدادات الطاقة من الارسال هو 12 فولت. عند اختبار الجهاز ، عندما يكون المحلل والقواطع بجانب سطح المكتب ، يمكن تشغيل المحلل من منفذ USB بالكمبيوتر عن طريق توفير الجهد لوحدة NodeMCU CP2102 باستخدام كبل USB قياسي - microUSB. الجهد الكهربائي للوحدة NodeMCU CP2102 و MH Z-19 هو 5 فولت ، ويمثل مصدر الطاقة للعقد المتبقية من الدائرة (3.3 فولت) عامل استقرار وحدة NodeMCU CP2102.


مستشعر درجة الحرارة والرطوبة DHT22 متصل بمحطة D6 من وحدة NodeMCU CP2102. يتم توصيل الساعة DC3231 وشاشة 0.96 "ESP8266 (على الوحدة النمطية NodeMCU CP2102) عبر واجهة I2C ثنائية السلك ، ويتم توصيل دبابيس Tx و Rx لمستشعر محتوى CO2 MH Z-19 إلى دبابيس Rx ، Tx ESP8266 ، على التوالي. يتم إرسال الإشارة إلى المحول المستويات المنطقية ، التي تحول الإشارة من NodeMCU CP2102 بسعة تبلغ حوالي 3.3 فولت إلى إشارة ذات سعة قريبة من الجهد العرض لجهاز الإرسال 12V.


إذا كنت تستخدم بطارية بدلاً من بطارية في وحدة المراقبة ، فلا تنسى كسر دائرة شحن البطارية ، وإلا ستنتفخ البطارية بعد بضعة أسابيع من العمل تحت الجهد. مع دقة ساعة تعمل بالطاقة الذاتية تبلغ 2 ثانية / سنة ، نضمن لك ذلك.


رسم محلل للتحميل في ESP8266 تحت المفسد.


رسم محلل
/* *    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++; } } 

إذا كانت إحدى معلمات الهواء على الأقل خارج الحدود المبرمجة ، فإن الجهاز يرسل خطابًا إلى البريد الإلكتروني في نصف ساعة كل ساعة:



يتم إرسال الرسائل إلى البريد الإلكتروني بواسطة البرنامج النصي 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") ?> 


قواطع



يتم التحكم في الموصل بواسطة وحدة Arduino Pro Mini. يستقبل إشارة من مستقبل الترددات اللاسلكية وينتج إشارات لتجاوز قيم عتبة معلمات الهواء.



يأتي الجهد الكهربائي لكل عقد الموصل 5 فولت من محول HLK-PM01 AC / DC.


يمكن استخدام الإشارات الصادرة عن جهاز التحكم في الإخراج 6 (h> Hmin) و 5 (co2> CO2max) و 3 (t> Tmax) لتنظيم الترطيب التلقائي أو التهوية القسرية أو تكييف الهواء. الميزة هي أنه ليست هناك حاجة لوضع كابل لنقل إشارة تحكم من المستشعر إلى نظام معين - ما عليك سوى وضع الموصل بالقرب من أحد طرفي سلك التحكم في الطاقة أو النظام.


على سبيل المثال ، بالإضافة إلى التحكم في غلاية التدفئة ، أخطط لتوصيل غطاء طباخ بالموصل - يوجد المرجل وغطاء الطباخ في مكان قريب.


رسم قواطع للتحميل في Arduino Pro Mini - تحت المفسد.


رسم قواطع
 /* *    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(); } } 


بدء ترموستات للعمل


حان الوقت لتشغيل الحرارة.


الخطوة 1:


أولا ، قم بتشغيل المحلل.



تحتاج أولاً إلى التحلي بالصبر ، وبدون القيام بأي شيء ، انتظر 3 دقائق. سوف ينتقل منظم الحرارة تلقائيًا إلى وضع عدم الاتصال - دون الاتصال عبر شبكة Wi-Fi بالشبكة المنزلية والإنترنت. بعد 3 دقائق ، على شاشة المُحلل في ثلاثة أسطر ، يضيء كل شيء يعمل عليه منظم الحرارة.



لا يحتاج أول سطرين على الشاشة إلى تعليقات. يحتوي السطر الثالث على وضع تشغيل الترموستات ( دون اتصال ، عبر الإنترنت أو OffBlynk ) ومعلومات حول تجاوز قيم العتبة المحددة لمعلمات الهواء. على سبيل المثال ، يعمل Offline CO2> 1000 - يعمل الترموستات في الوضع المستقل ، ويكون محتوى ثاني أكسيد الكربون المقاس أعلى من قيمة العتبة المحددة وهي 1000 جزء في المليون.


ستظهر الساعة غير المتصلة الوقت غير المناسب. لم تتم مزامنتها بعد مع خادم الوقت المحدد ، ولم يتم إدخال المنطقة الزمنية بعد - هذه هي الخطوة التالية.


في الوضع المستقل ، يتم ضبط درجة حرارة الترموستات عند 21 درجة مئوية خلال اليوم.


الخطوة 2:


بعد أن أتقنت وضع عدم الاتصال ، قم بإيقاف تشغيل ومحول محول محلل AC / DC. سوف تظهر رسالة مألوفة على الشاشة ، والتي اعتدنا عليها في ثلاث دقائق من انتظار وضع عدم الاتصال.


رفع الجهاز نقطة الوصول am-5108. نجد هذه النقطة في قائمة الشبكات المتاحة والاتصال بها ، وكلمة المرور على الشاشة. ثم افتح صفحة المتصفح http://192.168.4.1.



انقر فوق الزر تكوين WiFi (بدون مسح ضوئي) . سيتم فتح صفحة مع نموذج إعدادات الحرارة:



نفس النموذج مع الحقول الفارغة والتعليقات:



اذكر في شكل اسم وكلمة مرور الشبكة المنزلية ، مفتاح تحديد BLynk ، البريد الإلكتروني. قم بتغيير المنطقة الزمنية الافتراضية والوقت (ساعات) ودرجة الحرارة للنقاط الزمنية ، وكذلك قيم عتبة درجة الحرارة والرطوبة ومحتوى ثاني أكسيد الكربون.


يتم تقسيم اليوم بنقطتين زمنيتين إلى ثلاثة نطاقات زمنية - الأولى: من 00 ساعة 00 دقيقة إلى النقطة 1 ( ساعة 1 ، دقيقة 1 ) ، والثانية: من النقطة 1 ( ساعة 1 ، دقيقة 1 ) إلى نقطة 2 ( ساعة 2 ، دقيقة 2 ) والثالثة: من النقطة 2 ( ساعة 2 ، دقيقة 2 ) إلى 00 ساعة 00 دقيقة . لا توجد حقول لإدخال الدقائق في النموذج ، يمكن تغيير الدقائق للنقاط 1،2 في المخطط (المتغيرات MinPoint1 و MinPoint2 ). في كل من النطاقات الزمنية الثلاثة ، يمكنك ضبط درجة حرارة التحكم في درجة الحرارة الخاصة بك - درجة الحرارة 0 ، درجة الحرارة 1 ودرجة الحرارة 2 . إذا كنت تخطط للحفاظ على نفس درجة الحرارة ثابتة طوال اليوم ، فقم فقط بتعيين القيمة على درجة الحرارة 0 ، وترك الحقول للنقاط 1.2 فارغة.


عند اختيار قيم العتبة لمعلمات الهواء ، استرشد بالمؤشرات التي وجدتها على الإنترنت:


  1. درجة حرارة مريحة ليلا أثناء النوم 19 ... 21 درجة مئوية ، خلال النهار - 22 ... 23 درجة مئوية
  2. تعتبر الرطوبة النسبية المثلى في موسم البرد الرطوبة من 30 ... 45 ٪ ، وفي الحارة - 30 ... 60 ٪. الحد الأقصى لمؤشرات الرطوبة: في فصل الشتاء يجب ألا يتجاوز 60 ٪ ، وفي الصيف - 65 ٪.
  3. يجب ألا يتجاوز الحد الأقصى لمستوى ثاني أكسيد الكربون في الغرف 1000 جزء في المليون. المستوى الموصى به لغرف النوم ، غرف الأطفال - لا يزيد عن 600 جزء في المليون. علامة 1400 جزء في المليون هي الحد المسموح به لمحتوى ثاني أكسيد الكربون في الغرفة. إذا كان هناك أكثر من ذلك ، فتعتبر جودة الهواء منخفضة.

بشكل افتراضي ، يتم تعيين برنامج التحكم في درجة الحرارة اليومي (خلال النهار - درجة حرارة عالية ، في الليل - منخفض) على افتراض أنه خلال اليوم واحد من المستأجرين في الغرفة ، على سبيل المثال ، العمل في المنزل. البرنامج سهل التغيير ليناسب واقعك.


يمكن ترك حقل البريد الإلكتروني فارغًا. ثم ستفقد فرصة تلقي رسائل البريد الإلكتروني حول خروج المعلمات الهواء وراء القيم العتبة. بدون مفتاح Blynk الذي تم إدخاله ، يستحيل التحكم في الترموستات وتلقي معلومات حول معلمات الهواء عن بُعد. ومع ذلك ، لا يتم "فقدان" الترموستات ، إذا ظلت الحقول ذات القيم المحددة لمعلمات الهواء فارغة ، عندها سيكون لها وظيفة واحدة فقط: ترموستات.


وأكثر شيء واحد. يرجى إدخال جميع الأرقام بتنسيق متغيرات الفاصلة العائمة ، ثم يتم إجراء التحويل إلى التنسيق المطلوب في المخطط. استثناء: النقاط الزمنية 1.2 (ساعة) - تنسيق عدد صحيح.


بعد حفظ الإعدادات في ذاكرة ESP8266 (زر حفظ ) ، سيتصل المحلل بالشبكة ويبدأ التشغيل.


إذا قمت بخطأ ما (حدث ذلك!) أو قررت تغيير الإعدادات ، فسيلزمك مرة أخرى تحميل المخطط في ESP8266 مرتين. أول مرة - مع السطر factoryReset () uncommented في Setup'e ؛ والثاني علق بها ، ثم كرر الخطوة 2.


الخطوة 3:


الآن يمكنك تشغيل المقاولين.


مع الاتصال اللاسلكي المستقر بين المحلل والمقوم ، يومض المصباح D13 على لوحة Arduino بتردد يبلغ حوالي 1 هرتز.


إذا استلم الموصل أمرًا من المحلل لتشغيل جهاز التسخين أو نظام التسخين ، فستغلق جهات اتصال الترحيل المفتوحة عادةً ويضيء مؤشر LED المقابل في وحدة الترحيل.


إذا لم تكن هناك مشكلة مع قواطع الخمول ، فإننا نربط جهاز التدفئة أو الالكترونيات لنظام التدفئة. يجب توصيل جهاز التسخين بسلك من قسم معين. المؤشر المحدد لحساب المقطع العرضي لسلك نحاسي هو 5 أ / ملم 2 .



الخطوة 4:


حان الوقت لإطلاق تطبيق Blynk على هاتفك الذكي. هناك الكثير من المعلومات حول تطبيق Blynk على الإنترنت - لا فائدة من تكرار ذلك.


متغيرات Blynk (عدم البحث عنها في رسم المحلل): درجة الحرارة - V1 ، الرطوبة - محتوى V2 ، CO 2 - V3 ، درجة حرارة التحكم في درجة الحرارة - V4 ، الزر الظاهري - V10 .


على هاتفي الذكي ، تبدو واجهة Blynk'a (يمكنك تغييرها) كما يلي:



يوضح الرسم البياني درجة الحرارة المقاسة (أبيض) ، درجة حرارة الترموستات (أصفر) ، الفاصل الزمني 24 ساعة. لا يتم عرض متغيرات الرطوبة ومحتوى ثاني أكسيد الكربون على الرسم البياني ، حيث إن هناك ميزانين إضافيين يحدان بشدة من حقل الرسم البياني حيث يمكن اعتبار المنحنيات نفسها.


يتم إنشاء إشارة من زر THERMOSTAT الظاهري فقط عند الضغط على الزر. عندما يتم الضغط على زر على شاشة محلل ، والرسالة الحرارية OFF! أو الحرارية! - اعتمادا على الحالة السابقة للزر. هذه الرسالة ذات صلة عند اختبار الحرارة.


توضح لقطة الشاشة أدناه عملية تسخين سخان مروحة بقوة 2 كيلو وات / ساعة بمساحة حوالي 5 أمتار مربعة مع درجة حرارة أولية قدرها 16 درجة مئوية. هنا هي درجة الحرارة (الصفراء) والرطوبة (الأزرق) ومحتوى ثاني أكسيد الكربون (الأحمر).



يمثل منحنى الرطوبة المسنن المتزامن مع درجة حرارة المنشار على الرسم البياني تأكيدًا آخر لحقيقة معروفة مفادها أن عنصر التسخين المفتوح يجفف الهواء ، وأن القمم الموجودة على منحنى محتوى ثاني أكسيد الكربون دليل على زياراتي قصيرة المدى إلى الغرفة.


نحن الآن نختبر تشغيل نظام الإخطار عبر البريد الإلكتروني. أدخل السطر الذي تم التعليق عليه مع عنوان http من كود php-script في شريط عنوان المتصفح. إذا لم تنسَ تحديد بريدك الإلكتروني في الإعدادات ، وفي نافذة المتصفح - المعلومات ، كما في الصورة أدناه ، فمن المحتمل ألا تكون هناك مشاكل في تلقي الإشعارات. يكون الاختبار مفيدًا بشكل خاص عند نقل برنامج نصي php من الخادم إلى خادم آخر.



النوايا


في المستقبل ، أخطط للعمل على تحسين الحرارة (كما يقولون ، ليس هناك حد للكمال!)


المهام - الكثير:


  • تكملة الترموستات بجهاز استشعار درجة الحرارة مع اتصال لاسلكي لقياس درجة الحرارة في الهواء الطلق.
  • استبدل زوج مرسل مستقبل الترددات اللاسلكية بزوج آخر بمدى اتصال أطول بجهد إمداد لا يزيد عن 3 فولت. من الناحية المثالية ، أود تجميع محلل يعمل ببطاريتين AA خلال موسم التدفئة.
  • تجنب التنسيق اليدوي للذاكرة ESP8266 قبل كل تغيير في إعدادات الحرارة عن طريق إعادة تحميل المخطط.
  • تمديد دورة ترموستات للبرمجة من يومية إلى أسبوعية.
  • استبدال شاشة أحادية اللون بالألوان ودقة أعلى. سيمكن ذلك من عرض جميع المعلومات حول عملية الترموستات في إطار واحد ، وإخراج معلمات الهواء خارج الحدود المحددة - عن طريق تغيير اللون.
  • ثم تعامل مع لوحات الدوائر المطبوعة ومظهر أنيق من الحرارة.

ماذا يمكن تحسينه؟ اقتراحات مقبولة ، تعليقات. أستمع إلى النقد البناء.


الاستنتاجات


  • بفضل اتصال الإنترنت ، توسعت وظيفة الحرارة بشكل كبير. بالإضافة إلى الوظيفة الرئيسية ، فإنها تنفذ عددًا من المهام الأخرى: من إرسال التنبيهات عبر البريد الإلكتروني إلى القدرة على الحفاظ على جودة الهواء الداخلي تلقائيًا.
  • ظهرت جودة جديدة في الترموستات: يمكن التحكم فيها عبر الإنترنت.
  • السهولة التي يتم بها برمجة منظم درجة الحرارة (ترموستات) مُرضية: تحتاج فقط إلى ملء النموذج على صفحة المتصفح.
  • يمكنك الآن حفظ البيانات الشخصية في ذاكرة منظم الحرارة ، كما هو الحال ، على سبيل المثال ، في أجهزة التوجيه.

انتباه!
المؤلف ليس مسؤولاً عن سلبي محتمل عند تكرار المشروع. أنت مسؤول عن كل ما تفعله.


ملحوظة 1. استحوذ النموذج من المشروع على مكان الترموستات القديم ، لأنه في موسم التدفئة الرابع بدأ أحيانًا "ينسى" تشغيل نظام التدفئة وإيقاف تشغيله.
2. حول الأساليب في حل بعض المهام المذكورة أعلاه ، من الممكن التعرف على مقالاتي الأخرى عن حبري:



إشاراتي المرجعية حول موضوع من هبر


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


All Articles