ESP32-CAM Guru Meditation Error

Hello again,

I’ve run into a new problem. I was moreso wondering if anyone had run into this type of error before and how to identify memory address issues. After compiling the following code:



// Fill-in information from your Blynk Template here
#define BLYNK_TEMPLATE_ID "xxxxx"
#define BLYNK_DEVICE_NAME "xxxxx"
#define PHOTOCLICK V5
#define LED 4
#define DOORUP V7
#define DOORDOWN V6
#define BLYNK_HEARTBEAT 30
#define BLYNK_FIRMWARE_VERSION        "0.1.0"
#define BLYNK_PRINT Serial
//#define BLYNK_DEBUG
#define APP_DEBUG

// Uncomment your board, or configure a custom board in Settings.h
//define USE_WROVER_BOARD

#include "BlynkEdgent.h"
#include "esp_camera.h"
#include "Arduino.h"
#include <WiFi.h>
#include <WiFiClient.h> 
#include <ESP32_FTPClient.h>
#include "soc/soc.h"           // Disable brownout problems
#include "soc/rtc_cntl_reg.h"  // Disable brownout problems

WidgetLED led1(V1);
WidgetLED led2(V2);
WidgetLED led3(V3);
WidgetLED led4(V4);

// Pin definition for CAMERA_MODEL_AI_THINKER
#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


char ftp_server[] = "files.000webhost.com";
char ftp_user[] = "xxxxx";
char ftp_pass[] ="xxxxx";

bool takePhoto = false; 
bool loading = false;
String displayImage = "https://coopcommandimages.000webhostapp.com/uploads/1.png";
String coopImageURL = "https://coopcommandimages.000webhostapp.com/uploads/1.png";
String coopImage = "coopPic.jpg";
int i = 1;
int ic = 1;
int imageFlip = 1;
char coopRx; // Info received from CoopCommand
bool newDataRx = false; //has CoopCam received new data from CoopCommand

// you can pass a FTP timeout and debbug mode on the last 2 arguments
ESP32_FTPClient ftp (ftp_server,ftp_user,ftp_pass, 5000, 2);

void setup()
{
  
  Serial.begin(115200);
  delay(100);

 pinMode(LED, OUTPUT);
 timer.setInterval(1000,coopCom);
 timer.setInterval(250,loadingImage);
  Blynk.setProperty(V0, "url", 1, coopImageURL);
  Blynk.virtualWrite(V0, 1); 
  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector
  
  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; 
  
  if(psramFound()){
    config.frame_size = FRAMESIZE_SVGA; // FRAMESIZE_ + QVGA|CIF|VGA|SVGA|XGA|SXGA|UXGA
    config.jpeg_quality = 20;
    config.fb_count = 2;
  } 
  else {
    config.frame_size = FRAMESIZE_VGA;
    config.jpeg_quality = 20;
    config.fb_count = 1;
  }
  
  // Init Camera
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
//    Serial.printf("Camera init failed with error 0x%x", err);
    return;
  }

  BlynkEdgent.begin();
}

BLYNK_CONNECTED() {
    Blynk.syncAll();
}

void loadingImage( void ) {
      if (loading) {
      displayImage= "https://coopcommandimages.000webhostapp.com/uploads/";
      displayImage+= i;
      displayImage+= ".png";
      Blynk.setProperty(V0, "url", 1, displayImage); 
      if (i <= 6) {
      i ++;
      }
      else if (i > 6) {
        i = 1;
      }
      }
      else if (!loading) {
        if (ic == 1) {
        coopImageURL = "https://coopcommandimages.000webhostapp.com/uploads/coopPic2.jpg";
        }
        else if (ic == 2) {
        coopImageURL = "https://coopcommandimages.000webhostapp.com/uploads/coopPic3.jpg";
        }
        else if (ic == 3) {
          coopImageURL = "https://coopcommandimages.000webhostapp.com/uploads/coopPic4.jpg";
        }
        Blynk.setProperty(V0, "url", 1, coopImageURL);
        Blynk.virtualWrite(V0, 1);  
        i = 1;      
      }

}

 
void sendPhoto ( void ) {
   if (takePhoto) {

   camera_fb_t * fb = NULL;
  
  // Take Picture with Camera
  fb = esp_camera_fb_get(); 
  digitalWrite(LED, LOW); 
  delay (50);
  if(!fb) {
//    Serial.println("Camera capture failed");
    ESP.restart();
    return;
  }

if (imageFlip == 1) {
  ftp.OpenConnection();
  // Create the new file and send the image
  ftp.ChangeWorkDir("/public_html/uploads/");
  ftp.InitFile("Type I");
  ftp.NewFile("coopPic2.jpg");
  ftp.WriteData( fb->buf, fb->len );
  ftp.CloseFile();
  ftp.CloseConnection();
  takePhoto = false;
  imageFlip = 2;
  ic = 1;
  esp_camera_fb_return(fb);
    Serial.print('N');
   }
else if (imageFlip == 2) {
   ftp.OpenConnection();
  // Create the new file and send the image
  ftp.ChangeWorkDir("/public_html/uploads/");
  ftp.InitFile("Type I");
  ftp.NewFile("coopPic3.jpg");
  ftp.WriteData( fb->buf, fb->len );
  ftp.CloseFile();
  ftp.CloseConnection();
  takePhoto = false;
  imageFlip = 3;
  ic = 2;
  esp_camera_fb_return(fb);
  Serial.print('N');
   }
else if (imageFlip == 3) {
   ftp.OpenConnection();
  // Create the new file and send the image
  ftp.ChangeWorkDir("/public_html/uploads/");
  ftp.InitFile("Type I");
  ftp.NewFile("coopPic4.jpg");
  ftp.WriteData( fb->buf, fb->len );
  ftp.CloseFile();
  ftp.CloseConnection();
  takePhoto = false;
  imageFlip = 1;
  ic = 3;
  esp_camera_fb_return(fb);
  Serial.print('N');
   }
}
}

void coopCom ( void ) {

  if (Serial.available() > 0) {
    coopRx = Serial.read();
    newDataRx = true;
  }
  if (newDataRx == true) {
    if (coopRx == 'O') { //If CoopCommand says the door is up
      led1.on();
      led2.off();
      led3.off();
      led4.off();
      newDataRx = false;
    }
    if (coopRx == 'S') { //If CoopCommand says the door is down

      led1.off();
      led2.on();
      led3.off();
      led4.off();
      newDataRx = false;
    }
    if (coopRx == 'U') { //If CoopCommand says the door is opening
      led1.off();
      led2.off();
      led3.on();
      led4.off();
      newDataRx = false;
    }
    if (coopRx == 'D') { //If CoopCommand says the door is closing
      led1.off();
      led2.off();
      led3.off();
      led4.on();
      newDataRx = false;
    }
  }
}

BLYNK_WRITE(DOORUP) {
Serial.print('U');
}

BLYNK_WRITE(DOORDOWN) {
Serial.print('D');
}

BLYNK_WRITE(V5) {
digitalWrite(LED, HIGH);
Serial.print('L'); 
takePhoto = true;
loading = true;
loadingImage();
timer.setTimeout(250, sendPhoto);
timer.setTimeout(3000, []()
{
  loading = false;
  loadingImage();
});
}

void loop() {
  BlynkEdgent.run();
}



I can successfully load it into my ESP32-CAM AI Thinker. However, as soon as I reboot the board, I get the following in the serial monitor:

