WLED selbst kompilieren: ein verstecktes Feature und eine Partitionstabelle, die OTA nicht ändern kann

Worum es geht Link zu Überschrift

In Teil 7 meiner Homelab-Serie zerfallen die WLAN-Geräte in drei Gruppen: es funktioniert, es funktioniert mit Aufwand, und es funktioniert gar nicht. In der letzten Gruppe stand genau ein Gerät — mein Lichtcontroller mit WLED.

Die Release Notes führen 802.1X-Unterstützung als Feature auf. In der Oberfläche gibt es dafür keine Felder. Die Erklärung stand im zugehörigen Pull Request: Die Funktion wurde umgesetzt, zusammengeführt und dann bewusst hinter einen Compile-Schalter gelegt, weil sie über 100 KB Flash kostet.

Dieser Beitrag ist die Auflösung. Er braucht die Serie nicht als Voraussetzung — wer einen ESP32 mit WLED hat und ein Feature vermisst, das es angeblich gibt, ist hier richtig.

Warum das Feature versteckt ist Link zu Überschrift

Der Grund ist plausibler, als es zunächst klingt, und er hängt an einer Eigenheit der Hardware.

Mein Board ist ein QuinLED Dig-Next-2 mit einem ESP32-PICO-V3-02, 8 MB Flash und 2 MB PSRAM. Das lässt sich in einer Zeile bestätigen:

~/.venv-pio/bin/esptool.py --port /dev/ttyUSB0 flash_id

Nur: Boards dieser Familie werden nicht durchgängig mit derselben Flash-Grösse bestückt. Mal sind es 4 MB, mal 8. Auf der Produktseite steht eine Zahl, im Karton kann etwas anderes liegen, und der Käufer erfährt es erst, wenn er nachmisst. Das finde ich schwer nachvollziehbar — die Flash-Grösse ist keine Nebensächlichkeit, sondern entscheidet darüber, was auf dem Gerät überhaupt läuft.

Für die Firmware hat das eine direkte Folge. Ein veröffentlichtes Binary muss auf allen Boards laufen, also auf dem kleinsten. Bei der 4-MB-Aufteilung bleiben je Anwendungspartition rund 1,5 MB. Was nicht hineinpasst, kann nicht drin sein — und 802.1X mit über 100 KB ist ein guter Kandidat zum Streichen, weil es die wenigsten brauchen.

Das ist keine schlechte Entscheidung. Sie führt nur dazu, dass ein Feature existiert, dokumentiert ist und trotzdem für niemanden erreichbar, der nicht selbst baut.

Der Build Link zu Überschrift

Nichts Besonderes, aber zwei Details, die Zeit kosten können.

Das Environment für dieses Board liegt nicht in platformio.ini, sondern in platformio_override.ini. Wer nur in der Hauptdatei sucht, findet es nicht:

[env:dig-next-2]
board_build.partitions = tools/WLED_ESP32_8MB.csv
board_upload.flash_size = 8MB
board_upload.maximum_size = 8388608
build_flags = ${common.build_flags} -D WLED_ENABLE_WPA_ENTERPRISE

Das Flag WLED_ENABLE_WPA_ENTERPRISE ist der ganze Schalter. Die Partitionierung für 8 MB war im Environment bereits korrekt hinterlegt — was sich später als die eigentliche Pointe herausstellt.

Gebaut wird aus dem Quellbaum mit PlatformIO. Bei mir liegt es in einer virtuellen Umgebung, ein globales pio gibt es nicht:

~/.venv-pio/bin/pio run -e dig-next-2

Mit allen Usermods, die ich haben wollte — audioreactive, multi_relay, sht — kommt das Image auf 1 794 117 Bytes.

Eine Kleinigkeit, die verwirrt: Die entstehende Datei heisst WLED_16.0.1-dev_Dig-Next-2.bin. Das -dev ist nur der Versionsstring aus dem Quellbaum und kein Debug-Build. Pro Environment entsteht genau ein Binary, das anschliessend nur umbenannt wird.

Das eigentliche Problem Link zu Überschrift

Image fertig, hochladen über die Weboberfläche, und dann:

Bad Size Given

Das ist der Punkt, an dem die Sache interessant wird. Der Chip hat 8 MB, das Environment ist auf 8 MB konfiguriert, das Image ist 1,79 MB gross. Trotzdem passt es nicht.

Vergleich: In der Werks-Partitionstabelle für 4 MB ist die App-Partition 1,5 MB gross, das selbst gebaute Image mit 1,79 MB passt nicht hinein. In der 8-MB-Tabelle ist die Partition 2 MB gross, dort passt es mit 85,6 Prozent Auslastung.

Der Chip hat 8 MB. Die Tabelle, die draufliegt, weiss davon nichts.

Auf dem Chip lag noch die Partitionstabelle vom Werksflash, und die ist für 4 MB ausgelegt: app0 und app1 je 0x180000, also 1,5 MB. Das 1,79-MB-Image ist knapp 290 KB zu gross. Die Fehlermeldung ist also völlig korrekt, sie sagt nur nicht dazu, worauf sie sich bezieht.

