[dezentrale] ESP8266 Workshop WiFi-Basics

Send to Kindle

OK, weiter im Text. Heute wollen wir den ESP mit einem vorhandenen Netzwerk verbinden oder einen Accesspoint aufspannen lassen. Am Schluss soll ein Webserver auf jede Anfrage mit einer „Hallo Welt!“ Seite antworten.

ESP als Client

Beginnen wir mit dem Code, der zur Verbindung mit einem vorhandenen Netzwerk dient. Er ist recht geradeaus, ich habe ein wenig serielles Debugging eingebuat, das aber letztlich nur bei einem sehr langsamen Accesspoint Erkenntnissgewinn bringt. Viel aussagekräftiger ist hier die LED: In der Setup-Routine lasse ich die LED im Abstand von 100ms blinken, meist ist nach weniger als einer Sekunde die Verbindung hergestellt. Anschließend blinkt die LED im Zweisekundentakt. Die gesamte Magie steckt in den beiden Zeilen „Wifi.mode“ und „Wifi.begin(…)“.

Die IP-Adresse gebe ich über den seriellen Port alle zwei Sekunden aus. Einfach mit der seriellen Konsole verbinden (oder im Webinterface des WiFi-Accesspoints/Routers nachschauen).

#include <ESP8266WiFi.h>

#define STASSID "meinnetzzuhause"
#define STAPSK  "1234567890123456"

const char* ssid     = STASSID;
const char* password = STAPSK;

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  Serial.begin(9600);
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  // explicitly set to station (client) mode
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    digitalWrite(LED_BUILTIN, HIGH);
    delay(100);
    digitalWrite(LED_BUILTIN, LOW);
    delay(100);
  }
}

void loop() {
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
  digitalWrite(LED_BUILTIN, HIGH);
  delay(1000);
  digitalWrite(LED_BUILTIN, LOW);
  delay(1000);
}

ESP als Accesspoint

Oder als Accesspoint… Die Konfiguration benötigt IP-Adresse des APs, Name und Key. Optional ist anzugeben, ob der Accesspoint versteckt werden soll (hier: false) und die Anzahl der maximal gleichzeitig versorgten Clients, hier volle 8. Wenn Ihr bspw. ein über WLAN fernsteuerbares Fahrzeug bauen wollt, möchtet Ihr exklusiven Zugriff vergeben und setzt maximal 1. Hier gebe ich im Loop zum Debugging die Zahl der gerade verbundenen Clients aus:

#include <ESP8266WiFi.h>

#define APSSID "esp8266-workshop"
#define APPSK  "1234567890123456"

IPAddress local_IP(10,10,42,1);
IPAddress gateway(10,10,42,2);
IPAddress subnet(255,255,255,0);

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  Serial.begin(9600);
  Serial.println();

  Serial.print("Setting soft-AP configuration ... ");
  Serial.println(WiFi.softAPConfig(local_IP, gateway, subnet) ? "Ready" : "Failed!");
  Serial.print("Setting soft-AP ... ");
  Serial.println(WiFi.softAP(APSSID, APPSK, false, 8) ? "Ready" : "Failed!");
}

void loop() {
  Serial.print("Stations connected: ");
  Serial.println(WiFi.softAPgetStationNum());
  digitalWrite(LED_BUILTIN, HIGH);
  delay(1000);
  digitalWrite(LED_BUILTIN, LOW);
  delay(1000);
}

Verbindet mit Handy oder Notebook mit dem Accesspoint. Sobald die Verbindung hergestellt ist, ist die ausgegebene Zahl für „Stations connected“ höher. Bei Smartphones müsst Ihr meist bestätigen, dass Ihr das Netzwerk behalten wollt, auch wenn es keine Verbindung ins Internet bereitstellt. Wenn Ihr mit dem Notebook verbindet, könnt Ihr den ESP8266 anpingen:

ping 10.10.42.1

