Der ESP8266 Webserver

In dieser mehrteiligen Anleitung werde ich einen kombinierten Webserver - Webclient für den ESP8266 zeigen. Die Arduino IDE kommt zwar mit vielen Beispielen und mit allem was man benötigt aber offenbar haben Einsteiger Probleme bei der Auswahl der richtigen Vorgangsweise.

Das "HelloWorld" Beispiel für einen Webserver am ESP8266 findet sich unter

  • ESP8266Webserver | HelloServer

Im ersten Schritt werden SSID und WIFI Passwort gesetzt.

const char* ssid = "........";
const char* password = "........";

Am besten passt du auch die LED und Button PIns an deine Hardware an. Ich verwende keinen nackten ESP8266 sondern einen NodeMCU, daher verwende ich Pin 2.

const int led = 2;  // GPIO02/D4 on NodeMCU is the (blue) LED on the ESP-12E

Der Upload dieses Sketches soll problemlos funktionieren. Nach dem Upload wird eine WiFi Verbindung hergestellt werden und im seriellen Monitor wird eine IP-Adresse ausgegeben. Mit dieser IP-Adresse kann der Webserver in einem Browser aufgerufen werden.

Auf Basis dieses Sketches arbeite ich schrittweise weiter. Da ich viele Uploads machen werden - kommt als erstes das ArduinoOTA hinzu. ArduinoOTA ermöglicht den Sketch-Upload direkt aus der IDE über WiFi und ist wesentlich schneller als über die serielle Schnittstelle. ArduinoOTA verdient sich eigentlich ein eigenes Tutorial - daher nur kurz zusammengefasst die wesentlichen Teile

im Deklarationsteil ergänze ich

#include <ArduinoOTA.h> // OTA Upload via ArduinoIDE

im setup()

ArduinoOTA.begin(); // OTA Upload via ArduinoIDE

im loop()

ArduinoOTA.handle(); // OTA Upload via ArduinoIDE

Da wir den Webserver großzügig erweitern werden, wandern alle Server relevante Funktionen wie z.B. handleRoot() und handleNotfound() in einen neuen IDE-Tab "server". Das hilft später die Übersicht zu behalten.

Dann verschiebe ich alle Fixtexte aus dem RAM in den Flash/Programmspeicher des ESP8266. Dazu nehme ich einfach das F-Makro

Serial.println(F(""));
Serial.print(F("Connected to "));
Serial.println(ssid);
Serial.print(F("IP address: "));

Konsequent angewandt wird eine Menge RAM gespart - und Flash (Programmspeicher) ist am ESP8266 ausreichend zur Verfügung.

Im IDE-Tab "server" adaptiere ich auch die Seite 404 (File not found). Testweise lasse ich mir eventuell übergebene Parameter auf der Webseite ausgeben.

void handleNotFound() {
// Output a "404 not found" page. It includes the parameters which comes handy for test purposes.
Serial.println(F("D015 handleNotFound()"));
String message;
message += F("404 - File Not Found\n\n"
             "URI: ");
message += server.uri();
message += F("\nMethod: ");
message += (server.method() == HTTP_GET) ? "GET" : "POST";
message += F("\nArguments: ");
message += server.args();
message += F("\n");
for (uint8_t i = 0; i < server.args(); i++)
{
  message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
}
  server.send(404, "text/plain", message);
}

Der Beispiel-Sketch hat noch relativ wenige Seiten. Ich lege daher im Setup einige weitere Seiten an: 0.htm, 1.htm, 2.htm, x.htm
und weil es "hübsch" aussehen soll, ein Stylesheet f.css

server.on("/0.htm", handlePage);
server.on("/1.htm", handlePage1);
server.on("/2.htm", handlePage2);
server.on("/x.htm", handleOtherPage); // just another page to explain my usage of HTML pages ...
server.on("/f.css", handleCss);       // a stylesheet

x.htm ist ein Beispiel, wie man grundsätzlich eine Seite gestaltet: Seite definieren und einen Handler dazu definieren - den wir dann im Tab "server" ablegen.

Beginne zunächst mit der x.htm - dem handleOtherPage(). Du legst ein String Objekt message an. In dieses schreibst du deinen HTML Code. Am Schluss schickst du den ganzen Inhalt aus message mit HTTP Response Code 200 an den Client:

void handleOtherPage()
// a very simple example how to output a HTML page from program memory
{
  String message;
  message = F("<!DOCTYPE html>\n"
              "<html lang='en'>\n"
              "<head>\n"
              "<title>" TXT_BOARDNAME " - Board " TXT_BOARDID "</title>\n"
              "</head>\n"
              "<body>\n"
              "<h1>Your webserver on " TXT_BOARDNAME " - Board " TXT_BOARDID " is running!</h1>\n"
              "<p>This is just an example how you can create a webpage.</p>\n"
              "<p>But as most of my webserver should have the same look and feel"
              " I'm using one layout for all html pages.</p>\n"
              "<p>Therefore all my html pages come from the function handlePage()</p>\n"
              "<p>To go back to the formated pages <a href='0.htm'>use this link</a></p>\n"
              "</body>\n"
              "</html>");
  server.send(200, "text/html", message);
}

Weiter ausgebaut sind die Seiten 0.htm, 1.htm, 2.htm. Diese Seiten teilen sich den Beginn des HTML Dokuments. Das beinhaltet die HTML Metadaten, die Überschrift und das Menü. Daher schreibst du dir eine Helper Funktion addTop(). Diese Übernimmt eine REFERENZ (&) auf ein String Objekt und ergänzt diese um Daten. In anderen Worten: du lagerst den Kopf der Datei in eine Funktion aus damit du sie öfters wiederverwenden kannst.

