WiFi-Thermostat-Projekt-Repository mit Maschinencodes für die Zielplattform ESP8266 und ESP32: Github repo
Die Firmware WiFi_Thermostat und WiFi_Thermostat_mDNS ist in englischer Sprache für die Mikrocontroller ESP32 und ESP8266 (Espressif Systems) verfügbar. Die experimentelle Version der Basis-Firmware mit manueller Ausgabesteuerung ist nur in slowakischer Sprache verfügbar.
Unterstützen Sie das WiFi-Thermostat-Projekt über PayPal. Der Support ermöglicht es Ihnen, in Zukunft neue Funktionen hinzuzufügen und den Quellcode der Anwendung zu öffnen: PayPal donate
ESP8266 ESP32 WiFi DS18B20 OneWire Dallas HTML Webserver WebSocket JSON mDNS UART

WiFi Thermostat - ESP8266 / ESP32


Die Softwareimplementierung ist speziell für Mikrocontroller-Plattformen von Espressif Systems mit WiFi-Konnektivität konzipiert. Sie unterstützt WiFi (2,4 GHz) auf ESP8266- und ESP32-Mikrocontrollern. Der WiFi-Thermostat ist über das LAN-Netzwerk zugänglich und mit einer Weboberfläche ausgestattet. Diese dient der Konfiguration aller Thermostatelemente und ermöglicht gleichzeitig die Visualisierung der aktuellen Zustände. Der Thermostat steuert den Kessel basierend auf der gemessenen Solltemperatur und einer Hysterese. Diese Regelung erfolgt unabhängig von der Webapplikation, die für die Konfiguration und Entscheidungsschwelle des Thermostats dient. Zusätzlich zur Verfügbarkeit des Thermostats unter einer bestimmten IP-Adresse kann der Thermostat durch einen mDNS-Record ergänzt werden. Dieser generiert eine lokale Domain (hostname.local) innerhalb des Multicast-Dienstes, die nur im LAN-Netzwerk erreichbar ist. Die Konfiguration des Thermostats im Heim-WLAN-LAN-Netzwerk wird durch den WiFiManager gesichert. Dieser speichert die WLAN-Netzwerkdaten (SSID und Passwort) im Flash-Speicher des Mikrocontrollers. Die Konfiguration wird einmalig durchgeführt, und das Gerät merkt sich die Einstellungen dauerhaft. Nach erfolgreicher Konnektivität (Zuweisung einer IP-Adresse durch den Router des WLAN-Heimnetzwerks) ist der Thermostat vollständig einsatzbereit. Der HTTP-Webserver auf dem ESP8266/ESP32-Mikrocontroller ermöglicht das Ausführen mehrerer unabhängiger HTML-Seiten. Diese können informativ oder sogar funktional gestaltet sein.

