Projekt-Repository für Ethernet-Thermostate - Maschinencodes: Github
Version 1.0.4 des Ethernet-Thermostat-Projekts ist das neueste kostenlose Versions-Update. Andere Fehler, Fehler und Sicherheitsfehler werden nicht behoben!
Die Firmware für den Ethernet-Thermostat ist vollständig in der deutschen Sprachversion verfügbar.
Unterstützen Sie das Ethernet-Thermostat-Projekt über PayPal. Der Support ermöglicht es Ihnen, in Zukunft neue Funktionen hinzuzufügen und den Quellcode der Anwendung zu öffnen: PayPal donate
Arduino ist eine praktische eingebettete Plattform, mit der beispielsweise ein Raumthermostat mit Ethernet-Konnektivität und einem auf Arduino implementierten Webserver erstellt werden kann.
Die Implementierung verwendet Arduino Uno / Nano mit AVR ATmega328P-Chip in Kombination mit dem Ethernet-Shield Wiznet W5100 / W5500, mit dem es über SPI-Bus kommuniziert, resp. ICSP-Header.
Der Thermostat arbeitet programmgesteuert im Webserver-Modus, wo er über das HTTP-Protokoll Anfragen von Clients im Netzwerk empfangen und ihnen eine Antwort senden kann - HTML / JSON-Code und die Backend-Funktion (Funktionalität) ausführen, wenn sie gestartet werden.
Der Thermostat ist über das LAN-Netzwerk, in dem er sich befindet, zugänglich, während er mit einer Webschnittstelle ausgestattet ist, mit der alle Elemente des Thermostats konfiguriert werden können, d. Soll-(Referenz-)Temperatur und Hysterese.
Der Webserver ermöglicht den Betrieb mehrerer unabhängiger HTML-Seiten, die informativ oder sogar funktional sein können (implementiertes Backend).
Der Webserver läuft auf einem Standard-HTTP-Port - 80.
Der Thermostat kann über den GPIO-Ausgang automatisch das Melderelais zum Ein-/Ausschalten des Kessels ansteuern.
Es kann somit ein vorhandenes Raumthermostat ersetzen und den Clients im Netzwerk zur Verfügung stellen.
Der Thermostat kann jedes Gerät mit einem Browser bedienen - Computer / Smartphone / Tablet / Smart TV und dergleichen.
Der Entscheidungsalgorithmus verwendet eine Solltemperatur mit Hysterese, die mit der von einem digitalen Temperatursensor Dallas DS18B20 gemessenen Temperatur verglichen wird.
Solltemperatur und Hysterese werden aus dem EEPROM-Speicher gelesen, wo sie auch bei Stromausfall dauerhaft gespeichert und beim Schreiben neuer Daten überschrieben werden.
Die Auflösung des DS18B20-Sensors bei der Messung beträgt 12 Bit, was durch die Auflösung bis zu Temperaturen von 0,0625 ° C angegeben wird, was der minimale Auflösungsschritt zwischen verschiedenen Messungen ist.
Daten über den OneWire-Bus können bei Bedarf in 500 bis 1000 ms zum Mikrocontroller kommen, abhängig von der Anzahl der Sensoren am OneWire-Bus, der Länge des Busses usw. ...
Die Entscheidungslogik des Thermostats wird alle 10 Sekunden unabhängig von der Webanwendung ausgeführt, es ist keine Keep-Alive-Verbindung erforderlich, um die Logik auszuführen, so dass das System autonom arbeitet und keine Aufmerksamkeit des Benutzers erfordert.
Temperatursensor DS18B20 am OneWire-Bus im TO-92-Gehäuse oder in wasserdichter Ausführung im Aluminiumrohr
Elektromagnetisches Relais SRD-5VDC-SL-C / SSR-Relais OMRON G3MB-202P zur Kesselschaltung (Active-LOW-Signal)
HTML-Seiten, die auf Arduino ausgeführt werden:
/ - Grundseite mit Formular, aktueller Logikausgang für Relais, Temperatur
/action.html - verarbeitet Werte aus dem Formular, schreibt sie in den EEPROM-Speicher, leitet den Benutzer zurück zur Stammseite
/get_data.json - verteilt aktuelle Temperatur-, Referenztemperatur- und Hysteresedaten an Dritte (Computer, Mikrocontroller, anderer Client ...) im JSON-Format
Das im Projekt verwendete elektromagnetische Relais SRD-5VDC-SL-C ermöglicht das Schalten von bis zu 10A bei 230V - maximale Leistung 2300W.
Beim Schalten eines Gleichstromkreises (Last) können 300W (10A bei 30V DC max.) geschaltet werden.
Das OMRON G3MB-202P SSR-Relais ist auch voll kompatibel zum Schaltplan, der nur für nicht induktive Lasten und ausschließlich für AC-Lasten geeignet ist (DC-Kreis kann nach dem Einschalten nicht öffnen).
Maximale Schaltleistung 460W (230V, 2A). Der Thermostat kann das ganze Jahr über verwendet werden. Im Falle einer unnötigen Steuerung kann der Ausgang physisch getrennt werden und der Thermostat kann als Ethernet-Thermometer verwendet werden, um Daten aus dem Raum zu erhalten, in dem er sich befindet. Die Webschnittstelle für den Ethernet-Thermostat ermöglicht Ihnen:
Sehen Sie in Echtzeit die Temperatur des DS18B20-Sensors, die Betriebszeit des Geräts, den Ausgabewert mit dynamischer Änderung, die aktuell eingestellten Konfigurationsdaten für den Thermostat, d. Solltemperatur und Hysterese
Ändern Sie die Zieltemperatur (Referenztemperatur) von 5 bis 50 °C in Schritten von 0,25 °C
Ändern Sie die Hysterese von 0 bis 10 °C in Schritten von 0,25 °C
Kessel EIN / AUS-Steuerung:
Beispiel für EIN / AUS der Heizungssteuerung - VISUALISIERUNG IST NICHT TEIL DES PROJEKTS
Der Kessel ist aktiv, solange die Solltemperatur + Hysterese erreicht ist
Die Visualisierung von Wassertemperaturen zeigt die sogenannte Heizlauf und anschließendes Abkühlen des Wassers bis die Heizung wieder aktiviert wird, wenn die gemessene Temperatur unter der eingestellten Solltemperatur liegt - Hysterese
Die Weboberfläche ist für größere und kleinere Bildschirme ausgelegt. Es ist reaktionsschnell, unterstützt Breitbild-High-Definition-Bildschirme, aber auch 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 Arduino ausgeführt wird.
Da der Arduino Uno speicherbegrenzt ist, kann er nur Seiten mit einer Größe von wenigen KB ausführen. Durch den Import von CSS-Stilen von einem externen Server können Sie die Leistung und Speicherbelastung von Arduino reduzieren.
Die Software-Implementierung (für Arduine Uno) verwendet ca. 70% des Flash-Speichers (32kB - 4kB Bootloader) und 44% des RAM-Speichers (2kB).
Statische Teile einer Webseite (HTML-Dokument-Header und -Fußzeile, Bootstrap-CSS-Verknüpfung, Meta-Tags, HTTP-Response-Header, Inhaltstyp, Formular und mehr) werden direkt im Flash-Speicher von Arduino gespeichert, was den für den Benutzer verwendeten RAM erheblich reduzieren kann -generierte Inhalte.
Der Webserver ist dadurch stabiler und kann die Mehrfachverbindung mehrerer Geräte im Netzwerk gleichzeitig verarbeiten. Der Verbrauch des gesamten Thermostats beträgt bis zu 200 mA bei 5V Versorgung - unter 1W.
Um die eingestellten Werte auch nach einem Stromausfall beizubehalten, werden diese im EEPROM-Speicher des Arduino (insgesamt 512 B) abgelegt.
Die Referenztemperatur wird auf Offset 10 geschrieben, Hysterese auf Offset 100. Jeder der Werte belegt maximal 5B im EEPROM-Speicher + Terminator.
Die EEPROM-Transkriptionsgrenze liegt bei 100.000 Transkripten. Daten werden nur überschrieben, wenn das HTML-Formular gesendet wird. Der Betrieb des Thermostaten schont somit den EEPROM-Speicher, um dessen Lebensdauer zu maximieren.
Wenn das Gerät beim ersten Start nichts auf den genannten EEPROM-Offsets gespeichert hat, wird automatisch mit Standardwerten geschrieben - Referenz: 20,25 ° C, Hysterese 0,25 ° C, sog. ausfallsichere Lösung, damit der Thermostat sofort funktions- und betriebsbereit ist.
Mit dem Refresh-Meta-Tag aktualisiert der Webserver alle 30 Sekunden die gesamte Seite und die ungefähre Aktualisierungszeit 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. Basierend auf dem Feedback von Nutzern von Android-Geräten wurde die Aktualisierungszeit von 10 auf 30 Sekunden verlängert.
Da die eingebaute Ethernet-Bibliothek die Verwendung eines asynchronen Webservers (der beispielsweise mit den Espressif ESP8266 / ESP32 Mikrocontrollern verwendet werden kann) nicht beinhaltet, ist es notwendig die gesamte Seite neu zu schreiben.
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 Logik des Systems 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 (115200 baud/s) geschrieben.
Auf der Website des Thermostats findet der Benutzer auch Informationen zur Betriebszeit des Geräts (wie lange es in Betrieb war), d. Zeit in Tagen, Stunden, Minuten und Sekunden.
Der Autor des Ethernet-Thermostats ist nicht verantwortlich für die Funktionalität des Thermostats, Kesselausfall, Stromschlag aufgrund einer unsachgemäßen Installation des Thermostats im Netzwerk. Der Thermostat wird unter der MIT-Lizenz vertrieben.
Hauptseite zur Änderung der Solltemperatur und der Hysterese - Ausgabe am Beispiel:
Beispieldaten
Zieltemperatur: 22.75 °C
Hysterese: 0.25 ° C
Messdaten: 22.49 ° C
Ausgang: EIN
Der Thermostat heizt ab einer gemessenen Temperatur von 22.49 °C und darunter.
Wenn die Temperatur 23.01 °C erreicht, wird der Ausgang ausgeschaltet, das Melderelais öffnet und der Gaskessel hört auf zu heizen.
Das Aufheizen und Abkühlen des Raumes, in dem die Messungen durchgeführt werden, erfolgt.
Der Thermostat wird erst wieder aktiviert, wenn die Temperatur 22.49 °C oder niedriger erreicht.
Verarbeitung gültiger und ungültiger Daten aus HTML-Formular:JSON-Ausgabe von Ziel, gemessener Temperatur und Hysterese:
Ausgabe an UART-Monitor - Zustände der Ausgänge, Einstellung des Ethernet-Adapters:
Verfügbare Bibliotheken für Mikrocontroller (Arduino)
Bibliotheksarchiv (.zip) expandieren zu C:/Users/[User]/Documents/Arduino/libraries
Name der Bibliothek
Bibliotheksfunktion
Herunterladen
Dallas
Bibliothek für AVR-Mikrocontroller (ATmega) Arduino Uno / Nano / Mega.
Es ermöglicht die Kommunikation mit dem Dallas DS18B20-Sensor auf dem OneWire-Bus. Kommunikationsmöglichkeit nach normaler oder parasitärer Verbindung.
/*|----------------------------------------------------------|*/
/*|HTTP webserver - FORM - HTML - PROCESSING - EEPROM |*/
/*|AUTHOR: Martin Chlebovec |*/
/*|EMAIL: martinius96@gmail.com |*/
/*|DONATE: paypal.me/chlebovec |*/
/*|----------------------------------------------------------|*/
#include <avr\wdt.h>
#include <SPI.h>
#include <Ethernet.h>
#include <EEPROM.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#define ONE_WIRE_BUS 5
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensorsA(&oneWire);
const int rele = 6;
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; //physical mac address
EthernetServer server(80); //server port
const char terminator1[2] = " ";
const char terminator3[2] = "?";
const char terminator4[2] = "&";
const char terminator5[2] = "=";
unsigned long cas = 0;
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 teplota;
String stav;
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) == '.') {
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, String data)
{
int _size = data.length();
int i;
for (i = 0; i < _size; i++)
{
EEPROM.write(add + i, data[i]);
}
EEPROM.write(add + _size, '\0'); //Add termination null character for String Data
}
String read_String(char add)
{
int i;
char data[100]; //Max 100 Bytes
int len = 0;
unsigned char k;
k = EEPROM.read(add);
while (k != '\0' && len < 500) //Read until null character
{
k = EEPROM.read(add + len);
data[len] = k;
len++;
}
data[len] = '\0';
return String(data);
}
void setup() {
String a = read_String(10);
String b = read_String(100);
if (a == "" || a == NULL) {
writeString(10, "20.25");
}
if (b == "" || b == NULL) {
writeString(100, "0.25");
}
sensorsA.begin();
pinMode(rele, OUTPUT);
digitalWrite(rele, HIGH);
Serial.begin(115200);
sensorsA.requestTemperatures();
delay(2000);
Ethernet.begin(mac);
server.begin();
Serial.println(F("Webanwendung erstellt: Martin Chlebovec"));
Serial.println(F("Build 1.0.4 z 18. Sep 2021"));
Serial.println(F("IP-Adresse des Ethernet-Thermostats:"));
Serial.println(Ethernet.localIP());
wdt_enable(WDTO_8S);
}
void loop() {
wdt_reset();
if ((millis() - cas) >= 10000 || cas == 0) {
cas = millis();
Serial.println(F("IP-Adresse des Ethernet-Thermostats:"));
Serial.println(Ethernet.localIP());
teplota = sensorsA.getTempCByIndex(0);
String referencia = read_String(10);
String hystereza = read_String(100);
float referencia_teplota = referencia.toFloat();
float hystereza_teplota = hystereza.toFloat();
float minus_hystereza_teplota = (-1 * hystereza_teplota);
float rozdiel = referencia_teplota - teplota;
if (rozdiel > hystereza_teplota) {
Serial.println(F("Ausgang ein"));
stav = "EIN";
digitalWrite(rele, LOW);
} else if (rozdiel < minus_hystereza_teplota) {
Serial.println(F("Ausgang aus"));
stav = "AUS";
digitalWrite(rele, HIGH);
} else {
Serial.println(F("Die Differenz zwischen Soll- und Ist-Temperatur liegt nicht über oder unter der Hysterese. Der Ausgangszustand ändert sich nicht."));
Serial.print(F("Aktuelle Leistung des Kessels: "));
Serial.println(stav);
}
Ethernet.maintain();
sensorsA.requestTemperatures();
}
EthernetClient client = server.available();
if (client) {
while (client.connected()) {
if (client.available()) {
String line = client.readStringUntil('\n');
char str[line.length() + 1];
line.toCharArray(str, line.length());
char *method;
char *request;
method = strtok(str, terminator1);
request = strtok(NULL, terminator1);
if (String(request) == "/") {
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;
//HLAVNA ROOT HTTP STRANKA
client.println(F("HTTP/1.1 200 OK"));
client.println(F("Content-Type: text/html"));
client.println();
client.println(F("<!DOCTYPE html>"));
client.println(F("<html>"));
client.println(F("<head>"));
client.println(F("<script type='text/javascript'>"));
client.println(F("window.smartlook||(function(d) {"));
client.println(F("var o=smartlook=function(){ o.api.push(arguments)},h=d.getElementsByTagName('head')[0];"));
client.println(F("var c=d.createElement('script');o.api=new Array();c.async=true;c.type='text/javascript';"));
client.println(F("c.charset='utf-8';c.src='https://rec.smartlook.com/recorder.js';h.appendChild(c);"));
client.println(F("})(document);"));
client.println(F("smartlook('init', '6beae97f98b9844b761672af23f38fc60b962338');"));
client.println(F("</script>"));
client.println(F("<meta charset='utf-8'>"));
client.println(F("<meta name='author' content='Martin Chlebovec'>"));
client.println(F("<meta http-equiv='Refresh' content='30'; />"));
client.println(F("<meta name='viewport' content='width=device-width, initial-scale=1'>"));
client.println(F("<link rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css'>"));
client.println(F("<script type='text/javascript'>"));
client.println(F("var timeleft = 30;"));
client.println(F("var downloadTimer = setInterval(function(){"));
client.println(F("if(timeleft <= 0){"));
client.println(F("clearInterval(downloadTimer);"));
client.println(F("document.getElementById(\"countdown\").innerHTML = \"Aktualisierung...\";"));
client.println(F("} else {"));
client.println(F("document.getElementById(\"countdown\").innerHTML = timeleft + \" Sekunden zum Aktualisieren\";"));
client.println(F("}"));
client.println(F("timeleft -= 1;"));
client.println(F("}, 1000);"));
client.println(F("</script>"));
client.println(F("<title>HTTP webserver - Arduino + Ethernet</title>"));
client.println(F("</head>"));
client.println(F("<body>"));
client.println(F("<center><h3>Daten für Webserver eingeben (wird im EEPROM gespeichert):</h3>"));
client.println(F("<form action='/action.html' method='get'>"));
client.println("<b>Zieltemperatur:</b><br><input type='text' id='fname' name='fname' value=" + read_String(10) + "><br>");
client.println("<b>Hysterese:</b><br><input type='text' id='fname2' name='fname2' value=" + read_String(100) + "><br>");
client.println(F("<input type='submit' class='btn btn-success' value='Schreiben'>"));
client.println(F("</form><hr>"));
if (stav == "EIN") {
client.println(F("<b><font color='green'>Ausgang: Ein</font></b>"));
}
if (stav == "AUS") {
client.println(F("<b><font color='red'>Ausgang: Aus</font></b>"));
}
client.println(F("<div id=\"countdown\"></div>"));
client.print(F("<b>Aktuelle Temperatur des DS18B20-Sensors:</b> "));
client.print(teplota);
client.println(F(" °C"));
client.print(F("<hr>"));
client.print(F("<b>Uptime: </b>"));
client.print(days);
client.print(F("d"));
client.print(F(" "));
client.print(hours);
client.print(F("h"));
client.print(F(" "));
client.print(minutes);
client.print(F("m"));
client.print(F(" "));
client.print(seconds);
client.print(F("s"));
client.println(F("<h3>Autor: Martin Chlebovec - martinius96@gmail.com - https://martinius96.github.io/termostat-ethernet/de/</h3>"));
client.println(F("<h4>Freie Version - 1.0.4 build: 18. Sep. 2021</h4>"));
client.println(F("</center>"));
client.println(F("</body>"));
client.println(F("</html>"));
delay(1);
client.stop();
client.flush();
} else if (String(request) == "/get_data.json") {
//PODSTRANKA PRE VYCITANIE DAT (INYM MIKROKONTROLEROM)
client.println(F("HTTP/1.1 200 OK"));
client.println(F("Content-Type: application/json"));
client.println();
client.println(F("{"));
client.print(F("\"Hysteresis\":"));
client.print(read_String(100));
client.println(F(","));
client.print(F("\"Target_Temperature\":"));
client.print(read_String(10));
client.println(F(","));
client.print(F("\"Actual_Temperature\":"));
client.println(String(teplota));
client.println(F("}"));
delay(1);
client.stop();
client.flush();
} else if (String(request) == "/favicon.ico") { //fix chybajuceho faviconu
client.stop();
} else {
String myString = String(request);
if (myString.startsWith("/action.html")) {
char* parameter;
char* value;
char* hodnota1;
char* hodnota2;
parameter = strtok(request, terminator3);
Serial.println(parameter);
value = strtok(NULL, terminator3);
hodnota1 = strtok(value, terminator4);
hodnota2 = strtok(NULL, terminator4);
char* H_1;
char* H_2;
strtok(hodnota1, terminator5);
H_1 = strtok(NULL, terminator5);
strtok(hodnota2, terminator5);
H_2 = strtok(NULL, terminator5);
String first_param = String(H_1);
String second_param = String(H_2);
if (isFloat(first_param)) {
writeString(10, String(H_1));
} else {
Serial.println(F("Benutzereingabe für Taste fname (Zieltemperatur) ist keine Zahl!"));
}
if (isFloat(second_param)) {
writeString(100, String(H_2));
} else {
Serial.println(F("Benutzereingabe für Taste fname2 (Hysterese) ist keine Zahl!"));
}
client.println(F("HTTP/1.1 200 OK"));
client.println(F("Content-Type: text/html"));
client.println();
client.println(F("<!DOCTYPE html>"));
client.println(F("<html>"));
client.println(F("<head>"));
client.println(F("<meta charset='utf-8'>"));
client.println(F("<meta http-equiv='Refresh' content='5; url=/' />"));
client.println(F("<title>HTTP webserver - Arduino + Ethernet</title>"));
client.println(F("</head>"));
client.println(F("<body>"));
client.println(F("<center><h3>Der Server hat Daten vom Formular erhalten:</h3>"));
if (!isFloat(second_param) || !isFloat(second_param)) {
client.println(F("<h3><font color='red'>Eingegebene Daten sind keine Zahlen !!! Bitte versuchen Sie es nach der Umleitung erneut.</font></h3>"));
} else {
client.println("<li><b>Referenčná teplota: </b>" + String(H_1) + "</li>");
client.println("<li><b>Hysteréza: </b>" + String(H_2) + "</li>");
}
client.println(F("<b>Umleitung ... Bitte warten</b></center>"));
client.println(F("</body>"));
client.println(F("</html>"));
delay(1);
client.stop();
client.flush();
} else {
client.println(F("HTTP/1.1 404 Not Found"));
client.println();
delay(1);
client.stop();
client.flush();
}
}
}
}
}
}
Die Implementierung enthält Programme für die dem Ethernet-Shield zugewiesene statische / dynamische IPv4-Adresse.
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.