rst:0xc (SW_CPU_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:1
load:0x3fff0018,len:4
load:0x3fff001c,len:1216
ho 0 tail 12 room 4
load:0x40078000,len:10944
load:0x40080400,len:6388
entry 0x400806b4

Guru Meditation Error: Core  1 panic'ed (LoadProhibited). Exception was unhandled.
Core 1 register dump:
PC      : 0x4017d867  PS      : 0x00060030  A0      : 0x800dea7a  A1      : 0x3ffb1e40  
A2      : 0x00000000  A3      : 0x00000000  A4      : 0x400812cc  A5      : 0x00000000  
A6      : 0x3ffc3750  A7      : 0x00000001  A8      : 0x80164842  A9      : 0x3ffb1df0  
A10     : 0x00000105  A11     : 0x0000040e  A12     : 0x00000000  A13     : 0x00000000  
A14     : 0x400812cc  A15     : 0x00000000  SAR     : 0x00000020  EXCCAUSE: 0x0000001c  
EXCVADDR: 0x00000000  LBEG    : 0x4000c2e0  LEND    : 0x4000c2f6  LCOUNT  : 0xffffffff  

ELF file SHA256: 0000000000000000

Backtrace: 0x4017d867:0x3ffb1e40 0x400dea77:0x3ffb1e60 0x400dead5:0x3ffb1e90 0x400d2173:0x3ffb1eb0 0x400d480d:0x3ffb1ed0 0x400e0826:0x3ffb1fb0 0x4008e522:0x3ffb1fd0

Rebooting...

I’ve played around with a couple pin conflictions I’ve found between the settings.h file and the pins used by the AI-Thinker board and have had the error addresses change however I’m not sure where I should begin my search to locate the source of this issue.

My googling indicates the error means an illegal attempt to write to memory but I’m not sure how to narrow down in my code where I should be looking to track this down.

Any help is appreciated,

Thanks.

What board type are you choosing when you compile the code, and what partition scheme are you using?

Pete.

Hey Pete,

I tried compiling first as an AI-thinker ESP32 CAM which has no partition options. Then I remembered that I need to upload as an ESP32DEV module to have OTA available and the partition scheme I use is:

Minimal Spiffs (1.9Mb App with OTA, 190kb spiffs).

The error is the same with either board selected, but it seems to reference different memory addresses depending on board, and changing the reset pin in settings.h

I thought the ESP32 Cam board was a WROVER chip with PSRAM. If you compile as an ESP32 Dev Board then the PSRAM isn’t available.

What’s the program size when it compiles? If you’re using SPIFFS for OTA then the allocated partition needs to be at least large enough to hold the program.

Pete.

Hmm, did run across somewhere that allows adds in additional partition schemes for the AI-Thinker ESP32-CAM, maybe that would be a better option.

Do you have a preferred partition scheme you’ve been using with ESP32 cams and Blynk with OTA?

The program size definitely might be the issue, it is 1094626 bytes, or just over 1000kb.

I guess I should start digging into see how Edgent does OTA, if it is using Spiffs this is likely my problem.

The only ESP32 CAM board that I have is currently in a different country, so I can’t help with that I’m afraid.

Pete.

No problem, was worth the ask!

I’ll play around with partitions and see what I come up with, I agree with you that seems like a great place to start.

Making progress!

Using the standard partition scheme I have a successful upload, and the device successfully boots into provisioning mode. I can provision it, it reboots, connects to the cloud, awesome!

My app seems to work as before mostly, with the exception of taking a photo. As soon as I send the request to take a photo, the device crashes again with another, but different Guru mediation error. I have attached the newest error.

I definitely have a pin and memory conflict with my ESP CAM and Blynk but I am unsure how to read these memory addresses to track it down.

rst:0xc (SW_CPU_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:1
load:0x3fff0018,len:4
load:0x3fff001c,len:1216
ho 0 tail 12 room 4
load:0x40078000,len:10944
load:0x40080400,len:6388
entry 0x400806b4
17696290281953066862N[1225] Hold the button to reset configuration...
[1226] Hold the button to reset configuration...
[1227] Hold the button to reset configuration...
[1228] Hold the button to reset configuration...
[1232] Hold the button to reset configuration...
[1237] Hold the button to reset configuration...
[1243] Hold the button to reset configuration...
[1245] Hold the button to reset configuration...
[1250] Hold the button to reset configuration...
[1255] Hold the button to reset configuration...
[1258] Hold the button to reset configuration...
[1263] Hold the button to reset configuration...
[1267] Hold the button to reset configuration...
[1273] Hold the button to reset configuration...
[1276] Hold the button to reset configuration...
[1281] Hold the button to reset configuration...
[1284] Hold the button to reset configuration...
[1289] Hold the button to reset configuration...
[E][camera.c:1327] camera_init(): gpio_installGuru Meditation Error: Core  1 panic'ed (Interrupt wdt timeout on CPU1)
Core 1 register dump:
PC      : 0x40092980  PS      : 0x00060034  A0      : 0x80091b97  A1      : 0x3ffc0fa0  
A2      : 0x3ffafd54  A3      : 0x3ffb8074  A4      : 0x00000001  A5      : 0x00000001  
A6      : 0x00060023  A7      : 0x00000000  A8      : 0x3ffb8074  A9      : 0x3ffb8074  
A10     : 0x00000018  A11     : 0x00000018  A12     : 0x00000001  A13     : 0x00000001  
A14     : 0x00060021  A15     : 0x00000000  SAR     : 0x0000000a  EXCCAUSE: 0x00000006  
EXCVADDR: 0x00000000  LBEG    : 0x4008d785  LEND    : 0x4008d795  LCOUNT  : 0xfffffffc  
Core 1 was running in ISR context:
EPC1    : 0x4009107a  EPC2    : 0x00000000  EPC3    : 0x00000000  EPC4    : 0x40092980

ELF file SHA256: 0000000000000000

Backtrace: 0x40092980:0x3ffc0fa0 0x40091b94:0x3ffc0fc0 0x4008fe73:0x3ffc0fe0 0x400e0ba9:0x3ffc1020 0x400dee25:0x3ffc1040 0x4017b551:0x3ffc1060 0x400d16ad:0x3ffc1080 0x400d25f9:0x3ffc10a0 0x40081379:0x3ffc10c0 0x4008aab5:0x3ffc10e0 0x40009217:0x3ffb1d30 0x40007d13:0x3ffb1d50 0x40007c69:0x3ffb1d70 0x40008106:0x3ffb1d90 0x400e0fc6:0x3ffb1e20 0x400e392d:0x3ffb1e80 0x400e3ab9:0x3ffb1eb0 0x400d4982:0x3ffb1ee0 0x400e064a:0x3ffb1fb0 0x4009007a:0x3ffb1fd0

I used ESP CAM .
USE HUGE PP Partition scheme

Thanks for the reply! Have you used this with “new” Blynk? I’ll give it a shot, I’ve migrated my error a bit further down the line now haha

I used camera web server a month ago.

Got the errorm

Browsed YouTube
Then changed to huge pp

Ok, with suggested changes in partitioning, I’ve gotten to the point where the error is caused by the camera initializing. It seems I am attempting to write illegal to both cores at this point. The device will crash, dump this error and reboot, successfully connecting to the cloud and working fine until I ask the device to take another picture, at which point it fails again.

[E][camera.c:1327] camera_init(): gpio_install_isr_service failed (105)
[1306] Hold the button to reset configuration...
[E][cameGuru Meditation Error: Core  1 panic'ed (Interrupt wdt timeout on CPU1)
Core 1 register dump:
PC      : 0x40092980  PS      : 0x00060e34  A0      : 0x80091b97  A1      : 0x3ffc0fa0  
A2      : 0x3ffafd88  A3      : 0x3ffb8074  A4      : 0x00000001  A5      : 0x00000001  
A6      : 0x00060e23  A7      : 0x00000000  A8      : 0x3ffb8074  A9      : 0x3ffb8074  
A10     : 0x00000018  A11     : 0x00000018  A12     : 0x00000001  A13     : 0x00000001  
A14     : 0x00060e21  A15     : 0x00000000  SAR     : 0x0000000a  EXCCAUSE: 0x00000006  
EXCVADDR: 0x00000000  LBEG    : 0x4008d785  LEND    : 0x4008d795  LCOUNT  : 0xfffffffb  
Core 1 was running in ISR context:
EPC1    : 0x4009107a  EPC2    : 0x00000000  EPC3    : 0x00000000  EPC4    : 0x40092980

ELF file SHA256: 0000000000000000

Backtrace: 0x40092980:0x3ffc0fa0 0x40091b94:0x3ffc0fc0 0x4008fe73:0x3ffc0fe0 0x400e0ba9:0x3ffc1020 0x400dee25:0x3ffc1040 0x4017b551:0x3ffc1060 0x400d16ad:0x3ffc1080 0x400d25f9:0x3ffc10a0 0x40081379:0x3ffc10c0 0x4008aab5:0x3ffc10e0 0x40009217:0x3ffb1d60 0x40007d13:0x3ffb1d80 0x40007c69:0x3ffb1da0 0x40008106:0x3ffb1dc0 0x400e0fc6:0x3ffb1e50 0x400e3ad5:0x3ffb1eb0 0x400d4982:0x3ffb1ee0 0x400e064a:0x3ffb1fb0 0x4009007a:0x3ffb1fd0

Core 0 register dump:
PC      : 0x40091085  PS      : 0x00060e34  A0      : 0x800921d5  A1      : 0x3ffb3b70  
A2      : 0x3ffc04c0  A3      : 0x0000cdcd  A4      : 0xb33fffff  A5      : 0x00000001  
A6      : 0x00060e23  A7      : 0x0000abab  A8      : 0x0000abab  A9      : 0x3ffb3b80  
A10     : 0x3ffb98fc  A11     : 0x3ffb98fc  A12     : 0x00060620  A13     : 0x00000001  
A14     : 0x00060820  A15     : 0x00000000  SAR     : 0x00000000  EXCCAUSE: 0x00000006  
EXCVADDR: 0x00000000  LBEG    : 0x4008d26e  LEND    : 0x4008d279  LCOUNT  : 0x00000000  

ELF file SHA256: 0000000000000000

Backtrace: 0x40091085:0x3ffb3b70 0x400921d2:0x3ffb3ba0 0x4008fddb:0x3ffb3bc0 0x4014c4f6:0x3ffb3c00 0x4014c7e4:0x3ffb3c20 0x401427a8:0x3ffb3c40 0x40142806:0x3ffb3c60 0x40150a32:0x3ffb3c80 0x40150b92:0x3ffb3ca0 0x40150a11:0x3ffb3cc0 0x40150bc4:0x3ffb3ce0 0x4014d00c:0x3ffb3d00 0x4009007a:0x3ffb3d30

Rebooting...

I think it’s time to re-visit your rather messy pin assignments, and comment-out the ones that you think aren’t being used. Hopefully then you’ll start to see the wood in amongst the trees.

Pete.

Sounds good Pete, I agree it is definitely something to do with the camera pins as the initialization fails on camera init.

Thanks everyone for the pointers, I’ll dig in and see what I can find. I’ll come back to this and update with my findings.

I’ve gone through my code and cleaned it up a bit for readability. I’m a bit confused about the “messy pin assignments”, all pins assigned are required. Are you maybe referencing how there is a section for defining the pins and then lower down in setup the initialization? That isn’t a double declaration but rather setting the pins up and then initializing the camera with those pins.

Here is my cleaned up code, followed by the error received when requesting a photo through the app. The behaviour is a little inconsistent. Sometimes the device fails when trying to initialize the camera on boot up, at which point it will reboot 2-3 times before successfully booting with a connection to Blynk. Once it is connected it is stable and all functions work with the exception of taking a photo. Taking a photo causes the device to crash with the same error (sometimes slightly different memory addresses) at which point it will reboot, sometimes immediately with a successful boot and other times with the above mentioned 2-3 reboot cycle before success.

Is it possible that Blynk Edgent is storing either the provisioning setup or OTA in the same area the camera is attempting to write too? If so does anyone know how to adjust the write address for the camera or have experience in anything like this? Otherwise, any ideas on the reason for the crash if you feel this isn’t the issue?

Code:

 
// Fill-in information for your Blynk connection here


#define BLYNK_TEMPLATE_ID "xxxx"
#define BLYNK_DEVICE_NAME "xxxx"
#define BLYNK_HEARTBEAT 30
#define BLYNK_FIRMWARE_VERSION        "0.1.0"
#define BLYNK_PRINT Serial
//#define BLYNK_DEBUG
#define APP_DEBUG

// Pin Defines

#define PHOTOCLICK V5
#define LED 4
#define DOORUP V7
#define DOORDOWN V6


// Uncomment your board, or configure a custom board in Settings.h
//define USE_WROVER_BOARD

// Libraries

#include "BlynkEdgent.h"
#include "esp_camera.h"
#include "Arduino.h"
#include <WiFi.h>
#include <WiFiClient.h> 
#include <ESP32_FTPClient.h>
#include "soc/soc.h"           // Disable brownout problems
#include "soc/rtc_cntl_reg.h"  // Disable brownout problems

// Blynkv LED setup

WidgetLED led1(V1);
WidgetLED led2(V2);
WidgetLED led3(V3);
WidgetLED led4(V4);

// Pin definition for CAMERA_MODEL_AI_THINKER

#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

//FTP server information and setup

char ftp_server[] = "files.000webhost.com";
char ftp_user[] = "xxxxx";
char ftp_pass[] ="xxxxx";
ESP32_FTPClient ftp (ftp_server,ftp_user,ftp_pass, 5000, 2); // you can pass a FTP timeout and debbug mode on the last 2 arguments

// Variables 

bool takePhoto = false; 
bool loading = false;
String displayImage = "https://coopcommandimages.000webhostapp.com/uploads/1.png";
String coopImageURL = "https://coopcommandimages.000webhostapp.com/uploads/1.png";
String coopImage = "coopPic.jpg";
int i = 1;
int ic = 1;
int imageFlip = 1;
char coopRx; // Info received from CoopCommand
bool newDataRx = false; //has CoopCam received new data from CoopCommand


void setup() {
  
  // Startup Serial, Blynk, Blynk Timers and initial imamge. Disable brownout detector
  Serial.begin(115200);
  delay(50);
  BlynkEdgent.begin();
  pinMode(LED, OUTPUT);
  timer.setInterval(1000,coopCom);
  timer.setInterval(250,loadingImage);
  Blynk.setProperty(V0, "url", 1, coopImageURL);
  Blynk.virtualWrite(V0, 1); 
  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector
  
  // Configure the Camera

  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; 
  
// Image settings depending on PSRAM availability

  if(psramFound()){
    config.frame_size = FRAMESIZE_SVGA; // FRAMESIZE_ + QVGA|CIF|VGA|SVGA|XGA|SXGA|UXGA
    config.jpeg_quality = 20;
    config.fb_count = 2;
  } 
  else {
    config.frame_size = FRAMESIZE_VGA;
    config.jpeg_quality = 20;
    config.fb_count = 1;
  }
  // Initialize Camera
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    return;
  }

}


// Changes image in Blynk app depending on camera state
void loadingImage( void ) {
if (loading) {
  displayImage= "https://coopcommandimages.000webhostapp.com/uploads/";
  displayImage+= i;
  displayImage+= ".png";
  Blynk.setProperty(V0, "url", 1, displayImage); 
  if (i <= 6) {
    i ++;
  }
  else if (i > 6) {
    i = 1;
  }
}
else if (!loading) {
  if (ic == 1) {
    coopImageURL = "https://coopcommandimages.000webhostapp.com/uploads/coopPic2.jpg";
  }
  else if (ic == 2) {
    coopImageURL = "https://coopcommandimages.000webhostapp.com/uploads/coopPic3.jpg";
  }
  else if (ic == 3) {
    coopImageURL = "https://coopcommandimages.000webhostapp.com/uploads/coopPic4.jpg";
  }
Blynk.setProperty(V0, "url", 1, coopImageURL);
Blynk.virtualWrite(V0, 1);  
i = 1;      
}
}

// Sends a new image to the FTP server 

void sendPhoto ( void ) {
   if (takePhoto) {

   camera_fb_t * fb = NULL;
  
  // Take Picture with Camera
  fb = esp_camera_fb_get(); 
  digitalWrite(LED, LOW); 
  delay (50);
  if(!fb) {
//    Serial.println("Camera capture failed");
    ESP.restart();
    return;
  }

if (imageFlip == 1) {
  ftp.OpenConnection();
  // Create the new file and send the image
  ftp.ChangeWorkDir("/public_html/uploads/");
  ftp.InitFile("Type I");
  ftp.NewFile("coopPic2.jpg");
  ftp.WriteData( fb->buf, fb->len );
  ftp.CloseFile();
  ftp.CloseConnection();
  takePhoto = false;
  imageFlip = 2;
  ic = 1;
  esp_camera_fb_return(fb);
    Serial.print('N');
   }
else if (imageFlip == 2) {
   ftp.OpenConnection();
  // Create the new file and send the image
  ftp.ChangeWorkDir("/public_html/uploads/");
  ftp.InitFile("Type I");
  ftp.NewFile("coopPic3.jpg");
  ftp.WriteData( fb->buf, fb->len );
  ftp.CloseFile();
  ftp.CloseConnection();
  takePhoto = false;
  imageFlip = 3;
  ic = 2;
  esp_camera_fb_return(fb);
  Serial.print('N');
   }
else if (imageFlip == 3) {
   ftp.OpenConnection();
  // Create the new file and send the image
  ftp.ChangeWorkDir("/public_html/uploads/");
  ftp.InitFile("Type I");
  ftp.NewFile("coopPic4.jpg");
  ftp.WriteData( fb->buf, fb->len );
  ftp.CloseFile();
  ftp.CloseConnection();
  takePhoto = false;
  imageFlip = 1;
  ic = 3;
  esp_camera_fb_return(fb);
  Serial.print('N');
   }
}
}

// Serial communication with CoopCommand board

void coopCom ( void ) {

  if (Serial.available() > 0) {
    coopRx = Serial.read();
    newDataRx = true;
  }
  if (newDataRx == true) {
    if (coopRx == 'O') { //If CoopCommand says the door is up
      led1.on();
      led2.off();
      led3.off();
      led4.off();
      newDataRx = false;
    }
    if (coopRx == 'S') { //If CoopCommand says the door is down

      led1.off();
      led2.on();
      led3.off();
      led4.off();
      newDataRx = false;
    }
    if (coopRx == 'U') { //If CoopCommand says the door is opening
      led1.off();
      led2.off();
      led3.on();
      led4.off();
      newDataRx = false;
    }
    if (coopRx == 'D') { //If CoopCommand says the door is closing
      led1.off();
      led2.off();
      led3.off();
      led4.on();
      newDataRx = false;
    }
  }
}

// Put the door up

BLYNK_WRITE(DOORUP) {
Serial.print('U');
}

// Put the door down

BLYNK_WRITE(DOORDOWN) {
Serial.print('D');
}

// Take a photo and display it in the app

BLYNK_WRITE(V5) {
digitalWrite(LED, HIGH);
Serial.print('L'); 
takePhoto = true;
loading = true;
loadingImage();
timer.setTimeout(250, sendPhoto);
timer.setTimeout(3000, []()
{
  loading = false;
  loadingImage();
});
}

// Sync app and device when connected

BLYNK_CONNECTED() {
    Blynk.syncAll();
}

void loop() {
  BlynkEdgent.run();
}

Error:

[E][camera.c:1327] camera_init(): gpio_install_isr_service failed (105)
[1353] Hold the button to reset configuration...
[E][camera.c:14Guru Meditation Error: Core  1 panic'ed (Interrupt wdt timeout on CPU1)
Core 1 register dump:
PC      : 0x4009297a  PS      : 0x00060e34  A0      : 0x80091b97  A1      : 0x3ffc0fa0  
A2      : 0x3ffb81f0  A3      : 0x3ffb8074  A4      : 0x00000001  A5      : 0x00000001  
A6      : 0x00060e23  A7      : 0x00000000  A8      : 0x3ffb8074  A9      : 0x3ffb8074  
A10     : 0x00000018  A11     : 0x00000018  A12     : 0x00000001  A13     : 0x00000001  
A14     : 0x00060e21  A15     : 0x00000000  SAR     : 0x0000000a  EXCCAUSE: 0x00000006  
EXCVADDR: 0x00000000  LBEG    : 0x4008d785  LEND    : 0x4008d795  LCOUNT  : 0xfffffffb  
Core 1 was running in ISR context:
EPC1    : 0x4009107a  EPC2    : 0x00000000  EPC3    : 0x00000000  EPC4    : 0x4009297a

ELF file SHA256: 0000000000000000

Backtrace: 0x4009297a:0x3ffc0fa0 0x40091b94:0x3ffc0fc0 0x4008fe73:0x3ffc0fe0 0x400e0b65:0x3ffc1020 0x400dedf1:0x3ffc1040 0x4017b509:0x3ffc1060 0x400d16a5:0x3ffc1080 0x400d25f1:0x3ffc10a0 0x40081379:0x3ffc10c0 0x4008aab5:0x3ffc10e0 0x4000921c:0x3ffb1d60 0x40007d13:0x3ffb1d80 0x40007c69:0x3ffb1da0 0x40008106:0x3ffb1dc0 0x400e0f82:0x3ffb1e50 0x400e3a91:0x3ffb1eb0 0x400d4951:0x3ffb1ee0 0x400e0606:0x3ffb1fb0 0x4009007a:0x3ffb1fd0

Core 0 register dump:
PC      : 0x4009107a  PS      : 0x00060e34  A0      : 0x800921d5  A1      : 0x3ffb3d70  
A2      : 0x3ffc04c0  A3      : 0x0000cdcd  A4      : 0xb33fffff  A5      : 0x00000001  
A6      : 0x00060e23  A7      : 0x0000abab  A8      : 0x0000abab  A9      : 0x3ffb3d80  
A10     : 0x3ffb32a0  A11     : 0x3ffb32a0  A12     : 0x00060620  A13     : 0x00000001  
A14     : 0x00060820  A15     : 0x00000000  SAR     : 0x00000000  EXCCAUSE: 0x00000006  
EXCVADDR: 0x00000000  LBEG    : 0x4008d26e  LEND    : 0x4008d279  LCOUNT  : 0x00000000  

ELF file SHA256: 0000000000000000

Backtrace: 0x4009107a:0x3ffb3d70 0x400921d2:0x3ffb3da0 0x4008fddb:0x3ffb3dc0 0x4014c4ae:0x3ffb3e00 0x4014c79c:0x3ffb3e20 0x40142760:0x3ffb3e40 0x401427be:0x3ffb3e60 0x401509ea:0x3ffb3e80 0x40150b4a:0x3ffb3ea0 0x401509c9:0x3ffb3ec0 0x40150b7c:0x3ffb3ee0 0x4014cfc4:0x3ffb3f00 0x4009007a:0x3ffb3f30

Rebooting...
ets Jun  8 2016 00:22:57

rst:0xc (SW_CPU_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:1
load:0x3fff0018,len:4
load:0x3fff001c,len:1216
ho 0 tail 12 room 4
load:0x40078000,len:10944
load:0x40080400,len:6388
entry 0x400806b4
[1044] 

I was referring to this…

Part of your cleaned-up code, but not the full picture, as none of your #included files are included.

Pete.

Thanks for the quick reply Pete.

Sorry for the confusion. Initially I didn’t understand the declarations but after some schematic reading they are all needed. I should have clarified.

Here are the additional included files in separate code blocks:

BlynkEdgent.h


extern "C" {
  void app_loop();
  void eraseMcuConfig();
  void restartMCU();
}

#include "Settings.h"
#include <BlynkSimpleEsp32_SSL.h>

#ifndef BLYNK_NEW_LIBRARY
#error "Old version of Blynk library is in use. Please replace it with the new one."
#endif

#if !defined(BLYNK_TEMPLATE_ID) || !defined(BLYNK_DEVICE_NAME)
#error "Please specify your BLYNK_TEMPLATE_ID and BLYNK_DEVICE_NAME"
#endif

#include "BlynkState.h"
#include "ConfigStore.h"
#include "ResetButton.h"
#include "ConfigMode.h"
// #include "Indicator.h"
#include "OTA.h"

inline
void BlynkState::set(State m) {
  if (state != m && m < MODE_MAX_VALUE) {
    DEBUG_PRINT(String(StateStr[state]) + " => " + StateStr[m]);
    state = m;

    // You can put your state handling here,
    // i.e. implement custom indication
  }
}

void printDeviceBanner()
{
  Blynk.printBanner();
  DEBUG_PRINT("--------------------------");
  DEBUG_PRINT(String("Product:  ") + BLYNK_DEVICE_NAME);
  DEBUG_PRINT(String("Hardware: ") + BOARD_HARDWARE_VERSION);
  DEBUG_PRINT(String("Firmware: ") + BLYNK_FIRMWARE_VERSION " (build " __DATE__ " " __TIME__ ")");
  if (configStore.getFlag(CONFIG_FLAG_VALID)) {
    DEBUG_PRINT(String("Token:    ...") + (configStore.cloudToken+28));
  }
  DEBUG_PRINT(String("Device:   ") + BLYNK_INFO_DEVICE + " @ " + ESP.getCpuFreqMHz() + "MHz");
  DEBUG_PRINT(String("MAC:      ") + WiFi.macAddress());
  DEBUG_PRINT(String("Flash:    ") + ESP.getFlashChipSize() / 1024 + "K");
  DEBUG_PRINT(String("ESP sdk:  ") + ESP.getSdkVersion());
  DEBUG_PRINT(String("Chip rev: ") + ESP.getChipRevision());
  DEBUG_PRINT(String("Free mem: ") + ESP.getFreeHeap());
  DEBUG_PRINT("--------------------------");
}

void runBlynkWithChecks() {
  Blynk.run();
  if (BlynkState::get() == MODE_RUNNING) {
    if (!Blynk.connected()) {
      if (WiFi.status() == WL_CONNECTED) {
        BlynkState::set(MODE_CONNECTING_CLOUD);
      } else {
        BlynkState::set(MODE_CONNECTING_NET);
      }
    }
  }
}

class Edgent {

public:
  void begin()
  {
  //  indicator_init();
    button_init();
    config_init();

    WiFi.persistent(false);
    WiFi.enableSTA(true);   // Needed to get MAC

    printDeviceBanner();

    if (configStore.getFlag(CONFIG_FLAG_VALID)) {
      BlynkState::set(MODE_CONNECTING_NET);
    } else if (config_load_blnkopt()) {
      DEBUG_PRINT("Firmware is preprovisioned");
      BlynkState::set(MODE_CONNECTING_NET);
    } else {
      BlynkState::set(MODE_WAIT_CONFIG);
    }
  }

  void run() {
    app_loop();
    switch (BlynkState::get()) {
    case MODE_WAIT_CONFIG:       
    case MODE_CONFIGURING:       enterConfigMode();    break;
    case MODE_CONNECTING_NET:    enterConnectNet();    break;
    case MODE_CONNECTING_CLOUD:  enterConnectCloud();  break;
    case MODE_RUNNING:           runBlynkWithChecks(); break;
    case MODE_OTA_UPGRADE:       enterOTA();           break;
    case MODE_SWITCH_TO_STA:     enterSwitchToSTA();   break;
    case MODE_RESET_CONFIG:      enterResetConfig();   break;
    default:                     enterError();         break;
    }
  }

};

Edgent BlynkEdgent;
BlynkTimer timer;

void app_loop() {
    timer.run();
}

BlynkState.h

enum State {

  MODE_WAIT_CONFIG,

  MODE_CONFIGURING,

  MODE_CONNECTING_NET,

  MODE_CONNECTING_CLOUD,

  MODE_RUNNING,

  MODE_OTA_UPGRADE,

  MODE_SWITCH_TO_STA,

  MODE_RESET_CONFIG,

  MODE_ERROR,

  MODE_MAX_VALUE

};

#if defined(APP_DEBUG)

const char* StateStr[MODE_MAX_VALUE+1] = {

  "WAIT_CONFIG",

  "CONFIGURING",

  "CONNECTING_NET",

  "CONNECTING_CLOUD",

  "RUNNING",

  "OTA_UPGRADE",

  "SWITCH_TO_STA",

  "RESET_CONFIG",

  "ERROR",

  "INIT"

};

#endif

namespace BlynkState

{

  volatile State state = MODE_MAX_VALUE;

  State get()        { return state; }

  bool  is (State m) { return (state == m); }

  void  set(State m);

};

ConfigMode.h


#include <WiFiClient.h>
#include <WebServer.h>
#include <DNSServer.h>
#include <Update.h>

#include <nvs.h>
#include <nvs_flash.h>

#ifdef BLYNK_USE_SPIFFS
  #include "SPIFFS.h"
#else
  const char* config_form = R"html(
<!DOCTYPE HTML>
<html>
<head>
  <title>WiFi setup</title>
  <style>
  body {
    background-color: #fcfcfc;
    box-sizing: border-box;
  }
  body, input {
    font-family: Roboto, sans-serif;
    font-weight: 400;
    font-size: 16px;
  }
  .centered {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);

    padding: 20px;
    background-color: #ccc;
    border-radius: 4px;
  }
  td { padding:0 0 0 5px; }
  label { white-space:nowrap; }
  input { width: 20em; }
  input[name="port"] { width: 5em; }
  input[type="submit"], img { margin: auto; display: block; width: 30%; }
  </style>
</head> 
<body>
<div class="centered">
  <form method="get" action="config">
    <table>
    <tr><td><label for="ssid">WiFi SSID:</label></td>  <td><input type="text" name="ssid" length=64 required="required"></td></tr>
    <tr><td><label for="pass">Password:</label></td>   <td><input type="text" name="pass" length=64></td></tr>
    <tr><td><label for="blynk">Auth token:</label></td><td><input type="text" name="blynk" placeholder="a0b1c2d..." pattern="[-_a-zA-Z0-9]{32}" maxlength="32" required="required"></td></tr>
    <tr><td><label for="host">Host:</label></td>       <td><input type="text" name="host" length=64></td></tr>
    <tr><td><label for="port_ssl">Port:</label></td>   <td><input type="number" name="port_ssl" value="443" min="1" max="65535"></td></tr>
    </table><br/>
    <input type="submit" value="Apply">
  </form>
</div>
</body>
</html>
)html";
#endif

WebServer server(WIFI_AP_CONFIG_PORT);
DNSServer dnsServer;
const byte DNS_PORT = 53;

static const char serverUpdateForm[] PROGMEM =
  R"(<html><body>
      <form method='POST' action='' enctype='multipart/form-data'>
        <input type='file' name='update'>
        <input type='submit' value='Update'>
      </form>
    </body></html>)";

void restartMCU() {
  ESP.restart();
  while(1) {};
}

void eraseMcuConfig() {
  // Erase ESP32 NVS
  int err;
  //err=nvs_flash_init();
  //BLYNK_LOG2("nvs_flash_init: ", err ? String(err) : "Success");
  err=nvs_flash_erase();
  BLYNK_LOG2("nvs_flash_erase: ", err ? String(err) : "Success");
}

void getWiFiName(char* buff, size_t len, bool withPrefix = true) {
  const uint64_t chipId = ESP.getEfuseMac();
  uint32_t unique = 0;
  for (int i=0; i<4; i++) {
    unique = BlynkCRC32(&chipId, sizeof(chipId), unique);
  }
  if (withPrefix) {
    snprintf(buff, len, "Blynk %s-%05X", BLYNK_DEVICE_NAME, unique & 0xFFFFF);
  } else {
    snprintf(buff, len, "%s-%05X", BLYNK_DEVICE_NAME, unique & 0xFFFFF);      
  }
}

void enterConfigMode()
{
  char ssidBuff[64];
  getWiFiName(ssidBuff, sizeof(ssidBuff));

  WiFi.mode(WIFI_OFF);
  delay(100);
  WiFi.mode(WIFI_AP);
  delay(2000);
  WiFi.softAPConfig(WIFI_AP_IP, WIFI_AP_IP, WIFI_AP_Subnet);
  WiFi.softAP(ssidBuff);
  delay(500);

  IPAddress myIP = WiFi.softAPIP();
  DEBUG_PRINT(String("AP SSID: ") + ssidBuff);
  DEBUG_PRINT(String("AP IP:   ") + myIP[0] + "." + myIP[1] + "." + myIP[2] + "." + myIP[3]);

  // Set up DNS Server
  dnsServer.setTTL(300); // Time-to-live 300s
  dnsServer.setErrorReplyCode(DNSReplyCode::ServerFailure); // Return code for non-accessible domains
#ifdef WIFI_CAPTIVE_PORTAL_ENABLE
  dnsServer.start(DNS_PORT, "*", WiFi.softAPIP()); // Point all to our IP
  server.onNotFound(handleRoot);
#else
  dnsServer.start(DNS_PORT, CONFIG_AP_URL, WiFi.softAPIP());
  DEBUG_PRINT(String("AP URL:  ") + CONFIG_AP_URL);
#endif

  server.on("/update", HTTP_GET, []() {
    server.sendHeader("Connection", "close");
    server.send(200, "text/html", serverUpdateForm);
  });
  server.on("/update", HTTP_POST, []() {
    server.sendHeader("Connection", "close");
    if (!Update.hasError()) {
      server.send(200, "text/plain", "OK");
    } else {
      server.send(500, "text/plain", "FAIL");
    }
    delay(1000);
    restartMCU();
  }, []() {
    HTTPUpload& upload = server.upload();
    if (upload.status == UPLOAD_FILE_START) {
      DEBUG_PRINT(String("Update: ") + upload.filename);
      //WiFiUDP::stop();

      if (!Update.begin(UPDATE_SIZE_UNKNOWN)) { //start with max available size
        Update.printError(BLYNK_PRINT);
      }
    } else if (upload.status == UPLOAD_FILE_WRITE) {
      /* flashing firmware to ESP*/
      if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
        Update.printError(BLYNK_PRINT);
      }
      BLYNK_PRINT.print(".");
    } else if (upload.status == UPLOAD_FILE_END) {
      BLYNK_PRINT.println();
      DEBUG_PRINT("Finishing...");
      if (Update.end(true)) { //true to set the size to the current progress
        DEBUG_PRINT("Update Success. Rebooting");
      } else {
        Update.printError(BLYNK_PRINT);
      }
    }
  });

  server.on("/config", []() {
    DEBUG_PRINT("Applying configuration...");
    String ssid = server.arg("ssid");
    String ssidManual = server.arg("ssidManual");
    String pass = server.arg("pass");
    if (ssidManual != "") {
      ssid = ssidManual;
    }
    String token = server.arg("blynk");
    String host  = server.arg("host");
    String port  = server.arg("port_ssl");

    String ip   = server.arg("ip");
    String mask = server.arg("mask");
    String gw   = server.arg("gw");
    String dns  = server.arg("dns");
    String dns2 = server.arg("dns2");

    bool save  = server.arg("save").toInt();

    String content;

    DEBUG_PRINT(String("WiFi SSID: ") + ssid + " Pass: " + pass);
    DEBUG_PRINT(String("Blynk cloud: ") + token + " @ " + host + ":" + port);

    if (token.length() == 32 && ssid.length() > 0) {
      configStore.setFlag(CONFIG_FLAG_VALID, false);
      CopyString(ssid, configStore.wifiSSID);
      CopyString(pass, configStore.wifiPass);
      CopyString(token, configStore.cloudToken);
      if (host.length()) {
        CopyString(host,  configStore.cloudHost);
      }
      if (port.length()) {
        configStore.cloudPort = port.toInt();
      }

      IPAddress addr;
      
      if (ip.length() && addr.fromString(ip)) {
        configStore.staticIP = addr;
        configStore.setFlag(CONFIG_FLAG_STATIC_IP, true);
      } else {
        configStore.setFlag(CONFIG_FLAG_STATIC_IP, false);
      }
      if (mask.length() && addr.fromString(mask)) {
        configStore.staticMask = addr;
      }
      if (gw.length() && addr.fromString(gw)) {
        configStore.staticGW = addr;
      }
      if (dns.length() && addr.fromString(dns)) {
        configStore.staticDNS = addr;
      }
      if (dns2.length() && addr.fromString(dns2)) {
        configStore.staticDNS2 = addr;
      }

      if (save) {
        configStore.setFlag(CONFIG_FLAG_VALID, true);
        config_save();

        content = R"json({"status":"ok","msg":"Configuration saved"})json";
      } else {
        content = R"json({"status":"ok","msg":"Trying to connect..."})json";
      }
      server.send(200, "application/json", content);

      BlynkState::set(MODE_SWITCH_TO_STA);
    } else {
      DEBUG_PRINT("Configuration invalid");
      content = R"json({"status":"error","msg":"Configuration invalid"})json";
      server.send(500, "application/json", content);
    }
  });
  server.on("/board_info.json", []() {
    DEBUG_PRINT("Sending board info...");
    const char* tmpl = BLYNK_TEMPLATE_ID;
    char ssidBuff[64];
    getWiFiName(ssidBuff, sizeof(ssidBuff));
    char buff[512];
    snprintf(buff, sizeof(buff),
      R"json({"board":"%s","tmpl_id":"%s","fw_type":"%s","fw_ver":"%s","hw_ver":"%s","ssid":"%s","bssid":"%s","last_error":%d,"wifi_scan":true,"static_ip":true})json",
      BLYNK_DEVICE_NAME,
      tmpl ? tmpl : "Unknown",
      BLYNK_FIRMWARE_TYPE,
      BLYNK_FIRMWARE_VERSION,
      BOARD_HARDWARE_VERSION,
      ssidBuff,
      WiFi.softAPmacAddress().c_str(),
      configStore.last_error
    );
    server.send(200, "application/json", buff);
  });
  server.on("/wifi_scan.json", []() {
    DEBUG_PRINT("Scanning networks...");
    int wifi_nets = WiFi.scanNetworks(true, true);
    while (wifi_nets == -1) {
      delay(20);
      wifi_nets = WiFi.scanComplete();
    }
    DEBUG_PRINT(String("Found networks: ") + wifi_nets);

    String result = "[\n";
    if (wifi_nets) {
      
      // Sort networks
      int indices[wifi_nets];
      for (int i = 0; i < wifi_nets; i++) {
        indices[i] = i;
      }
      for (int i = 0; i < wifi_nets; i++) {
        for (int j = i + 1; j < wifi_nets; j++) {
          if (WiFi.RSSI(indices[j]) > WiFi.RSSI(indices[i])) {
            std::swap(indices[i], indices[j]);
          }
        }
      }

      wifi_nets = BlynkMin(15, wifi_nets); // Show top 15 networks

      // TODO: skip empty names

      char buff[256];
      for (int i = 0; i < wifi_nets; i++){
        int id = indices[i];

        const char* sec;
        switch (WiFi.encryptionType(id)) {
        case WIFI_AUTH_WEP:          sec = "WEP"; break;
        case WIFI_AUTH_WPA_PSK:      sec = "WPA/PSK"; break;
        case WIFI_AUTH_WPA2_PSK:     sec = "WPA2/PSK"; break;
        case WIFI_AUTH_WPA_WPA2_PSK: sec = "WPA/WPA2/PSK"; break;
        case WIFI_AUTH_OPEN:         sec = "OPEN"; break;
        default:                     sec = "unknown"; break;
        }

        snprintf(buff, sizeof(buff),
          R"json(  {"ssid":"%s","bssid":"%s","rssi":%i,"sec":"%s","ch":%i})json",
          WiFi.SSID(id).c_str(),
          WiFi.BSSIDstr(id).c_str(),
          WiFi.RSSI(id),
          sec,
          WiFi.channel(id)
        );

        result += buff;
        if (i != wifi_nets-1) result += ",\n";
      }
      server.send(200, "application/json", result + "\n]");
    } else {
      server.send(200, "application/json", "[]");
    }
  });
  server.on("/reset", []() {
    BlynkState::set(MODE_RESET_CONFIG);
    server.send(200, "application/json", R"json({"status":"ok","msg":"Configuration reset"})json");
  });
  server.on("/reboot", []() {
    restartMCU();
  });

#ifdef BLYNK_USE_SPIFFS
  if (SPIFFS.begin()) {
    server.serveStatic("/img/favicon.png", SPIFFS, "/img/favicon.png");
    server.serveStatic("/img/logo.png", SPIFFS, "/img/logo.png");
    server.serveStatic("/", SPIFFS, "/index.html");
  } else {
    DEBUG_PRINT("Webpage: No SPIFFS");
  }
#endif

  server.begin();

  while (BlynkState::is(MODE_WAIT_CONFIG) || BlynkState::is(MODE_CONFIGURING)) {
    delay(10);
    dnsServer.processNextRequest();
    server.handleClient();
    app_loop();
    if (BlynkState::is(MODE_WAIT_CONFIG) && WiFi.softAPgetStationNum() > 0) {
      BlynkState::set(MODE_CONFIGURING);
    } else if (BlynkState::is(MODE_CONFIGURING) && WiFi.softAPgetStationNum() == 0) {
      BlynkState::set(MODE_WAIT_CONFIG);
    }
  }

  server.stop();
  
#ifdef BLYNK_USE_SPIFFS
  SPIFFS.end();
#endif
}

