Контроль влажности подвала ESP8266

Идея создания проекта - Контроль влажности подвала Arduino pro mini | Аппаратная платформа Arduino

Переделано на ESP8266 - полностью.

Исходники и скриншоты прилагаются.
Server ESP_Gar_server.ino

// 15.04.2023
// https://randomnerdtutorials.com/esp8266-esp-now-wi-fi-web-server/

#include <espnow.h>  
#include <ESP8266WiFi.h>
#include <ESPAsyncWebServer.h> 
#include <Arduino_JSON.h>      // version=0.2.0
#include <SparkFunHTU21D.h>    // version=1.1.3  
#include <LiquidCrystal_I2C.h>
#include <ESP8266mDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>        // version=1.0
#include <ESP8266HTTPClient.h>
#include <WiFiClient.h>
#include <UrlEncode.h>         // version=1.0.1
#include <RemoteDebug.h>       // version=2.1.2
#include "html.h"      // веб-страница
#include "settings.h"  // Настройки - ввести свои данные!!!

#define ENABLE_OTA_UPDATE true //Обновление ESP8266 по воздуху
 
//Глобальные переменные - не изменять!!!!!
//-----------------------------------
// данные WiFi AP точки доступа, должны совпадать с клиентом в подвале
const char ssidAP[] = "esp_8266";     
const char passAP[] = "00000000";
//-----------------------------------
//    пины вентилятора и отопителя  ////////////////////////////
const int FANpin = 14;       //  GPIO14(D5). 
bool FANstatus = false;    //  реле ВЫКЛ.   
const int HEATERpin = 12;    //  GPIO12(D6). 
bool HEATERstatus = false; //  реле ВЫКЛ.  
String fan_= "OFF";      // ON, OFF статус
String hea_= "OFF";
//-----------------------------------
bool DataRecv_ERROR = true; // ошибка получения данных от передатчика в погребе

bool WiFi_status = false; // статус WiFi - отключен

bool RemoteSerial = true; //true = Remote and local serial, false = local serial only
const char* host = "R_Debug";  // ArduinoOTA  

// Вводим переменные для  показаний   гараж
  float temp_gar;
  float hum_gar;
  float outAbsH; // абс влажность гараж
// Вводим переменные для хранения входящих показаний
  float incomingTemp = Temp_Heater_ON + 1; // подвал, предварительно устанавливаем температуру  для не вкл. отопителя при старте модуля
//  float incomingTemp; // подвал
  float incomingHum;
  float cellarAbsH;  // абс влажность подвал
  unsigned int Read_id; 
  int32_t rssi_dB; 
  int32_t rssi_dB_WiFi;  
//-----------------------------------  
// принимаемая структура
typedef struct struct_message {  
  int id;
  float temp;
  float hum;
  unsigned int readingId;
  int32_t rssi;
} struct_message;
  struct_message incomingReadings;  //создание структуры incomingReadings
//-----------------------------------  
// инициализация  
  RemoteDebug RSerial;                  // удалённый мониторинг - telnet
  HTU21D myHumidity;                    // датчик температуры и влажности
  LiquidCrystal_I2C lcd(0x27, 16, 2);   // дисплей
  JSONVar board;                        // Переменная JSON с именем board  
  AsyncWebServer server(80);            // Асинхронный веб-сервер, порт:80.
  AsyncEventSource events("/events");   // Источник событий /events
 
// Обработчик событий Wi-Fi
//WiFiEventHandler wifiConnectHandler;    //Connect
WiFiEventHandler wifiDisconnectHandler; //Disconnect

void setup() {
  Serial.begin(115200);             // Запуск Serial Monitor
  RSerial.begin(host);              // удалённый мониторинг - telnet
  RSerial.setSerialEnabled(true);  
//Зарегистрировать обработчики событий Wi-Fi
//  wifiConnectHandler = WiFi.onStationModeGotIP(onWifiConnect);
  wifiDisconnectHandler = WiFi.onStationModeDisconnected(onWifiDisconnect);  
// GPIO как выходы
  pinMode(FANpin, OUTPUT);
  pinMode(HEATERpin, OUTPUT);  
  digitalWrite(FANpin, HIGH);       // выключаем вентилятор
  digitalWrite(HEATERpin, HIGH);    // выключаем отопитель
  myHumidity.begin(); 
  delay (500);   
  initWiFi();

  if ( ENABLE_OTA_UPDATE )
  {
	initOTA();
  }

  lcd.begin();
  lcd.backlight();
  lcd.clear();
  delay(500); 
  lcd.setCursor(0, 0);lcd.print(WiFi.localIP()); 
  lcd.setCursor(0, 1);lcd.print(WiFi.softAPIP()); 	
  // Init ESP-NOW получаем информацию
  initEspNow();  
  getReadings();
  
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/html", index_html);
  });
   
  events.onConnect([](AsyncEventSourceClient *client){
    if(client->lastId()){
      RSerial.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId());
    }
    // отправить событие с сообщением "hello!" и устанавливаем задержку переподключения
  client->send("hello!", NULL, millis(), 10000);
  });
  server.addHandler(&events);
  server.begin();
} // end setup
 