Und hier schliesst sich der Kreis: Dieselbe konservative Tabelle, die dafür sorgt, dass 802.1X aus den fertigen Binaries fliegt, verhindert anschliessend, dass man das selbst gebaute Image aufspielen kann.

Merksatz: OTA kann die Partitionstabelle bei Offset 0x8000 grundsätzlich nicht ändern. Nur seriell. Kein Web-Installer und kein Repartition-Hack umgeht das sinnvoll.

Das ist keine Einschränkung der Implementierung, sondern des Verfahrens: Ein Over-the-Air-Update schreibt in die jeweils inaktive Anwendungspartition und schaltet danach um. Es kann per Konstruktion nicht die Struktur ändern, in der es selbst steht.

Die Lösung Link zu Überschrift

Einmal Kabel, dann nie wieder.

~/.venv-pio/bin/esptool.py --port /dev/ttyUSB0 flash_id        # 8MB bestätigt
~/.venv-pio/bin/esptool.py --port /dev/ttyUSB0 erase-flash
~/.venv-pio/bin/pio run -e dig-next-2 -t upload --upload-port /dev/ttyUSB0

Der USB-Wandler meldet sich bei mir als /dev/ttyUSB0; auf anderen Systemen kann es ttyACM0 sein. Das erase-flash ist der entscheidende Schritt — es räumt die alte Tabelle mit weg, sodass der anschliessende Upload die aus dem Environment schreibt.

Danach sieht die Aufteilung so aus:

app0      0x010000  2M
app1      0x210000  2M
spiffs    0x410000  3968K
coredump  0x7f0000  64K

Nachprüfen lässt sich das, ohne zu raten:

~/.venv-pio/bin/esptool.py --port /dev/ttyUSB0 read-flash 0x8000 0xc00 /tmp/part.bin
python3 ~/.platformio/packages/framework-arduinoespressif32/tools/gen_esp32part.py /tmp/part.bin

Auf aktuellen Debian-Systemen ist es python3, nicht python — der ungeprefixte Aufruf existiert dort nicht mehr.

Künftige Updates laufen wieder normal per OTA, solange das Image unter 2 MB bleibt. Das Kabel braucht man genau einmal.

Wenn der Platz doch knapp wird Link zu Überschrift

Falls es je eng werden sollte, lohnt sich ein Blick darauf, was tatsächlich Platz frisst. Meine Erwartung lag daneben.

audioreactive kostet nur rund 46 KB. Das ist der Usermod, den man als erstes verdächtigt, weil er nach viel klingt — er ist es nicht.

Die grossen Posten sind stattdessen IRremoteESP8266, esp_dmx, AnimatedGIF samt GifDecoder, espalexa und QuickEspNow. Wer sparen muss, fängt dort an und nicht beim Mikrofon-Support.

Das Gerät im Netz Link zu Überschrift

Mit dem selbst gebauten Image hängt der Controller jetzt auf derselben SSID wie alles andere, meldet sich per PEAP mit eigenen Zugangsdaten an und bekommt sein VLAN vom RADIUS-Server zugewiesen. Genau das, was in Teil 7 nicht ging.

Ein Rat für die Einrichtung: Das USB-Kabel angeschlossen lassen, bis die Anmeldung nachweislich funktioniert. Ein falsch eingetragener Benutzername bedeutet sonst, dass das Gerät gar nicht mehr ins Netz kommt — und dann steht man wieder mit dem Schraubenzieher da.

Und wofür das Ganze Link zu Überschrift

Der Controller treibt 177 LEDs auf zwei Datenausgängen, und das Licht folgt dem Fernsehbild. Die Bildanalyse macht Hyperion.NG auf dem Mediaplayer, der die Farbwerte an WLED schickt.

Als Protokoll DDP über UDP 4048. Bei 177 LEDs sind das 531 Kanäle. E1.31 bräuchte dafür zwei Universen samt Adressrechnerei, DDP schafft es in einem Paket — und der Gerätetyp „WLED" in Hyperion nutzt ohnehin DDP.

Weil Mediaplayer und Controller bei mir in getrennten Netzen liegen, ist das eine einzelne Firewall-Regel: UDP von einem zum anderen auf Port 4048, sonst nichts. Wie dieses Regelwerk aussieht, steht in Teil 10.

Zwei Dinge, die man dabei leicht übersieht: Die LED-Anzahl muss auf beiden Seiten übereinstimmen — in WLED unter Config → LED Preferences, in Hyperion in der Geräteeinstellung. Und unter Config → Sync Interfaces lohnt ein Blick auf das Realtime-Timeout und darauf, ob die WLED-Helligkeit auf die empfangenen Daten angewendet wird. Beides erklärt Effekte, die man sonst der Netzwerkstrecke zuschreibt.


Gehört thematisch zu meiner Serie über den Umbau meines Heimnetzes, steht aber für sich. Fragen, Korrekturen und eigene Erfahrungen gerne in die Kommentare.