ESP32 Async Webserver mit WebSocket: Steuerung in Echtzeit
ESP32 Async Webserver mit WebSocket: Relais schalten, Sensorwerte live ohne Neuladen sehen. Bibliotheken, PROGMEM oder LittleFS, esp32.local, Passwort, Fehler.
Ein ESP32 Async Webserver, also die Bibliothek ESPAsyncWebServer, macht aus deinem ESP32 einen schnellen Webserver, der mehreren Handys und Computern gleichzeitig antworten kann. Mit der WebSocket-Unterstützung, die in der Bibliothek steckt, entsteht zwischen Browser und Board eine dauerhaft offene Leitung: Drückst du auf der Seite den Knopf, zieht das Relais sofort an, und ändert sich die Temperatur, aktualisiert sich die Zahl auf der Seite von selbst, ohne dass du neu laden musst. Jedes andere Gerät, auf dem dasselbe Bedienfeld offen ist, sieht deine Änderung in derselben Sekunde. Kurz gesagt ist dieses Duo der praktischste Weg, dein eigenes Smart-Home-Panel ohne Cloud-Dienst direkt in deinem Heimnetz aufzubauen.
In dieser Anleitung siehst du zuerst, worin sich ein asynchroner Server von der klassischen WebServer-Bibliothek unterscheidet und warum WebSocket schneller ist als normale HTTP-Anfragen. Danach installierst du die richtigen Bibliotheken, startest den einfachsten möglichen Server und baust mit einem Relais und einem DHT22-Sensor ein Bedienfeld in Echtzeit. Anschließend erfährst du, wie du das HTML in PROGMEM oder LittleFS ablegst, statt der IP-Adresse esp32.local nutzt, mehrere Clients verwaltest, das Panel mit einem einfachen Passwort schützt und die häufigsten Compiler- und Verbindungsfehler behebst. Der Code ist so geschrieben, dass er mit den Versionen 2.x und 3.x des ESP32-Cores in der Arduino IDE läuft.

