Dois em um: relógio e monitor de qualidade do ar programável Wi-Fi

Ao mesmo tempo, gostei do monitor de qualidade do ar da publicação de Sergei Silnov, "Monitor doméstico compacto de ar (CO2, temperatura, umidade, pressão) com Wi-Fi e uma interface móvel" .


No monitor de qualidade do ar (doravante denominado monitor) do projeto de Sergey, as informações dos sensores de temperatura, umidade, pressão e conteúdo de 2 no ar são processadas pelo controlador ESP8266 e exibidas em uma tela monocromática em vários quadros. Além disso, no monitor, através do formulário no navegador, a chave de identificação do serviço Blynk é armazenada na memória do ESP8266 e os dados são enviados automaticamente para o Blynk.


O monitor tinha um problema sério: travava ao ligar / desligar ou até "piscar" a tensão de alimentação do monitor.


Repeti o projeto com pequenas alterações e, para eliminar os congelamentos do monitor, adicionei energia alternativa ao circuito. Simples como um ancinho: o enrolamento do relé estava sob a tensão do adaptador CA / CC, e os contatos do relé trocaram a energia do adaptador para as baterias quando a tensão na rede de 220V desapareceu.


Meu sucesso imaginário durou até a primeira longa interrupção na casa (isso acontece conosco). Baterias baratas estavam gastas antes que a tensão aparecesse nas tomadas e voltei ao ponto de partida.


Depois de pisar em seu próprio rake, decidi não procurar soluções simples.


Alterações / adições feitas ao esboço por S. Silnov:


  • O monitor fica off-line automaticamente 90 segundos depois de desligar e ligar novamente a tensão de alimentação do monitor, se o Wi-Fi não estiver disponível no momento.
  • Os valores da temperatura limite, o conteúdo de CO2, a umidade do ar, o fuso horário e o inverno / verão são inseridos na memória do ESP8266, como a tecla Blynk, no formulário do navegador.
  • A mudança nos valores dos limites é radicalmente simplificada. Se anteriormente esse procedimento foi realizado por meio do compilador de código, agora é suficiente alterar o registro em um dos campos do formulário de "0" para "1". Este trabalho tornou-se viável, nem mesmo usuário avançado.
  • As informações sobre a operação do monitor são exibidas na tela colorida 1,44 ”, 128x128 em um quadro. A saída dos parâmetros do ar além dos valores limite é exibida no quadro em cores.
  • No monitor, o índice de calor (humindex) é calculado e exibido na tela.
  • As notificações são enviadas do monitor por e-mail se a temperatura, o conteúdo de CO2 ou a umidade do ar estiverem fora dos limites estabelecidos pelo usuário.
  • O monitor pode funcionar sem conectar-se ao serviço e endereço de e-mail Blynk.
  • Um relógio analógico em tempo real foi adicionado ao monitor, sincronizado via Internet com o servidor de tempo exato.


Esta é uma das tarefas não resolvidas do meu projeto “Termostato programável de Wi-Fi sem fio com monitor de qualidade do ar e outras funções úteis” . Decidi formalizar essa tarefa como um artigo separado, porque hoje em dia muitos estão interessados ​​na qualidade do ar na habitação.


Assembléia


Para montar o dispositivo, você precisará de componentes, cuja lista e seu custo estimado aos preços do site do AliExpress são mostrados na tabela.


ComponentePreço ($)
Placa Wi-Fi NodeMCU CP2102 ESP82662,53
Sensor de temperatura e umidade DHT222,34
Sensor de conteúdo de CO2 MH Z-1918,50
Tela TFTLCD 1,44 "SPI 128x1282,69
Relógio RTC DS32311,00
Fios de montagem, outras ninharias2,00
Total (aproximadamente):30

O cérebro do monitor é o controlador ESP8266 na placa do módulo NodeMCU CP2102. Ele recebe sinais de sensores, observa e gera um sinal de controle de tela, sincroniza o relógio e também envia informações para Blynk e e-mail.



