ESP32 OTA-Updates: Firmware kabellos über WLAN aufspielen (ArduinoOTA & Web-Updater)

Dein ESP32 hängt fertig verbaut im Gehäuse unter der Decke, im Garten oder hinter dem Aquarium – und genau jetzt fällt dir ein Tippfehler im Code auf. Leiter holen, Gehäuse öffnen, USB-Kabel anstöpseln? Muss nicht sein. Mit OTA-Updates (Over-the-Air) flashst du neue Firmware bequem übers WLAN – direkt aus der Arduino IDE oder sogar per Browser-Upload. In diesem Tutorial richtest du ArduinoOTA Schritt für Schritt ein, integrierst es sauber in deine bestehenden Sketche, baust einen Web-Updater für Firmware-Uploads per Browser und sicherst alles mit einem Passwort ab.

Wie funktioniert ein OTA-Update?

Der Flash-Speicher des ESP32 ist in Partitionen aufgeteilt – ähnlich wie eine Festplatte. Beim Standard-Schema gibt es zwei gleich große App-Partitionen: In der einen läuft dein aktuelles Programm, die andere ist frei. Ein OTA-Update schreibt die neue Firmware in die freie Partition, prüft sie und markiert sie als Boot-Partition. Beim nächsten Neustart startet der ESP32 aus der neuen Partition – schlägt irgendetwas fehl, bleibt die alte Firmware unangetastet.

Das Schöne daran: Dieses Sicherheitsnetz ist eingebaut. Ein abgebrochener Upload (WLAN weg, Strom weg) macht dein Board nicht kaputt – es läuft einfach mit der alten Version weiter. Voraussetzung ist nur, dass die laufende Firmware den OTA-Code enthält. Und das führt zur wichtigsten Regel dieses Tutorials:

⚠️ Die eiserne OTA-Regel: Das erste Update kommt immer noch per USB-Kabel – nämlich der Sketch, der OTA überhaupt erst aktiviert. Und: Jeder später per OTA hochgeladene Sketch muss den OTA-Code wieder enthalten. Vergisst du ihn einmal, ist die Funkstrecke tot und du musst doch wieder mit dem Kabel ran.

Das brauchst du

ESP32 Entwicklungsboard (USB-C) – unser Hauptdarsteller mit WLAN an Bord
ESP32-C3 Mini (Alternative) – OTA funktioniert auf allen ESP32-Varianten identisch
Wemos D1 Mini (ESP8266) – auch der kleine Bruder beherrscht ArduinoOTA
ESP32 Acryl-Gehäuse – fest verbaut und trotzdem jederzeit aktualisierbar
USB-Kabel & 2,4-GHz-WLAN – einmalig fürs Erst-Flashen bzw. dauerhaft fürs Funken

Falls dein ESP32 noch nie am WLAN war: Die Grundlagen von WLAN-Verbindung und Webserver erklärt der Artikel ESP32 Webserver einrichten.

1ArduinoOTA aktivieren: der Basis-Sketch

Die Bibliothek ArduinoOTA ist im ESP32-Boardpaket schon enthalten – nichts zu installieren. Dieser Sketch verbindet sich mit dem WLAN und lauscht auf Update-Anfragen aus der IDE. Lade ihn einmal klassisch per USB hoch:

#include <WiFi.h>
#include <ArduinoOTA.h>

const char* WLAN_NAME     = "DeinWLAN";
const char* WLAN_PASSWORT = "DeinPasswort";

void setup() {
  Serial.begin(115200);

  WiFi.mode(WIFI_STA);
  WiFi.begin(WLAN_NAME, WLAN_PASSWORT);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.printf("\nVerbunden! IP: %s\n", WiFi.localIP().toString().c_str());

  ArduinoOTA.setHostname("esp32-wohnzimmer");   // Name im Netzwerk-Menue

  ArduinoOTA
    .onStart([]() { Serial.println("OTA-Update startet ..."); })
    .onProgress([](unsigned int done, unsigned int total) {
      Serial.printf("Fortschritt: %u %%\r", (done * 100) / total);
    })
    .onEnd([]() { Serial.println("\nFertig! Neustart ..."); })
    .onError([](ota_error_t fehler) {
      Serial.printf("Fehler [%u]\n", fehler);
    });

  ArduinoOTA.begin();
  Serial.println("Bereit fuer OTA-Updates.");
}

void loop() {
  ArduinoOTA.handle();   // MUSS regelmaessig aufgerufen werden
}

Nach dem Upload und Neustart erscheint das Board in der Arduino IDE als Netzwerkgerät. So flashst du ab jetzt kabellos:

  1. Arduino IDE (neu) öffnen: Werkzeuge → Port – unter \"Netzwerk-Ports\" taucht esp32-wohnzimmer at 192.168.x.x auf
  2. Diesen Netzwerk-Port auswählen (statt des seriellen COM-Ports)
  3. Ganz normal auf Hochladen klicken – der Sketch wandert übers WLAN aufs Board
  4. Der ESP32 startet automatisch mit der neuen Firmware neu