Ebenso bereitest du den Schluss der HTML Datei vor: Am Ende sind im wesentlichen der Footer mit ein paar Infodaten und die schließenden HTML bzw Body Tags. Nenne die Funktion addBottom(). Auch diese übernimmt wieder eine Referenz auf ein String Objekt.

Schau dir nun die handlePage() an:

void handlePage()
{
String message;
addTop(message);

message += F("<article>\n"
"<h2>Homepage</h2>\n" // here you write your html code for your homepage. Let's give some examples...
"<p>This is an example for a webserver on your ESP8266. "
"Values are getting updated with Fetch API/JavaScript and JSON.</p>\n"
"</article>\n");

message += F("<article>\n"
"<h2>Values (with update)</h2>\n");
message += F("<p>Internal Voltage measured by ESP: <span id='internalVcc'>"); 
message += ESP.getVcc();
message += F("</span>mV</p>\n");

message += F("<p>Button 1: <span id='button1'>"); // example how to show values on the webserver
message += digitalRead(BUTTON1_PIN);
message += F("</span></p>\n");

message += F("<p>Output 1: <span id='output1'>"); // example 3
message += digitalRead(OUTPUT1_PIN);
message += F("</span></p>\n");

message += F("<p>Output 2: <span id='output2'>"); // example 4
message += digitalRead(OUTPUT2_PIN);
message += F("</span></p>\n"
"</article>\n");

message += F("<article>\n"
"<h2>Switch</h2>\n" // example how to switch/toggle an output
"<p>Example how to switch/toggle outputs, or to initiate actions. "
"The buttons are 'fake' buttons and only styled by CSS. Click to toggle the output.</p>\n"
"<p class='off'><a href='c.php?toggle=1' target='i'>Output 1</a></p>\n"
"<p class='off'><a href='c.php?toggle=2' target='i'>Output 2</a></p>\n"
"<iframe name='i' style='display:none' ></iframe>\n" // hack to keep the button press in the window
"</article>\n");

addBottom(message);
server.send(200, "text/html", message);
}

Die Funktion beginnt mit der Anlage eines String Objektes. Mit der addTop wird das (lokale) String Objekt mit den Daten aus der Funktion ergänzt.

Im Code (blau markiert) sieht man nun auch, wie man "dymamisch" Werte aus Variablen einfügt. Einfach den Fixtext beenden, die Variable hinzufügen und wieder mit Fixtext fortsetzen. Die verteufelte String Klasse macht das eigentlich - in diesem speziellen Fall - ganz gut.

Am Ende fügen fügst du noch den allgemeinen schließenden Teil des HTML Codes mit addBottom(message) hinzu bevor du mit server.send deine zusammengebaute Response sendest.

Ebenso verfährst du mit den Datei Handlern für 1.htm und 2.htm.

CSS - Ein Stylesheet für den Webserver

Eingangs schrieb ich von "den Webserver behübschen". Also brauchen wir noch die Funktion für den CSS Handler. Im Prinzip ist ein CSS nur eine Text-Ausgabe mit dem MIME-Type text/css:

void handleCss()
{
// output of stylesheet
// this is a straight forward example how to generat a static page from program memory
String message;
message = F("*{font-family:sans-serif}\n"
             "body{margin:10px}\n"
             "h1, h2{color:white;background:" CSS_MAINCOLOR ";text-align:center}\n"
             "h1{font-size:1.2em;margin:1px;padding:5px}\n"
             "h2{font-size:1.0em}\n"
             "h3{font-size:0.9em}\n"
             "a{text-decoration:none;color:dimgray;text-align:center}\n"
             ".small{font-size:0.6em}\n"
             ".value{font-size:1.8em;text-align:center;line-height:50%}\n"
             "footer p, .infodaten p{font-size:0.7em;color:dimgray;background:silver;"
             "text-align:center;margin-bottom:5px}\n"
             "nav{background-color:silver;margin:1px;padding:5px;font-size:0.8em}\n"
             "nav a{color:white;padding:10px;text-decoration:none}\n"
             "nav a:hover{text-decoration:underline}\n"
             "nav p{margin:0px;padding:0px}\n"
             ".btn{background-color:#C0C0C0;color:dimgray;text-decoration:none;"
             "border-style:solid;border-color:dimgray}\n"
             ".on, .off{margin-top:0;margin-bottom:0.2em;margin-left:3em;"
             "font-size:1.4em;background-color:#C0C0C0;"
             "border-style:solid;width:5em;height:1.5em;text-decoration:none;text-align:center}\n"
             ".on{border-color:green}\n"
server.send(200, "text/css", message);
}

Obiges konsequent angewendet bringt uns einen lauffähigen Webserver - der je nach Fertigkeit auch schon mal besser bzw. bunter aussieht als eine reine Textseite.

Wer tapfer bis hier her durchgehalten hat, der bekommt unten bei den Links auch meinen fertigen Sketch

Links

  • Download des finalen Sketch ESP8266 Webserver WebClient
  • Amazon (Suchlink für NodeMCU V2 mit CP2102 *)
  • Amazon (Alternativ: Wemos D1 pro Mini *)

Die mit Sternchen (*) gekennzeichneten Verweise sind sogenannte Affiliate/Provision-Links. Wenn du auf so einen Verweis klickst und über diesen Link einkaufst, bekomme ich von deinem Einkauf eine (kleine) Provision. Für dich verändert sich der Preis dadurch nicht. Ich empfehle nur Produkte die ich selber besitze und wenn ich überzeugt bin, dass sie für andere Maker interesssant sind.

 

Protokoll

erstellt: 2018-09-02 | Stand: 2021-02-10