Infelizmente, não encontrei a biblioteca Fritzing para a tela colorida de 1,44 ”e 128x128 com uma pinagem de 8 pinos; portanto, a tela possui uma tela de 11 pinos. Ao montar, preste atenção não ao local da saída da tela em relação a outros, mas à sua carga funcional.


Para quem não gosta de montar um protótipo para o diagrama de fiação, a tabela de conexão:


NodeMCU (GPIO)Pino dos sensores
D0 (GPIO 16)displ_1.44, CS
D1 (GPIO 5)DS3231, SCL
D2 (GPIO 4)DS3231, SDA
D3 (GPIO 0)DHT22, DADOS;
D4 (GPIO 2)displ_1.44, A0
D5 (GPIO 14)displ_1.44, SCK
D6 (GPIO 12)displ_1.44, RST
D7 (GPIO 13)displ_1.44, SDA
D8 (GPIO 15)GND
TxMH-Z19, Rx
RxMH-Z19, Tx
Vin (5V)displ_1.44, Vcc; DHT22; Mh-z19
3.3Vdispl_1.44, LED; DS3231, Vcc
GNDSensores, GND

Se você usar uma bateria em vez de uma bateria no módulo do relógio, não se esqueça de interromper o circuito de carga da bateria, caso contrário, a bateria inchará após algumas semanas de trabalho sob tensão. Com precisão de relógio de 2 segundos / ano, você está garantido.


A voltagem do monitor 5V pode ser fornecida a partir da porta USB do computador com um cabo USB padrão - microUSB para o módulo NodeMCU esp12 .


O esboço do monitor para carregamento no ESP8266 está sob o spoiler.


Por precaução, não tenha preguiça de ajustar o driver I2C embutido no núcleo do Arduino ESP8266. As instruções estão aqui .


