اثنان في واحد: واي فاي مراقبة جودة الهواء للبرمجة وعلى مدار الساعة

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


في شاشة مراقبة جودة الهواء (المشار إليها فيما يلي باسم الشاشة) من مشروع Sergey ، تتم معالجة المعلومات من أجهزة استشعار درجة الحرارة والرطوبة والضغط ومحتوى 2 في الهواء بواسطة وحدة التحكم ESP8266 وعرضها على شاشة أحادية اللون في عدة إطارات. بالإضافة إلى ذلك ، في الشاشة ، من خلال النموذج الموجود في المستعرض ، يتم تخزين مفتاح تعريف خدمة Blynk في ذاكرة ESP8266 ويتم إرسال البيانات تلقائيًا إلى Blynk.


واجهت الشاشة مشكلة خطيرة واحدة: لقد تعطلت عند إيقاف / تشغيل أو حتى "وميض" الجهد الكهربائي للمراقب.


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


استمر نجاحي الخيالي حتى أول انقطاع طويل في المنزل (يحدث هذا معنا). تم استنفاد البطاريات الرخيصة قبل ظهور الجهد في المخارج ، وعدت إلى نقطة البداية.


بعد أن تخطت أشعل النار بنفسه ، قررت عدم البحث عن حلول بسيطة.


التغييرات / الإضافات التي أدخلت على المخطط من قبل S. Silnov:


  • تتحول الشاشة تلقائيًا إلى وضع عدم الاتصال بعد 90 ثانية من إيقاف تشغيل التيار الكهربائي لشاشة العرض مرة أخرى ، إذا كانت Wi-Fi غير متوفرة في هذا الوقت.
  • يتم إدخال قيم درجة حرارة الحدود ومحتوى ثاني أكسيد الكربون ورطوبة الهواء بالإضافة إلى المنطقة الزمنية ووقت الشتاء / الصيف في ذاكرة ESP8266 ، مثل مفتاح Blynk ، من النموذج الموجود في المستعرض.
  • تم تغيير جذري في القيم الحدود. إذا تم تنفيذ هذا الإجراء في وقت سابق من خلال برنامج التحويل البرمجي للرمز ، يكفي الآن تغيير السجل في أحد حقول النموذج من "0" إلى "1". أصبح هذا العمل ممكنا ولا حتى مستخدم متقدم.
  • يتم عرض معلومات حول تشغيل الشاشة على شاشة ملونة 1.44 "، 128 × 128 في إطار واحد. يتم عرض إخراج معلمات الهواء التي تتجاوز قيم الحد في الإطار بالألوان.
  • في الشاشة ، يتم حساب مؤشر الحرارة (humindex) وعرضه على الشاشة.
  • يتم إرسال الإشعارات من الشاشة عن طريق البريد الإلكتروني إذا كانت درجة الحرارة أو محتوى ثاني أكسيد الكربون أو الرطوبة الجوية خارج الحدود التي حددها المستخدم.
  • يمكن أن تعمل الشاشة دون الاتصال بخدمة Blynk وعنوان البريد الإلكتروني.
  • تمت إضافة ساعة تماثلية في الوقت الفعلي إلى الشاشة ، والتي تتم مزامنتها عبر الإنترنت مع خادم التوقيت الدقيق.


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


الجمعية


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


مكونالسعر ($)
لوحة Wi-Fi NodeMCU CP2102 ESP82662،53
استشعار درجة الحرارة والرطوبة DHT222،34
استشعار محتوى CO2 MH Z-1918.50
شاشة TFTLCD 1.44 "SPI 128x1282.69
RTC DS3231 ووتش1.00
أسلاك التجميع ، تفاهات أخرى2.00
المجموع (تقريبًا):30

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



لسوء الحظ ، لم أجد مكتبة Fritzing لشاشة ملونة بحجم 1.44 بوصة ، 128 × 128 مع دبوس ذو 8 أسنان ، وبالتالي فإن الشاشة بها شاشة ذات 11 سنًا. عند التركيب ، انتبه ليس لموقع إخراج الشاشة بالنسبة إلى الآخرين ، ولكن إلى حمله الوظيفي.


بالنسبة لأولئك الذين لا يحبون تجميع نموذج أولي لمخطط الأسلاك ، فإن جدول الاتصال:


