WebSocket ile Çoklu İstemci Senkronize Canlı Kontrol Paneli
Proje Amacı
Birden fazla tarayıcının aynı anda açık kalıcı bir WebSocket bağlantısıyla bir RGB LED'in rengini canlı kontrol edip, bir istemcideki değişikliğin tüm diğer istemcilerde anında (polling olmadan) görünmesini sağlamayı öğretir. ESP32'nin ikinci çekirdeği, her biri açık kalan onlarca WebSocket bağlantısını sensör okuma döngüsünü hiç bloklamadan yönetebilecek yeterli RAM ve async ağ yığınına sahiptir. Bu eşzamanlı bağlantı yönetimi Arduino Uno'da kurulamaz: WiFi donanımı yoktur; olsaydı bile kalıcı, çift yönlü (full-duplex) birden fazla soket bağlantısını aynı anda açık tutmak 2KB RAM'i fazlasıyla aşar.
Gerekli Malzemeler
- ESP32 DevKit V1 (38 pin)
- RGB LED (ortak katot)
- 3x 330Ω direnç
- Breadboard
- Jumper kablolar
Gerekli Kütüphaneler
ESPAsyncWebServer + AsyncTCP (me-no-dev, GitHub'dan zip kurulum — bkz. proje #06). AsyncWebSocket sınıfı ESPAsyncWebServer paketiyle birlikte gelir, ayrı kurulum gerekmez.
Pin Bağlantıları
| Bileşen Pini | Bağlantı |
|---|---|
| RGB LED Kırmızı (330Ω üzerinden) | GPIO4 |
| RGB LED Yeşil (330Ω üzerinden) | GPIO16 |
| RGB LED Mavi (330Ω üzerinden) | GPIO17 |
| RGB LED Katot (ortak) | GND |
Devre Şeması
ESP32 Kodu
| 1 | #include <WiFi.h> |
| 2 | #include <ESPAsyncWebServer.h> |
| 3 | #include <AsyncWebSocket.h> |
| 4 | |
| 5 | const char* ssid = "WIFI_ADINIZ"; |
| 6 | const char* password = "WIFI_SIFRENIZ"; |
| 7 | const int PIN_R = 4, PIN_G = 16, PIN_B = 17; |
| 8 | |
| 9 | AsyncWebServer server(80); |
| 10 | AsyncWebSocket soket("/ws"); |
| 11 | |
| 12 | const char anaSayfaHtml[] PROGMEM = R"HTML( |
| 13 | <!DOCTYPE html><html><body style="font-family:sans-serif;text-align:center;padding-top:30px"> |
| 14 | <h2>Canlı RGB Kontrol Paneli</h2> |
| 15 | <input type="color" id="renk" value="#00d4ff" style="width:120px;height:60px"> |
| 16 | <script> |
| 17 | var ws = new WebSocket('ws://' + location.host + '/ws'); |
| 18 | document.getElementById('renk').oninput = function(e) { |
| 19 | ws.send(e.target.value); // örn: "#ff0000" |
| 20 | }; |
| 21 | ws.onmessage = function(e) { |
| 22 | document.getElementById('renk').value = e.data; // başka istemciden gelen değişikliği yansıt |
| 23 | }; |
| 24 | </script></body></html> |
| 25 | )HTML"; |
| 26 | |
| 27 | void renkUygula(String hex) { |
| 28 | long deger = strtol(hex.substring(1).c_str(), NULL, 16); |
| 29 | int kirmizi = (deger >> 16) & 0xFF, yesil = (deger >> 8) & 0xFF, mavi = deger & 0xFF; |
| 30 | analogWrite(PIN_R, kirmizi); // ESP32 core >= 3.0'da analogWrite PWM'i otomatik yönetir |
| 31 | analogWrite(PIN_G, yesil); |
| 32 | analogWrite(PIN_B, mavi); |
| 33 | } |
| 34 | |
| 35 | void soketOlay(AsyncWebSocket* sunucu, AsyncWebSocketClient* istemci, AwsEventType tur, |
| 36 | void* arg, uint8_t* veri, size_t uzunluk) { |
| 37 | if (tur == WS_EVT_DATA) { |
| 38 | String hex = String((char*)veri).substring(0, uzunluk); |
| 39 | renkUygula(hex); |
| 40 | // KRİTİK: gelen değişikliği GÖNDEREN HARİÇ tüm diğer istemcilere anında |
| 41 | // yay — senkronizasyonun kalbi burasıdır. |
| 42 | sunucu->textAll(hex); |
| 43 | } |
| 44 | } |
| 45 | |
| 46 | void setup() { |
| 47 | Serial.begin(115200); |
| 48 | pinMode(PIN_R, OUTPUT); pinMode(PIN_G, OUTPUT); pinMode(PIN_B, OUTPUT); |
| 49 | |
| 50 | WiFi.begin(ssid, password); |
| 51 | while (WiFi.status() != WL_CONNECTED) { delay(400); Serial.print("."); } |
| 52 | Serial.println("\nBağlandı: " + WiFi.localIP().toString()); |
| 53 | |
| 54 | soket.onEvent(soketOlay); |
| 55 | server.addHandler(&soket); |
| 56 | server.on("/", [](AsyncWebServerRequest* istek) { |
| 57 | istek->send_P(200, "text/html", anaSayfaHtml); |
| 58 | }); |
| 59 | server.begin(); |
| 60 | } |
| 61 | |
| 62 | void loop() { |
| 63 | soket.cleanupClients(); // kopan bağlantıları düzenli temizler, bellek sızıntısını önler |
| 64 | delay(1000); |
| 65 | } |
Çalışma Açıklaması
soket.cleanupClients() düzenli çağrılmazsa, tarayıcı sekmesi kapatılıp da düzgün 'disconnect' göndermeyen istemcilerin bıraktığı bağlantı nesneleri bellekte birikip zamanla cihazı yavaşlatabilir veya çökertebilir. sunucu->textAll(hex) ile tüm istemcilere (gönderen dahil) yayın yapan bu basit yaklaşım tercih edilmiştir çünkü tarayıcı zaten kendi input değerini biliyor, kendine geri gelen mesaj bir sorun yaratmaz.
Test Bilgisi
Kodu yükleyin, IP adresini tarayıcıda açın (birden fazla sekmede veya farklı cihazlarda). Bir sekmedeki renk seçiciyi değiştirin; RGB LED anında değişmeli ve açık olan diğer tüm sekmelerdeki renk seçici de otomatik güncellenmeli — bu, klasik 'sayfayı yenile' (polling) yaklaşımından temel farkıdır ve birden fazla cihazla test edildiğinde çok daha etkileyici gözlemlenir.
Olası Sorunlar ve Çözümleri
WebSocket bağlantısı hiç kurulamıyorsa (tarayıcı konsolunda hata): 'ws://' önekinin (wss:// değil, bu proje TLS kullanmıyor) doğru yazıldığından ve location.host'un doğru IP:port ürettiğinden emin olun. Renk değişimi gecikmeli veya takılarak geliyorsa: aynı anda çok fazla istemci bağlıysa (10'dan fazla) ESP32'nin RAM'i zorlanabilir, gerçekçi bir üst sınır (örn. 5-6 istemci) koyun. analogWrite derleme hatası veriyorsa: kullandığınız ESP32 Arduino core sürümünü kontrol edin (core >= 3.0'da yerleşiktir, daha eski sürümlerde ledcWrite + ledcAttachPin API'si kullanılmalıdır).
Alternatif Çözümler
Renk yayınına ek olarak bir 'canlı sıcaklık grafiği' yayınlanarak sensör verisi de tüm istemcilere anlık push edilebilir. Kimlik doğrulama olmadan herkesin kontrol edebildiği bu basit modele, bağlantı anında bir token veya parola kontrolü eklenerek yetkisiz erişim engellenebilir. textAll yerine binary WebSocket mesajları kullanılarak (örn. RGB için 3 byte) veri boyutu ve gecikme daha da azaltılabilir.
Benzer Projeler
FreeRTOS Dual-Core Görev Paylaşımlı Veri Toplama
xTaskCreatePinnedToCore() ile iki farklı sensör okuma görevini ESP32'nin iki fiziksel çekirdeğine (Core 0 ve Core 1) sabitleyerek tamamen paralel çalıştırmayı öğretir — biri yavaş ve bloklayıcı DHT22'yi okurken diğeri hızlı analog LDR okumaya kesintisiz devam eder, ikisi birbirini beklemez. ESP32, iki bağımsız Xtensa LX6 çekirdeğine sahiptir; Arduino sketch'i normalde sadece Core 1'de çalışır, Core 0 ise bilinçli olarak görev atanarak devreye alınabilir. Bu paralellik Arduino Uno'da kurulamaz: ATmega328P tek çekirdekli 8-bit bir mikrodenetleyicidir, ikinci bir çekirdeğe görev atamak donanımsal olarak mümkün değildir ve kart FreeRTOS gibi bir RTOS da çalıştıramaz.
ESPAsyncWebServer ile Bloklamayan Çoklu İstemci REST API
Birden fazla tarayıcı veya istemcinin aynı anda, birbirini bloklamadan bağlanıp iki rölenin durumunu okuyup değiştirebildiği asenkron (event-driven) bir REST API sunmayı öğretir — akıllı ev panelleri ve endüstriyel kontrol arayüzlerinde tercih edilen mimarinin küçük ölçekli bir örneğidir. ESPAsyncWebServer, gelen her isteği ayrı ayrı bekletmeden (non-blocking) LwIP ağ yığınıyla doğrudan çalışır; bunun için ESP32'nin ikinci çekirdeği ve yeterli RAM'i (520KB SRAM) devreye girer. Bu mimari Arduino Uno'da kurulamaz: WiFi donanımı yoktur; harici bir WiFi shield eklense bile klasik senkron 'WiFiServer' modeli her isteği sırayla işler ve 2KB RAM, birden fazla eşzamanlı bağlantı arabelleğini aynı anda tutmaya yetmez.
Şifreli BLE ile Kimlik Doğrulamalı Akıllı Kilit
BLE (Bluetooth Low Energy) bonding/pairing ile bir telefonun kimliğini bir kez eşleştirip, sonraki bağlantılarda ESP32'nin donanımsal AES şifrelemesiyle korunan bir kanaldan 'kilidi aç' komutunu doğrulamayı ve bir servo ile fiziksel kilidi açmayı öğretir. ESP32'nin dahili BLE radyosu, Secure Connections (LE Secure Connections) ile şifreli eşleştirme yapabilecek donanımsal kripto desteğine sahiptir. Bu güvenlik düzeyi Arduino Uno'da kurulamaz: kart hiçbir Bluetooth radyosu (Classic veya BLE) içermez ve kripto hızlandırma donanımı da yoktur; harici bir BLE modülü (HM-10 gibi) eklense bile bu modüller genelde bonding/güvenli eşleştirmeyi tam desteklemez.