#include <Arduino.h>
#include <WiFi.h>
#include <WebServer.h>
#include <HTTPClient.h>
#include "esp_camera.h"
#include "time.h"
#include "SPIFFS.h"  // Tambahkan SPIFFS untuk penyimpanan file

// WiFi credentials
const char* ssid = "didinganteng";
const char* password = "didin123";

// NTP Server untuk waktu
const char* ntpServer = "pool.ntp.org";
const long gmtOffset_sec = 25200; // GMT+7 (WIB) in seconds (7*3600)
const int daylightOffset_sec = 0;

// Web server on port 80
WebServer server(80);

// Server Laravel (ganti dengan alamat server Laravel Anda)
const char* laravelServerUrl = "https://monitoring-anggrek.oyi.web.id/api/esp32cam/upload";  // Ganti dengan IP komputer Anda

// Status variables
bool cameraInitialized = false;
unsigned long startTime = 0;
// Menghilangkan timeout 5 menit
// const unsigned long TIMEOUT = 300000; // 5 menit (300000 ms) sebelum sleep

// Variabel untuk pengambilan gambar otomatis
const unsigned long AUTO_CAPTURE_DELAY = 5000; // 5 detik setelah boot
bool hasAutoCapture = false; // Flag untuk menandai sudah auto capture atau belum

// Variabel untuk streaming
bool isStreaming = false;  // Flag untuk status streaming
const int streamingFrameRate = 10; // Frame rate untuk streaming (fps)
const int streamingDelay = 1000 / streamingFrameRate; // Delay antara frame dalam ms

// Variabel untuk pengaturan kamera
framesize_t streamResolution = FRAMESIZE_SVGA; // Default 800x600
int streamQuality = 10; // Default quality (0-63, lower is better)

// Variabel untuk pengiriman ulang foto
const int MAX_RETRY_PHOTOS = 5; // Maksimal foto yang disimpan sementara
const unsigned long RETRY_INTERVAL = 30000; // Interval pengiriman ulang (30 detik)
unsigned long lastRetryTime = 0; // Waktu terakhir mencoba mengirim ulang
int pendingPhotoCount = 0; // Jumlah foto yang menunggu dikirim
bool isRetryScheduled = false; // Flag untuk menandai jadwal pengiriman ulang

// ESP32 Camera pins
#define PWDN_GPIO_NUM     32
#define RESET_GPIO_NUM    -1
#define XCLK_GPIO_NUM      0
#define SIOD_GPIO_NUM     26
#define SIOC_GPIO_NUM     27
#define Y9_GPIO_NUM       35
#define Y8_GPIO_NUM       34
#define Y7_GPIO_NUM       39
#define Y6_GPIO_NUM       36
#define Y5_GPIO_NUM       21
#define Y4_GPIO_NUM       19
#define Y3_GPIO_NUM       18
#define Y2_GPIO_NUM        5
#define VSYNC_GPIO_NUM    25
#define HREF_GPIO_NUM     23
#define PCLK_GPIO_NUM     22

// Helper function to add CORS headers to all responses
void setCorsHeaders() {
  server.sendHeader("Access-Control-Allow-Origin", "*");
  server.sendHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
  server.sendHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
}

// Helper function to initialize SPIFFS
bool initSPIFFS() {
  if (!SPIFFS.begin(true)) { // Format SPIFFS if mounting fails
    Serial.println("SPIFFS gagal diinisialisasi");
    return false;
  }
  Serial.println("SPIFFS berhasil diinisialisasi");
  
  // Bersihkan file foto lama jika ada
  cleanupPendingPhotos();
  
  return true;
}

// Helper function untuk membersihkan file foto yang pending
void cleanupPendingPhotos() {
  // Cek jumlah foto pending
  int count = 0;
  File root = SPIFFS.open("/");
  File file = root.openNextFile();
  
  while (file) {
    String fileName = file.name();
    if (fileName.startsWith("/photo_") && fileName.endsWith(".jpg")) {
      count++;
      Serial.println("Found pending photo: " + fileName);
    }
    file = root.openNextFile();
  }
  
  pendingPhotoCount = count;
  Serial.println("Jumlah foto pending: " + String(pendingPhotoCount));
  
  // Jika ada foto pending, jadwalkan pengiriman ulang
  if (pendingPhotoCount > 0) {
    isRetryScheduled = true;
    Serial.println("Pengiriman ulang foto terjadwal");
  }
}

