Schönere Schalter für unser ESP-Webserver-Relais Projekt

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="/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

 

Letzter Artikel BMP180, BMP280 und BME280 ausführlich erklärt
Neuer Artikel Relais mit SMS schalten

Hinterlasse einen Kommentar

Kommentare müssen vor der Veröffentlichung überprüft werden

Erforderliche Angabe