SD Kart + Bulut Senkronizasyonlu Kesintisiz Veri Kayıt Sistemi
Proje Amacı
Sensör verisini sürekli olarak SPI üzerinden bir SD karta yazıp, WiFi bağlantısı müsait olduğunda kaydedilmiş verileri toplu halde buluta senkronize etmeyi öğretir — bağlantı kesilse bile veri kaybı olmadan çalışan, sahadaki gerçek IoT sistemlerinde aranan bir kayıt mimarisidir. ESP32'nin iki çekirdeği, bir tarafta SD karta yazma (SPI, göreceli yavaş) işlemini yaparken diğer tarafta WiFi/HTTP senkronizasyonunu eşzamanlı ve birbirini bloklamadan yürütebilir. Bu mimari Arduino Uno'da kurulamaz: SD kartı SPI ile teorik olarak bağlamak mümkün olsa da WiFi donanımı olmadığından buluta senkronizasyon yapılamaz; ayrıca 2KB RAM, SD dosya sistemi arabellekleriyle birlikte bir ağ yığınını aynı anda çalıştırmaya yetmez.
Gerekli Malzemeler
- ESP32 DevKit V1 (38 pin)
- MicroSD kart modülü (SPI arayüzlü)
- MicroSD kart (FAT32 formatlı)
- DHT22 sıcaklık/nem sensörü
- 10kΩ direnç (pull-up)
- Breadboard
- Jumper kablolar
Gerekli Kütüphaneler
SD.h + SPI.h (ikisi de ESP32 core ile gelir). DHT sensor library (Adafruit, >= 1.4.4). HTTPClient.h (senkronizasyon için, ESP32 core ile gelir).
Pin Bağlantıları
| Bileşen Pini | Bağlantı |
|---|---|
| SD CS | GPIO5 |
| SD SCK | GPIO18 |
| SD MISO | GPIO19 |
| SD MOSI | GPIO23 |
| SD VCC | 5V (VIN) |
| SD GND | GND |
| DHT22 VCC | 3V3 |
| DHT22 GND | GND |
| DHT22 DATA (10kΩ pull-up ile 3V3'e) | GPIO27 |
Devre Şeması
ESP32 Kodu
| 1 | #include <SPI.h> |
| 2 | #include <SD.h> |
| 3 | #include <WiFi.h> |
| 4 | #include <HTTPClient.h> |
| 5 | #include <DHT.h> |
| 6 | |
| 7 | #define DHTPIN 27 |
| 8 | #define DHTTYPE DHT22 |
| 9 | #define SD_CS 5 |
| 10 | #define GUNLUK_DOSYA "/veri_gunlugu.csv" |
| 11 | |
| 12 | DHT dht(DHTPIN, DHTTYPE); |
| 13 | const char* ssid = "WIFI_ADINIZ"; |
| 14 | const char* password = "WIFI_SIFRENIZ"; |
| 15 | const char* sunucuUrl = "https://ornek-sunucu.com/api/veri"; |
| 16 | |
| 17 | void kaydiYazSD(float sicaklik, float nem) { |
| 18 | File dosya = SD.open(GUNLUK_DOSYA, FILE_APPEND); |
| 19 | if (dosya) { |
| 20 | dosya.printf("%lu,%.1f,%.1f,0\n", millis(), sicaklik, nem); // son alan: 0=senkronize edilmedi |
| 21 | dosya.close(); |
| 22 | } else { |
| 23 | Serial.println("SD karta yazılamadı!"); |
| 24 | } |
| 25 | } |
| 26 | |
| 27 | void bulutaSenkronizeEt() { |
| 28 | if (WiFi.status() != WL_CONNECTED) return; // bağlantı yoksa dene bile, sıradaki döngüde tekrar denenir |
| 29 | |
| 30 | File okuma = SD.open(GUNLUK_DOSYA, FILE_READ); |
| 31 | if (!okuma) return; |
| 32 | |
| 33 | int gonderilen = 0; |
| 34 | while (okuma.available()) { |
| 35 | String satir = okuma.readStringUntil('\n'); |
| 36 | if (satir.indexOf(",0") == satir.length() - 2) { // sonu ",0" ile bitenler henüz gönderilmemiş |
| 37 | HTTPClient http; |
| 38 | http.begin(sunucuUrl); |
| 39 | http.addHeader("Content-Type", "text/csv"); |
| 40 | int kod = http.POST(satir); |
| 41 | if (kod == 200) gonderilen++; |
| 42 | http.end(); |
| 43 | } |
| 44 | } |
| 45 | okuma.close(); |
| 46 | if (gonderilen > 0) Serial.printf("%d kayıt buluta senkronize edildi.\n", gonderilen); |
| 47 | // NOT: Üretim kodunda gönderilen satırları "1" olarak işaretlemek için |
| 48 | // dosyanın yeniden yazılması (veya ayrı bir "senkronize edildi" indeks |
| 49 | // dosyası tutulması) gerekir — bu iskelet basitlik için atlanmıştır. |
| 50 | } |
| 51 | |
| 52 | void setup() { |
| 53 | Serial.begin(115200); |
| 54 | dht.begin(); |
| 55 | |
| 56 | if (!SD.begin(SD_CS)) { |
| 57 | Serial.println("SD kart bulunamadı! Kayıt bu oturumda YAPILAMAYACAK."); |
| 58 | } else { |
| 59 | Serial.println("SD kart hazır."); |
| 60 | } |
| 61 | |
| 62 | WiFi.begin(ssid, password); |
| 63 | unsigned long baslangic = millis(); |
| 64 | while (WiFi.status() != WL_CONNECTED && millis() - baslangic < 8000) delay(300); |
| 65 | Serial.println(WiFi.status() == WL_CONNECTED ? "WiFi bağlı." : "WiFi yok, sadece yerel kayıt yapılacak."); |
| 66 | } |
| 67 | |
| 68 | void loop() { |
| 69 | float sicaklik = dht.readTemperature(); |
| 70 | float nem = dht.readHumidity(); |
| 71 | if (!isnan(sicaklik) && !isnan(nem)) { |
| 72 | kaydiYazSD(sicaklik, nem); // ÖNCE her zaman yerel SD karta yaz (asla veri kaybetme) |
| 73 | Serial.printf("Kaydedildi: %.1fC, %%%.1f\n", sicaklik, nem); |
| 74 | } |
| 75 | |
| 76 | bulutaSenkronizeEt(); // SONRA fırsat varsa buluta gönder |
| 77 | delay(5000); |
| 78 | } |
Çalışma Açıklaması
Mimarinin kritik prensibi 'önce yerel diske yaz, sonra fırsat buldukça buluta gönder' sırasıdır — asla tersi olmamalıdır, aksi halde WiFi kesintisinde veri tamamen kaybolur. Bu iskelet kod basitlik için gönderilen kayıtları dosyada işaretlemiyor (yorum satırında belirtildiği gibi); üretimde tekrar gönderimi önlemek için bu mutlaka eklenmelidir, aksi halde aynı veri buluta birden fazla kez gönderilebilir.
Test Bilgisi
WiFi router'ı kapatıp kodu çalıştırın; Seri Monitör'de 'WiFi yok, sadece yerel kayıt yapılacak' mesajını ve düzenli SD kayıt loglarını görün. SD kartı çıkarıp bilgisayarda /veri_gunlugu.csv dosyasını açıp verilerin doğru biçimde (zaman,sıcaklık,nem,0) yazıldığını doğrulayın. Router'ı tekrar açın; birkaç saniye içinde 'buluta senkronize edildi' mesajının göründüğünü doğrulayın (gerçek bir test sunucusu adresi girmeniz gerekir) — bu, kesinti sırasında verinin kaybolmadığının somut kanıtıdır.
Olası Sorunlar ve Çözümleri
'SD kart bulunamadı' hatası: SD modülünün 3.3V yerine yanlışlıkla 5V mantık seviyesiyle çalışan bir klon olup olmadığını kontrol edin (çoğu modülün kendi gerilim regülatörü/seviye çevirici vardır ama ucuz klonlarda olmayabilir, ESP32 pinlerini 3.3V mantık dışına maruz bırakmak pinlere kalıcı zarar verebilir). Kayıtlar SD karta yazılıyor ama hiç senkronize olmuyorsa: sunucuUrl adresinin gerçek ve erişilebilir olduğunu, HTTPS kullanılıyorsa proje #02'deki gibi sertifika doğrulaması gerekip gerekmediğini kontrol edin. SD yazma işlemi zamanla yavaşlıyorsa: dosya çok büyümüş olabilir, periyodik olarak eski ve senkronize edilmiş kayıtları temizleyen bir 'log rotation' mekanizması ekleyin.
Alternatif Çözümler
Düz CSV yerine her satırı ayrı bir JSON nesnesi (NDJSON formatı) olarak yazmak, bulut tarafında ayrıştırmayı kolaylaştırır. WiFi yerine veya yanında proje #02'deki TLS korumalı MQTT ile senkronizasyon yapılarak veri iletimi de şifrelenebilir. Tek bir SD kart yerine, kart dolduğunda otomatik olarak en eski senkronize edilmiş kayıtları silen döngüsel (circular buffer) bir dosya yönetimi eklenerek kart kapasitesi sınırsız gibi kullanı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.
Firebase Cloud Functions ile Sunucusuz Otomasyon Mantığı
ESP32'nin sadece ham sensör verisini (ışık seviyesi) HTTPS ile Firebase Realtime Database'e yazıp, 'ışık şu eşiğin altına düşerse röleyi aç' gibi karar mantığını cihaz üzerinde değil, bulutta çalışan bir Firebase Cloud Function'da (sunucusuz/serverless) tutmayı öğretir — cihaz koduna hiç dokunmadan bulut tarafındaki otomasyon kuralını değiştirebilme esnekliği sunar. ESP32'nin donanımsal kripto hızlandırması ve yeterli RAM'i, Firebase'in gerektirdiği TLS/HTTPS bağlantısını ve JSON işlemeyi sorunsuz yürütür. Bu tür bir bulut entegrasyonu Arduino Uno'da kurulamaz: WiFi yoktur; olsaydı bile TLS handshake için gereken kripto işlemi ve JSON ayrıştırma belleği 2KB SRAM'e hiç sığmaz.
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.