void loop() {

  if ( ENABLE_OTA_UPDATE )
  {
    ArduinoOTA.handle();
  }
  
  static unsigned long lastEventTime = millis();
  if ((millis() - lastEventTime) > EVENT_INTERVAL_MS * 1000) {
    events.send("ping",NULL,millis());
    // lastEventTime = millis();
	
    incomingTemp = incomingReadings.temp; // температура 
    incomingHum = incomingReadings.hum;    // влажность 
    Read_id = incomingReadings.readingId;
    rssi_dB = incomingReadings.rssi;	
    getReadings();
	
   // проверка от ложных показаний датчиков
   if (CalcAbsH(incomingTemp,incomingHum) <100){
	   cellarAbsH = CalcAbsH(incomingTemp,incomingHum); // абсолют. влажность в подвале
   }
   if (CalcAbsH(temp_gar,hum_gar) <100){
	   outAbsH = CalcAbsH(temp_gar,hum_gar); // абсолют. влажность в подвале
   }   
    printIncomingReadings();	             // Отображаем показания в мониторе порта
   // полученный уровень сигнала WiFi dB:
   rssi_dB_WiFi = getWiFidBm(ssid);
   
   // переподключение к нашей сети WiFi после пропадания и потом в момент появления
   if ((getWiFiSSID(ssid) == String (ssid)) and (WiFi_status == false))
      {
       RSerial.print("Connect to: ");
       RSerial.println(WiFi.SSID());	   
       RSerial.println("initWiFi() .. ");
	   initWiFi();
       // RSerial.println("restart .. ");	
       // restart(); 
       } 
       else {
	    RSerial.println("Connect-successfully");   
        }
		
   //обработка датчиков подвала и гаража
   if ( DataRecv_ERROR == false)  // если данные получены управляем вентилятором и отопителем
	  {
	   RSerial.println("DATA RECV.- YES");
       if (incomingTemp < Temp_Heater_ON) //при температуре в подвале менее +1.5 градуса включить отопитель и выкл вентилятор.
	      { digitalWrite(FANpin, HIGH);      // выключаем вентилятор
	    	FANstatus = false; //  реле ВЫКЛ.  - мониторинг
	    	fan_= "OFF";
		    digitalWrite(HEATERpin, LOW);             // включаем отопитель
		    HEATERstatus = true;  //  реле ВКЛ.  - мониторинг
		    hea_= "ON";	           }//end if (incomingTemp < Temp_Heater_ON)
		
	     if (incomingTemp >= Temp_Heater_ON + Temp_Heater_Hysteresis) //выключаем отопитель +1 для гистерезиса вкл/откл отопителя
	      { digitalWrite(HEATERpin, HIGH); 
	        HEATERstatus = false; //  реле ВЫКЛ.  - мониторинг
			hea_= "OFF";	       }//if (incomingTemp >=

         if (temp_gar > Temp_FAN_ON)                      // включить вентилятор при температуре в гараже более +3 градуса
	      {                                                   
         // if ((outAbsH + Temp_FAN_Hysteresis < cellarAbsH) && (incomingTemp > temp_gar)){  // сверяем значение и управляем вентилятором 
           if ((outAbsH + Temp_FAN_Hysteresis < cellarAbsH) and (incomingTemp > temp_gar)){  // сверяем значение и управляем вентилятором 	 
		   digitalWrite(FANpin, LOW);                                          //Вентилятор вкл, когда абсолютная влажность в погребе выше, + гистерезис вкл/откл вентилятора
		   FANstatus = true;   //  реле ВКЛ.  - мониторинг - мониторинг        //и температура в гараже должна быть ниже чем в погребе
	       fan_= "ON";
           } 
		   else 
		     {
		       digitalWrite(FANpin, HIGH);           // выключаем вентилятор
		       FANstatus = false;  //  реле ВЫКЛ.  - мониторинг
		       fan_= "OFF";        }
             }//if (temp_gar > Temp_FAN_ON) 							   
         // if ((outAbsH > cellarAbsH) || (temp_gar > incomingTemp)) {    // абсолютная влажность в гараже выше чем в погребе
         if ((outAbsH > cellarAbsH) or (temp_gar > incomingTemp)) {    // абсолютная влажность в гараже выше чем в погребе	 
		    digitalWrite(FANpin, HIGH);                                 // или температура в гаражее выше чем в погребе;  
		    FANstatus = false;  //  реле ВЫКЛ.  - мониторинг                                       // выключаем вентилятор   
		    fan_= "OFF";              
                                  }//end if ((outAbsH > cellarAbsH)			
   sendLCD();	  // выводим показания на дисплей
   id_WhatsAPP = 0; // обнуляем кол-во неудачных циклов обновления данных после возобновления приёма данных
   
   // Формируем GET запрос
   String httpGet = String(omApiId) + omApiKey;
   // Добавляем значения с сенсоров...здесь нужное количество данных для отправки...
   httpGet = httpGet + omApiField + String(1) + "=" + String(temp_gar,2);  
   httpGet = httpGet + omApiField + String(2) + "=" + String(incomingTemp,2);
   httpGet = httpGet + omApiField + String(3) + "=" + String(hum_gar,2);  
   httpGet = httpGet + omApiField + String(4) + "=" + String(incomingHum,2);
   httpGet = httpGet + omApiField + String(5) + "=" + String(outAbsH,2);  
   httpGet = httpGet + omApiField + String(6) + "=" + String(cellarAbsH,2);
   httpGet = httpGet + omApiField + String(7) + "=" + String(fan_);
   httpGet = httpGet + omApiField + String(8) + "=" + String(FANstatus);
   httpGet = httpGet + omApiField + String(9) + "=" + String(hea_);
   httpGet = httpGet + omApiField + String(10) + "=" + String(HEATERstatus);
   httpGet = httpGet + omApiField + String(11) + "=" + String(Read_id);
   httpGet = httpGet + omApiField + String(12) + "=" + String(rssi_dB); 
   httpGet = httpGet + omApiField + String(13) + "=" + String(rssi_dB_WiFi);
   // Отправляем запрос на сервер
   omPublish(httpGet);
     }//end if ( DataRecv_ERROR == false)	
     else // если ошибка получения данных DataRecv_ERROR=TRUE
      {
      // Отправляем сообщение WhatsAPP	
       if ( id_WhatsAPP == 1 ) { // задержка отправки на один цикл
	       WhatsAPP_ERROR();
           sendMessage("DATA RECEIVER ERROR. FAN/HEATER-OFF");
          }//end if
     id_WhatsAPP = id_WhatsAPP + 1;
     // пропускаем 30 циклов перезагрузка - esp	   
       if ( id_WhatsAPP == 30 ){
         RSerial.println("ESP restart");
		 sendMessage("ESP RESTART");
         delay (5000); 
         restart();			  
         // id_WhatsAPP = 0;
	    } //end if
      }  //end else
		  
  DataRecv_ERROR = true;		 
  lastEventTime = millis();
  }//end if ((millis()
	  
  if (RemoteSerial) RSerial.handle();
  yield();
  
} //end loop


