Die Softwareimplementierung ist für Mikrocontroller-Plattformen des Herstellers Espressif Systems mit WiFi-Konnektivität ausgelegt. Unterstützte WiFi (2,4 GHz) ESP8266- und ESP32-Mikrocontroller. Der WiFi-Thermostat ist über das LAN-Netzwerk, in dem er sich befindet, zugänglich, während er mit einer Weboberfläche ausgestattet ist, die dazu dient, alle Elemente des Thermostats zu konfigurieren und gleichzeitig die aktuellen Zustände zu visualisieren. Der Thermostat regelt den Kessel auf Basis der gemessenen Solltemperatur und Hysterese unabhängig von der Webapplikation, die zur Konfiguration und Entscheidungsschwelle des Thermostats dient. Neben der Verfügbarkeit des Thermostats unter einer bestimmten IP-Adresse kann der Thermostat mit einem mDNS-Record ergänzt werden, der innerhalb dieses Multicast-Dienstes eine nur im LAN-Netzwerk erreichbare lokale Domain (hostname.local) generiert. Die Konfiguration des Thermostats (aus Sicht der Konnektivität) im Heim-WLAN-LAN-Netzwerk wird durch den WiFiManager gesichert, der Daten über das WLAN-Netzwerk (SSID und Passwort) im Flash-Speicher des Mikrocontrollers speichert. Die Speicherung erfolgt einmalig und das Gerät merkt sich die Konfiguration dauerhaft. Nach dem Herstellen der Konnektivität (Zuweisung einer IP-Adresse vom Router Ihres WLAN-Heimnetzwerks) kann der Thermostat vollständig verwendet werden. Der HTTP-Webserver, der auf dem ESP8266 / ESP32-Mikrocontroller läuft, ermöglicht das Ausführen mehrerer unabhängiger HTML-Seiten, die informativ oder sogar funktional sein können.
In Bezug auf die Hardware verwendet das Projekt:Die Auflösung des DS18B20 Sensors während der Messung beträgt 12 Bit, was durch die Temperaturauflösung bei 0,0625 °C angezeigt wird, was ebenfalls ein minimaler 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, je nach Anzahl der Sensoren, Buslänge. Das im Projekt verwendete elektromagnetische Relais SRD-5VDC-SL-C ermöglicht das Schalten bis 10A bei 230V - Leistung 2300W. Beim Schalten eines Gleichstromkreises (Last) können 300W (10A bei 30V DC) geschaltet werden. Alternativ ist das OMRON G3MB-202P SSR-Relais voll kompatibel zum Schaltplan, der nur für nicht induktive Lasten und ausschließlich für Wechselstromkreise geeignet ist. Maximale Schaltleistung 460W (230V, 2A). Der Thermostat kann das ganze Jahr über verwendet werden. Im Falle einer unnötigen Steuerung ist es möglich, 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 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 angeschlossenen Clients alle 10 Sekunden ausgeführt, für die Ausführung ist keine Keep-Alive-Verbindung erforderlich. Bei jeder Ausführung der Logik schreibt der Thermostat auch Informationen über die aktuelle IP-Adresse oder den mDNS-Record (bei Verwendung mit mDNS-Firmware) auf den UART-Thermostat und gibt so dem Benutzer Auskunft darüber, wo der Thermostat mit seinem Webinterface im LAN erreichbar ist Netzwerk. Darüber hinaus listet es auch den dynamischen freien Speicher - HEAP, der von 40 bis 44 kB reicht, sowie den aktuellen Status der Ausgabe mit Benachrichtigung über die Änderung, falls sie auftritt -> (wenn die Entscheidungsschwelle nach + oder nach - fließt) ). 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: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 beizubehalten, werden diese im EEPROM-Speicher ESP gespeichert, der im Flash-Speicher emuliert wird, da die Plattform keinen physikalischen EEPROM-Chip (Speicher) besitzt. Referenztemperatur auf Offset 10, Hysterese auf Offset 100. Jeder der Werte belegt maximal 5B im EEPROM-Speicher + das Endezeichen. Die Daten werden erst beim Senden des HTML-Formulars überschrieben, der Betrieb des Thermostats schont den EEPROM-Speicher für maximale Haltbarkeit. Der Zustand des Ausgangs existiert nur im RAM-Speicher, wo er bei der Änderung überschrieben wird. Der Wert wird nicht im emulierten EEPROM-Speicher im Flash-Speicher abgelegt.
Wenn das Gerät beim ersten Start nichts auf den genannten EEPROM-Offsets gespeichert hat, wird das automatische Schreiben mit Standardwerten durchgeführt - Referenz: 20,25 ° C, Hysterese 0,00 ° C. Dadurch wird sichergestellt, dass der Thermostat auf Mikrocontrollern arbeiten kann, die nichts im EEPROM-Speicher geschrieben haben - ausfallsichere Lösung. ESP8266 und ESP32 verwenden die Funktion EEPROM.put(), um in den EEPROM-Speicher zu schreiben, der jeden Datentyp unterstützt, und EEPROM.commit(), um das Schreiben in den Ziel-Offset zu bestätigen. Die Implementierung verwendet den Datentyp float() für eine 32-Bit-Zahl, die im EEPROM gespeichert wird und zur Referenz-(Soll-)Temperatur sowie 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 in das 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 ist mit dem originalen Ethernet-Thermostat.
Die sich hauptsächlich ändernden dynamischen Daten sind der aktuelle Wert der Ausgabe - EIN / AUS , die den Bediener über den aktuellen Zustand der Ausgabe zusammen mit der Farbcodierung informiert. Da die Systemlogik unabhängig vom Webserver ausgeführt wird, kann sich die Ausgabe bereits in einem anderen Zustand befinden als aktuell in der Webanwendung aufgelistet. Die Ausgabeänderung wird z. B. 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 in Betrieb war), d. Zeit in Tagen, Stunden, Minuten und Sekunden. Der Thermostat ist nur für Innentemperaturen gedacht! (über 0°C), an die die Systemlogik angepasst ist! Der Thermostat kann verwendet werden, um einen vorhandenen Raumthermostat zu ersetzen, die Heizung im Aquarium / Terrarium kann vorübergehend ersetzt werden, um eine konstante Temperatur zu halten.
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.
Name der Bibliothek | Bibliotheksfunktion | Herunterladen |
---|---|---|
Dallas |
Bibliothek für ESP8266- und ESP32-Mikrocontroller. Es ermöglicht die Kommunikation mit dem Dallas DS18B20-Sensor auf dem OneWire-Bus. Kommunikationsmöglichkeit nach normaler oder parasitärer Verbindung. |
Herunterladen |
WiFiManager |
Bibliothek für ESP8266- und ESP32-Mikrocontroller. Erstellt einen Zugangspunkt (AP) und ein Captive-Portal zum Konfigurieren eines WLAN-Thermostats in einem WLAN-Heimnetzwerk. |
Herunterladen |
DNS Server |
ESP32-Mikrocontroller-Bibliothek. Erforderlich, damit die WiFiManager-Bibliothek ordnungsgemäß funktioniert. |
Herunterladen |
#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'>"); 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(); }
#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(); }