bool initCamera() {
  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;
  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;
  config.pin_sscb_sda = SIOD_GPIO_NUM;
  config.pin_sscb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  config.xclk_freq_hz = 20000000;
  config.pixel_format = PIXFORMAT_JPEG;

  // Initialize with high quality
  config.frame_size = FRAMESIZE_SVGA; // 800x600
  config.jpeg_quality = 10; // 0-63, lower is better quality
  config.fb_count = 2; // Meningkatkan jumlah frame buffer untuk streaming

  // Camera init
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Kamera gagal diinisialisasi, error 0x%x", err);
    return false;
  }

  Serial.println("Kamera berhasil diinisialisasi");
  return true;
}

void connectToWifi() {
  Serial.print("Menghubungkan ke WiFi...");
  WiFi.begin(ssid, password);
  
  int attempts = 0;
  while (WiFi.status() != WL_CONNECTED && attempts < 20) {
    delay(500);
    Serial.print(".");
    attempts++;
  }
  
  if (WiFi.status() == WL_CONNECTED) {
    Serial.println();
    Serial.println("WiFi terhubung!");
    Serial.print("IP Address: ");
    Serial.println(WiFi.localIP());
    Serial.print("Subnet Mask: ");
    Serial.println(WiFi.subnetMask());
    Serial.print("Gateway IP: ");
    Serial.println(WiFi.gatewayIP());
  } else {
    Serial.println();
    Serial.println("Gagal terhubung ke WiFi. Restart ESP32...");
    delay(1000);
    ESP.restart();
  }
}

void initTime() {
  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
  struct tm timeinfo;
  if (!getLocalTime(&timeinfo)) {
    Serial.println("Gagal mendapatkan waktu dari NTP server");
    return;
  }
  
  Serial.println("NTP waktu diinisialisasi");
  Serial.print("Waktu saat ini: ");
  Serial.print(&timeinfo, "%A, %d-%m-%Y %H:%M:%S");
  Serial.println();
}

// Simpan foto ke SPIFFS untuk dikirim nanti
bool savePhotoForLater(camera_fb_t *fb) {
  if (pendingPhotoCount >= MAX_RETRY_PHOTOS) {
    Serial.println("Terlalu banyak foto pending, tidak bisa menyimpan lagi");
    return false;
  }
  
  // Buat nama file dengan timestamp
  char fileName[32];
  struct tm timeinfo;
  if(getLocalTime(&timeinfo)){
    sprintf(fileName, "/photo_%04d%02d%02d_%02d%02d%02d.jpg", 
            timeinfo.tm_year + 1900, timeinfo.tm_mon + 1, timeinfo.tm_mday, 
            timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec);
  } else {
    sprintf(fileName, "/photo_%d.jpg", millis());
  }
  
  // Simpan ke SPIFFS
  File file = SPIFFS.open(fileName, FILE_WRITE);
  if (!file) {
    Serial.println("Gagal membuka file untuk menulis");
    return false;
  }
  
  if (file.write(fb->buf, fb->len) != fb->len) {
    Serial.println("Gagal menulis file");
    file.close();
    return false;
  }
  
  file.close();
  Serial.println("Foto berhasil disimpan di SPIFFS: " + String(fileName));
  pendingPhotoCount++;
  isRetryScheduled = true;
  
  return true;
}

