Mastering GDAL Tools – ein Online Kurs für den Einstieg

Wer mit Geodaten arbeitet, der landet relativ fix bei der Geospatial Data Abstraction Library (GDAL). Einem Open Source toolset, um eine ganze Menge an georaumbezogenen Datentransformationen durchführen zu können.

Leider ist die Dokumentation und die Tutorials eher so na ja bei gdal.org. Jetzt hab ich einen supercoole Quelle bei Spatial Thoughts gefunden, um sich mit den Tools richtig gut vertraut zu machen:
Mastering GDAL Tools (Full Course Material)

GDAL is an open-source library for raster and vector geospatial data formats. The library comes with a vast collection of utility programs that can perform many geoprocessing tasks and build scalable spatial ETL pipelines without the need for expensive software.

Ich hab mit den GDAL tools ein wenig herumexperimentiert und stelle fest, da steckt eine Menge Power drin.

Installation von GDAL

Die Installation ist ein wenig tricky. Auf macOS kann se auch etwas Zeit in Anspruch nehmen. Ich habe Homebrew benutzt und einfach folgende Zeile eingegeben:

brew install gdal

Ob alles geklappt hat, kann man testen, indem man eingibt:

gdalinfo --version

Das sollte die installierte Version ausgeben. Bei mir ist das gerade
GDAL 3.6.3, released 2023/03/07

Maptiles generieren

So kann man z.B. sehr einfach seine eigenen WMTS/WMS (Web Map Tile Service) erzeugen mit folgendem Befehl:

gdal2tiles.py land_shallow_topo_21600.tif --s_srs=wgs84 -d -p raster

Dafür lädt man einfach zuvor von z.B. der NASA eine Weltkarte im Mercator Format (das ist das gängigste Format, dass auch Google nutzt) runter. Die NASA hat auch ein README.pdf parat, das ein wenig erklärt, wie man die Daten nutzen kann.

Um zu gucken, welche Metainformationen da in dem TIF enthalten sind kann man folgende Zeile eingeben:

gdalinfo land_shallow_topo_21600.tif

Das ergibt dann z.b. eine Ausgabe wie:

Driver: GTiff/GeoTIFF
Files: land_shallow_topo_21600.tif
Size is 21600, 10800
Metadata:
  TIFFTAG_RESOLUTIONUNIT=2 (pixels/inch)
  TIFFTAG_XRESOLUTION=72
  TIFFTAG_YRESOLUTION=72
Image Structure Metadata:
  COMPRESSION=LZW
  INTERLEAVE=PIXEL
  PREDICTOR=2
Corner Coordinates:
Upper Left  (    0.0,    0.0)
Lower Left  (    0.0,10800.0)
Upper Right (21600.0,    0.0)
Lower Right (21600.0,10800.0)
Center      (10800.0, 5400.0)
Band 1 Block=21600x1 Type=Byte, ColorInterp=Red
Band 2 Block=21600x1 Type=Byte, ColorInterp=Green
Band 3 Block=21600x1 Type=Byte, ColorInterp=Blue

Dann transformiert man das z.B. auch mit folgendem Tool in ein .png File:

gdal_translate -of png land_shallow_topo_21600.tif land_shallow_topo_21600.png

So bekommt man ein PNG aus dem TIFF.

Why do I blog this? Es ist nicht so leicht mit Geodaten zu arbeiten. Jede Hilfe die man bekommen kann ist da willkommen. Ich hab festgestellt, das man mit den GDAL tools – z.B. eingebunden per Python – eine ganze Menge Tricks machen kann.

ESP32/ESP8266: Von 100 zu 10000 Zeilen Code

Die ersten Zeilen Code waren schnell geschrieben und liefen in der Arduino IDE. Doch bei der einzigen Methode void testLedStrip() sollte es nicht unbedingt bleiben. Und bereits diese allererste Version behrrschte das Art-Net-Protokoll. Doch schnell wurde es deutlich mehr Code…