void enterConnectNet() {
  BlynkState::set(MODE_CONNECTING_NET);
  DEBUG_PRINT(String("Connecting to WiFi: ") + configStore.wifiSSID);

  char ssidBuff[64];
  getWiFiName(ssidBuff, sizeof(ssidBuff));
  String hostname(ssidBuff);
  hostname.replace(" ", "-");

  WiFi.setHostname(hostname.c_str());

  if (configStore.getFlag(CONFIG_FLAG_STATIC_IP)) {
    if (!WiFi.config(configStore.staticIP,
                    configStore.staticGW,
                    configStore.staticMask,
                    configStore.staticDNS,
                    configStore.staticDNS2)
    ) {
      DEBUG_PRINT("Failed to configure Static IP");
      config_set_last_error(BLYNK_PROV_ERR_CONFIG);
      BlynkState::set(MODE_ERROR);
      return;
    }
  }

  WiFi.begin(configStore.wifiSSID, configStore.wifiPass);

  unsigned long timeoutMs = millis() + WIFI_NET_CONNECT_TIMEOUT;
  while ((timeoutMs > millis()) && (WiFi.status() != WL_CONNECTED))
  {
    delay(10);
    app_loop();
    if (!BlynkState::is(MODE_CONNECTING_NET)) {
      WiFi.disconnect();
      return;
    }
  }

  if (WiFi.status() == WL_CONNECTED) {
    IPAddress localip = WiFi.localIP();
    if (configStore.getFlag(CONFIG_FLAG_STATIC_IP)) {
      BLYNK_LOG_IP("Using Static IP: ", localip);
    } else {
      BLYNK_LOG_IP("Using Dynamic IP: ", localip);
    }

    BlynkState::set(MODE_CONNECTING_CLOUD);
  } else {
    config_set_last_error(BLYNK_PROV_ERR_NETWORK);
    BlynkState::set(MODE_ERROR);
  }
}

