ESP32 Async Web Server: WebSocket ile Canlı Kontrol Paneli
ESP32 async web server ve WebSocket ile sayfa yenilemeden röle kontrolü ve canlı sensör paneli: kurulum, PROGMEM ve LittleFS, esp32.local, parola, sık hatalar.
ESP32 async web server, yani ESPAsyncWebServer kütüphanesi, ESP32'yi aynı anda birden çok telefona ve bilgisayara yanıt verebilen hızlı bir web sunucusuna çevirir. Kütüphanenin içinde gelen WebSocket desteğiyle tarayıcı ile kart arasında sürekli açık bir hat kurulur: sayfadaki düğmeye bastığında röle anında çeker, sıcaklık değiştiğinde sayfadaki rakam kendiliğinden güncellenir ve bunun için sayfayı yenilemen gerekmez. Bir telefonda yaptığın değişikliği aynı paneli açmış diğer cihazlar da aynı saniyede görür. Kısacası bu ikili, kendi akıllı ev panelini bulut hizmeti kullanmadan, ev ağının içinde kurmanın en pratik yoludur.
Bu rehberde önce asenkron sunucunun klasik WebServer kütüphanesinden farkını ve WebSocket'in HTTP isteklerinden neden daha hızlı olduğunu göreceksin. Ardından doğru kütüphaneleri kurup en basit sunucuyu çalıştıracak, sonra bir röle ve DHT22 sensörüyle gerçek zamanlı bir kontrol paneli yapacaksın. Devamında HTML'i PROGMEM ya da LittleFS içinde tutmayı, IP adresi yerine esp32.local adresini kullanmayı, birden çok istemciyi yönetmeyi, panele basit bir parola koymayı ve en sık karşılaşılan derleme ve bağlantı hatalarının çözümlerini bulacaksın. Kodlar, Arduino IDE'deki ESP32 çekirdeğinin 2.x ve 3.x sürümleriyle çalışacak şekilde yazıldı.