Handbremse lösen hilft

Als eine der wichtigsten Veränderungen sehe ich rückblickend den Wechsel auf PlatformIO in VSCode von der Arduino IDE. Die Arduino IDE hat durchaus ihre Berechtigung, jedoch ist die Java Basis ein solcher Nervfaktor bei der Geschwindigkeit bestimmter Operationen, dass man schon viel Geduld aufbringen muss. VSCode + PlatformIO hat einige sehr wichtige Vorteile gebracht:

  • Code Completion
  • Macros
  • Übersichtliches File Handling
  • Konfigurierbare Build-Targets
  • Perfekte Suchen/Ersetzen-Funktionen für Refactoring
  • Pre-Compiler checks

Um nur einige zu nennen. VSCode ist im Prinzip mehr der Atom Editor und hat mit Visual Studio wenig zu tun. Das PlatformIO Plugin rüstet den Editor auf zu einer IDE. Das ist durchaus verblüffend, wie gut das funktioniert.

Sehr hilfreich ist vor allem die Peek-Funktion in den Code der hinter bestehenden Funktionsaufrufen steht. Gerade wenn diese Funktionsaufrufe z.B. im Code eines Frameworks liegen oder in Code den man länger nicht angefasst hat, ist es superhilfreich ein kleines Snippet von dem betroffenen Code mal eben sehen zu können. Ruck Zuck weiß man wieder was das Ding eigentlich tut.

Auch die breite Miniatur-Vorschau-Scrollleiste ist einfach megapraktisch. Weil man sich gerade bei langen Codefiles optisch perfekt orientieren kann.

Ab und an muss man VSCode dann zwar mal neustarten, wenn es zu langsam wird, aber das ist vertretbar, ich hatte keinen einzigen Crash der App und keine Datenverluste beim Coden damit.

C/C++

Etwas kann einem die IDE nicht abnehmen, das Wissen um C/C++ ist nicht mal eben auf Abruf erhältlich. Und hier bin ich im Learning-by-Doing, schnell in viele kleine Fallen gerannt. Von den speziellen Dingen rund um Scopes bis hin zu Pointern und Objektorientierung C-Style, hab ich alles berührt was nötig war, um stabilen Code zu schreiben.

Eine der wichtigsten Erkenntnisse für mich war, dass diese ganzen Dateien die man da so anlegt, letztlich nur Strukturierungshilfen für Menschen sind, dem Compiler ist es völlig schnurz, denn behandelt wird all das lustig geschriebene als wäre es EINE große DATEI. Und daher ergeben sich auch manche Probleme die man nicht erkennen kann, wenn man nicht auf diese Metaebene steigt und sieht, dass alle Dateien eigentlich in eine große Datei einfliessen, die für den Compiler die Wahrheit abbildet.

Von Variablen die angeblich schon deklariert wurden, bis hin zu fehlenden Deklarationen und doppelten imports sowie weitergereichten Pointern, die beim Verlassen des Scope ungültig wurden, habe ich alle Fehler gemacht, die man sich so vorstellen kann.

Aber es hat mich dennoch nicht vom Ziel abgebracht und mit einigem Google-Aufwand habe ich mich soweit fortgebildet dass mir die Arbeitsweise mit dieser Sprache immer vertrauter wurde. Am Ende habe ich sogar Bit-Shifting gemacht, um das Art-Net-Protokoll für soegenannte Art-Poll-Requests zu befähigen. Bits zählen ist so mit das Langweiligste was man tun kann, und sehr fehleranfällig noch dazu.

Mein wichtigstes Debugging Werkzeug war

Serial.println( "DEBUG: I AM HERE." )