// вычисление абсалютной влажности из относительной и температуры.
// https://carnotcycle.wordpress.com/2012/08/04/how-to-convert-relative-humidity-to-absolute-humidity/
  float CalcAbsH(float t, float h) {  
    double tmp = pow(2.718281828,(17.67*t)/(t+243.5));
    return (6.112*tmp*h*2.1674)/(273.15+t);    // Это граммы воды в одном кубометре воздуха.
//	Serial.println(F("Abs Humidity is "  absHumid " g/m3"));
}  

// мониторинг данных на LCD и Serial подвал - гараж
void sendLCD(){      
  lcd.clear(); 
  lcd.setCursor(0, 0);lcd.print(temp_gar, 1); lcd.print("/"); lcd.print(hum_gar, 0); lcd.print("="); lcd.print(outAbsH, 0);
  lcd.print(" F:"); lcd.print(fan_);
  lcd.setCursor(0, 1);lcd.print(incomingTemp, 1); lcd.print("/"); lcd.print(incomingHum, 0); lcd.print("="); lcd.print(cellarAbsH, 0);
  lcd.print(" H:"); lcd.print(hea_);  

  RSerial.print(temp_gar, 1); RSerial.print("/"); RSerial.print(hum_gar, 0); RSerial.print("="); RSerial.print(outAbsH, 0);
  RSerial.print(" F:"); RSerial.println(fan_);
  RSerial.print(incomingTemp, 1); RSerial.print("/"); RSerial.print(incomingHum, 0); RSerial.print("="); RSerial.print(cellarAbsH, 0);
  RSerial.print(" H:"); RSerial.println(hea_);
} 
 
// функция  будет выполняться при получении данных от клиента в подвале 
void OnDataRecv(uint8_t * mac_addr,uint8_t *incomingData, uint8_t len)            //ESP8266
 { 
  // Копирует mac-адрес отправителя в строку
  char macStr[18];
  RSerial.print("Packet received from: ");
  snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x",
  mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
  RSerial.println(macStr);
  memcpy(&incomingReadings, incomingData, sizeof(incomingReadings));
  DataRecv_ERROR = false; //данных приняты успешно
  board["id"] = incomingReadings.id;
  board["temperature"] = incomingReadings.temp;
  board["humidity"] = incomingReadings.hum;
  board["readingId"] = String(incomingReadings.readingId);
  board["tempGAR"] = String(temp_gar);  
  board["humGAR"] = String(hum_gar);
  board["outAbsH"] = String(outAbsH);
  board["cellarAbsH"] = String(cellarAbsH); 
  board["fan1"] = String(fan_);
  board["hea1"] = String(hea_); 
  board["rssi1"] = String(incomingReadings.rssi);
  board["rssi2"] = String(rssi_dB_WiFi);
  String jsonString = JSON.stringify(board);
  events.send(jsonString.c_str(), "new_readings", millis());
} 