ℹ️ PC und ESP32 müssen im selben Netz sein: Die IDE findet das Board per mDNS-Broadcast. Gäste-WLANs, VPNs oder strenge Firewalls blockieren das oft – der Troubleshooting-Abschnitt unten hilft dann weiter.

2OTA in dein echtes Projekt integrieren

Der Basis-Sketch tut nichts außer warten. In der Praxis soll OTA neben deiner eigentlichen Anwendung laufen. Dafür gibt es nur zwei Anforderungen: ArduinoOTA.handle() muss regelmäßig drankommen, und dein Code darf den loop() nicht mit langen delay()-Aufrufen blockieren. Hier das Muster mit einer nicht-blockierenden Blink-LED als Platzhalter für deine Anwendung:

#include <WiFi.h>
#include <ArduinoOTA.h>

const char* WLAN_NAME     = "DeinWLAN";
const char* WLAN_PASSWORT = "DeinPasswort";

const int LED = 2;                    // Onboard-LED
unsigned long letzterWechsel = 0;
bool ledAn = false;

void otaEinrichten() {
  ArduinoOTA.setHostname("esp32-projekt");
  ArduinoOTA.begin();
}

void setup() {
  Serial.begin(115200);
  pinMode(LED, OUTPUT);

  WiFi.mode(WIFI_STA);
  WiFi.begin(WLAN_NAME, WLAN_PASSWORT);
  while (WiFi.status() != WL_CONNECTED) delay(500);

  otaEinrichten();
  Serial.printf("Laeuft! IP: %s\n", WiFi.localIP().toString().c_str());
}

void loop() {
  ArduinoOTA.handle();                // 1. immer zuerst

  // 2. deine Anwendung - nicht-blockierend mit millis()
  if (millis() - letzterWechsel >= 1000) {
    letzterWechsel = millis();
    ledAn = !ledAn;
    digitalWrite(LED, ledAn);
  }
}

💡 Versionsnummer mitgeben: Gib beim Start eine Versionsnummer aus (Serial.println("Firmware v1.4");) oder zeige sie auf einem Display an. Bei kabellosen Updates verliert man sonst schnell den Überblick, welche Version gerade auf welchem Board läuft – spätestens beim dritten ESP32 im Haus.

⚠️ OTA und Deep Sleep vertragen sich schlecht: Ein schlafender ESP32 kann keine Updates empfangen. Üblicher Kompromiss: Das Board bleibt nach dem Aufwachen ein kurzes Zeitfenster (z. B. 30 Sekunden) wach und lauscht auf OTA, bevor es wieder schlafen geht – oder du aktivierst den Wartungsmodus über einen Taster.

3Web-Updater: Firmware per Browser hochladen

ArduinoOTA setzt voraus, dass auf dem Rechner die Arduino IDE läuft. Es geht auch ohne: Der ESP32 kann selbst eine Update-Webseite ausliefern, auf der du eine kompilierte .bin-Datei hochlädst – praktisch, wenn du Firmware an Freunde weitergibst, die keine IDE installiert haben. Dafür exportierst du in der IDE mit Sketch → Kompilierte Binärdatei exportieren die .bin-Datei.

#include <WiFi.h>
#include <WebServer.h>
#include <Update.h>

const char* WLAN_NAME     = "DeinWLAN";
const char* WLAN_PASSWORT = "DeinPasswort";

WebServer server(80);

const char* SEITE =
  "<h1>ESP32 Web-Updater</h1>"
  "<form method='POST' action='/update' enctype='multipart/form-data'>"
  "<input type='file' name='firmware'>"
  "<input type='submit' value='Update starten'></form>";

void setup() {
  Serial.begin(115200);
  WiFi.mode(WIFI_STA);
  WiFi.begin(WLAN_NAME, WLAN_PASSWORT);
  while (WiFi.status() != WL_CONNECTED) delay(500);
  Serial.printf("Web-Updater: http://%s\n", WiFi.localIP().toString().c_str());

  server.on("/", HTTP_GET, []() { server.send(200, "text/html", SEITE); });

  server.on("/update", HTTP_POST,
    []() {   // Antwort nach dem Upload
      server.send(200, "text/plain", Update.hasError() ? "FEHLER" : "OK - Neustart");
      delay(500);
      ESP.restart();
    },
    []() {   // Upload-Datenstrom verarbeiten
      HTTPUpload& up = server.upload();
      if (up.status == UPLOAD_FILE_START) {
        Update.begin();                          // freie Partition oeffnen
      } else if (up.status == UPLOAD_FILE_WRITE) {
        Update.write(up.buf, up.currentSize);    // Firmware-Haeppchen schreiben
      } else if (up.status == UPLOAD_FILE_END) {
        Update.end(true);                        // pruefen und aktivieren
      }
    });

  server.begin();
}

void loop() {
  server.handleClient();
}