das habe ich dann später ausgebaut zu ein paar Macros, die ich über des BUILD-Flag DEBUG_LAM dann komplett abschalten konnte.

    #ifdef DEBUG_LAM
    #define LOG( x )  Serial.print( x )
    #define LOG_ENC( x, y )  Serial.print( x, y )
    #define LOG_LN( x )  Serial.println( x )
    #define LOG_LN_ENC( x, y )  Serial.println( x, y )
    #else
    #define LOG( x )
    #define LOG_ENC( x, y )    
    #define LOG_LN(x)
    #define LOG_LN_ENC( x, y )
    #endif

Später habe ich dann begonnen auch Stackdumps zu dekodieren. Aber da war die Software eigentlich schon fertig.

Features: OTA Updates, WebServer, Art-Net, Drucktaster, Konfiguration

Nachdem die ersten LED’s am leuchten waren, wollte ich natürlich Effekte. Also wurde ein paar mal die LED-Routine die den Streifen befeuert umgeändert und erweitert. Doch das reichte irgendwann nicht mehr, denn die Anzahl der Effekte nahm zu. Und ich wollte gerne wechseln können zwischen den Effekten. Auch ein einfacher Ein-Aus-Schalter wäre nützlich.

Also begann ich zunächst zwei PINS mit Drucktastern zu verbinden und diese Taster mit einen PULL-UP-Widerstand zu verdrahten, der beim Drücken der Taste volles Pegelsignal liefert. Hier war tatsächlich die Vorarbeit im Arduinokurs zu Ampelschaltungen mit Grünanforderung extrem nützlich. Denn ich wusste dass ich einen PULL-UP oder PULL-DOWN Widerstand in der Schaltung brauchte für ein gutes Signalhandling.

WebServer zur Bedienung

Relativ schnell kam dann der Wunsch auf, nicht nur einen Effekt zu haben, sondern eine Art Benutzeroberfläche / UI für die Bedienung der „Lampe“. Das An-/Ausschalten und auch z.B. die Helligkeit zu regeln. Durch Delphino hab ich dann gelernt, dass es einen kleinen WebServer für Microcontroller gibt, den ESPAsyncWebServer.

Nach ein wenig Rumprobieren, hatte ich den Server am Laufen und konnte zumindest rudimentär meine Lampe steuern. Das war schon ein beeindruckender erster Moment, das sowas auch mit einem Microcontroller geht. Es war nicht sonderlich schnell aber es funktionierte mit einem simplen Webbrowser und ich konnte damit die die Lampe selbst An- und Ausschalten, Art-Net Support An- und Ausschalten, die Helligkeit regeln, und den Controller Neustarten. Das waren so die wichtigsten Sachen die mir erstmal einfielen, die ich haben wollte.

Die aktuelle Ausbaustufe der Webseite sieht jedoch mittlerweile deutlich umfangreicher aus. Die gesamte Web UI ist mittlerweile optimiert darauf, nicht nur auf dem Desktop, sondern vor allem auf mobile Devices perfekt als Progressive Web Application (PWA) zu funktionieren, also möglichst den Eindruck einer eigenständigen App zu erzeugen.

Durch einige ergänzte <meta>-Tags und ein paar Icon-Dateien konnte ich für iOS und Android ein App-Erscheinungsbild realisieren, das auf beiden Plattformen eine schöne Bedienung zulässt und Home-Screen-Shortcuts erlaubt. Nachstehend ein Foto von der PWA auf einem alten iPod.

Webseiten in Code bauen?

Die erste Webseite die man da weiter oben im Screenshot sieht, hatte ich komplett in Code zusammengesetzt, inkl. des CSS. Das ist eine ziemlich krass Speicherfressende Methode wie ich schnell feststellte. Den gerade das Verketten von Strings sorgt schnell für Speicherfragmentierung und Stackoverflows. Ein Ausweg: Das Flash Filesystem (FS).

Mit Hilfe des SPIFFS Framework, aktivierte ich ein Filesystem auf dem Microcontroller, das es erlaubt Dateien auf dem Controller zu speichern und abzurufen.