// https://kotyara12.ru/iot/open-monitoring/
// Функция отправки данных на сервер https://open-monitoring.online
bool omPublish(const String request)
{ 
  WiFiClient client;
  bool result = false;
  if (client.connect(omApiServer, 80)) {  // Отправляем HTTP-запрос
    client.printf(omApiPost, request.c_str());
    client.printf(omApiHost, omApiServer);
    client.println(F("User-Agent: ESP8266 (nothans)/1.0"));
    client.println(F("Connection: close"));
    client.println();
    // Читаем результат (первую строку отправленного заголовка)
    RSerial.printf("OpenMon :: send data \"%s\": %s\n", request.c_str(), client.readStringUntil('\n'));
    result = true;
  }
  else {
    RSerial.print(F("OpenMon :: Failed connection to "));
    RSerial.println(omApiServer);
  };
  return result;
}

//Отправить сообщение WhatsApp // https://randomnerdtutorials.com/esp8266-nodemcu-send-messages-whatsapp/
 void sendMessage(String message){ 
  // URL для запроса с номером телефона, ключом API и сообщением
  String url = "http://api.callmebot.com/whatsapp.php?phone=" + phoneNumber + "&apikey=" + apiKey + "&text=" + urlEncode(message);
  WiFiClient clientWhatsApp;    
  HTTPClient http;
  http.begin(clientWhatsApp, url);
  http.addHeader("Content-Type", "application/x-www-form-urlencoded"); // тип контента
  int httpResponseCode = http.POST(url); // код ответа 200,  запрос прошел успешно
  if (httpResponseCode == 200){
    RSerial.println("Message sent successfully");
  }
  else{
    RSerial.println("Error sending the message");
    RSerial.print("HTTP response code: ");
    RSerial.println(httpResponseCode);
  }
  http.end(); // освободить ресурсы
}


// Отображаем показания в мониторе порта
void printIncomingReadings(){  
    RSerial.print("POD Reading ID: "); RSerial.println(Read_id); 
    RSerial.print("GAR temperature: "); RSerial.print(temp_gar); RSerial.println(" C°");
    RSerial.print("GAR humidity: ");  RSerial.print(hum_gar);  RSerial.println(" C°");
    RSerial.print("GAR outAbsH: "); RSerial.print(outAbsH); RSerial.println(" %");
    RSerial.print("POD temperature: ");  RSerial.print(incomingTemp);  RSerial.println(" %");
    RSerial.print("POD humidity: "); RSerial.print(incomingHum); RSerial.println(" %");
	RSerial.print("POD cellarAbsH: ");  RSerial.print(cellarAbsH);  RSerial.println(" %"); 
    RSerial.print("WiFi_AP: ");  RSerial.print(rssi_dB);  RSerial.println(" dB");   RSerial.println(" ............................."); 
}

// Снимаем значение температуры и влажности
void getReadings(){
	delay(1000);
    temp_gar = myHumidity.readTemperature(); 
    hum_gar = myHumidity.readHumidity();
}

// инициализация EspNow
void initEspNow() {    // Initialize EspNow
    if (esp_now_init() != 0) {
        RSerial.println("ESP NOW failed to initialize");
		return;
    }
    esp_now_register_recv_cb(OnDataRecv);
}
// определяем уровень WiFi
int32_t getWiFidBm(const char *ssid) {
  if (int32_t n = WiFi.scanNetworks()) {
      for (uint8_t i=0; i<n; i++) {
		  RSerial.print(WiFi.SSID(i));
		  RSerial.printf(" STA_RSSI: %d dBm\n", WiFi.RSSI(i));
//		  RSerial.println(WiFi.localIP());
          return WiFi.RSSI(i);
      }
  }
  return 0;
}

// определяем наличие определённой сети WiFi
String getWiFiSSID(const char *ssid) {
  if (int32_t n = WiFi.scanNetworks()) {
      for (uint8_t i=0; i<n; i++) {
		  RSerial.print(WiFi.SSID(i)); RSerial.print(" IP: ");
		  RSerial.println(WiFi.localIP());
		  if (WiFi.SSID(i) == ssid){
          return WiFi.SSID(i);
		  }
      }
  }
  return String(".");
}

