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: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: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 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 |
/*|----------------------------------------------------------|*/
/*|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();
}
/*|----------------------------------------------------------|*/
/*|HTTP webserver - WiFi thermostat - ESP32 + DS18B20 |*/
/*|AUTHOR: Martin Chlebovec |*/
/*|EMAIL: martinius96@gmail.com |*/
/*|DONATE: paypal.me/chlebovec |*/
/*|Arduino Core 2.0.7 (August 2022) |*/
/*|----------------------------------------------------------|*/
#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();
}