NodeMCU (GPIO)مجسات دبوس
D0 (GPIO 16)Dis_1.44 ، CS
D1 (GPIO 5)DS3231 ، SCL
D2 (GPIO 4)DS3231 ، SDA
D3 (GPIO 0)DHT22 ، البيانات ؛
D4 (GPIO 2)Dis_1.44 ، A0
D5 (GPIO 14)Dis_1.44 ، SCK
D6 (GPIO 12)Dis_1.44 ، RST
D7 (GPIO 13)Dis_1.44 ، SDA
D8 (GPIO 15)GND
تكساسMH-Z19 ، آر إكس
آر إكسMH-Z19 ، تكساس
فين (5 فولت)Dis_1.44، Vcc؛ DHT22 ؛ MH-Z19
3.3 فولتDis_1.44 ، الصمام ؛ DS3231 ، Vcc
GNDمجسات ، GND

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


يمكن توفير جهد شاشة 5V من منفذ USB للكمبيوتر باستخدام كابل USB قياسي - microUSB إلى وحدة NodeMCU esp12 .


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


فقط في حالة ، لا تكون كسولًا جدًا لتعديل برنامج تشغيل I2C المدمج من أجل مجموعة Arduino ESP8266. التعليمات هنا .


رصد رسم
/* *   :   Wi-Fi       * aqm_32F_14F_P */ #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 //OLED #include <SPI.h> #include <Adafruit_GFX.h> #include <TFT_ILI9163C.h> //clock #include <pgmspace.h> #include <TimeLib.h> #include <ESP8266WiFi.h> #include <WiFiUdp.h> #include <Wire.h> #include <RtcDS3231.h> RtcDS3231<TwoWire> Rtc(Wire); #define countof(a) (sizeof(a) / sizeof(a[0])) //e-mail #include <ESP8266WiFiMulti.h> #include <ESP8266HTTPClient.h> #define USE_SERIAL Serial ESP8266WiFiMulti WiFiMulti; //e-mail, address char address[64] {"e-mail"}; // 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 // Debounce interval in ms #define DEBOUNCE_INTERVAL 10 Bounce hwReset {Bounce()}; // Humidity/Temperature #include <DHT.h> #define DHTPIN 0 //D3 gpio0, DHT22 DATA #define DHTTYPE DHT22 // DHT 22 DHT dht(DHTPIN, DHTTYPE); // Blynk token char blynk_token[33] {"Blynk token"}; // Setup Wifi connection WiFiManager wifiManager; // Network credentials String ssid { "am180206" }; String pass { "vb654321" }; //flag for saving data bool shouldSaveConfig = false; // Sensors data float t {-100}, t_old{-100}; float hic {-1}, hic_old{-1}; int h {-1}, h_old{-1}; int co2 {-1}, co2_old{-1}; char Tmn[5]{}, Tmx[5]{}, Hmn[5]{}, Cmx[7]{}, tZ[5]{}, timeSW[4]{}, formFS[]{"0"}; //  t, h  co2, .  float Tmin, Tmax, Hmin, Cmax, tZone, timeSummerWinter, formatingFS; float trp = 0; int crbn, bl, ml=18000; int md; // : 1 - , 2 -  int blnk; // Color definitions #define BLACK 0x0000 #define BLUE 0x001F #define RED 0xF800 #define GREEN 0x07E0 #define CYAN 0x07FF #define MAGENTA 0xF81F #define YELLOW 0xFFE0 #define WHITE 0xFFFF #define GRAY 0x9999 #define __CS 16 //D0(gpio16)- CS(display) #define __DC 2 //D4 gpio2 - AO(display) #define __RST 12 // D6 gpio12 - RESET(display) //char datestring[20]; char time_r[9]; char date_r[12]; //analog clock uint16_t ccenterx = 64,ccentery = 70;//center x,y of the clock clock const uint16_t cradius = 40;//radius of the clock const float scosConst = 0.0174532925; float sx = 0, sy = 1, mx = 1, my = 0, hx = -1, hy = 0; float sdeg=0, mdeg=0, hdeg=0; uint16_t osx,osy,omx,omy,ohx,ohy; uint16_t x0 = 0, x1 = 0, yy0 = 0, yy1 = 0; //uint32_t targetTime = 0;// for next 1 second timeout uint8_t hh,mm,ss; //containers for current time TFT_ILI9163C display = TFT_ILI9163C(__CS, __DC, __RST); String utf8(String source) { int i,k; String target; unsigned char n; char m[2] = { '0', '\0' }; k = source.length(); i = 0; while (i < k) { n = source[i]; i++; if (n >= 0xC0) { switch (n) { case 0xD0: { n = source[i]; i++; if (n == 0x81) { n = 0xA8; break; } if (n >= 0x90 && n <= 0xBF) n = n + 0x30; break; } case 0xD1: { n = source[i]; i++; if (n == 0x91) { n = 0xB8; break; } if (n >= 0x80 && n <= 0x8F) n = n + 0x70; break; } } } m[0] = n; target = target + String(m); } return target; } // NTP Servers: //static const char ntpServerName[] = "us.pool.ntp.org"; static const char ntpServerName[] = "time.nist.gov"; //const int timeZone = 2; // , , , , ,  //const int timeSummer = 1; 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 readCO2(){ #define mySerial Serial 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}, hic1 {-1}; float h1 {-1}; // 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) + "%"); // Humindex hic1 = dht.computeHeatIndex(t, h, false); if (t >= 21.0) hic = hic1; else hic = t; Serial.println("Ti: "+String(hic)+"*C"); // CO2 crbn++; if (crbn > 110) {readCO2(); crbn = 0; Serial.println("CO2: " + String(co2) + "ppm"); } } void drawConnectionDetails() { display.clearScreen(); display.setTextSize(1); display.setCursor(12,24); display.setTextColor(WHITE); display.println(utf8("Connect to WiFi:")); display.setCursor(12,36); display.println(utf8("net: " + String(ssid))); display.setCursor(12,48); display.println(utf8("pass: " + String(pass))); display.setCursor(12,60); display.println(utf8("Open browser:")); display.setCursor(12,72); display.println(utf8("http://192.168.4.1")); display.setCursor(2,84); display.setTextColor(RED); display.println(utf8(" Enter your personal information!")); } 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, timeSummerWinterI; tZoneI = (int)tZone; timeSummerWinterI = (int)timeSummerWinterI; 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 + timeSummerWinterI * SECS_PER_HOUR; } } 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 draw(){ //temperature display.setTextSize(1); display.setCursor(1,6); display.setTextColor(CYAN); display.println(utf8("T: CO2:")); String t_p; t_p = String(t); char t_p_m [12]; t_p.toCharArray(t_p_m, 5); if (t != t_old) { display.fillRect(1,15,48,18,BLACK); display.setTextSize(2); display.setCursor(1,15); display.setTextColor(GREEN); if(t < Tmin) display.setTextColor(RED); if(t > Tmax) display.setTextColor(RED); if ((t > -100) and (t < 100)) display.println(utf8(String(t_p_m))); else display.println(utf8("----"));} //heat index display.setTextSize(1); display.setCursor(2,98); display.setTextColor(CYAN); display.println(utf8("H: Ti:")); String hic_p; hic_p = String(hic); char hic_p_m [12]; hic_p.toCharArray(t_p_m, 5); if (hic != hic_old) { display.fillRect(80,108,48,18,BLACK); display.setTextSize(2); display.setCursor(80,108); display.setTextColor(GREEN); // if(t < Tmin) display.setTextColor(RED); if(hic > 27.0) display.setTextColor(YELLOW); if(hic > 31.0) display.setTextColor(RED); if ((hic > 0) and (hic < 100)) display.println(utf8(String(t_p_m))); else display.println(utf8("----"));} //CO2 if (co2 != co2_old) { display.fillRect(80,15,48,18,BLACK); display.setTextSize(2); display.setCursor(80,15); display.setTextColor(GREEN); if (co2 > Cmax) display.setTextColor(RED); if (co2 > 600) display.setTextColor(CYAN); if ((co2 > -1) and (co2 <= 2000)) display.println(utf8(String(co2))); else display.println(utf8("---")); } //humidity if (h != h_old) { display.fillRect(1,108,49,18,BLACK); display.setTextSize(2); display.setCursor(1,108); display.setTextColor(GREEN); if (h < Hmin) display.setTextColor(RED); if (h > 60) display.setTextColor(RED); if ((h > -1) and (h < 100)) display.println(utf8(String(h))); else display.println(utf8("--")); } //date if (hh==0) display.fillRect(28,1,60,10,BLACK); display.setCursor(28,1); display.setTextSize(1); display.setTextColor(CYAN); display.println(utf8(date_r)); //OFFLINE if (md == 2) { display.fillRect(106,44,18,8,RED); display.setCursor(106,44); display.setTextSize(1); display.setTextColor(CYAN); display.println(" A"); } //OFF BLYNK if (blnk == 1) { display.fillRect(106,44,18,8,RED); display.setCursor(106,44); display.setTextSize(1); display.setTextColor(CYAN); display.println(" B"); } } void synchronClockA() { 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); //setSyncInterval(300); 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(); // WiFi.begin(lnet, key); 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); } //analog void drawClockFace(){ display.fillCircle(ccenterx, ccentery, cradius, BLUE); display.fillCircle(ccenterx, ccentery, cradius-4, BLACK); // Draw 12 lines for(int i = 0; i<360; i+= 30) { sx = cos((i-90)*scosConst); sy = sin((i-90)*scosConst); x0 = sx*(cradius)+ccenterx; yy0 = sy*(cradius)+ccentery; x1 = sx*(cradius-8)+ccenterx; yy1 = sy*(cradius-8)+ccentery; display.drawLine(x0, yy0, x1, yy1, 0x0377); } // Draw 4 lines for(int i = 0; i<360; i+= 90) { sx = cos((i-90)*scosConst); sy = sin((i-90)*scosConst); x0 = sx*(cradius+6)+ccenterx; yy0 = sy*(cradius+6)+ccentery; x1 = sx*(cradius-11)+ccenterx; yy1 = sy*(cradius-11)+ccentery; display.drawLine(x0, yy0, x1, yy1, 0x0377); } } //analog static uint8_t conv2d(const char* p) { uint8_t v = 0; if ('0' <= *p && *p <= '9') v = *p - '0'; return 10 * v + *++p - '0'; } //analog void drawClockHands(uint8_t h,uint8_t m,uint8_t s){ // Pre-compute hand degrees, x & y coords for a fast screen update sdeg = s * 6; // 0-59 -> 0-354 mdeg = m * 6 + sdeg * 0.01666667; // 0-59 -> 0-360 - includes seconds hdeg = h * 30 + mdeg * 0.0833333; // 0-11 -> 0-360 - includes minutes and seconds hx = cos((hdeg-90)*scosConst); hy = sin((hdeg-90)*scosConst); mx = cos((mdeg-90)*scosConst); my = sin((mdeg-90)*scosConst); sx = cos((sdeg-90)*scosConst); sy = sin((sdeg-90)*scosConst); // Erase just old hand positions display.drawLine(ohx, ohy, ccenterx+1, ccentery+1, BLACK); display.drawLine(omx, omy, ccenterx+1, ccentery+1, BLACK); display.drawLine(osx, osy, ccenterx+1, ccentery+1, BLACK); // Draw new hand positions display.drawLine(hx*(cradius-20)+ccenterx+1, hy*(cradius-20)+ccentery+1, ccenterx+1, ccentery+1, WHITE); display.drawLine(mx*(cradius-8)+ccenterx+1, my*(cradius-8)+ccentery+1, ccenterx+1, ccentery+1, WHITE); display.drawLine(sx*(cradius-8)+ccenterx+1, sy*(cradius-8)+ccentery+1, ccenterx+1, ccentery+1, RED); display.fillCircle(ccenterx+1, ccentery+1, 3, RED); // Update old x&y coords osx = sx*(cradius-8)+ccenterx+1; osy = sy*(cradius-8)+ccentery+1; omx = mx*(cradius-8)+ccenterx+1; omy = my*(cradius-8)+ccentery+1; ohx = hx*(cradius-20)+ccenterx+1; ohy = hy*(cradius-20)+ccentery+1; } void FaceClock(){ display.clearScreen(); display.setTextColor(WHITE, BLACK); osx = ccenterx; osy = ccentery; omx = ccenterx; omy = ccentery; ohx = ccenterx; ohy = ccentery; drawClockFace();// Draw clock face } void drawSynchron() { display.clearScreen(); display.setTextSize(2); display.setCursor(2,48); display.setTextColor(WHITE); display.println(utf8(" Clock")); display.setTextSize(1); display.setCursor(2,68); display.setTextColor(WHITE); display.println(utf8("synchronization...")); } void drawWiFi() { display.clearScreen(); display.setTextSize(2); display.setCursor(2,48); display.setTextColor(RED); display.println(utf8("Connection to Wi-Fi")); } void drawBlynk() { display.clearScreen(); display.setTextSize(2); display.setCursor(2,48); display.setTextColor(RED); display.println(utf8("Connection to Blynk")); } 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/wst41.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(); } } //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); } bool loadConfigS() { Blynk.config(address); Serial.print("e-mail: "); Serial.println( address ); Blynk.config(tZ); Serial.print("T_Zone: "); Serial.println( tZ ); Blynk.config(Tmx); Serial.print("T max: "); Serial.println( Tmx ); Blynk.config(Cmx); Serial.print("CO2 max: "); Serial.println( Cmx ); Blynk.config(Tmn); Serial.print("T min: "); Serial.println( Tmn ); Blynk.config(Hmn); Serial.print("H min: "); Serial.println( Hmn ); Blynk.config(timeSW); Serial.print("Time Summer/Winter: "); Serial.println( timeSW ); Blynk.config(formFS); Serial.print("format FS: "); Serial.println( formFS ); 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(tZ, json["tZ"]); strcpy(Tmx, json["Tmx"]); strcpy(Cmx, json["Cmx"]); strcpy(Tmn, json["Tmn"]); strcpy(Hmn, json["Hmn"]); strcpy(timeSW, json["timeSW"]); strcpy(formFS, json["formFS"]); } 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(); } void setupWiFi() { //set config save notify callback wifiManager.setSaveConfigCallback(saveConfigCallback); // Custom parameters WiFiManagerParameter custom_blynk_token("blynk_token", "Blynk token", blynk_token, 34); wifiManager.addParameter(&custom_blynk_token); WiFiManagerParameter custom_address("address", "E-mail", address, 64); wifiManager.addParameter(&custom_address); WiFiManagerParameter custom_tZ("tZ", "Time Zone", tZ, 5); wifiManager.addParameter(&custom_tZ); WiFiManagerParameter custom_Tmn("Tmn", "T min", Tmn, 5); wifiManager.addParameter(&custom_Tmn); WiFiManagerParameter custom_Tmx("Tmx", "T max", Tmx, 5); wifiManager.addParameter(&custom_Tmx); WiFiManagerParameter custom_Cmx("Cmx", "C max", Cmx, 7); wifiManager.addParameter(&custom_Cmx); WiFiManagerParameter custom_Hmn("Hmn", "H min", Hmn, 5); wifiManager.addParameter(&custom_Hmn); WiFiManagerParameter custom_timeSW("timeSW", "Time Summer(1)/Winter(0)", timeSW, 4); wifiManager.addParameter(&custom_timeSW); WiFiManagerParameter custom_formFS("formFS", "formating FS", formFS, 4); wifiManager.addParameter(&custom_formFS); wifiManager.setAPCallback(configModeCallback); wifiManager.setTimeout(60); if (!wifiManager.autoConnect(ssid.c_str(), pass.c_str())) { md = 2; Serial.println("mode OffLINE :("); loadConfigS(); } //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["tZ"] = custom_tZ.getValue(); json["Tmx"] = custom_Tmx.getValue(); json["Cmx"] = custom_Cmx.getValue(); json["Tmn"] = custom_Tmn.getValue(); json["Hmn"] = custom_Hmn.getValue(); json["timeSW"] = custom_timeSW.getValue(); json["formFS"] = custom_formFS.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()); } void connectBlynk(){ if(String(blynk_token)== "Blynk token"){ blnk = 0; Serial.println("! Off Blynk!"); } else { Serial.println("Connecting to blynk..."); while (Blynk.connect() == false) { delay(500); Serial.println("Connecting to blynk..."); } } } void sendToBlynk(){ Blynk.virtualWrite(V1, t); Blynk.virtualWrite(V2, h); Blynk.virtualWrite(V3, co2); Blynk.virtualWrite(V5, hic); } void formatFS(){ SPIFFS.format(); SPIFFS.begin(); } void setup() { Serial.begin(115200); display.begin(); // Init filesystem if (!SPIFFS.begin()) { Serial.println("Failed to mount file system"); ESP.reset(); } md = 1; // Setup WiFi drawWiFi(); //"Connecting to Wi-Fi..." setupWiFi(); if(md == 1){ // Load configuration 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(tZ); Serial.print("T_Zone: "); Serial.println( tZ ); Blynk.config(Tmx); Serial.print("T max: "); Serial.println( Tmx ); Blynk.config(Cmx); Serial.print("CO2 max: "); Serial.println( Cmx ); Blynk.config(Tmn); Serial.print("T min: "); Serial.println( Tmn ); Blynk.config(Hmn); Serial.print("H min: "); Serial.println( Hmn ); Blynk.config(timeSW); Serial.print("Time Summer/Winter: "); Serial.println( timeSW ); Blynk.config(formFS); Serial.print("format FS: "); Serial.println( formFS ); Blynk.config(blynk_token, "blynk-cloud.com", 8442); Serial.print("token: " ); Serial.println( blynk_token ); Tmax = atof (Tmx); Cmax = atof (Cmx); Tmin = atof (Tmn); Hmin = atof (Hmn); tZone = atof (tZ); timeSummerWinter = atof (timeSW); formatingFS = atof (formFS); drawSynchron(); synchronClock(); connectBlynk(); FaceClock(); if (formatingFS == 1) { formatFS(); } } else if(md == 2) { Tmax = atof (Tmx); Cmax = atof (Cmx); Tmin = atof (Tmn); Hmin = atof (Hmn); tZone = atof (tZ); timeSummerWinter = atof (timeSW); formatingFS = atof (formFS); synchronClockA(); FaceClock(); if (formatingFS == 1) { formatFS(); } } } void loop() { if (md == 2) Serial.println(":( OffLINE"); else if (md == 1) Serial.println(":) OnLINE"); sendMeasurements(); draw(); Clock(); drawClockHands(hh,mm,ss); if (ml >= 480000) ml = 0; //  if ((ml >= 20000) and ((t > Tmax) or (co2 > Cmax) or (t < Tmin) or (h < Hmin))) { mailer(); ml = 0; } Blynk.run(); if (bl > 210){ // 30 sec sendToBlynk(); Serial.println("   Blynk"); bl = 0; } bl++; ml++; delay(100); t_old = t; hic_old = hic; h_old = h; co2_old = co2; Serial.println(" "); } 

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



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


