WLAN mit ESP8266 und ESP32

Über den Beitrag

Das Thema WLAN bzw. Wi-Fi hatte ich zwar schon häufig, aber eher beiläufig behandelt, beispielsweise in meinen Beiträgen über den ESP32, den ESP8266, die Wemos Boards, IFTTT und einige mehr. In diesem Beitrag möchte ich nun tiefer in dieses spannende Thema einsteigen. Im Einzelnen behandele ich die folgenden Themen:

Ich habe versucht, das Thema detailliert und Schritt für Schritt aufzubereiten, um auch weniger Erfahrene „abzuholen“. Wer sich schon auskennt, kann sicherlich das eine oder andere überspringen.

Einführung / Grundbegriffe

Bevor es richtig losgeht, möchte ich zum besseren Verständnis ein paar Begriffe erklären. Man möge mir nachsehen, dass ich die Dinge teilweise vereinfacht darstelle.

Wi-Fi vs WLAN

Wi-Fi (im Englischen: WiFi ohne Bindestrich) bezeichnet sowohl das Firmenkonsortium Wi-Fi Alliance, das WLAN-Geräte zertifiziert, als auch die zugehörigen Markenbegriffe Wi-Fi 4, 5 und 6 (Quelle: Wikipedia). Die Abkürzung Wi-Fi steht für „Wireless Fidelity“. WLAN hingegen ist die Abkürzung für Wireless Local Area Network, also ein lokales, kabelloses Netzwerk. In nicht-deutschsprachigen Ländern wird Wi-Fi oft als Synonym für WLAN verwendet. 

HTTP

Die Grundlage für die Kommunikation im World Wide Web ist HTTP = Hypertext Transfer Protocol. Dieses Protokoll legt fest, wie die im WWW vernetzten Geräte miteinander sprechen. Damit ist es auch die Grundlage für die hier besprochene Kommunikation per WLAN.

Client und Server

Die Kommunikation per HTTP erfolgt nach dem Client-Server Prinzip. Vereinfacht ausgedrückt: Der Client fragt an, der Server antwortet. Das ist vergleichbar mit dem Master-Slave Prinzip, das ihr von I2C und SPI her kennt.

Host

„Ein Host kann in der IT im Grunde jede Ressource sein, die eine andere Ressource als Gast bei sich aufnehmen beziehungsweise die mit ihr verbundenen Clients mit bestimmten IT-Diensten versorgen kann“ (Quelle: Frings Informatic). Jeder Server ist somit ein Host, aber nicht jeder Host ein Server.

HTML und CSS

HTML ist die Abkürzung für „Hypertext Markup Language“. Es ist die textbasierte, universelle Sprache des WWW. CSS steht für „Cascading Style Sheets“ und baut auf HTML auf. CSS bringt Struktur und objektorientiertes Arbeiten in HTML. Man erstellt Vorlagen (Klassen), die man immer wieder verwenden kann. Die Grundlagen von HTML und CSS sind schnell erlernbar.

IP und Router

Die IP ist die eindeutige Adresse eines Gerätes innerhalb eines Computernetzwerkes nach dem Internetprotokoll. Nach dem IPv4 Standard besteht die IP aus 4 Bytes, die jeweils mit einem Punkt voneinander getrennt sind. Jedes Gerät, das direkt mit dem WWW verbunden ist, hat eine individuelle IP, so auch euer Router. „Hinter“ dem Router liegt euer Heimnetzwerk mit eigenen IPs. Diese sind meist nach dem Schema 192.168.x.y aufgebaut. Ein Router ist eine Schnittstelle zwischen zwei Netzwerken.

Port

Ports sind zwei Byte große Erweiterungen der Netzwerkadressen, welche die übertragenen Daten bestimmten Anwendungen zuordnen. So kann unterschieden werden, ob ein Datenpaket beispielsweise für das Mailprogramm oder den Browser (typischerweise 80) bestimmt ist. Der Port wird der IP angehängt, getrennt durch einen Doppelpunkt. Also z.B.: 87.184.193.186:80.

Subnetzmaske

Euer Router vergibt in der Regel IPs, die sich nur im letzten Byte unterscheiden. In meinem Heimnetzwerk lauten die Adressen beispielsweise 192.168.178.x. Wenn eine Adresse von diesem Schema abweicht, dann weiß der Router, dass sie außerhalb des Heimnetzwerkes liegt. Das wird mit der Subnetzmaske ausgedrückt. Sie ist für mein Heimnetzwerk 255.255.255.0. In binärer Schreibweise wird die Subnetzmaske von links nach rechts mit Einsen aufgefüllt. Dieser Bereich ist fest vergeben. Nur der Teil mit den Nullen kann für die Vergabe von Subnetzadressen frei genutzt werden.

Gateway

Ein Gateway ist ein Gerät, das zwei Netzwerke verbindet. In meinem Heimnetzwerk ist das der Router. Seine IP innerhalb meines Heimnetzwerkes ist 192.168.178.1.

DHCP

DHCP ist die Abkürzung für Dynamic Host Configuration Protocol. Auf eurem Router läuft ein DHCP Server, der dafür sorgt, dass ein jedes Gerät in eurem Netztwerk (Computer, Smartphone, Laptop, IP Kamera, usw.) automatisch eine IP zugewiesen bekommt.

Einrichten eines Webservers

Ich gehe davon aus, dass ihr euer WLAN fähiges Board schon in die Arduino IDE integriert habt. Es gibt dafür so viele Anleitungen, dass ich mir das in diesem Beitrag spare.

Wir beginnen ganz einfach mit der Einrichtung eines Webservers im Heimnetz. Ich zeige das hier am Beispiel eines ESP8266 (ESP8266-ESP01 oder Wemos D1 Mini). Für ein ESP32 Board müsst ihr nur zwei Zeilen ändern (siehe Kommentare).

#include <ESP8266WebServer.h> // <WebServer.h> for ESP32
#define PORT 80

const char* ssid = "Your SSID";
const char* pass = "Your Password";

ESP8266WebServer server(PORT); // WebServer server(PORT); for ESP32
 
