Programová implementácia je navrhnutá pre mikrokontrólerové platformy od výrobcu Espressif Systems s WiFi konektivitou. Podporované WiFi (2,4 GHz) mikrokontroléry ESP8266 a ESP32. WiFi termostat je prístupný z LAN siete v ktorej sa nachádza, pričom je vybavený webovým rozhraním ktoré slúži na konfiguráciu všetkých prvkov termostatu a zároveň pre vizualizáciu aktuálnych stavov. Termostat riadi kotol na základe nameranej, cieľovej teploty a hysterézy nezávisle na webaplikácii, ktorá slúži na konfiguráciu a rozhodovací prah termostatu. Okrem dostupnosti termostatu na konkrétnej IP adrese je možné termostat doplniť o mDNS záznam, ktorý generuje lokálnu doménu (hostname.local) prístupnú iba v LAN sieti v rámci tejto multicastovej služby. Konfigurácia termostatu (z pohľadu konektivity) na domácu WiFi LAN sieť je zabezpečená prostredníctvom WiFiManagera, ktorý údaje o WiFi sieti (SSID a heslo) ukladá do flash pamäte mikrokontroléru. Uloženie je jednorázové a zariadenie si konfiguráciu pamätá permanentne. Po nadobudnutí konektivity (priradení IP adresy od routra vašej domácej WiFi siete) je možné termostat plne používať. HTTP webserver bežiaci na mikrokontorléri ESP8266 / ESP32 umožňuje beh niekoľkých na sebe nezávislých HTML stránok, ktoré môžu mať informatívny, alebo aj funkcionálny charakter.
Po hardvérovej stránke projekt využíva:Rozlíšenie senzora DS18B20 pri meraní je 12-bitov, čomu rozpovedá rozlíšenie teploty na 0.0625 °C, čo je zároveň aj minimálny krok medzi rôznými nameranými hodnotami. Dáta po OneWire zbernici môžu vrátiť do mikrokontroléru po vyžiadaní za 500 až 1000 ms v závislosti od počtu senzorov, dĺžky zbernice. Elektromagnetické relé SRD-5VDC-SL-C, ktoré je v projekte použité umožňuje spínať až 10A pri 230V - výkon 2300W. V prípade spínania jednosmerného obvodu (záťaže) je možné spínať 300W (10A pri 30V DC). Prípadne je pre schému zapojenia plne kompatibilné aj SSR relé OMRON G3MB-202P, ktoré je vhodné iba pre neindukčnú záťaž a výhradne pre obvod so striedavým napätím. 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 WiFi teplomer pre zisk dát z miestnosti, kde sa nachádza.
V prípade firmvéru s podporou manuálneho ovládania GPIO výstupu mikrokontroléru ESP je možné termostat fyzicky vypnúť bez nutnosti jeho odpojenia zo svorkovnice relé. Logika termostatu sa vykonáva každých 10 sekúnd nezávisle na webserveri a pripojených klientoch, nevyžaduje sa keep-alive spojenie pre jej vykonanie. Vždy pri vykonaní logiky vypíše na UART termostat aj informáciu o aktuálnej IP adrese, respektíve aj mDNS zázname (ak sa s mDNS firmvér používa) a dá tak používateľovi informáciu, kde v LAN sieti je termostat dosiahnuteľný so svojim webovým rozhraním. Okrem toho vypíše aj dynamickú voľnú pamäť - HEAP, ktorá sa pohybuje na úrovni 40 až 44kB a taktiež aj aktuálny stav výstupu s oznámením zmeny ak k nej dôjde --> (ak pretečie rozhodovací threshold do + respektíve do -). 3V3 operačná logika GPIO mikrokontrolérov ESP8266 a ESP32 postačuje pre digitálny signál zmeny, relé však musí byť napájané na 5V z VUSB, respektíve VIN.
Webové rozhranie pre WiFi termostat umožňuje:Do základnej verzie WiFi termostatu (bez mDNS záznamu) bol implementovaný manuálny režim ovládania (natvrdo ZAP/VYP) s možnosťou prepínania medzi manuálnym a automatickým režimom. Webové rozhranie je navrhnuté pre prispôsobenie sa väčším i menším obrazovkám. Je responzí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 ESP. Importovaním CSS štýlov z externého servera umožní znížiť výkonové a pamäťové zaťaženie mikrokontroléru.
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.
V prípade, že zariadenie pri prvom spustení nemá nič uložené na spomenutých EEPROM offsetoch, vykoná sa automatický zápis s predvolenými hodnotami - referencia: 20.25 °C, hysteréza 0.00 °C. Tým je zabezpečené, že termostat dokáže fungovať aj na mikrokontroléroch, ktoré nemajú nič zapísané v EEPROM pamäti - fail-safe riešenie. ESP8266 a ESP32 využíva pre zápis do EEPROM pamäte funkciu EEPROM.put(), ktorá podporuje akýkoľvek dátový typ a EEPROM.commit() pre potvrdenie zápisu na cieľový ofset. Implementácia používa dátový typ float() pre 32-bitové číslo, ktoré je uložené do EEPROM a prislúcha referenčnej (cieľovej) teplote a taktiež aj hysteréze. Prostredníctvom meta tagu Refresh vykonáva webserver obnovu celej HTML 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. Nakoľko built-in knižnica Ethernet neobsahuje využitie asynchrónneho webservera (ktorý je možné využiť napríklad u mikrokontrolérov Espressif ESP8266 / ESP32), je nutné prepisovať celú stránku z dôvodu, že je táto implementácia 1:1 s pôvodným Ethernet termostatom.
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. 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. 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.
Termostat vykuruje od nameranej teploty 22.49 °C a nižšej. V prípade dosiahnutia teploty 23.01 °C 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.
Názov knižnice | Funkcia knižnice | Stiahnuť |
---|---|---|
Dallas |
Knižnica pre mikrokontroléry ESP8266 a ESP32. Umožňuje komunikáciu so senzorom Dallas DS18B20 na OneWire zbernici. Možnosť komunikácie po normálnom, alebo parazitnom zapojení. |
Stiahnuť |
WiFiManager |
Knižnica pre mikrokontroléry ESP8266 a ESP32. Vytvára prístupový bod (AP) a Captive portal pre konfiguráciu WiFi termostatu na domácu WiFi sieť. |
Stiahnuť |
DNS Server |
Knižnica pre mikrokontróler ESP32. Vyžaduje sa pre správne fungovanie knižnice WiFiManager. |
Stiahnuť |
/*|----------------------------------------------------------|*/ /*|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 = "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; 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 = \"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>WiFi termostat - ESP8266</title>"); stranka += F("</head>"); stranka += F("<body>"); stranka += F("<center><h3>WiFi termostat - ESP8266:</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/WiFi-termostat/</h3>"); stranka += F("<h4>Verzia free - 1.0.3 build: 22. Jan. 2021</h4>"); stranka += F("</center>"); stranka += F("<div class='alert alert-info'>"); stranka += F("Finálny build projektu WiFi termostat. Ďakujem za vyskúšanie webaplikácie. Rozšírené verzie projektu sú platené.<br><strong>Obsah platených verzií:</strong><li>Async Webserver - AJAX update</li><li>Manuálny režim</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 (WiFi 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>WiFi termostat - ESP8266 - 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); 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 termostat - 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 addresa ESP8266 termostat: ")); Serial.print(WiFi.localIP()); Serial.print(F(", pre pristup k termostatu navstivte http://")); Serial.print(WiFi.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(); }
/*|----------------------------------------------------------|*/ /*|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> WebServer server(80); #include <EEPROM.h> #include <OneWire.h> #include <DallasTemperature.h> #define ONE_WIRE_BUS 23 //D1 OneWire oneWire(ONE_WIRE_BUS); DallasTemperature sensorsA(&oneWire); const int rele = 22; //D2 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; 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 = \"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>WiFi termostat - ESP32</title>"); stranka += F("</head>"); stranka += F("<body>"); stranka += F("<center><h3>WiFi 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/WiFi-termostat/</h3>"); stranka += F("<h4>Verzia free - 1.0.3 build: 22. Jan. 2021</h4>"); stranka += F("</center>"); stranka += F("<div class='alert alert-info'>"); stranka += F("Finálny build projektu WiFi termostat. Ďakujem za vyskúšanie webaplikácie. Rozšírené verzie projektu sú platené.<br><strong>Obsah platených verzií:</strong><li>Async Webserver - AJAX update</li><li>Manuálny režim</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 (WiFi 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>WiFi 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); 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 termostat - 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 addresa ESP32 termostat: ")); Serial.print(WiFi.localIP()); Serial.print(F(", pre pristup k termostatu navstivte http://")); Serial.print(WiFi.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(); }