Asynchroner Webdav-Server für das ESP8266 und ESP32

22.01.2020 Web Embedded
 

Einleitung

In der Vergangenheit habe ich öfter Projekte für das ESP8266 erstellt. Viele Leute online betten ihren HTML-Code in den C++-Code ein. Aus Gründen der Softwarearchitektur habe ich mich entschlossen eine Lösung für diese unsaubere Art der Programmierung zu finden und das Backend und Frontend zu trennen.

ESP8266 und ESP32

Bevor ich wie üblich direkt loslegen möchte ich das ESP8266 und ESP32 vorstellen. Für meine Projekte habe ich zwei der meiner Meinung nach sinnvollsten und preiswertesten Devboards herausgesucht und stelle diese vor.

Wemos D1 mini (ESP8266)
Das erste Bild zeigt die Vorderseite und das zweite die Rückseite. Es ist eine USB-Buchse vorhanden über die das Modul geflasht werden kann.

Wemos D1 Mini Top Preview
Wemos D1 Mini Bottom Preview

Cores: 1
Clock Speed: 80 Mhz
SRAM: 96 kB (ca. 50 kB nutzbar)
Flash Speicher: 4 MB bzw. 16 MB (pro)
Kosten: ab 2,50 €

ESP32-CAM (ESP32)
Leider ist keine USB-Buchse vorhanden und es muss über einen extra USB-to-Serial-Konverter geflasht werden. Allerdings besitzt das Board bereits eine Kamera, was später für Bilderkennung noch interessant sein könnte.

Wemos D1 Mini Top Preview
Wemos D1 Mini Bottom Preview

Cores: 2 (einer nur für WiFi)
Clock Speed: je Core 80 Mhz
SRAM: 520 kB
Flash Speicher: 4 MB
Kosten: ab 5,50 €

Da das ESP32 das ESP8266 meiner Meinung nach in naher Zukunft nicht ersetzen wird (preislich und wegen des höheren Stromverbrauches), entschloss ich mich mein Projekt für beide Chips kompatibel zu machen. Ich setzte mir also ein Limit von max. 10 kB Verbrauch im Heap, da noch andere Software auf dem Modul laufen sollte.

Async Webserver

Der Entwickler me-no-dev hat auf Github sein Projekt ESPAsyncWebServer veröffentlicht, welches einen asynchronen Webserver bereitstellt, der nicht in der loop()-Methode aufgerufen werden muss. Das hat die folgenden Vorteile:
  • mehrere Verbindungen gleichzeitig verarbeiten
  • deutlich schneller als synchrone Webserver
  • eventbasierte Entwicklung
Außerdem besitzt der Server noch weitere Funktionen wie z. B. Websockets, Anzeigen statischer Inhalte auf dem Flash-Speicher, URL-Rewrites und Templates. Leider ist es gar nicht so einfach Daten auf den Flash-Speicher hochzuladen. Es gibt Tools wie den ESP8266-Filesystem-Uploader, der beim Flashen die Daten hochlädt. Leider hat die Verwendung des Tools bei mir nicht wie gewünscht funktioniert, weshalb ich eine alternative Lösung brauchte.

Also habe ich nachgedacht, welche leichtgewichtigen Protokolle für das Hochladen von Dateien geeignet sein könnten und mir sind die folgenden zwei eingefallen:
  • FTP: geringer Overhead bei der Dateiübertragung, aber komplexes Protokoll (PASV usw.)
  • Webdav: mehr Overhead bei der Übertragung, allerdings Plug-in für einen bereits vorhandenen Webserver
Da Webdav deutlich weniger Aufwand in der Umsetzung bedeutete, entschied ich mich gegen FTP. Jedoch nehme ich mir hiermit vor in naher Zukunft noch einen asynchronen FTP-Server für die ESP-Module entwickeln.

Webdav

Zuerst einmal müssen wir uns Webdav ansehen, bevor es zur Implementierung geht. Im RFC 4918 standardisiert, erweitert es HTTP um folgende Verben:
  • PROPFIND: Ruft Eigenschaften einer Ressource ab und gibt diese im XML-Format aus. Wird ebenfalls zum Auflisten von Verzeichnissen verwendet.
  • PROPPATCH: Ändert und Löscht mehrere Eigenschaften einer Ressource in einem einzigen atomaren Aufruf.
  • MKCOL: Erzeugt Verzeichnisse
  • COPY: Kopiert eine Ressource
  • MOVE: Verschiebt eine Ressource bzw. benennt diese um
  • DELETE: Löscht eine Ressource
  • LOCK: Sperrt eine Ressource
  • UNLOCK: Entsperrt eine Ressource
Da sich der Aufwand für den Einsatzfall nicht lohnt, habe ich mich entschlossen PROPPATCH, LOCK und UNLOCK nicht umzusetzen. Jedoch galt es vorerst ein anderes Problem zu lösen. ESPAsyncWebServer unterstützt nur acht mögliche HTTP-Verben (GET, POST, DELETE, PUT, PATCH, HEAD, OPTIONS, ANY) und kann auch nicht erweitert werden, da das Verb als unsigned 8-bit Integer abgespeichert wird. Deshalb musste ich in folgendem Pull Request auf Github erst die Variable zu einem unsigned 16-bit Integer erweitern und die neuen Verben abfragen.