Rufe die IP des Boards im Browser auf, wähle die .bin-Datei und klicke auf \"Update starten\" – nach wenigen Sekunden bootet der ESP32 mit der neuen Firmware. Die Weboberfläche kannst du beliebig ausbauen, zum Beispiel mit Statusanzeigen deiner Sensoren auf derselben Seite.

4Absichern: Passwortschutz für OTA

Ohne Schutz kann jeder im selben WLAN deinem ESP32 neue Firmware unterschieben. Für ArduinoOTA genügt eine Zeile vor ArduinoOTA.begin():

ArduinoOTA.setPassword("geheim123");

// besser: nur den MD5-Hash im Code speichern
// (Hash von "geheim123" vorher z. B. per Online-Tool erzeugen)
// ArduinoOTA.setPasswordHash("5715790a892990382d98858c4aa38d0e");

Die IDE fragt das Passwort dann beim ersten kabellosen Upload ab. Beim Web-Updater schützt du die Seite mit HTTP-Basic-Auth:

server.on("/", HTTP_GET, []() {
  if (!server.authenticate("admin", "geheim123")) {
    return server.requestAuthentication();
  }
  server.send(200, "text/html", SEITE);
});

ℹ️ Realistische Einordnung: Passwortschutz hält neugierige Mitbewohner und versehentliche Uploads aufs falsche Board ab – für kritische Anwendungen gehört das Board zusätzlich in ein eigenes IoT-WLAN. Fürs Heimnetz ist die Kombination aus Passwort und eigenem Netz ein solider Standard.

Wofür sich OTA besonders lohnt

📡 Typische OTA-Kandidaten

  • Fest verbaute Projekte: Wetterstation im Gehäuse, Sensorknoten im Garten, LED-Steuerung hinterm Schrank
  • Smart-Home-Knoten: MQTT-Sensoren bekommen neue Features, ohne dass du sie abmontierst – siehe MQTT mit ESP32 & Home Assistant
  • Mehrere Boards gleichzeitig pflegen: Jedes Board bekommt per setHostname() einen sprechenden Namen und ist in der IDE sofort zuzuordnen
  • ESP8266-Projekte: Der Code funktioniert fast identisch auf dem Wemos D1 Mini – nur WiFi.h durch ESP8266WiFi.h ersetzen

Perfekt für: alles, was du nach dem Einbau nie wieder anfassen willst

Troubleshooting

⚠️ Netzwerk-Port taucht in der IDE nicht auf: mDNS-Problem. Prüfe, ob PC und ESP32 im selben WLAN hängen (nicht im Gästenetz!), deaktiviere testweise VPN und Firewall und starte die IDE neu. Manchmal hilft es, dem Board 1–2 Minuten Zeit zu geben.

⚠️ Upload bricht mitten drin ab: Meist schwaches WLAN. Näher an den Router oder externe Antenne verwenden. Der Abbruch ist dank Zwei-Partitionen-Prinzip ungefährlich – die alte Firmware läuft weiter.

⚠️ \"Sketch too big\" beim Kompilieren: OTA braucht zwei App-Partitionen, dein Sketch muss in eine davon passen. Wähle unter Werkzeuge → Partition Scheme z. B. \"Minimal SPIFFS (large APPS with OTA)\".

⚠️ Board reagiert nach OTA-Update nicht mehr auf OTA: Der neue Sketch enthält kein ArduinoOTA.handle() mehr – die häufigste OTA-Falle überhaupt. Jetzt hilft nur noch das USB-Kabel; danach den OTA-Block wieder fest in deine Sketch-Vorlage übernehmen.

⚠️ Update läuft, aber alte Version startet: Beim Web-Updater fehlt vermutlich Update.end(true) oder die .bin-Datei ist für ein anderes Board kompiliert. Boardtyp in der IDE prüfen und neu exportieren.

🚀 Boards mit OTA-Superkraft

Alle diese Boards aus dem Shop lassen sich kabellos aktualisieren:

Fazit

OTA-Updates gehören zu den Features, die man nie wieder hergibt, wenn man sie einmal genutzt hat. Du hast gelernt, wie das Zwei-Partitionen-Prinzip abgebrochene Updates ungefährlich macht, hast ArduinoOTA in der Arduino IDE eingerichtet, den OTA-Code sauber in eine nicht-blockierende Anwendung integriert, einen Web-Updater für Browser-Uploads gebaut und alles mit einem Passwort abgesichert.

Die drei Merksätze zum Schluss: Das erste Flashen läuft immer über USB. Jeder OTA-fähige Sketch muss den OTA-Code wieder enthalten – mach ihn zum festen Bestandteil deiner Vorlage. Und ArduinoOTA.handle() braucht einen flüssigen loop() ohne lange delays. Damit kannst du deine Projekte endlich dort einbauen, wo sie hingehören – und nicht dort, wo das USB-Kabel hinreicht. Viel Spaß beim Bauen!

Zurück zum Blog