void enterConnectCloud() {
  BlynkState::set(MODE_CONNECTING_CLOUD);

  Blynk.config(configStore.cloudToken, configStore.cloudHost, configStore.cloudPort);
  Blynk.connect(0);

  unsigned long timeoutMs = millis() + WIFI_CLOUD_CONNECT_TIMEOUT;
  while ((timeoutMs > millis()) &&
        (!Blynk.isTokenInvalid()) &&
        (Blynk.connected() == false))
  {
    delay(10);
    Blynk.run();
    app_loop();
    if (!BlynkState::is(MODE_CONNECTING_CLOUD)) {
      Blynk.disconnect();
      return;
    }
  }

  if (millis() > timeoutMs) {
    DEBUG_PRINT("Timeout");
  }

  if (Blynk.isTokenInvalid()) {
    config_set_last_error(BLYNK_PROV_ERR_TOKEN);
    BlynkState::set(MODE_WAIT_CONFIG);
  } else if (Blynk.connected()) {
    BlynkState::set(MODE_RUNNING);

    if (!configStore.getFlag(CONFIG_FLAG_VALID)) {
      configStore.last_error = BLYNK_PROV_ERR_NONE;
      configStore.setFlag(CONFIG_FLAG_VALID, true);
      config_save();
    }
  } else {
    config_set_last_error(BLYNK_PROV_ERR_CLOUD);
    BlynkState::set(MODE_ERROR);
  }
}