SPIFFS steht für (S)erial (P)eripheral (I)nterface (F)lash (F)ile (S)ystem und gemeint ist dabei, dass unser ESP im SPI Programmspeicher, der auch unseren Programmcode enthält, ein einfaches Dateisystem halten kann. — Quelle

Ab jetzt musste der WebServer „nur noch“ Dateien ausliefern. Und ich konnte deutlich schneller meine Webseiten bauen, das CSS anpassen. Es dauerte nicht sehr lange und ich stellte fest, dass jeder TcpRequest der den WebServer traf ganz schön die CPU belastet. Trotz dass der WebServer auf dem zweiten Kern läuft, wird das Rendering der Lampen beeinflusst und andere Tasks scheinbar solange angehalten.

Daher war das Neuladen einer Webseite nach jedem Klick überhaupt keine gute Idee. Es musste eine leichtgewichtige Kommunikation musste her. Als Mittel der Wahl entschied ich mich für Asynchrone Requests per JavaScript. Das war dann auch der Startschuss, ein API zu definieren, denn ich wollte ja keine ganze Webseite mehr ausliefern, sondern am liebsten nur noch kleine JSON-Datenpakete.

Es folgte ein massiver Aufbau einer API (siehe Screenshot). Und mit jedem neuen API Endpunkt konnte die Bedienungs-UI komplexer und umfangreicher werden. Neue Features waren jetzt mehr eine Frage der Nützlichkeit, denn per API hätte ich jetzt alles einbauen können.

Ich hab mir gleich auch doe Mühe gemacht, das API auf dem Controller selbst mit einer HTML-Datei zu dokumentieren. So ist auch for andere ein schneller Lookup möglich.

Remote Fernbedienung? Remote Firmware Update!

Hat man sich erstmal dran gewöhnt, dass man die Lampe fernsteuern kann per Browser über das Wifi, kommt schnell der Wunsch auf mal eben ein Firmwareupdate auf den Controller zu spielen OHNE dass man ihn wieder per USB an den Rechner hängt.

Und das geht tatsächlich megaeinfach. Es nennt sich Over the Air (OTA) Update und wird von den Frameworks unterstützt. Mit einem PlatformIO CLI Command kann man das Update over the Air wie folgt starten:

platformio run -e BUILD_ESP8266_OTA --target upload --upload-port LAM-ESP8266.local

Wichtig ist hier die Angabe der Buildenvironment (hier BUILD_ESP8266_OTA) und des Upload Port (hier LAM-ESP8266.local der mDNS-Hostname den ich für den Microcontroller festgelegt habe; man kann aber auch die IP-Adresse direkt angeben).

Mit in folgender Zeile definiertem Handler im Code legt man z.B. fest, was passieren soll bei Beginn eines Firmwareupdates over the Air:

ArduinoOTA.onStart( []() {

// Code zur Behandlung dieses Ereignisses hier einfügen

});

Das besonders Praktische an einem Update over the Air ist, dass man kein USB-Kabel mehr zum Microcontroller braucht. Und der Controller muss sich lediglich per Wifi im selben Netz befinden, um ihn zu erreichen für ein Update. Es gibt natürlich die Möglichkeit das Update auch mit einem Passwort abzusichern und nur authentifizierte Updates zuzulassen.

Tatsächlich geht ein Update over the Air beim ESP8266 sogar schneller als per USB. Und es ist eben auch ein Weg einen Controller zu erreichen, der vielleicht sogar bereits in einem Gehäuse oder schlecht erreichbar hinter einer Wand montiert ist.

Zwischenfazit