Was ist der ESP32 Async Webserver und wie funktioniert er?
Die Bibliothek WebServer.h, die mit dem ESP32-Core kommt, arbeitet synchron: Eingehende Anfragen werden erst dann, nacheinander und einzeln, bearbeitet, wenn in loop() server.handleClient() aufgerufen wird. Steht in loop() ein delay() von 2 Sekunden, wartet auch die Seite 2 Sekunden; verbinden sich zwei Personen gleichzeitig, wartet einer, bis der andere fertig ist. ESPAsyncWebServer läuft dagegen in einem eigenen FreeRTOS-Task (async_tcp), den die Bibliothek AsyncTCP anlegt. Kommt eine Anfrage an, werden die Header im Hintergrund gelesen, sobald die Anfrage vollständig ist, wird deine Funktion (Callback) aufgerufen, und auch die Antwort geht im Hintergrund raus. loop() läuft währenddessen einfach weiter. So können mehrere Verbindungen gleichzeitig offen bleiben, und dauerhaft offene Verbindungen wie WebSocket und Server-Sent Events (SSE) werden möglich. Wenn dich die beiden Kerne des ESP32 und Tasks interessieren, wirf einen Blick in den Artikel ESP32 FreeRTOS.
| Merkmal | WebServer.h (synchron) | ESPAsyncWebServer (asynchron) |
|---|---|---|
| Installation | Im ESP32-Core enthalten | 2 Bibliotheken aus dem Bibliotheksverwalter |
| Bearbeitung der Anfragen | Nacheinander per handleClient() in loop() | Im async_tcp-Task im Hintergrund, mehrere gleichzeitig |
| delay() in loop() | Verzögert die Antwort der Seite | Beeinflusst den Server nicht |
| WebSocket | Nein; eigene Bibliothek nötig (z. B. die WebSockets-Bibliothek von Markus Sattler) | Eingebaut (AsyncWebSocket) |
| Server-Sent Events | Nein | Eingebaut (AsyncEventSource) |
| Statische Dateien | Mit serveStatic | serveStatic, automatische .gz-Unterstützung und Cache-Steuerung |
| Worauf achten? | Am einfachsten zu lernen | Kein delay() und keine langen Aufgaben im Callback |
Was ist WebSocket und was unterscheidet es von einer HTTP-Anfrage?
Bei einer normalen HTTP-Anfrage öffnet der Browser eine Verbindung, sagt "gib mir diese Seite", erhält die Antwort, und das Gespräch ist vorbei. Der Server kann dem Browser von sich aus nichts mitteilen; um einen neuen Wert zu sehen, muss der Browser erneut fragen. WebSocket (RFC 6455) ist dagegen ein Handshake, der als HTTP beginnt: Der Browser hängt an seine Anfrage den Header Upgrade: websocket, der ESP32 nimmt mit der Antwort 101 Switching Protocols an, und dieselbe TCP-Verbindung bleibt offen. Ab jetzt können beide Seiten jederzeit Nachrichten senden (Vollduplex). Jede Nachricht hat nur 2 bis 14 Byte Overhead, während jede HTTP-Anfrage Hunderte Byte an Headern mitschleppt. Deshalb kommen WebSocket-Nachrichten innerhalb von Millisekunden an, und für Panels, die mehrmals pro Sekunde aktualisieren, ist es die effizienteste Methode. So schneiden die vier Wege ab, eine Seite mit Sensorwerten aktuell zu halten:
| Methode | Wie funktioniert sie? | Verzögerung und Last | Wann einsetzen? |
|---|---|---|---|
| Seite neu laden (meta refresh) | Der Browser lädt alle paar Sekunden die ganze Seite neu | Hoch; die Seite flackert jedes Mal | Besser nicht |
| Abfragen mit fetch (Polling) | JavaScript holt alle 1–2 Sekunden ein kleines JSON | Mittel; jede Anfrage trägt neue Header | Ein einzelner, selten wechselnder Wert |
| Server-Sent Events (SSE) | Der Server schickt Ereignisse in eine Richtung über eine offene Verbindung | Niedrig; nur vom Server zum Browser | Reine Anzeige-Dashboards |
| WebSocket | Nachrichten in beide Richtungen über eine offene Verbindung | Am niedrigsten; Frames von wenigen Byte | Knöpfe, Schieberegler und Live-Daten zusammen |
Benötigte Bibliotheken: ESPAsyncWebServer und AsyncTCP installieren
- Stelle sicher, dass das ESP32-Boardpaket (Espressif Systems) in der Arduino IDE installiert ist; die Schritte findest du in Was ist der ESP32?
- Öffne den Bibliotheksverwalter (Strg+Umschalt+I) und suche nach ESPAsyncWebServer. Installiere die Bibliothek ESP Async WebServer mit dem Autor ESP32Async.
- Diese Bibliothek meldet ihre Abhängigkeit nicht an den Bibliotheksverwalter, deshalb bietet die IDE keine weiteren Bibliotheken an: Installiere die ebenfalls von ESP32Async veröffentlichte Bibliothek Async TCP separat. Auf einem ESP8266 brauchst du stattdessen die Bibliothek ESP Async TCP von ESP32Async (ESPAsyncTCP.h).
- Für das Panel-Beispiel installierst du die DHT sensor library von Adafruit und die verlangte Abhängigkeit Adafruit Unified Sensor. Installierst du zum ersten Mal eine Bibliothek, hilft dir die Anleitung Bibliotheken in Arduino hinzufügen.
- Liegen im Ordner Dokumente/Arduino/libraries noch früher per ZIP hinzugefügte Ordner wie ESPAsyncWebServer-master, AsyncTCP-master oder ESPAsyncWebSrv, lösche sie. Zwei Kopien derselben Bibliothek führen dazu, dass der Compiler die falsche nimmt.
Erster Schritt: der einfachste Server mit AsyncWebServer
Bevor es ans Panel geht, schauen wir uns die Logik der Bibliothek an einem kleinen Beispiel an. Der folgende Code verbindet den ESP32 mit deinem Heimnetz, zeigt auf der Startseite einen Gruß und schaltet über die Adressen /led?durum=1 und /led?durum=0 die eingebaute LED ein und aus. Lade den Code hoch, öffne den seriellen Monitor mit 115200 Baud und gib die angezeigte Adresse im Browser eines Handys ein, das im selben WLAN ist. Kommt das Board nicht ins Netz, nutze den Diagnosecode aus der Anleitung ESP32 verbindet sich nicht mit dem WLAN.
1#include <WiFi.h>
2#include <AsyncTCP.h>
3#include <ESPAsyncWebServer.h>
4
5const char* ssid = "AgAdi"; // Wi-Fi adın / your Wi-Fi name
6const char* password = "Sifre123"; // Wi-Fi şifren / your Wi-Fi password
7const int LED_PIN = 2; // çoğu DevKit'te dahili LED / onboard LED on most DevKits
8
9AsyncWebServer server(80); // 80 = standart HTTP portu / standard HTTP port
10bool ledAcik = false;
11
12void setup() {
13 Serial.begin(115200);
14 pinMode(LED_PIN, OUTPUT);
15
16 WiFi.mode(WIFI_STA);
17 WiFi.begin(ssid, password);
18 while (WiFi.status() != WL_CONNECTED) {
19 delay(500);
20 Serial.print(".");
21 }
22 Serial.println();
23 Serial.print("Adres / address: http://");
24 Serial.println(WiFi.localIP());
25
26 // Ana sayfa / home page
27 server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
28 request->send(200, "text/plain", "Merhaba, ben ESP32 / Hello from ESP32");
29 });
30
31 // /led?durum=1 yakar, /led?durum=0 söndürür / turns the LED on or off
32 server.on("/led", HTTP_GET, [](AsyncWebServerRequest *request) {
33 if (request->hasParam("durum")) {
34 ledAcik = request->getParam("durum")->value() == "1";
35 digitalWrite(LED_PIN, ledAcik ? HIGH : LOW);
36 }
37 request->send(200, "text/plain", ledAcik ? "LED: ON" : "LED: OFF");
38 });
39
40 // Tanımsız adresler / unknown addresses
41 server.onNotFound([](AsyncWebServerRequest *request) {
42 request->send(404, "text/plain", "Sayfa yok / Not found");
43 });
44
45 server.begin();
46}
47
48void loop() {
49 // Sunucu arka planda çalışır, loop() boş kalabilir / the server runs in the background
50}| Befehl | Was macht er? |
|---|---|
| AsyncWebServer server(80) | Erstellt den Server, der auf dem HTTP-Port 80 lauscht |
| server.on("/led", HTTP_GET, ...) | Registriert die Funktion (Callback) für eine Adresse und einen Anfragetyp |
| request->hasParam("durum") | Prüft, ob die Adresse einen Parameter ?durum=... enthält |
| request->getParam("durum")->value() | Liefert den Wert des Parameters als String |
| request->send(Code, Typ, Inhalt) | Sendet die Antwort: 200 OK, 404 nicht gefunden; Typ text/plain, text/html oder application/json |
| server.onNotFound(...) | Läuft für jede nicht definierte Adresse |
| server.begin() | Startet den Server; nach allen server.on()-Zeilen aufrufen |
Bauteile und Verkabelung für das Bedienfeld
Im Panel-Beispiel schaltest du mit einem Relaismodul eine Lampe oder einen Lüfter und misst mit einem DHT22 Temperatur und Luftfeuchtigkeit. Du brauchst ein ESP32 DevKit, ein einkanaliges 5-V-Relaismodul, ein DHT22- (oder DHT11-) Sensormodul, ein Breadboard und Jumperkabel. Nimm beim Relais am besten ein Modul mit Optokoppler. Der DHT22 misst von −40 bis 80 °C mit ±0,5 °C Genauigkeit und darf höchstens alle 2 Sekunden gelesen werden; der DHT11 ist günstiger, schafft aber nur ±2 °C im Bereich 0–50 °C. Details zum Sensor findest du im Artikel DHT11 Temperatur- und Feuchtigkeitssensor, die Pinbelegung deines Boards im Werkzeug Pinout.
| Modul-Pin | ESP32 DevKit | ESP8266 NodeMCU | Hinweis |
|---|---|---|---|
| Relais VCC | VIN (5 V) | VIN oder VU (5 V) | Die Relaisspule braucht 5 V; Versorgung über USB |
| Relais GND | GND | GND | Gemeinsame Masse |
| Relais IN | GPIO 26 | D1 (GPIO 5) | Die meisten Module ziehen bei LOW an |
| DHT22 VCC (+) | 3V3 | 3V3 | Bei 3,3 V Versorgung hat auch die Datenleitung 3,3 V |
| DHT22 DATA (OUT) | GPIO 4 | D2 (GPIO 4) | Nackter Sensor: 10-kΩ-Widerstand zwischen DATA und VCC |
| DHT22 GND (−) | GND | GND | Gemeinsame Masse |
Bedienfeld in Echtzeit: Relais, DHT22 und WebSocket
Jetzt zum eigentlichen Projekt. Der folgende Code ist eine einzige Datei: HTML, CSS und JavaScript stecken als PROGMEM-Array im Code. Beim Öffnen der Seite baut der Browser eine WebSocket-Verbindung zu /ws auf. Alle 2 Sekunden liest der ESP32 den DHT22 und schickt Temperatur, Luftfeuchtigkeit, Relaiszustand und die Zahl der verbundenen Geräte in einer einzigen JSON-Nachricht an alle Browser. Drückst du den Knopf, sendet der Browser die Nachricht toggle, der ESP32 schaltet das Relais um und meldet den neuen Zustand wieder an alle. Trage Netzwerkname und Passwort ein, lade hoch und öffne im Browser http://esp32.local oder die IP-Adresse aus dem seriellen Monitor.
1#include <WiFi.h>
2#include <AsyncTCP.h>
3#include <ESPAsyncWebServer.h>
4#include <ESPmDNS.h>
5#include <DHT.h>
6
7const char* ssid = "AgAdi"; // Wi-Fi adın / your Wi-Fi name
8const char* password = "Sifre123"; // Wi-Fi şifren / your Wi-Fi password
9
10const int ROLE_PIN = 26; // röle modülü IN / relay module IN
11const bool ROLE_AKTIF_LOW = true; // çoğu modül LOW ile çeker / most modules switch on LOW
12const int DHT_PIN = 4; // DHT22 veri pini / DHT22 data pin
13
14DHT dht(DHT_PIN, DHT22); // DHT11 için DHT11 yaz / write DHT11 for a DHT11
15AsyncWebServer server(80);
16AsyncWebSocket ws("/ws"); // WebSocket adresi / WebSocket path
17
18bool roleAcik = false;
19float sicaklik = NAN;
20float nem = NAN;
21volatile bool gonder = false; // loop() yayın yapsın mı? / should loop() broadcast?
22unsigned long sonOlcum = 0;
23unsigned long sonTemizlik = 0;
24
25// Tarayıcıya giden sayfa / the page sent to the browser
26const char index_html[] PROGMEM = R"rawliteral(
27<!DOCTYPE html>
28<html lang="tr">
29<head>
30<meta charset="utf-8">
31<meta name="viewport" content="width=device-width, initial-scale=1">
32<title>ESP32 Panel</title>
33<style>
34body{font-family:sans-serif;background:#0f172a;color:#e2e8f0;text-align:center;margin:0;padding:16px}
35.kart{background:#1e293b;border-radius:12px;padding:16px;margin:12px auto;max-width:320px}
36.deger{font-size:2.4em;font-weight:bold}
37button{font-size:1.2em;padding:14px 32px;border:0;border-radius:8px;color:#fff;width:100%}
38.acik{background:#16a34a}.kapali{background:#475569}
39</style>
40</head>
41<body>
42<h2>ESP32 Panel</h2>
43<p id="baglanti">Bağlanıyor / Connecting...</p>
44<div class="kart">Sıcaklık / Temperature<div class="deger"><span id="sicaklik">--</span> °C</div></div>
45<div class="kart">Nem / Humidity<div class="deger"><span id="nem">--</span> %</div></div>
46<div class="kart">Röle / Relay<p><button id="dugme" class="kapali" onclick="degistir()">KAPALI / OFF</button></p></div>
47<p>Bağlı cihaz / Clients: <span id="istemci">0</span></p>
48<script>
49let ws;
50const $ = (id) => document.getElementById(id);
51function baglan() {
52 ws = new WebSocket('ws://' + location.host + '/ws');
53 ws.onopen = () => { $('baglanti').textContent = 'Bağlı / Connected'; };
54 ws.onclose = () => {
55 $('baglanti').textContent = 'Koptu, yeniden deneniyor / Reconnecting...';
56 setTimeout(baglan, 2000);
57 };
58 ws.onmessage = (e) => {
59 const d = JSON.parse(e.data);
60 $('sicaklik').textContent = d.sicaklik === null ? '--' : d.sicaklik.toFixed(1);
61 $('nem').textContent = d.nem === null ? '--' : d.nem.toFixed(0);
62 $('istemci').textContent = d.istemci;
63 $('dugme').textContent = d.role ? 'AÇIK / ON' : 'KAPALI / OFF';
64 $('dugme').className = d.role ? 'acik' : 'kapali';
65 };
66}
67function degistir() {
68 if (ws.readyState === WebSocket.OPEN) ws.send('toggle');
69}
70baglan();
71</script>
72</body>
73</html>
74)rawliteral";
75
76void roleAyarla(bool acik) {
77 roleAcik = acik;
78 digitalWrite(ROLE_PIN, ROLE_AKTIF_LOW ? !acik : acik);
79}
80
81// Durumu JSON metnine çevir / build the state as JSON text
82String durumJson() {
83 String s = "{\"role\":";
84 s += roleAcik ? "true" : "false";
85 s += ",\"sicaklik\":";
86 s += isnan(sicaklik) ? String("null") : String(sicaklik, 1);
87 s += ",\"nem\":";
88 s += isnan(nem) ? String("null") : String(nem, 0);
89 s += ",\"istemci\":";
90 s += (unsigned long)ws.count();
91 s += "}";
92 return s;
93}
94
95// WebSocket olayları (async_tcp görevinde çalışır) / WebSocket events (run in the async_tcp task)
96void wsOlay(AsyncWebSocket *sunucu, AsyncWebSocketClient *client, AwsEventType type,
97 void *arg, uint8_t *data, size_t len) {
98 switch (type) {
99 case WS_EVT_CONNECT:
100 Serial.printf("Istemci #%u baglandi / connected: %s\n",
101 (unsigned)client->id(), client->remoteIP().toString().c_str());
102 gonder = true; // yeni gelen güncel durumu alsın / newcomer gets the state
103 break;
104 case WS_EVT_DISCONNECT:
105 Serial.printf("Istemci #%u ayrildi / disconnected\n", (unsigned)client->id());
106 gonder = true;
107 break;
108 case WS_EVT_DATA: {
109 AwsFrameInfo *info = (AwsFrameInfo *)arg;
110 // Tek parça, tam bir metin mesajı mı? / a complete single-frame text message?
111 if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) {
112 if (len == 6 && memcmp(data, "toggle", 6) == 0) {
113 roleAyarla(!roleAcik);
114 gonder = true; // tüm ekranlar eşitlensin / sync every screen
115 }
116 }
117 break;
118 }
119 default: // PONG, ERROR vb. / PONG, ERROR etc.
120 break;
121 }
122}
123
124void setup() {
125 Serial.begin(115200);
126 pinMode(ROLE_PIN, OUTPUT);
127 roleAyarla(false); // açılışta röle kapalı / relay off at start
128 dht.begin();
129
130 WiFi.mode(WIFI_STA);
131 WiFi.begin(ssid, password);
132 Serial.print("Baglaniyor / connecting");
133 while (WiFi.status() != WL_CONNECTED) {
134 delay(500);
135 Serial.print(".");
136 }
137 WiFi.setSleep(false); // daha hızlı tepki / lower latency
138 Serial.println();
139 Serial.print("http://");
140 Serial.println(WiFi.localIP());
141
142 if (MDNS.begin("esp32")) { // http://esp32.local
143 MDNS.addService("http", "tcp", 80);
144 Serial.println("http://esp32.local");
145 }
146
147 ws.onEvent(wsOlay);
148 server.addHandler(&ws);
149
150 server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
151 request->send(200, "text/html", index_html);
152 });
153 server.onNotFound([](AsyncWebServerRequest *request) {
154 request->send(404, "text/plain", "Sayfa yok / Not found");
155 });
156 server.begin();
157}
158
159void loop() {
160 if (millis() - sonOlcum >= 2000) { // DHT22 en fazla 2 sn'de bir / DHT22: once per 2 s
161 sonOlcum = millis();
162 float t = dht.readTemperature();
163 float h = dht.readHumidity();
164 if (!isnan(t) && !isnan(h)) { // hatalı okumayı atla / skip failed reads
165 sicaklik = t;
166 nem = h;
167 }
168 gonder = true;
169 }
170
171 if (gonder) {
172 gonder = false;
173 ws.textAll(durumJson()); // tüm tarayıcılara yolla / send to every browser
174 }
175
176 if (millis() - sonTemizlik >= 1000) {
177 sonTemizlik = millis();
178 ws.cleanupClients(); // kopuk istemcileri temizle / drop stale clients
179 }
180}Wie funktioniert der Code?
- AsyncWebSocket ws("/ws") legt den WebSocket-Endpunkt an, server.addHandler(&ws) hängt ihn an den Server. Die Adresse im JavaScript muss exakt diesem Pfad entsprechen.
- Die Funktion wsOlay() wird bei jeder Verbindung, jedem Abbruch und jeder eingehenden Nachricht aufgerufen. WS_EVT_CONNECT bedeutet einen neuen Client, WS_EVT_DISCONNECT einen Client, der geht, und WS_EVT_DATA eine eingehende Nachricht.
- AwsFrameInfo enthält die Informationen zum eingehenden Frame. Die Prüfung von final, index == 0 und len stellt sicher, dass die Nachricht vollständig in einem Frame kam, und WS_TEXT, dass es Text ist. Kurze Befehle passen immer in einen Frame.
- Die eingehenden Daten werden mit memcmp() samt Länge verglichen; so bist du nicht davon abhängig, ob am Ende der Daten ein Nullzeichen steht.
- Weil die Callbacks im async_tcp-Task laufen, schalten sie nur das Relais um und setzen das Flag gonder. Das eigentliche Senden passiert in loop() mit ws.textAll(); so gehen alle Nachrichten von einer Stelle aus der Reihe nach raus.
- ws.cleanupClients() wird einmal pro Sekunde aufgerufen: Es gibt den Speicher abgebrochener Verbindungen frei und schließt die älteste Verbindung, wenn die Zahl der Clients das Limit überschreitet.
- Auf der JavaScript-Seite verbindet das Ereignis onclose nach 2 Sekunden neu. Selbst wenn der ESP32 neu startet oder das WLAN kurz wegbricht, fängt sich die Seite von allein. Weil die Adresse aus location.host gebildet wird, funktioniert die Seite mit der IP ebenso wie mit esp32.local.
- WiFi.setSleep(false) schaltet das WLAN-Stromsparen ab. Der standardmäßige Modem-Sleep kann Antworten um bis zu einige Hundert Millisekunden verzögern; bei einem Panel am Netzteil machst du die Knöpfe damit spürbar direkter.
JSON als Nachrichtenformat macht dir das Leben leicht: Der Browser verwandelt den eingehenden Text mit JSON.parse() in einer Zeile in ein Objekt. Eine Beispielnachricht sieht so aus: {"role":true,"sicaklik":23.4,"nem":48,"istemci":2}. Kann der Sensor nicht gelesen werden, wird der Wert als null gesendet und die Seite zeigt "--"; ginge ein Lesefehler als NaN raus, würde das ungültige JSON die Seite gar nicht mehr aktualisieren. Da pro Nachricht nur rund 60 Byte anfallen, belasten selbst mehrere Updates pro Sekunde das Netz nicht. Für komplexere Nachrichten kannst du auf die Bibliothek ArduinoJson umsteigen, aber für ein Panel dieser Größe reicht das Zusammensetzen von Strings völlig aus. Statt des Relais kannst du auch eine LED oder einen Summer anschließen und denselben Code unverändert ausprobieren.
Wo gehört das HTML hin: PROGMEM oder LittleFS?
Im Panel-Code steht die Seite als Raw-String-Literal in der Form R"rawliteral( ... )rawliteral". Diese Schreibweise erlaubt Anführungszeichen und Zeilenumbrüche ohne Escape-Zeichen. Auf AVR-basierten Arduinos braucht man PROGMEM, damit Text im Flash statt im RAM liegt; beim ESP32 werden konstante Arrays ohnehin aus dem Flash gelesen, PROGMEM steht dort also nur aus Kompatibilitätsgründen und schadet nicht. Wächst die Seite, wird das Bearbeiten von HTML im Code mühsam; dann ist es übersichtlicher, die Dateien ins Dateisystem LittleFS im Flash auszulagern.
| Kriterium | PROGMEM (im Code) | LittleFS (Dateisystem) |
|---|---|---|
| Einrichtung | Kein Zusatzschritt, eine Datei | data-Ordner und Upload-Plugin nötig |
| Bearbeiten | HTML in einem C++-Array | Getrennte Dateien index.html, style.css und app.js |
| Aktualisieren | Nach jeder Änderung den Sketch neu hochladen | Nur das Dateisystem neu hochladen |
| Größe | Ideal für Seiten von wenigen KB | Mit der Standard-Partitionierung rund 1,5 MB Platz |
| Bilder und Schriften | Nicht praktikabel | Symbole, Bilder und .gz-Dateien möglich |
| Wann? | Erste Tests und kleine Panels | Mehrseitige oder wachsende Oberflächen |
HTML-, CSS- und JavaScript-Dateien mit LittleFS ausliefern
- Lege im Sketch-Ordner (dem Ordner mit der .ino-Datei) einen Ordner namens data an und speichere das HTML zwischen R"rawliteral( und )rawliteral" aus dem Panel-Code als data/index.html. Wenn du möchtest, teilst du CSS und JavaScript in eigene Dateien auf.
- Arduino IDE 2 unterstützt das alte Menü ESP32 Sketch Data Upload nicht. Lade stattdessen die .vsix-Datei des Plugins arduino-littlefs-upload herunter und lege sie unter Windows in C:\Users\(dein Benutzername)\.arduinoIDE\plugins, unter macOS und Linux in ~/.arduinoIDE/plugins ab (Ordner notfalls anlegen); danach die IDE neu starten. Das Plugin braucht IDE 2.2.1 oder neuer.
- Schließe den seriellen Monitor, falls er offen ist, öffne mit Strg+Umschalt+P (macOS: Cmd+Umschalt+P) die Befehlspalette und wähle Upload LittleFS to Pico/ESP8266/ESP32. Die Dateien werden in die Dateisystem-Partition im Flash geschrieben.
- Unter Werkzeuge > Partition Scheme muss ein Schema mit Dateisystem-Partition gewählt sein (zum Beispiel Default 4MB with spiffs). LittleFS nutzt genau diese Partition mit dem Namen spiffs.
- Lade den folgenden Code wie gewohnt hoch. Solange die Option Erase All Flash im Menü Werkzeuge deaktiviert ist, löscht das Hochladen des Sketches das Dateisystem nicht; änderst du die Dateien, wiederholst du nur Schritt 3.
1#include <WiFi.h>
2#include <AsyncTCP.h>
3#include <ESPAsyncWebServer.h>
4#include <LittleFS.h>
5
6const char* ssid = "AgAdi"; // Wi-Fi adın / your Wi-Fi name
7const char* password = "Sifre123"; // Wi-Fi şifren / your Wi-Fi password
8
9AsyncWebServer server(80);
10
11void setup() {
12 Serial.begin(115200);
13
14 if (!LittleFS.begin(true)) { // true: bağlanamazsa biçimlendir / format if mount fails
15 Serial.println("LittleFS hatasi / LittleFS error");
16 return;
17 }
18
19 WiFi.mode(WIFI_STA);
20 WiFi.begin(ssid, password);
21 while (WiFi.status() != WL_CONNECTED) {
22 delay(500);
23 }
24 Serial.println(WiFi.localIP());
25
26 // data/ klasörünün içeriğini sun / serve the contents of the data/ folder
27 server.serveStatic("/", LittleFS, "/")
28 .setDefaultFile("index.html") // varsayılan index.htm'dir / default is index.htm
29 .setCacheControl("max-age=600"); // tarayıcı 10 dk önbelleğe alsın / cache for 10 min
30
31 server.onNotFound([](AsyncWebServerRequest *request) {
32 request->send(404, "text/plain", "Sayfa yok / Not found");
33 });
34 server.begin();
35}
36
37void loop() {
38}mDNS: esp32.local statt IP-Adresse
Dein Router kann dem ESP32 bei jedem Start eine andere IP-Adresse geben, und Zahlen wie 192.168.1.47 merkt man sich schlecht. mDNS (Multicast DNS) lässt ein Gerät seinen eigenen Namen im lokalen Netz bekannt geben. Dank der Zeile MDNS.begin("esp32") im Panel-Code öffnen Geräte im selben Netz die Seite unter http://esp32.local, und MDNS.addService("http", "tcp", 80) teilt Netzwerk-Scanner-Apps mit, dass hier ein Webserver läuft. Häng an den Namen kein ".local" an und verwende nur Kleinbuchstaben, Ziffern und Bindestriche. Nutzt du mehrere ESP32, gib jedem einen eigenen Namen (kueche, wohnzimmer usw.), denn zwei Geräte mit demselben Namen stören sich gegenseitig. mDNS wird nicht überall gleich gut unterstützt:
| System | .local-Unterstützung | Wenn es nicht klappt |
|---|---|---|
| iPhone, iPad, macOS | Eingebaut (Bonjour) | Prüfen, ob du im selben WLAN bist |
| Windows 10 und 11 | Klappt in aktuellen Versionen meist | Bonjour-Dienst installieren oder IP nutzen |
| Linux | Funktioniert, wenn avahi-daemon installiert ist | Pakete avahi-daemon und nss-mdns installieren |
| Android | Hängt von Version und Browser ab | IP-Adresse nutzen oder im Router eine DHCP-Reservierung anlegen |
Mehrere Clients: Handy, Tablet und Computer gleichzeitig
Das Beliebteste an WebSocket ist, dass alle, die das Panel öffnen, denselben Zustand sehen. Schaltest du das Relais am Handy ein, schickt der ESP32 den neuen Zustand mit textAll() an alle Verbindungen, und der Tab auf dem Computer wird im selben Moment grün. Jeder Tab ist ein eigener Client: Öffnest du im selben Browser drei Tabs, zeigt der Zähler 3. Standardmäßig erlaubt die Bibliothek auf dem ESP32 8 und auf dem ESP8266 4 gleichzeitige WebSocket-Clients (DEFAULT_MAX_WS_CLIENTS). Dieses Limit greift nicht von selbst; erst beim Aufruf von cleanupClients() wird die älteste Verbindung über dem Limit geschlossen. Außerdem solltest du Folgendes beachten:
- Wenn das Handy schläft: Ein Handy mit ausgeschaltetem Display schließt die Verbindung oft nicht sauber. Solche halb offenen Verbindungen werden dank cleanupClients() und regelmäßiger Datenübertragung mit der Zeit aufgeräumt.
- Senderate: Pro Client können höchstens 32 Nachrichten in der Warteschlange stehen (beim ESP8266 8). Rufst du textAll() in jedem Durchlauf von loop() auf, läuft die Warteschlange voll und neue Nachrichten werden stillschweigend verworfen; stellst du unter Werkzeuge > Core Debug Level Warn oder eine ausführlichere Stufe ein, erscheint im seriellen Monitor die Warnung Too many messages queued. Sende einen Wert nur, wenn er sich ändert, oder ein paar Mal pro Sekunde.
- Neu verbundener Client: Schicke jeder neu verbundenen Seite sofort den aktuellen Zustand; sonst schaut der Nutzer bis zur ersten Messung auf ein leeres Panel. Im Panel-Code erledigt das das Flag, das in WS_EVT_CONNECT gesetzt wird.
- Antwort an einen einzelnen Client: Um nur dem fragenden Browser statt allen zu antworten, nutze client->text("..."); die Nummer des Clients liefert client->id().
- Speicher: Jede offene Verbindung belegt einen TCP-Socket und Pufferspeicher. Die 520 KB RAM des ESP32 reichen für einige Clients locker, doch wenn du ab und zu mit ESP.getFreeHeap() den freien Speicher ausgibst, erkennst du Speicherlecks früh.
Einfacher Passwortschutz
Jeder in deinem Heimnetz, der die Adresse des Panels kennt, kann das Relais schalten. Mit den Funktionen request->authenticate() und request->requestAuthentication() der Bibliothek versiehst du die Seite mit Benutzername und Passwort; der Browser öffnet sein eigenes Anmeldefenster und merkt sich die Daten für die Sitzung. Standardmäßig wird Digest-Authentifizierung verwendet, das Passwort geht also nicht im Klartext durchs Netz. Nur die Seite zu schützen reicht aber nicht: Die WebSocket-Adresse ist eine eigene Tür, und jemand könnte sich direkt mit ws://ip/ws verbinden. Das folgende Beispiel schließt auch diese Tür: Der ESP32 erzeugt bei jedem Start einen zufälligen Schlüssel, gibt ihn nur einem angemeldeten Browser unter /anahtar heraus und lehnt mit setFilter() alle WebSocket-Verbindungen ab, die nicht den richtigen Schlüssel mitbringen.
1#include <WiFi.h>
2#include <AsyncTCP.h>
3#include <ESPAsyncWebServer.h>
4
5const char* ssid = "AgAdi"; // Wi-Fi adın / your Wi-Fi name
6const char* password = "Sifre123"; // Wi-Fi şifren / your Wi-Fi password
7const char* KULLANICI = "admin"; // panel kullanıcı adı / panel username
8const char* PAROLA = "Gizli-2026"; // panel parolası / panel password
9const int LED_PIN = 2;
10
11AsyncWebServer server(80);
12AsyncWebSocket ws("/ws");
13String wsAnahtar; // her açılışta yeni anahtar / a new key on every boot
14bool ledAcik = false;
15
16const char index_html[] PROGMEM = R"rawliteral(
17<!DOCTYPE html>
18<html><head><meta charset="utf-8">
19<meta name="viewport" content="width=device-width, initial-scale=1"></head>
20<body style="font-family:sans-serif;text-align:center">
21<h2>Korumali panel / Protected panel</h2>
22<button style="font-size:1.4em;padding:12px 32px" onclick="ws && ws.send('toggle')">LED</button>
23<p id="durum">...</p>
24<script>
25let ws;
26fetch('/anahtar').then(r => r.text()).then(k => {
27 ws = new WebSocket('ws://' + location.host + '/ws?k=' + k);
28 ws.onmessage = (e) => { document.getElementById('durum').textContent = e.data; };
29});
30</script>
31</body></html>
32)rawliteral";
33
34bool girisVar(AsyncWebServerRequest *request) {
35 return request->authenticate(KULLANICI, PAROLA);
36}
37
38void setup() {
39 Serial.begin(115200);
40 pinMode(LED_PIN, OUTPUT);
41
42 WiFi.mode(WIFI_STA);
43 WiFi.begin(ssid, password);
44 while (WiFi.status() != WL_CONNECTED) {
45 delay(500);
46 }
47 Serial.println(WiFi.localIP());
48
49 // Wi-Fi açıkken random() donanım üretecini kullanır / random() uses the hardware RNG
50 wsAnahtar = String(random(0x7FFFFFFF), HEX) + String(random(0x7FFFFFFF), HEX);
51
52 // Sayfa: tarayıcı kullanıcı adı ve parola sorar / page: the browser asks for login
53 server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
54 if (!girisVar(request)) return request->requestAuthentication();
55 request->send(200, "text/html", index_html);
56 });
57
58 // Anahtar yalnız giriş yapana verilir / the key is given only after login
59 server.on("/anahtar", HTTP_GET, [](AsyncWebServerRequest *request) {
60 if (!girisVar(request)) return request->requestAuthentication();
61 request->send(200, "text/plain", wsAnahtar);
62 });
63
64 // Doğru anahtarı getirmeyen bağlantıyı reddet / reject sockets without the right key
65 ws.setFilter([](AsyncWebServerRequest *request) {
66 return request->hasParam("k") && request->getParam("k")->value() == wsAnahtar;
67 });
68
69 ws.onEvent([](AsyncWebSocket *sunucu, AsyncWebSocketClient *client, AwsEventType type,
70 void *arg, uint8_t *data, size_t len) {
71 if (type == WS_EVT_DATA && len == 6 && memcmp(data, "toggle", 6) == 0) {
72 ledAcik = !ledAcik;
73 digitalWrite(LED_PIN, ledAcik ? HIGH : LOW);
74 sunucu->textAll(ledAcik ? "LED: ON" : "LED: OFF");
75 }
76 });
77
78 server.addHandler(&ws);
79 server.begin();
80}
81
82void loop() {
83 ws.cleanupClients(); // saniyede bir yeterli / once per second is enough
84 delay(1000);
85}Hinweise für ESP8266 und Raspberry Pi Pico W
Dieselbe Bibliothek läuft auch auf dem ESP8266 (NodeMCU, Wemos D1 mini). Das musst du ändern: Installiere statt Async TCP die Bibliothek ESP Async TCP von ESP32Async; binde statt WiFi.h ESP8266WiFi.h, statt AsyncTCP.h ESPAsyncTCP.h und statt ESPmDNS.h ESP8266mDNS.h ein und füge in loop() die Zeile MDNS.update() hinzu. Ersetze WiFi.setSleep(false) durch WiFi.setSleepMode(WIFI_NONE_SLEEP) und passe die Pins an die NodeMCU-Spalte der Verkabelungstabelle an. Der ESP8266 hat nur rund 80 KB nutzbaren RAM, halte die Seite also klein und denk daran, dass das Client-Limit 4 beträgt. Nutzt du einen Raspberry Pi Pico W, findest du einen Webserver mit MicroPython in der Anleitung Raspberry Pi Pico W WLAN; neuere Versionen von ESPAsyncWebServer unterstützen auf der Arduino-Seite auch die Chips RP2040 und RP2350.
Projektideen mit dem ESP32-Webserver
- Mehrkanalige smarte Steckdose: Mit einer 4-Kanal-Relaisplatine baust du getrennte Knöpfe für Lampe, Lüfter und Steckdose; unterscheide die Befehle durch kurze Texte wie "r1" und "r2". Zur Wahl des Relais siehe den Artikel zum Relaismodul.
- Bewässerungs-Panel: Zeige den Wert des Bodenfeuchtesensors live an, stelle die Gießschwelle mit einem Schieberegler auf der Seite ein und starte die Pumpe bei Bedarf von Hand.
- Wetterstation: Nimm statt des DHT22 einen BME280 und ergänze den Luftdruck; zeige die letzten 60 Messungen als kleines Liniendiagramm auf der Seite.
- Lampe mit Farbwähler: Schicke den Wert eines Farbwählers auf der Seite per WebSocket und färbe einen WS2812B-NeoPixel-Streifen sofort ein.
- Servo per Schieberegler: Sende den Wert des Schiebereglers bei jeder Bewegung und drehe einen Servomotor; ideal zum Ausrichten einer Kamera oder für einen Türriegel.
- Protokoll mit Zeitstempel: Ergänze jede Messung um die per NTP geholte Uhrzeit und aktualisiere deinen Code nicht jedes Mal per USB, sondern drahtlos per OTA.
Häufige Fehler und Lösungen
| Symptom | Ursache | Lösung |
|---|---|---|
| 'mbedtls_md5_starts_ret' was not declared in this scope | Alte me-no-dev-Bibliothek mit ESP32-Core 3.x | ESP Async WebServer und Async TCP von ESP32Async installieren, alte Ordner löschen |
| Multiple libraries were found for "AsyncTCP.h" | Zwei Kopien derselben Bibliothek | Alte -master-Kopien im Ordner libraries löschen |
| Deprecated-Warnung bei send_P | send_P wird in der neuen Version entfernt | request->send(200, "text/html", index_html) verwenden |
| Die Seite öffnet sich gar nicht | Handy im Gastnetz oder mobilen Netz, oder die IP hat sich geändert | Ins selbe WLAN gehen und die aktuelle IP aus dem seriellen Monitor nutzen |
| Die Seite öffnet sich, aber die Werte bleiben bei -- | WebSocket verbindet nicht: anderer Pfad oder addHandler vergessen | F12-Konsole öffnen, ws("/ws") und die Adresse im JavaScript angleichen |
| Task watchdog got triggered mit async_tcp | delay(), lange Schleife oder HTTP-Anfrage im Callback | Callback kurz halten, Arbeit per Flag in loop() verlagern |
| Too many messages queued | textAll() wird zu oft aufgerufen | Nur bei Änderung oder ein paar Mal pro Sekunde senden |
| Die LittleFS-Seite liefert 404 | data-Ordner nicht hochgeladen oder setDefaultFile fehlt | Dateien mit dem Plugin hochladen, setDefaultFile("index.html") ergänzen |
| does not exist, no permits for creation im seriellen Monitor | Die angeforderte Datei fehlt in LittleFS | Dateinamen samt Groß- und Kleinschreibung prüfen; Zeilen mit .gz sind harmlos |
| Das Relais schaltet nie ab | Das 5-V-Modul erkennt 3,3 V nicht als HIGH | 3,3-V-taugliches Modul, JD-VCC oder Transistor verwenden |
| Der ESP32 startet neu, wenn das Relais anzieht | Der Spulenstrom bringt die Versorgung zum Einbrechen (Brownout) | Stabiles 5-V-Netzteil und 470-µF-Kondensator verwenden |
| esp32.local öffnet sich nicht | Gerät ohne mDNS-Unterstützung oder Name mit .local geschrieben | MDNS.begin("esp32") schreiben, notfalls die IP-Adresse nutzen |
| Der Knopf reagiert verzögert | WLAN-Modem-Sleep ist aktiv | Nach dem Verbinden WiFi.setSleep(false) aufrufen |
Die Kombination aus ESP32 Async Webserver und WebSocket ist der kürzeste Weg zu einem schnellen Bedienfeld, das ohne Cloud-Dienst und ohne eigene Handy-App auskommt und das sich mehrere Geräte teilen können. Starte mit dem PROGMEM-Panel in einer Datei, steig auf LittleFS um, wenn die Oberfläche wächst, und gib jedem neuen Gerät in deinem Netz einen eigenen mDNS-Namen. Bist du neu beim ESP32, findest du die Grundlagen in Was ist der ESP32? In der BlueGrays-App kannst du außerdem ESP32-Projekte wie Async-Webserver, WebSocket-Zähler, Zugriff per mDNS, NTP-Uhr und OTA-Update Schritt für Schritt mit Schaltplänen und fertigem Code nachbauen.
Häufige Fragen
Was ist der Unterschied zwischen ESPAsyncWebServer und WebServer beim ESP32?
Die WebServer-Bibliothek bearbeitet Anfragen nacheinander über den Aufruf von handleClient() in loop(); wird loop() langsam, wird es auch die Seite. ESPAsyncWebServer bearbeitet Anfragen in einem eigenen Hintergrund-Task, verkraftet mehrere Verbindungen gleichzeitig und bringt WebSocket und SSE gleich mit. Für Echtzeit-Panels ist die asynchrone Variante die bessere Wahl.
Wie viele Clients können sich gleichzeitig mit einem ESP32-Webserver verbinden?
ESPAsyncWebServer erlaubt auf dem ESP32 standardmäßig 8 WebSocket-Clients und schließt überzählige beim Aufruf von cleanupClients(); beim ESP8266 liegt das Limit bei 4. Da normale Seitenaufrufe kurz sind, können mehr Geräte die Seite nacheinander öffnen. In der Praxis laufen auf einem Heim-Panel 3–5 gleichzeitige Geräte problemlos.
Kann ich von außen über das Internet auf meinen ESP32-Webserver zugreifen?
Technisch geht das per Portweiterleitung im Router, empfehlenswert ist es aber nicht: Die Bibliothek bietet auf dem ESP32 kein HTTPS, und das Gerät ist Angriffen aus dem Internet ausgesetzt. Für die Fernsteuerung sind ein Telegram-Bot, ein MQTT-basiertes System oder die VPN-Funktion deines Routers deutlich sicherer.
Warum funktioniert esp32.local nicht?
Meist kann das Handy oder der Computer mDNS-Namen nicht auflösen; das kommt vor allem bei manchen Android-Handys und älteren Windows-Versionen vor. Auch MDNS.begin("esp32.local") im Code ist ein Fehler, denn der Name gehört ohne .local hinein. Klappt die Auflösung trotzdem nicht, nutze die IP-Adresse aus dem seriellen Monitor oder reserviere im Router eine feste IP für den ESP32.
Funktioniert ein ESP32-Webserver auch ohne Router?
Ja. Mit WiFi.softAP("ESP32-Panel", "12345678") baut der ESP32 sein eigenes WLAN auf; verbinde dein Handy damit und öffne im Browser 192.168.4.1. In diesem Access-Point-Modus gibt es kein Internet, Panel und WebSocket funktionieren aber genauso. Das WLAN-Passwort muss mindestens 8 Zeichen lang sein.
Welche ESPAsyncWebServer-Bibliothek soll ich installieren?
Installiere im Bibliotheksverwalter der Arduino IDE die Bibliotheken ESP Async WebServer und Async TCP mit dem Autor ESP32Async. Die ursprüngliche Version im Konto me-no-dev wird nicht mehr gepflegt und lässt sich mit ESP32-Core 3.x nicht kompilieren. Lösche außerdem alte, per ZIP hinzugefügte Kopien aus deinem libraries-Ordner.
Projekte, die du danach bauen kannst
Alle Projekte ansehen
ESP32 Async-Webserver (mit IP-Anzeige auf dem LCD)
In diesem Projekt verwandeln wir den ESP32 in einen asynchronen Webserver, den Sie über Ihr Smartphone oder Ihren Computer im selben WLAN-Netzwerk steuern können. Dank der ESPAsyncWebServer-Bibliothek kann der Server mehrere Anfragen gleichzeitig bearbeiten (im Gegensatz zum klassischen WebServer.h, der bei einer einzigen Anfrage blockiert). Sie können über den Browser die IP-Adresse des ESP32 öffnen und durch Drücken der ON/OFF-Schaltflächen (Routen /on und /off) die an GPIO 5 anges