void enterSwitchToSTA() {
  BlynkState::set(MODE_SWITCH_TO_STA);

  DEBUG_PRINT("Switching to STA...");

  delay(1000);
  WiFi.mode(WIFI_OFF);
  delay(100);
  WiFi.mode(WIFI_STA);

  BlynkState::set(MODE_CONNECTING_NET);
}

void enterError() {
  BlynkState::set(MODE_ERROR);
  
  unsigned long timeoutMs = millis() + 10000;
  while (timeoutMs > millis() || g_buttonPressed)
  {
    delay(10);
    app_loop();
    if (!BlynkState::is(MODE_ERROR)) {
      return;
    }
  }
  DEBUG_PRINT("Restarting after error.");
  delay(10);

  restartMCU();
}

ConfigStore.h


#define CONFIG_FLAG_VALID       0x01
#define CONFIG_FLAG_STATIC_IP   0x02

#define BLYNK_PROV_ERR_NONE     0      // All good
#define BLYNK_PROV_ERR_CONFIG   700    // Invalid config from app (malformed token,etc)
#define BLYNK_PROV_ERR_NETWORK  701    // Could not connect to the router
#define BLYNK_PROV_ERR_CLOUD    702    // Could not connect to the cloud
#define BLYNK_PROV_ERR_TOKEN    703    // Invalid token error (after connection)
#define BLYNK_PROV_ERR_INTERNAL 704    // Other issues (i.e. hardware failure)