void setup(){
  Serial.begin(115200); 
  Serial.print("Connecting to: ");
  Serial.println(ssid);
  WiFi.begin(ssid, pass);
   
  while(WiFi.status() != WL_CONNECTED){
    delay(500); 
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.print("IP-Address of ESP8266 module: ");
  Serial.println(WiFi.localIP());
  
  server.begin();
}
  
void loop(){
}

Erläuterung des Sketches:

  • Ihr bindet ESP8266WebServer.h ein.
  • Der Port für den Webserver ist 80.
  • „ssid“ (Service Set Indentifier) und „pass“ sind die Zugangsdaten eures Heimnetzwerkes.
  • ESP8266WebServer server(PORT); erzeugt ein Webserver Objekt.
  • WiFi.begin(ssid, pass) startet die WLAN-Verbindung.
  • Den Status eurer Verbindung erfahrt ihr über WiFi.status().
  • WiFi.localIP() liefert die IP, die der DHCP Server eures Routers für den ESP8266 vergeben hat.
  • server.begin() startet den WebServer, der hier aber noch absolut gar nichts tut.

Und so sieht das dann im seriellen Monitor aus. Mein Router hat dem ESP8266 die IP 192.168.178.47 gegeben:

Ausgabe von webserver_basic.ino
Ausgabe von webserver_basic.ino

IP selber festlegen

Ihr könnt mit WiFi.config() auch selbst bestimmen, welche IP der ESP8266 erhalten soll – vorausgesetzt, sie ist noch nicht vergeben. Dazu ergänzt ihr den Sketch folgendermaßen:

#include <ESP8266WebServer.h>  // <WebServer.h> for ESP32
#define PORT 80

const char* ssid = "Your SSID";
const char* pass = "Your Password";

IPAddress ip(192,168,178,110);
IPAddress gateway(192,168,178,1);
IPAddress subnet(255,255,255,0);

ESP8266WebServer server(PORT); // WebServer server(PORT) for ESP32
 
void setup(){
  Serial.begin(115200); 
  Serial.print("Connecting to: ");
  Serial.println(ssid);
  WiFi.config(ip, gateway, subnet); 
  WiFi.begin(ssid, pass);
   
  while(WiFi.status() != WL_CONNECTED){
    delay(500); 
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.print("IP-Address of ESP8266 module: ");
  Serial.println(WiFi.localIP());
  
  server.begin();
}
  
void loop(){
}
webserver_basic_choose_IP.ino
Ausgabe von webserver_basic_choose_IP.ino

In den meisten Routern ist es möglich, Geräten eine feste IP zu geben. So „legt ihr euer Handtuch darauf“ und verhindert, dass die IP anderweitig vergeben wird.

Steuern eines Mikrocontrollers per WLAN und Browser

Im Folgenden möchte ich zeigen, wie ihr euer ESP8266- oder ESP32-Board per Browser vom PC, Laptop, Smartphone usw. steuert. Die konkrete Aufgabe ist die Schaltung von drei LEDs und das Auslesen eines Messwertes. Viele Wege führen nach Rom, so auch hier. Ich stelle zwei davon vor, die „server.on()-“ und die „client-Methode“.

Bis auf Weiteres werden die Beispiele im Heimnetz realisiert, d.h. die Kommunikation läuft über den Router:

MCU Board per WLAN über den Browser steuern
Den Mikrocontroller per Browser über WLAN steuern

Die Sketche sind für ein Wemos Board geschrieben. Bei Verwendung anderer Boards müsst ihr sie hinsichtlich der Pins noch anpassen. Wenn weitere Änderungen für ESP32 Boards notwendig sind, dann findet Ihr entsprechende Kommentare.

Die server.on()-Methode

Der Begriff „server.on()-Methode“ ist kein allgemein üblicher Begriff, zumal ihr euer Serverobjekt anstelle „server“ genauso gut „Pflaumenkuchen“ nennen könntet. Gleiches gilt für die „client-Methode“. Ich musste einfach nur Bezeichnungen festlegen, um die Methoden zu unterscheiden und etwas Besseres fiel mir nicht ein.

Eine LED per Browser schalten

Wir fangen erst einmal einfach an und schalten nur eine LED.  Verbindet die LED mit einem Pin eures Boards. Nehmt den folgenden Sketch und ladet ihn auf euer Board. Ggf. müsst ihr ihn hinsichtlich der IP und des Pins noch anpassen. Hier zunächst der Sketch:

#include <ESP8266WebServer.h>  // <WebServer.h> for ESP32
#define LEDPIN D6  // choose an appropriate pin

const char* ssid = "Your SSID";
const char* pass = "Your Password";

IPAddress ip(192,168,178,110);
IPAddress gateway(192,168,178,1);
IPAddress subnet(255,255,255,0);

ESP8266WebServer server(80); // WebServer server(80); for ESP32
 
String led1= "<a href=\"/led_on\">LED On</a>";
String led0= "<a href=\"/led_off\">LED Off</a>";

void setup(){
  pinMode(LEDPIN, OUTPUT);
  digitalWrite(LEDPIN, LOW);
  Serial.begin(115200); 
  Serial.println("Minimal Program to switch one LED");
  Serial.print("Connecting to: ");
  Serial.println(ssid);
  WiFi.config(ip, gateway, subnet); 
  WiFi.begin(ssid, pass);
  
  while(WiFi.status() != WL_CONNECTED){
    delay(500); 
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.print("IP-Address of ESP8266 module: ");
  Serial.println(WiFi.localIP());
  
  server.on("/",handleRoot);
  server.on("/led_on", ledon);
  server.on("/led_off", ledoff);
  server.begin();
}
  
void loop(){
  server.handleClient(); 
}

void handleRoot() {
  String message="<h1>Control your ESP8266 by your Browser</h1>";
  message += "Minimal version, just one LED</BR></BR>";
  message += led1;
  server.send(200, "text/html", message);
}

void ledon(){
  digitalWrite(LEDPIN, HIGH);
  server.send(200, "text/html", led0);
}

void ledoff(){
  digitalWrite(LEDPIN, LOW);
  server.send(200, "text/html", led1);
}

Nun ruft ihr den Browser eines Gerätes auf, das mit eurem Heimnetzwerk verbunden ist. Tippt dort die IP eures Boards ein, also in meinem Fall 192.168.178.110. Dann sollte es ungefähr so aussehen:

Steuern per WLAN: Bildschirmausgabe von ESP8266_ESP32_one_LED.ino
Bildschirmausgabe von ESP8266_ESP32_one_LED.ino

Wenn ihr nun auf den Link „LED On“ klickt, wird die LED angeschaltet und auf dem Bildschirm erscheint der Link „LED Off“. Außerdem seht ihr, dass sich die Adresszeile des Browsers ändert. In meinem Fall: 192.168.178.110/led_on. Jetzt könnt ihr die LED wieder ausschalten.

Steuern per WLAN: Bildschirmausgabe von ESP8266_ESP32_one_LED.ino, Pfad: /led_on
Bildschirmausgabe von ESP8266_ESP32_one_LED.ino, Pfad: /led_on

Erklärungen zum Sketch

Ich fange mal in der Mitte an, nämlich in loop(). Dort findet ihr lediglich die Funktion server.handleClient(). Sie fragt ab, ob Anfragen eines Clients vorliegen.

Der Aufruf von 192.168.178.110 führt in den Hauptpfad „/“. Genau genommen sendet der Browser dazu einen sogenannten Get-Request. Darauf komme ich später zurück.

Über die Funktion server.on("/", handleRoot) wurde zuvor festgelegt, dass bei Aufruf von „/“ die Funktion handleRoot() ausgeführt wird. Dort wird ein String („message“) definiert, der mittels server.send(200, „text/html“, message) an den Browser zurückgesendet wird. Die „200“ ist ein Fehlercode, der dem Browser sagt, dass alles OK ist. Ihr kennt vielleicht den Fehlercode „404 – page not found“.

„text/html“ besagt, dass Text und HTML Code gesendet wird. Alles in eckigen Klammern ist HTML. In den meisten Fällen sind HTML Anweisungen für einen bestimmten Bereich gültig. <Anweisung> ist der Beginn des Bereiches, </Anweisung> ist das Ende. <h1> leitet die größte Überschrift ein, </BR> bewirkt einen Zeilenumbruch und mit <a> beginnt ein Link. href ist das Linkziel. Wenn ihr mehr über HTML wissen wollt, findet ihr hier einen sehr kompakten Schnelleinstieg.

Bei einem Klick auf den Link „LED On“, wird die Seite 192.168.178.110/led_on aufgerufen. Durch server.on("/led_on", ledon) ist festgelegt, dass nun die Funktion ledon() aufgerufen wird. Dort wiederum wird zum einen die LED angeschaltet, zum anderen wird der Link zur Seite 192.168.178.110/led_off an den Browser gesendet und dort dargestellt.

Ihr könnt die LED natürlich auch anschalten, indem ihr die IP mit Pfad direkt in die Adresszeile des Browsers eingebt, also: 192.168.178.110/led_on.

Ich hoffe, dass damit das Prinzip dieser Methode klar geworden ist.

Drei LEDS schalten und einen Messwert auslesen

Jetzt steigern wir uns, indem wir drei LEDs steuern und einen Messwert auf die Ferne auslesen. Als Beispiel für einen Messwert lese ich die Spannung an einem analogen Eingang.

Den folgenden Sketch müsst ihr ggf. wieder etwas an euer Board anpassen.

#include <ESP8266WebServer.h> // <WebServer.h> for ESP32

int led[3] = {D5,D6,D7}; // choose suitable pins
bool led_status[3] = {false};
const char* ssid = "Your SSID";
const char* pass = "Your Password";

IPAddress ip(192,168,178,110);
IPAddress gateway(192,168,178,1);
IPAddress subnet(255,255,255,0);

ESP8266WebServer server(80);  // WebServer server(80); for ESP32
 
String headAndTitle = "<head><meta http-equiv=\"refresh\" content=\"5\"></head>"
                      "<h1>Control your ESP8266 by Browser</h1>"
                      "Switch three LEDs and get a measured value, minimal version</BR></BR>";

String led0_1= "<a href=\"/led0_on\">LED0 On</a>";
String led0_0= "<a href=\"/led0_off\">LED0 Off</a>";
String led1_1= "</BR><a href=\"/led1_on\">LED1 On</a>";
String led1_0= "</BR><a href=\"/led1_off\">LED1 Off</a>";
String led2_1= "</BR><a href=\"/led2_on\">LED2 On</a>";
String led2_0= "</BR><a href=\"/led2_off\">LED2 Off</a>";


void setup(){
  pinMode(led[0], OUTPUT);
  digitalWrite(led[0], LOW);
  pinMode(led[1], OUTPUT);
  digitalWrite(led[1], LOW);
  pinMode(led[2], OUTPUT);
  digitalWrite(led[2], LOW);
  
  WiFi.config(ip, gateway, subnet);
  WiFi.begin(ssid, pass);
   
  server.on("/",handleRoot);
  server.on("/led0_on", led0on);
  server.on("/led0_off", led0off);
  server.on("/led1_on", led1on);
  server.on("/led1_off", led1off);
  server.on("/led2_on", led2on);
  server.on("/led2_off", led2off);
  server.begin();
}
  
void loop(){
  server.handleClient(); 
}

void handleRoot() {
  led0off();
}

void led0on(){
  led_status[0] = true;
  switchLEDAndSend(0,1);
}

void led0off(){
  led_status[0] = false;
  switchLEDAndSend(0,0);
}

void led1on(){
  led_status[1] = true;
  switchLEDAndSend(1,1);
}

void led1off(){
  led_status[1] = false;
  switchLEDAndSend(1,0);
}

void led2on(){
  led_status[2] = true;
  switchLEDAndSend(2,1);
}

void led2off(){
  led_status[2] = false;
  switchLEDAndSend(2,0);
}

void switchLEDAndSend(int num, bool state){
  String message = "";
  message += headAndTitle;
  
  digitalWrite(led[num], state);
  (led_status[0]==true)?(message += led0_0):(message += led0_1); // short version for if..then..else
  (led_status[1]==true)?(message += led1_0):(message += led1_1);
  (led_status[2]==true)?(message += led2_0):(message += led2_1);
  
  float measuredValue = analogRead(A0)/1024.0 * 3.3; // adjust to your board
  message += "</BR></BR>Voltage [V]: ";
  message += String(measuredValue, 2); // float to String, two decimal places
  server.send(200, "text/html", message); 
}

 

Und so sieht das Ergebnis im Browser aus:

Steuern per WLAN - Bildschirmausgabe von ESP8266_ESP32_three_LEDs.ino
Bildschirmausgabe von ESP8266_ESP32_three_LEDs.ino

Da wir hier drei LEDs an- und ausschalten, benötigen wir dafür sechs server.on() Funktionen plus eine weitere für die Hauptseite. Und wir müssen uns den Status der LEDs „merken“. Dafür habe ich das Array „led_status“ definiert. Ansonsten macht der Sketch hinsichtlich der LEDs nichts Neues, er ist nur länger.

Neu ist, dass wir hier noch einen Messwert auslesen, in einen String umwandeln und an den Browser senden. Nun möchtet ihr wahrscheinlich die Seite nicht immer wieder neu aufrufen, um Änderungen des Messwertes zu verfolgen. Für ein regelmäßiges Update sorgt deshalb in Zeile 14: <head><meta http-equiv=\"refresh\" content=\"5\"></head>. Dadurch wird die Webseite alle 5 Sekunden neu aufgerufen.

Hübscher mit HTML und CSS

Unsere Webseite ist optisch noch nicht besonders ansprechend. Tauscht einmal die Zeilen 18-23 gegen die folgenden Zeilen aus:

String led0_1= "<a href=\"/led0_on\"><button style=\"background: green; color: white; font-size: x-large; \">LED0 On</button></a>";
String led0_0= "<a href=\"/led0_off\"><button style=\"background: red; color: white; font-size: x-large; \">LED0 Off</button></a>";
String led1_1= "</BR></BR><a href=\"/led1_on\"><button style=\"background: green; color: white; font-size: x-large;\">LED1 On</button></a>";
String led1_0= "</BR></BR><a href=\"/led1_off\"><button style=\"background: red; color: white; font-size: x-large; \">LED1 Off</button></a>";
String led2_1= "</BR></BR><a href=\"/led2_on\"><button style=\"background: green; color: white; font-size: x-large; \">LED2 On</button></a>";
String led2_0= "</BR></BR><a href=\"/led2_off\"><button style=\"background: red; color: white; font-size: x-large; \">LED2 Off</button></a>";

Das Ergebnis ist schon ein wenig netter:

Bildschirmausgabe, verbessert
Bildschirmausgabe, verbessert

Oder hier nochmal der ganze Sketch mit CSS Elementen:

#include <ESP8266WebServer.h> // <WebServer.h> for ESP32

int led[3] = {D5,D6,D7};
bool led_status[3] = {false};
const char* ssid = "Your SSID";
const char* pass = "Your Password";

IPAddress ip(192,168,178,110);
IPAddress gateway(192,168,178,1);
IPAddress subnet(255,255,255,0);

ESP8266WebServer server(80);  // WebServer server(80); for ESP32
 
String headAndTitle = "<head><style>"
                        ".button {"
                          "border: none;"
                          "color: white;"
                          "width: 350px;"
                          "padding: 20px;"
                          "text-align: center;"
                          "margin: 20px 200px;"
                        "}"
                        ".greenButton {background-color: green; font-size: 64px;}"
                        ".greenButton:hover {background-color: darkgreen; font-size: 64px;}"
                        ".redButton {background-color: red; font-size: 64px;}"
                        ".redButton:hover {background-color: darkred; font-size: 64px;}"
                        ".blueBox {"
                          "background-color: blue;"
                          "color: white;"
                          "width: 350px;" 
                          "padding: 20px;"
                          "text-align: center;"
                          "font-size: 50px;"
                          "font-family: arial;"
                          "margin: 20px 200px;"
                        "}"
                      "</style>"
                      "</head><meta http-equiv=\"refresh\" content=\"5\"></head>"
                      "</BR></BR><h1 align=\"center\">Control your ESP8266 by Browser</h1></div>"
                      "<div align=\"center\">Control three LEDs and get measured value, nicer with CSS</BR></BR></div>";

String led0_1= "<a href=\"/led0_on\"><button class=\"button greenButton\">LED0 On</button></a>";
String led0_0= "<a href=\"/led0_off\"><button class=\"button redButton\">LED0 Off</button></a>";
String led1_1= "</BR><a href=\"/led1_on\"><button class=\"button greenButton\">LED1 On</button></a>";
String led1_0= "</BR><a href=\"/led1_off\"><button class=\"button redButton\">LED1 Off</button></a>";
String led2_1= "</BR><a href=\"/led2_on\"><button class=\"button greenButton\">LED2 On</button></a>";
String led2_0= "</BR><a href=\"/led2_off\"><button class=\"button redButton\">LED2 Off</button></a>";

void setup(){
  pinMode(led[0], OUTPUT);
  digitalWrite(led[0], LOW);
  pinMode(led[1], OUTPUT);
  digitalWrite(led[1], LOW);
  pinMode(led[2], OUTPUT);
  digitalWrite(led[2], LOW);
  
  WiFi.config(ip, gateway, subnet); 
  WiFi.begin(ssid, pass);

  server.on("/",handleRoot);
  server.on("/led0_on", led0on);
  server.on("/led0_off", led0off);
  server.on("/led1_on", led1on);
  server.on("/led1_off", led1off);
  server.on("/led2_on", led2on);
  server.on("/led2_off", led2off);
  server.begin();
}
  
void loop(){
  server.handleClient(); 
}

void handleRoot() {
  led0off();
}

void led0on(){
  led_status[0] = true;
  switchLEDAndSend(0,1);
}

void led0off(){
  led_status[0] = false;
  switchLEDAndSend(0,0);
}

void led1on(){
  led_status[1] = true;
  switchLEDAndSend(1,1);
}

void led1off(){
  led_status[1] = false;
  switchLEDAndSend(1,0);
}

void led2on(){
  led_status[2] = true;
  switchLEDAndSend(2,1);
}

void led2off(){
  led_status[2] = false;
  switchLEDAndSend(2,0);
}

void switchLEDAndSend(int num, bool state){
  String message = "";
  message += headAndTitle;
  message += "<div align=\"center\";>";
  
  digitalWrite(led[num], state);
  (led_status[0]==true)?(message += led0_0):(message += led0_1);
  (led_status[1]==true)?(message += led1_0):(message += led1_1);
  (led_status[2]==true)?(message += led2_0):(message += led2_1);
  
  float measuredValue = analogRead(A0)/1024.0 * 3.3;
  message += "</BR><div class=\"blueBox\">";
  message += "Voltage [V]: </BR>";
  message += String(measuredValue, 2);
  message += "</div>";
  message += "</div>";
  server.send(200, "text/html", message); 
}

 

Die Darstellung ist etwas großzügiger und deshalb auch gut auf dem Smartphone nutzbar:

Drei LEDs per WLAN steuern: ESP8266_ESP32_three_LEDs_nice.ino auf dem PC Bildschirm
ESP8266_ESP32_three_LEDs_nice.ino auf dem PC Bildschirm….
Drei LEDs per WLAN steuern: ESP8266_ESP32_three_LEDs_nice.ino auf dem Smartphone
…und auf dem Smartphone

Tiefer in CSS einzusteigen, würde den Beitrag sprengen. Eine kompakte Einführung findet ihr hier.

Die client-Methode

Eine Alternative zum Auswerten der Clientanfrage über server.on() ist die client-Methode. Wie schon erwähnt, ist das keine offizielle Bezeichnung. Hier wird ein Client-Objekt geschaffen, das über Read- und Printfunktionen Client-Requests liest oder Nachrichten an den Client sendet. Das ist ähnlich wie bei einem Serial-Objekt. 

#include "ESP8266WebServer.h" // use "WebServer.h" for ESP32
 
int led[3] = {D5,D6,D7};  // choose suitable pins
int analogPin = A0;
bool led_status[3] = {false};
const char* ssid = "Your SSID";
const char* password = "Your Password";

String headAndTitle = "<head><meta http-equiv=\"refresh\" content=\"5\"></head>"
                      "<h1>Control your ESP8266 by Browser</h1>"
                      "Control three LEDs and get measured value, client method</BR></BR>";

String led0_1= "<a href=\"/led0_on\"><button style=\"background: green; color: white; font-size: x-large; \">LED0 On</button></a>";
String led0_0= "<a href=\"/led0_off\"><button style=\"background: red; color: white; font-size: x-large; \">LED0 Off</button></a>";
String led1_1= "</BR></BR><a href=\"/led1_on\"><button style=\"background: green; color: white; font-size: x-large;\">LED1 On</button></a>";
String led1_0= "</BR></BR><a href=\"/led1_off\"><button style=\"background: red; color: white; font-size: x-large; \">LED1 Off</button></a>";
String led2_1= "</BR></BR><a href=\"/led2_on\"><button style=\"background: green; color: white; font-size: x-large; \">LED2 On</button></a>";
String led2_0= "</BR></BR><a href=\"/led2_off\"><button style=\"background: red; color: white; font-size: x-large; \">LED2 Off</button></a>";

IPAddress ip(192,168,178,110);
IPAddress gateway(192,168,178,1);
IPAddress subnet(255,255,255,0);
 
WiFiServer server(80);
WiFiClient client;
 
void setup() {
  pinMode(led[0], OUTPUT);
  digitalWrite(led[0], LOW);
  pinMode(led[1], OUTPUT);
  digitalWrite(led[1], LOW);
  pinMode(led[2], OUTPUT);
  digitalWrite(led[2], LOW);
  
  Serial.begin(115200);
  while(!Serial) {}
   
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);
   
  WiFi.config(ip, gateway, subnet); 
  WiFi.begin(ssid, password);
   
  while(WiFi.status() != WL_CONNECTED){
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP()); 
  server.begin();
}
 
void loop(){
  client = server.available();
  if(client){
    Serial.println("New client");
    boolean currentLineIsBlank = true;
    String clientMessage = "";
    
    while(client.connected()){     
      if(client.available()){
        char c = client.read();
        // Serial.write(c); //uncomment to print the request
        if(clientMessage.length()<50){
          clientMessage += c;
        }
        if(c == '\n' && currentLineIsBlank){
          evaluateClientMessage(clientMessage);
          break;
        }
        if(c == '\n'){
          currentLineIsBlank = true;
        } 
        else if(c != '\r'){
          currentLineIsBlank = false;
        }
      }
    }
    delay(1);
    client.stop();
    Serial.println("client disconnected");
  }
}

void evaluateClientMessage(String &msg){
   
  if(msg.indexOf("led0_off") > 0){
    led_status[0] = false;
  }
  else if(msg.indexOf("led0_on") > 0){
    led_status[0] = true;
  }
  else if(msg.indexOf("led1_off") > 0){
    led_status[1] = false;
  }
  else if(msg.indexOf("led1_on") > 0){
    led_status[1] = true;
  }
  else if(msg.indexOf("led2_off") > 0){
    led_status[2] = false;
  }
  else if(msg.indexOf("led2_on") > 0){
    led_status[2] = true;
  }

  for(int i=0; i<3; i++){
    digitalWrite(led[i], led_status[i]);
  }

  String ledString = "";
  (led_status[0]==true)?(ledString += led0_0):(ledString += led0_1);
  (led_status[1]==true)?(ledString += led1_0):(ledString += led1_1);
  (led_status[2]==true)?(ledString += led2_0):(ledString += led2_1);

  float measuredValue = analogRead(analogPin)/1024.0 * 3.3;
    
  client.println("HTTP/1.1 200 OK");
  client.println("Content-Type: text/html");
  client.println("Connection: close"); 
  client.println(); // this line is not redundant!
  client.println(headAndTitle);
  client.println(ledString);
  client.println("</BR></BR></BR><span style=\"font-family: Arial; background: blue; color: white; font-size: x-large;\">&nbsp; Voltage [V]: ");
  client.println(String(measuredValue,2));
  client.println("&nbsp;</span>");
}

Ich gehe nur auf die wesentlichen Teile des Sketches ein. In der loop-Schleife wird fortlaufend geprüft, ob der Client einen Get-Request sendet. Wenn das der Fall ist, wird der Get-Request zeichenweise mit client.read() ausgelesen und zu dem String „clientMessage“ zusammengesetzt. Der Get-Request endet mit einer Leerzeile (if(c == '\n' && currentLineIsBlank){...}). ‚\n‘ ist die Escape-Sequenz für den Zeilenvorschub (new line), ‚\r‘ ist die Escape-Sequenz für den Wagenrücklauf (carriage return). Die entscheidende Information befindet sich im ersten Teil des Requests, deswegen nehmen wir nur die ersten 50 Zeichen auf. 

Wenn ihr die Zeile 66 entkommentiert, könnt ihr den ganzen Request auf dem seriellen Monitor sehen:

Get-Request des Clients (hier: "/led2_off")
Get-Request des Clients (hier: „/led2_off“)

Nun, da wir den relevanten Teil des Get-Requests in dem String „clientMessage“ gespeichert haben, durchsuchen wir ihn nach den Schlüsselwörtern led0_on, led0_off, led1_on, usw. Dafür gibt es die praktische Funktion indexOf(searchstring), die die Position des gesuchten Teilstrings zurückliefert. Wenn der gesuchte Teilstring nicht vorhanden ist, dann liefert indexOf() den Wert -1 zurück. 

Der Rest ist den vorherigen Sketchen ähnlich. Nur wird die Information nicht mit server.send(), sondern mit client.println() an den Client gesendet. Das Ergebnis ist dasselbe.

Station (STA) und Access point (AP) WLAN Modi

Bei den bisherigen Beispielen hat sich das Mikrocontroller-Board in ein vorhandenes WLAN Netzwerk eingeklinkt. Das ist der sogenannte STA („Station“) Modus. Mit geringen Änderungen kann das Mikrocontroller-Board selbst zum Access Point (= Zugangspunkt oder Hot Spot) gemacht werden. Dies ist der AP Modus, mit dem ihr euch von eurem Router unabhägig macht. 

ESP8266 im WLAN Modus AP
Der ESP8266 im WLAN Modus AP

Die folgenden Zeilen machen den Unterschied:

IPAddress ip(192,168,4,1);
IPAddress gateway(192,168,4,1); 
IPAddress subnet(255,255,255,0);
....
....
WiFi.softAPConfig(ip, gateway, subnet);
Wifi.softAP(ssid, pass);

Und hier der ganze Sketch:

#include <ESP8266WebServer.h>  // <WebServer.h> for ESP32  

int led[3] = {D5,D6,D7};
bool led_status[3] = {false};
const char* ssid = "ESP8266_Server";  // choose a name
const char* pass = "blablablabla"; // must be >= 8 characters (at least for the ESP32)!

IPAddress ip(192,168,4,1); // should be 192.168.4.x
IPAddress gateway(192,168,4,1);  // should be 192.168.4.x
IPAddress subnet(255,255,255,0);

ESP8266WebServer server(80);  // WebServer server(80); for ESP32
 
String headAndTitle = "<head><style>"
                        ".button {"
                          "border: none;"
                          "color: white;"
                          "width: 350px;"
                          "padding: 20px;"
                          "text-align: center;"
                          "margin: 20px 200px;"
                        "}"
                        ".greenButton {background-color: green; font-size: 64px;}"
                        ".redButton {background-color: red; font-size: 64px;}"
                        ".blueButton {background-color: blue; font-size: 50px;}"
                      "</style>"
                      "</head><meta http-equiv=\"refresh\" content=\"5\"></head>"
                      "</BR></BR><h1 align=\"center\">Control your ESP8266 by Browser</h1></div>"
                      "<div align=\"center\">Control three LEDs and get measured value, AP - ModeS</BR></BR></div>";

String led0_1= "<a href=\"/led0_on\"><button class=\"button greenButton\">LED0 On</button></a>";
String led0_0= "<a href=\"/led0_off\"><button class=\"button redButton\">LED0 Off</button></a>";
String led1_1= "</BR><a href=\"/led1_on\"><button class=\"button greenButton\">LED1 On</button></a>";
String led1_0= "</BR><a href=\"/led1_off\"><button class=\"button redButton\">LED1 Off</button></a>";
String led2_1= "</BR><a href=\"/led2_on\"><button class=\"button greenButton\">LED2 On</button></a>";
String led2_0= "</BR><a href=\"/led2_off\"><button class=\"button redButton\">LED2 Off</button></a>";

void setup(){
  pinMode(led[0], OUTPUT);
  digitalWrite(led[0], LOW);
  pinMode(led[1], OUTPUT);
  digitalWrite(led[1], LOW);
  pinMode(led[2], OUTPUT);
  digitalWrite(led[2], LOW);
  
  WiFi.softAPConfig(ip, gateway, subnet); 
  WiFi.softAP(ssid, pass);  
 
  delay(500); 

  server.on("/",handleRoot);
  server.on("/led0_on", led0on);
  server.on("/led0_off", led0off);
  server.on("/led1_on", led1on);
  server.on("/led1_off", led1off);
  server.on("/led2_on", led2on);
  server.on("/led2_off", led2off);
  server.begin();
}
  
void loop(){
  server.handleClient(); 
}

void handleRoot() {
  led0off();
}

void led0on(){
  led_status[0] = true;
  switchLEDAndSend(0,1);
}

void led0off(){
  led_status[0] = false;
  switchLEDAndSend(0,0);
}

void led1on(){
  led_status[1] = true;
  switchLEDAndSend(1,1);
}

void led1off(){
  led_status[1] = false;
  switchLEDAndSend(1,0);
}

void led2on(){
  led_status[2] = true;
  switchLEDAndSend(2,1);
}

void led2off(){
  led_status[2] = false;
  switchLEDAndSend(2,0);
}

void switchLEDAndSend(int num, bool state){
  String message = "";
  message += headAndTitle;
  message += "<div align=\"center\";>";
  
  digitalWrite(led[num], state);
  (led_status[0]==true)?(message += led0_0):(message += led0_1);
  (led_status[1]==true)?(message += led1_0):(message += led1_1);
  (led_status[2]==true)?(message += led2_0):(message += led2_1);
  
  float measuredValue = analogRead(A0)/1024.0 * 3.3; // adjust for your board
  message += "</BR><button class=\"button blueButton\">";
  message += "Voltage [V]: </BR>";
  message += String(measuredValue, 2);
  message += "</button>";
  message += "</div>";
  server.send(200, "text/html", message); 
}

Ihr müsst euch hier mit eurem PC/Laptop/Smartphone/Tablet in das WLAN Netzwerk der MCU einklinken und nicht in das Netzwerk eures Routers. An der Darstellung im Browser ändert sich nichts.

Mehrere Mikrocontroller per WLAN vernetzen

Mehrere Mikrocontroller (kurz: MCU für Microcontroller Unit) in einem WLAN Netzwerk anzumelden, ist natürlich auch kein Problem. In diesem Abschnitt geht es vielmehr darum, wie die teilnehmenden MCUs untereinander kommunizieren können.

Eine typische Anwendung wäre: Ihr habt in eurem Heim mehrere MCUs im Einsatz, die irgendetwas messen (Temperatur, Luftfeuchte, o. ä.). Dann möchtet ihr wahrscheinlich nicht jede Station einzeln abfragen, sondern die Werte auf einem Server sammeln und auf einer einzigen Website ausgeben.

Einstieg: Einen Get-Request an einen Server senden

Wir setzen das Vorhaben um, indem die „sammelnde“ MCU (Client) die Daten von den anderen MCUs über Get-Requests anfordert. Wie das vom Prinzip her funktioniert, zeige ich erst einmal mithilfe dieses einfachen Setups:

Wir fügen dem bisherigen Aufbau also einen weiteren Mikrocontroller als Client hinzu, der über den Router mit dem WLAN Netzwerk verbunden ist. Diese Client MCU fordert über einen Get-Request die Hauptseite „/“ des Servers an (so wie es der Browser des PCs auch tut, wenn er die Seite aufruft).

Auf der Server MCU läuft der Sketch ESP8266_ESP32_three_LEDs.ino. Hier der Sketch für den Client:

#include <ESP8266WiFi.h>  // use "WebServer.h" for ESP32
 
const char* ssid     = "Your SSID";
const char* password = "Your Password";
const char* host = "192.168.178.110";

IPAddress ip(192,168,178,111);
IPAddress gateway(192,168,178,1);
IPAddress subnet(255,255,255,0);
 
void setup() {
  Serial.begin(115200);
  delay(10);
  Serial.println();
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);
 
  WiFi.config(ip, gateway, subnet); 
  WiFi.begin(ssid, password);
   
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
 
  Serial.println("");
  Serial.println("WiFi connected");  
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
  Serial.println();
}
 
void loop() {
  Serial.print("connecting to ");
  Serial.println(host);
  WiFiClient client;
  const int httpPort = 80;
  if (!client.connect(host, httpPort)) {
    Serial.println("connection failed");
    return;
  }
  String url = "/";
  Serial.print("Requesting URL: ");
  Serial.println(url);
   
  client.print(String("GET ") + url + " HTTP/1.1\r\n" +
               "Host: " + host + "\r\n" + 
               "Connection: close\r\n\r\n");
  unsigned long lasttime = millis();
  
  while (!client.available() && ((millis() - lasttime) < 3000)){
    delay(1);
  }  
  while (client.available()) {
    String line = client.readStringUntil('\r');
    Serial.println(line);
  }
  Serial.println();
  Serial.println("closing connection");
  delay(60000);
}

 

Die wesentlichen Punkte sind:

  • Die Client MCU klinkt sich mit der IP-Adresse 192.168.178.111 in das WLAN Netzwerk ein.
  • Der Client verbindet sich mit dem Server („host“):
    • client.connect(host, httpPort)
  • Dann sendet der Client einen Get-Request.
  • Er wartet bis zu drei Sekunden darauf, dass eine Antwort kommt:
    • while (!client.available() && ((millis() - lasttime) < 3000)){
    • Eine Wartezeit ist wichtig, da der Host unter Umständen gerade einen anderen Client (hier: der PC) bedient.
  • Die Antwort des Hosts wird zeilenweise ausgelesen und auf dem seriellen Monitor ausgegeben:
Ausgabe von get_request_to_another_mcu.ino

Jetzt könnten wir die Nachricht weiter auswerten und den analogen Messwert extrahieren. Das tun wir aber nicht, denn dieser Sketch sollte nur das Prinzip verdeutlichen.

Mehrere MCUs vernetzen mit MCU als Access Point

Die Aufgabenstellung:

  • Drei Mikrocontroller messen Spannungen an analogen Eingängen
  • Einer der Mikrocontroller dient als Access Point, die beiden anderen laufen im STA Modus
  • Die beiden Mikrocontroller im STA Modus senden ihre Messwerte an den AP Mikrocontroller, von wo aus sie im Browser eines PCs, Smartphones, Tablets, usw. ausgegeben werden können.
 MCU WLAN Netzwerk mit MCU als Access Point
MCU WLAN Netzwerk mit MCU als Access Point

Ich habe hier zur Illustration drei Wemos D1 Mini Boards gewählt. Ihr könnt aber genauso gut – HTTP sei Dank – eine Mischung verschiedener MCUs verwenden.

Damit ihr nicht durcheinanderkommt, auf welches Board ihr welchen Sketch ladet, empfehle ich, die Arduino IDE mehrfach zu öffnen („mehrere Instanzen erzeugen“). Dann kann man für jede Instanz einen eigenen seriellen Port auswählen. Ich jedenfalls finde das einfacher, als immer den Port innerhalb einer Instanz zu wechseln.

Server Sketch

Die Sketche für die Server (Hosts) können wir ganz einfach halten, da die MCU lediglich ihren Messwert übermitteln sollen.

#include <ESP8266WebServer.h>  // use <WebServer.h> for ESP32

const char* ssid = "ESP8266_Server";
const char* pass = "blablablabla";

IPAddress ip(192,168,4,2);  // ip(192,168,4,3); for second host
IPAddress gateway(192,168,4,1);
IPAddress subnet(255,255,255,0);

ESP8266WebServer server(80); // WebServer server(80); for ESP32
 
void setup(){
  WiFi.config(ip, gateway, subnet);
  WiFi.begin(ssid, pass);
  
  server.on("/voltage",voltage); 
  server.begin();
}

void loop(){
  server.handleClient();
}

void voltage(){
  float measuredValue_1 = analogRead(A0)/1024.0 * 3.3;
  String message = String(measuredValue_1, 2);
  server.send(200, "text/html", message);
}

 

Sketch für den Access Point MCU (Client und Server)

Hier ist die Besonderheit, dass dieses Mitglied des Netzwerkes ein Client in Bezug auf die beiden Hosts und ein Server in Bezug auf den PC ist.

Der PC schickt alle 10 Sekunden einen Get-Request an die AP MCU, da ich das über „refresh“ so eingestellt habe. In „echten“ Projekten sollte man die Frequenz besser nicht so hoch wählen.

Alle 15 Sekunden schickt die AP MCU ihrerseits Get-Requests zu den Hosts, um die Messwerte einzusammeln. Auch das ist eine recht hohe Frequenz. Senkt sie auf das Maß ab, das ihr benötigt. Das spart Strom und vermeidet „Kollisionen“.

Über den Browser könnt ihr euch dann schließlich alle Messwerte anschauen. Oben rechts seht Ihr, wie die das Ergebnis auf meinem Smartphone ausschaut.

Und hier nun der Sketch:

#include <ESP8266WebServer.h> // use <WebServer.h> for ESP32
#define HOST_1 0
#define HOST_2 1

const char* ssid = "ESP8266_Server";
const char* pass = "blablablabla";

const char* host_1 = "192.168.4.2";
const char* host_2 = "192.168.4.3";

String voltage[2] = {"",""};
unsigned long requestPeriod = 15000;

IPAddress ip(192,168,4,1);
IPAddress gateway(192,168,4,1);
IPAddress subnet(255,255,255,0);

ESP8266WebServer server(80); // WebServer server(80); for ESP32
 
String headAndTitle = "<head><style>"
                        ".blueBox {"
                          "background-color: blue;"
                          "color: white;"
                          "width: 350px;" 
                          "padding: 20px;"
                          "text-align: center;"
                          "font-size: 50px;"
                          "font-family: arial;"
                        "}"
                      "</style>"
                      "</head><meta http-equiv=\"refresh\" content=\"10\"></head>"
                      "</BR></BR><h1 align=\"center\">Get data from other ESP8266 modules</h1></div>"
                      "<div align=\"center\">AP Mode, get measured values from two hosts</BR></BR></div>";

void setup(){
  WiFi.softAPConfig(ip, gateway, subnet);
  WiFi.softAP(ssid, pass);
  
  
  server.on("/",handleRoot);
  server.begin();
}

void loop(){
  static unsigned long startTime = millis();
  server.handleClient();
  if ((millis() - startTime) > requestPeriod){
    requestESP8266(HOST_1);
    requestESP8266(HOST_2);
    startTime = millis();
  }
}

void handleRoot(){
  String message = "";
  message += headAndTitle;
  message += "<div align=\"center\";>";
  float measuredValue_1 = analogRead(A0)/1024.0 * 3.3;
  
  message += "</BR><div class=\"blueBox\">";
  message += "Voltage_1 [V]: </BR>";
  message += String(measuredValue_1, 2);
  message += "</div>";

  message += "</BR><div class=\"blueBox\">";
  message += "Voltage_2 [V]: </BR>";
  message += voltage[HOST_1];
  message += "</div>";
  
  message += "</BR><div class=\"blueBox\">";
  message += "Voltage_3 [V]: </BR>";
  message += voltage[HOST_2];
  message += "</div></div>";
  server.send(200, "text/html", message); 
}

void requestESP8266(int hostNo){
  WiFiClient client;
  String host = "";
  if(hostNo == HOST_1){
    if (!client.connect(host_1, 80)) {
      host = host_1;
      return;
    }
  }
  if(hostNo == HOST_2){
    if (!client.connect(host_2, 80)) {
      host = host_2;
      return;
    }
  }
  String url = "/voltage";
    
  client.print(String("GET ") + url + " HTTP/1.1\r\n" +
               "Host: " + host + "\r\n" + 
               "Connection: close\r\n\r\n");
  unsigned long lasttime = millis();
  while (!client.available() && ((millis() - lasttime) < 3000)){
    delay(1);
  }  
  while (client.available()) {
    String line = client.readStringUntil('\r');
    if(line != ""){
      voltage[hostNo] = line;
    }
  }
}

 

Der Sketch enthält eigentlich nichts Neues. Ich hoffe, dass er halbwegs nachvollziehbar ist.

Mehrere MCUs vernetzen mit Router als Access Point

Jetzt modifizieren wir die letzte Version noch dahin gehend, dass der Router der Access Point ist. Außerdem soll auch die direkte Abfrage der beiden Host MCUs ein ansprechendes Bild im Browser erzeugen. Die MCU, die die Werte von den Hosts einsammelt, habe ich hier „Main“ genannt:

MCU WLAN Netzwerk mit Router als Access Point
MCU WLAN Netzwerk mit Router als Access Point

Sketch für die Host MCUs

#include <ESP8266WebServer.h>  // <WebServer.h> for ESP32

const char* ssid = "Your SSID";
const char* pass = "Your Password";

IPAddress ip(192,168,178,111); // ip(192,168,178,112); for second host
IPAddress gateway(192,168,178,1);
IPAddress subnet(255,255,255,0);

ESP8266WebServer server(80); // WebServer server(80); for ESP32
 
String headAndTitle = "<head><style>"
                        ".blueButton {"
                          "border: none;"
                          "color: white;"
                          "width: 400px;"
                          "padding: 20px;"
                          "text-align: center;"
                          "margin: 20px 200px;"
                          "background-color: blue;"
                          "font-size: 50px;"
                        "}"
                      "</style>"
                      "</head><meta http-equiv=\"refresh\" content=\"15\"></head>"
                      "</BR></BR><h1 align=\"center\">ESP8266 Host 1</h1></div>";

void setup(){
  WiFi.begin(ssid, pass);
  WiFi.config(ip, gateway, subnet);
  
  server.on("/",handleRoot);
  server.on("/voltage",voltage); 
  server.begin();
}

void loop(){
  server.handleClient();
}

void handleRoot(){
  String message = "";
  message += headAndTitle;
  message += "<div align=\"center\";>";
  float measuredValue_1 = analogRead(A0)/1024.0 * 3.3;
  message += "</BR><button class=\"button blueButton\">";
  message += "Voltage_2 [V]: </BR>";
  message += String(measuredValue_1, 2);
  message += "</button>";
  message += "</div>";
  server.send(200, "text/html", message); 
}

void voltage(){
  float measuredValue_1 = analogRead(A0)/1024.0 * 3.3;
  String message = String(measuredValue_1, 2);
  server.send(200, "text/html", message);
}

 

Die Host MCUs stellen hier also zwei Seiten zur Verfügung. Bei Aufruf des Hauptpfades wird die „angehübschte“ Seite übermittelt, bei Aufruf von „/voltage“ wird einfach nur der blanke Messwert übergeben.

Sketch für die „main“ MCU

#include <ESP8266WebServer.h>  // <WebServer.h> for ESP32
#define HOST_1 0
#define HOST_2 1

const char* ssid = "Your SSID";
const char* pass = "Your Password";

const char* host_1 = "192.168.178.111";
const char* host_2 = "192.168.178.112";

String voltage[2] = {"",""};
unsigned long requestPeriod = 15000;

IPAddress ip(192,168,178,110);
IPAddress gateway(192,168,178,1);
IPAddress subnet(255,255,255,0);

ESP8266WebServer server(80);  // WebServer server(80); for ESP32
 
String headAndTitle = "<head><style>"
                        ".blueBox {"
                          "background-color: blue;"
                          "color: white;"
                          "width: 350px;" 
                          "padding: 20px;"
                          "text-align: center;"
                          "font-size: 50px;"
                          "font-family: arial;"
                        "}"
                      "</style>"
                      "</head><meta http-equiv=\"refresh\" content=\"10\"></head>"
                      "</BR></BR><h1 align=\"center\">Get data from other ESP8266 modules</h1></div>"
                      "<div align=\"center\">STA Mode, get measured values from two hosts</BR></BR></div>";

void setup(){
  Serial.begin(115200);
  WiFi.config(ip, gateway, subnet);
  WiFi.begin(ssid, pass);
 
  server.on("/",handleRoot);
  server.begin();
}

void loop(){
  static unsigned long startTime = millis();
  server.handleClient();
  if ((millis() - startTime) > requestPeriod){
    requestESP8266(HOST_1);
    requestESP8266(HOST_2);
    startTime = millis();
  }
}

void handleRoot(){
  String message = "";
  message += headAndTitle;
  message += "<div align=\"center\";>";
  float measuredValue_1 = analogRead(A0)/1024.0 * 3.3;
  
  message += "</BR><div class=\"blueBox\">";
  message += "Voltage_1 [V]: </BR>";
  message += String(measuredValue_1, 2);
  message += "</div>";

  message += "</BR><div class=\"blueBox\">";
  message += "Voltage_2 [V]: </BR>";
  message += voltage[HOST_1];
  message += "</div>";
  
  message += "</BR><div class=\"blueBox\">";
  message += "Voltage_3 [V]: </BR>";
  message += voltage[HOST_2];
  message += "</div></div>";
  server.send(200, "text/html", message); 
}

void requestESP8266(int hostNo){
  WiFiClient client;
  String host = "";
  if(hostNo == HOST_1){
    if (!client.connect(host_1, 80)) {
      Serial.println("connection failed to host 2");
      host = host_1;
      return;
    }
  }
  if(hostNo == HOST_2){
    if (!client.connect(host_2, 80)) {
      Serial.println("connection failed to host 3");
      host = host_2;
      return;
    }
  }
  String url = "/voltage";
  Serial.print("Requesting URL: ");
  Serial.println(url);
   
  
  client.print(String("GET ") + url + " HTTP/1.1\r\n" +
               "Host: " + host + "\r\n" + 
               "Connection: close\r\n\r\n");
  unsigned long lasttime = millis();
  while (!client.available() && ((millis() - lasttime) < 3000)){
    delay(1);
  }  
  while (client.available()) {
    String line = client.readStringUntil('\r');
    if(line != ""){
      voltage[hostNo] = line;
    }
  }
  Serial.println();
  Serial.println("closing connection to ESP8266");
}

 

Nun könnt ihr euch die gesammelten Messwerte, den Hauptpfad der Hosts oder einfach nur den unformatierten Messwert eines Hosts anschauen. Ich habe hier einmal alle drei Varianten parallel aufgerufen:

MCU WLAN Netzwerk, links: "Main MCU", Mitte: Host (Hauptseite), rechts: Host ("/voltage")
MCU WLAN Netzwerk, links: „Main MCU“, Mitte: Host (Hauptseite), rechts: Host („/voltage“)

Danksagung

Auch für diesen Beitrag habe ich mich wieder fleißig bei den Bildern auf Pixabay bedient:

46 thoughts on “WLAN mit ESP8266 und ESP32

  1. Hallo Wolfgang Ewald
    tolle Anleitung – hat bei mir alles auf Anhieb funktioniert, nach den Anpassungen auf mein System (ESP32 Lolin).
    Nun habe ich paar Fragen:
    > zwei meiner drei LEDs haben einen Transistor als Endstufe und schalten also entgegengesetzt zu der LED auf dem Board (die ja direkt am Port angeschlossen ist). Wie kann ich im Code den Pegel der LEDs invertieren, so dass trotzdem alles richtig schaltet?
    > warum muss man sich den Status der LEDs in einem Array merken, kann man den nicht aus den Ports direkt auslesen?
    >ich will mehrere Temperatursensoren (DS18B20) auslesen und genauso wie die Spannung in farbigen Boxen anzeigen lassen. Wie füge ich die weiteren Boxen hinzu?
    > wie kann man einen Sicherheitscode einbauen, da das Ganze ja völlig offen ist und jeder per Webseite auf die Schalter und Messwerte zugreifen kann?

    Danke im Voraus

    1. Hallo,
      zu den vier Fragen:
      1) In switchLEDAndSend beim Aufruf von digitalWrite state in !state ändern. Und natürlich in setup den Ausgangszustand ändern.
      2) Geht auch, ich fand es so aber besser als bei jedem Schalten alle Zustände abzufragen. Aber globale Variablen einzusparen ist auch gut.
      3) Ich kann hier im Rahmen der Kommentare kein HTML Tutorial geben. Um einfach noch eine Box dranhängen, muss man im Prinzip nur den entsprechenden Code für die erste Box kopieren und hinten dran hängen.
      4) Die durch den ESP32 oder ESP8266 bereitgestellten Webseiten sind nur im lokalen Netzwerk (Heimnetz) erreichbar, welches von außen normalerweise nicht zugänglich ist (höchstens über VPN). Das Heimnetz selbst ist über ein Passwort geschützt. Also stellt sich die Frage eigentlich nicht, es sei denn, du möchtest auch anderen Nutzern deines lokalen Netzes den Zugang verwehren. Das führt hier aber zu weit.

      VG, Wolfgang

      1. danke für die schnelle Antwort – das mit den LEDs funktioniert soweit.
        nun habe ich zu der blueBox noch eine yellowBox definiert mit den selben Parametern nur Boxfarbe yellow statt blue und Schriftfarbe schwarz anstatt weiß. Die Box wird auch so dargestellt mit dem Wert des DS18B20 darin.
        Aber beide boxen lassen sich nicht untereinander anordnen – die blaue überlagert die gelbe box (es muss was mit dem Textfluss float zu tun haben) Ich hab versucht Aufklärung zu finden aber die CSS-Anleitungen sagen mit nichts.
        Wie kann ich also die yellowBox (und später weitere Boxen oder Buttons) unter die blueBox positionieren?

        hier der Auszug aus meinem Code:
        //Akkuspannung
        float measuredValue1 = analogRead(35)/955.0; //Spannungsteiler Akkuspannung und ADC justiert
        message += „“;
        message += „Akku-Spannung [V]: „;
        message += String(measuredValue1, 2); //zwei Nachkommastellen
        message += „“;
        message += „“;
        server.send(200, „text/html“, message);

        //Tempsensor1
        float measuredValue2 = (tempSensor1); //DS18B20 auslesen
        message += „“;
        message += „Temp [ºC]: „;
        message += String(measuredValue2, 1); //eine Nachkommastelle
        message += „“;
        message += „“;
        server.send(200, „text/html“, message);

        1. Die Kommentarfelder verstehen HTML und interpretieren deshalb alle Eingaben zwischen „kleiner als“ und „größer als“ als HTML-Code. Deswegen wird Code hier nicht richtig dargestellt. Außerdem werden Einrückungen ignoriert. Das kann ich nicht abstellen. Da muss man dann die HTML Anweisungen zur Darstellung dieser Zeichen nehmen. Führt zu weit hier. Du kannst den Code schicken an wolfgang.ewald@wolles-elektonikkiste.de.

          Zeilenumbrüche werden mit </BR> eingefügt und jede Box muss in eine eigene div Umgebung <div>….</div> .

  2. Hallo Wolfgang,
    erstmal auch von mir herzlichen Dank für Deine Tollen Beispiele zum Webserver. Für mich als Laie zu dem Thema waren diese sehr gut zu verstehen. Leider funktionieren die Beispiele nicht für meine Anwendung oder ich bin noch nicht auf den geeigneten Workaround gekommen. Zum einen möchte ich auf meiner Seite Sensor Werte anzeigen, zum anderen einen Sollwert via zwei Button (+ / -) um ein Inkrement nach oben oder unten verändern. Da die Seiten aber immer wieder aktualisiert werden verändert sich der Wert nach der ersten Betätigung ständig je nachdem welche Taste (+ / -) zuletzt gedrückt wurde. Eine Enter Taste würde Abhilfe schaffen ist aber nicht sehr schön in der Anwendung. Vielleicht hast Du ja eine Idee?
    Viele Grüße und Danke schonmal. Daniel

    1. Hallo Daniel,
      interessante Frage – und diese Einleitung heißt, dass ich selber erst einmal ins Grübeln kam. Ich habe es mal nachvollzogen und bin auf dasselbe Problem gestoßen. Die Lösung ist, dass du von den + und – Seiten auf die Hauptseite umleitest. Dazu habe ich das „one LED“ Beispiel genommen und für das + definiert:

      server.on(„/plus“, add); mit „/plus“ anstelle von ledon

      Den Wert, der inkrementiert werden soll, habe ich target genannt. Mein ESP32 Modul hat die Adresse 192.168.18.115. Dann ist die add-Funktion:

      void add(){
        target++;
        String message = "<head><meta http-equiv=\"refresh\" content=\"0; URL=//192.168.178.115\"></head>";
        server.send(200, "text/html", message);
      }
      

      Mit Firefox und Edge hat das problemlos funktioniert.

      Sprich, wenn du auf das Plus drückst, wirst du automatisch zur Hauptseite zurückgeleitet. Ich sende dir den ganzen Sketch per mail.
      VG, Wolfgang

  3. Hallo Wolfgang,
    erst einmal vielen Dank für eine ausführliche Anleitung, sie ist sehr gut verständlich. Ich möchte in meinem Projekt mehrere ESP8266 über einen Router vebinden und diese mit einem ESP32 fernsteuern. Da die ESP8266 nur jeweils zwei Relais schalten sollen, habe ich mich für die server.on() Methode entschieden, weil dabei keiner der Teilnehmer Traffic erzeugt, wenn nichts geschalten werden soll. Der Sketch auf dem ESP8266 ist fertig und funktioniert über den Browser. Mir ist nur nicht klar, wie dann der ESP32 den Befehl (z.b. LED0_on an den ESP8266 senden soll. Der ESP8266 ist dann Server und der ESP32 Clinet, oder? Vielen Dank im Voraus!

    1. Hallo Michael,

      ja, so würde ich es machen. Die ESP8266 als Server und den ESP32 als Client. Im Prinzip ist das der Aufbau aus dem letzten Abschnitt „Mehrere MCUs vernetzen mit Router als Access Point“. Dann nimmst du den Sketch main_mcu_setup_with_router.ino und änderst ihn ab. In Zeile 94 wird der Nachricht an die Server „\voltage“ zugefügt. Das musst du dann entsprechend umschreiben, so dass du „\led0_on“ oder „\led0_off“ wahlweise versenden kannst. Ich hoffe, es is ungefähr klar?! Ich muss mich da übrigens auch immer wieder neu reindenken!

      VG, Wolfgang

      1. Hallo Wolfgang,
        Vielen Dank für deine Erklärung, meist genügt ein Kick in die richtige Richtung:-) Hab es ausprobiert, funktioniert! Ich brauche eigentlich die Antwortfunktion des Servers nicht, werd es aber trotzden als Bestätigung verwenden, dass das Relais geschalten wurde.

      2. Hallo Wolfgang,
        Danke für Deine Anleitungen. Ich bin nun Tag 2 gespannt vor dem PC und probiere das Gelernte mit meinen ESP8266 D1 mini etc. aus. Heute erwarte ich die nächste Lieferung und dann kann ich meine ESP Familie mit Aufgaben versehen. Es ist toll, am PC, per Tablet oder mit dem Handy den Überblick zu behalten, über Sensordaten inclusive Spannungen bei externen Aufbauten. Ich werde das für meine mini Solartechnik (im ‚kleinen‘ Balkonkraftwerk ohne Einspeisung, aber mit mehreren Powerboxen und weiteren Speichern) und mehr nutzen. Damit wird ein langjähriger Wunsch wahr. Es ist aufregend und toll, vielen Dank!

  4. Hallo 🙂 Ich muss das Thema nochmals hochholen und hoffe auf Antwort .
    Ich bin in der Firma ins kalte wasser gschmissen worden und muß ein az delivery esp32-wroom-32 ins wlan reinbekommen um überhaupt mit meinem projekt weiter zu kommen .
    Da habe ich aber schon das problem das ich das schon nicht hinbekomme .
    Wie wäre da die einstellungen ?
    const char* ssid = „Your SSID“;
    const char* pass = „Your Password“;

    1. Hallo, „Your SSID“ ist der Platzhalter für den Namen des WLANs, „Your Password“ ist der Platzhalter für das Passwort des WLANs. Die Zugangsdaten des WLANs deiner Firma kenne ich natürlich nicht. Falls du sie auch nicht kennen solltest, musst du den IT-Experten bei euch fragen. Die Anführungszeichen musst du beibehalten. Hoffe, das hilft….

  5. Hallo Wolfgang, ich bin begeistert was alles mit dem ESP8266 geht und wie du das gut rüber bringst.
    Vielen dank für die guten Beispiele.
    LG, Martin

  6. Guten Morgen Wolfgang,
    ich habe bereits erfolgreich mit Deinen Anleitungen hier und aus dem HX711 Beitrag eine Körperschwerpunktwaage gebaut die von einem ESP8266 ausgewertet und als Webseite ausgeliefert wird. Da ich ständig 2 oder mehr Deiner Anleitungen verwende ist es etwas schwierig für mich die Richtige Anleitung für meine Frage auszuwählen.
    Ich versuche es aber mal hier, weil es nicht um die Messung sondern die Übertragung geht :
    Ich möchte nun die Abtastrate des HX711 auf 80Hz ändern und 5 Sekunden messen. Macht 80×5=400 Messerwerte pro HX711. Ich verwende derer 4 also kommen 1600 Messwerte a 24bit in 5 Sekunden zusammen. Die möchte ich in einem Rutsch via HTTP übertragen. Weder das Übertragen, noch das Auslesen macht mir Sorge. Die Frage ist wo sammle ich diese 1600 Werte und speichere sie zwischen? 24bit = 3 Byte, macht 4,8KiB. Kann man das einfach in eine Variable Packen und dann raussenden? Oder müsste ich die Daten irgendwie abspeichern?
    VG Leif

    1. Hallo Leif,

      für 24 Bit brauchst du ein long oder unsigned long. Ich war mir nicht sicher, wie weit man mit der Größe von Arrays auf dem ESP8266 gehen kann, also habe ich gerade einmal folgendes ausprobiert:

      unsigned long test[1600] ;
      for(int i=0; i<1600; i++){
      test[i] = 16000000 + i;
      }
      und konnte dann auf ausgewählte Element zugreifen. Ein AVR basierter Arduino könnte das nicht, da der Speicher nicht ausreicht.

      Eine long Variable belegt 4 Byte und damit verschenkst du ein Byte pro Wert. Wenn du das nicht möchtest könntest du alternativ ein Array von 4800 bytes erzeugen, also
      uint8_t test[4800];
      Dann nimmst du die Messwerte, „zerhackst“ sie in einzelne Bytes und schiebst sie in das Array. Nach dem Übertragen müsstest du daraus dann wieder long oder unsigned long Werte basteln.
      Viel Erfolg!
      VG, Wolfgang

  7. sehr coole Anleitung
    ich bin momentan gerade mit dem ESP32 am rumprobieren aber finde die Library nicht. Es gibt tausende die Webserver heißen und unter Webserver.h finde ich gar nichts. kannst du evtl. noch durchgeben welche Library du für das ESP32 verwendet hast? 😀 vielen Dank schonmal im voraus 🙂

      1. Vielen dank für die Antwort. Ja da hab ich wohl das ESP32 falsch implementiert. Mitlerweile funktionierts 😍

  8. Hallo!
    Die Seiten sind wirklich eine Bereicherung für Arduino, ESP etc. Es ist alles sehr gut strukturiert und ausreichend erklärt ohne irgendwelche Besserwisser-Kommentare. Ich habe aber ein kleines Problem. Als Modul habe ich einen ESP8266 (Board: NodeMCU1.0(ESP-12E Module) )und eine Arduino IDE 1.8.19. Der erste Sketch (web_server_basic_choose_IP.ino), bei dem ein Webserver eingerichtet wird, funktioniert so wie angegeben. Es wird über den seriellen Monotor die IP_ Adresse ausgegeben (bei mir: 192.168.1.108). Beim 2.Beispiel (ESP8266_ESP32_one_LED.ino) gibt es folgendes Problem: Das Hochladen ist o.k. Ruft man den seriellen Monitor auf; so erscheint auch die Meldung: Connecting to: (Anzeige ist meine ssid), dann erscheinen Punkte, deren Anzahl nicht endet, d.h. die Verbindung kann nicht stattfinden?? Passwort und ssid stimmen, mit einem ESP32 klappt es.
    Kann mir geholfen werden?
    Dank im voraus.

    1. Hallo Heinz,

      erst mal vielen Dank für das Lob – geht runter wiel Öl!

      Ich habe gerade den Sketch, der bei dir nicht funktioniert, auf einem NodeMCU 1.0(ESP-12F) Modul getestet. Also anstelle 12E steht auf meinem Modul 12F. In der Arduino IDE habe ich aber die „E“ Version ausgewählt. Bis auf die Zugangsdaten und die IP Adresse habe ich nichts verändert. Das Modul verbindet sich ohne Probleme. E oder F sollte eigentlich keine Rolle spielen. Was mich wundert ist, dass es mit dem ESP32 geht und nicht mit dem NodeMCU. Ist die IP Adresse im Router vielleicht fest vergeben an den ESP32? Ich habe eine FritzBox, da kann man Geräten eine feste IP zuordnen, und die könnte man dann auch nicht anderweitig vergeben. Aber bevor du lange in den Router Menüs suchst, was passiert denn, wenn du einfach eine andere IP Adresse eingibst? Z.B. die ….109 oder ….107? Und hast du bei der IP Adresse außer der letzten Zahl auch die vorletzte (178 in meinem Beispiel) auf 1 geändert? Also bei dir müsste es heißen:
      IPAddress ip(192,168,1,108);
      IPAddress gateway(192,168,1,1);
      Anderseits dürfte es dann auch nicht mit dem ESP32 funktionieren. Also, probiere am besten mal ein paar IP Adressen aus.
      Du kannst dich ja nochmal melden, wenn es immer noch nicht geht.
      VG, Wolfgang

  9. Hallo,

    sehr gute Erklärung….
    Ich als Anfänger kann mehr solche Gute Infos benötigen.

    Versuche das nachzubauen mit einer RGB LED und einem ESP32.
    Besten Dank für die ausführlichen Erklärungen

  10. Ein sehr schöner Beitrag, der auch Grundlagen für Client Server Projekte liefert.

    Ich versuche -als ein blutiger Anfänger – ein Projekt zu realisieren, in dem ein Wemo D1 Mini beim Schließen eines Kontaktes (GPIO) eine Seite auf einem Server(Raspberry Pi) aufruft.
    Der Raspberry macht schon was ich will.

    Dir eine gute Zeit und bleib gesund.
    Martin

    1. Danke für’s Feedback und viel Erfolg mit dem Projekt. Wenn es irgendwo hakt, dann kannst du dich ja noch einmal melden. Bleib auch gesund!

  11. Hallo,
    pmmernoch sehr gut!
    Aber Vorsicht! Uch habe es unter Linux installiert, dabei wurden aber nicht alle Bibliotheken eingebunden.
    Als ich es später unter Kleinweich 10 installierte ging es! Danach nochmals mit Linux und nachgeladenen Bibliotheken. Jetzt geht es.
    Bei Linux wird leider nicht angezeigt, welche Bibliotheken fehlen.
    Das Problem ist dabei die Arduino-IDE. Unter Kleinweich ist es besser ausgeführt, Warum?

    1. Hi, ich kann dir nicht sagen, wieso es unter „Kleinweich“ (nette Bezeichnung für Microsoft) läuft und nicht unter Linux. Vielleicht funktionieren die älteren Arduino IDE Versionen unter Linux? Die 2.0.0 ist ja noch im Beta Stadium, vielleicht wird an der Stelle noch gebastelt.

  12. ich bekomme folgende Fehlermeldung:

    oot ERROR Error from plugin host: Channel closed
    root ERROR Error from plugin host: Channel closed
    daemon INFO inventory.Store.ConfigFileUsed() /home/bernd/.arduino15/inventory.yaml
    daemon INFO inventory.Store.ConfigFileUsed() /home/bernd/.arduino15/inventory.yaml
    daemon INFO Searching tools required for board esp8266:esp8266:nodemcuv2
    daemon INFO Required tool [tool: {ToolName:“mklittlefs“,ToolVersion:“3.0.4-gcc10.3-1757bed“,ToolPackager:“esp8266″}]
    daemon INFO Required tool [tool: {ToolName:“mkspiffs“,ToolVersion:“3.0.4-gcc10.3-1757bed“,ToolPackager:“esp8266″}]
    daemon INFO Required tool [tool: {ToolName:“python3″,ToolVersion:“3.7.2-post1″,ToolPackager:“esp8266″}]
    daemon INFO Required tool [tool: {ToolName:“xtensa-lx106-elf-gcc“,ToolVersion:“3.0.4-gcc10.3-1757bed“,ToolPackager:“esp8266″}]
    daemon INFO Adding libraries dir [dir: /home/bernd/.arduino15/packages/esp8266/hardware/esp8266/3.0.2/libraries, location: platform]
    daemon INFO Adding libraries dir [dir: /home/bernd/Arduino/libraries, location: user]
    daemon INFO Resolving include ESP8266WiFi.h for arch esp8266
    daemon INFO found better lib [lib: ESP8266WiFi, prio: 5E8]
    root ERROR Request upload failed with error: 2 UNKNOWN: exit status 1
    root ERROR Error from plugin host: Channel closed

    1. Hallo,
      in deinem ersten Kommentar hast du geschrieben, dass du einen ESP32 verwendest – in deiner Fehlermeldung steht aber nur etwas von ESP8266. Bist du irgendwie mit den Boards durcheinander gekommen? Welches Board verwendest du? Welchen Sketch? Welche IDE?
      VG, Wolfgang

  13. Moin
    wie immer – sehr ausführlich und informativ. Vielen Dank.

    Hätte zum Thema WLan und WiFi auch OTA (over the Air) gepasst?
    Gerade für den ESP-01/ESP-01S sehr interessant.

    Gruß
    Robert

    1. Ja, sicherlich. Danke für die Anregung. Vielleicht mache ich dazu nochmal was. VG, Wolfgang

  14. Sehr gute Erklärung. Schön strukturiert. Besten Dank dafür.
    Selten eine so gute Seite im Web gefunden!

  15. Vielen Dank für die Super Erklärungen und die tollen Beispiele.
    Besondern Danke noch für den Beitrag über DCF77 funktioniert super.
    Ich bin noch am Anfang mit Arduino usw. aber durch deine Beschreibungen mache es sehr viel
    spaß und man kommt sehr gut voran.
    Bin gerade dran meinen Smoker endlich auf WiFi umzubauen ( UDP ).
    Bin mir noch nicht ganz sicher auf welchem Weg ich die Daten schicken soll.
    Da hat mir dein Beitrag schon sehr viel geholfen.

  16. Auch von mir ein herzliches Danke und bei der Gelegenheit gesegnete Weihnachten.
    Endlich eine Seite, auf der es so gut erklärt wird, das auch ein Anfänger den Fuß in die Tür bekommt.
    Es ist extrem schwer wenn man sich selbst alles beibringen muß und auch Bücher sind oft nicht hilfreich.
    Da bin ich froh, wenn sich jemand solche Mühe macht und man Perlen wie diese findet.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert