Pca grayscale handler cannot run below UXGA resolution

pca grayscale handler cannot run below UXGA resolution. Lowering the resolution will restart

// HTTP handler for PCA grayscale conversion and Otsu thresholding
esp_err_t pca_grayscale_handler(httpd_req_t *req) {
    camera_fb_t * fb = NULL;
    esp_err_t res = ESP_OK;
    size_t fb_len = 0;
    int64_t fr_start = esp_timer_get_time();

    // Ubah resolusi sebelum capture
    esp_err_t err = init_camera(FRAME_SIZE_CAPTURE, PIXFORMAT_JPEG);
    if (err != ESP_OK) {
        Serial.printf("Failed to set high resolution: 0x%x\n", err);
        return ESP_FAIL;
    }

    fb = esp_camera_fb_get();
    if (!fb) {
        Serial.println("Camera capture failed");
        return ESP_FAIL;
    }

    fb_len = fb->len;

    size_t out_len = 0;
    uint8_t * out_buf = NULL;
    bool s;

    // BMP conversion based on frame size
    if(!frame2bmp(fb, &out_buf, &out_len)){
        Serial.println("BMP conversion failed");
        esp_camera_fb_return(fb);
        return ESP_FAIL;
    }
    esp_camera_fb_return(fb);

    // Extract width and height from BMP header (dynamic frame size support)
    size_t out_width = out_buf[18] + (out_buf[19] << 8); // Dynamic width
    size_t out_height = out_buf[22] + (out_buf[23] << 8); // Dynamic height

    // Perform PCA-based grayscale conversion
    uint8_t* pca_gray = pca_grayscale(fb);
    if (pca_gray) {
        // Copy PCA result to BMP buffer (starting from pixel data offset, typically 54 bytes)
        memcpy(out_buf + 54, pca_gray, out_width * out_height);
        free(pca_gray);
    } else {
        // If PCA fails, fall back to simple grayscale conversion
        for (size_t i = 54; i < out_len; i += 3) {
            uint8_t gray = (out_buf[i] + out_buf[i+1] + out_buf[i+2]) / 3;
            out_buf[i] = out_buf[i+1] = out_buf[i+2] = gray;
        }
    }

    httpd_resp_set_type(req, "image/bmp");
    httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=pca_grayscale.bmp");
    s = httpd_resp_send(req, (const char *)out_buf, out_len);
    free(out_buf);

    // Kembali ke resolusi rendah setelah capture
    err = init_camera(FRAME_SIZE_STREAM, PIXFORMAT_JPEG);
    if (err != ESP_OK) {
        Serial.printf("Failed to set low resolution: 0x%x\n", err);
    }

    int64_t fr_end = esp_timer_get_time();
    Serial.printf("BMP: %uB %ums\n", (uint32_t)(fb_len), (uint32_t)((fr_end - fr_start)/1000));

    return s ? ESP_OK : ESP_FAIL;
}

full code

#include "Arduino.h"
#include "esp_camera.h"
#include <cmath>
#include "WiFi.h"
#include "esp_http_server.h"
#include <esp_system.h>
#include <esp_err.h>

// Camera pins and configurations
#define CAMERA_MODEL_ESP32S3_EYE // Has PSRAM
#include "camera_pins.h"

// WiFi credentials
const char* ap_ssid = "ESP32_Object_Counter";
const char* ap_password = "12345678";  // Password must be at least 8 characters

#define PART_BOUNDARY "123456789000000000000987654321"
#define _STREAM_CONTENT_TYPE "multipart/x-mixed-replace;boundary=" PART_BOUNDARY
#define _STREAM_BOUNDARY "\r\n--" PART_BOUNDARY "\r\n"
#define _STREAM_PART "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n"

// Web server
httpd_handle_t server = NULL;
httpd_handle_t stream_httpd = NULL;

// Define the RGB structure
struct RGB {
    uint8_t r, g, b;
};

#define FRAME_SIZE_STREAM FRAMESIZE_SVGA
#define FRAME_SIZE_CAPTURE FRAMESIZE_UXGA

// Function prototypes
void startCameraServer();

// Fungsi untuk inisialisasi kamera dengan resolusi dan format yang fleksibel
esp_err_t init_camera(framesize_t frame_size, pixformat_t pixel_format) {
    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;

    // Parameter input untuk resolusi dan format
    config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
    config.fb_location = CAMERA_FB_IN_PSRAM;
    config.pixel_format = pixel_format;
    config.frame_size = frame_size;
    config.jpeg_quality = 10; // Kualitas JPEG (semakin tinggi angka semakin rendah kualitas)
    config.fb_count = 2;      // Jumlah frame buffer

    // Deinit kamera jika sebelumnya sudah diinisialisasi
    esp_camera_deinit();

    // Init ulang kamera dengan konfigurasi yang baru
    return esp_camera_init(&config);
}

// HTTP handler untuk streaming video
esp_err_t stream_handler(httpd_req_t *req) {
    camera_fb_t * fb = NULL;
    esp_err_t res = ESP_OK;
    size_t _jpg_buf_len = 0;
    uint8_t * _jpg_buf = NULL;
    char * part_buf[64];

    static int64_t last_frame = 0;
    if(!last_frame) {
        last_frame = esp_timer_get_time();
    }

    res = httpd_resp_set_type(req, _STREAM_CONTENT_TYPE);
    if(res != ESP_OK){
        return res;
    }

    while(true) {
        fb = esp_camera_fb_get();
        if (!fb) {
            Serial.println("Camera capture failed");
            res = ESP_FAIL;
        } else {
            if(fb->format != PIXFORMAT_JPEG){
                bool jpeg_converted = frame2jpg(fb, 80, &_jpg_buf, &_jpg_buf_len);
                esp_camera_fb_return(fb);
                fb = NULL;
                if(!jpeg_converted){
                    Serial.println("JPEG compression failed");
                    res = ESP_FAIL;
                }
            } else {
                _jpg_buf_len = fb->len;
                _jpg_buf = fb->buf;
            }
        }
        if(res == ESP_OK){
            size_t hlen = snprintf((char *)part_buf, 64, _STREAM_PART, _jpg_buf_len);
            res = httpd_resp_send_chunk(req, (const char *)part_buf, hlen);
        }
        if(res == ESP_OK){
            res = httpd_resp_send_chunk(req, (const char *)_jpg_buf, _jpg_buf_len);
        }
        if(res == ESP_OK){
            res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY));
        }
        if(fb){
            esp_camera_fb_return(fb);
            fb = NULL;
            _jpg_buf = NULL;
        } else if(_jpg_buf){
            free(_jpg_buf);
            _jpg_buf = NULL;
        }
        if(res != ESP_OK){
            break;
        }
        int64_t fr_end = esp_timer_get_time();
        int64_t frame_time = fr_end - last_frame;
        last_frame = fr_end;
        frame_time /= 1000;
        Serial.printf("MJPG: %uB %ums (%.1ffps)\n",
            (uint32_t)(_jpg_buf_len),
            (uint32_t)frame_time, 1000.0 / (uint32_t)frame_time
        );
    }

    last_frame = 0;
    return res;
}

// Convert image to grayscale
void convertToGrayscale(uint8_t* buf, int len) {
    for (int i = 0; i < len; i += 3) {
        uint8_t r = buf[i];
        uint8_t g = buf[i + 1];
        uint8_t b = buf[i + 2];
        uint8_t gray = (r + g + b) / 3; // Simple average grayscale conversion
        buf[i] = buf[i + 1] = buf[i + 2] = gray;
    }
}

// Function to convert YUV422 to RGB
RGB yuv422_to_rgb(uint8_t y, uint8_t u, uint8_t v) {
    int r = y + 1.402f * (v - 128);
    int g = y - 0.344f * (u - 128) - 0.714f * (v - 128);
    int b = y + 1.772f * (u - 128);

    r = r > 255 ? 255 : (r < 0 ? 0 : r);
    g = g > 255 ? 255 : (g < 0 ? 0 : g);
    b = b > 255 ? 255 : (b < 0 ? 0 : b);

    return {static_cast<uint8_t>(r), static_cast<uint8_t>(g), static_cast<uint8_t>(b)};
}

