O sistema de aquecimento autônomo do meu apartamento tem um termostato sem fio disponível no mercado. O sistema, é claro, funciona sem ele: o termostato foi adquirido para economizar consumo de gás e aumentar o conforto.
A coisa é muito útil, mas, na minha opinião, um pouco desatualizada. Foi decidido montar algo semelhante a um termostato adquirido, adicionando uma configuração e conexão à Internet mais convenientes no layout do termostato.
O que aconteceu como resultado - continue lendo. Espero que, além de mim, o projeto seja interessante para os outros.
Conhecimento
Características e especificações:
- A conexão entre os nós do termostato é realizada pelo ar em uma frequência de rádio.
- Durante o dia, o termostato mantém constantes três pontos de ajuste de temperatura.
- As configurações do termostato (programa de trabalho, parâmetros de ar de contorno, outros) são definidas remotamente via Wi-Fi a partir do formulário no navegador.
- O termostato inclui a função de um monitor de qualidade do ar com medição de temperatura, nível de dióxido de carbono e umidade do ar.
- O termostato está equipado com um relógio em tempo real com sincronização do relógio com o servidor de horas exato via Internet.
- O termostato é controlado a partir da interface do aplicativo móvel Blynk. Além disso, o aplicativo Blynk aceita e exibe os resultados das medições de temperatura, CO2 e umidade do ar.
- O termostato fica offline automaticamente na ausência de Wi-Fi.
- As mensagens são enviadas do termostato para o e-mail se a temperatura, o conteúdo de CO 2 ou a umidade do ar estiverem fora dos valores limite.
- No termostato, além da temperatura, é possível manter o restante dos parâmetros de ar medidos dentro dos limites especificados.
- No final da estação de aquecimento, o termostato não precisará ser oculto: o monitor de qualidade do ar permanecerá em operação com o envio de mensagens para o correio e o relógio.
O termostato consiste em dois dispositivos. No primeiro dispositivo, um sinal de controle para o dispositivo de aquecimento ou sistema de aquecimento é gerado e transmitido ao segundo dispositivo, vamos chamar esse dispositivo de analisador. O segundo dispositivo, recebe o sinal, descriptografa e controla a fonte de calor - seja um contator. A conexão entre o analisador e o contator é sem fio, na radiofrequência.
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.
Componente | Preço, $ |
analisador |
Placa Wi-Fi NodeMCU CP2102 ESP8266 | 2,53 |
Sensor de temperatura e umidade DHT22 | 2,34 |
Sensor de conteúdo de CO2 MH Z-19 | 18,50 |
Relógio RTC DS3231 | 1,00 |
Tela OLED LCD azul 0,96 "I2C 128x64 | 1,95 |
Módulo RF 433MHz, transmissor (preço do kit: transmissor, receptor) | 0,99 |
Conversor de nível lógico de 4 canais 3.3V-5V (Logical Layer Converter) | 0,28 |
Estabilizador de tensão LM7805 (10 unid.) | 0,79 |
Adaptador AC100-240V 50 / 60Hz DC12V 2A | 10,70 |
Placa de desenvolvimento (fibra de vidro), contatos, etc. | 2,00 |
contator |
Módulo Arduino Pro Mini 5V | 1,45 |
Módulo RF 433MHz (receptor) | - |
Módulo de relé de 2 canais | 0,98 |
Adaptador AC-DC HLK-PM01 | 4,29 |
Placa de desenvolvimento (fibra de vidro), contatos, etc. | 2,00 |
Total (aproximadamente): | 50. |
Se você planeja montar um termostato com dimensões mínimas, substitua o conversor de nível lógico de 4 canais por um módulo de relé de 2 e 2 canais por um de 1 canal.
Ambos os dispositivos são montados em placas de prototipagem de fibra de vidro. Montagem - montada. Os módulos são instalados nos painéis, montados a partir do "pente" de contatos. Essa abordagem tem várias vantagens: os componentes são facilmente desmontados, a instalação de uma nova versão do esboço é facilmente alterada e, finalmente, no corpo caseiro não é visível como é feito.
As antenas do transmissor e receptor são fios de 17,3 cm de comprimento, o aumento da potência do transmissor e as antenas mais simples fornecem comunicação confiável dentro do apartamento.
Analisador

O cérebro do analisador é o controlador ESP8266 na placa do módulo NodeMCU CP2102. Ele recebe sinais dos sensores e gera sinais de controle para o transmissor e a tela.