Kurzer Einwurf: Der ESP8266 kann gleichzeitig als Station (Client) und Soft Accesspoint arbeiten. Das ist mit ein Grund für die große Beliebtheit bei Hausautomatiserungslösungen. Im einfachsten Fall stellt der ESP ein Accesspoint bereit, der erreichbar ist, wenn der ESP nicht als Station mit einem anderen Accesspoint verbunden ist. Über den wird der ESP als Client konfiguriert, der für alle anderen Rechner im Netz sichtbar ist. Ein zweiter Nutzen dieser parallelen Funktionalität ist die Möglichkeit, Mesh-Netzwerke aufzubauen, beispielsweise Sensorknoten, die nicht alle zu demselben zentralen Accesspoint verbinden, sondern zum nächstgelegenen ESP. Mehr hier.

Und der „Hallo Welt!“ Webserver

Ihr könnt als Basis für diesen Code jedes der beiden bisherigen Beispiele nutzen. Ich nutze das erste, weil der Code kürzer und damit übersichtlicher ist. Neu ist hier das Objekt des Typs „WiFiServer“, mit dem der Webserver initialisiert wird. Aus dem Client-Objekt wird werden hier die Zeilen den HTTP-Requests ausgelesen – und komplett verworfen (client.readBytes(buf, a)). Im Falle eines REST-APIs müssen wir später den GET-String ermitteln und auswerten. Sobald vom Client keine Request-Zeilen mehr kommen, wird der Puffer geflusht und eine Antwort gesendet. Die Antwort besteht aus dem HTTP-Header, es folgt nach einer Leerzeile der Inhalt der Webseite, hier der minimale Header und eine minimale valide HTML-Seite:

#include <ESP8266WiFi.h>

#define STASSID "pinguinbaendiger"
#define STAPSK  "1234567890123456"

const char* ssid     = STASSID;
const char* password = STAPSK;

WiFiServer server(80);

void setup() {
  Serial.begin(9600);
  pinMode(LED_BUILTIN, OUTPUT);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    digitalWrite(LED_BUILTIN, HIGH);
    delay(100);
    digitalWrite(LED_BUILTIN, LOW);
    delay(100);
  }
  server.begin();
}

void loop() {
  WiFiClient client = server.available();
  if (!client) {
    return;
  }
  while(!client.available()){
    delay(5);
  }
  while(client.available()){
    String line = client.readStringUntil('\r');
    Serial.print(line);
    // max 1 Byte im Puffer? Feddisch, Requests enden
    // auf eine leere Zeile!
    if (client.available() < 2) {
      byte c = client.read();
    }
  }
  client.flush();
  client.print("HTTP/1.1 200 OK\r\n"
    "Content-Type: text/html\r\n"
    "Connection: close\r\n"
    "\r\n" 
    "<!DOCTYPE HTML>" 
    "<html>" 
    "<head></head>" 
    "<body>Hallo Welt!</body>"
    "</html>" 
    "\r\n\r\n");
  client.stop();
}

Für den Test der Verbindung solltet Ihr nicht nur den Browser nehmen, sondern auch CURL. Das zeigt schön beide Teile der Kommunikation:

mattias@barium:~$ curl -v http://10.76.23.208/
*   Trying 10.76.23.208...
* TCP_NODELAY set
* Connected to 10.76.23.208 (10.76.23.208) port 80 (#0)
> GET / HTTP/1.1
> Host: 10.76.23.208
> User-Agent: curl/7.58.0
> Accept: */*
> 
< HTTP/1.1 200 OK
< Content-Type: text/html
< Connection: close
< 
<!DOCTYPE HTML><html><head></head><body>Hallo Welt!</body></html>

* Closing connection 0

Genug für heute!? Ich stehe ab ca. 20:00 auf der Mailingliste und in den auf der Mailingliste vereinbarten Kanälen (Jitsi, Skype) zur Verfügung. Dann können wir auch Dinge besprechen wie das Anzeigen verfügbarer Netzwerke oder einen Blick aufs Mesh werfen (auch für mich Neuland…). Und wenn Ihr Lust habt, können wir das Beispiel noch dahingehend erweitern, dass ein Schalter am ESP ausgelesen und sein Zustand ausgegeben wird. Ich habe zudem einen UDP-Sensor mit Empfängerscript in petto. Das folgt ggf. in der nächsten Session.

Nachtrag, 27. März, 22:05 Uhr: Besser lesbares Parsen des HTTP-Requests und Ausgabe dieses über die serielle Schnittstelle, auch als Vorbereitung für das einfache REST-API im nächsten Workshop-Teil.