// Fungsi untuk mengirim ulang foto yang tersimpan di SPIFFS
void retryPendingPhotos() {
  if (pendingPhotoCount <= 0) {
    isRetryScheduled = false;
    return;
  }
  
  if (WiFi.status() != WL_CONNECTED) {
    Serial.println("Tidak ada koneksi WiFi, menunda pengiriman ulang");
    return;
  }
  
  Serial.println("Mencoba mengirim foto yang tertunda...");
  
  File root = SPIFFS.open("/");
  File file = root.openNextFile();
  bool anySuccess = false;
  
  while (file && pendingPhotoCount > 0) {
    String fileName = file.name();
    if (fileName.startsWith("/photo_") && fileName.endsWith(".jpg")) {
      Serial.println("Mengirim foto: " + fileName);
      
      // Baca file
      if (file.size() > 0) {
        uint8_t *buffer = (uint8_t*) malloc(file.size());
        if (buffer) {
          size_t size = file.read(buffer, file.size());
          
          // Kirim ke server
          HTTPClient http;
          http.begin(laravelServerUrl);
          http.addHeader("Content-Type", "image/jpeg");
          
          int httpResponseCode = http.POST(buffer, size);
          free(buffer);
          
          if (httpResponseCode > 0) {
            String response = http.getString();
            Serial.println("HTTP Response code: " + String(httpResponseCode));
            Serial.println("Response: " + response);
            
            // Jika sukses, hapus file
            String fullFileName = fileName;
            file.close();
            if (SPIFFS.remove(fullFileName)) {
              Serial.println("File berhasil dihapus: " + fullFileName);
              pendingPhotoCount--;
              anySuccess = true;
            } else {
              Serial.println("Gagal menghapus file: " + fullFileName);
            }
          } else {
            Serial.print("Error pada HTTP request: ");
            Serial.println(httpResponseCode);
          }
          
          http.end();
        }
      }
      
      if (!anySuccess) {
        // Jika tidak ada yang berhasil dikirim, kita hentikan dulu
        // untuk menghemat resource dan mencoba lagi nanti
        break;
      }
    }
    
    file = root.openNextFile();
  }
  
  // Update status
  if (pendingPhotoCount <= 0) {
    isRetryScheduled = false;
    Serial.println("Semua foto tertunda berhasil dikirim");
  } else {
    Serial.println("Masih ada " + String(pendingPhotoCount) + " foto tertunda");
  }
}

// Rute untuk halaman utama
void handleRoot() {
  setCorsHeaders();
  String html = "<html><body>";
  html += "<h1>ESP32-CAM Monitoring Anggrek</h1>";
  html += "<p>Akses <a href='/capture'>capture</a> untuk mengambil foto</p>";
  // Hilangkan link sleep
  // html += "<p>Akses <a href='/sleep'>sleep</a> untuk masuk mode deep sleep</p>";
  html += "<p>Akses <a href='/status'>status</a> untuk melihat status perangkat</p>";
  html += "<p>Akses <a href='/stream'>stream</a> untuk melihat live streaming</p>";
  
  struct tm timeinfo;
  if (getLocalTime(&timeinfo)) {
    char timeString[50];
    strftime(timeString, sizeof(timeString), "%A, %d-%m-%Y %H:%M:%S", &timeinfo);
    html += "<p>Waktu saat ini: " + String(timeString) + "</p>";
  }
  
  html += "<p>Waktu hidup: " + String((millis() - startTime) / 1000) + " detik</p>";
  
  // Tambahkan info foto pending
  html += "<p>Foto tertunda: " + String(pendingPhotoCount) + "</p>";
  
  html += "</body></html>";
  
  server.send(200, "text/html", html);
}

// Rute untuk mengambil foto
void handleCapture() {
  setCorsHeaders();
  if (!cameraInitialized) {
    server.send(500, "text/plain", "Kamera tidak diinisialisasi");
    return;
  }
  
  camera_fb_t *fb = NULL;
  
  // Ambil foto
  fb = esp_camera_fb_get();
  if (!fb) {
    Serial.println("Gagal mengambil foto");
    server.send(500, "text/plain", "Gagal mengambil foto");
    return;
  }
  
  Serial.println("Foto berhasil diambil");
  Serial.print("Ukuran: ");
  Serial.print(fb->len);
  Serial.println(" bytes");
  
  // Kirim foto sebagai respons HTTP
  server.sendHeader("Content-Type", "image/jpeg");
  server.sendHeader("Content-Disposition", "inline; filename=capture.jpg");
  server.sendHeader("Content-Length", String(fb->len));
  server.send_P(200, "image/jpeg", (const char *)fb->buf, fb->len);
  
  // Bebaskan memori
  esp_camera_fb_return(fb);
  
  Serial.println("Foto berhasil dikirim ke client");
}