قم بتشغيل الشاشة.



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



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



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


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


1. درجة حرارة مريحة ليلا أثناء النوم 19 ... 21 درجة مئوية ، خلال النهار - 22 ... 23 درجة مئوية


2. تعتبر الرطوبة النسبية المثلى في موسم البرد الرطوبة من 30 ... 45 ٪ ، وفي الحارة - 30 ... 60 ٪. الحد الأقصى لمؤشرات الرطوبة: في فصل الشتاء يجب ألا يتجاوز 60 ٪ ، وفي الصيف - 65 ٪.


3. يجب ألا يتجاوز الحد الأقصى لمستوى ثاني أكسيد الكربون في المبنى 1000 جزء في المليون. المستوى الموصى به لغرف النوم ، غرف الأطفال - لا يزيد عن 600 جزء في المليون. علامة 1400 جزء في المليون لفترة طويلة هي الحد الأقصى المسموح به لمحتوى ثاني أكسيد الكربون في الغرفة. إذا كان هناك أكثر من ذلك ، فإن جودة الهواء تعتبر منخفضة.


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


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


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


النظر في الصورة التي تظهر على الشاشة.



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


يشير الحرف "B" على خلفية حمراء إلى أن الشاشة تعمل دون الاتصال بـ Blynk ، وإذا ظهر الحرف "A" ، فقد اختفت الطاقة ولم تكن هناك شبكة Wi-Fi في وقت ظهورها (توقف الجهاز عن العمل).


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