ESP32-WebSocket-Zähler (2 Tasten + LCD + Web-Echtzeit)
In diesem Projekt ändern zwei am ESP32 angeschlossene Taster (GPIO 5 zählt hoch, GPIO 4 zählt herunter) einen Zähler. Der Wert wird sofort auf einem 16x2-I2C-LCD und über die WebSocket-Unterstützung von ESPAsyncWebServer in allen verbundenen Browsern aktualisiert, ohne die Seite neu zu laden. Statt HTTP-Polling wird eine einzige dauerhafte Verbindung genutzt; der ESP32 sendet den neuen Wert mit ws.textAll() an alle.

ESP32 mDNS: Zugriff auf das Gerät über „bluegrays-esp.local“ (mit LCD-Display)
In diesem Projekt fügen wir dem ESP32 einen mDNS-Dienst (Multicast DNS) hinzu, um den Zugriff nicht über eine IP-Adresse, sondern über einen lesbaren Hostnamen wie bluegrays-esp.local zu ermöglichen. Selbst wenn sich die vom DHCP zugewiesene IP-Adresse ändert, können Sie das Gerät immer unter demselben Namen erreichen. Indem wir zusätzlich einen kleinen Webserver darauf betreiben, senden wir über den Browser unter der Adresse http://bluegrays-esp.local/ Befehle zur LED-Steuerung. Auf dem I2C