Es ist schlichtweg beeindruckend, was man mit diesem Microcontroller alles tun kann. Allerdings profitiert man deutlich von genügend Vorkenntnissen die man mitbringt. Bei mir waren das Vorkenntnisse in folgenden Bereichen:

  • C (vor allem Macros, Debugging Strategien, Library Management)
  • HTML (wie schaut eine gültige Webseite aus)
  • CSS (wie styled man eine Seite gezielt für hübsches UI, siehe cssdiner.com) bzw. https://flukeout.github.io/
  • Javascript (für eine API-basierte Application)
  • API (wie designe ich eine RESTful API)
  • Arduino Grundlagen (wie kann ich Hardware prototypen mit dem Breadboard)
  • WebServer/TCP/UDP (Grundkenntnisse über Netzwerktraffic, Requesthandling)
  • Grafikbearbeitung (um nötige Icons in benötigter Größe selbst zu erstellen)
  • Browser Dev Tools (Umgang mit der Webconsole des Browsers, oder z.B. Postman als HTTP Client)

Why do I blog this? Der Schritt von einer Lights.ino in der Arduino IDE hin zu einer main.cpp in PlatformIO und dann zu einem Projektordner /src voller Files die sich sauber um einzelne Teile/Features kümmern, war nicht so einfach. Ich hab mindestens zwei größere Refactorings durchgeführt.

Aber es lohnt sich. Belohnt wird man mit einer sauberen Code-Struktur, einer tadellos funktionierenden RESTful API inkl. Dokumentation und einer effizienten Web-Application die auf nahezu allen Browsern Mobile und Desktop reibungslos funktioniert.

Demnächst dann eine Vorstellung des „fertigen“ Systems, sofern so ein Teil jemals fertig sein kann. Denn eines sei verraten, bei einer simplen Lampe hab ich es nicht belassen. Der LED-Strip beherrscht mittlerweile Apps genauso wie Audioausgaben und eine Art-Net-Schnittstelle über die er durch Art-Net bzw. DMX-over-IP prima durch Drittanbieter Apps gesteuert werden kann..

ESP32/ESP8266: Optimizing the hell out of ESPAsyncWebServer

Das schöne an dem Microcontroller ist ja, dass man einen kleinen WebServer darauf laufen lassen kann. Damit hat man eine tolle Möglichkeit ein kleines aber immer verfügbares User Interface (UI) bereitzustellen, das mit fast allen modernen Geräten die einen Webbrowser haben (Tablet, Smartphone, Laptop, PC) sofort funktioniert.

WebServer in klein

Exception Decoder MenuWas man jedoch schnell vergisst, wenn man zuvor schon WebApplications mit WebServern wie nginx oder Apache ausgeliefert hat, ist der Ressourcenbedarf von so einem Server. Der Microcontroller ist halt klein deshalb ja auch „Micro“, hat wenig Speicher und wenig CPU-Zyklen die er für sowas entbehren kann.

Die Kunst liegt also in der Beschränkung. Doch das fällt sauschwer, wenn man erstmal einen gewissen Anspruch an seine Webseiten hatte. Niemand mag gern zurück in die Steinzeit und eine Webseite bauen z.B. ohne Stylsheets. Klar, das geht, sieht aber so schlimm aus und funktioniert auf dem Smartphone z.B. so gar nicht.

Zunächst habe ich den klassischen Weg zur Gestaltung meiner Webseite gewählt, ich hab HTML geschrieben mit Bildern darin und JavaScript das dynamisch Bilder austauscht und platziert, ich habe CSS geschrieben, das Bilder zur Gestaltung einsetzt. Es sieht wunderschön aus auf dem Desktop, aber… es crasht den WebServer des Microcontrollers. Da nutzt dann das ganze tolle Design auch nichts, wenn es letztlich nicht funktioniert.

Und ich habe zig Crashes gehabt. Ich habe mir mühsam den Arduino ESP8266/ESP32 Exception Stack Trace Decoder in der Arduino IDE installiert (Anleitung). Ein dekodierter Stacktrace sieht dann z.B. so aus:

Da sieht man, dass es im konkreten Fall eher ein Speicherproblem ist bei diesem Crash, weil eine alloc Funktion involviert ist. Aber viele andere Crashes sind eben anderer Natur.