في الزاوية اليمنى السفلى من الشاشة ، نرى مؤشر الحرارة (مؤشر الحرارة ، humindex).


Humidex هو كمية غير محددة على أساس نقطة الندى. يستخدم هذا المؤشر على نطاق واسع في تقارير الطقس الكندية في فصل الصيف.
وفقا لخدمة الأرصاد الجوية الكندية ، فإن قيم Humidex التي تزيد عن 30 تتسبب في بعض الانزعاج ، وتسبب أعلى من 40 درجة إزعاجًا كبيرًا ، والقيمة فوق 45 خطرة. إذا وصل humidex إلى 54 ، فإن ضربة الشمس أمر لا مفر منه. الريح لا تأخذ هذا المؤشر في الاعتبار ". (ويكيبيديا ، humidex ).


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


وفقًا لجدول المقالة نفسها على Wikipedia ، فإن درجة الحرارة المقاسة البالغة 25 درجة مئوية عند رطوبة 30٪ يتم الشعور بها عند 24 درجة مئوية ، عند 50٪ - 28 درجة مئوية ، وفي 90٪ - 35 درجة مئوية (10 درجة مئوية أعلى من مقياس الحرارة). في الداخل ، يمكن تقليل هذا المؤشر من خلال ترتيب مسودة أو تشغيل المروحة. سيتم تشغيل مكيف الهواء تلقائيًا إذا ضبطت درجة حرارة تكييف الهواء على 25 درجة مئوية. مؤشر الحرارة ، في رأيي ، هو أكثر ملاءمة لجودة الهواء من الضغط ، على سبيل المثال ، لا يمكننا التأثير بأي شكل من الأشكال.


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


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