// устанавливаем соеденение WiFi 
void initWiFi() {
  //ESP - станция и точка доступа одновременно
  WiFi.mode(WIFI_AP_STA);
  WiFi.softAP(ssidAP, passAP);
  if (!WiFi.config(local_IP, gateway, subnet, primaryDNS, secondaryDNS)) {
      RSerial.println("STA Failed to configure");} //  static IP address  
  // Установить ESP в качестве станции Wi-Fi
  WiFi.begin(ssid, password);
  /* Если persistent - false,то SSID и пароль будут записаны на flash-память только в том случае, 
  если новые значения не будут соответствовать тем, что хранятся во flash-памяти. */   
  WiFi.persistent(false);
  //  WiFi.setAutoConnect(true);
  WiFi.setAutoReconnect(true);//Если параметр autoReconnect выставлен на true, модуль сделает попытку повторного подключения к точке доступа, а если false, то нет.  
  RSerial.println("._.");  
  // проверка WiFi
  byte a = 10; // выполнится 10 раз
  while (a--) { 
    delay(6000); // нет WiFi через минуту работаем без него
    //  RSerial.print("_.");  
    if (WiFi.status() != WL_CONNECTED) {
     RSerial.println("Connection failed..");
    }  
    else
      {
        RSerial.println("Connected to Wi-Fi sucessfully.");
    	RSerial.println("Setting as a Wi-Fi Station..");
        RSerial.print("Station IP Address: "); // Данные  Wi-Fi
        RSerial.println(WiFi.localIP());
        RSerial.print("AP IP Address: ");
        RSerial.println(WiFi.softAPIP());  
        RSerial.print("Wi-Fi Channel: ");
        RSerial.println(WiFi.channel());
        RSerial.print("ESP Board MAC Address:  ");
        RSerial.println(WiFi.macAddress());
        RSerial.print("ESP Board gatewayIP:  ");
        RSerial.println(WiFi.gatewayIP());
        RSerial.print("ESP Board subnetMask:  ");
        RSerial.println(WiFi.subnetMask());
        RSerial.print("ESP Board dnsIP:  ");
        RSerial.println(WiFi.dnsIP());
	    WiFi_status = true;
        break;	  
      }
   }
}

/* ESP8266 получает IP-адрес
void onWifiConnect(const WiFiEventStationModeGotIP& event) {
  RSerial.println("Connected to Wi-Fi sucessfully.");
  RSerial.print("IP address: ");
  RSerial.println(WiFi.localIP());
}
*/
// ESP8266 не подключен к точке доступа.
void onWifiDisconnect(const WiFiEventStationModeDisconnected& event) {
  RSerial.println("Disconnected from Wi-Fi, trying to connect...");
  WiFi_status = false;
  WiFi.disconnect(); 
}

// нет получения данных от клиента в подвале
void WhatsAPP_ERROR() {
  digitalWrite(FANpin, HIGH);      // выключаем вентилятор
  FANstatus = false; //  реле ВЫКЛ.  - мониторинг
  fan_= "OFF";
  digitalWrite(HEATERpin, HIGH);   //выключаем отопитель
  HEATERstatus = false;  //  реле ВЫКЛ.  - мониторинг
  hea_= "OFF";	
  lcd.clear(); 
  lcd.setCursor(0, 0);lcd.print("FAN/HEATER-OFF"); // выводим показания на дисплей
  lcd.setCursor(0, 1);lcd.print("DATA RECV. ERROR"); 		
  RSerial.println("FAN/HEATER-OFF"); 
  RSerial.println("DATA RECV. ERROR"); 
}

// перезапуск ESP8266
void restart()
{
  RSerial.println("Restart ESP...");
  ESP.restart();
}

void initOTA()
{
	ArduinoOTA.setHostname("Esp8266_Gar");  
    RSerial.println("Begin OTA handler Esp8266_Gar");
    // ArduinoOTA.setPassword("admin");
    ArduinoOTA.onStart([]() {
      String type;
      if (ArduinoOTA.getCommand() == U_FLASH)
        type = "sketch";
      else // U_SPIFFS
        type = "filesystem";
      // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
      RSerial.println("Start updating " + type);
    });
    ArduinoOTA.onEnd([]() {
      RSerial.println("\nEnd");
    });
    ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
      RSerial.printf("Progress: %u%%\r", (progress / (total / 100)));
    });
    ArduinoOTA.onError([](ota_error_t error) {
      RSerial.printf("Error[%u]: ", error);
      if (error == OTA_AUTH_ERROR)         RSerial.println("Auth Failed");
      else if (error == OTA_BEGIN_ERROR)   RSerial.println("Begin Failed");
      else if (error == OTA_CONNECT_ERROR) RSerial.println("Connect Failed");
      else if (error == OTA_RECEIVE_ERROR) RSerial.println("Receive Failed");
      else if (error == OTA_END_ERROR)     RSerial.println("End Failed");
    });
    ArduinoOTA.begin();
}


/*
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/esp8266-esp-now-wi-fi-web-server/
 // https://randomnerdtutorials.com/esp8266-nodemcu-send-messages-whatsapp/ 
  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files.
  
  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.
*/