// Rute untuk status perangkat
void handleStatus() {
  setCorsHeaders();
  String status = "{";
  
  // Informasi sistem
  status += "\"heap_size\":" + String(ESP.getFreeHeap()) + ",";
  status += "\"uptime\":" + String(millis() / 1000) + ",";
  
  // Informasi WiFi
  status += "\"wifi\":{";
  status += "\"connected\":" + String(WiFi.status() == WL_CONNECTED ? "true" : "false") + ",";
  status += "\"ip\":\"" + WiFi.localIP().toString() + "\",";
  status += "\"rssi\":" + String(WiFi.RSSI());
  status += "},";
  
  // Informasi kamera
  status += "\"camera\":{";
  status += "\"initialized\":" + String(cameraInitialized ? "true" : "false") + ",";
  status += "\"streaming\":" + String(isStreaming ? "true" : "false");
  status += "},";
  
  // Informasi foto pending
  status += "\"pending_photos\":{";
  status += "\"count\":" + String(pendingPhotoCount) + ",";
  status += "\"retry_scheduled\":" + String(isRetryScheduled ? "true" : "false");
  status += "}";
  
  status += "}";
  
  server.sendHeader("Content-Type", "application/json");
  server.send(200, "application/json", status);
}

// Handler untuk streaming MJPEG
void handleStream() {
  if (!cameraInitialized) {
    setCorsHeaders();
    server.send(500, "text/plain", "Kamera tidak diinisialisasi");
    return;
  }
  
  Serial.println("Permintaan streaming diterima");
  
  // Set header HTTP untuk streaming MJPEG
  WiFiClient client = server.client();
  
  // Tambahkan CORS headers manuallly (tanpa DefaultHeaders)
  client.println("HTTP/1.1 200 OK");
  client.println("Content-Type: multipart/x-mixed-replace; boundary=frame");
  client.println("Access-Control-Allow-Origin: *");
  client.println("Connection: keep-alive");
  client.println();
  
  // Set flag streaming
  isStreaming = true;
  Serial.println("Streaming dimulai");
  
  // Loop untuk mengirim frame selama klien terhubung dan streaming aktif
  while (client.connected() && isStreaming) {
    camera_fb_t *fb = esp_camera_fb_get();
    if (!fb) {
      Serial.println("Capture gagal selama streaming");
      delay(100);
      continue;
    }
    
    client.println("--frame");
    client.println("Content-Type: image/jpeg");
    client.println("Content-Length: " + String(fb->len));
    client.println();
    client.write(fb->buf, fb->len);
    client.println();
    
    // Bebaskan memori
    esp_camera_fb_return(fb);
    
    // Delay sesuai frame rate
    delay(streamingDelay);
  }
  
  // Jika keluar dari loop, streaming dihentikan
  isStreaming = false;
  Serial.println("Streaming dihentikan");
}

// Handler untuk menghentikan streaming
void handleStopStream() {
  isStreaming = false;
  setCorsHeaders();
  Serial.println("Permintaan menghentikan streaming diterima");
  server.send(200, "text/plain", "Streaming dihentikan");
}