struct ConfigStore {
  uint32_t  magic;
  char      version[15];
  uint8_t   flags;

  char      wifiSSID[34];
  char      wifiPass[64];

  char      cloudToken[34];
  char      cloudHost[34];
  uint16_t  cloudPort;

  uint32_t  staticIP;
  uint32_t  staticMask;
  uint32_t  staticGW;
  uint32_t  staticDNS;
  uint32_t  staticDNS2;

  int       last_error;

  void setFlag(uint8_t mask, bool value) {
    if (value) {
      flags |= mask;
    } else {
      flags &= ~mask;
    }
  }

  bool getFlag(uint8_t mask) {
    return (flags & mask) == mask;
  }
} __attribute__((packed));

ConfigStore configStore;

const ConfigStore configDefault = {
  0x626C6E6B,
  BLYNK_FIRMWARE_VERSION,
  0x00,
  
  "",
  "",
  
  "invalid token",
  CONFIG_DEFAULT_SERVER,
  CONFIG_DEFAULT_PORT,
  0,
  BLYNK_PROV_ERR_NONE
};

template<typename T, int size>
void CopyString(const String& s, T(&arr)[size]) {
  s.toCharArray(arr, size);
}

static bool config_load_blnkopt()
{
  static const char blnkopt[] = "blnkopt\0"
    BLYNK_PARAM_KV("ssid" , BLYNK_PARAM_PLACEHOLDER_64
                            BLYNK_PARAM_PLACEHOLDER_64
                            BLYNK_PARAM_PLACEHOLDER_64
                            BLYNK_PARAM_PLACEHOLDER_64)
    BLYNK_PARAM_KV("host" , CONFIG_DEFAULT_SERVER)
    BLYNK_PARAM_KV("port" , BLYNK_TOSTRING(CONFIG_DEFAULT_PORT))
    "\0";

  BlynkParam prov(blnkopt+8, sizeof(blnkopt)-8-2);
  BlynkParam::iterator ssid = prov["ssid"];
  BlynkParam::iterator pass = prov["pass"];
  BlynkParam::iterator auth = prov["auth"];
  BlynkParam::iterator host = prov["host"];
  BlynkParam::iterator port = prov["port"];

  if (!(ssid.isValid() && auth.isValid())) {
    return false;
  }

  // reset to defaut before loading values from blnkopt
  configStore = configDefault;

  if (ssid.isValid()) { CopyString(ssid.asStr(), configStore.wifiSSID); }
  if (pass.isValid()) { CopyString(pass.asStr(), configStore.wifiPass); }
  if (auth.isValid()) { CopyString(auth.asStr(), configStore.cloudToken); }
  if (host.isValid()) { CopyString(host.asStr(), configStore.cloudHost); }
  if (port.isValid()) { configStore.cloudPort = port.asInt(); }

  return true;
}