Optimizing the hell out of it

Dann beginnt man zu begreifen und optimiert. Was kann man alles tun um zu optimieren? Anfangs dachte ich die Dateigröße wäre der Bottleneck des Microcontrollers. Ich hab die JS, CSS und HTML Dateien mittels GZIP komprimiert und mit dem content-type/gzip im Header ausliefern lassen. Das reduziert auch die Menge an Daten hervorragend. Oft wird nur noch ein Zehntel der Daten übertragen, was die Auslieferung selbst deutlich schneller macht.

Doch die Dateigröße ist gar nicht das Problem beim ESPAsyncWebServer. Das eigentliche Limit ist, dass der nur maximal 3 bis 4 Requests „gleichzeitig“ bzw. parallel bearbeiten kann. Der Rest der Requests hängt dann einfach und wartet ewig auf Antwort und wird verworfen oder führt zu einer Blockade des Webservers und einem späteren Crash. Es hat auch nichts damit zu tun, dass das SPIFFS Filesystem eine Obergrenze an gleichzeitig geöffneten Files hat wie mir ein Entwickler des ESPAsyncWebServer versicherte. Und darauf nun eine passende Antwort bzw. Optimierung zu finden ist gar nicht so einfach gewesen.

Klar, man kann das CSS und das JS alles mit in das HTML-file kippen, aber dann wird es schon arg unübersichtlich. Und des weiteren bleibt das Problem mit den vielen kleinen Icons bestehen, die alle je einen Request auslösen. Das kostet Zeit und trifft auf die Limitierung des Webservers. Das Hauptziel kann also zunächst nur sein die Anzahl initialer Requests (also der Requests die benötigt werden die Seite einmal komplett zu laden) zu minimieren. Und zwar sollte das so passieren, dass man unterhalb des Limits bleibt von max. 3-4 gleichzeitig eintreffenden Requests. Das ist natürlich ein Limit, dass sobald mehr als ein Nutzer gleichzeitig auf das UI zugreift bereits zu Problemen führen kann. Es ist schade, dass die Entwickler von ESPAsyncWebServer das nicht vorhergesehen haben und entsprechend zuviele Requests schlicht und einfach mit Fehler 503 ablehnen sondern stattdessen den Server crashen lassen. Aber vielleicht gibt es auch einen Grund der mir nicht begreiflich ist derzeit.

The Magic of Data-URI’s

Hier kommt dann jetzt eine Optimierungsstrategie zum Zuge, die auch bei „normalen“ Webseiten zu krass verbesserter User Experience (UX) führen kann. Das Einbetten der Bilddaten als sogenannte Daten-URI. Was ist das eine Daten-URI?

The data URI scheme is a uniform resource identifier (URI) scheme that provides a way to include data in-line in Web pages as if they were external resources. It is a form of file literal or here document. This technique allows normally separate elements such as images and style sheets to be fetched in a single Hypertext Transfer Protocol (HTTP) request […]

Das schreibt die Wikipedia dazu. und auf dieser Webseite heißt es „You can embed the image data directly into the document with data URIs.“ und genau das ist eine sehr sinnvolle Sache. Einerseits wird das Dokument (HTML, JS, CSS) in das man diese Daten dann einbettet zwar etwas größer, aber dafür unterbleiben Requests für Bilddaten komplett.

Aus folgendem Code für ein Favicon als Meta-Link im Header in HTML…

<link rel='icon' type='image/png' sizes='32x32' href='fav-icon-32x32.png'>

…wird folgender Code als eingebettete URI:

<link rel='icon' type='image/png' sizes='32x32' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA9tpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wUmlnaHRzPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvcmlnaHRzLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcFJpZ2h0czpNYXJrZWQ9IkZhbHNlIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9ImFkb2JlOmRvY2lkOnBob3Rvc2hvcDowMzhmMTQ5NC1lNGJkLTExZTEtOGQ4MC1jYjVjZDBjYjc4N2QiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6RDJDODA5ODk0RjU1MTFFQThBRjNFQkJENEQ2MUU4MDUiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6RDJDODA5ODg0RjU1MTFFQThBRjNFQkJENEQ2MUU4MDUiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjAyODAxMTc0MDcy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'>

