ESP8266 State Machines sind eine der effektivsten Methoden, um auf dem WLAN-Mikrocontroller sauberen, stabilen und gut wartbaren Code zu schreiben – und dabei konsequent auf blockierende delay()-Aufrufe zu verzichten. Wer mit dem ESP8266 Sensoren ausliest, Relais schaltet, MQTT nutzt oder ein Webinterface bereitstellt, merkt schnell: Sobald das Programm an irgendeiner Stelle „wartet“, leidet die Reaktionsfähigkeit. WLAN-Verbindungen können instabil werden, ein Webserver reagiert verzögert, Messintervalle driften, und bei mehreren Aufgaben gleichzeitig wird Debugging zur Geduldsprobe. State Machines (Zustandsautomaten) lösen dieses Problem, indem sie Abläufe in klar definierte Zustände und Übergänge aufteilen. Statt „warte 5 Sekunden“ heißt es dann: „Wenn 5 Sekunden vergangen sind, wechsle in den nächsten Zustand.“ Dadurch bleibt die Hauptschleife schnell, Ereignisse werden zuverlässig verarbeitet, und Ihr Projekt wirkt plötzlich „professionell“ – auch ohne Betriebssystem. In diesem Artikel erfahren Sie, wie Zustandsautomaten funktionieren, wie Sie sie in typischen ESP8266-Projekten einsetzen, welche Muster sich bewährt haben und wie Sie ganz konkret Delay-Fallen vermeiden, ohne dass Ihr Code unübersichtlich wird.
Warum delay() beim ESP8266 so oft Probleme macht
delay() ist bequem, weil es den Ablauf „linear“ erscheinen lässt. Doch der ESP8266 ist kein reiner „Sensorcontroller“, sondern betreibt parallel einen WLAN-Stack und häufig Netzwerkdienste. Blockierendes Warten kann dazu führen, dass zeitkritische Hintergrundaufgaben zu selten bedient werden. Das Ergebnis sind Verbindungsabbrüche, Watchdog-Resets oder merkwürdige Timing-Effekte, die im Labor nicht auftreten, aber im Dauerbetrieb schon.
- Unresponsive Netzwerk: Webserver, OTA oder MQTT reagieren träge oder verlieren Verbindungen.
- Timing driftet: Messintervalle und Schaltvorgänge werden ungenau, wenn Warten und Arbeit sich stapeln.
- Schlechte Skalierbarkeit: Zwei Delays in zwei Funktionen „multiplizieren“ das Problem, statt es zu addieren.
- Schwieriges Debugging: Fehler wirken zufällig, weil sie vom Timing abhängen.
Grundsätzlich ist nicht jede Verzögerung sofort „verboten“, aber wer mehrere Aufgaben gleichzeitig betreiben will (WLAN + Sensoren + Aktoren + Protokolle), kommt mit blockierendem Warten sehr schnell an Grenzen.
Was ist eine State Machine und was macht sie „embedded-tauglich“?
Eine State Machine (Zustandsmaschine) beschreibt ein System als Menge von Zuständen und Übergängen. Ein Zustand steht für eine klar definierte Phase, in der bestimmte Aktionen erlaubt sind. Ein Übergang wird ausgelöst, wenn eine Bedingung erfüllt ist – zum Beispiel ein Timer abgelaufen ist, ein Sensorwert einen Grenzwert überschreitet oder eine Netzwerkantwort eingetroffen ist. Dieses Denken passt ideal zu Mikrocontrollern, weil es nicht voraussetzt, dass Sie „schlafen“ oder blockieren müssen. Stattdessen prüfen Sie in kurzen, schnellen Schritten, was als Nächstes zu tun ist.
- Zustand: „WLAN verbinden“, „Sensor lesen“, „Daten senden“, „Warten auf Antwort“.
- Ereignis: „Zeit abgelaufen“, „Button gedrückt“, „MQTT Nachricht empfangen“.
- Übergang: Wechsel in den nächsten Zustand, sobald das Ereignis eintritt.
- Aktion: Arbeit, die beim Eintritt/Verlassen oder während eines Zustands ausgeführt wird.
Für ein formales Verständnis lohnt sich ein Blick auf die Grundlagen von endlichen Automaten: Endlicher Automat.
Der wichtigste Baustein: Nicht blockierendes Timing mit millis()
Der klassische Ersatz für delay() ist ein Zeitvergleich mit einem Zeitstempel. Sie speichern, wann eine Aktion gestartet wurde, und prüfen in jeder Loop, ob die gewünschte Zeitspanne vergangen ist. Dadurch bleibt die Loop frei für andere Aufgaben. Auf Arduino-kompatiblen Plattformen wird dafür typischerweise millis() verwendet. Die offizielle Referenz ist hier nützlich: Arduino millis() Referenz.
Einfaches Zeitkriterium als MathML
Ein nicht blockierender Timer lässt sich als Bedingung ausdrücken: Wenn die aktuelle Zeit
In der Praxis bedeutet das: Sie setzen beim Eintritt in einen Zustand
Typische ESP8266-Projekte, die sofort von State Machines profitieren
State Machines sind kein „akademisches“ Konzept, sondern ein sehr praktisches Werkzeug. Besonders stark ist der Effekt, wenn Ihr Projekt mehrere Dinge gleichzeitig tun muss.
- Sensorik + MQTT: Sensoren zyklisch lesen, dabei WLAN stabil halten und MQTT-Verbindungen überwachen.
- Relaissteuerung + Webinterface: Schalten ohne spürbare Verzögerungen im Browser.
- OTA + Laufzeitlogik: Updates ermöglichen, ohne dass die Hauptlogik „stehen bleibt“.
- Mehrstufige Prozesse: z. B. Kalibrierung, Mehrfachmessung, Mittelwertbildung, Senden, Schlafen.
Für ESP8266-spezifische Hinweise zum Arduino-Core und zu Netzwerkfunktionen ist die Dokumentation des Projekts eine solide Quelle: ESP8266 Arduino Core Dokumentation.
State Machine Design: Zustände so schneiden, dass sie klein bleiben
Ein häufiger Fehler ist, Zustände zu groß zu definieren. Ein Zustand sollte in der Regel eine kleine, klar abgegrenzte Aufgabe erfüllen. Wenn ein Zustand viele Sekunden „arbeitet“, wird er indirekt wieder blockierend – nur eben ohne delay(). Stattdessen sollten Zustände schnell sein und lange Operationen in „Starten“ und „Warten“ aufteilen.
- Gut: „HTTP-Request starten“ und danach „auf Antwort warten“ als getrennte Zustände.
- Schlecht: „HTTP-Request ausführen und komplett abwarten“ in einem Zustand, der lange läuft.
- Gut: „Relais einschalten“, „Impuls laufen lassen“, „Relais ausschalten“ als Sequenz.
- Schlecht: „Relais einschalten, 500 ms delay, ausschalten“.
Das Entry/Do/Exit-Muster: Ordnung ohne Overengineering
Damit Zustände übersichtlich bleiben, hat sich ein einfaches Strukturmuster bewährt: Beim Eintritt in einen Zustand führen Sie Initialisierung aus (Entry), während des Zustands wiederholen Sie kurze Checks und ggf. kleine Aktionen (Do), und beim Verlassen räumen Sie auf oder setzen Flags (Exit). Diese Denkweise ist besonders nützlich, wenn Zustände Timer setzen oder externe Ressourcen (z. B. eine Netzwerkverbindung) verwalten.
- Entry: Zeitstempel setzen, Variablen zurücksetzen, Operation starten.
- Do: prüfen, ob Bedingung erfüllt ist; minimaler, schneller Loop.
- Exit: Status speichern, nächsten Zustand wählen, ggf. Logging.
Ereignisgesteuert statt „Polling-Wüste“: Events definieren
State Machines werden besonders elegant, wenn Sie Ereignisse klar benennen. Ein Event kann ein Zeitablauf sein, ein Sensortrigger, ein eingehendes Netzwerkpaket oder ein Benutzerbefehl. Der ESP8266 arbeitet zwar oft in einer Loop, aber das Prinzip bleibt: Sie müssen nicht ständig alles „fragen“, sondern reagieren auf Ereignisse, die Sie selbst ableiten.
- Zeit-Event: „Intervall abgelaufen“ oder „Timeout erreicht“.
- Input-Event: Taster gedrückt, Reedkontakt gewechselt, Interrupt gesetzt.
- Netzwerk-Event: MQTT-Nachricht angekommen, WLAN getrennt, Antwort empfangen.
- System-Event: Fehlerflag, Neustartanforderung, Konfigurationsänderung.
Timeouts als Pflicht: Netzwerkzustände ohne Hänger
Wenn Sie mit WLAN, HTTP oder MQTT arbeiten, ist ein Timeout nicht optional. In blockierendem Code bleibt man oft „einfach hängen“, wenn eine Antwort ausbleibt. In einer State Machine definieren Sie für jeden Wartezustand eine maximale Dauer. Wird sie überschritten, gehen Sie in einen Fehlerzustand oder versuchen einen Reconnect. Das macht Ihr Projekt robust, auch wenn der Router neu startet oder der Broker kurz nicht erreichbar ist.
- WLAN verbinden: nach X Sekunden abbrechen und später erneut versuchen.
- MQTT connect: nach X Sekunden neu ansetzen, ggf. Backoff.
- HTTP warten: nach X Sekunden Request verwerfen und in einen sicheren Zustand wechseln.
- Sensorbereitstellung: falls Sensor nicht antwortet, Fehler zählen und degradierten Betrieb erlauben.
Backoff und Retry: Stabilität im Alltag statt aggressiver Endlosschleifen
Ein weiteres typisches Problem ist „Reconnect-Spam“: Wenn WLAN oder MQTT ausfällt, versucht der ESP8266 in einer engen Schleife zu reconnecten. Das ist ineffizient und kann den Router zusätzlich belasten. Besser ist ein kontrollierter Retry mit wachsendem Abstand (Backoff). Auch das lässt sich sehr sauber als Zustandsautomat abbilden: Zustand „Reconnect versuchen“, Zustand „Warten vor nächstem Versuch“.
- Konstantes Intervall: z. B. alle 5 Sekunden ein Versuch – einfach, aber nicht optimal bei längeren Ausfällen.
- Exponentielles Backoff: Abstand wächst nach jedem Fehlschlag, bis zu einem Maximalwert.
- Reset bei Erfolg: Nach erfolgreicher Verbindung wieder auf Minimalabstand zurücksetzen.
- Fehlertelemetrie: Anzahl der Fehlversuche als Diagnosewert publizieren.
Mehrere Aufgaben gleichzeitig: State Machines als „Soft-Multitasking“
Viele ESP8266-Projekte bestehen aus mehreren „kleinen“ Abläufen: Sensor lesen, Daten senden, LED blinken, Eingaben prüfen. Anstatt alles in eine einzige riesige State Machine zu pressen, hat sich oft ein Ansatz mit mehreren kleinen State Machines bewährt, die jeweils ihren eigenen Zustand und Timer verwalten. Das ist in der Praxis sehr wartbar: Jede Teilmaschine ist verantwortlich für eine Funktion.
- Sensor-State Machine: Messung, Mittelwert, Bereitstellung.
- Netzwerk-State Machine: WLAN, MQTT, Reconnect, Backoff.
- UI-State Machine: Button-Handling, LED-Feedback, Webrequests.
- Aktoren-State Machine: Relaisimpulse, Sicherheitsabschaltung, Sperrzeiten.
Wichtig ist, dass jede Maschine schnell bleibt und keine langen Operationen in der Loop „festhält“.
Debouncing ohne delay(): Taster und Kontakte sauber auswerten
Mechanische Taster und Reedkontakte prellen. Die klassische „quick and dirty“-Lösung ist ein kurzes delay() nach dem Erkennen einer Flanke. In einer State Machine arbeiten Sie stattdessen mit einem Zustandswechsel und einem Zeitfenster: Nach dem ersten Signal wechseln Sie in einen „Stabilisieren“-Zustand und akzeptieren erst nach Ablauf einer kurzen Zeit den finalen Zustand. Das ist zuverlässig und blockiert nichts.
- Edge erkannt: Übergang in „Stabilisieren“ und Zeitstempel setzen.
- Nach Debounce-Zeit: Eingang erneut prüfen.
- Nur stabile Änderung: Event auslösen (z. B. „Button pressed“).
- Optional: Long-Press und Double-Click als zusätzliche Zustände.
Saubere Struktur im Code: Lesbarkeit trotz Logik
State Machines sind dann ein Gewinn, wenn der Code klarer wird – nicht wenn er zu einem undurchsichtigen Switch-Monster mutiert. Die Lesbarkeit steigt, wenn Sie Zustände sprechend benennen, Zustandswechsel zentralisieren und Nebenwirkungen minimieren. Ein guter Stil ist, die Transition-Logik von der eigentlichen „Arbeit“ zu trennen: Der Zustand entscheidet, was als Nächstes passiert; die Aktionen sind kurze, testbare Schritte.
- Sprechende Zustandsnamen: „WLAN_CONNECT“, „MQTT_WAIT“, „SENSOR_READ“ statt „STATE_3“.
- Zentrale Transition-Funktion: ein klarer Ort, an dem Zustände wechseln.
- Minimale globale Variablen: pro State Machine eigene Struktur/Container.
- Klare Fehlerzustände: „ERROR_SENSOR“, „ERROR_NET“ statt verstreuter if-Abbrüche.
Finite State Machine vs. Hierarchical State Machine: Wann lohnt mehr Struktur?
Für die meisten ESP8266-Projekte reicht eine einfache endliche Zustandsmaschine. Komplexer wird es, wenn Sie Zustände haben, die viele Unterzustände teilen (z. B. ein globales „Offline“-Verhalten). Dann kann eine hierarchische Zustandsmaschine helfen, weil gemeinsame Logik nicht kopiert werden muss. In Maker-Projekten ist das aber eher die Ausnahme. Oft reicht ein „Superzustand“ als Konzept: Sie prüfen global, ob WLAN verfügbar ist, und lassen Unterzustände nur laufen, wenn diese Voraussetzung erfüllt ist.
- Einfach (FSM): wenige Zustände, klare Sequenzen, sehr gut für Einsteiger.
- Hierarchisch (HSM): gut bei vielen gemeinsamen Pfaden, aber anspruchsvoller.
- Pragmatisch: globale Vorbedingungen (z. B. „Netz ok?“) als Gate vor Teilabläufen.
Typische Delay()-Fallen und ihre State-Machine-Alternative
Viele Delay()-Stellen lassen sich mit wenigen, wiederkehrenden Mustern ersetzen. Wenn Sie diese Muster einmal verinnerlichen, schreiben Sie automatisch nicht blockierenden Code.
- LED blinken: statt delay() wechseln Sie zwischen „LED an“ und „LED aus“ über Timer.
- Relaisimpuls: „Relais an“ (Entry), „warte X ms“ (Do + Timer), „Relais aus“ (Exit).
- Sensor-Warmup: „Start“, „Warmup läuft“, „Messung“, jeweils über Zeitbedingungen.
- API-Request: „Request starten“, „auf Antwort warten“ mit Timeout, „verarbeiten“.
- MQTT-Reconnect: „versuchen“, „Backoff warten“, „erneut versuchen“.
Messintervalle stabil halten: Scheduler-Denken ohne Betriebssystem
Viele Projekte wollen „alle 10 Sekunden messen“ oder „jede Minute senden“. Mit delay() rutscht das Timing, sobald andere Aufgaben länger dauern. In einer State Machine oder einem kleinen Scheduler speichern Sie den nächsten Fälligkeitszeitpunkt und prüfen in jeder Loop, ob die Zeit erreicht ist. Das ist präziser und bleibt stabil, selbst wenn die Loop gelegentlich etwas mehr Arbeit hat.
- Fälligkeitszeitpunkt: statt „warte 10 Sekunden“ speichern Sie „nächster Termin = jetzt + 10 Sekunden“.
- Überlauf robust: Zeitvergleiche sollten so gestaltet sein, dass sie auch bei millis()-Überlauf funktionieren.
- Jitter minimieren: kurze Loop, keine langen Blöcke, kurze Aktionen pro Tick.
Testbarkeit und Debugging: Zustände machen Fehler sichtbar
Ein unterschätzter Vorteil von State Machines ist, dass sie Debugging vereinfachen. Wenn Sie jederzeit wissen, in welchem Zustand das Gerät ist, können Sie Probleme schneller eingrenzen. Statt „es hängt manchmal“ haben Sie konkrete Aussagen wie „es bleibt in MQTT_WAIT hängen“ oder „der Timeout in HTTP_WAIT läuft aus“. Das hilft auch beim Logging: Sie loggen Zustandswechsel statt jede Iteration.
- Zustandswechsel loggen: weniger Log-Spam, mehr Signal.
- Fehlerzähler: Anzahl Timeouts, Reconnects, Sensorfehler als Diagnosewerte.
- Status über MQTT: Zustände publizieren, um remote zu sehen, was passiert.
- Watchdog-freundlich: kurze Loops reduzieren Risiko von Resets.
Outbound-Links zu relevanten Informationsquellen
- Arduino Referenz: millis() für nicht blockierendes Timing
- ESP8266 Arduino Core Dokumentation (Netzwerk, OTA, Plattformdetails)
- Grundlagen: Endlicher Automat (Zustandsautomaten verständlich erklärt)
- RFC Editor (Netzwerkstandards für IoT-Protokolle und HTTP-Grundlagen)
IoT-PCB-Design, Mikrocontroller-Programmierung & Firmware-Entwicklung
PCB Design • Arduino • Embedded Systems • Firmware
Ich biete professionelle Entwicklung von IoT-Hardware, einschließlich PCB-Design, Arduino- und Mikrocontroller-Programmierung sowie Firmware-Entwicklung. Die Lösungen werden zuverlässig, effizient und anwendungsorientiert umgesetzt – von der Konzeptphase bis zum funktionsfähigen Prototyp.
Diese Dienstleistung richtet sich an Unternehmen, Start-ups, Entwickler und Produktteams, die maßgeschneiderte Embedded- und IoT-Lösungen benötigen. Finden Sie mich auf Fiverr.
Leistungsumfang:
-
IoT-PCB-Design & Schaltplanerstellung
-
Leiterplattenlayout (mehrlagig, produktionstauglich)
-
Arduino- & Mikrocontroller-Programmierung (z. B. ESP32, STM32, ATmega)
-
Firmware-Entwicklung für Embedded Systems
-
Sensor- & Aktor-Integration
-
Kommunikation: Wi-Fi, Bluetooth, MQTT, I²C, SPI, UART
-
Optimierung für Leistung, Stabilität & Energieeffizienz
Lieferumfang:
-
Schaltpläne & PCB-Layouts
-
Gerber- & Produktionsdaten
-
Quellcode & Firmware
-
Dokumentation & Support zur Integration
Arbeitsweise:Strukturiert • Zuverlässig • Hardware-nah • Produktorientiert
CTA:
Planen Sie ein IoT- oder Embedded-System-Projekt?
Kontaktieren Sie mich gerne für eine technische Abstimmung oder ein unverbindliches Angebot. Finden Sie mich auf Fiverr.