Ao instalar o sensor DHT22 na placa, a temperatura medida é 1,5 ... 2 ° C maior que a real (mesmo sem a caixa!). Portanto, você deve colocar o sensor de temperatura longe de elementos com alta dissipação de calor LM7805 e NodeMCU CP2102. Além disso, seria bom instalar o regulador de tensão LM7805 no radiador e é definitivamente necessário garantir uma boa convecção de ar no caso para diminuir a temperatura e reduzir o erro de medição. Outra opção para se livrar do erro é mover o sensor DHT22 além do volume do corpo - essa opção é mais simples e eu a escolhi.
Existem muitas reclamações na Internet sobre a baixa precisão do DHT22. Hoje existe uma alternativa: sensores mais modernos de temperatura e umidade HTU21D, Si7021, SHT21.
Uma tensão de 12V CC do adaptador CA / CC é fornecida ao analisador. Além disso, o estabilizador de tensão constante LM7805 gera uma tensão de 5V. A tensão da fonte de alimentação do transmissor é 12V. Ao testar o dispositivo, quando o analisador e o contator estão próximos à área de trabalho, o analisador pode ser alimentado pela porta USB do computador fornecendo tensão ao módulo NodeMCU CP2102 com um cabo USB padrão - microUSB. A tensão de alimentação do NodeMCU CP2102 e MH Z-19 é de 5V, a fonte de alimentação para os nós restantes do circuito (3,3V) forma o estabilizador do módulo NodeMCU CP2102.
O sensor de temperatura e umidade DHT22 está conectado ao terminal D6 do módulo NodeMCU CP2102. O relógio DC3231 e a tela de 0,96 "são conectados ao ESP8266 (no módulo NodeMCU CP2102) via interface I2C de dois fios, e os pinos Tx, Rx do sensor de conteúdo de CO2 MH Z-19 são conectados aos pinos Rx, Tx ESP8266, respectivamente. O sinal é transmitido ao transmissor pelo NodeMCU CP210. níveis lógicos, que convertem o sinal do NodeMCU CP2102 com uma amplitude de cerca de 3,3V em um sinal cuja amplitude está próxima da tensão de alimentação do transmissor de 12V.
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.
O esboço do analisador para carregamento no ESP8266 está sob o spoiler.
esboço do analisador #include <FS.h> #include <Arduino.h> #include <ESP8266WiFi.h> //https://github.com/esp8266/Arduino // Wifi Manager #include <DNSServer.h> #include <ESP8266WebServer.h> #include <WiFiManager.h> //https://github.com/tzapu/WiFiManager //e-mail #include <ESP8266WiFiMulti.h> //https://github.com/esp8266/Arduino/blob/master/libraries/ESP8266WiFi/src/ESP8266WiFiMulti.h #include <ESP8266HTTPClient.h> ESP8266WiFiMulti WiFiMulti; char address[64] {"e-mail"}; //e-mail, address // HTTP requests #include <ESP8266HTTPClient.h> // OTA updates #include <ESP8266httpUpdate.h> // Blynk #include <BlynkSimpleEsp8266.h> // Debounce #include <Bounce2.h> //https://github.com/thomasfredericks/Bounce2 // JSON #include <ArduinoJson.h> //https://github.com/bblanchon/ArduinoJson //clock #include <pgmspace.h> #include <TimeLib.h> #include <WiFiUdp.h> #include <Wire.h> #include <RtcDS3231.h> //https://github.com/Makuna/Rtc RtcDS3231<TwoWire> Rtc(Wire); #define countof(a) (sizeof(a) / sizeof(a[0])) //timer #include <SimpleTimer.h> SimpleTimer timer; // unsigned int timerCO2; // MH-Z19 unsigned int timerBl; // Blynk unsigned int timerMail; // // GPIO Defines #define I2C_SDA 4 // D2 - OLED #define I2C_SCL 5 // D1 - OLED #define DHTPIN 12 //D6 cp2102 // Humidity/Temperature #include <DHT.h> #define DHTTYPE DHT22 // DHT 22 DHT dht(DHTPIN, DHTTYPE); #define mySerial Serial // Use U8g2 for i2c OLED Lib #include <SPI.h> #include <U8g2lib.h> U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, I2C_SCL, I2C_SDA, U8X8_PIN_NONE); byte x {0}; byte y {0}; // Blynk token char blynk_token[33] {"Blynk token"}; //Transmitter #include <RCSwitch.h> RCSwitch transmitter = RCSwitch(); unsigned long TimeTransmitMax; // / // Setup Wifi connection WiFiManager wifiManager; // Network credentials String ssid {"am-5108"}; String pass {"vb" + String(ESP.getFlashChipId())}; //flag for saving data bool shouldSaveConfig = false; // float t {-100}; // int h {-1}; // int co2 {-1}; // co2 float Chs = 0.2; // () (: 0.1( ) - 0.4 ( )) char Tmx[]{"25.0"}, Hmn[]{"35"}, Cmx[]{"1000"}, tZ[]{"2.0"}; // t, h co2, . float Cmax, Tmax, Hmin, tZone; char Temperature0[]{"20.0"}, Temperature1[]{"22.0"}, Temperature2[]{"19.0"};// float TemperaturePoint0, TemperaturePoint1, TemperaturePoint2, TemperaturePoint1Mn, TemperaturePoint2Mn, TemperaturePoint1Pl, TemperaturePoint2Pl; float TemperaturePointA0 = 21.0; // char Hour1[]{"6"}, Hour2[]{"22"}; // , float HourPoint1, HourPoint2; float MinPoint1 = 0, MinPoint2 = 0; int n, j, m; // , int progr = 0; // int timeSummerWinter = 0; // (1)/(0) int a = 1; // : 1 - , 2 - bool buttonBlynk = true; // (true)/(falce) V(10) Blynk //NTP, clock uint8_t hh,mm,ss; //containers for current time char time_r[9]; char date_r[12]; // NTP Servers: //static const char ntpServerName[] = "us.pool.ntp.org"; static const char ntpServerName[] = "time.nist.gov"; WiFiUDP Udp; unsigned int localPort = 2390; // local port to listen for UDP packets time_t getNtpTime(); void digitalClockDisplay(); void printDigits(int digits); void sendNTPpacket(IPAddress &address); void digitalClockDisplay() { // digital clock display of the time Serial.print(hour()); printDigits(minute()); printDigits(second()); Serial.print(" "); Serial.print(day()); Serial.print("."); Serial.print(month()); Serial.print("."); Serial.print(year()); Serial.println(); } void printDigits(int digits) { // utility for digital clock display: prints preceding colon and leading 0 Serial.print(":"); if (digits < 10) Serial.print('0'); Serial.print(digits); } //NTP code const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets time_t getNtpTime() { int tZoneI; tZoneI = (int)tZone; IPAddress ntpServerIP; // NTP server's ip address while (Udp.parsePacket() > 0) ; // discard any previously received packets Serial.println("Transmit NTP Request"); // get a random server from the pool WiFi.hostByName(ntpServerName, ntpServerIP); Serial.print(ntpServerName); Serial.print(": "); Serial.println(ntpServerIP); sendNTPpacket(ntpServerIP); uint32_t beginWait = millis(); while (millis() - beginWait < 1500) { int size = Udp.parsePacket(); if (size >= NTP_PACKET_SIZE) { Serial.println("Receive NTP Response"); Udp.read(packetBuffer, NTP_PACKET_SIZE); // read packet into the buffer unsigned long secsSince1900; // convert four bytes starting at location 40 to a long integer secsSince1900 = (unsigned long)packetBuffer[40] << 24; secsSince1900 |= (unsigned long)packetBuffer[41] << 16; secsSince1900 |= (unsigned long)packetBuffer[42] << 8; secsSince1900 |= (unsigned long)packetBuffer[43]; return secsSince1900 - 2208988800UL + tZoneI * SECS_PER_HOUR + timeSummerWinter * SECS_PER_HOUR; //tZoneI } } Serial.println("No NTP Response (:-()"); return 0; // return 0 if unable to get the time } // send an NTP request to the time server at the given address void sendNTPpacket(IPAddress &address) { // set all bytes in the buffer to 0 memset(packetBuffer, 0, NTP_PACKET_SIZE); // Initialize values needed to form NTP request // (see URL above for details on the packets) packetBuffer[0] = 0b11100011; // LI, Version, Mode packetBuffer[1] = 0; // Stratum, or type of clock packetBuffer[2] = 6; // Polling Interval packetBuffer[3] = 0xEC; // Peer Clock Precision // 8 bytes of zero for Root Delay & Root Dispersion packetBuffer[12] = 49; packetBuffer[13] = 0x4E; packetBuffer[14] = 49; packetBuffer[15] = 52; // all NTP fields have been given values, now // you can send a packet requesting a timestamp: Udp.beginPacket(address, 123); //NTP requests are to port 123 Udp.write(packetBuffer, NTP_PACKET_SIZE); Udp.endPacket(); } void synchronClockA() { WiFiManager wifiManager; Rtc.Begin(); Serial.print("IP number assigned by DHCP is "); Serial.println(WiFi.localIP()); Serial.println("Starting UDP"); Udp.begin(localPort); Serial.print("Local port: "); Serial.println(Udp.localPort()); Serial.println("waiting for sync"); setSyncProvider(getNtpTime); if(timeStatus() != timeNotSet){ digitalClockDisplay(); Serial.println("here is another way to set rtc"); time_t t = now(); char date_0[12]; snprintf_P(date_0, countof(date_0), PSTR("%s %02u %04u"), monthShortStr(month(t)), day(t), year(t)); Serial.println(date_0); char time_0[9]; snprintf_P(time_0, countof(time_0), PSTR("%02u:%02u:%02u"), hour(t), minute(t), second(t)); Serial.println(time_0); Serial.println("Now its time to set up rtc"); RtcDateTime compiled = RtcDateTime(date_0, time_0); // printDateTime(compiled); Serial.println(""); if (!Rtc.IsDateTimeValid()) { // Common Cuases: // 1) first time you ran and the device wasn't running yet // 2) the battery on the device is low or even missing Serial.println("RTC lost confidence in the DateTime!"); // following line sets the RTC to the date & time this sketch was compiled // it will also reset the valid flag internally unless the Rtc device is // having an issue } Rtc.SetDateTime(compiled); RtcDateTime now = Rtc.GetDateTime(); if (now < compiled) { Serial.println("RTC is older than compile time! (Updating DateTime)"); Rtc.SetDateTime(compiled); } else if (now > compiled) { Serial.println("RTC is newer than compile time. (this is expected)"); } else if (now == compiled) { Serial.println("RTC is the same as compile time! (not expected but all is fine)"); } // never assume the Rtc was last configured by you, so // just clear them to your needed state Rtc.Enable32kHzPin(false); Rtc.SetSquareWavePin(DS3231SquareWavePin_ModeNone); } } void synchronClock() { Rtc.Begin(); wifiManager.autoConnect(ssid.c_str(), pass.c_str()); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(" "); Serial.print("IP number assigned by DHCP is "); Serial.println(WiFi.localIP()); Serial.println("Starting UDP"); Udp.begin(localPort); Serial.print("Local port: "); Serial.println(Udp.localPort()); Serial.println("waiting for sync"); setSyncProvider(getNtpTime); if(timeStatus() != timeNotSet){ digitalClockDisplay(); Serial.println("here is another way to set rtc"); time_t t = now(); char date_0[12]; snprintf_P(date_0, countof(date_0), PSTR("%s %02u %04u"), monthShortStr(month(t)), day(t), year(t)); Serial.println(date_0); char time_0[9]; snprintf_P(time_0, countof(time_0), PSTR("%02u:%02u:%02u"), hour(t), minute(t), second(t)); Serial.println(time_0); Serial.println("Now its time to set up rtc"); RtcDateTime compiled = RtcDateTime(date_0, time_0); Serial.println(""); if (!Rtc.IsDateTimeValid()) { // Common Cuases: // 1) first time you ran and the device wasn't running yet // 2) the battery on the device is low or even missing Serial.println("RTC lost confidence in the DateTime!"); // following line sets the RTC to the date & time this sketch was compiled // it will also reset the valid flag internally unless the Rtc device is // having an issue } Rtc.SetDateTime(compiled); RtcDateTime now = Rtc.GetDateTime(); if (now < compiled) { Serial.println("RTC is older than compile time! (Updating DateTime)"); Rtc.SetDateTime(compiled); } else if (now > compiled) { Serial.println("RTC is newer than compile time. (this is expected)"); } else if (now == compiled) { Serial.println("RTC is the same as compile time! (not expected but all is fine)"); } // never assume the Rtc was last configured by you, so // just clear them to your needed state Rtc.Enable32kHzPin(false); Rtc.SetSquareWavePin(DS3231SquareWavePin_ModeNone); } } void Clock(){ RtcDateTime now = Rtc.GetDateTime(); //Print RTC time to Serial Monitor hh = now.Hour(); mm = now.Minute(); ss = now.Second(); sprintf(date_r, "%d.%d.%d", now.Day(), now.Month(), now.Year()); if (mm < 10) sprintf(time_r, "%d:0%d", hh, mm); else sprintf(time_r, "%d:%d", hh, mm); Serial.println(date_r); Serial.println(time_r); } //callback notifying the need to save config void saveConfigCallback() { Serial.println("Should save config"); shouldSaveConfig = true; } void factoryReset() { Serial.println("Resetting to factory settings"); wifiManager.resetSettings(); SPIFFS.format(); ESP.reset(); } void printString(String str) { Serial.println(str); } void readCO2() { static byte cmd[9] = {0xFF,0x01,0x86,0x00,0x00,0x00,0x00,0x00,0x79}; // byte response[9]; byte crc = 0; while (mySerial.available())mySerial.read(); // UART memset(response, 0, 9);// mySerial.write(cmd,9);// CO2 mySerial.readBytes(response, 9);// 9 // crc = 0; for (int i = 1; i <= 7; i++) { crc += response[i]; } crc = ((~crc)+1); { // CRC if ( !(response[0] == 0xFF && response[1] == 0x86 && response[8] == crc) ) { Serial.println("CRC error"); } else { // CO2 co2 = (((unsigned int) response[2])<<8) + response[3]; Serial.println("CO2: " + String(co2) + "ppm"); } } } void sendMeasurements() { float t1 {-100}; int h1 {-1}, i; // Temperature t1 = dht.readTemperature(); if ((t1 > -1) and (t1 < 100)) t = t1; Serial.println("T: " + String(t) + "C"); // Humidity h1 = dht.readHumidity(); if ((h1 > -1) and (h1 < 100)) h = h1; Serial.println("H: " + String(h) + "%"); // CO2 readCO2(); } void sendToBlynk(){ Blynk.virtualWrite(V1, t); Blynk.virtualWrite(V2, h); Blynk.virtualWrite(V3, co2); Blynk.virtualWrite(V4, TemperaturePoint0); } void noData() { u8g2.setFont(u8g2_font_9x18_mf); x = 48; y = 40; u8g2.drawStr(x, y, "***"); } void drawOn() { float TemperatureP0; char Online_ch[]{" Online"}; TemperatureP0 = TemperaturePoint0 - Chs; dtostrf(TemperatureP0, 4, 1, Temperature0); // float char String Temperature0_i; Temperature0_i = String(Temperature0); char Temperature0_i_m [16]; Temperature0_i.toCharArray(Temperature0_i_m, 16); u8g2.clearBuffer(); String Temperature0_p; String onl1 = "OnLine T<"; Temperature0_p = onl1 + Temperature0_i_m; char Temperature0_p_m [16]; Temperature0_p.toCharArray(Temperature0_p_m, 16); String Tmx_i; Tmx_i = String(Tmx); char Tmx_i_m [16]; Tmx_i.toCharArray(Tmx_i_m, 16); u8g2.clearBuffer(); String Tmx_p; String onl2 = "OnLine T>"; Tmx_p = onl2 + Tmx_i_m; char Tmx_p_m [16]; Tmx_p.toCharArray(Tmx_p_m, 16); String Cmx_i; Cmx_i = String(Cmx); char Cmx_i_m [16]; Cmx_i.toCharArray(Cmx_i_m, 16); u8g2.clearBuffer(); String Cmx_p; String onl3 = "OnL CO2>"; Cmx_p = onl3 + Cmx_i_m; char Cmx_p_m [16]; Cmx_p.toCharArray(Cmx_p_m, 16); String Hmn_i; Hmn_i = String(Hmn); char Hmn_i_m [16]; Hmn_i.toCharArray(Hmn_i_m, 16); u8g2.clearBuffer(); String Hmn_p; String onl4 = "OnLine H<"; Hmn_p = onl4 + Hmn_i_m; char Hmn_p_m [16]; Hmn_p.toCharArray(Hmn_p_m, 16); //string 3 u8g2.setFont(u8g2_font_9x18_mf); x = 0; y = 64; u8g2.drawStr(x, y, Online_ch); if ((hh>=HourPoint1) and (hh<=HourPoint2) and (t<TemperatureP0)) u8g2.drawStr(x, y, Temperature0_p_m); else if (t > Tmax) u8g2.drawStr(x, y, Tmx_p_m); else if (co2 > Cmax) u8g2.drawStr(x, y, Cmx_p_m); else if (h < Hmin) u8g2.drawStr(x, y, Hmn_p_m); switch((millis() / 100) % 4) { // Temperature case 0: { String info_t; String paramT; String tmpr = "T("; String grad = "C):"; const char degree {176}; paramT = tmpr + degree + grad; char paramT_m [12]; paramT.toCharArray(paramT_m, 12); info_t = String(t); char info_t_m [12]; info_t.toCharArray(info_t_m, 5); //string 1 u8g2.setFont(u8g2_font_9x18_mf); x = 16; y = u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, paramT_m); //string 2 if ((t > -100) and (t < 100)) { u8g2.setFont(u8g2_font_inb24_mf); x = (128 - u8g2.getStrWidth(info_t_m))/2; y = y + 2 + u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, info_t_m); } else noData(); } break; //Humidity case 1: { String info_h; info_h = String(h); char info_h_m [12]; info_h.toCharArray(info_h_m, 12); //string 1 u8g2.setFont(u8g2_font_9x18_mf); x = 16; y = u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, "H(%):"); //string 2 if ((h > -1) and (h < 100)){ u8g2.setFont(u8g2_font_inb24_mf); x = (128 - u8g2.getStrWidth(info_h_m))/2; y = y + 2 + u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, info_h_m); } else noData(); } break; //CO2 case 2: { String info_co2; info_co2 = String(co2); char info_co2_m [12]; info_co2.toCharArray(info_co2_m, 12); //string 1 u8g2.setFont(u8g2_font_9x18_mf); x = 8; y = u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, "CO2(ppm):"); //string 2 if ((co2 > -1) and (co2 <= 2000)) { u8g2.setFont(u8g2_font_inb24_mf); x = (128 - u8g2.getStrWidth(info_co2_m))/2; y = y + 2 + u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, info_co2_m); } else noData(); } break; //time, date case 3: { //string 1 u8g2.setFont(u8g2_font_9x18_mf); x = (128 - u8g2.getStrWidth(date_r))/2; y = u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, date_r); //string 2 u8g2.setFont(u8g2_font_inb24_mf); x = (128 - u8g2.getStrWidth(time_r))/2; y = y + 2 + u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, time_r); } break; } u8g2.sendBuffer(); } void drawOff() { float TemperatureP0A; char OffLine_ch[]{"Offline Tst=21"}; TemperatureP0A = TemperaturePointA0 - Chs; // dtostrf(TemperatureP0A, 4, 1, TemperaturePointA0); // float char String TemperaturePointA0_i; TemperaturePointA0_i = String(TemperaturePointA0); char TemperaturePointA0_i_m [16]; TemperaturePointA0_i.toCharArray(TemperaturePointA0_i_m, 16); u8g2.clearBuffer(); String TemperaturePointA0_p; String onl1 = "Offline T<"; TemperaturePointA0_p = onl1 + TemperaturePointA0_i_m; char TemperaturePointA0_p_m [16]; TemperaturePointA0_p.toCharArray(TemperaturePointA0_p_m, 16); //string 3 u8g2.setFont(u8g2_font_9x18_mf); x = 0; y = 64; u8g2.drawStr(x, y, OffLine_ch); if (t<TemperatureP0A) u8g2.drawStr(x, y, TemperaturePointA0_p_m); switch((millis() / 100) % 4) { // Temperature case 0: { String info_t; String paramT; String tmpr = "T("; String grad = "C):"; const char degree {176}; paramT = tmpr + degree + grad; char paramT_m [12]; paramT.toCharArray(paramT_m, 12); info_t = String(t); char info_t_m [12]; info_t.toCharArray(info_t_m, 5); //string 1 u8g2.setFont(u8g2_font_9x18_mf); x = 16; y = u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, paramT_m); //string 2 if ((t > -100) and (t < 100)) { u8g2.setFont(u8g2_font_inb24_mf); x = (128 - u8g2.getStrWidth(info_t_m))/2; y = y + 2 + u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, info_t_m); } else noData(); } break; //Humidity case 1: { String info_h; info_h = String(h); char info_h_m [12]; info_h.toCharArray(info_h_m, 12); //string 1 u8g2.setFont(u8g2_font_9x18_mf); x = 16; y = u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, "H(%):"); //string 2 if ((h > -1) and (h < 100)){ u8g2.setFont(u8g2_font_inb24_mf); x = (128 - u8g2.getStrWidth(info_h_m))/2; y = y + 2 + u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, info_h_m); } else noData(); } break; //CO2 case 2: { String info_co2; info_co2 = String(co2); char info_co2_m [12]; info_co2.toCharArray(info_co2_m, 12); //string 1 u8g2.setFont(u8g2_font_9x18_mf); x = 8; y = u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, "CO2(ppm):"); //string 2 if ((co2 > -1) and (co2 <= 2000)) { u8g2.setFont(u8g2_font_inb24_mf); x = (128 - u8g2.getStrWidth(info_co2_m))/2; y = y + 2 + u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, info_co2_m); } else noData(); } break; //time, date case 3: { //string 1 u8g2.setFont(u8g2_font_9x18_mf); x = (128 - u8g2.getStrWidth(date_r))/2; y = u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, date_r); //string 2 u8g2.setFont(u8g2_font_inb24_mf); x = (128 - u8g2.getStrWidth(time_r))/2; y = y + 2 + u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, time_r); } break; } u8g2.sendBuffer(); } void drawOffBlynk() { float TemperatureP0; char OffBlynk_ch[]{" OffBlynk"}; TemperatureP0 = TemperaturePoint0 - Chs; dtostrf(TemperatureP0, 4, 1, Temperature0); // float char String Temperature0_i; Temperature0_i = String(Temperature0); char Temperature0_i_m [16]; Temperature0_i.toCharArray(Temperature0_i_m, 16); u8g2.clearBuffer(); String Temperature0_p; String onl1 = "OffBL T<"; Temperature0_p = onl1 + Temperature0_i_m; char Temperature0_p_m [16]; Temperature0_p.toCharArray(Temperature0_p_m, 16); String Tmx_i; Tmx_i = String(Tmx); char Tmx_i_m [16]; Tmx_i.toCharArray(Tmx_i_m, 16); u8g2.clearBuffer(); String Tmx_p; String onl2 = "OffBL T>"; Tmx_p = onl2 + Tmx_i_m; char Tmx_p_m [16]; Tmx_p.toCharArray(Tmx_p_m, 16); String Cmx_i; Cmx_i = String(Cmx); char Cmx_i_m [16]; Cmx_i.toCharArray(Cmx_i_m, 16); u8g2.clearBuffer(); String Cmx_p; String onl3 = "OnL CO2>"; Cmx_p = onl3 + Cmx_i_m; char Cmx_p_m [16]; Cmx_p.toCharArray(Cmx_p_m, 16); String Hmn_i; Hmn_i = String(Hmn); char Hmn_i_m [16]; Hmn_i.toCharArray(Hmn_i_m, 16); u8g2.clearBuffer(); String Hmn_p; String onl4 = "OffBL H<"; Hmn_p = onl4 + Hmn_i_m; char Hmn_p_m [16]; Hmn_p.toCharArray(Hmn_p_m, 16); //string 3 u8g2.setFont(u8g2_font_9x18_mf); x = 0; y = 64; u8g2.drawStr(x, y, OffBlynk_ch); if ((hh>=HourPoint1) and (hh<=HourPoint2) and (t<TemperatureP0)) u8g2.drawStr(x, y, Temperature0_p_m); else if (t > Tmax) u8g2.drawStr(x, y, Tmx_p_m); else if (co2 > Cmax) u8g2.drawStr(x, y, Cmx_p_m); else if (h < Hmin) u8g2.drawStr(x, y, Hmn_p_m); switch((millis() / 100) % 4) { // Temperature case 0: { String info_t; String paramT; String tmpr = "T("; String grad = "C):"; const char degree {176}; paramT = tmpr + degree + grad; char paramT_m [12]; paramT.toCharArray(paramT_m, 12); info_t = String(t); char info_t_m [12]; info_t.toCharArray(info_t_m, 5); //string 1 u8g2.setFont(u8g2_font_9x18_mf); x = 16; y = u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, paramT_m); //string 2 if ((t > -100) and (t < 100)) { u8g2.setFont(u8g2_font_inb24_mf); x = (128 - u8g2.getStrWidth(info_t_m))/2; y = y + 2 + u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, info_t_m); } else noData(); } break; //Humidity case 1: { String info_h; info_h = String(h); char info_h_m [12]; info_h.toCharArray(info_h_m, 12); //string 1 u8g2.setFont(u8g2_font_9x18_mf); x = 16; y = u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, "H(%):"); //string 2 if ((h > -1) and (h < 100)){ u8g2.setFont(u8g2_font_inb24_mf); x = (128 - u8g2.getStrWidth(info_h_m))/2; y = y + 2 + u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, info_h_m); } else noData(); } break; //CO2 case 2: { String info_co2; info_co2 = String(co2); char info_co2_m [12]; info_co2.toCharArray(info_co2_m, 12); //string 1 u8g2.setFont(u8g2_font_9x18_mf); x = 8; y = u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, "CO2(ppm):"); //string 2 if ((co2 > -1) and (co2 <= 2000)) { u8g2.setFont(u8g2_font_inb24_mf); x = (128 - u8g2.getStrWidth(info_co2_m))/2; y = y + 2 + u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, info_co2_m); } else noData(); } break; //time, date case 3: { //string 1 u8g2.setFont(u8g2_font_9x18_mf); x = (128 - u8g2.getStrWidth(date_r))/2; y = u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, date_r); //string 2 u8g2.setFont(u8g2_font_inb24_mf); x = (128 - u8g2.getStrWidth(time_r))/2; y = y + 2 + u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, time_r); } break; } u8g2.sendBuffer(); } void drawBoot(String msg = "Loading...") { u8g2.clearBuffer(); u8g2.setFont(u8g2_font_9x18_mf); x = (128 - u8g2.getStrWidth(msg.c_str())) / 2; y = 32 + u8g2.getAscent() / 2; u8g2.drawStr(x, y, msg.c_str()); u8g2.sendBuffer(); } void drawConnectionDetails(String ssid, String pass, String url) { String msg {""}; u8g2.clearBuffer(); msg = "Connect to WiFi:"; u8g2.setFont(u8g2_font_7x13_mf); x = (128 - u8g2.getStrWidth(msg.c_str())) / 2; y = u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, msg.c_str()); msg = "net: " + ssid; x = (128 - u8g2.getStrWidth(msg.c_str())) / 2; y = y + 1 + u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, msg.c_str()); msg = "pw: "+ pass; x = (128 - u8g2.getStrWidth(msg.c_str())) / 2; y = y + 1 + u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, msg.c_str()); msg = "Open browser:"; x = (128 - u8g2.getStrWidth(msg.c_str())) / 2; y = y + 1 + u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, msg.c_str()); // URL // u8g2.setFont(u8g2_font_6x12_mf); x = (128 - u8g2.getStrWidth(url.c_str())) / 2; y = y + 1 + u8g2.getAscent() - u8g2.getDescent(); u8g2.drawStr(x, y, url.c_str()); u8g2.sendBuffer(); } bool loadConfigS(){ Blynk.config(address); Serial.print("e-mail: "); Serial.println( address ); Blynk.config(Tmx); Serial.print("T max: "); Serial.println( Tmx ); Blynk.config(Cmx); Serial.print("CO2 max: "); Serial.println( Cmx ); Blynk.config(Temperature0); Serial.print("Temperature 0: "); Serial.println( Temperature0 ); Blynk.config(Temperature1); Serial.print("Temperature1: "); Serial.println( Temperature1 ); Blynk.config(Temperature2); Serial.print("Temperature2: "); Serial.println( Temperature2 ); Blynk.config(Hmn); Serial.print("H min: "); Serial.println( Hmn ); Blynk.config(Hour1); Serial.print("Hour 1: "); Serial.println( Hour1 ); Blynk.config(Hour2); Serial.print("Hour 2: "); Serial.println( Hour2 ); Blynk.config(tZ); Serial.print("Time Zone: "); Serial.println( tZ ); Blynk.config(blynk_token, "blynk-cloud.com", 8442); Serial.print("token: " ); Serial.println( blynk_token ); } bool loadConfig() { Serial.println("Load config..."); File configFile = SPIFFS.open("/config.json", "r"); if (!configFile) { Serial.println("Failed to open config file"); return false; } size_t size = configFile.size(); if (size > 1024) { Serial.println("Config file size is too large"); return false; } // Allocate a buffer to store contents of the file. std::unique_ptr<char[]> buf(new char[size]); // We don't use String here because ArduinoJson library requires the input // buffer to be mutable. If you don't use ArduinoJson, you may as well // use configFile.readString instead. configFile.readBytes(buf.get(), size); StaticJsonBuffer<200> jsonBuffer; JsonObject &json = jsonBuffer.parseObject(buf.get()); if (!json.success()) { Serial.println("Failed to parse config file"); return false; } // Save parameters strcpy(blynk_token, json["blynk_token"]); strcpy(address, json["address"]); strcpy(Tmx, json["Tmx"]); strcpy(Cmx, json["Cmx"]); strcpy(Temperature0, json["Temperature0"]); strcpy(Temperature1, json["Temperature1"]); strcpy(Temperature2, json["Temperature2"]); strcpy(Hmn, json["Hmn"]); strcpy(Hour1, json["Hour1"]); strcpy(Hour2, json["Hour2"]); strcpy(tZ, json["tZ"]); } void configModeCallback (WiFiManager *wifiManager) { String url {"http://192.168.4.1"}; printString("Connect to WiFi:"); printString("net: " + ssid); printString("pw: "+ pass); printString("Open browser:"); printString(url); printString("to setup device"); drawConnectionDetails(ssid, pass, url); } void setupWiFi() { //set config save notify callback wifiManager.setSaveConfigCallback(saveConfigCallback); // Custom parameters WiFiManagerParameter custom_tZ("tZ", "Time Zone", tZ, 5); wifiManager.addParameter(&custom_tZ); WiFiManagerParameter custom_Temperature0("Temperature0", "Temperature 0", Temperature0, 5); wifiManager.addParameter(&custom_Temperature0); WiFiManagerParameter custom_Hour1("Hour1", "Hour 1", Hour1, 5); wifiManager.addParameter(&custom_Hour1); WiFiManagerParameter custom_Temperature1("Temperature1", "Temperature 1", Temperature1, 5); wifiManager.addParameter(&custom_Temperature1); WiFiManagerParameter custom_Hour2("Hour2", "Hour 2", Hour2, 5); wifiManager.addParameter(&custom_Hour2); WiFiManagerParameter custom_Temperature2("Temperature2", "Temperature 2", Temperature2, 5); wifiManager.addParameter(&custom_Temperature2); WiFiManagerParameter custom_Cmx("Cmx", "Cmax", Cmx, 7); wifiManager.addParameter(&custom_Cmx); WiFiManagerParameter custom_Hmn("Hmn", "Hmin", Hmn, 5); wifiManager.addParameter(&custom_Hmn); WiFiManagerParameter custom_Tmx("Tmx", "Tmax", Tmx,5); wifiManager.addParameter(&custom_Tmx); WiFiManagerParameter custom_address("address", "E-mail", address, 64); wifiManager.addParameter(&custom_address); WiFiManagerParameter custom_blynk_token("blynk_token", "Blynk Token", blynk_token, 34); wifiManager.addParameter(&custom_blynk_token); wifiManager.setAPCallback(configModeCallback); wifiManager.setTimeout(180); if (!wifiManager.autoConnect(ssid.c_str(), pass.c_str())) { a++; Serial.println("mode OffLINE :("); loadConfigS(); synchronClockA(); } //save the custom parameters to FS if (shouldSaveConfig) { Serial.println("saving config"); DynamicJsonBuffer jsonBuffer; JsonObject &json = jsonBuffer.createObject(); json["blynk_token"] = custom_blynk_token.getValue(); json["address"] = custom_address.getValue(); json["Tmx"] = custom_Tmx.getValue(); json["Cmx"] = custom_Cmx.getValue(); json["Temperature0"] = custom_Temperature0.getValue(); json["Temperature1"] = custom_Temperature1.getValue(); json["Temperature2"] = custom_Temperature2.getValue(); json["Hmn"] = custom_Hmn.getValue(); json["Hour1"] = custom_Hour1.getValue(); json["Hour2"] = custom_Hour2.getValue(); json["tZ"] = custom_tZ.getValue(); File configFile = SPIFFS.open("/config.json", "w"); if (!configFile) { Serial.println("failed to open config file for writing"); } json.printTo(Serial); json.printTo(configFile); configFile.close(); //end save } //if you get here you have connected to the WiFi Serial.println("WiFi connected"); Serial.print("IP address: "); Serial.println(WiFi.localIP()); } BLYNK_WRITE(V10) { if (param.asInt() == 1) { buttonBlynk = true; Blynk.virtualWrite(V10, HIGH); drawBoot("Thermo ON"); } else { buttonBlynk = false; Blynk.virtualWrite(V10, LOW); drawBoot("Thermo OFF"); } } void mailer() { // wait for WiFi connection if((WiFiMulti.run() == WL_CONNECTED)) { HTTPClient http; Serial.print("[HTTP] begin...\n"); http.begin("http://skorovoda.in.ua/php/aqm42.php?mymail="+String(address)+"&t="+String(t) +"&h="+String(h)+"&co2="+String(co2)+"&ID="+String(ESP.getChipId())); Serial.print("[HTTP] GET...\n"); // start connection and send HTTP header int httpCode = http.GET(); // httpCode will be negative on error if(httpCode > 0) { // HTTP header has been send and Server response header has been handled Serial.printf("[HTTP] GET... code: %d\n", httpCode); // file found at server if(httpCode == HTTP_CODE_OK) { String payload = http.getString(); Serial.println(payload); } } else { Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str()); } http.end(); } } void HystTemperatureA() { float TemperaturePointA0Mn, TemperaturePointA0Pl; TemperaturePointA0Mn = TemperaturePointA0-Chs; TemperaturePointA0Pl = TemperaturePointA0+Chs; if (t<TemperaturePointA0Mn) { if (millis() - TimeTransmitMax > 120000){ TimeTransmitMax = millis(); transmitter.send(B11111110, 8); Serial.println ("t<TemperaturePointA0Mn Thermostat ON"); } } else if (millis() - TimeTransmitMax > 120000) { TimeTransmitMax = millis(); transmitter.send(B10000000, 8); Serial.println ("t>TemperaturePointA0Mn Thermostat OFF"); } if (t<TemperaturePointA0Pl) { if (millis() - TimeTransmitMax > 120000){ TimeTransmitMax = millis(); transmitter.send(B11111110, 8); Serial.println ("t<TemperaturePointA0Pl Thermostat ON"); } } else if (millis() - TimeTransmitMax > 120000) { TimeTransmitMax = millis(); transmitter.send(B10000000, 8); Serial.println ("t>TemperaturePointA0Pl Thermostat OFF"); } } void HystTemperature() { float TemperaturePoint0Mn, TemperaturePoint0Pl; TemperaturePoint0Mn = TemperaturePoint0-Chs; TemperaturePoint0Pl = TemperaturePoint0+Chs; if (t<TemperaturePoint0Mn) { if (millis() - TimeTransmitMax > 120000){ TimeTransmitMax = millis(); transmitter.send(B11111110, 8); Serial.println ("t<TemperaturePoint0Mn Thermostat ON"); } } else if (millis() - TimeTransmitMax > 120000) { TimeTransmitMax = millis(); transmitter.send(B10000000, 8); Serial.println ("t>TemperaturePoint0Mn Thermostat OFF"); } if (t<TemperaturePoint0Pl) { if (millis() - TimeTransmitMax > 120000){ TimeTransmitMax = millis(); transmitter.send(B11111110, 8); Serial.println ("t<TemperaturePoint0Pl Thermostat ON"); } } else if (millis() - TimeTransmitMax > 120000) { TimeTransmitMax = millis(); transmitter.send(B10000000, 8); Serial.println ("t>TemperaturePoint0Pl Thermostat OFF"); } } void TransmitterA(){ transmitter.send(B10101010, 8); //B10101010 - HystTemperatureA(); } void Transmitter(){ transmitter.send(B10101010, 8); //B10101010 - if (n>=24) n = 0; if (m>=60) m = 0; progr = 0; if ((hh >= HourPoint1) and (hh < HourPoint2)){ progr = 1; if (mm >= MinPoint1) progr = 1; if (mm < MinPoint2) progr = 1; } else if (hh >= HourPoint2) { progr = 2; if (mm >= MinPoint2) progr = 2; } if (buttonBlynk==true) { Serial.println ("BLynk: "); if (progr == 0) { TemperaturePoint0 = TemperaturePoint0; HystTemperature(); Serial.println (": t = " + String(TemperaturePoint0)); } else if (progr == 1) { TemperaturePoint0 = TemperaturePoint1; HystTemperature(); Serial.println (": t = " + String(TemperaturePoint0)); } else if (progr == 2){ TemperaturePoint0 = TemperaturePoint2; HystTemperature(); Serial.println (": t = " + String(TemperaturePoint0)); } } else { transmitter.send(B10000000, 8); Serial.println ("BLynk: "); } if (co2 > Cmax) { transmitter.send(B11111101, 8); Serial.println("co2 > Cmax"); } else transmitter.send(B00000010, 8); if (h < Hmin) { transmitter.send(B11111011, 8); Serial.println("h < Hmin"); } else transmitter.send(B00000100, 8); if (t > Tmax) { transmitter.send(B11110111, 8); Serial.println("t > Tmax"); } else transmitter.send(B00001000, 8); } void connectBlynk(){ if(String(blynk_token)== "Blynk token"){ drawBoot("OFFBLYNK!"); delay (3000); } else { drawBoot("Connect. Blynk"); Serial.println("Connecting to blynk..."); while (Blynk.connect() == false) { delay(500); Serial.println("Connecting to blynk..."); } } } void setup() { // factoryReset(); // RAM mySerial.begin(9600); Serial.begin(115200); transmitter.enableTransmit(2); u8g2.begin(); // drawBoot("Loading..."); // if (!SPIFFS.begin()) { Serial.println("Failed to mount file system"); ESP.reset(); } // drawBoot("Connect. WiFi"); setupWiFi(); timerCO2 = timer.setInterval(15000, readCO2); buttonBlynk = true; if(a == 1){ // Load config drawBoot("Load Config"); if (!loadConfig()) { Serial.println("Failed to load config"); factoryReset(); } else { Serial.println("Config loaded"); } Blynk.config(address); Serial.print("e-mail: "); Serial.println(address); Blynk.config(Tmx); Serial.print("T max: "); Serial.println(Tmx); Blynk.config(Cmx); Serial.print("CO2 max: "); Serial.println(Cmx); Blynk.config(Temperature0); Serial.print("Temperature 0: "); Serial.println(Temperature0); Blynk.config(Temperature1); Serial.print("Temperature1: "); Serial.println(Temperature1); Blynk.config(Temperature2); Serial.print("Temperature2: "); Serial.println(Temperature2); Blynk.config(Hmn); Serial.print("H min: "); Serial.println(Hmn); Blynk.config(Hour1); Serial.print("Hour 1: "); Serial.println(Hour1); Blynk.config(Hour2); Serial.print("Hour 2: "); Serial.println(Hour2); Blynk.config(tZ); Serial.print("Time Zone: "); Serial.println(tZ); Blynk.config(blynk_token, "blynk-cloud.com", 8442); Serial.print("token: " ); Serial.println(blynk_token); // char float Tmax = atof (Tmx); Cmax = atof (Cmx); TemperaturePoint0 = atof (Temperature0); TemperaturePoint1 = atof (Temperature1); TemperaturePoint2 = atof (Temperature2); Hmin = atof (Hmn); HourPoint1 = atof (Hour1); HourPoint2 = atof (Hour2); tZone = atof (tZ); // drawBoot("Clock synchr."); synchronClock(); // timerCO2 = timer.setInterval(15000, readCO2); timerBl = timer.setInterval(5000, sendToBlynk); connectBlynk(); // Blynk Blynk.virtualWrite(V10, HIGH); // V10 buttonBlynk = true; } } void loop(){ if (a == 2) { Serial.println(":( OffLINE"); timer.run(); Clock(); sendMeasurements(); TransmitterA(); drawOff(); delay(1000); } else if (a == 1) { Serial.println(":) OnLINE"); timer.run(); Clock(); Blynk.run(); BLYNK_WRITE(V10); Transmitter(); sendMeasurements(); if(String(blynk_token) == "Blynk token") drawOffBlynk(); else drawOn(); if (j>=24) j =0; if (hh == j){ if ((mm==30) and ((ss<30) )){ if ((t > Tmax) or (co2 > Cmax) or (h < Hmin) or ((progr == 0) and (t<(TemperaturePoint0-1.0)) or ((progr == 1) and (t<(TemperaturePoint1-1.0)) or ((progr == 2) and (t<(TemperaturePoint2-1.0)))))) mailer(); } } j++; } }
Se pelo menos um dos parâmetros de ar estiver fora dos limites programados, o dispositivo enviará uma carta ao e-mail a cada meia hora a cada 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.
Contator