ESP32 Async Web Server nedir, nasıl çalışır?
ESP32 çekirdeğiyle birlikte gelen WebServer.h kütüphanesi senkron çalışır: gelen istekler ancak loop() içinde server.handleClient() çağrıldığında, sırayla ve tek tek işlenir. loop() içinde 2 saniyelik bir delay() varsa sayfa da 2 saniye bekler; iki kişi aynı anda bağlanırsa biri diğerinin bitmesini bekler. ESPAsyncWebServer ise AsyncTCP kütüphanesinin kurduğu ayrı bir FreeRTOS görevinde (async_tcp) çalışır. Bir istek geldiğinde başlıklar arka planda okunur, istek hazır olunca senin yazdığın fonksiyon (callback) çağrılır ve yanıt yine arka planda gönderilir. Bu sırada loop() kendi işine devam eder. Böylece aynı anda birkaç bağlantı açık kalabilir; WebSocket ve Server-Sent Events (SSE) gibi sürekli açık bağlantılar da mümkün olur. ESP32'nin iki çekirdeğini ve görevleri merak ediyorsan ESP32 FreeRTOS yazısına göz atabilirsin.
| Özellik | WebServer.h (senkron) | ESPAsyncWebServer (asenkron) |
|---|---|---|
| Kurulum | ESP32 çekirdeğiyle hazır gelir | Kütüphane Yöneticisi'nden 2 kütüphane |
| İsteklerin işlenmesi | loop() içinde handleClient() ile, sırayla | Arka plandaki async_tcp görevinde, aynı anda birden çok |
| loop() içinde delay() | Sayfa yanıtını geciktirir | Sunucuyu etkilemez |
| WebSocket | Yok; ayrı bir kütüphane gerekir (ör. Markus Sattler'ın WebSockets kütüphanesi) | Yerleşik (AsyncWebSocket) |
| Server-Sent Events | Yok | Yerleşik (AsyncEventSource) |
| Statik dosya sunma | serveStatic ile | serveStatic, otomatik .gz desteği ve önbellek ayarı |
| Dikkat edilecek nokta | Öğrenmesi en kolay olanı | Callback içinde delay() ve uzun işlem yok |
WebSocket nedir? HTTP isteğinden farkı ne?
Normal bir HTTP isteğinde tarayıcı bir bağlantı açar, "bana bu sayfayı ver" der, yanıtı alır ve konuşma biter. Sunucu kendiliğinden tarayıcıya bir şey söyleyemez; yeni bir değer görmek için tarayıcının tekrar sorması gerekir. WebSocket (RFC 6455) ise HTTP ile başlayan bir el sıkışmadır: tarayıcı isteğine Upgrade: websocket başlığını ekler, ESP32 101 Switching Protocols yanıtıyla kabul eder ve aynı TCP bağlantısı açık kalır. Artık iki taraf da istediği an mesaj gönderebilir (full-duplex). Her mesajın ek yükü yalnızca 2 ila 14 bayttır; oysa her HTTP isteği yüzlerce baytlık başlık taşır. Bu yüzden WebSocket mesajları milisaniyeler içinde ulaşır ve saniyede birkaç güncelleme gönderen paneller için en verimli yöntemdir. Sayfayı sensör değerleriyle güncel tutmanın dört yolu şöyle karşılaştırılır:
| Yöntem | Nasıl çalışır? | Gecikme ve yük | Ne zaman kullanılır? |
|---|---|---|---|
| Sayfa yenileme (meta refresh) | Tarayıcı tüm sayfayı birkaç saniyede bir baştan indirir | Yüksek; sayfa her seferinde göz kırpar | Tercih etme |
| fetch ile yoklama (polling) | JavaScript her 1–2 saniyede bir küçük bir JSON ister | Orta; her istek yeni başlıklar taşır | Seyrek değişen tek bir değer |
| Server-Sent Events (SSE) | Sunucu açık bir bağlantı üzerinden tek yönlü olay yollar | Düşük; yalnızca sunucudan tarayıcıya | Yalnızca izleme yapan paneller |
| WebSocket | Açık bağlantı üzerinden iki yönlü mesajlaşma | En düşük; birkaç baytlık çerçeveler | Düğme, kaydırıcı ve canlı veri bir arada |
Gerekli kütüphaneler: ESPAsyncWebServer ve AsyncTCP kurulumu
- Arduino IDE'de ESP32 kart paketinin (Espressif Systems) kurulu olduğundan emin ol; kurulum adımları ESP32 Nedir? yazısında.
- Kütüphane Yöneticisi'ni aç (Ctrl+Shift+I) ve ESPAsyncWebServer ara. Yazarı ESP32Async olan ESP Async WebServer kütüphanesini kur.
- Bu kütüphane bağımlılığını Kütüphane Yöneticisi'ne bildirmediği için IDE ek kütüphane sormaz: yine ESP32Async'in yayımladığı Async TCP kütüphanesini ayrıca kur. ESP8266 kullanıyorsan bunun yerine ESP32Async'in ESP Async TCP kütüphanesi (ESPAsyncTCP.h) gerekir.
- Panel örneği için Adafruit'in DHT sensor library kütüphanesini ve istediği Adafruit Unified Sensor bağımlılığını kur. Kütüphane kurmayı ilk kez yapıyorsan Arduino'ya kütüphane ekleme rehberine göz at.
- Belgeler/Arduino/libraries klasöründe eskiden ZIP ile eklenmiş ESPAsyncWebServer-master, AsyncTCP-master ya da ESPAsyncWebSrv klasörleri varsa sil. Aynı kütüphanenin iki kopyası, derleyicinin yanlış olanı seçmesine yol açar.
İlk adım: AsyncWebServer ile en basit sunucu
Panele geçmeden önce kütüphanenin mantığını küçük bir örnekle görelim. Aşağıdaki kod ESP32'yi ev ağına bağlar, ana sayfada bir karşılama yazısı gösterir ve /led?durum=1 ile /led?durum=0 adresleriyle dahili LED'i yakıp söndürür. Kodu yükle, Seri Monitör'ü 115200 baud hızında aç ve görünen adresi aynı Wi-Fi'ye bağlı telefonunun tarayıcısına yaz. Kart ağa bağlanmıyorsa ESP32 WiFi bağlanmıyor rehberindeki teşhis kodunu kullan.
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}| Komut | Ne işe yarar? |
|---|---|
| AsyncWebServer server(80) | 80 numaralı HTTP portunu dinleyen sunucuyu oluşturur |
| server.on("/led", HTTP_GET, ...) | Bir adres ve istek türü için çalışacak fonksiyonu (callback) kaydeder |
| request->hasParam("durum") | Adreste ?durum=... parametresi olup olmadığına bakar |
| request->getParam("durum")->value() | Parametrenin değerini String olarak verir |
| request->send(kod, tür, içerik) | Yanıtı gönderir: 200 başarılı, 404 bulunamadı; tür text/plain, text/html ya da application/json |
| server.onNotFound(...) | Tanımlanmamış her adres için çalışır |
| server.begin() | Sunucuyu başlatır; tüm server.on() satırlarından sonra çağrılır |
Kontrol paneli için malzemeler ve bağlantı
Panel örneğinde bir röle modülü ile bir lambayı ya da fanı açıp kapatacak, DHT22 ile sıcaklık ve nemi ölçeceksin. Gerekenler: bir ESP32 DevKit, tek kanallı 5 V röle modülü, DHT22 (ya da DHT11) sensör modülü, breadboard ve jumper kablolar. Röle seçerken optokuplörlü modülleri tercih et. DHT22, −40 ile 80 °C arasında ±0,5 °C doğrulukla ölçer ve en fazla 2 saniyede bir okunabilir; DHT11 daha ucuzdur ama 0–50 °C aralığında ±2 °C doğruluk verir. Sensörün ayrıntıları için DHT11 sıcaklık ve nem sensörü yazısına, kartının pin haritası için Pinout aracına bakabilirsin.
| Modül pini | ESP32 DevKit | ESP8266 NodeMCU | Not |
|---|---|---|---|
| Röle VCC | VIN (5 V) | VIN ya da VU (5 V) | Röle bobini 5 V ister; USB'den beslenir |
| Röle GND | GND | GND | Ortak toprak |
| Röle IN | GPIO 26 | D1 (GPIO 5) | Çoğu modül LOW sinyalle çeker |
| DHT22 VCC (+) | 3V3 | 3V3 | 3,3 V ile beslenince veri hattı da 3,3 V olur |
| DHT22 DATA (OUT) | GPIO 4 | D2 (GPIO 4) | Çıplak sensörde DATA ile VCC arasına 10 kΩ direnç |
| DHT22 GND (−) | GND | GND | Ortak toprak |
Gerçek zamanlı kontrol paneli: röle, DHT22 ve WebSocket
Şimdi asıl projeye geçelim. Aşağıdaki kod tek bir dosyadır: HTML, CSS ve JavaScript, PROGMEM dizisi olarak kodun içindedir. Sayfa açıldığında tarayıcı /ws adresine bir WebSocket bağlantısı kurar. ESP32 her 2 saniyede bir DHT22'yi okur ve sıcaklık, nem, röle durumu ile bağlı cihaz sayısını tek bir JSON mesajıyla bütün tarayıcılara gönderir. Düğmeye bastığında tarayıcı toggle mesajını yollar, ESP32 röleyi çevirir ve yeni durumu yine herkese bildirir. Ağ adını ve şifreni değiştirip yükle, ardından tarayıcıda http://esp32.local ya da Seri Monitör'deki IP adresini aç.
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}Kod nasıl çalışıyor?
- AsyncWebSocket ws("/ws") WebSocket uç noktasını oluşturur, server.addHandler(&ws) ise onu sunucuya bağlar. JavaScript'teki adres ile buradaki yol birebir aynı olmalı.
- wsOlay() fonksiyonu her bağlantıda, kopmada ve gelen mesajda çağrılır. WS_EVT_CONNECT yeni bir istemci, WS_EVT_DISCONNECT ayrılan bir istemci, WS_EVT_DATA ise gelen bir mesaj demektir.
- AwsFrameInfo, gelen çerçevenin bilgisini taşır. final, index == 0 ve len kontrolü mesajın tek parça ve eksiksiz geldiğini, WS_TEXT ise metin olduğunu doğrular. Kısa komutlar her zaman tek çerçeveye sığar.
- Gelen veri memcmp() ile uzunluğuyla birlikte karşılaştırılır; böylece verinin sonunda sıfır karakteri olup olmamasına bağımlı kalmazsın.
- Callback fonksiyonları async_tcp görevinde çalıştığı için yalnızca röleyi çevirip gonder bayrağını kaldırır. Asıl gönderim loop() içinde ws.textAll() ile yapılır; böylece tüm mesajlar tek bir yerden, sırayla çıkar.
- ws.cleanupClients() saniyede bir çağrılır: kopmuş bağlantıların belleğini geri verir ve istemci sayısı sınırı aştığında en eski bağlantıyı kapatır.
- JavaScript tarafında onclose olayı 2 saniye sonra yeniden bağlanır. ESP32 yeniden başlasa ya da Wi-Fi bir anlığına kopsa bile sayfa kendini toparlar. Adres location.host ile oluşturulduğu için sayfa IP ile de esp32.local ile de çalışır.
- WiFi.setSleep(false), Wi-Fi güç tasarrufunu kapatır. Varsayılan modem uykusu tepkilere yüzlerce milisaniyeye varan gecikme ekleyebilir; prizden beslenen bir panelde bunu kapatmak düğmeleri anlık hale getirir.
Mesaj biçimi olarak JSON seçmek işini kolaylaştırır: tarayıcı JSON.parse() ile gelen metni tek satırda nesneye çevirir. Örnek bir mesaj şöyle görünür: {"role":true,"sicaklik":23.4,"nem":48,"istemci":2}. Sensör okunamadığında değer null gönderilir ve sayfa "--" gösterir; okuma hatası NaN olarak gitseydi geçersiz JSON yüzünden sayfa hiç güncellenmezdi. Mesaj başına 60 bayt civarında veri gittiği için saniyede birkaç güncelleme bile ağı yormaz. Daha karmaşık mesajlar için ArduinoJson kütüphanesine geçebilirsin, ama bu boyutta bir panelde String birleştirmek fazlasıyla yeterlidir. Röle yerine bir LED ya da buzzer bağlayarak aynı kodu hiç değiştirmeden deneyebilirsin.
HTML'i nerede tutmalı: PROGMEM mi, LittleFS mi?
Panel kodunda sayfa, R"rawliteral( ... )rawliteral" biçiminde ham bir metin dizisi olarak duruyor. Bu yazım, tırnakları ve satır sonlarını kaçış karakteri yazmadan kullanmanı sağlar. AVR tabanlı Arduino'larda PROGMEM, metni RAM yerine flaşta tutmak için gereklidir; ESP32'de ise sabit diziler zaten flaştan okunduğu için PROGMEM yalnızca uyumluluk için yazılır ve zararı yoktur. Sayfa büyüdükçe kodun içinde HTML düzenlemek zorlaşır; o noktada dosyaları flaştaki LittleFS dosya sistemine taşımak daha düzenlidir.
| Ölçüt | PROGMEM (kodun içinde) | LittleFS (dosya sistemi) |
|---|---|---|
| Kurulum | Ek adım yok, tek dosya | data klasörü ve yükleme eklentisi gerekir |
| Düzenleme | HTML, C++ dizisinin içinde | Ayrı index.html, style.css ve app.js dosyaları |
| Güncelleme | Her değişiklikte kodu yeniden yüklersin | Yalnızca dosya sistemini yeniden yüklersin |
| Boyut | Birkaç KB'lık sayfalar için ideal | Varsayılan bölümlemede yaklaşık 1,5 MB alan |
| Görsel ve yazı tipi | Pratik değil | Simge, resim ve .gz dosyaları sunulabilir |
| Ne zaman? | İlk deneme ve küçük paneller | Çok sayfalı ya da büyüyen arayüzler |
LittleFS ile HTML, CSS ve JavaScript dosyalarını sunmak
- Proje klasörünün (.ino dosyasının bulunduğu klasör) içinde data adlı bir klasör oluştur ve panel kodunda R"rawliteral( ile )rawliteral" arasında kalan HTML'i data/index.html dosyasına kaydet. İstersen CSS ve JavaScript'i ayrı dosyalara bölebilirsin.
- Arduino IDE 2, eski sürümdeki ESP32 Sketch Data Upload menüsünü desteklemez. Bunun yerine arduino-littlefs-upload eklentisinin .vsix dosyasını indir; Windows'ta C:\Users\(kullanıcı adın)\.arduinoIDE\plugins, macOS ve Linux'ta ~/.arduinoIDE/plugins klasörüne koy (klasör yoksa oluştur) ve IDE'yi yeniden başlat. Eklenti için IDE 2.2.1 ya da daha yeni bir sürüm gerekir.
- Seri Monitör açıksa kapat, sonra Ctrl+Shift+P (macOS'ta Cmd+Shift+P) ile komut paletini aç ve Upload LittleFS to Pico/ESP8266/ESP32 komutunu seç. Dosyalar flaştaki dosya sistemi bölümüne yazılır.
- Araçlar > Partition Scheme menüsünde dosya sistemi bölümü olan bir şema seçili olsun (örneğin Default 4MB with spiffs). LittleFS, adı spiffs olan bu bölümü kullanır.
- Aşağıdaki kodu her zamanki gibi yükle. Araçlar menüsündeki Erase All Flash seçeneği kapalıysa kod yüklemek dosya sistemini silmez; dosyaları değiştirdiğinde yalnızca 3. adımı tekrarlaman yeter.
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: IP adresi yerine esp32.local
Modem ESP32'ye her açılışta farklı bir IP adresi verebilir ve 192.168.1.47 gibi sayıları akılda tutmak zordur. mDNS (multicast DNS), cihazın yerel ağda kendi adını duyurmasını sağlar. Panel kodundaki MDNS.begin("esp32") satırı sayesinde aynı ağdaki cihazlar sayfayı http://esp32.local adresinden açabilir; MDNS.addService("http", "tcp", 80) ise ağ tarayıcı uygulamalarına burada bir web sunucusu olduğunu söyler. Ada ".local" ekleme; yalnızca küçük harf, rakam ve tire kullan. Birden çok ESP32 kullanıyorsan her birine farklı bir ad ver (mutfak, salon gibi), çünkü aynı adı taşıyan iki cihaz birbirini bozar. mDNS her sistemde aynı ölçüde desteklenmez:
| Sistem | .local desteği | Çalışmazsa |
|---|---|---|
| iPhone, iPad, macOS | Yerleşik (Bonjour) | Aynı Wi-Fi ağında olduğundan emin ol |
| Windows 10 ve 11 | Güncel sürümlerde genellikle çalışır | Bonjour hizmetini kur ya da IP kullan |
| Linux | avahi-daemon kuruluysa çalışır | avahi-daemon ve nss-mdns paketlerini kur |
| Android | Sürüme ve tarayıcıya göre değişir | IP adresini kullan ya da modemde DHCP rezervasyonu yap |
Birden çok istemci: telefon, tablet ve bilgisayar aynı anda
WebSocket'in en sevilen yanı, paneli açan herkesin aynı durumu görmesidir. Telefonundan röleyi açtığında ESP32 yeni durumu textAll() ile bütün bağlantılara gönderir; bilgisayardaki sekme de aynı anda yeşile döner. Her sekme ayrı bir istemcidir: aynı tarayıcıda üç sekme açarsan sayaç 3 gösterir. Kütüphane, ESP32'de varsayılan olarak 8, ESP8266'da 4 eşzamanlı WebSocket istemcisine izin verir (DEFAULT_MAX_WS_CLIENTS). Bu sınır kendiliğinden uygulanmaz; cleanupClients() çağrıldığında sınırı aşan en eski bağlantı kapatılır. Bunun yanında dikkat etmen gerekenler şunlar:
- Telefon uykuya geçince: Ekranı kapanan telefon bağlantıyı düzgün kapatmayabilir. Bu tür yarım kalmış bağlantılar, cleanupClients() ve düzenli veri gönderimi sayesinde zamanla temizlenir.
- Gönderim hızı: Her istemci için en fazla 32 mesaj kuyrukta bekleyebilir (ESP8266'da 8). Her loop() turunda textAll() çağırırsan kuyruk dolar ve yeni mesajlar sessizce atılır; Araçlar > Core Debug Level ayarını Warn ya da daha ayrıntılı bir seviyeye alırsan Seri Monitör'de Too many messages queued uyarısını görürsün. Değeri yalnızca değiştiğinde ya da saniyede birkaç kez gönder.
- Yeni gelen istemci: Bağlanan her sayfaya güncel durumu hemen yolla; yoksa kullanıcı ilk ölçüme kadar boş bir panele bakar. Panel kodunda bu işi WS_EVT_CONNECT içinde kaldırılan bayrak yapar.
- Tek bir istemciye yanıt: Herkese değil yalnızca soran tarayıcıya cevap vermek için client->text("...") kullan; istemcinin numarasını client->id() verir.
- Bellek: Her açık bağlantı bir TCP soketi ve tampon bellek tutar. ESP32'nin 520 KB RAM'i birkaç istemci için fazlasıyla yeterlidir, ama ESP.getFreeHeap() ile boş belleği ara sıra yazdırmak sızıntıları erkenden yakalamanı sağlar.
Basit parola koruması
Ev ağındaki herkes panelin adresini bilirse röleyi açıp kapatabilir. Kütüphanedeki request->authenticate() ve request->requestAuthentication() fonksiyonlarıyla sayfaya kullanıcı adı ve parola koyabilirsin; tarayıcı kendi giriş penceresini açar ve bilgiyi oturum boyunca hatırlar. Varsayılan yöntem Digest kimlik doğrulamadır, yani parola ağda açık metin olarak gitmez. Ancak yalnızca sayfayı korumak yetmez: WebSocket adresi ayrı bir kapıdır ve biri ws://ip/ws adresine doğrudan bağlanabilir. Aşağıdaki örnek bu kapıyı da kapatır: ESP32 her açılışta rastgele bir anahtar üretir, bu anahtarı yalnızca giriş yapmış tarayıcıya /anahtar adresinden verir ve setFilter() ile doğru anahtarı getirmeyen WebSocket bağlantılarını reddeder.
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}ESP8266 ve Raspberry Pi Pico W için notlar
Aynı kütüphane ESP8266 (NodeMCU, Wemos D1 mini) üzerinde de çalışır. Değiştirmen gerekenler şunlar: Async TCP yerine ESP32Async'in ESP Async TCP kütüphanesini kur; WiFi.h yerine ESP8266WiFi.h, AsyncTCP.h yerine ESPAsyncTCP.h, ESPmDNS.h yerine ESP8266mDNS.h ekle ve loop() içine MDNS.update() satırını koy. WiFi.setSleep(false) satırını WiFi.setSleepMode(WIFI_NONE_SLEEP) ile değiştir, pinleri de bağlantı tablosundaki NodeMCU sütununa göre ayarla. ESP8266'nın kullanılabilir RAM'i 80 KB civarında olduğu için sayfayı küçük tut ve istemci sınırının 4 olduğunu unutma. Raspberry Pi Pico W kullanıyorsan MicroPython ile web sunucu kurmayı Raspberry Pi Pico W Wi-Fi rehberinde bulabilirsin; ESPAsyncWebServer'ın yeni sürümleri RP2040 ve RP2350 yongalarını da Arduino tarafında destekler.
ESP32 web sunucusu ile proje fikirleri
- Çok kanallı akıllı priz: 4 kanallı bir röle kartıyla lamba, fan ve priz için ayrı düğmeler yap; komutları "r1", "r2" gibi kısa metinlerle ayır. Röle seçimi için röle modülü yazısına bak.
- Bitki sulama paneli: Toprak nem sensörü değerini canlı göster, sulama eşiğini sayfadaki kaydırıcıyla ayarla ve pompayı istersen elle de çalıştır.
- Hava istasyonu: DHT22 yerine BME280 kullanıp basıncı da ekle; son 60 ölçümü sayfada küçük bir çizgi grafikle göster.
- Renk seçici lamba: Sayfadaki renk seçicinin değerini WebSocket ile gönder ve WS2812B NeoPixel şeridini anında boya.
- Kaydırıcıyla servo: Kaydırıcının değerini her harekette gönderip bir servo motoru çevir; kamera yönlendirme ya da kapı mandalı için idealdir.
- Zaman damgalı kayıt: Ölçümlerin yanına NTP ile alınan saati ekle ve kodu her seferinde USB ile değil, OTA ile kablosuz güncelle.
Sık hatalar ve çözümleri
| Belirti | Sebep | Çözüm |
|---|---|---|
| 'mbedtls_md5_starts_ret' was not declared in this scope | Eski me-no-dev kütüphanesi ve ESP32 çekirdeği 3.x | ESP32Async'in ESP Async WebServer ve Async TCP sürümlerini kur, eski klasörleri sil |
| Multiple libraries were found for "AsyncTCP.h" | Aynı kütüphanenin iki kopyası var | libraries klasöründeki eski -master kopyalarını sil |
| send_P için deprecated uyarısı | Yeni sürümde send_P kaldırılıyor | request->send(200, "text/html", index_html) kullan |
| Sayfa hiç açılmıyor | Telefon misafir ağında ya da mobil veride, IP adresi değişmiş | Aynı Wi-Fi'ye bağlan, Seri Monitör'deki güncel IP'yi kullan |
| Sayfa açılıyor ama değerler -- kalıyor | WebSocket bağlanamadı: yol farklı ya da addHandler unutulmuş | F12 konsolunu aç, ws("/ws") ile JavaScript'teki adresi eşitle |
| Task watchdog got triggered ve async_tcp | Callback içinde delay(), uzun döngü ya da HTTP isteği | Callback'i kısa tut, işi bayrakla loop()'a taşı |
| Too many messages queued | textAll() çok sık çağrılıyor | Yalnızca değişince ya da saniyede birkaç kez gönder |
| LittleFS'teki sayfa 404 veriyor | data klasörü yüklenmemiş ya da setDefaultFile eksik | Eklentiyle dosyaları yükle, setDefaultFile("index.html") ekle |
| Seri Monitör'de does not exist, no permits for creation | İstenen dosya LittleFS'te yok | Dosya adını ve büyük-küçük harfleri kontrol et; .gz geçen satırlar zararsızdır |
| Röle hiç kapanmıyor | 5 V modül 3,3 V HIGH sinyalini tanımıyor | 3,3 V uyumlu modül, JD-VCC ya da transistör kullan |
| Röle çekince ESP32 resetleniyor | Bobin akımı beslemeyi çökertiyor (brownout) | Sağlam bir 5 V adaptör ve 470 µF kondansatör kullan |
| esp32.local açılmıyor | Cihazın mDNS desteği yok ya da ad .local ile yazılmış | MDNS.begin("esp32") yaz, gerekirse IP adresini kullan |
| Düğme geç tepki veriyor | Wi-Fi modem uykusu açık | Bağlantıdan sonra WiFi.setSleep(false) çağır |
ESP32 async web server ve WebSocket ikilisi, bulut hizmetine ya da telefon uygulaması yazmaya gerek kalmadan çalışan, hızlı ve birden çok cihazla paylaşılabilen bir kontrol paneli kurmanın en kısa yoludur. Önce tek dosyalık PROGMEM paneliyle başla, arayüz büyüdükçe LittleFS'e geç, ağına yeni cihazlar ekledikçe her birine kendi mDNS adını ver. ESP32 ile yeni tanışıyorsan temelleri ESP32 Nedir? yazısında bulabilirsin. BlueGrays uygulamasında da async web server, WebSocket sayacı, mDNS ile erişim, NTP saati ve OTA güncelleme gibi ESP32 projelerini devre şemaları ve hazır kodlarıyla adım adım inceleyebilirsin.
Sıkça sorulan sorular
ESP32 AsyncWebServer ile WebServer arasındaki fark nedir?
WebServer kütüphanesi istekleri loop() içindeki handleClient() çağrısıyla sırayla işler; loop() yavaşlarsa sayfa da yavaşlar. ESPAsyncWebServer ise istekleri arka plandaki ayrı bir görevde işler, aynı anda birden çok bağlantıyı kaldırır ve WebSocket ile SSE desteğini hazır getirir. Gerçek zamanlı paneller için asenkron sürüm daha uygundur.
ESP32 web sunucusuna aynı anda kaç cihaz bağlanabilir?
ESPAsyncWebServer, ESP32'de varsayılan olarak 8 WebSocket istemcisine izin verir ve cleanupClients() çağrıldığında fazlasını kapatır; ESP8266'da bu sınır 4'tür. Normal sayfa istekleri kısa sürdüğü için daha fazla cihaz sırayla sayfa açabilir. Pratikte bir ev panelinde 3–5 eşzamanlı cihaz sorunsuz çalışır.
ESP32 web sunucusuna internetten erişebilir miyim?
Modemde port yönlendirme yaparak teknik olarak mümkündür, ama önerilmez: kütüphane ESP32'de HTTPS sunmaz ve cihaz internetten gelen saldırılara açık kalır. Dışarıdan kontrol için Telegram bot, MQTT tabanlı bir sistem ya da modemindeki VPN özelliği çok daha güvenlidir.
esp32.local adresi neden açılmıyor?
En sık sebep, telefonun ya da bilgisayarın mDNS adlarını çözememesidir; bu durum özellikle bazı Android telefonlarda ve eski Windows sürümlerinde görülür. Kodda MDNS.begin("esp32.local") yazmak da hatadır, adı .local olmadan vermelisin. Ad yine çözülmüyorsa Seri Monitör'deki IP adresini kullan ya da modemden ESP32'ye sabit bir IP ayır.
ESP32 web sunucusu modem olmadan çalışır mı?
Evet. WiFi.softAP("ESP32-Panel", "12345678") ile ESP32 kendi Wi-Fi ağını kurar; telefonunu bu ağa bağlayıp tarayıcıda 192.168.4.1 adresini açarsın. Bu erişim noktası modunda internet yoktur, ama panel ve WebSocket aynen çalışır. Ağ şifresi en az 8 karakter olmalıdır.
ESPAsyncWebServer için hangi kütüphaneyi kurmalıyım?
Arduino Kütüphane Yöneticisi'nde yazarı ESP32Async olan ESP Async WebServer ve Async TCP kütüphanelerini kur. me-no-dev hesabındaki orijinal sürüm artık güncellenmiyor ve ESP32 çekirdeği 3.x ile derlenmiyor. ZIP ile eklenmiş eski kopyaları da libraries klasöründen sil.
Bu yazıdan sonra yapabileceğin projeler
Tüm projeleri gör
ESP32 Async Web Server (LCD'de IP Göstergeli)
Bu projede ESP32'yi aynı Wi-Fi ağındaki telefonunuzdan veya bilgisayarınızdan kontrol edebileceğiniz asenkron bir web sunucusuna dönüştürüyoruz. ESPAsyncWebServer kütüphanesi sayesinde sunucu birden fazla isteği aynı anda işleyebiliyor (klasik WebServer.h gibi tek istekte bloklamıyor). Tarayıcıdan ESP32'nin IP adresine bağlanıp ON/OFF butonlarına basarak (/on ve /off adresleri) GPIO 5'e bağlı LED'i açıp kapatabileceksiniz.

ESP32 WebSocket Sayaç (2 Buton + LCD + Web Realtime)
Bu projede ESP32'ye bağlı iki butonla (GPIO 5 artırır, GPIO 4 azaltır) bir sayaç değiştirilir. Değer hem 16x2 I2C LCD'de hem de ESPAsyncWebServer'ın WebSocket desteğiyle bağlı tüm tarayıcılarda anında güncellenir; sayfayı yenilemek gerekmez. HTTP polling yerine tek bir kalıcı bağlantı kullanılır ve ESP32 yeni değeri ws.textAll() ile herkese gönderir.

ESP32 mDNS: bluegrays-esp.local ile Cihaza Erişim (LCD Ekranlı)
Bu projede ESP32'ye mDNS (Multicast DNS) servisi ekleyerek IP adresi yerine bluegrays-esp.local gibi okunabilir bir host adıyla erişim sağlıyoruz. DHCP'nin verdiği IP değişse bile cihaza her zaman aynı isimle ulaşabilirsiniz. Üzerinde küçük bir web server da çalıştırarak tarayıcıdan http://bluegrays-esp.local/ adresiyle LED kontrolü gönderiyoruz. I2C LCD ekranda hostname, IP ve LED durumu hem Türkçe hem İngilizce gösterilir. iOS, macOS ve Linux mDNS'i doğal olarak destekler; Windows'ta Bonjo

ESP32 Wi-Fi STA Bağlantısı (LCD Ekranlı)
Bu projede ESP32'yi Station (STA) modunda evdeki Wi-Fi ağına güvenli ve hata toleranslı şekilde bağlıyoruz. SSID/şifreyi secrets.h dosyasına ayırarak Git'e sızmasını önlüyor, otomatik yeniden bağlanma mekanizması kuruyor, RSSI sinyal seviyesini ve IP adresini I2C LCD ekranda gösteriyoruz. Dahili LED durum göstergesi olarak çalışır. Üretim seviyesi bir bağlantı yöneticisidir.

ESP32 DHT22 Sıcaklık ve Nem (LCD Ekranlı)
DHT22 sensörü ile ortam sıcaklığını ve nem oranını ölçüp 16x2 I2C LCD ekranda gerçek zamanlı olarak gösteren bir projedir. Adafruit DHT kütüphanesi kullanılarak tek kablo (one-wire) protokolü ile sensörden veri okunur. Bu proje, IoT hava istasyonu, akıllı ev otomasyonu veya seralarda ortam takip sistemlerinin temelini oluşturur. ESP32'nin GPIO 4 pini üzerinden DHT22 ile haberleşme sağlanır.

ESP32 Akıllı Aydınlatma (PIR + LDR + Röle + OLED)
ESP32, PIR hareket sensörü, LDR ışık sensörü ve röle modülü ile evinin koridorunu, tuvaletini veya kilerini akıllı aydınlatmaya dönüştürüyoruz. Klasik "karanlıkta hareket algılanınca ışık yansın" senaryosunu inşa ediyoruz — ortam aydınlıksa hareket olsa bile ışık yanmaz, böylece gündüz gereksiz yere yanmaz ve enerji tasarrufu sağlanır. Işık 30 saniye yanık kalır; hareket devam ederse timer otomatik sıfırlanır ve süre uzar. SSD1306 OLED ekranda büyük fontlarla durum (AÇIK / GÜNDÜZ / BEKLE), geri

ESP32 OTA Update — Kablosuz Firmware Yükleme (LCD Ekranlı)
Bu projede ESP32'ye USB kablosu olmadan, Wi-Fi üzerinden yeni firmware yüklemeyi öğreneceksin. ArduinoOTA kütüphanesi ile ESP32 ağda görünür hale gelir ve Arduino IDE'nin "Network port" sekmesinde bluegrays-ota olarak otomatik listelenir. İlk yükleme USB ile yapılır, sonrasında tüm güncellemeler kablosuz olur — saha cihazları için çok kritik bir özelliktir. I2C LCD ekranda WiFi durumu, IP, hostname, OTA yükleme yüzdesi ve hata mesajları hem Türkçe hem İngilizce gösterilir. Parola koruması, h

ESP32 NTP ile İnternetten Gerçek Zaman
ESP32, Wi-Fi'a bağlanıp NTP (Network Time Protocol) sunucularından milisaniye hassasiyetiyle gerçek zamanı çeker. configTzTime() fonksiyonu sayesinde Türkiye saat dilimi (TRT, UTC+3) otomatik uygulanır ve yaz/kış saati uygulayan ülkelerde DST geçişi POSIX TZ string'i ile otomatik yönetilir. Saat hem Serial Monitor'e hem de opsiyonel I2C LCD'ye yazdırılır. configTzTime() SNTP hizmetini başlatır; ağ erişimi varken periyodik yeniden senkronizasyon yapılır ve iki senkronizasyon arasında sistem s
Diğer yazılar
BlueGrays uygulaması
Bu devreyi telefonunda adım adım kur
Devre şeması, kod ve parça listesi tek ekranda. 200'den fazla Arduino, ESP32 ve Pico projesi seni bekliyor.
Telefonunla tara
Kamerayı koda tut; telefonunun uygulama mağazası açılır.
ya da mağazada aç

