Контроль влажности подвала 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
Нет возможности дальше опубликовать код из-за ограничения сайта для новичков, попытаюсь прикрепить весь проект архивом.
Подскажите как прирепить сюда архив?

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

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

не получается залить
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

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

то же самое, поделитесь библиотекой Используем библиотеку 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

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

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

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

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