O controle no contator é realizado pelo módulo Arduino Pro Mini. Ele recebe um sinal do receptor de RF e gera sinais para exceder os valores limite dos parâmetros do ar.
A tensão de alimentação de todos os nós do contator de 5V vem do adaptador HLK-PM01 CA / CC.
Os sinais das saídas 6 (h> Hmin), 5 (co2> CO2max), 3 (t> Tmax) do controlador podem ser usados para organizar a umidificação automática, ventilação forçada ou ar condicionado. A vantagem é que não há necessidade de instalar um cabo para transmitir um sinal de controle do sensor para um sistema específico - basta colocar o contator próximo a uma das extremidades dos fios de controle de energia ou sistema.
Por exemplo, além de controlar a caldeira de aquecimento, pretendo conectar um exaustor ao contator - a caldeira e o exaustor estão localizados nas proximidades.
Esboço do contator para carregamento no Arduino Pro Mini - sob o spoiler.
esboço do contator #include <RCSwitch.h> //https://github.com/sui77/rc-switch RCSwitch mySwitch = RCSwitch(); void setup() { pinMode(13, OUTPUT); pinMode(3, OUTPUT); pinMode(4, OUTPUT); pinMode(5, OUTPUT); pinMode(6, OUTPUT); digitalWrite(3, HIGH); digitalWrite(4, HIGH); digitalWrite(5, HIGH); digitalWrite(6, HIGH); digitalWrite(13, LOW); mySwitch.enableReceive(0); } void loop() { if( mySwitch.available() ){ int value = mySwitch.getReceivedValue(); //t < Tmin if(value == B11111110) digitalWrite(4, LOW); else if (value == B10000000) digitalWrite(4, HIGH); //co2 > Cmax if(value == B11111101) digitalWrite(5, LOW); else if (value == B00000010) digitalWrite(5, HIGH); //h < Hmin if(value == B11111011) digitalWrite(6, LOW); else if (value == B00000100) digitalWrite(6, HIGH); //t > Tmax if(value == B11110111)digitalWrite(3, LOW); else if (value == B00001000) digitalWrite(3, HIGH); // D13 Arduino - - ( - ) if(value == B10101010) digitalWrite(13, HIGH); // B10101010 - , else digitalWrite(13, LOW); mySwitch.resetAvailable(); } }

