Podporte projekt Ethernet termostat cez PayPal. Podpora umožní pridať nové funkcionality (async webserver, Over-The-Air firmware upload...) v budúcnosti a otvorenie zdrojového kódu aplikácie: PayPal donate
Pri záujme o zdrojový kód pre ESP32 + PHY Ethernet termostat: martinius96@gmail.com
Nakoľko používatelia prestali rešpektovať licenciu projektu, nie je projekt ďalej dostupný so zdrojovým kódom. Môžete využiť skompilovaný firmvér .bin s dynamickou IPv4 adresou v LAN. Návod na nahratie firmvéru do ESP32 nájdete pod WiFi termostatom.
Stianuť PHY termostat .bin
ESP32 Ethernet LAN8720 DS18B20 OneWire Dallas HTML Webserver WebSocket

Kompatibilný hardvér pre Ethernet termostat:


Riadiaci mikrokontróler ESP32 Devkit V1 - ESP-WROOM-32 / ESP32-S
ESP32 DevKit
Kúpiť Techfun.sk
Ethernet modul LAN 8720 PHY Ethernet
Ethernet modul PHY LAN8720
Kúpiť Techfun.sk
Teplotný senzor DS18B20 v puzdre TO-92
Dallas DS18B20 - puzdro TO-92
Kúpiť Techfun.sk
Teplotný senzor DS18B20 - vodotesné vyhotovenie
Dallas DS18B20 - vonkajšie vyhotovenie
Kúpiť Techfun.sk
SSR relé OMRON G3MB-202P
SSR relé OMRON G3MB-202P
Kúpiť Techfun.sk
Elektromagnetické relé SRD-05VDC-SL-C
Elmg. relé SRD-05VDC-SL-C
Kúpiť Techfun.sk

Izbový termostat - ESP32 + LAN8720


Mikrokontróler ESP32 je prepojený s PHY Ethernet modulom LAN8720 cez rozhranie RMII (Reduced Media Independent Interface). ESP32 cez WiFi kontróler zabezpečuje MAC vrstvu pre PHY Ethernet. Webserver implementovaný v Arduino Core a umožňuje riadiť termostat cez prehliadač v LAN sieti, kde je ESP32 dostupné na pridelenej IPv4 adrese z DHCP servera v LAN sieti. Logika termostatu sa vykonáva nezávisle na otvorenej webaplikácii termostatu. Zmena logiky, prahových teplôt sa vykonáva prostredníctom HTTP požiadaviek od klientov v sieti cez HTML formulár, prípadne je možné požiadavku zaslať aj priamo prostredníctvom HTTP POST požiadavky s predvolenými argumentami fname a fname2. Na základe požiadavky na konkrétnu podstránku je možné ovládať výstup, napríklad manuálne / automaticky, alebo je možné argumentom nastaveným na určitú hodnotu prepísať riadiace dáta (cieľová teplota, hysteréza - dátový typ float). Riadiace dáta sú uložené v emulovanej EEPROM pamäti na ofsete flash pamäte, pričom je životnosť tohto sektora na úrovni 10-tisíc prepisov. Webserver beží na štandardnom HTTP porte - 80.

Termostat dokáže automatizovane prostredníctvom GPIO výstupu (invertovaná logika) ovládať signalizačné relé pre zapnutie / vypnutie kotla. Dokáže tak nahradiť existujúci izbový termostat a sprístupniť ho v sieti všetkým klientom. Termostat dokáže obsluhovať akékoľvek zariadenie s prehliadačom - počítač / smartfón / tablet / Smart TV a podobne. Ako rozhodovací algoritmus sa využíva cieľová teplota s hysterézou, ktorá sa porovnáva s nameranou teplotou z digitálneho senzora teploty Dallas DS18B20. Cieľová teplota a hysteréza je načítaná z EEPROM pamäte, kde je uchovaná aj v prípade výpadku napájania permanentne a pri zápise nových dát sa prepíše. Rozlíšenie senzora DS18B20 pri meraní je 12-bitov, čomu rozpovedá rozlíšenie na teploty 0.0625 °C, čo je minimálny rozlišovací krok medzi rozlišnými meraniami. Dáta po OneWire zbernici môžu prísť do mikrokontroléru po vyžiadaní za 500 až 1000 ms v závislosti od počtu senzorov na OneWire zbernici, dĺžke zbernice a podobne... Rozhodovacia logika termostatu sa vykonáva každých 10 sekúnd nezávisle na webaplikácii, nevyžaduje sa keep-alive spojenie pre vykonanie logiky, systém tak funguje autonómne a nevyžaduje pozornosť používateľa.