// Fungsi untuk mengirim gambar ke server Laravel
void sendImageToServer() {
  if (!cameraInitialized) {
    Serial.println("Kamera tidak diinisialisasi, tidak bisa mengirim gambar");
    return;
  }
  
  camera_fb_t *fb = NULL;
  
  // Ambil foto
  fb = esp_camera_fb_get();
  if (!fb) {
    Serial.println("Gagal mengambil foto untuk dikirim ke server");
    return;
  }
  
  Serial.println("Foto berhasil diambil untuk dikirim ke server");
  Serial.print("Ukuran: ");
  Serial.print(fb->len);
  Serial.println(" bytes");
  
  // Cek koneksi WiFi
  if (WiFi.status() != WL_CONNECTED) {
    Serial.println("Tidak ada koneksi WiFi, menyimpan foto untuk dikirim nanti");
    bool saved = savePhotoForLater(fb);
    if (saved) {
      Serial.println("Foto disimpan untuk dikirim nanti");
    } else {
      Serial.println("Gagal menyimpan foto untuk dikirim nanti");
    }
    esp_camera_fb_return(fb);
    return;
  }
  
  // Kirim foto ke server Laravel
  HTTPClient http;
  http.begin(laravelServerUrl);
  http.addHeader("Content-Type", "image/jpeg");
  
  int httpResponseCode = http.POST(fb->buf, fb->len);
  
  if (httpResponseCode > 0) {
    String response = http.getString();
    Serial.println("HTTP Response code: " + String(httpResponseCode));
    Serial.println("Response: " + response);
  } else {
    Serial.print("Error pada HTTP request: ");
    Serial.println(httpResponseCode);
    
    // Jika gagal kirim, simpan untuk dikirim nanti
    bool saved = savePhotoForLater(fb);
    if (saved) {
      Serial.println("Foto disimpan untuk dikirim nanti");
    } else {
      Serial.println("Gagal menyimpan foto untuk dikirim nanti");
    }
  }
  
  http.end();
  
  // Bebaskan memori
  esp_camera_fb_return(fb);
  
  Serial.println("Proses pengiriman gambar ke server selesai");
}

// Fungsi untuk pengambilan gambar otomatis
void autoCapture() {
  Serial.println("Melakukan pengambilan gambar otomatis...");
  sendImageToServer();
  hasAutoCapture = true;
  Serial.println("Pengambilan gambar otomatis selesai");
}

// Handler untuk CORS preflight requests
void handleOptions() {
  setCorsHeaders();
  server.send(204);
}

// Handler untuk mengubah pengaturan kamera
void handleCameraSettings() {
  setCorsHeaders();
  
  // Ambil parameter
  String resolution = server.arg("resolution");
  String quality = server.arg("quality");
  
  bool changed = false;
  
  // Ubah resolusi jika diperlukan
  if (resolution.length() > 0) {
    framesize_t newResolution = FRAMESIZE_SVGA; // Default
    
    if (resolution == "UXGA") newResolution = FRAMESIZE_UXGA; // 1600x1200
    else if (resolution == "SXGA") newResolution = FRAMESIZE_SXGA; // 1280x1024
    else if (resolution == "XGA") newResolution = FRAMESIZE_XGA; // 1024x768
    else if (resolution == "SVGA") newResolution = FRAMESIZE_SVGA; // 800x600
    else if (resolution == "VGA") newResolution = FRAMESIZE_VGA; // 640x480
    else if (resolution == "CIF") newResolution = FRAMESIZE_CIF; // 400x296
    else if (resolution == "QVGA") newResolution = FRAMESIZE_QVGA; // 320x240
    else if (resolution == "HQVGA") newResolution = FRAMESIZE_HQVGA; // 240x176
    else if (resolution == "QQVGA") newResolution = FRAMESIZE_QQVGA; // 160x120
    
    if (newResolution != streamResolution) {
      streamResolution = newResolution;
      changed = true;
      
      // Terapkan ke sensor
      sensor_t *s = esp_camera_sensor_get();
      if (s) {
        s->set_framesize(s, streamResolution);
        Serial.println("Resolusi diubah ke " + resolution);
      }
    }
  }
  
  // Ubah kualitas jika diperlukan
  if (quality.length() > 0) {
    int newQuality = quality.toInt();
    
    if (newQuality >= 0 && newQuality <= 63 && newQuality != streamQuality) {
      streamQuality = newQuality;
      changed = true;
      
      // Terapkan ke sensor
      sensor_t *s = esp_camera_sensor_get();
      if (s) {
        s->set_quality(s, streamQuality);
        Serial.println("Kualitas gambar diubah ke " + quality);
      }
    }
  }
  
  // Kirim response
  String response = "{";
  response += "\"success\":" + String(changed ? "true" : "false") + ",";
  response += "\"resolution\":\"";
  
  switch (streamResolution) {
    case FRAMESIZE_UXGA: response += "UXGA"; break;
    case FRAMESIZE_SXGA: response += "SXGA"; break;
    case FRAMESIZE_XGA: response += "XGA"; break;
    case FRAMESIZE_SVGA: response += "SVGA"; break;
    case FRAMESIZE_VGA: response += "VGA"; break;
    case FRAMESIZE_CIF: response += "CIF"; break;
    case FRAMESIZE_QVGA: response += "QVGA"; break;
    case FRAMESIZE_HQVGA: response += "HQVGA"; break;
    case FRAMESIZE_QQVGA: response += "QQVGA"; break;
    default: response += "Unknown"; break;
  }
  
  response += "\",";
  response += "\"quality\":" + String(streamQuality);
  response += "}";
  
  server.sendHeader("Content-Type", "application/json");
  server.send(200, "application/json", response);
}