esboço do monitor
/* *   :   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(" "); } 

Se pelo menos um dos parâmetros de ar estiver fora dos limites definidos, o dispositivo enviará uma mensagem de email sobre o seguinte conteúdo uma vez por hora:



Mensagens para e-mail são enviadas por script php. O script é carregado no meu servidor de email. Será necessário se você planeja enviar mensagens de outro recurso.


script php
 <?php //  - http://skorovoda.in.ua/php/aqm42.php?mymail=my_login@my.site.net&t=22.2&h=55&co2=666 $EMAIL=0; $TEMPER=0; $vlaga=0; $carbon=0; $device=0; $EMAIL=$_GET["mymail"]; $device=$_GET["ID"]; echo $EMAIL; $TEMPER=$_GET["t"]; $vlaga=$_GET["h"]; $carbon=$_GET["co2"]; $mdate = date("H:i dmy"); echo <<<END <p>: $TEMPER °<p> <p>: $vlaga %<p> <p>  : $carbon ppm<p> <p>--------------------<p> <p> №: $device<p> END; echo <<<END <p>$mdate</p> END; mail($EMAIL, "Air Quality Monitor " .$device. " v.051018","       №" .$device. " .        (,     )      . === : ".$TEMPER."°C === ".": ".$vlaga."% === "."  : ".$carbon." ppm === "." ! === , : ".$mdate,"From: my_sensors@air-monitor.info \n") ?> 


Ligue o monitor.



O dispositivo elevou o ponto de acesso am180206. Encontramos esse ponto na lista de redes disponíveis e conectamos a ele, a senha está na tela. Tente conectar-se a esse ponto em um minuto e meio, caso contrário, o monitor entrará automaticamente no modo de bateria. Sobre o modo offline - um pouco mais tarde. Em seguida, abra a página do navegador http://192.168.4.1.



Clique no botão Configurar WiFi (sem digitalização) . Uma página será aberta com o formulário de configurações do termostato:



Indique no formulário o nome e a senha da sua rede doméstica, chave de identificação BLynk, seu e-mail, fuso horário, horário de verão / inverno, bem como os valores limite de temperatura, umidade e conteúdo de CO2.


Ao escolher valores-limite para parâmetros de ar, seja guiado por indicadores que encontrei na Internet:


1. Temperatura confortável durante a noite durante o sono 19 ... 21 ° C, durante o dia - 22 ... 23 ° C.


2. A umidade relativa ideal na estação fria é considerada umidade de 30 ... 45% e no quente - 30 ... 60%. Indicadores de umidade máxima máxima: no inverno, não deve exceder 60% e no verão - 65%.


3. O nível máximo de dióxido de carbono nas instalações não deve exceder 1000 ppm. O nível recomendado para quartos, quartos infantis - não mais que 600 ppm. A marca de 1400 ppm por um longo tempo é o limite do teor de CO2 permitido na sala. Se houver mais, a qualidade do ar é considerada baixa.


O campo e-mai pode ser deixado em branco. Então, a oportunidade de receber cartas por e-mail sobre a saída dos parâmetros do ar além dos valores limite será perdida. Sem a tecla Blynk inserida, você perderá a capacidade de receber informações sobre os parâmetros do ar à distância. No entanto, o monitor não será “perdido” se os campos com os valores limite dos parâmetros do ar permanecerem vazios.


Preencha sem hesitar todos os campos do formulário com números no formato de ponto flutuante, por exemplo, o fuso horário é 2.0 . O esboço fornece a conversão subsequente de números para o formato desejado.


Depois de salvar as configurações na memória do ESP8266 (botão Salvar ), o monitor se conectará à rede e começará a trabalhar.


Considere a imagem na tela.



Data, hora, temperatura medida, conteúdo de CO2 e umidade do ar são auto-explicativos. Esclarecerei se os parâmetros do ar ultrapassam os limites que você definiu, e a exibição deles na tela mudará de cor de verde para vermelho.


A letra "B" em um fundo vermelho indica que o monitor está funcionando sem conectar-se ao Blynk e, se a letra "A" aparecer, a energia desapareceu e não havia Wi-Fi no momento em que apareceu (o dispositivo ficou offline).


Em geral, a aparência da cor vermelha na tela deve alertar - há desvios do funcionamento normal do dispositivo.


No canto inferior direito da tela, vemos o índice de calor (índice de calor, humindex).


“Humidex é uma quantidade adimensional baseada no ponto de orvalho. Este índice é amplamente utilizado nos relatórios meteorológicos do Canadá no verão.
De acordo com o Serviço Meteorológico Canadense, os valores de Humidex acima de 30 causam algum desconforto, acima de 40 causam grande desconforto e um valor acima de 45 é perigoso. Se o humidex atingir 54, a insolação é inevitável. O vento não leva em conta esse índice. ” (Wikipedia, humidex ).


Deve-se esclarecer que o índice de calor é calculado apenas para temperaturas relativamente altas, quando a temperatura medida do ar é superior a 21 ° C. Figurativamente, o índice de calor é a temperatura percebida em um dia quente de verão sem vento.


De acordo com a tabela do mesmo artigo na Wikipedia, a temperatura medida de 25 ° C a uma umidade de 30% é sentida como 24 ° C, a 50% - 28 ° C e a 90% - 35 ° C (10 ° C acima do termômetro). Dentro de casa, esse indicador pode ser reduzido organizando um rascunho ou ligando o ventilador. O ar condicionado será ligado automaticamente se você definir a temperatura do ar-condicionado para não mais que 25 ° C. O índice de calor, na minha opinião, é um parâmetro mais relevante da qualidade do ar do que, digamos, pressão, que não podemos influenciar de forma alguma.


O sensor DHT22, como o DHT11, junto com o “menos” - baixa precisão na medição de umidade tem um inegável “mais”: o barramento de dados desse sensor possui informações sobre o índice de calor. Aproveitei esse “plus” e exibi o índice de calor na tela do dispositivo.


Existem muitas reclamações na Internet sobre a baixa precisão do DHT22. Para aqueles que estão determinados a se recusar a usar esse sensor em seus projetos, peço que olhe para os mais modernos sensores de temperatura e umidade HTU21D, Si7021 ou SHT21.


É hora de iniciar o aplicativo Blynk no seu smartphone.


Configure seu aplicativo. Variáveis ​​para Blynk (para não procurá-las no esboço): temperatura - V1 , umidade - V2 , conteúdo de CO2 - V3 , índice de calor - V5 .


No meu smartphone, a interface Blynk'a se parece com:



O gráfico mostra a temperatura medida (amarelo), umidade (azul), índice de calor (roxo). O primeiro pico no gráfico é o aquecimento do sensor de pressão e umidade na palma da mão: a curva do índice de calor está localizada acima da curva de temperatura. O segundo pico é o aquecimento do sensor com um secador de cabelo. A umidade do ar diminui quando o sensor aquece com um secador de cabelo e a curva do índice de calor em algumas áreas se repete ou abaixo da linha de temperatura. (veja texto explicativo).


Não se preocupe se a linha de temperatura desaparecer no gráfico: a temperaturas abaixo de 21 ° C, a curva do índice de calor repete a curva de temperatura medida.


Agora estamos testando a operação do sistema de notificação por email. Digite o endereço http do código php-script na barra de endereços do navegador (no script, a linha com o endereço http é comentada). Se você não se esqueceu de especificar seu e-mail nas configurações e na janela do navegador - informações, como na figura abaixo, provavelmente não haverá problemas em receber notificações. O teste é especialmente útil ao transferir um script php do meu servidor para outro.




Conclusões


Não vou repetir o que foi feito, mas me debruço sobre pontos ambíguos.


Lógico, por exemplo, é a pergunta "Por que o relógio está aqui?" É necessário um relógio de flecha para preencher a tela. Embora atualmente os relógios digitais estejam embutidos em quase todos os eletrodomésticos, os analógicos têm uma vantagem: a escala de tempo, graças ao mostrador, facilita a estimativa do tempo para um evento. Eu não vou discutir - você pode fazer sem horas.


No que você precisa trabalhar:


• Organize uma fonte de alimentação autônoma para o monitor usando duas pilhas AA por um longo período de tempo - pelo menos um ano.


• Não vale a pena discutir a aparência - aqui todos têm sua própria opinião e possibilidades. Por exemplo, eu gosto da opção avs24rus , que usou uma tela de 7 "em um quadro com impressão 3D como tela . Você pode substituir um quadro caro por um barato de uma baguete. E no modo de espera, exibe uma paisagem, um retrato de sua amada ou uma foto de crianças - você também terá moldura original em adição.


Se você não é muito exigente do lado estético da questão, talvez algo semelhante lhe sirva:



Nota Inicialmente, o monitor foi concebido como um dispositivo universal pronto para uso em um termostato . Sua versatilidade levou a uma complicação injustificada do esquema. Portanto, mais tarde, recusei a possibilidade de usar a solução do monitor em um termostato e editei a publicação. Traços da versão mais complexa anterior do dispositivo permaneceram nos comentários.


Boa sorte


Meus favoritos em um assunto de Habr


1. Termômetro Wi-Fi no ESP8266 + DS18B20 por apenas US $ 4


2. Monitor de ar doméstico compacto (CO2, temperatura, umidade, pressão) com Wi-Fi e uma interface móvel


3. Experiência prática usando Blynk para um sensor de CO2. Parte 1


4. Usando a memória SPI Flash para armazenar recursos gráficos ou exibir uma estação meteorológica doméstica


5. Medimos a concentração de CO2 no apartamento usando o MH-Z19


6. O lado escuro do MH-Z19


7. Medimos a concentração de CO2 no apartamento usando o MH-Z19


Por fim, agradeça a @kumekay pelos conselhos valiosos.
A publicação “Experiência prática do uso do Blynk para um sensor de CO2 me ajudou a resolver o problema de reinserir variáveis ​​na memória do ESP8622 Parte 1 " , @ a3x . Artigo versátil profundo!

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


All Articles