Zu einer Zeit mochte ich den Luftqualitätsmonitor aus der Veröffentlichung von Sergey Silnov, „Kompakter Haushaltsluftmonitor (CO2, Temperatur, Luftfeuchtigkeit, Druck) mit WLAN und mobiler Schnittstelle“ .
In dem Luftqualitätsmonitor (im Folgenden als Monitor bezeichnet) aus dem Projekt von Sergey werden Informationen von Sensoren für Temperatur, Luftfeuchtigkeit, Druck und CO2-Gehalt in der Luft vom ESP8266-Controller verarbeitet und in mehreren Bildern auf einem monochromen Bildschirm angezeigt. Darüber hinaus wird auf dem Monitor über das Formular im Browser der Blynk-Dienstidentifikationsschlüssel im ESP8266-Speicher gespeichert und die Daten werden automatisch an Blynk gesendet.
Der Monitor hatte ein ernstes Problem: Er stürzte beim Ein- und Ausschalten oder sogar beim „Blinken“ der Versorgungsspannung des Monitors ab.
Ich wiederholte das Projekt mit geringfügigen Änderungen, und um das Einfrieren des Monitors zu vermeiden, fügte ich der Schaltung alternative Stromversorgung hinzu. Einfach wie ein Rechen: Die Relaiswicklung befand sich unter der Spannung des AC / DC-Adapters, und die Relaiskontakte schalteten die Stromversorgung des Adapters auf die Batterien um, als die Spannung im 220-V-Netzwerk verschwand.
Mein imaginärer Erfolg dauerte bis zum ersten langen Ausfall im Haus (das passiert bei uns). Billige Batterien waren erschöpft, bevor die Spannung in den Steckdosen auftrat, und ich kehrte zum Ausgangspunkt zurück.
Nachdem ich auf seinen eigenen Rechen getreten war, beschloss ich, nicht nach einfachen Lösungen zu suchen.
Änderungen / Ergänzungen an der Skizze von S. Silnov:
- Der Monitor wird 90 Sekunden nach dem Aus- und Wiedereinschalten der Versorgungsspannung des Monitors automatisch offline geschaltet, wenn zu diesem Zeitpunkt kein WLAN verfügbar ist.
- Die Grenztemperaturwerte, der CO2-Gehalt, die Luftfeuchtigkeit sowie die Zeitzone und die Winter- / Sommerzeit werden wie der Blynk-Schlüssel aus dem Formular im Browser in den ESP8266-Speicher eingegeben.
- Die Änderung der Grenzwerte wird radikal vereinfacht. Wenn diese Prozedur früher über den Code-Compiler ausgeführt wurde, reicht es jetzt aus, den Datensatz in einem der Formularfelder von "0" auf "1" zu ändern. Diese Arbeit ist nicht einmal für fortgeschrittene Benutzer machbar geworden.
- Informationen zur Bedienung des Monitors werden auf dem Farbbildschirm 128 x 128 (1,44 Zoll) in einem Bild angezeigt. Die Ausgabe von Luftparametern über die Grenzwerte hinaus wird im Rahmen farbig angezeigt.
- Im Monitor wird der Wärmeindex (Humindex) berechnet und auf dem Bildschirm angezeigt.
- Benachrichtigungen werden vom Monitor per E-Mail gesendet, wenn die Temperatur, der CO2-Gehalt oder die Luftfeuchtigkeit außerhalb der vom Benutzer festgelegten Schwellenwerte liegen.
- Der Monitor kann funktionieren, ohne eine Verbindung zum Blynk-Dienst und zur E-Mail-Adresse herzustellen.
- Dem Monitor wurde eine analoge Echtzeituhr hinzugefügt, die über das Internet mit dem genauen Zeitserver synchronisiert wird.

Dies ist eine der ungelösten Aufgaben meines Projekts „Wireless Wi-Fi programmierbarer Raumthermostat mit Luftqualitätsmonitor und anderen nützlichen Funktionen“ . Ich habe beschlossen, diese Aufgabe als separaten Artikel zu formalisieren, da sich heutzutage viele für die Luftqualität im Wohnungsbau interessieren.
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.
Komponente | Preis ($) |
Wi-Fi-Karte NodeMCU CP2102 ESP8266 | 2,53 |
Temperatur- und Feuchtigkeitssensor DHT22 | 2,34 |
CO2-Gehaltssensor MH Z-19 | 18.50 |
Bildschirm TFTLCD 1.44 "SPI 128x128 | 2.69 |
RTC DS3231 Uhr | 1,00 |
Montagedrähte, andere Kleinigkeiten | 2.00 |
Insgesamt (ungefähr): | 30 |
Das Gehirn des Monitors ist der ESP8266-Controller auf der Platine des NodeMCU CP2102-Moduls. Es empfängt Signale von Sensoren, überwacht und erzeugt ein Bildschirmsteuersignal, synchronisiert die Uhr und sendet Informationen an Blynk und per E-Mail.