Der entscheidende Teil ist, dass man statt einer Hyperlink-URI eine Data-URI nutzt, die eben so ausschaut:
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAA

Als Werkzeug zur Umwandlung gibt es zahlreiche Online Tools dafür, wie z.B. den Image to data-URI converter. Dort kann man seine Grafikdateien umwandeln lassen. Es gibt auch apps z.B. für macOS den Data URI Converter von Chris Yip.
Mit dem richtigen Tool ist es jedenfalls schnell gemacht.

Ergebnis: Schnell, schneller, embedded Data-URI

Die Ergebnisse sprechen absolut für sich. Nachfolgend habe ich mit zwei Screenshots im Safari Browser festgehalten, die initialen Requests und deren Laufzeit und Datenumfang, die gegen den Controller gehen im zeitlichen Verlauf. Die Ergebnisse sind absolut eindeutig: Data-URI schlägt den klassischen Weg um Längen.

Man erkennt sofort, dass nach dem Laden der initialen HTML Seite core.html weitere Requests ausgelöst werden, klar ist die core.css und core.js werden benötigt, um die Seite überhaupt korrekt anzuzeigen. Aber allein schon die erste Seite auszuliefern benötigt 1,14 Sekunden Zeit, und dann wird es problematisch, da gleich fünf weitere Requests losgehen gleichzeitig, unter anderem eben auch requests wegen Grafiken. Das schafft der Server eben manchmal nicht. Und dann sieht man auch noch, dass jedes Bild ca. 300 bis 700 Millisekunden Zeit benötigt, und das ist sehr lang!! Die gesamte Seite ist erst nach über sechs Sekunden geladen.

Die gleiche Seite optimiert mit Data-URI’s verhält sich deutlich besser. Zwar wird der initiale Download der core.html um etwa 15KB größer, interessanterweise aber kostet das kaum Zeit. Und der initiale Download führt zu lediglich zwei weiteren Requests für die core.css und core.js. Beide Dateien enthalten selbst wiederum ausschließlich Data-URI’s und führen somit zu keinen Folgerequests.

Die Auswirkung ist enorm. Die Bilddaten sind nun in 10 bis 20 Millisekunden geladen statt wie vorher in 300 bis 700. Eine Verbesserung um Faktor zehn. Insgesamt ist die ganze Seite vollständig geladen nach nur etwas über zwei Sekunden. Das erspart satte 4 Sekunden Wartezeit.

Fazit

Wer Webseiten auf dem Microcontroller ausliefert, der sollte ausschließlich inline Data-URI’s für grafische Inhalte verwenden und die Anzahl der Requests soweit wie möglich senken. Für mein Setup bestünde jetzt noch die Option auch die core.css und core.js in die core.html zu integrieren. Dann wäre nur noch eine große Datei nötig.

Aus Gründen der Code-Übersicht sehe ich davon aber erstmal ab. Sollte es aber nochmal Probleme mit dem WebServer geben, dann kann ich hier gezielt weiter optimieren.

Why do I blog this? Das Problem des instabilen und ständig crashenden ESPAsyncWebServer hat mich einige endlos erscheinende Stunden des Debuggens gekostet. Weil ich auch einfach nicht wusste was den WebServer crasht und/oder blockiert und Serial.print() statements haben nicht wesentlich geholfen. Ich hoffe es hilft anderen nicht in die gleiche Falle zu tappen. Sehr hilfreiche Hinweise zum Thema ESPAsyncWebServer habe ich überigens im Diskussionsforum/Chat zu dem Server gefunden. Das hat mir sehr geholfen.