Iniciando o termostato para funcionar
É hora de ligar o termostato.
Etapa 1:
Primeiro, ligue o analisador.
Primeiro, você precisa ter paciência e, sem fazer nada, aguarde 3 minutos. O termostato entra automaticamente no modo offline - sem conectar via Wi-Fi à sua rede doméstica e à Internet. Após 3 minutos, na tela do analisador em três linhas, tudo o que o termostato liga pisca.
As duas primeiras linhas na tela não requerem comentários. A terceira linha contém o modo de operação do termostato ( offline, online ou offline ) e informações sobre como ir além dos valores limites estabelecidos dos parâmetros do ar. Por exemplo, CO2 offline> 1000 - o termostato opera no modo autônomo e o conteúdo de CO 2 medido é superior ao valor limite definido de 1000 ppm.
Um relógio offline mostrará a hora errada. Eles ainda não estão sincronizados com o servidor de horário exato e o fuso horário ainda não foi inserido - esta é a próxima etapa.
No modo autônomo, a temperatura do termostato é ajustada em 21 ° C durante o dia.
Etapa 2:
Depois de dominar o modo offline, desligue e ligue o adaptador do analisador CA / CC. Uma mensagem familiar aparecerá na tela, com a qual nos acostumamos em três minutos de espera pelo modo offline.
O dispositivo elevou o ponto de acesso do am-5108. Encontramos esse ponto na lista de redes disponíveis e conectamos a ele, a senha está na tela. 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:
O mesmo formulário com campos e comentários em branco:

Indique no formulário o nome e a senha da sua rede doméstica, chave de identificação BLynk, e-mail. Altere o fuso horário padrão, a hora (horas) e a temperatura dos pontos de tempo, bem como os valores limite para temperatura, umidade e conteúdo de CO 2 .
O dia é dividido por dois momentos em três intervalos de tempo - o primeiro: de 00 horas 00 min ao ponto 1 ( Hora 1, minuto 1 ), o segundo: do ponto 1 ( Hora 1, minuto 1 ) ao ponto 2 ( Hora 2, minuto 2 ) e o terceiro: do ponto 2 ( hora 2, minuto 2 ) a 00 horas e 00 minutos . Não há campos para inserir minutos no formulário; os minutos para os pontos 1,2 podem ser alterados no esboço (variáveis MinPoint1, MinPoint2 ). Em cada um dos três intervalos de tempo, você pode definir sua própria temperatura de controle de temperatura - Temperatura 0, Temperatura 1 e Temperatura 2 . Se você planeja manter a mesma temperatura constante ao longo do dia, defina o valor como Temperatura 0 e deixe os campos dos pontos 1.2 vazios.
Ao escolher valores-limite para parâmetros de ar, seja guiado por indicadores que encontrei na Internet:
- Temperatura confortável à noite durante o sono 19 ... 21 ° C, durante o dia - 22 ... 23 ° C.
- A umidade relativa ideal na estação fria é considerada umidade de 30 ... 45% e na quente - 30 ... 60%. Indicadores de umidade máxima máxima: no inverno, não deve exceder 60% e no verão - 65%.
- O nível máximo de dióxido de carbono nos quartos 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 é o limite do conteúdo permitido de CO 2 na sala. Se houver mais, a qualidade do ar é considerada baixa.
Por padrão, o programa diário de controle de temperatura (durante o dia - temperatura alta, durante a noite - baixa) é definido com base no pressuposto de que durante o dia um dos inquilinos está na sala, por exemplo, trabalha em casa. É fácil mudar o programa para se adequar às suas realidades.
O campo de email pode ser deixado em branco. Em seguida, a oportunidade de receber e-mails sobre a saída dos parâmetros aéreos além dos valores limite será perdida. Sem a tecla Blynk inserida, é impossível controlar o termostato e receber informações sobre os parâmetros do ar à distância. No entanto, o termostato não é "perdido", se os campos com os valores limite dos parâmetros do ar permanecerem vazios, apenas uma função permanecerá por trás dele: termostato.
E mais uma coisa. Digite todos os números no formato de variáveis de ponto flutuante e a conversão para o formato desejado é realizada no esboço. Exceção: pontos de tempo 1,2 (hora) - formato inteiro.
Depois de salvar as configurações na memória do ESP8266 (botão Salvar ), o analisador se conectará à rede e começará a operação.
Se você cometer um erro (isso acontece!) Ou decidir alterar as configurações, precisará novamente carregar o esboço no ESP8266 duas vezes. A primeira vez - com a linha factoryReset () descomentada em Setup'e; e o segundo comentou e repita a etapa 2.
Etapa 3:
Agora você pode ligar o contator.
Com uma comunicação de rádio estável entre o analisador e o contator, o LED D13 na placa Arduino pisca a uma frequência de cerca de 1 Hz.
Se o contator receber um comando do analisador para ligar o dispositivo de aquecimento ou o sistema de aquecimento, os contatos do relé normalmente abertos fecharão e o LED correspondente no módulo de relé acenderá.
Se não houver nenhum problema com o contator inativo, conectamos um dispositivo de aquecimento ou componentes eletrônicos do sistema de aquecimento. O dispositivo de aquecimento deve ser conectado com um fio de uma determinada seção. O indicador específico para o cálculo da seção transversal de um fio de cobre é de 5 A / mm 2 .
Etapa 4:
É hora de iniciar o aplicativo Blynk no seu smartphone. Há muitas informações sobre o aplicativo Blynk na Internet - não faz sentido repeti-lo.
Variáveis para Blynk (para não procurá-las no esboço do analisador): temperatura - V1 , umidade - V2 , conteúdo de CO 2 - V3 , temperatura de controle de temperatura - V4 , botão virtual - V10 .
No meu smartphone, a interface Blynk'a (você pode alterá-la) se parece com:
O gráfico mostra a temperatura medida (branca), temperatura termostática (amarela), o intervalo de tempo é de 24 horas. As variáveis de umidade e conteúdo de CO 2 não são exibidas no gráfico, pois duas escalas adicionais limitam severamente o campo do gráfico onde as próprias curvas podem ser consideradas.
O sinal do botão virtual THERMOSTAT é gerado apenas quando o botão é pressionado. Quando um botão é pressionado na tela do analisador, a mensagem Thermo OFF! ou Thermo ON! - dependendo do estado anterior do botão. Esta mensagem é relevante ao testar o termostato.
A captura de tela abaixo ilustra o processo de aquecimento de um aquecedor de ventilador de 2 kW / hora, com uma área de cerca de 5 metros quadrados e uma temperatura inicial de 16 ° C. Aqui estão a temperatura (amarelo), a umidade (azul) e o conteúdo de CO 2 (vermelho).
A curva de umidade dentada sincronizada com a serra de temperatura no gráfico é outra confirmação do fato bem conhecido de que um elemento de aquecimento aberto seca o ar, e os picos na curva de conteúdo de CO 2 são evidências de minhas visitas de curto prazo à sala.
Agora estamos testando a operação do sistema de notificação por email. Digite a linha comentada com o endereço http do código php-script na barra de endereços do navegador. Se você não 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.