Leider habe ich die Fritzing-Bibliothek für den 128 x 128-Farbbildschirm mit 1,44 Zoll und einer 8-poligen Pinbelegung nicht gefunden, sodass der Bildschirm über einen 11-poligen Bildschirm verfügt. Achten Sie bei der Montage nicht auf die Position der Bildschirmausgabe im Verhältnis zu anderen, sondern auf deren Funktionsbelastung.
Für diejenigen, die keinen Prototyp für den Schaltplan zusammenbauen möchten, die Verbindungstabelle:
NodeMCU (GPIO) | Sensorstift |
D0 (GPIO 16) | disp_1.44, CS |
D1 (GPIO 5) | DS3231, SCL |
D2 (GPIO 4) | DS3231, SDA |
D3 (GPIO 0) | DHT22, DATA; |
D4 (GPIO 2) | disp_1.44, A0 |
D5 (GPIO 14) | disp_1.44, SCK |
D6 (GPIO 12) | disp_1.44, RST |
D7 (GPIO 13) | disp_1.44, SDA |
D8 (GPIO 15) | GND |
Tx | MH-Z19, Rx |
Rx | MH-Z19, Tx |
Vin (5 V) | disp_1.44, Vcc; DHT22; Mh-z19 |
3,3V | disp_1.44, LED; DS3231, Vcc |
GND | Sensoren, GND |
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 Spannung des 5-V- Monitors kann vom USB-Anschluss des Computers mit einem Standard- USB- Kabel (microUSB) an das NodeMCU esp12- Modul geliefert werden.
Die Monitorskizze zum Laden in den ESP8266 befindet sich unter dem Spoiler.
Seien Sie für alle Fälle nicht zu faul, um den integrierten I2C-Treiber für den Arduino ESP8266-Kern zu optimieren. Anweisungen finden Sie hier .
Monitorskizze #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(" "); }
Wenn mindestens einer der Luftparameter außerhalb der festgelegten Schwellenwerte liegt, sendet das Gerät ungefähr einmal pro Stunde eine E-Mail-Nachricht über den folgenden Inhalt:

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.
Schalten Sie den Monitor ein.
Das Gerät hat den Zugangspunkt am180206 angehoben. Wir finden diesen Punkt in der Liste der verfügbaren Netzwerke und stellen eine Verbindung her, das Passwort wird auf dem Bildschirm angezeigt. Versuchen Sie, in anderthalb Minuten eine Verbindung zu diesem Punkt herzustellen, da sonst der Monitor automatisch in den Batteriemodus wechselt. Über den Offline-Modus - etwas später. Ö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:
Geben Sie in Form des Namens und des Passworts Ihres Heimnetzwerks, des BLynk-Identifikationsschlüssels, Ihrer E-Mail-Adresse, der Zeitzone, der Sommer- / Winterzeit sowie der Schwellenwerte für Temperatur, Luftfeuchtigkeit und CO2-Gehalt an.
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äumlichkeiten sollte 1000 ppm nicht überschreiten. Das empfohlene Niveau für Schlafzimmer, Kinderzimmer - nicht mehr als 600 ppm. Die Marke von 1400 ppm für eine lange Zeit ist die Grenze des zulässigen CO2-Gehalts im Raum. Wenn mehr vorhanden ist, wird die Luftqualität als niedrig angesehen.
Das E-Mai- Feld kann leer gelassen werden. Dann geht die Möglichkeit verloren, Briefe per E-Mail über den Austritt von Luftparametern über die Grenzwerte hinaus zu erhalten. Ohne den eingegebenen Blynk-Schlüssel verlieren Sie die Fähigkeit, Informationen über die Luftparameter aus der Ferne zu erhalten. Der Monitor geht jedoch nicht verloren, wenn die Felder mit den Grenzwerten der Luftparameter leer bleiben.
Füllen Sie ohne zu zögern alle Felder des Formulars mit Zahlen im Gleitkommaformat aus. Die Zeitzone beträgt beispielsweise 2,0 . Die Skizze sieht die anschließende Konvertierung von Zahlen in das gewünschte Format vor.
Nach dem Speichern der Einstellungen im ESP8266-Speicher (Schaltfläche Speichern) stellt der Monitor eine Verbindung zum Netzwerk her und beginnt mit der Arbeit.
Betrachten Sie das Bild auf dem Bildschirm.
Datum, Uhrzeit, gemessene Temperatur, CO2-Gehalt und Luftfeuchtigkeit sind selbsterklärend. Ich werde klären, ob die Luftparameter die von Ihnen festgelegten Grenzwerte überschreiten. Die Anzeige auf dem Bildschirm ändert dann die Farbe von grün nach rot.
Der Buchstabe „B“ auf rotem Hintergrund zeigt an, dass der Monitor ohne Verbindung zu Blynk funktioniert. Wenn der Buchstabe „A“ angezeigt wird, ist die Stromversorgung unterbrochen und zum Zeitpunkt des Auftretens gab es kein WLAN (das Gerät wurde offline geschaltet).
Im Allgemeinen sollte das Erscheinen einer roten Farbe auf dem Bildschirm alarmieren - es gibt Abweichungen von der normalen Funktion des Geräts.
In der unteren rechten Ecke des Bildschirms sehen wir den Wärmeindex (Hitzeindex, Humindex).
„Humidex ist eine dimensionslose Größe, die auf dem Taupunkt basiert. Dieser Index wird in kanadischen Wetterberichten im Sommer häufig verwendet.
Nach Angaben des Canadian Meteorological Service verursachen Humidex-Werte über 30 Unbehagen, über 40 große Unannehmlichkeiten und ein Wert über 45 ist gefährlich. Wenn Humidex 54 erreicht, ist ein Hitzschlag unvermeidlich. Der Wind berücksichtigt diesen Index nicht. “ (Wikipedia, Humidex ).
Es sollte klargestellt werden, dass der Wärmeindex nur für relativ hohe Temperaturen berechnet wird, wenn die gemessene Lufttemperatur über 21 ° C liegt. Im übertragenen Sinne ist der Hitzeindex die wahrgenommene Temperatur an einem heißen windstillen Sommertag.
Laut der Tabelle des gleichen Artikels auf Wikipedia wird die gemessene Temperatur von 25 ° C bei einer Luftfeuchtigkeit von 30% als 24 ° C, bei 50% - 28 ° C und bei 90% - 35 ° C (10 ° C höher als das Thermometer) empfunden. In Innenräumen kann diese Anzeige durch Anordnen eines Luftzuges oder Einschalten des Lüfters verringert werden. Die Klimaanlage schaltet sich automatisch ein, wenn Sie die Temperatur der Klimaanlage auf nicht mehr als 25 ° C einstellen. Der Wärmeindex ist meiner Meinung nach ein relevanterer Parameter für die Luftqualität als beispielsweise der Druck, den wir in keiner Weise beeinflussen können.
Der DHT22-Sensor hat wie der DHT11 zusammen mit dem „Minus“ - geringe Genauigkeit der Feuchtemessung ein unbestreitbares „Plus“: Der Datenbus dieses Sensors enthält Informationen über den Wärmeindex. Ich habe dieses „Plus“ genutzt und den Wärmeindex auf dem Gerätebildschirm angezeigt.
Im Internet gibt es viele Beschwerden über die geringe Genauigkeit des DHT22. Für diejenigen, die entschlossen sind, die Verwendung dieses Sensors in ihren Projekten zu verweigern, bitte ich Sie, sich für modernere Temperatur- und Feuchtigkeitssensoren HTU21D, Si7021 oder SHT21 zu entscheiden.
Es ist Zeit, die Blynk-App auf Ihrem Smartphone zu starten.
Richten Sie Ihre Anwendung ein. Variablen für Blynk (nicht in der Skizze zu suchen): Temperatur - V1 , Luftfeuchtigkeit - V2 , CO2-Gehalt - V3 , Wärmeindex - V5 .
Auf meinem Smartphone sieht die Blynk'a-Oberfläche folgendermaßen aus:
Die Grafik zeigt die gemessene Temperatur (gelb), Luftfeuchtigkeit (blau) und Hitzeindex (lila). Der erste Peak in der Grafik ist die Erwärmung des Druck-Feuchtigkeits-Sensors in der Handfläche: Die Wärmeindexkurve befindet sich über der Temperaturkurve. Der zweite Peak ist die Erwärmung des Sensors mit einem Haartrockner. Die Luftfeuchtigkeit nimmt ab, wenn sich der Sensor mit einem Haartrockner erwärmt, und die Wärmeindexkurve wiederholt sich in einigen Bereichen oder unterhalb der Temperaturlinie. (siehe Beschriftung).
Machen Sie sich keine Sorgen, wenn die Temperaturlinie in der Grafik verschwunden ist: Bei Temperaturen unter 21 ° C wiederholt die Wärmeindexkurve die gemessene Temperaturkurve.
Jetzt testen wir den Betrieb des Benachrichtigungssystems per E-Mail. Geben Sie die http-Adresse aus dem PHP-Skript-Code in die Adressleiste des Browsers ein (im Skript ist die Zeile mit der http-Adresse auskommentiert). Wenn Sie nicht vergessen haben, Ihre E-Mail-Adresse in den Einstellungen und im Browserfenster anzugeben - Informationen wie in der Abbildung unten -, treten höchstwahrscheinlich keine Probleme beim Empfang von Benachrichtigungen auf. Der Test ist besonders nützlich, wenn Sie ein PHP-Skript von meinem Server auf einen anderen übertragen.