// Handler untuk mencoba pengiriman ulang foto secara manual
void handleRetryPhotos() {
  setCorsHeaders();
  retryPendingPhotos();
  server.send(200, "text/plain", "Pengiriman ulang foto dimulai");
}

void setup() {
  Serial.begin(115200);
  delay(1000); // Tunggu serial siap
  
  Serial.println("\n\n=== ESP32-CAM Monitoring Anggrek ===");
  Serial.println("Starting as Web Server");
  
  // Catat waktu mulai
  startTime = millis();
  
  // Inisialisasi SPIFFS
  bool spiffsReady = initSPIFFS();
  
  // Inisialisasi kamera
  cameraInitialized = initCamera();
  
  // Hubungkan ke WiFi
  connectToWifi();
  
  // Inisialisasi waktu
  initTime();
  
  // Konfigurasi rute server
  server.on("/", handleRoot);
  server.on("/capture", handleCapture);
  server.on("/status", handleStatus);
  
  // Tambahkan rute untuk streaming
  server.on("/stream", HTTP_GET, handleStream);
  server.on("/stopstream", HTTP_GET, handleStopStream);
  
  // Tambahkan rute untuk pengambilan gambar dan pengiriman ke server
  server.on("/send-to-server", []() {
    setCorsHeaders();
    sendImageToServer();
    server.send(200, "text/plain", "Gambar berhasil dikirim ke server");
  });
  
  // Tambahkan rute untuk mencoba mengirim ulang foto
  server.on("/retry-photos", HTTP_GET, handleRetryPhotos);
  
  // Tambahkan rute untuk mengubah pengaturan kamera
  server.on("/camera-settings", HTTP_GET, handleCameraSettings);
  
  // Handle CORS preflight requests
  server.on("/capture", HTTP_OPTIONS, handleOptions);
  server.on("/status", HTTP_OPTIONS, handleOptions);
  server.on("/stream", HTTP_OPTIONS, handleOptions);
  server.on("/stopstream", HTTP_OPTIONS, handleOptions);
  server.on("/send-to-server", HTTP_OPTIONS, handleOptions);
  server.on("/retry-photos", HTTP_OPTIONS, handleOptions);
  server.on("/camera-settings", HTTP_OPTIONS, handleOptions);
  
  // Start server
  server.begin();
  Serial.println("HTTP server dimulai");
  Serial.println("Buka http://" + WiFi.localIP().toString() + " di browser untuk mengakses ESP32-CAM");
  Serial.println("Pengambilan gambar otomatis akan dilakukan dalam 5 detik...");
  Serial.println("Streaming tersedia di http://" + WiFi.localIP().toString() + "/stream");
}

void loop() {
  // Tangani request yang masuk
  server.handleClient();
  
  // Cek jika sudah waktunya untuk pengambilan gambar otomatis
  if (!hasAutoCapture && (millis() - startTime > AUTO_CAPTURE_DELAY)) {
    autoCapture();
  }
  
  // Cek jika perlu mengirim ulang foto yang tersimpan
  if (isRetryScheduled && pendingPhotoCount > 0 && WiFi.status() == WL_CONNECTED) {
    unsigned long currentTime = millis();
    if (currentTime - lastRetryTime > RETRY_INTERVAL) {
      lastRetryTime = currentTime;
      retryPendingPhotos();
    }
  }
  
  // Beri waktu untuk WiFi stack
  delay(1);
}
