diff --git a/README.md b/README.md index f0f844a..a4ca78c 100644 --- a/README.md +++ b/README.md @@ -29,11 +29,22 @@ Supported protocols This software supports the following ESP32-CAM (and alike) modules: -- ESP32CAM - AI THINKER -- TTGO T-CAM -- WROVER-KIT +- EspressIf ESP-EYE +- EspressIf ESP32S2-CAM +- EspressIf ESP32S3-CAM-LCD +- EspressIf ESP32S3-EYE +- Freenove WROVER KIT +- M5STACK ESP32CAM +- M5STACK_PSRAM +- M5STACK_UNITCAM +- M5STACK_V2_PSRAM +- M5STACK_PSRAM +- M5STACK_WIDE - M5STACK +- Seeed Studio XIAO ESP32S3 SENSE +- TTGO T-CAMERA +- TTGO T-JOURNAL The software provides a **configuration web server**, that can be used to: @@ -276,14 +287,21 @@ The availability of PSRAM can be seen in the HTML status overview. Not all the boards are equipped with PSRAM: -| Board | PSRAM | -|--- |--- | -| ESP32CAM | Yes | -| ESP32CAM (USB-C) | No | -| AI THINKER | Yes | -| TTGO T-CAM | No | -| M5 STACK | No | -| WROVER KIT | Yes | +| Board | PSRAM | +|--- |--- | +| WROVER_KIT | Yes | +| ESP_EYE | Yes | +| ESP32S3_EYE | Yes | +| M5STACK_PSRAM | Yes | +| M5STACK_V2_PSRAM | Version B only | +| M5STACK_WIDE | Yes | +| M5STACK_ESP32CAM | No | +| M5STACK_UNITCAM | No | +| AI_THINKER | Yes | +| TTGO_T_JOURNAL | No | +| ESP32_CAM_BOARD | ? | +| ESP32S2_CAM_BOARD | ? | +| ESP32S3_CAM_LCD | ? | Depending on the image resolution, framerate and quality, the PSRAM must be enabled and/or the number of frame buffers increased to keep up with the data generated by the sensor. There are (a lot of?) boards around with faulty PSRAM. If the camera fails to initialize, this might be a reason. See on [Reddit](https://www.reddit.com/r/esp32/comments/z2hyns/i_have_a_faulty_psram_on_my_esp32cam_what_should/). @@ -315,6 +333,10 @@ esp32cam-rtsp depends on PlatformIO, Bootstrap 5 and Micro-RTSP by Kevin Hester. ## Change history +- January 2024 + - Moved settings to board definitions + - Added new boards + - Removed OTA to increase performance - Oktober 2023 - Added support for Seeed Xiao esp32s3 - New build system diff --git a/assets/boards/esp-lyrap-cam-v1.0-3d.png b/assets/boards/esp-lyrap-cam-v1.0-3d.png new file mode 100644 index 0000000..d3f0a6b Binary files /dev/null and b/assets/boards/esp-lyrap-cam-v1.0-3d.png differ diff --git a/assets/boards/schematics/ESP-LyraP-CAM_V1.1_SCH_20200511A.pdf b/assets/boards/schematics/ESP-LyraP-CAM_V1.1_SCH_20200511A.pdf new file mode 100644 index 0000000..65c463e Binary files /dev/null and b/assets/boards/schematics/ESP-LyraP-CAM_V1.1_SCH_20200511A.pdf differ diff --git a/boards/esp32cam_espressif_esp32s2_cam_board.json b/boards/esp32cam_espressif_esp32s2_cam_board.json new file mode 100644 index 0000000..e09166a --- /dev/null +++ b/boards/esp32cam_espressif_esp32s2_cam_board.json @@ -0,0 +1,61 @@ +{ + "build": { + "arduino":{ + "ldscript": "esp32s2_out.ld" + }, + "core": "esp32", + "extra_flags": [ + "'-D ESP32CAM_ESPRESSIF_ESP32S2_CAM_BOARD'", + "'-D BOARD_HAS_PSRAM'", + "'-D ARDUINO_USB_MODE=1'", + "'-D ARDUINO_USB_CDC_ON_BOOT=1'", + "'-D ARDUINO_RUNNING_CORE=1'", + "'-D ARDUINO_EVENT_RUNNING_CORE=1'", + "'-D CAMERA_CONFIG_PIN_PWDN=1'", + "'-D CAMERA_CONFIG_PIN_RESET=2'", + "'-D CAMERA_CONFIG_PIN_XCLK=42'", + "'-D CAMERA_CONFIG_PIN_SCCB_SDA=41'", + "'-D CAMERA_CONFIG_PIN_SCCB_SCL=18'", + "'-D CAMERA_CONFIG_PIN_Y9=16'", + "'-D CAMERA_CONFIG_PIN_Y8=39'", + "'-D CAMERA_CONFIG_PIN_Y7=40'", + "'-D CAMERA_CONFIG_PIN_Y6=15'", + "'-D CAMERA_CONFIG_PIN_Y5=12'", + "'-D CAMERA_CONFIG_PIN_Y4=5'", + "'-D CAMERA_CONFIG_PIN_Y3=13'", + "'-D CAMERA_CONFIG_PIN_Y2=14'", + "'-D CAMERA_CONFIG_PIN_VSYNC=38'", + "'-D CAMERA_CONFIG_PIN_HREF=4'", + "'-D CAMERA_CONFIG_PIN_PCLK=3'", + "'-D CAMERA_CONFIG_CLK_FREQ_HZ=20000000'", + "'-D CAMERA_CONFIG_LEDC_TIMER=LEDC_TIMER_0'", + "'-D CAMERA_CONFIG_LEDC_CHANNEL=LEDC_CHANNEL_0'", + "'-D CAMERA_CONFIG_FB_COUNT=2'", + "'-D CAMERA_CONFIG_FB_LOCATION=CAMERA_FB_IN_PSRAM'" ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "mcu": "esp32s2", + "variant": "esp32s2" + }, + "connectivity": [ + "wifi" + ], + "debug": { + "openocd_target": "esp32s2.cfg" + }, + "frameworks": [ + "arduino", + "espidf" + ], + "name": "Espressif ESP32-S2-Saola-1", + "upload": { + "flash_size": "4MB", + "maximum_ram_size": 327680, + "maximum_size": 4194304, + "require_upload_port": true, + "speed": 460800 + }, + "url": "https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/hw-reference/esp32s2/user-guide-esp-lyrap-cam-v1.1.html", + "vendor": "Espressif" +} diff --git a/boards/esp32cam_espressif_esp32s2_cam_header.json b/boards/esp32cam_espressif_esp32s2_cam_header.json new file mode 100644 index 0000000..b96bb0f --- /dev/null +++ b/boards/esp32cam_espressif_esp32s2_cam_header.json @@ -0,0 +1,61 @@ +{ + "build": { + "arduino":{ + "ldscript": "esp32s2_out.ld" + }, + "core": "esp32", + "extra_flags": [ + "'-D ESP32CAM_ESPRESSIF_ESP32S2_CAM_BOARD'", + "'-D BOARD_HAS_PSRAM'", + "'-D ARDUINO_USB_MODE=1'", + "'-D ARDUINO_USB_CDC_ON_BOOT=1'", + "'-D ARDUINO_RUNNING_CORE=1'", + "'-D ARDUINO_EVENT_RUNNING_CORE=1'", + "'-D CAMERA_CONFIG_PIN_PWDN=1'", + "'-D CAMERA_CONFIG_PIN_RESET=2'", + "'-D CAMERA_CONFIG_PIN_XCLK=42'", + "'-D CAMERA_CONFIG_PIN_SCCB_SDA=41'", + "'-D CAMERA_CONFIG_PIN_SCCB_SCL=18'", + "'-D CAMERA_CONFIG_PIN_Y9=16'", + "'-D CAMERA_CONFIG_PIN_Y8=39'", + "'-D CAMERA_CONFIG_PIN_Y7=40'", + "'-D CAMERA_CONFIG_PIN_Y6=15'", + "'-D CAMERA_CONFIG_PIN_Y5=13'", + "'-D CAMERA_CONFIG_PIN_Y4=5'", + "'-D CAMERA_CONFIG_PIN_Y3=12'", + "'-D CAMERA_CONFIG_PIN_Y2=14'", + "'-D CAMERA_CONFIG_PIN_VSYNC=38'", + "'-D CAMERA_CONFIG_PIN_HREF=4'", + "'-D CAMERA_CONFIG_PIN_PCLK=3'", + "'-D CAMERA_CONFIG_CLK_FREQ_HZ=20000000'", + "'-D CAMERA_CONFIG_LEDC_TIMER=LEDC_TIMER_0'", + "'-D CAMERA_CONFIG_LEDC_CHANNEL=LEDC_CHANNEL_0'", + "'-D CAMERA_CONFIG_FB_COUNT=2'", + "'-D CAMERA_CONFIG_FB_LOCATION=CAMERA_FB_IN_PSRAM'" ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "mcu": "esp32s2", + "variant": "esp32s2" + }, + "connectivity": [ + "wifi" + ], + "debug": { + "openocd_target": "esp32s2.cfg" + }, + "frameworks": [ + "arduino", + "espidf" + ], + "name": "Espressif ESP32-S2-Saola-1", + "upload": { + "flash_size": "4MB", + "maximum_ram_size": 327680, + "maximum_size": 4194304, + "require_upload_port": true, + "speed": 460800 + }, + "url": "https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/hw-reference/esp32s2/user-guide-esp-lyrap-cam-v1.1.html", + "vendor": "Espressif" +} diff --git a/boards/esp32cam_seeed_xiao_esp32s3_sense.json b/boards/esp32cam_seeed_xiao_esp32s3_sense.json index 6db9457..5b3d158 100644 --- a/boards/esp32cam_seeed_xiao_esp32s3_sense.json +++ b/boards/esp32cam_seeed_xiao_esp32s3_sense.json @@ -49,7 +49,7 @@ ] ], "mcu": "esp32s3", - "variant": "xiao_esp32s3" + "variant": "esp32s3" }, "connectivity": [ "bluetooth", diff --git a/include/camera_config.h b/include/camera_config.h deleted file mode 100644 index 3319382..0000000 --- a/include/camera_config.h +++ /dev/null @@ -1,218 +0,0 @@ -#pragma once - -#include - -// constexpr camera_config_t esp32cam_camera_settings = { -// .pin_pwdn = -1, -// .pin_reset = 15, -// .pin_xclk = 27, -// .pin_sscb_sda = 25, -// .pin_sscb_scl = 23, -// .pin_d7 = 19, -// .pin_d6 = 36, -// .pin_d5 = 18, -// .pin_d4 = 39, -// .pin_d3 = 5, -// .pin_d2 = 34, -// .pin_d1 = 35, -// .pin_d0 = 17, -// .pin_vsync = 22, -// .pin_href = 26, -// .pin_pclk = 21, -// .xclk_freq_hz = 20000000, -// .ledc_timer = LEDC_TIMER_0, -// .ledc_channel = LEDC_CHANNEL_0, -// .pixel_format = PIXFORMAT_JPEG, -// .frame_size = FRAMESIZE_SVGA, -// .jpeg_quality = 12, -// .fb_count = 1, -// .fb_location = CAMERA_FB_IN_DRAM, -// .grab_mode = CAMERA_GRAB_LATEST}; - -// constexpr camera_config_t esp_eye_camera_settings = { -// .pin_pwdn = -1, -// .pin_reset = -1, -// .pin_xclk = 4, -// .pin_sscb_sda = 18, -// .pin_sscb_scl = 23, -// .pin_d7 = 36, -// .pin_d6 = 37, -// .pin_d5 = 38, -// .pin_d4 = 39, -// .pin_d3 = 35, -// .pin_d2 = 14, -// .pin_d1 = 13, -// .pin_d0 = 34, -// .pin_vsync = 5, -// .pin_href = 27, -// .pin_pclk = 25, -// .xclk_freq_hz = 20000000, -// .ledc_timer = LEDC_TIMER_0, -// .ledc_channel = LEDC_CHANNEL_0, -// .pixel_format = PIXFORMAT_JPEG, -// .frame_size = FRAMESIZE_SVGA, -// .jpeg_quality = 12, -// .fb_count = 1, -// .fb_location = CAMERA_FB_IN_DRAM, -// .grab_mode = CAMERA_GRAB_LATEST}; - -// constexpr camera_config_t aithinker_camera_settings = { -// .pin_pwdn = 32, -// .pin_reset = -1, -// .pin_xclk = 0, -// .pin_sscb_sda = 26, -// .pin_sscb_scl = 27, -// .pin_d7 = 35, -// .pin_d6 = 34, -// .pin_d5 = 39, -// .pin_d4 = 36, -// .pin_d3 = 21, -// .pin_d2 = 19, -// .pin_d1 = 18, -// .pin_d0 = 5, -// .pin_vsync = 25, -// .pin_href = 23, -// .pin_pclk = 22, -// .xclk_freq_hz = 20000000, -// .ledc_timer = LEDC_TIMER_1, -// .ledc_channel = LEDC_CHANNEL_1, -// .pixel_format = PIXFORMAT_JPEG, -// .frame_size = FRAMESIZE_SVGA, -// .jpeg_quality = 12, -// .fb_count = 2, -// .fb_location = CAMERA_FB_IN_PSRAM, -// .grab_mode = CAMERA_GRAB_LATEST}; - -// constexpr camera_config_t ttgo_t_camera_settings = { -// .pin_pwdn = 26, -// .pin_reset = -1, -// .pin_xclk = 32, -// .pin_sscb_sda = 13, -// .pin_sscb_scl = 12, -// .pin_d7 = 39, -// .pin_d6 = 36, -// .pin_d5 = 23, -// .pin_d4 = 18, -// .pin_d3 = 15, -// .pin_d2 = 4, -// .pin_d1 = 14, -// .pin_d0 = 5, -// .pin_vsync = 27, -// .pin_href = 25, -// .pin_pclk = 19, -// .xclk_freq_hz = 20000000, -// .ledc_timer = LEDC_TIMER_0, -// .ledc_channel = LEDC_CHANNEL_0, -// .pixel_format = PIXFORMAT_JPEG, -// .frame_size = FRAMESIZE_SVGA, -// .jpeg_quality = 12, -// .fb_count = 1, -// .fb_location = CAMERA_FB_IN_DRAM, -// .grab_mode = CAMERA_GRAB_LATEST}; - -// constexpr camera_config_t m5stack_camera_settings = { -// .pin_reset = -1, -// .pin_xclk = 10, -// .pin_sscb_sda = 40, -// .pin_sscb_scl = 39, -// .pin_d7 = 48, -// .pin_d6 = 11, -// .pin_d5 = 12, -// .pin_d4 = 14, -// .pin_d3 = 16, -// .pin_d2 = 18, -// .pin_d1 = 17, -// .pin_d0 = 15, -// .pin_vsync = 38, -// .pin_href = 47, -// .pin_pclk = 13, -// .xclk_freq_hz = 20000000, -// .ledc_timer = LEDC_TIMER_0, -// .ledc_channel = LEDC_CHANNEL_0, -// .pixel_format = PIXFORMAT_JPEG, -// .frame_size = FRAMESIZE_SVGA, -// .jpeg_quality = 12, -// .fb_count = 1, -// .fb_location = CAMERA_FB_IN_DRAM, -// .grab_mode = CAMERA_GRAB_LATEST}; - -// constexpr camera_config_t wrover_kit_camera_settings = { -// .pin_pwdn = -1, -// .pin_reset = -1, -// .pin_xclk = 21, -// .pin_sscb_sda = 26, -// .pin_sscb_scl = 27, -// .pin_d7 = 35, -// .pin_d6 = 34, -// .pin_d5 = 39, -// .pin_d4 = 36, -// .pin_d3 = 19, -// .pin_d2 = 18, -// .pin_d1 = 5, -// .pin_d0 = 4, -// .pin_vsync = 25, -// .pin_href = 23, -// .pin_pclk = 22, -// .xclk_freq_hz = 20000000, -// .ledc_timer = LEDC_TIMER_0, -// .ledc_channel = LEDC_CHANNEL_0, -// .pixel_format = PIXFORMAT_JPEG, -// .frame_size = FRAMESIZE_SVGA, -// .jpeg_quality = 12, -// .fb_count = 2, -// .fb_location = CAMERA_FB_IN_PSRAM, -// .grab_mode = CAMERA_GRAB_LATEST}; - -// constexpr camera_config_t xiao_esp32s3_camera_settings = { -// .pin_pwdn = -1, -// .pin_reset = -1, -// .pin_xclk = 10, -// .pin_sscb_sda = 40, -// .pin_sscb_scl = 39, -// .pin_d7 = 48, -// .pin_d6 = 11, -// .pin_d5 = 12, -// .pin_d4 = 14, -// .pin_d3 = 16, -// .pin_d2 = 18, -// .pin_d1 = 17, -// .pin_d0 = 15, -// .pin_vsync = 38, -// .pin_href = 47, -// .pin_pclk = 13, -// .xclk_freq_hz = 20000000, -// .ledc_timer = LEDC_TIMER_0, -// .ledc_channel = LEDC_CHANNEL_0, -// .pixel_format = PIXFORMAT_JPEG, // for streaming -// .frame_size = FRAMESIZE_UXGA, -// .jpeg_quality = 12, -// .fb_count = 2, -// .fb_location = CAMERA_FB_IN_PSRAM, -// .grab_mode = CAMERA_GRAB_LATEST}; - -// constexpr camera_config_t m5stack_unitcams3_camera_settings = { -// .pin_pwdn = -1, -// .pin_reset = 15, -// .pin_xclk = 27, -// .pin_sscb_sda = 25, -// .pin_sscb_scl = 23, -// .pin_d7 = 19, -// .pin_d6 = 36, -// .pin_d5 = 18, -// .pin_d4 = 39, -// .pin_d3 = 5, -// .pin_d2 = 34, -// .pin_d1 = 35, -// .pin_d0 = 32, -// .pin_vsync = 22, -// .pin_href = 26, -// .pin_pclk = 21, -// .xclk_freq_hz = 20000000, -// .ledc_timer = LEDC_TIMER_0, -// .ledc_channel = LEDC_CHANNEL_0, -// .pixel_format = PIXFORMAT_JPEG, -// .frame_size = FRAMESIZE_SVGA, -// .jpeg_quality = 12, -// .fb_count = 1, -// .fb_location = CAMERA_FB_IN_PSRAM, -// .grab_mode = CAMERA_GRAB_LATEST}; \ No newline at end of file diff --git a/include/settings.h b/include/settings.h index 0e925f5..9422efa 100644 --- a/include/settings.h +++ b/include/settings.h @@ -11,36 +11,6 @@ #define RTSP_PORT 554 -#if 0 -#if defined(BOARD_ESP32CAM) -constexpr const char *board_name = "ESP32CAM"; -constexpr camera_config_t default_camera_config = esp32cam_camera_settings; -#elif defined(BOARD_AITHINKER_ESP32CAM) -constexpr const char *board_name = "AI-Thinker ESP32CAM"; -constexpr camera_config_t default_camera_config = aithinker_camera_settings; -#elif defined(BOARD_ESP_EYE) -constexpr const char *board_name = "ESP-EYE"; -constexpr camera_config_t default_camera_config = esp_eye_camera_settings; -#elif defined(BOARD_TTGO_T_CAMERA) -constexpr const char *board_name = "TTGO-T-CAMERA"; -constexpr camera_config_t default_camera_config = ttgo_t_camera_settings; -#elif defined(BOARD_M5STACK_ESP32CAM) -constexpr const char *board_name = "M5STACK-CAMERA"; -constexpr camera_config_t default_camera_config = m5stack_camera_settings; -#elif defined(BOARD_ESP32_WROVER_CAM) -constexpr const char *board_name = "WROVER-KIT"; -constexpr camera_config_t default_camera_config = wrover_kit_camera_settings; -#elif defined(BOARD_SEEED_XIAO_ESP32S3_SENSE) -constexpr const char *board_name = "Seed Xiao ESP32S3 Sense"; -constexpr camera_config_t default_camera_config = xiao_esp32s3_camera_settings; -#elif defined(BOARD_M5STACK_UNITCAMS3) -constexpr const char *board_name = "M5Stack UnitCamS3"; -constexpr camera_config_t default_camera_config = m5stack_unitcams3_camera_settings; -#else -#error No board defined -#endif -#endif - #define DEFAULT_FRAME_DURATION 200 #define DEFAULT_FRAME_SIZE "VGA (640x480)" #define DEFAULT_JPEG_QUALITY (psramFound() ? 12 : 14) @@ -68,4 +38,4 @@ constexpr camera_config_t default_camera_config = m5stack_unitcams3_camera_setti #define DEFAULT_DCW true #define DEFAULT_COLORBAR false -#define DEFAULT_LED_INTENSITY 0 +#define DEFAULT_LED_INTENSITY 0 \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index 7787084..d5eea6e 100644 --- a/platformio.ini +++ b/platformio.ini @@ -13,6 +13,8 @@ #default_envs = esp32cam_ai_thinker #default_envs = esp32cam_espressif_esp_eye #default_envs = esp32cam_espressif_esp32s3_cam_lcd +#default_envs = esp32cam_espressif_esp32s2_cam_board +#default_envs = esp32cam_espressif_esp32s2_cam_header #default_envs = esp32cam_espressif_esp32s3_eye #default_envs = esp32cam_m5stack_esp32cam #default_envs = esp32cam_m5stack_psram @@ -60,6 +62,22 @@ board = esp32cam_ai_thinker [env:esp32cam_espressif_esp_eye] board = esp32cam_espressif_esp_eye +[env:esp32cam_espressif_esp32s2_cam_board] +# Use board connection +# The 18 pin header on the board has Y5 and Y3 swapped +board = esp32cam_espressif_esp32s2_cam_board +build_flags = + ${env.build_flags} + -D IOTWEBCONF_DEBUG_DISABLED + +[env:esp32cam_espressif_esp32s2_cam_header] +# Use header connection +# The 18 pin header on the board has Y5 and Y3 swapped +board = esp32cam_espressif_esp32s2_cam_header +build_flags = + ${env.build_flags} + -D IOTWEBCONF_DEBUG_DISABLED + [env:esp32cam_espressif_esp32s3_cam_lcd] board = esp32cam_espressif_esp32s3_cam_lcd diff --git a/src/main.cpp b/src/main.cpp index 2f69b75..6f814ba 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,4 @@ #include -#include #include #include #include @@ -8,7 +7,6 @@ #include #include #include -#include #include #include #include @@ -210,6 +208,7 @@ void handle_stream() client.stop(); log_v("stopped streaming"); } + esp_err_t initialize_camera() { log_v("initialize_camera"); @@ -302,8 +301,6 @@ void start_rtsp_server() void on_connected() { log_v("on_connected"); - // Start (OTA) Over The Air programming when connected - ArduinoOTA.begin(); // Start the RTSP Server if initialized if (camera_init_result == ESP_OK) start_rtsp_server(); @@ -327,8 +324,11 @@ void setup() digitalWrite(LED_BUILTIN, false); #endif +// ESP32S2 has no serial port +#ifndef ARDUINO_USB_CDC_ON_BOOT Serial.begin(115200); Serial.setDebugOutput(true); +#endif log_i("CPU Freq: %d Mhz, %d core(s)", getCpuFrequencyMhz(), ESP.getChipCores()); log_i("Free heap: %d bytes", ESP.getFreeHeap()); @@ -394,32 +394,11 @@ void setup() web_server.onNotFound([]() { iotWebConf.handleNotFound(); }); - - ArduinoOTA - .setPassword(OTA_PASSWORD) - .onStart([]() - { log_w("Starting OTA update: %s", ArduinoOTA.getCommand() == U_FLASH ? "sketch" : "filesystem"); }) - .onEnd([]() - { log_w("OTA update done!"); }) - .onProgress([](unsigned int progress, unsigned int total) - { log_i("OTA Progress: %u%%\r", (progress / (total / 100))); }) - .onError([](ota_error_t error) - { - switch (error) - { - case OTA_AUTH_ERROR: log_e("OTA: Auth Failed"); break; - case OTA_BEGIN_ERROR: log_e("OTA: Begin Failed"); break; - case OTA_CONNECT_ERROR: log_e("OTA: Connect Failed"); break; - case OTA_RECEIVE_ERROR: log_e("OTA: Receive Failed"); break; - case OTA_END_ERROR: log_e("OTA: End Failed"); break; - default: log_e("OTA error: %u", error); - } }); } void loop() { iotWebConf.doLoop(); - ArduinoOTA.handle(); if (camera_server) camera_server->doLoop();