Schlussfolgerungen
Ich werde nicht wiederholen, was getan wurde, sondern auf mehrdeutige Punkte eingehen.
Logisch ist zum Beispiel die Frage "Warum ist die Uhr hier?" Eine Pfeilüberwachung wird benötigt, um den Bildschirm auszufüllen. Obwohl derzeit in fast jedem Haushaltsgerät Digitaluhren eingebaut sind, haben analoge einen Vorteil: Die Zeitskala erleichtert dank des Zifferblatts die Schätzung der Zeit bis zu einem Ereignis. Ich werde nicht streiten - Sie können ohne Stunden auskommen.
Woran Sie arbeiten müssen:
• Organisieren Sie über einen langen Zeitraum - mindestens ein Jahr - eine autonome Stromversorgung für den Monitor aus zwei AA-Batterien.
• Das Aussehen ist keine Diskussion wert - hier hat jeder seine eigene Meinung und Möglichkeiten. Ich mag zum Beispiel die Option avs24rus , bei der ein 7- Zoll- Display in einem Rahmen mit 3D-Druck als Bildschirm verwendet wurde . Sie können einen teuren Rahmen durch einen billigen Rahmen aus einem Baguette ersetzen. Im Standby-Modus können Sie eine Landschaft, ein Porträt Ihrer Geliebten oder ein Foto von Kindern anzeigen Original Fotorahmen zusätzlich.
Wenn Sie in Bezug auf die ästhetische Seite des Problems nicht sehr anspruchsvoll sind, passt vielleicht etwas Ähnliches zu Ihnen:
Hinweis Ursprünglich war der Monitor als universelles Gerät konzipiert, das in einem Thermostat eingesetzt werden kann . Seine Vielseitigkeit hat zu einer ungerechtfertigten Komplikation des Systems geführt. Daher habe ich später die Möglichkeit der Verwendung der Monitorlösung in einem Thermostat abgelehnt und die Publikation bearbeitet. Spuren der vorherigen komplexeren Version des Geräts blieben in den Kommentaren.
Viel Glück!
Meine Lesezeichen zu einem Thema von Habr
1. Wi-Fi-Thermometer auf ESP8266 + DS18B20 für nur 4 US-Dollar
2. Kompakter Heimluftmonitor (CO2, Temperatur, Luftfeuchtigkeit, Druck) mit WLAN und mobiler Schnittstelle
3. Praktische Erfahrung mit Blynk für einen CO2-Sensor. Teil 1
4. Verwenden des SPI Flash-Anzeigespeichers zum Speichern von Grafikressourcen oder zum Anzeigen einer Heimwetterstation
5. Wir messen die CO2-Konzentration in der Wohnung mit dem MH-Z19
6. Die dunkle Seite des MH-Z19
7. Wir messen die CO2-Konzentration in der Wohnung mit dem MH-Z19
Zum Schluss danke @kumekay für den wertvollen Rat.
Die Veröffentlichung „Praktische Erfahrungen mit der Verwendung von Blynk für einen CO2-Sensor haben mir geholfen, das Problem der erneuten Eingabe von Variablen in den ESP8622-Speicher zu lösen. “ Teil 1 " , @ a3x . Tief vielseitiger Artikel!