Dein ESP32 sitzt fertig verbaut im Gehäuse, klebt hinter dem Schrank oder hängt draußen am Wetterhäuschen – und dann fällt dir auf, dass du noch eine Kleinigkeit im Code ändern musst. Genau dafür gibt es OTA (Over-the-Air): Du überträgst neue Sketches per WLAN auf deinen ESP32, ohne ihn jemals wieder an ein USB-Kabel zu hängen.
In diesem Tutorial richtest du OTA Schritt für Schritt an einem einfachen Beispiel ein: einer blinkenden LED, deren Blinkfrequenz du drahtlos änderst. Danach schaust du dir an, warum ein einzelnes delay() dir das ganze Update zerschießen kann, wie groß dein Sketch überhaupt werden darf – und was du tust, wenn dein ESP32 in der Arduino IDE partout nicht als Netzwerkport auftaucht.
Wie OTA funktioniert
Ein kurzer Blick unter die Haube, denn daraus ergeben sich fast alle Stolpersteine dieses Tutorials.
Der Flash-Speicher deines ESP32 ist in Partitionen aufgeteilt – und für Programme sind darin gleich zwei Bereiche vorgesehen: app0 und app1. In einem davon läuft dein aktueller Sketch. Kommt ein Update per WLAN herein, schreibt der ESP32 es in den jeweils anderen Bereich. Erst wenn alle Daten vollständig und fehlerfrei angekommen sind, wird der Startzeiger umgelegt und der Chip startet neu – dann aus der neuen Partition.
Das ist beruhigend: Bricht die Übertragung mittendrin ab, passiert nichts. Dein alter Sketch läuft einfach weiter, und du versuchst es noch einmal.
Zwei Konsequenzen solltest du dir aber merken, sie ziehen sich durch das ganze Tutorial:
- OTA steckt in deinem Sketch, nicht im Chip. Der ESP32 hat keinen eingebauten Update-Dienst, der immer lauscht. Es ist dein eigenes Programm, das den OTA-Server startet und offen hält. Lädst du versehentlich einen Sketch ohne OTA-Code hoch oder hängt sich dein Programm auf, ist die drahtlose Tür zu – und du brauchst wieder das USB-Kabel.
- Dein Sketch muss zweimal in den Speicher passen. Weil zwei Programmpartitionen nötig sind, steht dir nur etwa die Hälfte des Flash-Speichers für dein Programm zur Verfügung. Bei großen Projekten wird das später zum Thema – dazu unten mehr.
Das brauchst du
- Ein ESP32-Board (z.B. ein ESP32 DevKit V1)
- Eine LED und einen passenden Vorwiderstand (z.B. 220 Ohm)
- Ein Breadboard und zwei Jumper-Kabel
- Ein USB-Kabel – für den allerersten Upload
- Die Arduino IDE mit installiertem ESP32-Boardpaket
Die LED schließt du über den Vorwiderstand an GPIO 2 an, das andere Bein kommt an GND. Auf vielen ESP32-Boards sitzt an diesem Pin ohnehin schon eine kleine Onboard-LED – dann kannst du dir die Bauteile auch sparen und einfach auf dem Board mitschauen.
Falls dein ESP32 noch nie im WLAN war: Bei uns erfährst du, wie du deinen ESP32 mit dem Internet verbindest.
Die Bibliothek ArduinoOTA
Gute Nachricht: Du musst nichts installieren. Die Bibliothek ArduinoOTA gehört zum ESP32-Boardpaket – wenn du deinen ESP32 schon einmal mit der Arduino IDE programmiert hast, ist sie längst auf deinem Rechner. Ein #include <ArduinoOTA.h> genügt.
Wichtig: Im Bibliotheksverwalter findest du ebenfalls eine Bibliothek namens „ArduinoOTA“ – die ist aber für ganz andere Boards gedacht. Installierst du sie, kann sie die Version aus dem ESP32-Boardpaket verdecken, und dein Sketch lässt sich plötzlich nicht mehr kompilieren. Finger weg, du brauchst sie nicht.
Schritt 1: Der Sketch mit der blinkenden LED
Du startest mit einem Sketch, der die LED im Sekundentakt blinken lässt – und der nebenbei den OTA-Dienst startet. Diesen ersten Sketch lädst du noch ganz klassisch per USB-Kabel hoch. Er ist sozusagen der Türöffner: Erst danach ist dein ESP32 überhaupt drahtlos ansprechbar.
Trage im Code deine WLAN-Zugangsdaten ein und denk dir ein OTA-Passwort aus. Das schützt deinen ESP32 davor, dass jemand anderes in deinem Netzwerk einfach eine eigene Firmware aufspielt.
#include <WiFi.h>
#include <ArduinoOTA.h>
const char* ssid = "DEIN_WLAN_NAME"; // Ersetze durch deinen WLAN-Namen
const char* password = "DEIN_WLAN_PASSWORT"; // Ersetze durch dein WLAN-Passwort
// Das Passwort für OTA-Updates – nicht dein WLAN-Passwort!
const char* otaPasswort = "Mein_OTA_Passwort";
#define LED_PIN 2 // GPIO-Pin der LED
// Damit du nach dem Update erkennst, welche Version wirklich läuft
const char* FIRMWARE_VERSION = "1.0";
unsigned long previousMillis = 0;
unsigned long interval = 1000; // Blinkintervall in Millisekunden
bool ledAn = false;
void setup() {
Serial.begin(115200);
Serial.println();
Serial.print("Firmware-Version: ");
Serial.println(FIRMWARE_VERSION);
pinMode(LED_PIN, OUTPUT);
// WLAN-Verbindung herstellen
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println();
Serial.print("Verbunden. IP: ");
Serial.println(WiFi.localIP());
// OTA einrichten
ArduinoOTA.setHostname("esp32-led-ota"); // Name im Netzwerk
ArduinoOTA.setPassword(otaPasswort); // Schutz vor fremden Uploads
ArduinoOTA.begin();
Serial.println("OTA bereit.");
}
void loop() {
ArduinoOTA.handle(); // Nach OTA-Updates horchen
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
ledAn = !ledAn;
digitalWrite(LED_PIN, ledAn);
}
}
Öffne nach dem Upload den seriellen Monitor bei 115200 Baud. Dort solltest du die Firmware-Version, die IP-Adresse deines ESP32 und die Meldung OTA bereit. sehen. Die LED blinkt jetzt im Sekundentakt.
So funktioniert der Sketch
- Bibliotheken:
WiFi.hbringt deinen ESP32 ins Netzwerk,ArduinoOTA.hstellt den Update-Dienst bereit. Ohne WLAN kein OTA – die beiden gehören zusammen. - Die Versionsnummer:
FIRMWARE_VERSIONhat technisch keine Funktion, ist aber Gold wert. Nach einem Update siehst du im seriellen Monitor sofort, ob wirklich der neue Sketch läuft – oder ob du gerade eine halbe Stunde einen Fehler suchst, der längst behoben ist. - Der Hostname:
ArduinoOTA.setHostname("esp32-led-ota")legt fest, unter welchem Namen dein ESP32 im Netzwerk auftaucht. Gibst du keinen an, meldet er sich mit einem kryptischenesp32-plus MAC-Adresse. Verwende Bindestriche statt Unterstriche – Unterstriche sind in Netzwerknamen nicht vorgesehen und machen bei manchen Systemen Ärger. - Das OTA-Passwort:
ArduinoOTA.setPassword()verhindert, dass jemand anderes in deinem Netzwerk Updates aufspielt. Danach startetArduinoOTA.begin()den Dienst. Ab hier meldet sich dein ESP32 selbstständig im Netzwerk an, damit ihn die Arduino IDE finden kann. - Das Blinken ohne
delay(): Statt die LED mitdelay(1000)blinken zu lassen, merkt sich der Sketch inpreviousMillisden Zeitpunkt des letzten Wechsels und vergleicht ihn mitmillis(). Warum das hier kein Stilfrage ist, sondern Voraussetzung, liest du gleich im nächsten Abschnitt. - Der OTA-Handler:
ArduinoOTA.handle()steht ganz oben inloop()und prüft bei jedem Durchlauf, ob ein Update hereinkommt. Fehlt dieser Aufruf, passiert – nichts.
Schritt 2: Das Update drahtlos übertragen
Jetzt der eigentliche Test. Ändere im Sketch zwei Zeilen – die Blinkfrequenz und die Versionsnummer:
const char* FIRMWARE_VERSION = "1.1";
unsigned long interval = 500; // Blinkintervall in Millisekunden
Lass alles andere unverändert stehen. Das ist der häufigste Anfängerfehler: Der neue Sketch braucht die WLAN-Zugangsdaten und den kompletten OTA-Code genauso wie der alte. Was du hochlädst, ersetzt das alte Programm vollständig – vergisst du den OTA-Teil, war das dein letztes drahtloses Update.
Gehe nun in der Arduino IDE zu Werkzeuge > Port. Dort sollte dein ESP32 unter Netzwerkports als esp32-led-ota auftauchen. Wähle ihn aus.
Und das USB-Kabel? Das darf ruhig stecken bleiben – es stört das Update nicht. Du wechselst ja nur den Port in der IDE, die Übertragung läuft trotzdem durchs WLAN. Fürs erste Ausprobieren ist das sogar praktischer, weil dein ESP32 so mit Strom versorgt bleibt.
Klicke jetzt wie gewohnt auf den Upload-Button. Die Arduino IDE fragt dich nach dem OTA-Passwort. Gib das ein, das du im Sketch bei otaPasswort hinterlegt hast (in unserem Beispiel Mein_OTA_Passwort).
Nach ein paar Sekunden ist die Übertragung durch, dein ESP32 startet neu – und die LED blinkt im Halbsekundentakt. Damit hast du dein erstes drahtloses Update hinter dir.
Willst du auch die neue Firmware-Version schwarz auf weiß sehen, gibt es eine Kleinigkeit zu beachten: Der serielle Monitor der Arduino IDE hängt immer am ausgewählten Port. Solange also der Netzwerkport eingestellt ist, bekommst du keine serielle Ausgabe zu sehen – selbst wenn das USB-Kabel steckt. Stelle den Port danach einfach wieder auf deinen USB-Anschluss zurück, öffne den seriellen Monitor und drücke einmal den Reset-Knopf auf dem Board. Dann meldet sich dein ESP32 mit Firmware-Version: 1.1.
Und wenn du es wirklich wissen willst: Zieh das USB-Kabel ab und versorge deinen ESP32 mit einem Netzteil oder einer Powerbank. Ab jetzt kommst du nur noch per WLAN an ihn heran – genau so, wie er später auch in deinem Projekt sitzen wird.
Hinweis: Arbeitest du mit mehreren Geräten und verschiedenen Passwörtern, merkt sich die Arduino IDE gerne das falsche und bricht mit einem Authentifizierungsfehler ab. Schließe die IDE in diesem Fall und öffne sie erneut – dann wirst du wieder nach dem Passwort gefragt.
Warum delay() dein OTA lahmlegt
Das ist der Punkt, an dem OTA in echten Projekten am häufigsten scheitert – und der Grund, warum die LED oben mit millis() blinkt.
Ein Update wird nur angenommen, während ArduinoOTA.handle() läuft. Der Aufruf steht in loop(), also passiert das nur so oft, wie deine Schleife durchläuft. Hättest du die LED mit delay(1000) blinken lassen, wäre dein ESP32 die meiste Zeit blockiert und würde nur sporadisch nach Updates schauen. Die Arduino IDE bekommt dann keine Verbindung oder bricht mitten in der Übertragung ab.
Die Regel lautet deshalb: Deine loop() muss zügig durchlaufen. Alles, was länger blockiert – lange delay()-Aufrufe, Warteschleifen auf Sensoren, langsame Netzwerkabfragen –, macht OTA unzuverlässig. Ein delay(10) stört nicht, ein delay(5000) schon.
Und ein Sonderfall, der viele Bastler kalt erwischt: Deep Sleep und OTA vertragen sich nicht. Ein schlafender ESP32 führt keinen Code aus und nimmt folglich auch keine Updates an. Wenn dein Projekt schläft, brauchst du ein Zeitfenster nach dem Aufwachen, in dem der Sketch ein paar Sekunden lang ArduinoOTA.handle() aufruft, bevor er sich wieder schlafen legt.
Ein robusteres OTA-Setup
Der Sketch oben funktioniert, aber er ist stumm: Du siehst nicht, warum ein Update fehlschlägt. Dabei liegt bei OTA ja genau darin der Reiz, dass du nicht mehr am Gerät stehst. Die Bibliothek bietet dafür vier Rückmeldungen an, die du vor ArduinoOTA.begin() registrierst:
ArduinoOTA.setHostname("esp32-led-ota");
ArduinoOTA.setPassword(otaPasswort);
ArduinoOTA.onStart([]() {
Serial.println("Update startet ...");
});
ArduinoOTA.onProgress([](unsigned int fortschritt, unsigned int gesamt) {
Serial.printf("Fortschritt: %u%%\r", (fortschritt * 100) / gesamt);
});
ArduinoOTA.onEnd([]() {
Serial.println("\nUpdate fertig - der ESP32 startet neu.");
});
ArduinoOTA.onError([](ota_error_t fehler) {
Serial.printf("Fehler [%u]: ", fehler);
if (fehler == OTA_AUTH_ERROR) Serial.println("Falsches Passwort");
else if (fehler == OTA_BEGIN_ERROR) Serial.println("Kein Platz fuer das Update");
else if (fehler == OTA_CONNECT_ERROR) Serial.println("Verbindung abgebrochen");
else if (fehler == OTA_RECEIVE_ERROR) Serial.println("Daten unvollstaendig empfangen");
else if (fehler == OTA_END_ERROR) Serial.println("Abschluss fehlgeschlagen");
});
ArduinoOTA.begin();
Ersetze damit die drei OTA-Zeilen in deinem setup(). Beim nächsten Update siehst du im seriellen Monitor den Fortschritt in Prozent – und im Fehlerfall endlich einen Klartext statt eines stillen Abbruchs.
Eine zweite Schwachstelle steckt in der WLAN-Verbindung. Die Schleife while (WiFi.status() != WL_CONNECTED) wartet endlos – wenn dein Router gerade neu startet, hängt dein ESP32 dort für immer fest. Bei einem Gerät, an das du bewusst nicht mehr herankommen willst, ist das die schlechteste aller Varianten. Gib der Sache lieber ein Zeitlimit:
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
unsigned long startZeit = millis();
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
// Nach 20 Sekunden ohne Verbindung: einmal komplett neu starten
if (millis() - startZeit > 20000) {
Serial.println("\nKeine WLAN-Verbindung - Neustart.");
ESP.restart();
}
}
Wie groß darf dein Sketch sein?
Erinnerst du dich an die zwei Programmpartitionen vom Anfang? Hier wird es konkret. Mit dem Standard-Partitionsschema stehen deinem Programm 1.310.720 Bytes zur Verfügung – also rund 1,25 MB, egal wie groß der Flash-Speicher deines Boards insgesamt ist. Die Arduino IDE zeigt dir diese Zahl nach jedem Kompilieren an:
„Der Sketch verwendet 812345 Bytes (61%) des Programmspeicherplatzes. Das Maximum sind 1310720 Bytes.“
Solange du unter 100 Prozent bleibst, ist alles gut. Wird dein Projekt größer – etwa mit Bibliotheken für Displays oder einem Webserver mit eingebetteten Dateien –, hast du zwei Möglichkeiten unter Werkzeuge > Partition Scheme:
- Minimal SPIFFS (1.9MB APP with OTA/128KB SPIFFS): Gibt deinem Programm mit 1.966.080 Bytes deutlich mehr Platz, dafür bleiben nur noch 128 KB Dateisystem übrig. Für die meisten größeren Projekte die richtige Wahl.
- Huge APP (3MB No OTA/1MB SPIFFS): Der ganze Speicher für ein einziges Programm – aber wie der Name schon sagt, ohne OTA. Diese Option scheidet hier also aus.
Ein Wechsel des Partitionsschemas lässt sich übrigens nicht per OTA übertragen – dafür muss dein ESP32 einmal wieder ans Kabel. Überlege dir das Schema also am besten, bevor du dein Projekt fest einbaust.
Wenn es nicht funktioniert
Mit Abstand am häufigsten scheitert es daran, dass der Netzwerkport in der Arduino IDE gar nicht erst auftaucht. Diese Liste arbeitest du dann von oben nach unten ab:
- Läuft der Sketch überhaupt? Schau in den seriellen Monitor: Ohne die Meldung
OTA bereit.und eine IP-Adresse hat dein ESP32 keine WLAN-Verbindung – und ohne WLAN kein Netzwerkport. - Gleiches Netzwerk? Rechner und ESP32 müssen im selben Netz hängen. Ein 5-GHz-WLAN nützt dir nichts, wenn dein ESP32 nur 2,4 GHz kann und dein Router beides trennt. Auch ein Gast-WLAN ist ein Ausschlusskriterium.
- IDE neu starten. Die Arduino IDE sucht nicht permanent nach neuen Netzwerkgeräten. Manchmal reicht ein Neustart, damit der Port erscheint.
- Unter Windows: Bonjour installieren. Dein ESP32 macht sich per mDNS im Netzwerk bekannt. macOS und die meisten Linux-Systeme verstehen das ab Werk, Windows nicht immer. Ohne diesen Dienst bleibt die Portliste leer.
- Router blockiert Multicast. Manche Router und vor allem WLAN-Repeater lassen mDNS-Pakete nicht durch. Dann hilft nur, dem Board näher zu kommen – oder du gibst die IP-Adresse deines ESP32 direkt an, statt auf den Netzwerkport zu warten.
Und die übrigen Klassiker:
- „Authentication Failed“: Falsches OTA-Passwort – oder die IDE verwendet noch ein gemerktes von einem anderen Gerät. IDE schließen und neu öffnen.
- Das Update bricht bei 30, 50 oder 80 Prozent ab: Meist schlechter WLAN-Empfang – oder deine
loop()blockiert zu lange (siehe oben). - Nach dem Update ist der ESP32 nicht mehr erreichbar: Dann fehlte im neuen Sketch der OTA-Code oder die WLAN-Zugangsdaten. Jetzt hilft nur noch das USB-Kabel.
Was OTA nicht leistet
Zwei ehrliche Einschränkungen zum Schluss, damit du weißt, worauf du dich einlässt.
OTA ist nicht verschlüsselt. Das Passwort verhindert, dass jemand ungefragt eine Firmware aufspielt – die Übertragung selbst läuft unverschlüsselt durch dein lokales Netz. Für Heimprojekte ist das völlig in Ordnung. Enthält dein Sketch Zugangsdaten, die niemand mitlesen darf, ist ein Update per Kabel die sicherere Wahl.
OTA funktioniert nur im lokalen Netz. Du kannst deinen ESP32 damit nicht aus dem Urlaub heraus aktualisieren. Wer das braucht, lässt den ESP32 selbst regelmäßig bei einem Webserver nachfragen, ob eine neue Firmware bereitliegt – das nennt sich HTTP-OTA und ist ein Thema für sich. Einen Einstieg dazu findest du in der Dokumentation von Espressif.
Fazit
OTA ist mit vier Zeilen Code eingerichtet – die eigentliche Arbeit steckt darin, den Sketch so zu bauen, dass die Tür auch offen bleibt. Merk dir vor allem diese drei Dinge: Der OTA-Dienst lebt in deinem Programm, du musst ihn also in jedem Update wieder mitliefern. Deine loop() darf nicht blockieren, sonst hört niemand zu, wenn das Update anklopft. Und eine Versionsnummer im seriellen Monitor erspart dir mehr Rätselraten, als du jetzt vielleicht denkst.