html.h

// https://randomnerdtutorials.com/esp8266-esp-now-wi-fi-web-server/
//Веб - страница       

const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
  <title>ESP Метеостанция</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta charset='UTF-8'>   
  <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
  <link rel="icon" href="data:,">
  <style>
    html {font-family: Arial; display: inline-block; text-align: center;}
    p { font-size: 1rem;}
    body {  margin: 0;}
    .topnav { overflow: hidden; background-color: #2f4468; color: white; font-size: 0.6rem; }
    .content { padding: 20px; }
    .card { background-color: white; box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5); }
    .cards { max-width: 800px; margin: 0 auto; display: grid; grid-gap: 2rem; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }
    .reading { font-size: 1.4rem; }
    .packet { color: #bebebe; }
    .card.temperature { color: #fd7e14; }
    .card.humidity { color: #1b78e2; }
	.card.abc_humidity { color: #059e8a; }
  </style>
</head>
<body>
  <div class="topnav">
    <h1>Метео данные</h1>
  </div>
  <div class="content">
    <div class="cards">
      <div class="card temperature">
        <h4><i class="fas fa-thermometer-half"></i> Температура гараж</h4><p><span class="reading"><span id="t2"></span> &deg;C</span></p>
      </div>
      <div class="card humidity">
        <h4><i class="fas fa-tint"></i> Влажность гараж</h4><p><span class="reading"><span id="h2"></span> &percnt;</span></p>
      </div>
      <div class="card abc_humidity">
        <h4><i class="fas fa-tint"></i> Абс. влажность гараж</h4><p><span class="reading"><span id="ha2"></span> &percnt;</span></p>
      </div>	
      <div class="card temperature">
        <h4><i class="fas fa-thermometer-half"></i> Температура подвал</h4><p><span class="reading"><span id="t1"></span> &deg;C</span></p>
      </div>
      <div class="card humidity">
        <h4><i class="fas fa-tint"></i> Влажность подвал</h4><p><span class="reading"><span id="h1"></span> &percnt;</span></p>
      </div>
      <div class="card abc_humidity">
        <h4><i class="fas fa-tint"></i> Абс. влажность подвал</h4><p><span class="reading"><span id="ha1"></span> &percnt;</span></p>
      </div>	  
      <div class="card readingId">
        <h4><p class="packet">Вентилятор:  <span id="fan"></span></p><p class="packet">Отопитель:  <span id="hea"></span></p>
      </div>  
      <div class="card readingId">
        <h4><p class="packet">Дата: <span id="rt2"></span></p><p class="packet">DataID: <span id="rh2"></span></p>
      </div>
      <div class="card readingId">
        <h4><p class="packet">WiFi-STA: <span id="rssi_"></span> дБм</span></p><p class="packet">WiFi-AP:  <span id="rssi"></span> дБм</span></p>
      </div>
    </div>
  </div>
<script>
function getDateTime() {
  var currentdate = new Date();
  var datetime = currentdate.getDate() + "."
  + (currentdate.getMonth()+1) + "."
  + currentdate.getFullYear() + " - "
  + currentdate.getHours() + ":"
  + currentdate.getMinutes() + ":"
  + currentdate.getSeconds();
  return datetime;
}
if (!!window.EventSource) {
 var source = new EventSource('/events');
 
 source.addEventListener('open', function(e) {    // прослушиватели событий по умолчанию
  console.log("Events Connected");
 }, false);
 source.addEventListener('error', function(e) {
  if (e.target.readyState != EventSource.OPEN) {
    console.log("Events Disconnected");
  }
 }, false);
 
 source.addEventListener('message', function(e) {
  console.log("message", e.data);
 }, false);
 
 source.addEventListener('new_readings', function(e) {  //прослушиватель событий 
  console.log("new_readings", e.data);
  var obj = JSON.parse(e.data);
  document.getElementById("t1").innerHTML = obj.temperature.toFixed(2);
  document.getElementById("h1").innerHTML = obj.humidity.toFixed(2);  
  document.getElementById("t2").innerHTML = obj.tempGAR; 
  document.getElementById("h2").innerHTML = obj.humGAR;  
  document.getElementById("ha1").innerHTML = obj.cellarAbsH;
  document.getElementById("ha2").innerHTML = obj.outAbsH;
  document.getElementById("rh2").innerHTML = obj.readingId;  
  document.getElementById("rt2").innerHTML = getDateTime(); 
  document.getElementById("fan").innerHTML = obj.fan1;
  document.getElementById("hea").innerHTML = obj.hea1;  
  document.getElementById("rssi").innerHTML = obj.rssi1;
  document.getElementById("rssi_").innerHTML = obj.rssi2;
 }, false);
}
</script>
</body>
</html>)rawliteral";

/*
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/esp8266-esp-now-wi-fi-web-server/
  
  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files.
  
  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.
*/

settings.h
Нет возможности дальше опубликовать код из-за ограничения сайта для новичков, попытаюсь прикрепить весь проект архивом.
Подскажите как прирепить сюда архив?

Пока только так, больше вариантов как добавить на сайт не нашёл.

3 лайка

Добрый день перезалейте архив подалуйста

не получается залить
Arduino: 1.8.1 (Windows 10), Плата:“NodeMCU 1.0 (ESP-12E Module), 80 MHz, Serial, 115200, 4M (3M SPIFFS)”

C:\Users\wowka\OneDrive\Desktop\arduino-1.8.1\libraries\ESPAsyncWebServer-master\src\AsyncWebSocket.cpp:24:28: fatal error: libb64/cencode.h: No such file or directory

#include <libb64/cencode.h>

                        ^

compilation terminated.

Несколько библиотек найдено для “ESPAsyncTCP.h”
Используется: C:\Users\wowka\OneDrive\Документы\Arduino\libraries\ESPAsyncTCP
Не используется: C:\Users\wowka\OneDrive\Desktop\arduino-1.8.1\libraries\ESPAsyncTCP-master
exit status 1
Ошибка компиляции для платы NodeMCU 1.0 (ESP-12E Module).

Этот отчёт будет иметь больше информации с
включенной опцией Файл → Настройки →
“Показать подробный вывод во время компиляции”

Спасибо, что предупредили.

Используем библиотеку ESPAsyncTCP-master версии 1.2.2
Используем библиотеку ESPAsyncWebServer-master версии 1.2.3

1 лайк

Arduino: 1.8.1 (Windows 10), Плата:“NodeMCU 1.0 (ESP-12E Module), 80 MHz, Serial, 115200, 4M (3M SPIFFS)”

Изменены опции сборки, пересобираем все
C:\Users\wowka\OneDrive\Desktop\arduino-1.8.1\libraries\ESPAsyncWebSrv-1.2.3\src\AsyncWebSocket.cpp:24:28: fatal error: libb64/cencode.h: No such file or directory

#include <libb64/cencode.h>

                        ^

compilation terminated.

exit status 1
Ошибка компиляции для платы NodeMCU 1.0 (ESP-12E Module).

Этот отчёт будет иметь больше информации с
включенной опцией Файл → Настройки →
“Показать подробный вывод во время компиляции”

попробуй в 1.8.19

1 лайк

то же самое, поделитесь библиотекой Используем библиотеку ESPAsyncWebServer-master версии 1.2.3

скорее всего дело не в библиотеке, а в ядре ESP8266, под 3.1.1 компилируется

ПРЕДУПРЕЖДЕНИЕ: библиотека LiquidCrystal_I2C-master должна запускаться на архитектурах avr и может быть несовместима с вашей платой на архитектуре esp8266.
. Variables and constants in RAM (global, static), used 35688 / 80192 bytes (44%)
║   SEGMENT  BYTES    DESCRIPTION
╠══ DATA     1572     initialized variables
╠══ RODATA   6700     constants       
╚══ BSS      27416    zeroed variables
. Instruction RAM (IRAM_ATTR, ICACHE_RAM_ATTR), used 61668 / 65536 bytes (94%)
║   SEGMENT  BYTES    DESCRIPTION
╠══ ICACHE   32768    reserved space for flash instruction cache
╚══ IRAM     28900    code in IRAM    
. Code in flash (default, ICACHE_FLASH_ATTR), used 357916 / 1048576 bytes (34%)
║   SEGMENT  BYTES    DESCRIPTION
╚══ IROM     357916   code in flash   

// 04.04.2023
// https://randomnerdtutorials.com/esp8266-esp-now-wi-fi-web-server/

#include <espnow.h>  
#include <ESP8266WiFi.h>
#include <ESPAsyncWebServer.h> // https://codeload.github.com/bigbrodude6119/ESPAsyncWebServer/zip/refs/heads/master
                               // https://codeload.github.com/me-no-dev/AsyncTCP/zip/refs/heads/master 
#include <Arduino_JSON.h>      // version=0.2.0 https://codeload.github.com/arduino-libraries/Arduino_JSON/zip/refs/heads/master
#include <SparkFunHTU21D.h>    // version=1.1.3  https://codeload.github.com/sparkfun/SparkFun_HTU21D_Breakout_Arduino_Library/zip/refs/heads/master
#include <LiquidCrystal_I2C.h>
#include <ESP8266mDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>        // version=1.0
#include <ESP8266HTTPClient.h>
#include <WiFiClient.h>
#include <UrlEncode.h>         // version=1.0.1 https://www.arduino.cc/reference/en/libraries/urlencode/
#include <RemoteDebug.h>       // version=2.1.2 https://www.arduino.cc/reference/en/libraries/remotedebug/
#include "html.h"      // веб-страница
#include "settings.h"  // Настройки - ввести свои данные!!!

что нужно сделать?

на 3.1.1 вот, что
Arduino: 1.8.19 (Windows 10), Плата:“NodeMCU 0.9 (ESP-12 Module), 80 MHz, Flash, Disabled (new aborts on oom), Disabled, All SSL ciphers (most compatible), 32KB cache + 32KB IRAM (balanced), Use pgm_read macros for IRAM/PROGMEM, 4MB (FS:none OTA:~1019KB), v2 Lower Memory, Disabled, None, Only Sketch, 115200”

Using preferences from ‘C:\Users\wowka\AppData\local\Arduino15\preferences.txt’

*** Key ‘compiler.cache_core’ not found in file preferences.txt. Default to true.

Clean build, created dir core

Note: optional global include file ‘C:\Users\wowka\OneDrive\Desktop\ESP_Gar_server\ESP_Gar_server.ino.globals.h’ does not exist.

Read more at How to specify global build defines and options — ESP8266 Arduino Core 3.1.2-21-ga348833 documentation

ПРЕДУПРЕЖДЕНИЕ: библиотека LiquidCrystal_I2C должна запускаться на архитектурах avr и может быть несовместима с вашей платой на архитектуре esp8266.

C:\Users\wowka\OneDrive\Desktop\ESP_Gar_server\ESP_Gar_server.ino: In function ‘void setup()’:

ESP_Gar_server:99:13: error: no matching function for call to ‘LiquidCrystal_I2C::begin()’

99 | lcd.begin();

  |             ^

In file included from C:\Users\wowka\OneDrive\Desktop\ESP_Gar_server\ESP_Gar_server.ino:9:

C:\Users\wowka\OneDrive\���������\Arduino\libraries\LiquidCrystal_I2C/LiquidCrystal_I2C.h:58:8: note: candidate: ‘void LiquidCrystal_I2C::begin(uint8_t, uint8_t, uint8_t)’

58 | void begin(uint8_t cols, uint8_t rows, uint8_t charsize = LCD_5x8DOTS );

  |        ^~~~~

C:\Users\wowka\OneDrive\���������\Arduino\libraries\LiquidCrystal_I2C/LiquidCrystal_I2C.h:58:8: note: candidate expects 3 arguments, 0 provided

exit status 1

no matching function for call to ‘LiquidCrystal_I2C::begin()’

Этот отчёт будет иметь больше информации с
включенной опцией Файл → Настройки →
“Показать подробный вывод во время компиляции”

Пробовал так сделать?

в библиотеке нет функции begin(), замени на init к примеру или смотри какие параметры надо добавить в begin()

сделал,а зачем?

помогло, компилирует, но в лату не заливает, пишет ошибка

Это чтобы при возникновении проблем был более развернутый ответ от компилятора и нам было бы проще решать твою проблему.

Arduino: 1.8.19 (Windows 10), Плата:“NodeMCU 0.9 (ESP-12 Module), 80 MHz, Flash, Disabled (new aborts on oom), Disabled, All SSL ciphers (most compatible), 32KB cache + 32KB IRAM (balanced), Use pgm_read macros for IRAM/PROGMEM, 4MB (FS:2MB OTA:~1019KB), v2 Lower Memory, Disabled, None, Only Sketch, 115200”

Using preferences from ‘C:\Users\wowka\AppData\local\Arduino15\preferences.txt’

*** Key ‘compiler.cache_core’ not found in file preferences.txt. Default to true.

Note: optional global include file ‘C:\Users\wowka\OneDrive\Desktop\ESP_Gar_server\ESP_Gar_server.ino.globals.h’ does not exist.

Read more at How to specify global build defines and options — ESP8266 Arduino Core 3.1.2-21-ga348833 documentation

ПРЕДУПРЕЖДЕНИЕ: библиотека LiquidCrystal_I2C должна запускаться на архитектурах avr и может быть несовместима с вашей платой на архитектуре esp8266.

. Variables and constants in RAM (global, static), used 35680 / 80192 bytes (44%)

║ SEGMENT BYTES DESCRIPTION

╠══ DATA 1572 initialized variables

╠══ RODATA 6700 constants

╚══ BSS 27408 zeroed variables

. Instruction RAM (IRAM_ATTR, ICACHE_RAM_ATTR), used 61668 / 65536 bytes (94%)

║ SEGMENT BYTES DESCRIPTION

╠══ ICACHE 32768 reserved space for flash instruction cache

╚══ IRAM 28900 code in IRAM

. Code in flash (default, ICACHE_FLASH_ATTR), used 357868 / 1048576 bytes (34%)

║ SEGMENT BYTES DESCRIPTION

╚══ IROM 357868 code in flash

esptool.py v3.0

Serial port COM7

Произошла ошибка при загрузке скетча

Этот отчёт будет иметь больше информации с
включенной опцией Файл → Настройки →
“Показать подробный вывод во время компиляции”

Так сделал или нет?

Да, сюда выложить не могу, большой, Компиляцию теперь проходит. Ошибка при загрузке