Intenções
No futuro, pretendo trabalhar para melhorar o termostato (como se costuma dizer, não há limite para a perfeição!)
Tarefas - muitas:
- Complemente o termostato com um sensor de temperatura com conexão sem fio para medir a temperatura externa.
- Substitua o par transmissor-receptor de RF por outro par com um maior alcance de comunicação com uma tensão de alimentação não superior a 3V. Idealmente, eu gostaria de montar um analisador alimentado por duas pilhas AA durante a estação de aquecimento.
- Evite a formatação manual da memória do ESP8266 antes de cada alteração nas configurações do termostato, recarregando o esboço.
- Aumente o ciclo do termostato programável de diário para semanal.
- Substitua a tela monocromática por cores e resolução mais alta. Isso tornará possível mostrar todas as informações sobre a operação do termostato em um quadro e a saída dos parâmetros do ar além dos limites estabelecidos - por uma mudança de cor.
- Em seguida, lide com placas de circuito impresso e com uma aparência apresentável do termostato.
O que mais pode ser melhorado? Sugestões aceitas, comentários. Eu ouço críticas construtivas.
Conclusões
- Graças à conexão com a Internet, a funcionalidade do termostato aumentou significativamente. Além da função principal, ela implementa várias outras: desde o envio de alertas por e-mail até a capacidade de manter automaticamente a qualidade do ar interno.
- Uma nova qualidade apareceu no termostato: ele pode ser controlado pela Internet.
- A facilidade com que o termostato está programado é agradável: você só precisa preencher o formulário na página do navegador.
- Agora você pode salvar dados pessoais na memória do termostato, como é feito, por exemplo, nos roteadores.
Atenção!
O autor não é responsável por um possível negativo ao repetir o projeto. Você é responsável por tudo o que faz.
PS 1. O modelo do projeto substituiu dignamente o antigo termostato, pois na quarta estação de aquecimento ele ocasionalmente começou a "esquecer" de ligar e desligar o sistema de aquecimento.
2. Sobre as abordagens na solução de algumas das tarefas listadas acima, é possível se familiarizar nos meus outros artigos sobre Habré:
Meus favoritos em um assunto de Habr