ESP32-CAM saving timelapse to SD card as well as live streaming to Blynk

good afternoon gentlemen, i signed up to this blynk forum just to speak to you all, about this project, Gydota first of all well done on a great bit of code, is there a version of this code that also comes with a capture feature than could send still images to the on board sdcard. i must admit i have searched high and low for an existing code with no luck i also tried to combine two working codes, also with zero success for the last week … using a range of different example codes. and methods. this has lead me here. and i am resorting to requesting help from the wise elders of the blynk comunity. i wish to use the camera for live viewing but set the capture and save feature to the blynk eventor, to create times laps pictures. please advise.

hi @Chimpo_Mcdoodle, I’ve not seen any examples where community members have tried this, and I think it’s a bit of a niche requirement anyway.
There are examples of uploading stills to an FTP server, bity not the SD card.

Have you tried this approach/coad for the capture to SD card:

My advice would be to get capture to SD working successfully, and also to get the streaming to Blynk working independently, then try to combine the two sketches.

Pete.

My apologies for my late reply… https://randomnerdtutorials.com/esp32-cam-take-photo-save-microsd-card/ heres a link to an esp32 cam code and very clear description, that takes a picture on startup, then goes into deep sleep. when the user pushes the reset button on the camera, the process is repeated,and boom another photo,despite having all these codes functioning in separately… i have tried many times to isolate the capture and store code and commands and combine this code with the one above. i have also tried running one function after another but nothing, but with no luck,i have even tried to combine it with techiesms’s Blynk Door Bell Cam. - https://www.youtube.com/watch?v=gV3Pr2VrkFg&t=31s. i also tried separate virtual pins for the two different proceses… with no luck. any one feeling brave enough to give it a go? im sure it would be a simple task for such wise and noble gentlemen like yourself’s. i personally think an iot wifi camera that can be used for surveillance and take a picture and store anything interesting at the user’s command, or in my case a timed interval mines going into my greenhouse so i can watch my plant die, and have a nice little video to show for it at the end.

So are you saying that this device will be in deep sleep most of the time?
If so, are you saying that you don’t want streaming video to the Blynk app, rather the ability to view your time-lapse stills via another means? (I doubt very much whether you would be able to render then time lapse stills into a format that is compatible for the Blynk video streaming widget).

Where does Blynk fit into the equation?

Pete.

im sorry i wasnt very clear, gydota’s code .streams to blynk, we want that. the ‘‘unaltered sd card code’’ . spends all 95% of its time in deep sleep,and uses the reset button as a trigger i believe this is done done because there are no free gpio pins left when sd card is in use, we dont want that, however this was very easy to disable, and i only wanted to isolate the code that says ‘’ take photo’ and 'store photo. from the SD card code. and then activate that function iby using a ‘‘blink write’’ of V2, so when V2 is high (capture image , store image. bingo that hypothetical should run on top of gydota code… but it wont :frowning:

here is the isolated code from the sd card code as you can see it just captures and then stores,.shouldnt this just slot in nicely with a blinkwrite V2. but it does not :frowning:

  // 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;
  }
  
  //Serial.println("Starting SD Card");
  if(!SD_MMC.begin()){
    Serial.println("SD Card Mount Failed");
    return;
  }
  
  uint8_t cardType = SD_MMC.cardType();
  if(cardType == CARD_NONE){
    Serial.println("No SD Card attached");
    return;
  }
    
  camera_fb_t * fb = NULL;
  
  // Take Picture with Camera
  fb = esp_camera_fb_get();  
  if(!fb) {
    Serial.println("Camera capture failed");
    return;
  }
  // initialize EEPROM with predefined size
  EEPROM.begin(EEPROM_SIZE);
  pictureNumber = EEPROM.read(0) + 1;

  // Path where new picture will be saved in SD Card
  String path = "/picture" + String(pictureNumber) +".jpg";

  fs::FS &fs = SD_MMC; 
  Serial.printf("Picture file name: %s\n", path.c_str());
  
  File file = fs.open(path.c_str(), FILE_WRITE);
  if(!file){
    Serial.println("Failed to open file in writing mode");
  } 
  else {
    file.write(fb->buf, fb->len); // payload (image), payload length
    Serial.printf("Saved file to path: %s\n", path.c_str());
    EEPROM.write(0, pictureNumber);
    EEPROM.commit();
  }
  file.close();
  esp_camera_fb_return(fb); 
  
  // Turns off the ESP32-CAM white on-board LED (flash) connected to GPIO 4
  //digitalWrite(4, LOW);
 } 
BLYNK_WRITE(V2)
{
  int pinValue = param.asInt();
  if (pinValue == 1) {
  capture(); // take picture for blynk```

Which is why I pointed you to the youtube video that does this, and suggested that you get this working then add-in the Blynk part. Have you watch it?
If you want another example of how to take a picture and do something with it then you could look at this topic, but personally I think the youtube video is more appropriate:

I would have thought that if you wanted to do a timelapse then you’d either go for a timer to trigger the action every so often (once per 10 seconds would give 240x speed) and maybe use a timer widget to set the hours of operation (to avoid lost of pictures taken in the dark.

Pete.

yes by using the eventor or timer to drive V2 high./low i can control the duration between captures and not set timer for night time, as you can see i have isolated working code to its basic function, i am just having trouble putting this in the correct place and format for it to be correct, both parts work separately, i must just be combining the codes wrong.

Please edit your post with the code in it and add add triple backticks at the beginning and end of the code so that it displays correctly.
Triple backticks look like this:
```

BTW, the code seems incomplete, so you may wish to fix that while you’re editing it.

Pete.

ooo that is impressive, here u go

The code is incomplete.

Pete.

I’ve moved this to a separate topic, as it’s sufficiently different to the original topic, and I don’t want to clutter-up that discussion.

Pete…

sorry yet again for another late reply as you can imagine life is a roller coaster right now, i have heeded your advice and watched the video you linked above. it is very useful and informative and his code is very well explained. and as you recommended i should try to get each code working on blynk before i attempt to combine them. which leads me to my issue. this sd card code similarly takes a photo on start up. i just want to confine this and trigger it on the command V3 HIGH. i am having issues putting the code mentioned in the tutorial into a void/function. i understand how to trigger a void/function, but i am having an issue containing the code, i get constant ''a function-definition is not allowed here before ‘{’ token;; when i try to contain the code with ''void save() {" bellow is the raw code.

/**********************************************************
 * Example code for the video ESP32-Camera - SD Card
 * 
 * This code is released under Creative Commons, (C)2019 Curtis Ireland
 **********************************************************/

#include "FS.h"
#include "SD_MMC.h"
#include "esp_camera.h"
#include <EEPROM.h>            // read and write from flash memory

// Select camera pin configuration
#define CAMERA_MODEL_AI_THINKER
#include "camera_pins.h"

// define number of bytes to set aside in persistant memory
#define EEPROM_SIZE 1
int pictureNumber = 0;

static esp_err_t get_image(fs::FS &fs, const char * path) {
    // Variable definitions for camera frame buffer
    camera_fb_t * fb = NULL;
    int64_t fr_start = esp_timer_get_time();

    // Get the contents of the camera frame buffer
    fb = esp_camera_fb_get();
    if (!fb) {
        Serial.println("Camera capture failed");
        return ESP_FAIL;
    }

    // Save image to file
    size_t fb_len = 0;
    fb_len = fb->len;
    File file = fs.open(path, FILE_WRITE);
    if(!file){
      Serial.println("Failed to open file in writing mode");
    } else {
      file.write(fb->buf, fb->len); // payload (image), payload length
      Serial.printf("Saved file to path: %s\n", path);
    }
    file.close();

    // Release the camera frame buffer
    esp_camera_fb_return(fb);
    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));
    return ESP_OK;
}

void setup() {
  Serial.begin(115200);
//  Serial.setDebugOutput(true);
  Serial.println();

  // Initial camera configuration
  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; // This is very important for saving the frame buffer as a JPeg

  //init with high specs to pre-allocate larger buffers
  if(psramFound()){
    config.frame_size = FRAMESIZE_UXGA;
    config.jpeg_quality = 10;
    config.fb_count = 2;
  } else {
    config.frame_size = FRAMESIZE_SVGA;
    config.jpeg_quality = 12;
    config.fb_count = 1;
  }

  // camera init
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    return;
  }

/*****
You may need to adjust these settings to get a picture you like. 
Look in app_httpd.cpp in the CameraWebServer as an example on how these are used.
sensor_t * s = esp_camera_sensor_get();
int res = 0;
if(!strcmp(variable, "framesize")) {
    if(s->pixformat == PIXFORMAT_JPEG) res = s->set_framesize(s, (framesize_t)val);
}
else if(!strcmp(variable, "quality")) res = s->set_quality(s, val);
else if(!strcmp(variable, "contrast")) res = s->set_contrast(s, val);
else if(!strcmp(variable, "brightness")) res = s->set_brightness(s, val);
else if(!strcmp(variable, "saturation")) res = s->set_saturation(s, val);
else if(!strcmp(variable, "gainceiling")) res = s->set_gainceiling(s, (gainceiling_t)val);
else if(!strcmp(variable, "colorbar")) res = s->set_colorbar(s, val);
else if(!strcmp(variable, "awb")) res = s->set_whitebal(s, val);
else if(!strcmp(variable, "agc")) res = s->set_gain_ctrl(s, val);
else if(!strcmp(variable, "aec")) res = s->set_exposure_ctrl(s, val);
else if(!strcmp(variable, "hmirror")) res = s->set_hmirror(s, val);
else if(!strcmp(variable, "vflip")) res = s->set_vflip(s, val);
else if(!strcmp(variable, "awb_gain")) res = s->set_awb_gain(s, val);
else if(!strcmp(variable, "agc_gain")) res = s->set_agc_gain(s, val);
else if(!strcmp(variable, "aec_value")) res = s->set_aec_value(s, val);
else if(!strcmp(variable, "aec2")) res = s->set_aec2(s, val);
else if(!strcmp(variable, "dcw")) res = s->set_dcw(s, val);
else if(!strcmp(variable, "bpc")) res = s->set_bpc(s, val);
else if(!strcmp(variable, "wpc")) res = s->set_wpc(s, val);
else if(!strcmp(variable, "raw_gma")) res = s->set_raw_gma(s, val);
else if(!strcmp(variable, "lenc")) res = s->set_lenc(s, val);
else if(!strcmp(variable, "special_effect")) res = s->set_special_effect(s, val);
else if(!strcmp(variable, "wb_mode")) res = s->set_wb_mode(s, val);
else if(!strcmp(variable, "ae_level")) res = s->set_ae_level(s, val);
 *****/

  Serial.println("Camera initialized!");

  if(!SD_MMC.begin()){
    Serial.println("Card Mount Failed");
    return;
  }
  uint8_t cardType = SD_MMC.cardType();
  if(cardType == CARD_NONE){
    Serial.println("No SD_MMC card attached");
    return;
  }
  Serial.println("SD Card Initialized");

  // initialize EEPROM with predefined size
  EEPROM.begin(EEPROM_SIZE);
  pictureNumber = EEPROM.read(0) + 1;

  // Call function to capture the image and save it as a file
  String path = "/picture" + String(pictureNumber) + ".jpg";
  if(get_image(SD_MMC, path.c_str()) != ESP_OK ) {
    Serial.println("Failed to capture picture");
  } else{
    Serial.println("Captured picture");
    EEPROM.write(0, pictureNumber);
    EEPROM.commit();
  }
pinMode(4, OUTPUT);
  digitalWrite(4, LOW);
 }

void loop() {
}

No, that’s not what I said.

What I actually said is:

Have you succeeded in getting the code below working on its own, as it stands?

Pete.

yes sir, the code mention above functions correctly

now id like to attempt to replace the need for the reset button to restart the code, and use a virtual pin on blynk. like i said previously my issue is containing the code above to make it be called, when v3 is driven high. i understand this is how i run the function.

  {int pinValue = param.asInt();
  if (pinValue == 1) {
 save(); } 

i also understand i need to contain it within void save(){ } i just do not understand where i need to put this divide in the code above, i have tried multiple locations in attempt to get via trial and error with no success just compilation errors.

i have noticed the flash led is left on at the end of the capture. so i had to turn it off at the end of the process i have also added it to the code above.

It’s around the last 11 lines of your void setup that takes the picture.
Start by moving this into a function, and calling it at the end of void setup. If this continues to take a picture on reset then you’ve got that bit right.

You may find that some variables are being declared locally within void setup (such as ‘path’) and it may be necessary to change these so that they are declared as being global.

Once you have this working you can then call your new function from a BLYNK_WRITE(V3) callback function.

Pete.

that made the camera flash as before, a quick check of the sd card and the image has saved to the sd card. however now my serial says

Saved file to path: /picture172.jpg
JPG: 239748B 816ms
Captured picture
E (3102) sdmmc_cmd: sdmmc_read_sectors_dma: sdmmc_send_cmd returned 0xffffffff
E (3103) diskio_sdmmc: sdmmc_read_blocks failed (-1)
E (4103) sdmmc_req: sdmmc_host_wait_for_event returned 0x107
E (4103) sdmmc_cmd: sdmmc_read_sectors_dma: sdmmc_send_cmd returned 0x107
E (4104) diskio_sdmmc: sdmmc_read_blocks failed (263)

in spite of this new information the camera is functioning normally

these were the changes made.

void save(){
  // initialize EEPROM with predefined size
  EEPROM.begin(EEPROM_SIZE);
  pictureNumber = EEPROM.read(0) + 1;

  // Call function to capture the image and save it as a file
  String path = "/picture" + String(pictureNumber) + ".jpg";
  if(get_image(SD_MMC, path.c_str()) != ESP_OK ) {
    Serial.println("Failed to capture picture");
  } else{
    Serial.println("Captured picture");
    EEPROM.write(0, pictureNumber);
    EEPROM.commit();
    
  }
 pinMode(4, OUTPUT);
  digitalWrite(4, LOW);
}
 

void loop() {
  save();
}

I think you only need to call the EEPROM.begin once (in void setup) not every time you take a picture, but I might be wrong, it’s a very long time since I stored anything in EEPROM.

Pete.

i believe the photo number is stored in the eeprom, it needs to be read each time, and add +1 to the photo numbers, so the same file name isnt used, and one photo overwrites the other

i have now added the blynk write and the libraries, good news v3 triggers the void, but serial says

Camera capture failed
Failed to capture picture

#include <WiFi.h>
#include <WiFiClient.h>
#include <BlynkSimpleEsp32.h>
#include "FS.h"
#include "SD_MMC.h"
#include "esp_camera.h"
#include <EEPROM.h>            // read and write from flash memory

// Select camera pin configuration
#define CAMERA_MODEL_AI_THINKER
#include "camera_pins.h"
const char* ssid = "123";
const char* password = "123";
char auth[] = "123";

// define number of bytes to set aside in persistant memory
#define EEPROM_SIZE 1
int pictureNumber = 0;

static esp_err_t get_image(fs::FS &fs, const char * path) {
    // Variable definitions for camera frame buffer
    camera_fb_t * fb = NULL;
    int64_t fr_start = esp_timer_get_time();

    // Get the contents of the camera frame buffer
    fb = esp_camera_fb_get();
    if (!fb) {
        Serial.println("Camera capture failed");
        return ESP_FAIL;
    }

    // Save image to file
    size_t fb_len = 0;
    fb_len = fb->len;
    File file = fs.open(path, FILE_WRITE);
    if(!file){
      Serial.println("Failed to open file in writing mode");
    } else {
      file.write(fb->buf, fb->len); // payload (image), payload length
      Serial.printf("Saved file to path: %s\n", path);
    }
    file.close();

    // Release the camera frame buffer
    esp_camera_fb_return(fb);
    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));
    return ESP_OK;
}

void setup() {
  Serial.begin(115200);
//  Serial.setDebugOutput(true);
  Serial.println();
   WiFi.begin(ssid, password);
  Blynk.begin(auth, ssid, password);

  // Initial camera configuration
  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; // This is very important for saving the frame buffer as a JPeg

  //init with high specs to pre-allocate larger buffers
  if(psramFound()){
    config.frame_size = FRAMESIZE_UXGA;
    config.jpeg_quality = 10;
    config.fb_count = 2;
  } else {
    config.frame_size = FRAMESIZE_SVGA;
    config.jpeg_quality = 12;
    config.fb_count = 1;
  }

  // camera init
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    return;
  }

/*****
You may need to adjust these settings to get a picture you like. 
Look in app_httpd.cpp in the CameraWebServer as an example on how these are used.

sensor_t * s = esp_camera_sensor_get();
int res = 0;

if(!strcmp(variable, "framesize")) {
    if(s->pixformat == PIXFORMAT_JPEG) res = s->set_framesize(s, (framesize_t)val);
}
else if(!strcmp(variable, "quality")) res = s->set_quality(s, val);
else if(!strcmp(variable, "contrast")) res = s->set_contrast(s, val);
else if(!strcmp(variable, "brightness")) res = s->set_brightness(s, val);
else if(!strcmp(variable, "saturation")) res = s->set_saturation(s, val);
else if(!strcmp(variable, "gainceiling")) res = s->set_gainceiling(s, (gainceiling_t)val);
else if(!strcmp(variable, "colorbar")) res = s->set_colorbar(s, val);
else if(!strcmp(variable, "awb")) res = s->set_whitebal(s, val);
else if(!strcmp(variable, "agc")) res = s->set_gain_ctrl(s, val);
else if(!strcmp(variable, "aec")) res = s->set_exposure_ctrl(s, val);
else if(!strcmp(variable, "hmirror")) res = s->set_hmirror(s, val);
else if(!strcmp(variable, "vflip")) res = s->set_vflip(s, val);
else if(!strcmp(variable, "awb_gain")) res = s->set_awb_gain(s, val);
else if(!strcmp(variable, "agc_gain")) res = s->set_agc_gain(s, val);
else if(!strcmp(variable, "aec_value")) res = s->set_aec_value(s, val);
else if(!strcmp(variable, "aec2")) res = s->set_aec2(s, val);
else if(!strcmp(variable, "dcw")) res = s->set_dcw(s, val);
else if(!strcmp(variable, "bpc")) res = s->set_bpc(s, val);
else if(!strcmp(variable, "wpc")) res = s->set_wpc(s, val);
else if(!strcmp(variable, "raw_gma")) res = s->set_raw_gma(s, val);
else if(!strcmp(variable, "lenc")) res = s->set_lenc(s, val);
else if(!strcmp(variable, "special_effect")) res = s->set_special_effect(s, val);
else if(!strcmp(variable, "wb_mode")) res = s->set_wb_mode(s, val);
else if(!strcmp(variable, "ae_level")) res = s->set_ae_level(s, val);
 *****/

  Serial.println("Camera initialized!");

  if(!SD_MMC.begin()){
    Serial.println("Card Mount Failed");
    return;
  }
  uint8_t cardType = SD_MMC.cardType();
  if(cardType == CARD_NONE){
    Serial.println("No SD_MMC card attached");
    return;
  }
  Serial.println("SD Card Initialized");
}
void save(){
  // initialize EEPROM with predefined size
  EEPROM.begin(EEPROM_SIZE);
  pictureNumber = EEPROM.read(0) + 1;

  // Call function to capture the image and save it as a file
  String path = "/picture" + String(pictureNumber) + ".jpg";
  if(get_image(SD_MMC, path.c_str()) != ESP_OK ) {
    Serial.println("Failed to capture picture");
  } else{
    Serial.println("Captured picture");
    EEPROM.write(0, pictureNumber);
    EEPROM.commit();
    
  }
   pinMode(4, OUTPUT);
  digitalWrite(4, LOW);
  }
  BLYNK_WRITE(V3)
  {int pinValue = param.asInt();
  if (pinValue == 1) {
  save(); }
}
 

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