حان الوقت لإطلاق تطبيق Blynk على هاتفك الذكي.


قم بإعداد التطبيق الخاص بك. متغيرات Blynk (عدم البحث عنها في الرسم): درجة الحرارة - V1 ، الرطوبة - محتوى V2 ، ثاني أكسيد الكربون - V3 ، مؤشر الحرارة - V5 .


على هاتفي الذكي ، تبدو واجهة Blynk'a مثل:



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


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


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




النتائج


لن أكرر ما تم إنجازه ، لكنني أسهب في الحديث عن نقاط غامضة.


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


ما تحتاجه للعمل على:


• قم بتنظيم مصدر طاقة مستقل للشاشة من بطاريتي AA لفترة طويلة - على الأقل في السنة.


• المظهر لا يستحق المناقشة - هنا لكل شخص رأيه وإمكانياته الخاصة. على سبيل المثال ، أحب خيار avs24rus ، فقد استخدم شاشة مقاس 7 بوصات في إطار مع طباعة ثلاثية الأبعاد كشاشة . يمكنك استبدال إطار باهظ الثمن بإطار رخيص من الرغيف الفرنسي. إطار الصورة الأصلي بالإضافة إلى ذلك.


إذا لم تكن مطالبًا بشكل كبير من الناحية الجمالية للمشكلة ، فربما يكون هناك شيء مشابه يناسبك:



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


حظا سعيدا


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


1. ترمومتر Wi-Fi على ESP8266 + DS18B20 مقابل 4 دولارات فقط


2. جهاز مراقبة الهواء المنزلي المضغوط (CO2 ، درجة الحرارة ، الرطوبة ، الضغط) مع شبكة Wi-Fi وواجهة متنقلة


3. تجربة عملية باستخدام Blynk لجهاز استشعار CO2. الجزء 1


4. استخدام ذاكرة عرض SPI Flash لتخزين الموارد الرسومية أو عرض محطة الطقس الرئيسية


5. نقيس تركيز ثاني أكسيد الكربون في الشقة باستخدام MH-Z19


6. الجانب المظلم من MH-Z19


7. نقيس تركيز ثاني أكسيد الكربون في الشقة باستخدام MH-Z19


أخيرًا ، أشكر kumekay على النصيحة القيمة.
ساعدني المنشور "التجربة العملية لاستخدام Blynk لمستشعر CO2 على حل مشكلة إعادة إدخال المتغيرات إلى ذاكرة ESP8622 " الجزء 1 " ، @ a3x . المادة تنوعا عميق!

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


All Articles