Po hardvérovej stránke projekt využíva:
  • ESP32
  • Ethernet modul LAN8720
  • Teplotný senzor DS18B20 na OneWire zbernici v puzdre TO-92, alebo vo vodotesnom vyhotovení v hliníkovej rúrke
  • Elektromagnetické relé SRD-5VDC-SL-C / SSR relé OMRON G3MB-202P slúžiace na spínanie kotla (Active-LOW signál)

  • Zapojenie hardvérových komponentov pre PHY Ethernet termostat s ESP32:
    ESP32 Dallas DS18B20
    3V3 Vcc
    GND GND
    D5 DATA
    ESP32 Relé (OMRON G3MB-202P / SRD-05VDC-SL-C)
    5V Vcc
    GND GND
    D4 IN
    ESP32 PHY Ethernet LAN8720
    3V3 Vcc
    GND GND / RBIAS
    D18 MDIO
    D19 TXD0
    D21 TXEN
    D22 TXD1
    D23 MDC
    D25 RXD0
    D26 RXD1
    D27 CRS_DV
    ESP32, PHY Ethernet termostat, schéma zapojenia s relé a teplomerom DS18B20

    HTML stránky bežiace na ESP32:

  • / - root stránka obsahujúca formulár, aktuálny výpis logického výstupu pre relé, aktuálnu a cieľovú teplotu teplotu, hysterézu
  • /action.html - spracúvava hodnoty z formulára, zapisuje ich do emulovanej EEPROM pamäte, presmeruje používateľa späť na root stránku
  • /get_data.json - distribuuje dáta o aktuálnej teplote, referenčnej teplote a hysteréza tretej strane (počítač, mikrokontróler, iný klient...) v JSON formáte - možno využiť s príkladom JSON klient, ktorý dáta vie odoslať na MQTT Broker, napríklad do domácej automatizácie
  • /zap.html - permanentné zapnutie výstupu v manuálnom režime
  • /vyp.html - permanentné vypnutie výstupu v manuálnom režime
  • /automat.html - zmena režimu na automatický (používa hysterézu a cieľovú teplotu)
  • /manual.html - zmena režimu na manuálny (permanentné ovládanie výstupu ZAP / VYP natvrdo)

  • Elektromagnetické relé SRD-5VDC-SL-C, ktoré je v projekte použité umožňuje spínať až 10A pri 230V - maximálny výkon 2300W. V prípade spínania jednosmerného obvodu (záťaže) je možné spínať 300W (10A pri 30V DC max). Pre schému zapojenia je plne kompatibilné aj SSR relé OMRON G3MB-202P, ktoré je vhodné iba pre neindukčnú záťaž a výhradne pre záťaž so striedavým napätím (DC obvod nedokáže po zopnutí rozopnúť). Maximálny spínaný výkon 460W (230V, 2A). Termostat je možné používať celoročne. V prípade nepotrebného riadenia je možné výstup fyzicky odpojiť a termostat využívať ako Ethernet teplomer pre zisk dát z miestnosti, kde sa nachádza.

    ESP32 + PHY Ethernet thermostat - webserver Webové rozhranie pre Ethernet termostat umožňuje:
  • Prehliadať v reálnom čase teplotu zo senzora DS18B20 na OneWire zbernici, uptime zariadenia, stav výstupu s dynamickou zmenou, aktuálne nastavené konfiguračné údaje pre termostat t.j. cieľovú teplotu a hysterézu z EEPROM
  • Modifikovať cieľovú (referenčnú) teplotu v rozsahu 5 až 50 °C s 0,25 °C krokom
  • Modifikovať hysterézu v rozsahu 0 až 10 °C s 0,25 °C krokom
  • Voliť prevádzkový režim termostatu - automatický / manuálny režim
  • V manuálnom režime prepínať výstup na neurčitú dobu
  • ZAP/VYP regulácia kotla:
  • Príklad ZAP/VYP regulácie vykurovania - VIZUALIZÁCIA NIE JE SÚČASŤOU PROJEKTU
  • Kotol je aktívny po dobu dostiahnutia cieľovej teploty + hysterézy
  • Na vizualizácii teplôt vody je patrný tzv. dobeh vykurovania a následné chladnutie vody až do opätovnej aktivity vykurovania, kedy je nameraná teplota pod nastavenú cieľovú teplotu - hystérzu
  • ZAP/VYP regulácia kotla s hysterézou

    Webové rozhranie je navrhnuté pre prispôsobenie sa väčším i menším obrazovkám. Je reponzívne, podporuje širokouhlé obrazovky s vysokým rozlíšením, ale aj mobilné zariadenia. Rozhranie využíva importované CSS štýly Bootstrap frameworku z externého CDN servera, ktorý načíta client-side zariadenie pri otvorení stránky bežiacej na ESP32. Aby ostali nastavené hodnoty termostatu zachované aj po výpadku napájania, sú uložené do EEPROM pamäte ESP, ktorá je emulovaná vo flash pamäti, nakoľko platforma nemá fyzicky EEPROM čip (pamäť). Referenčná teplota na offset 10, hysteréza na offset 100. Každá z hodnôt zaberá maximálne 5B v EEPROM pamäti + ukončovací znak. Dáta sa prepisujú iba pri odoslaní HTML formulára, prevádzka termostatu je tak maximálne šetrná k EEPROM pamäti pre jej maximálnu trvácnosť. Stav výstupu existuje iba v RAM pamäti, kde sa pri zmene prepisuje. Hodnota sa neukladá do emulovanej EEPROM pamäte vo flash pamäti.


    Prostredníctvom meta tagu Refresh vykonáva webserver obnovu celej stránky každých 30 sekúnd a prostredníctvom Javascriptu sa vypisuje do HTML stránky aj orientačný čas do refreshu. Do tohto času je potrebné stihnúť zapísať zmenu pre termostat, inak sa input okná pre číselné vstupy do formulára resetujú pri obnovení stránky. Na základe spätnej väzby od používateľov Android zariadení bol čas pre Refresh predĺžený z 10 na 30 sekúnd. Dynamický údaj, ktorý sa predovšetkým mení je aktuálna hodnota výstupu - Zapnutý / Vypnutý, ktorý informuje prevádzkovateľa o skutočnom stave výstupu spoločne aj s farebným označením. Nakoľko sa logika systému vykonáva nezávisle na webserveri, do refreshu môže už byť výstup v inom stave, ako aktuálne vypísanom vo webaplikácii. Zmena výstupu je ihneď vypísaná napríklad na UART monitor (115200 baud/s). Na webovej stránke termostatu nájde používateľ aj informácie o uptime zariadenia (ako dlho beží), t.j. čas v dňoch, hodinách, minútach a sekundách.

    Autor Ethernet termostatu nezodpovedá za funkčnosť termostatu, poruchu kotla, úraz elektrickým prúdom pri neodbornej montáži termostatu do siete. Termostat je šírený pod MIT licenciou.
    Hlavná stránka pre modifikáciu cieľovej teploty a hysterézy - ukážka zapnutého:
    Ukážkové dáta
  • Cieľová teplota: 22.75 °C
  • Hysteréza: 0.25 °C
  • Namerané dáta: 22.49 °C
  • Výstup: Zapnutý

  • Termostat vykuruje od nameranej teploty 22.49 °C a nižšej. V prípade dosiahnutia teploty 23.01 °C a vyššej sa výstup vypne, signalizačné relé sa rozpojí a plynový kotol prestáva kúriť. Nastáva dobeh vykurovania a chladnutie miestnosti v ktorej sa vykonávajú merania. Termostat sa opäť aktivuje až pri dosiahnutí teploty 22.49 °C, alebo nižšej.


    Plnefunkčný Ethernet termostat s možnosťou nastavenia riadiach dát:


    Refresh webového rozhrania automaticky každých 30 sekúnd
    Hlavná stránka pre modifikáciu cieľovej teploty a hysterézy / navolenie manuálneho režimu s možnosťou ovládania ZAP/VYP: PHY Ethernet termostat ESP32 - Hlavný prehľad s modifikáciou cieľovej teploty a hysterézy - Vypnutý Priebeh spracovania zadaných údajov (presmerovanie používateľa): PHY Ethernet termostat ESP32 - spracovanie údajov z HTML formulára JSON výstup webservera v prehliadači / klientovi cez websocket:
    PHY Ethernet termostat ESP32 - JSON output
    Výstup do UART monitoru - logika systému + nastavená IP adresa:
    PHY Ethernet termostat ESP32 - cyklický výstup s IP adresou, aktuálnou teplotou a stavom GPIO výstupu

    Strojový kód - Ethernet termostat - ESP32 + PHY LAN8720

    /*|-----------------------------------------------------------|*/
    /*|HTTP webserver - Ethernet thermostat - ESP32 + PHY LAN8720 |*/
    /*|Project webpage:                                           |*/
    /*|https://martinius96.github.io/termostat-ethernet/          |*/
    /*|AUTHOR: Martin Chlebovec                                   |*/
    /*|EMAIL: martinius96@gmail.com                               |*/
    /*|DONATE: paypal.me/chlebovec                                |*/
    /*|-----------------------------------------------------------|*/
    
    #include <ETH.h>
    #include <WebServer.h>
    WebServer server(80);
    
    #include <EEPROM.h>
    #include <OneWire.h>
    #include <DallasTemperature.h>
    
    #define ONE_WIRE_BUS 5 //D5
    OneWire oneWire(ONE_WIRE_BUS);
    DallasTemperature sensorsA(&oneWire);
    const int rele = 4; //D4
    unsigned long cas = 0;
    String stav = "VYP";
    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
    float rezim;
    
    #ifdef ETH_CLK_MODE
    #undef ETH_CLK_MODE
    #endif
    #define ETH_CLK_MODE    ETH_CLOCK_GPIO17_OUT
    
    // Pin# of the enable signal for the external crystal oscillator (-1 to disable for internal APLL source)
    #define ETH_POWER_PIN   -1
    
    // Type of the Ethernet PHY (LAN8720 or TLK110)
    #define ETH_TYPE        ETH_PHY_LAN8720
    
    // I²C-address of Ethernet PHY (0 or 1 for LAN8720, 31 for TLK110)
    #define ETH_ADDR        1
    
    // Pin# of the I²C clock signal for the Ethernet PHY, DONT USE THIS PIN FOR ultrasonic sensor in this sketch
    #define ETH_MDC_PIN     23
    
    // Pin# of the I²C IO signal for the Ethernet PHY
    #define ETH_MDIO_PIN    18
    
    void WiFiEvent(WiFiEvent_t event)
    {
      switch (event) {
        case ARDUINO_EVENT_ETH_START:
          Serial.println("ETH Started");
          //set eth hostname here
          ETH.setHostname("esp32-ethernet");
          break;
        case ARDUINO_EVENT_ETH_CONNECTED:
          Serial.println("ETH Connected");
          break;
        case ARDUINO_EVENT_ETH_GOT_IP:
          Serial.print("ETH MAC: ");
          Serial.print(ETH.macAddress());
          Serial.print(", IPv4: ");
          Serial.print(ETH.localIP());
          if (ETH.fullDuplex()) {
            Serial.print(", FULL_DUPLEX");
          }
          Serial.print(", ");
          Serial.print(ETH.linkSpeed());
          Serial.println("Mbps");
          break;
        case ARDUINO_EVENT_ETH_DISCONNECTED:
          Serial.println("ETH Disconnected");
          break;
        case ARDUINO_EVENT_ETH_STOP:
          Serial.println("ETH Stopped");
          break;
        default:
          break;
      }
    }
    
    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', '6beae97f98b9844b761672af23f38fc60b962338');"); 
      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 = \"Reštart...\";");
      stranka += F("} else {");
      stranka += F("document.getElementById(\"countdown\").innerHTML = timeleft + \" sekúnd do reštartu\";");
      stranka += F("}");
      stranka += F("timeleft -= 1;");
      stranka += F("}, 1000);");
      stranka += F("</script>");
      stranka += F("<title>Ethernet termostat - ESP32</title>");
      stranka += F("</head>");
      stranka += F("<body>");
      stranka += F("<center><h3>Ethernet termostat - ESP32:</h3>");
      if (rezim == 0.00) {
        stranka += F("<form action='/action.html' method='post'>");
        stranka += "<b>Referenčná teplota:</b><br><input type='text' id='fname' name='fname' min='5' max='50' step='0.25' value=" + String(read_String(10)) + "><br>";
        stranka += "<b>Hysteréza:</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='Zapísať'>");
        stranka += F("</form>");
        stranka += F("<a href='manual.html' class='btn btn-primary' role='button'>Manuálny režim</a><hr>");
      } else if (rezim == 1.00) {
        if (stav == "ZAP") {
          stranka += F("<a href='vyp.html' class='btn btn-danger' role='button'>Vypnúť</a><br>");
        }
        if (stav == "VYP") {
          stranka += F("<a href='zap.html' class='btn btn-success' role='button'>Zapnúť</a><br>");
        }
        stranka += F("<a href='automat.html' class='btn btn-primary' role='button'>Automatický režim</a><hr>");
      }
      if (stav == "ZAP") {
        stranka += F("<b><font color='green'>Výstup: Zapnutý</font></b>");
      }
      if (stav == "VYP") {
        stranka += F("<b><font color='red'>Výstup: Vypnutý</font></b>");
      }
      stranka += F("<div id=\"countdown\"></div>");
      stranka += F("<b>Aktuálna teplota senzora DS18B20:</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>Autor: Martin Chlebovec - martinius96@gmail.com - https://martinius96.github.io/termostat-ethernet/phy_ethernet.html</h3>");
      stranka += F("<h4>Verzia free - 1.0.4 build: 22. Aug. 2022</h4>");
      stranka += F("</center>");
      stranka += F("<div class='alert alert-info'>");
      stranka += F("Finálny build projektu Ethernet termostat. Ďakujem za vyskúšanie webaplikácie.<br><strong>Rozšírenie o platený obsah:</strong><li>Async Webserver - AJAX update</li><li>Režim chladenia</li><li>mDNS záznam</li><li>OTA aktualizácie</li><li>Ovládanie hlasom cez Amazon Alexa</li><li>Ovládanie cez UDP callbacky</li><li>Možnosť publikácie dát na MQTT Broker (Loxone, IoT Industries Slovakia, Blynk...),</li><li>Dostupné senzory Bosch, Sensirion, DHT</li><li>Watchdog Timer</li><li>Zdrojový kód (.ino) pre aplikáciu.</li><li>Auto-test periférii, fail-safe riešenie</li><li>JSON output rozšírený o systémové dáta (Ethernet sieť, RSSI, uptime, napájacie napätie...)</li>");
      stranka += F("</div>");
      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("Do input pola cielovej teploty nebolo vlozene cislo!"));
          Serial.println(F("Zapis zakazany!"));
        }
      }
      if (server.hasArg("fname2")) {
        String hysteresis = server.arg("fname2");
        if (isFloat(hysteresis)) {
          float hystereza = hysteresis.toFloat();
          writeString(100, hystereza);
        } else {
          Serial.println(F("Do input pola hysterezy nebolo vlozene cislo!"));
          Serial.println(F("Zapis zakazany!"));
        }
      }
      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>Ethernet termostat - ESP32 - spracovanie riadiach dát</title>");
      stranka += F("</head>");
      stranka += F("<body>");
      stranka += F("<center><h3>Server prijal data z formulára:</h3>");
      stranka += "<li><b>Referenčná teplota: </b>" + String(read_String(10)) + " °C</li>";
      stranka += "<li><b>Hysteréza: </b>" + String(read_String(100)) + " °C</li>";
      stranka += F("<b>Presmerovanie... Prosím čakajte</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 = "ZAP";
      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 = "VYP";
      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);
      WiFi.onEvent(WiFiEvent);
      ETH.begin(ETH_ADDR, ETH_POWER_PIN, ETH_MDC_PIN, ETH_MDIO_PIN, ETH_TYPE, ETH_CLK_MODE);
      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("Ethernet termostat - Author: Martin Chlebovec"));
      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 addresa ESP32 termostat: "));
        Serial.print(ETH.localIP());
        Serial.print(F(", pre pristup k termostatu navstivte http://"));
        Serial.print(ETH.localIP());
        Serial.println(F("/"));
        Serial.print(F("Free HEAP: "));
        Serial.print(ESP.getFreeHeap());
        Serial.println(F(" B"));
        Serial.print(F("Aktuálna teplota: "));
        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("Kotol zapnuty"));
            stav = "ZAP";
            digitalWrite(rele, LOW);
          } else if (rozdiel < minus_hystereza_teplota) {
            Serial.println(F("Kotol vypnuty"));
            stav = "VYP";
            digitalWrite(rele, HIGH);
          } else {
            Serial.println(F("Rozdiel cielovej a aktuálnej teploty nie je nad, ani pod hysterezou. Stav vystupu sa nemeni."));
            Serial.print(F("Aktualny stav vystupu pre kotol: "));
            Serial.println(stav);
          }
        } else {
          Serial.print(F("Manualny rezim, stav vystupu: "));
          Serial.println(stav);
        }
      }
      server.handleClient();
      yield();
    }
    

    Implementácia obsahuje program pre dynamickú IPv4 adresu priradenú k Ethernet PHY termostatu z DHCP servera v sieti. Termostat je určený iba pre interiérové teploty! (nad 0°C), čomu je prispôsobená aj logika systému! Termostatom je možné nahradiť už existujúci izbový termostat, možno dočasne nahradiť ohrievač v akváriu / teráriu pre udržiavanie stálej teploty.