Für das Speichern der Daten habe ich mich für LittleFS entschieden. Im Gegensatz zu SPIFFS unterstützt dieses Verzeichnisse. Auf die Implementierung werde ich in diesem Post nicht eingehen, da dieser sonst zu lang wird. Wer sich trotzdem für meinen Code interessiert, kann gerne unter folgenden Links auf Github nachsehen:
- AsyncWebdav.cpp
- komplettes Projekt

Nutzung des Plug-ins

Um das Plug-in zu nutzen müssen wir erst einmal einen Sketch erstellen. Folgender Code reicht dafür bereits aus:
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <LittleFS.h>
#include <AsyncWebdav.h>

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

AsyncWebServer server(80);
AsyncWebdav dav("/dav");

void setup(void){
    // init serial and wifi
    Serial.begin(115200);
    WiFi.begin(ssid, password);
    Serial.println("");

    // wait for connection
    while (WiFi.status() != WL_CONNECTED){
        delay(500);
        Serial.print(".");
    }
    Serial.println("");
    Serial.print("Connected to ");
    Serial.println(ssid);
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());

    // init littlefs
    LittleFS.begin();

    // add websocket handler
    server.addHandler(&dav);

    // start webserver
    server.serveStatic("/", LittleFS, "/").setDefaultFile("index.html");
    server.begin();
}


void loop(void){
    // do whatever you want
}

Im ersten Schritt laden wir alle Abhängigkeiten. Anschließend legen wir die WLAN-Zugangsdaten fest und initialisieren den Webserver und das Plug-in. In setup() verbindet sich der Sketch zum WLAN und startet den Webserver mitsamt dem Plug-in. Wie man hier auch sehen kann, wird nichts in loop() ausgeführt.

Um den Sketch hochzuladen, müssen nach dem Verbinden des Moduls mit dem Computer in Arduino die Einstellungen angepasst werden. Möglicherweise gibt es mehrere COM-Ports.
Arduino Config

Den korrekten Port kann man herausfinden, indem man sich mit einem Programm (z. B. HTerm) die Ausgabe der jeweiligen Ports anzeigen lässt und das Modul neu startet. Beim Starten des Moduls werden mit der Baudrate 74880 Startinformationen übertragen.
load 0x4010f000, len 1392, room 16 <\r><\n>
tail 0<\r><\n>
chksum 0xd0<\r><\n>
csum 0xd0<\r><\n>
v3d128e5c<\r><\n>
Beispiel: Ausgabe beim Starten des ESP8266

Nach dem Flashen des Chips sollte via COM die IP-Adresse angezeigt werden. Die Baudrate muss wie im Code angegeben auf 115200 gestellt werden.
.<\r><\n>
Connected to dennisparsch<\r><\n>
IP address: 10.0.60.30<\r><\n>
Beispiel: Ausgabe des Chips nach dem Hochfahren.

Einbinden des ESP8266

Das ESP kann nun wie ein normales Netzlaufwerk eingebunden werden. Dazu muss man unter Computer > Netzlaufwerk verbinden klicken und die Daten eintragen. Windows Add Network Space
Aufgrund eines Fehlers (hier nachzulesen) in Windows wird die Laufwerkgröße falsch angezeigt. Wird eine andere Software, wie z. B. NetDrive3 zum Einbinden der Ressource genutzt, dann wird die Größe korrekt angezeigt. Windows Show Network Space
Lädt man nun eine Datei index.html mit dem Inhalt Hallo Welt hoch, dann wird diese beim Aufruf der Seite im Browser angezeigt: Show Uploaded File on ESP
Auf diese Weise lassen sich sogar Bilder und größere Dateien hochladen. Im Anschluss habe ich eine Seite mit Vue.js erstellt, welche mir Informationen über das Board anzeigt und sich automatisch aktualisiert. Wie ich diese Seite erstellt habe, werde ich aufgrund der Komplexität in diesem Blogpost nicht erklären. ESP Webinterface Preview

Tests

Zu Beginn dieses Blogposts hatte ich geschrieben, dass ich nicht mehr als 10 kB Heap verbrauchen wollte. Da die Software erst mal fertig war, konnte ich nun Tests laufen lassen: Memory Consumption
Was die Werte bedeuten:
  1. Speicherverbrauch nach dem Hochfahren des Moduls in loop()
  2. Anzeige des Laufwerks unter Computer (Abfrage der Kapazität)
  3. Auflisten der Dateien auf dem Speicher (5 Dateien zum Zeitpunkt des Aufrufs)
  4. Hochladen von drei Dateien mit je ca. 5 kB
Das Hochladen einer Datei verbraucht also im Worst Case meines Testszenarios maximal 6 kB Heap. Aus Interesse habe ich noch eine große Datei (ca. 1 MB) hochgeladen und konnte ebenfalls nicht mehr Verbrauch feststellen. Da größere Requests in Chunks aufgeteilt werden, ergibt das Ergebnis Sinn.
Zuletzt habe ich den Unterschied beim Verbrauch gemessen, wenn das Modul gar nicht aktiv ist.
  • nicht aktiv: 48,74 kB
  • aktiv: 46,16 kB
  • Differenz: 2,58 kB
In Summe liegt der Verbrauch noch immer unter den 10 kB.