// Function to perform PCA on RGB data
uint8_t* pca_grayscale(camera_fb_t* fb) {
    size_t pixel_count = fb->width * fb->height;
    uint8_t* grayscale = (uint8_t*)malloc(pixel_count);
    if (!grayscale) return nullptr;

    // Calculate mean of each channel
    float mean_r = 0, mean_g = 0, mean_b = 0;
    for (size_t i = 0; i < pixel_count * 2; i += 4) {
        RGB rgb = yuv422_to_rgb(fb->buf[i], fb->buf[i+1], fb->buf[i+3]);
        mean_r += rgb.r;
        mean_g += rgb.g;
        mean_b += rgb.b;
    }
    mean_r /= pixel_count;
    mean_g /= pixel_count;
    mean_b /= pixel_count;

    // Calculate covariance matrix
    float cov[3][3] = {{0}};
    for (size_t i = 0; i < pixel_count * 2; i += 4) {
        RGB rgb = yuv422_to_rgb(fb->buf[i], fb->buf[i+1], fb->buf[i+3]);
        float r = rgb.r - mean_r;
        float g = rgb.g - mean_g;
        float b = rgb.b - mean_b;

        cov[0][0] += r * r; cov[0][1] += r * g; cov[0][2] += r * b;
        cov[1][1] += g * g; cov[1][2] += g * b;
        cov[2][2] += b * b;
    }
    cov[1][0] = cov[0][1]; cov[2][0] = cov[0][2]; cov[2][1] = cov[1][2];
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            cov[i][j] /= pixel_count - 1;
        }
    }

    // Find eigenvector with largest eigenvalue (power iteration method)
    float vec[3] = {1, 1, 1};
    for (int iter = 0; iter < 10; iter++) {
        float new_vec[3] = {0};
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                new_vec[i] += cov[i][j] * vec[j];
            }
        }
        float len = sqrt(new_vec[0] * new_vec[0] + new_vec[1] * new_vec[1] + new_vec[2] * new_vec[2]);
        for (int i = 0; i < 3; i++) {
            vec[i] = new_vec[i] / len;
        }
    }

    // Project each pixel onto the principal component
    for (size_t i = 0; i < pixel_count * 2; i += 4) {
        RGB rgb = yuv422_to_rgb(fb->buf[i], fb->buf[i+1], fb->buf[i+3]);
        grayscale[i / 2] = vec[0] * rgb.r + vec[1] * rgb.g + vec[2] * rgb.b;
    }

    return grayscale;
}

// Otsu's thresholding method
uint8_t* otsu_threshold(uint8_t* grayscale, size_t pixel_count) {
    int histogram[256] = {0};
    for (size_t i = 0; i < pixel_count; i++) {
        histogram[grayscale[i]]++;
    }

    int total = pixel_count;
    float sum = 0;
    for (int i = 0; i < 256; i++) {
        sum += i * histogram[i];
    }

    float sumB = 0;
    int wB = 0;
    int wF = 0;
    float varMax = 0;
    int threshold = 0;

    for (int t = 0; t < 256; t++) {
        wB += histogram[t];
        if (wB == 0) continue;
        wF = total - wB;
        if (wF == 0) break;

        sumB += t * histogram[t];

        float mB = sumB / wB;
        float mF = (sum - sumB) / wF;

        float varBetween = wB * wF * (mB - mF) * (mB - mF);
        if (varBetween > varMax) {
            varMax = varBetween;
            threshold = t;
        }
    }

    uint8_t* binary = (uint8_t*)malloc(pixel_count);
    if (!binary) return nullptr;

    for (size_t i = 0; i < pixel_count; i++) {
        binary[i] = (grayscale[i] > threshold) ? 255 : 0;
    }

    return binary;
}