ESP32-WLAN-STA-Verbindung (mit LCD-Display)
In diesem Projekt verbinden wir den ESP32 im Station-Modus (STA) sicher und fehlertolerant mit dem heimischen WLAN. Wir lagern SSID und Passwort in eine separate secrets.h-Datei aus, um Leaks zu Git zu verhindern, richten einen Auto-Reconnect-Mechanismus ein und zeigen RSSI-Signalpegel und IP-Adresse auf einem I2C-LCD-Display an. Die interne LED dient als Statusanzeige. Ein Verbindungsmanager auf Produktionsniveau.

ESP32 DHT22 Temperatur und Luftfeuchtigkeit (mit LCD-Display)
Bei diesem Projekt werden die Umgebungstemperatur und die Luftfeuchtigkeit mit einem DHT22-Sensor gemessen und in Echtzeit auf einem 16x2 I2C-LCD angezeigt. Mithilfe der Adafruit-DHT-Bibliothek werden die Daten über das One-Wire-Protokoll vom Sensor ausgelesen. Dieses Projekt bildet die Grundlage für IoT-Wetterstationen, Smart-Home-Automatisierung oder Umgebungsüberwachungssysteme in Gewächshäusern. Die Kommunikation mit dem DHT22 erfolgt über den GPIO-4-Pin des ESP32.

