Hallo und herzlich willkommen zu unserem heutigen Blog-Beitrag, bei dem wir das Thema Webserver auf dem ESP weiterführen.
Es haben sich einige fleißige Leser bei uns gemeldet, die versucht haben die Buttons aus unserem vorherigen Beitrag zum ESP Webserver in ihren Code einzubinden. Leider ohne Erfolg.
Für jemanden der gerade angefangen hat sich mit Programmierung von Mikrocontrollern zu beschäftigen, und wenig bis keine Erfahrung mit HTML hat, und dann zusätzlich noch mit CSS-Code überschüttet wird, ist das alles auch sehr verwirrend.
Wenn dann noch alle drei in einem Textdokument kombiniert werden, wird es auch sehr schnell unübersichtlich.
Lassen Sie sich davon jedoch nicht entmutigen. Ich zeige Ihnen heute wie der funktionierende Code mit den schönen Buttons aussieht. Ich habe dabei bewusst darauf verzichtet den Code kompakt zu halten, damit er etwas besser zu lesen ist.
Sie werden aber schnell sehen wie aufwendig der Quellcode werden kann, auch wenn wir im Endeffekt nur das Aussehen von 2 Buttons ändern wollen.
Unser Ziel heute ist also folgende Änderung:
Hier zum Vergleich die beiden Sketche:
Wie Sie unschwer erkennen können, nimmt alleine die Gestaltung der Buttons mittels CSS einen großen Teil in Beschlag. Diesen Teil hatten jedoch die meisten Leser gemeistert.
Als schwierig erwies sich dann der Teil der den Buttons die Funktionalität gibt. Dieser wurde im HTML-Code auf der Beispielseite für die Buttons nämlich nicht implementiert.
Dies ist vermutlich bei den meisten Vorlagen und Beispielen die Sie zu den Buttons und anderen Elementen im Netz finden werden. Daher eine kurze Erklärung wie Sie dies anhand unseres ursprünglichen Codes umsetzen können. Es gibt wie immer viele Lösungen welche zum Ziel führen, und die von mir gezeigte entspricht mit Sicherheit nicht der einfachsten oder elegantesten.
Die Buttons die wir bis jetzt verwendet haben sehen, wenn nur auf den Button reduziert, als HTML Code wie folgt aus:
<a href="/de/5/on"><button class="button">EIN</button></a>
In dem Beispiel auf http://jsfiddle.net/tovic/ve8mU/light/ steht als HTML Code für einen Button:
<span class="toggle">
<input type="checkbox">
<label data-off="Stop" data-on="Play"></label>
</span>
Es fehlt also die Möglichkeit mittels dem <a> Tag einen Link zu definieren, weil das ganze eine Checkbox ist, und diese bei einem Klick zwar den zustand ändert, aber nicht mit dem Server kommuniziert. Das liegt daran dass diese Auswahlfelder in der Regel für Formulare verwendet werden, bei denen man ein Formular ausfüllt, und am Ende durch einen Klick auf einen Button (z.B. "Submit") an den Server sendet.
Damit sofort beim Klicken etwas passiert, müssen wir onclick='window.location.assign()' verwenden, und zwischen den Klammern die URL angeben die aufgerufen werden soll:
<span class=\"toggle\">
<input type=\"checkbox\" onclick='window.location.assign(\"/4/EIN\")'>
<label data-off=\"AUS\" data-on=\"EIN\"></label>
</span>
Und für den Fall dass der Button auf "EIN" steht:
<span class=\"toggle\">
<input type=\"checkbox\" onclick='window.location.assign(\"/4/AUS\")' checked> <label data-off=\"AUS\" data-on=\"EIN\"></label>
</span>
Beim zweiten setzen wir das <input> Objekt noch auf "checked".
Der komplette, überarbeitete Code sieht dann so aus:
/********* Rui Santos Complete project details at http://randomnerdtutorials.com *********/ // Wir laden die uns schon bekannte WiFi Bibliothek #include <ESP8266WiFi.h> // Hier geben wir den WLAN Namen (SSID) und den Zugansschlüssel ein const char* ssid = "IhrWLANName"; const char* password = "IhrWLANSchlüssel"; // Wir setzen den Webserver auf Port 80 WiFiServer server(80); // Eine Variable um den HTTP Request zu speichern String header; // Hier wird der aktuelle Status des Relais festgehalten String output5State = "AUS"; String output4State = "AUS"; // Die verwendeted GPIO Pins // D1 = GPIO5 und D2 = GPIO4 - einfach bei Google nach "Amica Pinout" suchen const int output5 = 5; const int output4 = 4; void setup() { Serial.begin(115200); // Die definierten GPIO Pins als output definieren ... pinMode(output5, OUTPUT); pinMode(output4, OUTPUT); // ... und erstmal auf LOW setzen digitalWrite(output5, LOW); digitalWrite(output4, LOW); // Per WLAN mit dem Netzwerk verbinden Serial.print("Connecting to "); Serial.println(ssid); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } // Die IP vom Webserver auf dem seriellen Monitor ausgeben Serial.println(""); Serial.println("WLAN verbunden."); Serial.println("IP Adresse: "); Serial.println(WiFi.localIP()); server.begin(); } void loop(){ WiFiClient client = server.available(); // Hört auf Anfragen von Clients if (client) { // Falls sich ein neuer Client verbindet, Serial.println("Neuer Client."); // Ausgabe auf den seriellen Monitor String currentLine = ""; // erstelle einen String mit den eingehenden Daten vom Client while (client.connected()) { // wiederholen so lange der Client verbunden ist if (client.available()) { // Fall ein Byte zum lesen da ist, char c = client.read(); // lese das Byte, und dann Serial.write(c); // gebe es auf dem seriellen Monitor aus header += c; if (c == '\n') { // wenn das Byte eine Neue-Zeile Char ist // wenn die aktuelle Zeile leer ist, kamen 2 in folge. // dies ist das Ende der HTTP-Anfrage vom Client, also senden wir eine Antwort: if (currentLine.length() == 0) { // HTTP-Header fangen immer mit einem Response-Code an (z.B. HTTP/1.1 200 OK) // gefolgt vom Content-Type damit der Client weiss was folgt, gefolgt von einer Leerzeile: client.println("HTTP/1.1 200 OK"); client.println("Content-type:text/html"); client.println("Connection: close"); client.println(); // Hier werden die GPIO Pins ein- oder ausgeschaltet if (header.indexOf("GET /5/EIN") >= 0) { Serial.println("GPIO 5 EIN"); output5State = "EIN"; digitalWrite(output5, HIGH); } else if (header.indexOf("GET /5/AUS") >= 0) { Serial.println("GPIO 5 AUS"); output5State = "AUS"; digitalWrite(output5, LOW); } else if (header.indexOf("GET /4/EIN") >= 0) { Serial.println("GPIO 4 EIN"); output4State = "EIN"; digitalWrite(output4, HIGH); } else if (header.indexOf("GET /4/AUS") >= 0) { Serial.println("GPIO 4 AUS"); output4State = "AUS"; digitalWrite(output4, LOW); } // Hier wird nun die HTML Seite angezeigt: client.println("<!DOCTYPE html><html>"); client.println("<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"> <title>Amica WebServer</title>"); client.println("<link rel=\"icon\" href=\"data:,\">"); // Es folgen der CSS-Code um die Ein/Aus Buttons zu gestalten client.println("<style> body {"); client.println(" font-family:Helvetica;"); client.println(" background-color:#eee;"); client.println(" text-align:center;"); client.println(" padding:85px 0;"); client.println("}"); client.println(""); client.println(".toggle {"); client.println(" position:relative;"); client.println(" display:inline-block;"); client.println(" width:80px;"); client.println(" height:120px;"); client.println(" background-color:#bbb;"); client.println(" -webkit-border-radius:4px;"); client.println(" -moz-border-radius:4px;"); client.println(" border-radius:4px;"); client.println(" text-align:center;"); client.println("}"); client.println(""); client.println(".toggle input {"); client.println(" width:100%;"); client.println(" height:100%;"); client.println(" margin:0 0;"); client.println(" padding:0 0;"); client.println(" position:absolute;"); client.println(" top:0;"); client.println(" right:0;"); client.println(" bottom:0;"); client.println(" left:0;"); client.println(" z-index:2;"); client.println(" cursor:pointer;"); client.println(" opacity:0;"); client.println(" filter:alpha(opacity=0);"); client.println("}"); client.println(""); client.println(".toggle label {"); client.println(" display:block;"); client.println(" position:absolute;"); client.println(" top:1px;"); client.println(" right:1px;"); client.println(" bottom:1px;"); client.println(" left:1px;"); client.println(" background-image:-webkit-linear-gradient(top,#fff 0%,#ddd 50%,#fff 50%,#eee 100%);"); client.println(" background-image:-moz-linear-gradient(top,#fff 0%,#ddd 50%,#fff 50%,#eee 100%);"); client.println(" background-image:-ms-linear-gradient(top,#fff 0%,#ddd 50%,#fff 50%,#eee 100%);"); client.println(" background-image:-o-linear-gradient(top,#fff 0%,#ddd 50%,#fff 50%,#eee 100%);"); client.println(" background-image:linear-gradient(top,#fff 0%,#ddd 50%,#fff 50%,#eee 100%);"); client.println(" -webkit-box-shadow:0 2px 3px rgba(0,0,0,0.4),"); client.println(" inset 0 -1px 1px #888,"); client.println(" inset 0 -5px 1px #bbb,"); client.println(" inset 0 -6px 0 white;"); client.println(" -moz-box-shadow:0 2px 3px rgba(0,0,0,0.4),"); client.println(" inset 0 -1px 1px #888,"); client.println(" inset 0 -5px 1px #bbb,"); client.println(" inset 0 -6px 0 white;"); client.println(" box-shadow:0 2px 3px rgba(0,0,0,0.4),"); client.println(" inset 0 -1px 1px #888,"); client.println(" inset 0 -5px 1px #bbb,"); client.println(" inset 0 -6px 0 white;"); client.println(" -webkit-border-radius:3px;"); client.println(" -moz-border-radius:3px;"); client.println(" border-radius:3px;"); client.println(" font:normal 11px Arial,Sans-Serif;"); client.println(" color:#666;"); client.println(" text-shadow:0 1px 0 white;"); client.println(" cursor:text;"); client.println("}"); client.println(""); client.println(".toggle label:before {"); client.println(" content:attr(data-off);"); client.println(" position:absolute;"); client.println(" top:6px;"); client.println(" right:0;"); client.println(" left:0;"); client.println(" z-index:4;"); client.println("}"); client.println(""); client.println(".toggle label:after {"); client.println(" content:attr(data-on);"); client.println(" position:absolute;"); client.println(" right:0;"); client.println(" bottom:11px;"); client.println(" left:0;"); client.println(" color:#666;"); client.println(" text-shadow:0 -1px 0 #eee;"); client.println("}"); client.println(""); client.println(".toggle input:checked + label {"); client.println(" background-image:-webkit-linear-gradient(top,#eee 0%,#ccc 50%,#fff 50%,#eee 100%);"); client.println(" background-image:-moz-linear-gradient(top,#eee 0%,#ccc 50%,#fff 50%,#eee 100%);"); client.println(" background-image:-ms-linear-gradient(top,#eee 0%,#ccc 50%,#fff 50%,#eee 100%);"); client.println(" background-image:-o-linear-gradient(top,#eee 0%,#ccc 50%,#fff 50%,#eee 100%);"); client.println(" background-image:linear-gradient(top,#eee 0%,#ccc 50%,#fff 50%,#eee 100%);"); client.println(" -webkit-box-shadow:0 0 1px rgba(0,0,0,0.4),"); client.println(" inset 0 1px 7px -1px #ccc,"); client.println(" inset 0 5px 1px #fafafa,"); client.println(" inset 0 6px 0 white;"); client.println(" -moz-box-shadow:0 0 1px rgba(0,0,0,0.4),"); client.println(" inset 0 1px 7px -1px #ccc,"); client.println(" inset 0 5px 1px #fafafa,"); client.println(" inset 0 6px 0 white;"); client.println(" box-shadow:0 0 1px rgba(0,0,0,0.4),"); client.println(" inset 0 1px 7px -1px #ccc,"); client.println(" inset 0 5px 1px #fafafa,"); client.println(" inset 0 6px 0 white;"); client.println("}"); client.println(""); client.println(".toggle input:checked:hover + label {"); client.println(" -webkit-box-shadow:0 1px 3px rgba(0,0,0,0.4),"); client.println(" inset 0 1px 7px -1px #ccc,"); client.println(" inset 0 5px 1px #fafafa,"); client.println(" inset 0 6px 0 white;"); client.println(" -moz-box-shadow:0 1px 3px rgba(0,0,0,0.4),"); client.println(" inset 0 1px 7px -1px #ccc,"); client.println(" inset 0 5px 1px #fafafa,"); client.println(" inset 0 6px 0 white;"); client.println(" box-shadow:0 1px 3px rgba(0,0,0,0.4),"); client.println(" inset 0 1px 7px -1px #ccc,"); client.println(" inset 0 5px 1px #fafafa,"); client.println(" inset 0 6px 0 white;"); client.println("}"); client.println(""); client.println(".toggle input:checked + label:before {"); client.println(" z-index:1;"); client.println(" top:11px;"); client.println("}"); client.println(""); client.println(".toggle input:checked + label:after {"); client.println(" bottom:9px;"); client.println(" color:#aaa;"); client.println(" text-shadow:none;"); client.println(" z-index:4;"); client.println("}"); client.println("</style></head>"); // Webseiten-Überschrift client.println("<body><h1>ESP8266 Web Server</h1>"); // Zeige den aktuellen Status, und EIN/AUS Buttons for GPIO 5 client.println("<p>Licht Garage: " + output5State + "</p>"); // wenn output5State = AUS, zeige den EIN Button if (output5State=="AUS") { client.println("<p><span class=\"toggle\"><input type=\"checkbox\" onclick='window.location.assign(\"/5/EIN\")'><label data-off=\"AUS\" data-on=\"EIN\"></label></span></p>"); } else { client.println("<p><span class=\"toggle\"><input type=\"checkbox\" onclick='window.location.assign(\"/5/AUS\")' checked><label data-off=\"AUS\" data-on=\"EIN\"></label></span></p>"); } // Das gleiche für GPIO 4 client.println("<p>Licht Keller: " + output4State + "</p>"); // Wenn output4State = off, zeige den EIN Button if (output4State=="AUS") { client.println("<p><span class=\"toggle\"><input type=\"checkbox\" onclick='window.location.assign(\"/4/EIN\")'><label data-off=\"AUS\" data-on=\"EIN\"></label></span></p>"); } else { client.println("<p><span class=\"toggle\"><input type=\"checkbox\" onclick='window.location.assign(\"/4/AUS\")' checked><label data-off=\"AUS\" data-on=\"EIN\"></label></span></p>"); } client.println("</body></html>"); // Die HTTP-Antwort wird mit einer Leerzeile beendet client.println(); // und wir verlassen mit einem break die Schleife break; } else { // falls eine neue Zeile kommt, lösche die aktuelle Zeile currentLine = ""; } } else if (c != '\r') { // wenn etwas kommt was kein Zeilenumbruch ist, currentLine += c; // füge es am Ende von currentLine an } } } // Die Header-Variable für den nächsten Durchlauf löschen header = ""; // Die Verbindung schließen client.stop(); Serial.println("Client getrennt."); Serial.println(""); } }
Nun bleibt noch eine Frage offen: warum nutzen wir in unserem Code /4/EIN oder /5/AUS um die GPIOs zu schalten?
Auf diesem weg ist es uns möglich, in einem Browser über die Eingabe der der IP gefolgt von /"Pin Nummer"/"Status" ein gewünschtes Relais in den gewünschten Zustand zu versetzen.
Aber die Anfrage muss nicht zwangsläufig von einem Webbrowser kommen. Wenn Sie in ihrem WLAN ein anderes Gerät haben, wie z.B. einen anderen ESP, so könnte dieser mit einem einfachen HTTP-Request das gewünschte Relais schalten. An diesen zweiten ESP könnten Sie einen Lichtschalter hängen. Oder einen Bewegungsmelder.
Oder Sie könnten eine APP für Ihr Smartphone schreiben, mit Buttons, die nichts wieter machen als die gewünschte URL auf zu rufen.
Natürlich darf ich an dieser Stelle das Thema Sicherheit nicht unerwähnt lassen.
Der ESP-Server ist für jeden Teilnehmer in ihrem WLAN erreichbar, und es gibt keinerlei Sicherheitsabfrage. Das ganze ist also nur so sicher, wie Ihr WLAN. Wenn Sie freunde oder Verwandte ihren Schlüssel geben, oder Ihr WLAN Router falsch konfiguriert ist, oder eine Sicherheitslücke aufweist, kann theoretisch jeder die Relais schalten.
Für einen Lichtschalter oder ähnliches dürfte dies nicht das große Problem darstellen, aber bevor Sie anfangen Türschlösser und Garagentore zu steuern, empfehle ich Dringend sich mit dem Thema Netzwerksicherheit auseinanderzusetzen.
Hoffentlich hat der heutige Beitrag Ihnen gefallen, und aufgezeigt dass es, mit ein wenig Einarbeitung, gar nicht so kompliziert ist den ESP in Ihre Projekte einzubinden, auch wenn die Umsetzung anfangs sehr kompliziert erscheint.
Ich verabschiede mich bis zum nächsten Beitrag, und freue mich über Lob und Kritik.
Ihr Markus Neumann
7 Kommentare
Josef Bernhardt
Gratuliere zu dem tollen Programm
Ich werde die Software evtl. für mein ESP8266 E/A Modul benutzen.
https://www.linkedin.com/feed/update/urn:li:activity:6706839832758497281/
Günther
Hallo, Super Beispiel welches ich auch gerne als Vorlage für mein Projekt verwende. Hier wäre der nächste Schritt die HTML Seite mit css und js in den ESP Speicher zu verfrachten um mehr Übersicht zu schaffen. Bin aber selbst noch nicht durch.
Danke für die Beispiele.
Andreas
Danke für die tollen Artikel, damit macht es auch Leien wie mir einfach nur Spaß soetwas nachzubauen!
Und wenn das kein Argument ist die Hardware bei euch zu kaufen, dann weis ich auch nicht!!!
Bitte weiter so !!!!!
DANKE
Andreas
Danke für die tollen Artikel, damit macht es auch Leien wie mir einfach nur Spaß soetwas nachzubauen!
Und wenn das kein Argument ist die Hardware bei euch zu kaufen, dann weis ich auch nicht!!!
Bitte weiter so !!!!!
DANKE
Jo
Super Beitrag, vielen Dank!!!!
Gehöre zu der Laien-Truppe, die bisher immer an der Problematik “Button-Funktionalität” gescheitert ist. Jetzt funzt es … Nochmals Danke …
Rainer
hat super geklappt,
wie mache ich das mit 6 Schalter ?
Grüsse
Mike
Sieht einfach gut aus, wills mal probieren