#include <Preferences.h>
Preferences preferences;

void config_load()
{
  memset(&configStore, 0, sizeof(configStore));
  preferences.getBytes("config", &configStore, sizeof(configStore));
  if (configStore.magic != configDefault.magic) {
    DEBUG_PRINT("Using default config.");
    configStore = configDefault;
    return;
  }
}

bool config_save()
{
  preferences.putBytes("config", &configStore, sizeof(configStore));
  DEBUG_PRINT("Configuration stored to flash");
  return true;
}

bool config_init()
{
  preferences.begin("blynk", false);
  config_load();
  return true;
}

void enterResetConfig()
{
  DEBUG_PRINT("Resetting configuration!");
  configStore = configDefault;
  config_save();
  eraseMcuConfig();
  BlynkState::set(MODE_WAIT_CONFIG);
}

void config_set_last_error(int error) {
  // Only set error if not provisioned
  if (!configStore.getFlag(CONFIG_FLAG_VALID)) {
    configStore = configDefault;
    configStore.last_error = error;
    BLYNK_LOG2("Last error code: ", error);
    config_save();
  }
}

OTA.h


#include <WiFi.h>
#include <Update.h>
#include <HTTPClient.h>

String overTheAirURL;

extern BlynkTimer timer;

BLYNK_WRITE(InternalPinOTA) {
  overTheAirURL = param.asString();

  timer.setTimeout(2000L, [](){
    // Start OTA
    Blynk.logEvent("sys_ota", "OTA started");

    // Disconnect, not to interfere with OTA process
    Blynk.disconnect();

    BlynkState::set(MODE_OTA_UPGRADE);
  });
}

void enterOTA() {
  BlynkState::set(MODE_OTA_UPGRADE);

  DEBUG_PRINT(String("Firmware update URL: ") + overTheAirURL);

  HTTPClient http;
  http.begin(overTheAirURL);

  int httpCode = http.GET();
  if (httpCode != HTTP_CODE_OK) {
    DEBUG_PRINT("HTTP response should be 200");
    BlynkState::set(MODE_ERROR);
    return;
  }
  int contentLength = http.getSize();
  if (contentLength <= 0) {
    DEBUG_PRINT("Content-Length not defined");
    BlynkState::set(MODE_ERROR);
    return;
  }

  bool canBegin = Update.begin(contentLength);
  if (!canBegin) {
    DEBUG_PRINT("Not enough space to begin OTA");
    BlynkState::set(MODE_ERROR);
    return;
  }

  Client& client = http.getStream();
  int written = Update.writeStream(client);
  if (written != contentLength) {
    DEBUG_PRINT(String("OTA written ") + written + " / " + contentLength + " bytes");
    BlynkState::set(MODE_ERROR);
    return;
  }

  if (!Update.end()) {
    DEBUG_PRINT("Error #" + String(Update.getError()));
    BlynkState::set(MODE_ERROR);
    return;
  }

  if (!Update.isFinished()) {
    DEBUG_PRINT("Update failed.");
    BlynkState::set(MODE_ERROR);
    return;
  }

  DEBUG_PRINT("=== Update successfully completed. Rebooting.");
  restartMCU();
}

ResetButton.h

/**************************************************************
 * This is a DEMO. You can use it only for development and testing.
 *
 * If you would like to add these features to your product,
 * please contact Blynk for Business:
 *
 *                  http://www.blynk.io/
 *
 **************************************************************/

volatile bool     g_buttonPressed = false;
volatile uint32_t g_buttonPressTime = -1;

void button_action(void)
{
  BlynkState::set(MODE_RESET_CONFIG);
}

void button_change(void)
{
#if BOARD_BUTTON_ACTIVE_LOW
  bool buttonState = !digitalRead(BOARD_BUTTON_PIN);
#else
  bool buttonState = digitalRead(BOARD_BUTTON_PIN);
#endif

  if (buttonState && !g_buttonPressed) {
    g_buttonPressTime = millis();
    g_buttonPressed = true;
    DEBUG_PRINT("Hold the button to reset configuration...");
  } else if (!buttonState && g_buttonPressed) {
    g_buttonPressed = false;
    uint32_t buttonHoldTime = millis() - g_buttonPressTime;
    if (buttonHoldTime >= BUTTON_HOLD_TIME_ACTION) {
      button_action();
    } else {
      // User action
    }
    g_buttonPressTime = -1;
  }
}

void button_init()
{
#if BOARD_BUTTON_ACTIVE_LOW
  pinMode(BOARD_BUTTON_PIN, INPUT_PULLUP);
#else
  pinMode(BOARD_BUTTON_PIN, INPUT_PULLDOWN);
#endif
  attachInterrupt(BOARD_BUTTON_PIN, button_change, CHANGE);
}

Settings.h

/*
 * General options
 */

#define BOARD_HARDWARE_VERSION        "1.0.0"

/*
 * Board configuration (see examples below).
 */

#if defined(USE_WROVER_BOARD)

  // Custom board configuration
  #define BOARD_BUTTON_PIN            15                    // Pin where user button is attached
  #define BOARD_BUTTON_ACTIVE_LOW     true                  // true if button is "active-low"

  #define BOARD_LED_PIN_R             0                     // Set R,G,B pins - if your LED is PWM RGB 
  #define BOARD_LED_PIN_G             2
  #define BOARD_LED_PIN_B             4
  #define BOARD_LED_INVERSE           false                 // true if LED is common anode, false if common cathode
  #define BOARD_LED_BRIGHTNESS        128                   // 0..255 brightness control

#else

  // Custom board configuration
  #define BOARD_BUTTON_PIN            16                     // Pin where user button is attached
  #define BOARD_BUTTON_ACTIVE_LOW     true                  // true if button is "active-low"

  #define BOARD_LED_PIN               4                     // Set LED pin - if you have a single-color LED attached
  //#define BOARD_LED_PIN_R           27                    // Set R,G,B pins - if your LED is PWM RGB 
  //#define BOARD_LED_PIN_G           26
  //#define BOARD_LED_PIN_B           25
  //#define BOARD_LED_PIN_WS2812      33                    // Set if your LED is WS2812 RGB
  #define BOARD_LED_INVERSE           false                 // true if LED is common anode, false if common cathode
  #define BOARD_LED_BRIGHTNESS        64                    // 0..255 brightness control

#endif


/*
 * Advanced options
 */

#define BUTTON_HOLD_TIME_INDICATION   3000
#define BUTTON_HOLD_TIME_ACTION       10000

#define BOARD_PWM_MAX                 1023

// #define LEDC_CHANNEL_1     1
// #define LEDC_CHANNEL_2     2
// #define LEDC_CHANNEL_3     3
// #define LEDC_TIMER_BITS    10
// #define LEDC_BASE_FREQ     12000

#define CONFIG_AP_URL                 "blynk.setup"
#define CONFIG_DEFAULT_SERVER         "blynk.cloud"
#define CONFIG_DEFAULT_PORT           443

#define WIFI_NET_CONNECT_TIMEOUT      30000
#define WIFI_CLOUD_CONNECT_TIMEOUT    30000
#define WIFI_AP_CONFIG_PORT           80
#define WIFI_AP_IP                    IPAddress(192, 168, 4, 1)
#define WIFI_AP_Subnet                IPAddress(255, 255, 255, 0)
//#define WIFI_CAPTIVE_PORTAL_ENABLE

#define USE_TICKER
//#define USE_TIMER_ONE
//#define USE_TIMER_THREE

#define BLYNK_NO_DEFAULT_BANNER

#if defined(APP_DEBUG)
  #define DEBUG_PRINT(...) BLYNK_LOG1(__VA_ARGS__)
#else
  #define DEBUG_PRINT(...)
#endif

Sorry, last include:

esp_camera.h

// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/*
 * Example Use
 *
    static camera_config_t camera_example_config = {
        .pin_pwdn       = PIN_PWDN,
        .pin_reset      = PIN_RESET,
        .pin_xclk       = PIN_XCLK,
        .pin_sscb_sda   = PIN_SIOD,
        .pin_sscb_scl   = PIN_SIOC,
        .pin_d7         = PIN_D7,
        .pin_d6         = PIN_D6,
        .pin_d5         = PIN_D5,
        .pin_d4         = PIN_D4,
        .pin_d3         = PIN_D3,
        .pin_d2         = PIN_D2,
        .pin_d1         = PIN_D1,
        .pin_d0         = PIN_D0,
        .pin_vsync      = PIN_VSYNC,
        .pin_href       = PIN_HREF,
        .pin_pclk       = PIN_PCLK,
        .xclk_freq_hz   = 20000000,
        .ledc_timer     = LEDC_TIMER_0,
        .ledc_channel   = LEDC_CHANNEL_0,
        .pixel_format   = PIXFORMAT_JPEG,
        .frame_size     = FRAMESIZE_SVGA,
        .jpeg_quality   = 10,
        .fb_count       = 2,
        .grab_mode      = CAMERA_GRAB_WHEN_EMPTY
    };
    esp_err_t camera_example_init(){
        return esp_camera_init(&camera_example_config);
    }
    esp_err_t camera_example_capture(){
        //capture a frame
        camera_fb_t * fb = esp_camera_fb_get();
        if (!fb) {
            ESP_LOGE(TAG, "Frame buffer could not be acquired");
            return ESP_FAIL;
        }
        //replace this with your own function
        display_image(fb->width, fb->height, fb->pixformat, fb->buf, fb->len);
        //return the frame buffer back to be reused
        esp_camera_fb_return(fb);
        return ESP_OK;
    }
*/

#pragma once

#include "esp_err.h"
#include "driver/ledc.h"
#include "sensor.h"
#include "sys/time.h"

#ifdef __cplusplus
extern "C" {
#endif

/**
 * @brief Configuration structure for camera initialization
 */
typedef enum {
    CAMERA_GRAB_WHEN_EMPTY,         /*!< Fills buffers when they are empty. Less resources but first 'fb_count' frames might be old */
    CAMERA_GRAB_LATEST              /*!< Except when 1 frame buffer is used, queue will always contain the last 'fb_count' frames */
} camera_grab_mode_t;

/**
 * @brief Configuration structure for camera initialization
 */
typedef struct {
    int pin_pwdn;                   /*!< GPIO pin for camera power down line */
    int pin_reset;                  /*!< GPIO pin for camera reset line */
    int pin_xclk;                   /*!< GPIO pin for camera XCLK line */
    int pin_sscb_sda;               /*!< GPIO pin for camera SDA line */
    int pin_sscb_scl;               /*!< GPIO pin for camera SCL line */
    int pin_d7;                     /*!< GPIO pin for camera D7 line */
    int pin_d6;                     /*!< GPIO pin for camera D6 line */
    int pin_d5;                     /*!< GPIO pin for camera D5 line */
    int pin_d4;                     /*!< GPIO pin for camera D4 line */
    int pin_d3;                     /*!< GPIO pin for camera D3 line */
    int pin_d2;                     /*!< GPIO pin for camera D2 line */
    int pin_d1;                     /*!< GPIO pin for camera D1 line */
    int pin_d0;                     /*!< GPIO pin for camera D0 line */
    int pin_vsync;                  /*!< GPIO pin for camera VSYNC line */
    int pin_href;                   /*!< GPIO pin for camera HREF line */
    int pin_pclk;                   /*!< GPIO pin for camera PCLK line */

    int xclk_freq_hz;               /*!< Frequency of XCLK signal, in Hz. EXPERIMENTAL: Set to 16MHz on ESP32-S2 or ESP32-S3 to enable EDMA mode */

    ledc_timer_t ledc_timer;        /*!< LEDC timer to be used for generating XCLK  */
    ledc_channel_t ledc_channel;    /*!< LEDC channel to be used for generating XCLK  */

    pixformat_t pixel_format;       /*!< Format of the pixel data: PIXFORMAT_ + YUV422|GRAYSCALE|RGB565|JPEG  */
    framesize_t frame_size;         /*!< Size of the output image: FRAMESIZE_ + QVGA|CIF|VGA|SVGA|XGA|SXGA|UXGA  */

    int jpeg_quality;               /*!< Quality of JPEG output. 0-63 lower means higher quality  */
    size_t fb_count;                /*!< Number of frame buffers to be allocated. If more than one, then each frame will be acquired (double speed)  */
    camera_grab_mode_t grab_mode;   /*!< When buffers should be filled */
} camera_config_t;

/**
 * @brief Data structure of camera frame buffer
 */
typedef struct {
    uint8_t * buf;              /*!< Pointer to the pixel data */
    size_t len;                 /*!< Length of the buffer in bytes */
    size_t width;               /*!< Width of the buffer in pixels */
    size_t height;              /*!< Height of the buffer in pixels */
    pixformat_t format;         /*!< Format of the pixel data */
    struct timeval timestamp;   /*!< Timestamp since boot of the first DMA buffer of the frame */
} camera_fb_t;

#define ESP_ERR_CAMERA_BASE 0x20000
#define ESP_ERR_CAMERA_NOT_DETECTED             (ESP_ERR_CAMERA_BASE + 1)
#define ESP_ERR_CAMERA_FAILED_TO_SET_FRAME_SIZE (ESP_ERR_CAMERA_BASE + 2)
#define ESP_ERR_CAMERA_FAILED_TO_SET_OUT_FORMAT (ESP_ERR_CAMERA_BASE + 3)
#define ESP_ERR_CAMERA_NOT_SUPPORTED            (ESP_ERR_CAMERA_BASE + 4)

/**
 * @brief Initialize the camera driver
 *
 * @note call camera_probe before calling this function
 *
 * This function detects and configures camera over I2C interface,
 * allocates framebuffer and DMA buffers,
 * initializes parallel I2S input, and sets up DMA descriptors.
 *
 * Currently this function can only be called once and there is
 * no way to de-initialize this module.
 *
 * @param config  Camera configuration parameters
 *
 * @return ESP_OK on success
 */
esp_err_t esp_camera_init(const camera_config_t* config);

/**
 * @brief Deinitialize the camera driver
 *
 * @return
 *      - ESP_OK on success
 *      - ESP_ERR_INVALID_STATE if the driver hasn't been initialized yet
 */
esp_err_t esp_camera_deinit();

/**
 * @brief Obtain pointer to a frame buffer.
 *
 * @return pointer to the frame buffer
 */
camera_fb_t* esp_camera_fb_get();

/**
 * @brief Return the frame buffer to be reused again.
 *
 * @param fb    Pointer to the frame buffer
 */
void esp_camera_fb_return(camera_fb_t * fb);

/**
 * @brief Get a pointer to the image sensor control structure
 *
 * @return pointer to the sensor
 */
sensor_t * esp_camera_sensor_get();

/**
 * @brief Save camera settings to non-volatile-storage (NVS)
 * 
 * @param key   A unique nvs key name for the camera settings 
 */
esp_err_t esp_camera_save_to_nvs(const char *key);

/**
 * @brief Load camera settings from non-volatile-storage (NVS)
 * 
 * @param key   A unique nvs key name for the camera settings 
 */
esp_err_t esp_camera_load_from_nvs(const char *key);

#ifdef __cplusplus
}
#endif

#include "img_converters.h"

I still don’t understand why your esp_camera.h doesn’t assign any pins to these variables

typedef struct {
    int pin_pwdn;                   /*!< GPIO pin for camera power down line */
    int pin_reset;                  /*!< GPIO pin for camera reset line */
    int pin_xclk;                   /*!< GPIO pin for camera XCLK line */
    int pin_sscb_sda;               /*!< GPIO pin for camera SDA line */
    int pin_sscb_scl;               /*!< GPIO pin for camera SCL line */
    int pin_d7;                     /*!< GPIO pin for camera D7 line */
    int pin_d6;                     /*!< GPIO pin for camera D6 line */
    int pin_d5;                     /*!< GPIO pin for camera D5 line */
    int pin_d4;                     /*!< GPIO pin for camera D4 line */
    int pin_d3;                     /*!< GPIO pin for camera D3 line */
    int pin_d2;                     /*!< GPIO pin for camera D2 line */
    int pin_d1;                     /*!< GPIO pin for camera D1 line */
    int pin_d0;                     /*!< GPIO pin for camera D0 line */
    int pin_vsync;                  /*!< GPIO pin for camera VSYNC line */
    int pin_href;                   /*!< GPIO pin for camera HREF line */
    int pin_pclk;                   /*!< GPIO pin for camera PCLK line */

If they aren’t used then why are they there?
If they are used then how can they work with null (or in effect GPIO0) declarations?

You haven’t uncommented the USE_WROVER_BOARD (despite the ESP32 CAM being a WROVER chip as far as I know), so the cutsom board configuration is used…

This means that GPIO4 is assigned to BOARD_LED_PIN and LED and although this isn’t necessarily causing your issue, it does support my “messy pin declaration” assertion.

Pete.

Good catch on the LED pin, I missed that one. The reason I hadn’t used the WROVER definition was the settings tab having a couple references to an addressable led and so I decided to go with a custom definition. I will change my LED definition.

The lines you link in the esp_camera.h are all called in my sketch. The struct sets it up in the esp_camera.h include and then you define and initialize those pins in your sketch like I do. Unless I’m still misunderstanding what you are saying there?