ESP32 Intelligente Beleuchtung (PIR + LDR + Relais + OLED)
Verwandle deinen Flur, dein Badezimmer oder deine Speisekammer mit dem ESP32 in intelligente Beleuchtung! Mit einem PIR-Bewegungssensor, einem LDR-Lichtsensor und einem Relaismodul realisieren wir das klassische Szenario "Licht geht an, wenn im Dunkeln Bewegung erkannt wird" — bei Tageslicht bleibt das Licht trotz Bewegung aus, was tagsüber Energie spart. Das Licht bleibt 30 Sekunden lang an; bei anhaltender Bewegung wird der Timer zurückgesetzt und das Licht bleibt länger an. Das SSD1306 OLED-D

ESP32-OTA-Update – Drahtlose Firmware-Installation (mit LCD-Display)
In diesem Projekt lernst du, wie du auf dem ESP32 ohne USB-Kabel über WLAN neue Firmware installieren kannst. Mit der ArduinoOTA-Bibliothek wird der ESP32 im Netzwerk sichtbar und wird im Reiter „Network port“ der Arduino-IDE automatisch als bluegrays-ota aufgelistet. Die erste Installation erfolgt über USB, alle nachfolgenden Updates erfolgen drahtlos – eine äußerst wichtige Funktion für Feldgeräte. Auf dem I2C-LCD-Display werden der WLAN-Status, die IP-Adresse, der Hostname, der Fortschrit

ESP32: Echtzeit aus dem Internet per NTP
Der ESP32 verbindet sich mit dem WLAN und ruft die Echtzeit mit Millisekunden-Genauigkeit von NTP-Servern (Network Time Protocol) ab. Dank configTzTime() wird die türkische Zeitzone (TRT, UTC+3) automatisch angewendet, und der DST-Wechsel wird per POSIX-TZ-String verwaltet. Die Uhrzeit wird auf dem Serial Monitor und einem optionalen I2C-LCD ausgegeben. configTzTime() startet den SNTP-Dienst; bei verfügbarem Netzwerkzugang erfolgt regelmäßig eine erneute Synchronisierung, und zwischen den Sy
Weitere Artikel
BlueGrays-App
Diese Schaltung Schritt für Schritt auf dem Handy nachbauen
Schaltplan, Code und Teileliste auf einem Bildschirm. Über 200 Arduino-, ESP32- und Pico-Projekte zum Nachbauen.
Mit dem Handy scannen
Richte die Kamera auf den Code, dann öffnet sich der App-Store deines Handys.
oder im Store öffnen