// HTTP handler for PCA grayscale conversion and Otsu thresholding
esp_err_t pca_grayscale_handler(httpd_req_t *req) {
    camera_fb_t * fb = NULL;
    esp_err_t res = ESP_OK;
    size_t fb_len = 0;
    int64_t fr_start = esp_timer_get_time();

    // Ubah resolusi sebelum capture
    esp_err_t err = init_camera(FRAME_SIZE_CAPTURE, PIXFORMAT_JPEG);
    if (err != ESP_OK) {
        Serial.printf("Failed to set high resolution: 0x%x\n", err);
        return ESP_FAIL;
    }

    fb = esp_camera_fb_get();
    if (!fb) {
        Serial.println("Camera capture failed");
        return ESP_FAIL;
    }

    fb_len = fb->len;

    size_t out_len = 0;
    uint8_t * out_buf = NULL;
    bool s;

    // BMP conversion based on frame size
    if(!frame2bmp(fb, &out_buf, &out_len)){
        Serial.println("BMP conversion failed");
        esp_camera_fb_return(fb);
        return ESP_FAIL;
    }
    esp_camera_fb_return(fb);

    // Extract width and height from BMP header (dynamic frame size support)
    size_t out_width = out_buf[18] + (out_buf[19] << 8); // Dynamic width
    size_t out_height = out_buf[22] + (out_buf[23] << 8); // Dynamic height

    // Perform PCA-based grayscale conversion
    uint8_t* pca_gray = pca_grayscale(fb);
    if (pca_gray) {
        // Copy PCA result to BMP buffer (starting from pixel data offset, typically 54 bytes)
        memcpy(out_buf + 54, pca_gray, out_width * out_height);
        free(pca_gray);
    } else {
        // If PCA fails, fall back to simple grayscale conversion
        for (size_t i = 54; i < out_len; i += 3) {
            uint8_t gray = (out_buf[i] + out_buf[i+1] + out_buf[i+2]) / 3;
            out_buf[i] = out_buf[i+1] = out_buf[i+2] = gray;
        }
    }

    httpd_resp_set_type(req, "image/bmp");
    httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=pca_grayscale.bmp");
    s = httpd_resp_send(req, (const char *)out_buf, out_len);
    free(out_buf);

    // Kembali ke resolusi rendah setelah capture
    err = init_camera(FRAME_SIZE_STREAM, PIXFORMAT_JPEG);
    if (err != ESP_OK) {
        Serial.printf("Failed to set low resolution: 0x%x\n", err);
    }

    int64_t fr_end = esp_timer_get_time();
    Serial.printf("BMP: %uB %ums\n", (uint32_t)(fb_len), (uint32_t)((fr_end - fr_start)/1000));

    return s ? ESP_OK : ESP_FAIL;
}

// HTTP handler untuk menangkap gambar dengan resolusi tinggi
esp_err_t capture_handler(httpd_req_t *req) {
    camera_fb_t *fb = NULL;
    esp_err_t res = ESP_OK;
    size_t fb_len = 0;
    int64_t fr_start = esp_timer_get_time();

    // Set high resolution
    esp_err_t err = init_camera(FRAME_SIZE_CAPTURE, PIXFORMAT_JPEG);
    if (err != ESP_OK) {
        Serial.printf("Failed to set high resolution: 0x%x\n", err);
        httpd_resp_send_500(req);
        return ESP_FAIL;
    }

    vTaskDelay(500 / portTICK_PERIOD_MS);

    fb = esp_camera_fb_get();
    if (!fb) {
        Serial.println("Camera capture failed");
        httpd_resp_send_500(req);
        return ESP_FAIL;
    }

    fb_len = fb->len;
    res = httpd_resp_set_type(req, "image/jpeg");
    if (res == ESP_OK) {
        res = httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=capture.jpg");
    }
    if (res == ESP_OK) {
        res = httpd_resp_send(req, (const char *)fb->buf, fb->len);
    }
    esp_camera_fb_return(fb);

    // Set low resolution
    err = init_camera(FRAME_SIZE_STREAM, PIXFORMAT_JPEG);
    if (err != ESP_OK) {
        Serial.printf("Failed to set low resolution: 0x%x\n", err);
    }

    int64_t fr_end = esp_timer_get_time();
    Serial.printf("JPG: %uB %ums\n", (uint32_t)(fb_len), (uint32_t)((fr_end - fr_start)/1000));

    if (res != ESP_OK) {
        Serial.printf("Error in capture_handler: 0x%x\n", res);
        httpd_resp_send_500(req);
    }

    return res;
}