In Bezug auf die Hardware verwendet das Projekt:
  • ESP8266 / ESP32
  • Temperatursensor DS18B20 am OneWire-Bus
  • Relais SRD-5VDC-SL-C / OMRON G3MB-202P zur Kesselschaltung (Active-LOW-Signal)

  • HTML-Seiten mit ESP8266 / ESP32:
  • / - Grundseite mit Formular, aktueller Logikausgang für das Relais, aktuelle und Solltemperatur, Hysterese
  • /action.html - verarbeitet Werte aus dem Formular, schreibt sie in den emulierten EEPROM-Speicher, leitet den Benutzer zurück zur Stammseite
  • /get_data.json - verteilt Daten zur aktuellen Temperatur, Referenztemperatur und Hysterese an Dritte (Computer, Mikrocontroller, anderer Client ...) im JSON-Format - kann mit dem Beispiel verwendet werden JSON-Client, der Daten an MQTT Broker senden kann, zum Beispiel an die Hausautomation

  • Erweitert mit HTML-Seiten in einer experimentellen Version mit manuellem Modus, falls vorhanden:
  • /on.html - Ausgabe im manuellen Modus dauerhaft einschalten
  • /vyp.html - Ausgabe im manuellen Modus dauerhaft deaktivieren
  • /automat.html - Modus auf Automatik ändern (verwendet Hysterese und Zieltemperatur)
  • /manual.html - Wechseln Sie in den manuellen Modus (permanente EIN / AUS-Hardsteuerung)

  • Die Auflösung des DS18B20-Sensors beträgt während der Messung 12 Bit, was eine Temperaturauflösung von 0,0625 °C ergibt, was gleichzeitig der minimale Schritt zwischen den verschiedenen Messwerten ist. Daten über den OneWire-Bus können bei Bedarf in 500 bis 1000 ms an den Mikrocontroller zurückgesendet werden, abhängig von der Anzahl der Sensoren und der Buslänge. Das im Projekt verwendete elektromagnetische Relais SRD-5VDC-SL-C ermöglicht das Schalten bis zu 10A bei 230V, was einer Leistung von 2300W entspricht. Beim Schalten eines Gleichstromkreises (Last) können 300W (10A bei 30V DC) geschaltet werden. Als Alternative ist das OMRON G3MB-202P SSR-Relais voll kompatibel zum Schaltplan, jedoch nur für nicht induktive Lasten und ausschließlich für Wechselstromkreise geeignet, mit einer maximalen Schaltleistung von 460W (230V, 2A). Der Thermostat ist das ganze Jahr über einsatzbereit. Im Falle einer unnötigen Steuerung besteht die Möglichkeit, den Ausgang physisch zu trennen und den Thermostat als WLAN-Thermometer zu verwenden, um Daten aus dem Raum zu erhalten, in dem er sich befindet.


    Bei einer Firmware mit Unterstützung für die manuelle Steuerung des GPIO-Ausgangs des ESP-Mikrocontrollers kann der Thermostat physisch ausgeschaltet werden, ohne ihn von der Relaisklemme trennen zu müssen. Die Thermostatlogik wird unabhängig vom Webserver und den angeschlossenen Clients alle 10 Sekunden ausgeführt, und es ist keine Keep-Alive-Verbindung erforderlich. Bei jeder Ausführung der Logik schreibt der Thermostat Informationen über die aktuelle IP-Adresse oder den mDNS-Record (bei Verwendung mit mDNS-Firmware) auf den UART-Thermostat. Dadurch erhält der Benutzer Auskunft darüber, wo der Thermostat mit seinem Webinterface im LAN-Netzwerk erreichbar ist. Zusätzlich listet er den dynamischen freien Speicher (HEAP), der von 40 bis 44 kB reicht, sowie den aktuellen Status der Ausgabe auf, mit Benachrichtigung über Änderungen, falls sie auftreten (z. B. wenn die Entscheidungsschwelle überschritten wird). Die 3V3-GPIO-Betriebslogik der ESP8266- und ESP32-Mikrocontroller reicht für ein digitales Änderungssignal aus, aber das Relais muss mit 5 V von VUSB bzw. VIN versorgt werden.

    Die Webschnittstelle des WiFi-Thermostats ermöglicht Ihnen:
  • Sehen Sie in Echtzeit die Temperatur des DS18B20-Sensors, die Betriebszeit des Geräts, den Ausgabewert mit dynamischer Änderung, die aktuell eingestellten Konfigurationsdaten für den Thermostat, d. Solltemperatur und Hysterese
  • Ändern Sie die Ziel-(Referenz-)Temperatur im Bereich von 5 bis 50 °C in Schritten von 0,25 °C
  • Ändern Hysterese von 0 bis 10 ° C in Schritten von 0,25 ° C
  • Die Programmimplementierung eines Thermostats mit automatischer und manueller Leistungsregelung ist experimentell!
    Kessel EIN / AUS-Steuerung - Automatikmodus:
  • Beispiel für EIN / AUS der Heizungssteuerung - VISUALISIERUNG IST NICHT TEIL DES PROJEKTS
  • Der Kessel ist aktiv, solange die Solltemperatur + Hysterese erreicht ist
  • Die Visualisierung von Wassertemperaturen zeigt die sogenannte Heizlauf und anschließendes Abkühlen des Wassers bis die Heizung wieder aktiviert wird, wenn die gemessene Temperatur unter der eingestellten Solltemperatur liegt - Hysterese
  • ZAP/VYP regulácia kotla s hysterézou

    In der Basisversion des WiFi-Thermostats (ohne mDNS-Aufzeichnung) wurde ein manueller Steuermodus (hart EIN / AUS) implementiert mit der Möglichkeit zwischen manuellem und automatischem Modus umzuschalten. Die Weboberfläche ist für größere und kleinere Bildschirme ausgelegt. Es ist reaktionsschnell und unterstützt hochauflösende Breitbild-Bildschirme sowie mobile Geräte. Die Schnittstelle verwendet importierte CSS-Stile des Bootstrap-Frameworks von einem externen CDN-Server, der das clientseitige Gerät lädt, wenn eine Seite geöffnet wird, die auf ESP ausgeführt wird. Durch den Import von CSS-Stilen von einem externen Server wird die Strom- und Speicherbelastung des Mikrocontrollers reduziert.


    Um die eingestellten Werte des Thermostats auch nach einem Stromausfall zu bewahren, werden diese im EEPROM-Speicher des ESP gespeichert, der im Flash-Speicher emuliert wird, da die Plattform keinen physischen EEPROM-Chip besitzt. Die Referenztemperatur wird am Offset 10 gespeichert, die Hysterese am Offset 100. Jeder der Werte belegt maximal 5 Bytes im EEPROM-Speicher, zusätzlich zum Endzeichen. Die Daten werden erst beim Senden des HTML-Formulars überschrieben, und der Betrieb des Thermostats schont den EEPROM-Speicher für maximale Haltbarkeit. Der Zustand des Ausgangs existiert nur im RAM-Speicher, wo er bei Änderungen überschrieben wird. Der Wert wird nicht im emulierten EEPROM-Speicher im Flash-Speicher abgelegt.


    Wenn das Gerät beim ersten Start keine Daten auf den genannten EEPROM-Offsets gespeichert hat, erfolgt automatisch ein Schreiben mit Standardwerten: Referenztemperatur 20,25 °C und Hysterese 0,00 °C. Dadurch wird sichergestellt, dass der Thermostat auf Mikrocontrollern arbeiten kann, die keine vorherigen Werte im EEPROM-Speicher gespeichert haben - eine ausfallsichere Lösung. ESP8266 und ESP32 verwenden die Funktion EEPROM.put() zum Schreiben in den EEPROM-Speicher, der jeden Datentyp unterstützt. Um das Schreiben in den Ziel-Offset zu bestätigen, wird EEPROM.commit() verwendet. Die Implementierung verwendet den Datentyp float() für eine 32-Bit-Zahl, die im EEPROM gespeichert wird und zur Referenz-(Soll-)Temperatur sowie zur Hysterese gehört. Mit dem Refresh-Meta-Tag aktualisiert der Webserver alle 30 Sekunden die gesamte HTML-Seite, und die JavaScript-Zeit bis zum Refresh wird ebenfalls per JavaScript in die HTML-Seite geschrieben. Zu diesem Zeitpunkt ist es notwendig, die Änderung für den Thermostaten zu schreiben, da sonst die Eingabefenster für numerische Eingaben im Formular beim Aktualisieren der Seite zurückgesetzt werden. Da die eingebaute Ethernet-Bibliothek die Verwendung eines asynchronen Webservers nicht beinhaltet (der beispielsweise mit den Espressif ESP8266/ESP32-Mikrocontrollern verwendet werden kann), ist es erforderlich, die gesamte Seite neu zu schreiben, da diese Implementierung 1:1 mit dem originalen Ethernet-Thermostat ist.


    Die sich hauptsächlich ändernden dynamischen Daten betreffen den aktuellen Wert der Ausgabe - EIN / AUS, der dem Bediener den aktuellen Zustand der Ausgabe zusammen mit der Farbcodierung zeigt. Es ist zu beachten, dass die Systemlogik unabhängig vom Webserver arbeitet, daher kann sich die Ausgabe bereits in einem anderen Zustand befinden als in der Webanwendung aufgeführt. Eine Änderung der Ausgabe wird beispielsweise sofort auf den UART-Monitor geschrieben. Auf der Website des Thermostats findet der Benutzer auch Informationen über die Betriebszeit des Geräts, wie lange es bereits in Betrieb war, angegeben in Tagen, Stunden, Minuten und Sekunden. Der Thermostat ist ausschließlich für Innentemperaturen gedacht (über 0°C), an die die Systemlogik angepasst ist! Der Thermostat kann vielseitig eingesetzt werden, beispielsweise um einen vorhandenen Raumthermostat zu ersetzen. Auch die Heizung im Aquarium oder Terrarium kann vorübergehend durch den Thermostat ersetzt werden, um eine konstante Temperatur aufrechtzuerhalten.

    Der Autor des WiFi-Thermostats übernimmt keine Verantwortung für die Funktionalität des Thermostats, Ausfälle des Kessels oder Stromschläge aufgrund unsachgemäßer Installation des Thermostats im Netzwerk.
    Hauptseite zur Änderung der Zieltemperatur und der Hysterese - Demo auf:
    Beispieldaten
  • Zieltemperatur: 22.75 °C
  • Hysterese: 0.25 ° C
  • Messdaten: 22.49 ° C
  • Ausgabe: EIN

  • Der Thermostat heizt ab einer gemessenen Temperatur von 22.49 °C und darunter. Wenn die Temperatur 23.01 °C erreicht, wird der Ausgang ausgeschaltet, das Melderelais öffnet und der Gaskessel hört auf zu heizen. Das Aufheizen und Abkühlen des Raumes, in dem die Messungen durchgeführt werden, erfolgt. Der Thermostat wird erst wieder aktiviert, wenn die Temperatur 22.49 °C oder niedriger erreicht.

    Hauptseite zur Änderung der Solltemperatur und Hysterese: WiFi-Thermostat - Hauptübersicht mit Änderung von Solltemperatur und Hysterese Verarbeitungsprozess der eingegebenen Daten (Nutzerumleitung): WiFi-Thermostat - Datenverarbeitung aus HTML-Formular JSON-Webserverausgabe im Browser / Client über Websocket:
    WiFi thermostat - JSON output
    Ausgabe an UART-Monitor - Systemlogik + IP-Adresse einstellen, mDNS-Record:
    WiFi termostat - UART - ESP8266 - mDNS protokollieren, IP-Adresse des Thermostats
    Eine erweiterte Version des WiFi-Thermostats umfasst:
  • Bearbeitbarer Quellcode (.ino)
  • Verfügbare Sensoren SHT21, SHT31, DHT22, BME280, BMP280 für die Kesselsteuerung
  • Kühlmodus (inverse Logik zum Heizthermostat)
  • Basic OTA, OTA Web Updater, Eleganter Remote OTA
  • Möglichkeit, die Ausgabe über Callbacks zu steuern, die von UDP-Datagrammen über UDP-Clients (Packet Sender) aufgerufen werden.
  • Interaktion mit Amazon Alexa Echo Dot mit der Möglichkeit der Sprachsteuerung des Thermostats (UDP-Rückrufe)
  • Möglichkeit zur Veröffentlichung von Daten an MQTT Broker / per HTTP / HTTPS-Anfrage an einen entfernten Webserver mit Datenspeicherung in einer MySQL-Datenbank (ohne JSON-Client)

  • Verfügbare Bibliotheken für Mikrocontroller (ESP)



    WiFi thermostat - Quellcode - ESP8266

    /*|----------------------------------------------------------|*/
    /*|HTTP webserver - WiFi thermostat - ESP8266 + DS18B20      |*/
    /*|AUTHOR: Martin Chlebovec                                  |*/
    /*|EMAIL: martinius96@gmail.com                              |*/
    /*|DONATE: paypal.me/chlebovec                               |*/
    /*|----------------------------------------------------------|*/
    
    #include <ESP8266WiFi.h>
    #include <ESP8266WebServer.h>
    #include <WiFiManager.h>
    ESP8266WebServer server(80);
    #include <DNSServer.h>
    #include <EEPROM.h>
    #include <OneWire.h>
    #include <DallasTemperature.h>
    
    #define ONE_WIRE_BUS 5 //D1
    OneWire oneWire(ONE_WIRE_BUS);
    DallasTemperature sensorsA(&oneWire);
    const int rele = 4; //D2
    unsigned long cas = 0;
    String stav = "OFF";
    float teplota; s
    long day = 86400000; // 86400000 milliseconds in a day
    long hour = 3600000; // 3600000 milliseconds in an hour
    long minute = 60000; // 60000 milliseconds in a minute
    long second =  1000; // 1000 milliseconds in a second
    float rezim;
    boolean isFloat(String tString) {
      String tBuf;
      boolean decPt = false;
    
      if (tString.charAt(0) == '+' || tString.charAt(0) == '-') tBuf = &tString[1];
      else tBuf = tString;
    
      for (int x = 0; x < tBuf.length(); x++)
      {
        if (tBuf.charAt(x) == '.' || tBuf.charAt(x) == ',') {
          if (decPt) return false;
          else decPt = true;
        }
        else if (tBuf.charAt(x) < '0' || tBuf.charAt(x) > '9') return false;
      }
      return true;
    }
    
    void writeString(char add, float data)
    {
      EEPROM.put(add, (data * 1000));
      EEPROM.commit();
    }
    
    
    float read_String(char add)
    {
      float payload = 0;
      float data = EEPROM.get(add, payload);
      return (data / 1000);
    }
    
    void handleRoot() {
      int days = millis() / day ;                                //number of days
      unsigned int hours = (millis() % day) / hour;                       //the remainder from days division (in milliseconds) divided by hours, this gives the full hours
      unsigned int minutes = ((millis() % day) % hour) / minute ;         //and so on...
      unsigned int seconds = (((millis() % day) % hour) % minute) / second;
      String stranka = F("<!DOCTYPE html>");
      stranka += F("<html>");
      stranka += F("<head>");
      stranka += F("<meta charset='utf-8'>");
      stranka += F("<meta name='author' content='Martin Chlebovec'>");
      stranka += F("<meta http-equiv='Refresh' content='30'; />");
      stranka += F("<meta name='viewport' content='width=device-width, initial-scale=1'>");
      stranka += F("<link rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css'>");
      stranka += F("<script type='text/javascript'> window.smartlook||(function(d) {"); 
      stranka += F("var o=smartlook=function(){ o.api.push(arguments)},h=d.getElementsByTagName('head')[0];");
      stranka += F("var c=d.createElement('script');o.api=new Array();c.async=true;c.type='text/javascript';");
      stranka += F("c.charset='utf-8';c.src='https://rec.smartlook.com/recorder.js';h.appendChild(c); })(document);"); 
      stranka += F("smartlook('init', '63ff3bf4db2f85029b19856425a4f2086533c9e6');"); 
      stranka += F("</script>");  
      stranka += F("<script type='text/javascript'>");
      stranka += F("var timeleft = 30;");
      stranka += F("var downloadTimer = setInterval(function(){");
      stranka += F("if(timeleft <= 0){");
      stranka += F("clearInterval(downloadTimer);");
      stranka += F("document.getElementById(\"countdown\").innerHTML = \"Refreshing...\";");
      stranka += F("} else {");
      stranka += F("document.getElementById(\"countdown\").innerHTML = timeleft + \" seconds to refresh\";");
      stranka += F("}");
      stranka += F("timeleft -= 1;");
      stranka += F("}, 1000);");
      stranka += F("</script>");
      stranka += F("<title>WiFi thermostat - ESP8266</title>");
      stranka += F("</head>");
      stranka += F("<body>");
      stranka += F("<center><h3>WiFi thermostat - ESP8266:</h3>");
      if (rezim == 0.00) {
        stranka += F("<form action='/action.html' method='post'>");
        stranka += "<b>Target temperature:</b><br><input type='text' id='fname' name='fname' min='5' max='50' step='0.25' value=" + String(read_String(10)) + "><br>";
        stranka += "<b>Hysteresis:</b><br><input type='text' id='fname2' name='fname2' min='0' max='10' step='0.25' value=" + String(read_String(100)) + "><br>";
        stranka += F("<input type='submit' class='btn btn-success' value='Send'>");
        stranka += F("</form>");
        stranka += F("<a href='manual.html' class='btn btn-primary' role='button'>Manual mode</a><hr>");
      } else if (rezim == 1.00) {
        if (stav == "ON") {
          stranka += F("<a href='vyp.html' class='btn btn-danger' role='button'>Turn off</a><br>");
        }
        if (stav == "OFF") {
          stranka += F("<a href='zap.html' class='btn btn-success' role='button'>Turn on</a><br>");
        }
        stranka += F("<a href='automat.html' class='btn btn-primary' role='button'>Automatic mode</a><hr>");
      }
      if (stav == "ON") {
        stranka += F("<b><font color='green'>Output: ON</font></b>");
      }
      if (stav == "OFF") {
        stranka += F("<b><font color='red'>Output: OFF</font></b>");
      }
      stranka += F("<div id=\"countdown\"></div>");
      stranka += F("<b>Current sensor DS18B20 temperature:</b> ");
      stranka += String(teplota);
      stranka += F(" °C");
      stranka += F("<hr>");
      stranka += F("<b>Uptime: </b>");
      stranka += String(days);
      stranka += F("d");
      stranka += F(" ");
      stranka += String(hours);
      stranka += F("h");
      stranka += F(" ");
      stranka += String(minutes);
      stranka += F("m");
      stranka += F(" ");
      stranka += String(seconds);
      stranka += F("s");
      stranka += F("<h3>Author: Martin Chlebovec - martinius96@gmail.com - https://martinius96.github.io/WiFi-termostat/en/</h3>");
      stranka += F("<h4>Free version - 1.0.4 build: 06. Oct. 2021</h4>");
      stranka += F("</center>");
      stranka += F("</body>");
      stranka += F("</html>");
      server.send(200, "text/html", stranka);
    }
    
    void handleBody() {
      if (server.hasArg("fname")) {
        String target_temp = server.arg("fname");
        // float cielova_teplota = target_temp.toFloat();
    
        if (isFloat(target_temp)) {
          float cielova_teplota = target_temp.toFloat();
          writeString(10, cielova_teplota);
        } else {
          Serial.println(F("No number was entered in the input according to the target temperature!"));
          Serial.println(F("Writing into EEPROM prohibited!"));
        }
      }
      if (server.hasArg("fname2")) {
        String hysteresis = server.arg("fname2");
        if (isFloat(hysteresis)) {
          float hystereza = hysteresis.toFloat();
          writeString(100, hystereza);
        } else {
          Serial.println(F("No number was entered in the input according to the hysteresis!"));
          Serial.println(F("Writing into EEPROM prohibited!"));
        }
      }
      String stranka = F("<!DOCTYPE html>");
      stranka += F("<html>");
      stranka += F("<head>");
      stranka += F("<meta charset='utf-8'>");
      stranka += F("<meta http-equiv='Refresh' content='5; url=/' />");
      stranka += F("<title>WiFi thermostat - ESP8266 - data processing</title>");
      stranka += F("</head>");
      stranka += F("<body>");
      stranka += F("<center><h3>Server received data from HTML form:</h3>");
      stranka += "<li><b>Target temperature: </b>" + String(read_String(10)) + " °C</li>";
      stranka += "<li><b>Hysteresis: </b>" + String(read_String(100)) + " °C</li>";
      stranka += F("<b>Redirecting... Please wait</b></center>");
      stranka += F("</body>");
      stranka += F("</html>");
      server.send(200, "text/html", stranka);
    }
    
    void handleGet() {
      String stranka = "{\n";
      stranka += F("\"Hysteresis\":");
      stranka += String(read_String(100));
      stranka += F(",\n");
      stranka += F("\"Target_Temperature\":");
      stranka += String(read_String(10));
      stranka += F(",\n");
      stranka += F("\"Actual_Temperature\":");
      stranka += String(teplota) + "\n";
      stranka += F("}\n");
      server.send(200, "application/json", stranka);
    }
    
    void handleZAP() {
      stav = "ON";
      digitalWrite(rele, LOW);
      String stranka = F("<!DOCTYPE html>");
      stranka += F("<html>");
      stranka += F("<head>");
      stranka += F("<meta charset='utf-8'>");
      stranka += F("<meta http-equiv='Refresh' content='0; url=/' />");
      stranka += F("</head>");
      stranka += F("</html>");
      server.send(200, "text/html", stranka);
    }
    
    void handleAuto() {
      writeString(150, 0.00);
      rezim = read_String(150);
      String stranka = F("<!DOCTYPE html>");
      stranka += F("<html>");
      stranka += F("<head>");
      stranka += F("<meta charset='utf-8'>");
      stranka += F("<meta http-equiv='Refresh' content='0; url=/' />");
      stranka += F("</head>");
      stranka += F("</html>");
      server.send(200, "text/html", stranka);
    }
    void handleManual() {
      writeString(150, 1.00);
      rezim = read_String(150);
      String stranka = F("<!DOCTYPE html>");
      stranka += F("<html>");
      stranka += F("<head>");
      stranka += F("<meta charset='utf-8'>");
      stranka += F("<meta http-equiv='Refresh' content='0; url=/' />");
      stranka += F("</head>");
      stranka += F("</html>");
      server.send(200, "text/html", stranka);
    }
    void handleVYP() {
      stav = "OFF";
      digitalWrite(rele, HIGH);
      String stranka = F("<!DOCTYPE html>");
      stranka += F("<html>");
      stranka += F("<head>");
      stranka += F("<meta charset='utf-8'>");
      stranka += F("<meta http-equiv='Refresh' content='0; url=/' />");
      stranka += F("</head>");
      stranka += F("</html>");
      server.send(200, "text/html", stranka);
    }
    void setup() {
      Serial.begin(115200);
      WiFiManager wifiManager;
      wifiManager.autoConnect("WiFi_TERMOSTAT_AP");
      EEPROM.begin(512);  //Initialize EEPROM
      float a = read_String(10);
      float b = read_String(100);
      float c = read_String(150);
      if (isnan(a)) {
        writeString(10, 20.25);
      }
      if (isnan(b)) {
        writeString(100, 0.25);
      }
      if (isnan(c)) {
        writeString(150, 0.00);
      }
      sensorsA.begin();
      pinMode(rele, OUTPUT);
      digitalWrite(rele, HIGH);
      sensorsA.requestTemperatures();
      delay(750);
      Serial.println(F("WiFi thermostat - Author: Martin Chlebovec"));
      Serial.println("");
      Serial.println(F("WiFi connected."));
      Serial.println(F("IP address: "));
      Serial.println(WiFi.localIP());
      server.on("/", handleRoot);
      server.on("/get_data.json", handleGet);
      server.on("/automat.html", handleAuto);
      server.on("/manual.html", handleManual);
      server.on("/zap.html", handleZAP);
      server.on("/vyp.html", handleVYP);
      server.on("/action.html", HTTP_POST, handleBody);
      server.begin();
    }
    
    void loop() {
      if ((millis() - cas) >= 10000 || cas == 0) {
        cas = millis();
        teplota = sensorsA.getTempCByIndex(0);
        Serial.println();
        Serial.println(F("----------------------------------------------"));
        Serial.print(F("IP address of ESP8266 thermostat: "));
        Serial.print(WiFi.localIP());
        Serial.print(F(", for access via mDNS use http://"));
        Serial.print(WiFi.localIP());
        Serial.println(F("/"));
        Serial.print(F("Free HEAP: "));
        Serial.print(ESP.getFreeHeap());
        Serial.println(F(" B"));
        Serial.print(F("Actual DS18B20 temperature: "));
        Serial.print(String(teplota));
        Serial.println(F(" °C"));
        sensorsA.requestTemperatures();
        rezim = read_String(150);
        if (rezim == 0.00) {
          float cielova_teplota = read_String(10);
          float  hystereza = read_String(100);
          float minus_hystereza_teplota = (-1 * hystereza);
          float rozdiel = cielova_teplota - teplota; //21 - 20
          if (rozdiel > hystereza) {
            Serial.println(F("Output ON"));
            stav = "ON";
            digitalWrite(rele, LOW);
          } else if (rozdiel < minus_hystereza_teplota) {
            Serial.println(F("Output OFF"));
            stav = "OFF";
            digitalWrite(rele, HIGH);
          } else {
            Serial.println(F("Difference between the target and actual temperature is not above or below the hysteresis. The output status does not change."));
            Serial.print(F("Actual output state: "));
            Serial.println(stav);
          }
        } else {
          Serial.print(F("Manual operation mode is used, output status: "));
          Serial.println(stav);
        }
      }
      server.handleClient();
      yield();
    }
    
    

    WiFi thermostat - Quellcode - ESP32

    /*|----------------------------------------------------------|*/
    /*|HTTP webserver - WiFi thermostat - ESP32 + DS18B20        |*/
    /*|AUTHOR: Martin Chlebovec                                  |*/
    /*|EMAIL: martinius96@gmail.com                              |*/
    /*|DONATE: paypal.me/chlebovec                               |*/
    /*|----------------------------------------------------------|*/
    
    #include <WiFi.h>
    #include <WebServer.h>
    #include <WiFiManager.h>
    #include <ESPmDNS.h>
    WebServer server(80);
    #include <EEPROM.h>
    #include <OneWire.h>
    #include <DallasTemperature.h>
    
    #define ONE_WIRE_BUS 23
    OneWire oneWire(ONE_WIRE_BUS);
    DallasTemperature sensorsA(&oneWire);
    const int rele = 22; //D2
    unsigned long cas = 0;
    String stav;
    float teplota;
    long day = 86400000; // 86400000 milliseconds in a day
    long hour = 3600000; // 3600000 milliseconds in an hour
    long minute = 60000; // 60000 milliseconds in a minute
    long second =  1000; // 1000 milliseconds in a second
    
    boolean isFloat(String tString) {
      String tBuf;
      boolean decPt = false;
    
      if (tString.charAt(0) == '+' || tString.charAt(0) == '-') tBuf = &tString[1];
      else tBuf = tString;
    
      for (int x = 0; x < tBuf.length(); x++)
      {
        if (tBuf.charAt(x) == '.' || tBuf.charAt(x) == ',') {
          if (decPt) return false;
          else decPt = true;
        }
        else if (tBuf.charAt(x) < '0' || tBuf.charAt(x) > '9') return false;
      }
      return true;
    }
    
    void writeString(char add, float data)
    {
      EEPROM.put(add, (data * 1000));
      EEPROM.commit();
    }
    
    
    float read_String(char add)
    {
      float payload = 0;
      float data = EEPROM.get(add, payload);
      return (data / 1000);
    }
    
    void handleRoot() {
      int days = millis() / day ;                                //number of days
      unsigned int hours = (millis() % day) / hour;                       //the remainder from days division (in milliseconds) divided by hours, this gives the full hours
      unsigned int minutes = ((millis() % day) % hour) / minute ;         //and so on...
      unsigned int seconds = (((millis() % day) % hour) % minute) / second;
      String stranka = F("<!DOCTYPE html>");
      stranka += F("<html>");
      stranka += F("<head>");
      stranka += F("<meta charset='utf-8'>");
      stranka += F("<meta name='author' content='Martin Chlebovec'>");
      stranka += F("<meta http-equiv='Refresh' content='30'; />");
      stranka += F("<meta name='viewport' content='width=device-width, initial-scale=1'>");
      stranka += F("<link rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css'>");
      stranka += F("<script type='text/javascript'> window.smartlook||(function(d) {"); 
      stranka += F("var o=smartlook=function(){ o.api.push(arguments)},h=d.getElementsByTagName('head')[0];");
      stranka += F("var c=d.createElement('script');o.api=new Array();c.async=true;c.type='text/javascript';");
      stranka += F("c.charset='utf-8';c.src='https://rec.smartlook.com/recorder.js';h.appendChild(c); })(document);"); 
      stranka += F("smartlook('init', '63ff3bf4db2f85029b19856425a4f2086533c9e6');"); 
      stranka += F("</script>");
      stranka += F("<script type='text/javascript'>");
      stranka += F("var timeleft = 30;");
      stranka += F("var downloadTimer = setInterval(function(){");
      stranka += F("if(timeleft <= 0){");
      stranka += F("clearInterval(downloadTimer);");
      stranka += F("document.getElementById(\"countdown\").innerHTML = \"Refreshing...\";");
      stranka += F("} else {");
      stranka += F("document.getElementById(\"countdown\").innerHTML = timeleft + \" seconds to refresh\";");
      stranka += F("}");
      stranka += F("timeleft -= 1;");
      stranka += F("}, 1000);");
      stranka += F("</script>");
      stranka += F("<title>WiFi thermostat - ESP32</title>");
      stranka += F("</head>");
      stranka += F("<body>");
      stranka += F("<center><h3>Enter datas for web server:</h3>");
      stranka += F("<form action='/action.html' method='post'>");
      stranka += "<b>Target temperature:</b><br><input type='text' id='fname' name='fname' min='5' max='50' step='0.25' value=" + String(read_String(10)) + "><br>";
      stranka += "<b>Hysteresis:</b><br><input type='text' id='fname2' name='fname2' min='0' max='10' step='0.25' value=" + String(read_String(100)) + "><br>";
      stranka += F("<input type='submit' class='btn btn-success' value='Send'>");
      stranka += F("</form><hr>");
      if (stav == "ON") {
        stranka += F("<b><font color='green'>Output: ON</font></b>");
      }
      if (stav == "OFF") {
        stranka += F("<b><font color='red'>Output: OFF</font></b>");
      }
      stranka += F("<div id=\"countdown\"></div>");
      stranka += F("<b>Current sensor DS18B20 temperature:</b> ");
      stranka += String(teplota);
      stranka += F(" °C");
      stranka += F("<hr>");
      stranka += F("<b>Uptime: </b>");
      stranka += String(days);
      stranka += F("d");
      stranka += F(" ");
      stranka += String(hours);
      stranka += F("h");
      stranka += F(" ");
      stranka += String(minutes);
      stranka += F("m");
      stranka += F(" ");
      stranka += String(seconds);
      stranka += F("s");
      stranka += F("<h3>Author: Martin Chlebovec - martinius96@gmail.com - https://martinius96.github.io/WiFi-termostat/en/</h3>");
      stranka += F("<h4>Free version - 1.0.4 build: 10. Oct. 2021</h4>");
      stranka += F("</center>");
      stranka += F("</body>");
      stranka += F("</html>");
      server.send(200, "text/html", stranka);
    }
    
    void handleBody() {
      if (server.hasArg("fname")) {
        String target_temp = server.arg("fname");
        // float cielova_teplota = target_temp.toFloat();
    
        if (isFloat(target_temp)) {
          float cielova_teplota = target_temp.toFloat();
          writeString(10, cielova_teplota);
        } else {
          Serial.println(F("No number was entered in the target temperature input field!"));
          Serial.println(F("Datas will be not stored in EEPROM!"));
        }
      }
      if (server.hasArg("fname2")) {
        String hysteresis = server.arg("fname2");
        if (isFloat(hysteresis)) {
          float hystereza = hysteresis.toFloat();
          writeString(100, hystereza);
        } else {
          Serial.println(F("No number was entered in the hysteresis input field!"));
          Serial.println(F("Datas will be not stored in EEPROM!"));
        }
      }
      String stranka = F("<!DOCTYPE html>");
      stranka += F("<html>");
      stranka += F("<head>");
      stranka += F("<meta charset='utf-8'>");
      stranka += F("<meta http-equiv='Refresh' content='5; url=/' />");
      stranka += F("<title>WiFi thermostat - ESP32 - data processing</title>");
      stranka += F("</head>");
      stranka += F("<body>");
      stranka += F("<center><h3>Server received datas from HTML form:</h3>");
      stranka += "<li><b>Target temperature: </b>" + String(read_String(10)) + " °C</li>";
      stranka += "<li><b>Hysteresis: </b>" + String(read_String(100)) + " °C</li>";
      stranka += F("<b>Redirecting... Please wait</b></center>");
      stranka += F("</body>");
      stranka += F("</html>");
      server.send(200, "text/html", stranka);
    }
    
    void handleGet() {
      String stranka = "{\n";
      stranka += F("\"Hysteresis\":");
      stranka += String(read_String(100));
      stranka += F(",\n");
      stranka += F("\"Target_Temperature\":");
      stranka += String(read_String(10));
      stranka += F(",\n");
      stranka += F("\"Actual_Temperature\":");
      stranka += String(teplota) + "\n";
      stranka += F("}\n");
      server.send(200, "application/json", stranka);
    }
    
    void setup() {
      Serial.begin(115200);
      WiFiManager wifiManager;
      wifiManager.autoConnect("WiFi_TERMOSTAT_AP");
      EEPROM.begin(512);  //Initialize EEPROM
      float a = read_String(10);
      float b = read_String(100);
      if (isnan(a)) {
        writeString(10, 20.25);
      }
      if (isnan(b)) {
        writeString(100, 0.25);
      }
      sensorsA.begin();
      pinMode(rele, OUTPUT);
      digitalWrite(rele, HIGH);
      sensorsA.requestTemperatures();
      delay(750);
      Serial.println(F("Webapp created by: Martin Chlebovec"));
      Serial.println(F("Build 1.0.4 from 10. Oct 2021"));
      Serial.println(F("WiFi connected."));
      Serial.println(F("IP address of WiFi thermostat: "));
      Serial.println(WiFi.localIP());
      if (!MDNS.begin("wifi-termostat")) {
        Serial.println("Error setting up MDNS responder!");
        while (1) {
          delay(1000);
        }
      }
      Serial.println("mDNS responder running");
      Serial.println(F("Thermostat available at: http://wifi-termostat.local"));
      server.on("/", handleRoot);
      server.on("/get_data.json", handleGet);
      server.on("/action.html", HTTP_POST, handleBody);
      server.begin();
      MDNS.addService("http", "tcp", 80);
    }
    
    void loop() {
      if ((millis() - cas) >= 10000 || cas == 0) {
        cas = millis();
        teplota = sensorsA.getTempCByIndex(0);
        Serial.println();
        Serial.println(F("----------------------------------------------"));
        Serial.print(F("IP address of WiFi Thermostat: "));
        Serial.print(WiFi.localIP());
        Serial.print(F(", to access WiFi thermostat, visit http://"));
        Serial.print(WiFi.localIP());
        Serial.println(F("/"));
        Serial.println(F("Thermostat also available at: http://wifi-termostat.local via mDNS record"));
        Serial.print(F("Free HEAP: "));
        Serial.print(ESP.getFreeHeap());
        Serial.println(F(" B"));
        Serial.print(F("Current temperature: "));
        Serial.print(String(teplota));
        Serial.println(F(" °C"));
        sensorsA.requestTemperatures();
        float cielova_teplota = read_String(10);
        float  hystereza = read_String(100);
        float minus_hystereza_teplota = (-1 * hystereza);
        float rozdiel = cielova_teplota - teplota; //21 - 20
        if (rozdiel > hystereza) {
          Serial.println(F("Output on"));
          stav = "ON";
          digitalWrite(rele, LOW);
        } else if (rozdiel < minus_hystereza_teplota) {
          Serial.println(F("Output off"));
          stav = "OFF";
          digitalWrite(rele, HIGH);
        } else {
          Serial.println(F("Difference between target and actual temp is not above or below the hysteresis. The output state does not change."));
          Serial.print(F("Actual state of output: "));
          Serial.println(stav);
        }
      }
      server.handleClient();
      yield();
    }