// HTML for the web interface
static esp_err_t index_handler(httpd_req_t *req) {
  const char* resp_str = R"rawliteral(
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ESP32 Camera</title>
  <style>
      body {
          font-family: Arial, sans-serif;
          display: flex;
          justify-content: center;
          align-items: center;
          height: 100vh;
          margin: 0;
          background-color: #f0f0f0;
      }
  
      .container {
          text-align: center;
          background-color: white;
          padding: 30px;
          border-radius: 15px;
          box-shadow: 0 0 20px rgba(0,0,0,0.1);
          width: 90%;
          max-width: 600px;
      }
  
      h1 {
          color: #333;
          margin-bottom: 30px;
      }
  
      .camera-container {
          position: relative;
          width: 100%;
          max-width: 400px;
          aspect-ratio: 1 / 1;
          overflow: hidden;
          margin: 0 auto 30px;
          border-radius: 50%;
          border: 5px solid #333;
          box-shadow: 0 5px 15px rgba(0,0,0,0.2);
      }
  
      #cameraFeed {
          width: 100%;
          height: 100%;
          object-fit: cover;
          border-radius: 50%;
      }
  
      button {
          background-color: #4CAF50;
          border: none;
          color: white;
          padding: 15px 32px;
          text-align: center;
          text-decoration: none;
          display: inline-block;
          font-size: 16px;
          margin: 4px 2px;
          cursor: pointer;
          border-radius: 8px;
          transition: background-color 0.3s;
          box-shadow: 0 2px 5px rgba(0,0,0,0.2);
      }

      button:hover {
          background-color: #45a049;
      }
  
      /* Media queries for responsiveness */
      @media (max-width: 768px) {
          body {
              padding: 20px;
          }
  
          .container {
              width: 100%;
              max-width: 500px;
              padding: 20px;
          }
  
          .camera-container {
              max-width: 300px;
          }
  
          button {
              padding: 12px 24px;
              font-size: 14px;
          }
      }
  
      @media (max-width: 480px) {
          body {
              padding: 10px;
          }
  
          .container {
              padding: 15px;
          }
  
          .camera-container {
              max-width: 250px;
          }
  
          button {
              padding: 10px 20px;
              font-size: 14px;
              width: 100%;
          }
      }
  </style>
</head>
<body>
    <div class="container">
        <h1>ESP32 Camera</h1>
        <div class="camera-container">
            <img id="cameraFeed" src="/stream" alt="Camera Stream">
        </div>
        <button onclick="saveImage()">Save Image</button>
    </div>
    <script>
        function saveImage() {
            const video = document.getElementById('cameraFeed');
            const canvas = document.createElement('canvas');
            canvas.width = 1600;
            canvas.height = 1600;
            const context = canvas.getContext('2d');
            
            // Draw white background
            context.fillStyle = 'white';
            context.fillRect(0, 0, canvas.width, canvas.height);
            
            // Calculate new position for the circular camera feed
            const centerY = 800;
            const radius = 600;
            const videoSize = radius * 2;
            const videoY = centerY - radius;
            
            // Draw circular camera feed
            context.save();
            context.beginPath();
            context.arc(800, centerY, radius, 0, Math.PI * 2);
            context.clip();
            context.drawImage(video, 200, videoY, videoSize, videoSize);
            context.restore();
            
            // Draw border
            context.strokeStyle = '#333';
            context.lineWidth = 20;
            context.beginPath();
            context.arc(800, centerY, radius, 0, Math.PI * 2);
            context.stroke();
            
            canvas.toBlob(function(blob) {
                const link = document.createElement('a');
                link.href = URL.createObjectURL(blob);
                link.download = 'esp32_camera.jpg';
                link.click();
            }, 'image/jpeg');
        }
    </script>
</body>
</html>
  )rawliteral";
  httpd_resp_send(req, resp_str, strlen(resp_str));
  return ESP_OK;
}

// Fungsi untuk memulai web server kamera
void startCameraServer() {
    httpd_config_t config = HTTPD_DEFAULT_CONFIG();
    config.stack_size = 10240; // Increase stack size if needed
    config.max_uri_handlers = 8; // Increase if you have many endpoints
    config.max_resp_headers = 8; // Adjust as needed

    httpd_uri_t index_uri = {
        .uri       = "/",
        .method    = HTTP_GET,
        .handler   = index_handler,
        .user_ctx  = NULL
    };

    httpd_uri_t stream_uri = {
        .uri       = "/stream",
        .method    = HTTP_GET,
        .handler   = stream_handler,
        .user_ctx  = NULL
    };

    httpd_uri_t capture_uri = {
        .uri       = "/capture",
        .method    = HTTP_GET,
        .handler   = capture_handler,
        .user_ctx  = NULL
    };

    httpd_uri_t pca_uri = {
        .uri       = "/capture_grayscale",
        .method    = HTTP_GET,
        .handler   = pca_grayscale_handler,
        .user_ctx  = NULL
    };

    if (httpd_start(&server, &config) == ESP_OK) {
        httpd_register_uri_handler(server, &index_uri);
        httpd_register_uri_handler(server, &stream_uri);
        httpd_register_uri_handler(server, &capture_uri);
        httpd_register_uri_handler(server, &pca_uri);
    }

    config.server_port += 1;
    config.ctrl_port += 1;
    if (httpd_start(&stream_httpd, &config) == ESP_OK) {
        httpd_register_uri_handler(stream_httpd, &stream_uri);
    }
}

void handle_error(esp_err_t error) {
    if (error != ESP_OK) {
        Serial.printf("Error occurred: %s\n", esp_err_to_name(error));
        // Tambahkan tindakan yang sesuai, seperti restart ESP32
        // esp_restart();
    }
}

// Fungsi setup utama
void setup() {
    Serial.begin(115200);
    
    // Setup WiFi dalam mode AP
    WiFi.softAP(ap_ssid, ap_password);

    IPAddress IP = WiFi.softAPIP();
    Serial.print("Access Point IP: ");
    Serial.println(IP);

    #ifdef RGB_BUILTIN
      digitalWrite(RGB_BUILTIN, HIGH);   // Turn the RGB LED white
      delay(1000);
      digitalWrite(RGB_BUILTIN, LOW);    // Turn the RGB LED off
      delay(1000);
    
      neopixelWrite(RGB_BUILTIN,RGB_BRIGHTNESS,0,0); // Red
      delay(1000);
      neopixelWrite(RGB_BUILTIN,0,RGB_BRIGHTNESS,0); // Green
      delay(1000);
      neopixelWrite(RGB_BUILTIN,0,0,RGB_BRIGHTNESS); // Blue
      delay(1000);
      neopixelWrite(RGB_BUILTIN,0,0,0); // Off / black
      delay(1000);
    #endif

    // Inisialisasi kamera pertama kali dengan resolusi VGA
    esp_err_t err = init_camera(FRAME_SIZE_STREAM, PIXFORMAT_JPEG);
    if (err != ESP_OK) {
        Serial.printf("Camera init failed with error 0x%x", err);
        return;
    }

    startCameraServer();
}

void loop() {
    if (server == NULL || stream_httpd == NULL) {
        Serial.println("Error: HTTP server not started. Restarting...");
        esp_restart();
    }
    delay(10000); // Check every 10 seconds
}

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.