ESP32 CAM Image Gallery Issue


I’m working on a project that involves an ESP32CAM (AI-THINKER) taking a picture and then displaying it in the Blynk Image Gallery widget.

I’m having issues getting the image to update in the app everytime I take a new picture and I could use some help. I can go into the widget settings and manually refresh the image but I’m having issues getting blynk.setProperty working the way I want it to.

I’ve attached my code at the bottom, but is there a way to connect setProperty to a flag or condition as opposed to a pin?

Description of program and problem:

  1. Image Gallery widget pointing to fileserver url.
  2. Button widget in BLYNK connected to V5 which triggers the ESP32CAM to take and send a photo over ftp. (everything works to this point, image file shows up in the fireserver).
  3. Update ImageGallery to display new image once uploaded (problem here, image does not update).

I think what is happening is the setProperty is running before the image has been uploaded to the server, so it updates to the image it already has. It would be nice if I could run setProperty periodically as opposed to via a virtual pin, that way the widget would update the image displayed every “x” seconds and catch the new image whenever it shows up at the url.

Notes: Image quality is not super important, this is taking pictures of the inside of a chicken coop to confirm number of chickens inside for the user. If we think this is a filesize upload issue I’m willing to reduce the image quality. Current size of image showing up in the fileserver @ SVGA is generally under 20kb so I doubt that is the problem.

Thanks in advance for any help offered!

// #define BLYNK_PRINT Serial
#define BUTTON V5
#define LED 4
#define DOORUP V7
#define DOORDOWN V6
// #define BLYNK_TEMPLATE_ID "TMPLQjB7UkPG"  //Clone or comment out for other deployments
// #define BLYNK_DEVICE_NAME "CoopCommand"
#include "esp_camera.h"
#include "SPI.h"
#include "driver/rtc_io.h"
#include "soc/soc.h"
#include "soc/rtc_cntl_reg.h"
#include <FS.h>
#include <WiFiUdp.h>
#include <ESPmDNS.h>
#include <WiFi.h>
#include <WiFiClient.h>
#include <BlynkSimpleEsp32.h>
#include <ESP32_FTPClient.h>
// #include <ArduinoOTA.h>
char ftp_server[] = "";
char ftp_user[] = "xxxxxxx";
char ftp_pass[] ="xxxxxxx";
String pic_url = "xxxxxxxx/";
WiFiClient client;
ESP32_FTPClient ftp (ftp_server,ftp_user,ftp_pass, 5000, 2);
// BLYNK Widgets
WidgetLED led1(V1);
WidgetLED led2(V2);
WidgetLED led3(V3);
WidgetLED led4(V4);
// Your WiFi credentials & Authentication Code from Blynk
// Set password to "" for open networks.
const char* ssid = "xxxxxxx";
const char* password = "xxxxxxx";
char auth[] = "xxxxxx";  //sent by Blynk
// Serial Communication Variables
char coopRx; // Info received from CoopCommand
bool newDataRx = false; //has CoopCam received new data from CoopCommand
bool takePhoto = false; //Did the Blynk App request a photo?
// Timers
unsigned long photoTimer = 500; // Time to run flash before taking photo
unsigned long lastPhotoTimer = 0; // Last time the flash timer was checked
unsigned long wifiTimer = 30000; // How often to try re-connecting if WIFI lost
unsigned long lastWifiTimer = 0; // Last time the WIFI re-connect timer was checked
BlynkTimer timer;
#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
#error "Camera model not selected"
void setup() {
 // ArduinoOTA.begin();
  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector
  pinMode(LED, OUTPUT);
  // Serial, WI-FI & Blynk Communication
  Blynk.begin(auth, ssid, password);
  WiFi.begin( ssid, password );
Blynk.setProperty(V8, "url", 1, pic_url);
  // You can also specify server:
  //Blynk.begin(auth, ssid, pass, "", 80);
  //Blynk.begin(auth, ssid, pass, IPAddress(192,168,1,100), 8080);
  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;
    config.jpeg_quality = 10;
    config.fb_count = 2;
  } else {
    config.frame_size = FRAMESIZE_CIF;
    config.jpeg_quality = 12;
    config.fb_count = 1;
  // Initialize camera
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
String sendPhoto() {
        camera_fb_t * fb = NULL;
  fb = esp_camera_fb_get();
  if(!fb) {
    Serial.println("Camera capture failed");
  //  delay(1000);
// Create a file and write the image data to it;
 ftp.ChangeWorkDir("/public_html/uploads/"); // change it to reflect your directory
 ftp.InitFile("Type I");
 ftp.WriteData(fb->buf, fb->len);
void coopCom ( void ) {
  if (WiFi.status() == WL_CONNECTED) {
    lastWifiTimer = millis();
  else if (WiFi.status() != WL_CONNECTED) {
    if ((unsigned long)(millis() - lastWifiTimer) >= wifiTimer) {
  if (Serial.available() > 0) {
    coopRx =;
    newDataRx = true;
  if (newDataRx == true) {
    if (coopRx == 'O') { //If CoopCommand says the door is up
      newDataRx = false;
    if (coopRx == 'S') { //If CoopCommand says the door is down;
      newDataRx = false;
    if (coopRx == 'U') { //If CoopCommand says the door is opening;;
      newDataRx = false;
    if (coopRx == 'D') { //If CoopCommand says the door is closing;;;
      newDataRx = false;
void flashOff ( void ) {
      digitalWrite(LED, LOW);
      takePhoto = false;
        Blynk.setProperty(V5, "urls", 1, pic_url);
void flashOn (void) {
    digitalWrite(LED, HIGH);
timer.setTimeout(500L, flashOff);
void loop() {
 // ArduinoOTA.handle();

I think that should be “url” not “urls”

If you close then re-open the Blynk app, does the new image display?


Hey Pete, thanks for the quick reply.

I’ve tried both url and urls, to no effect, sorry I must have uploaded a version of code that had the urls but either way is the same problem.

Opening and closing the app does not seem to display the new image, and actually the more I play with it even manually refreshing the image by going into the widget itself does not seem to reliably update the new image.

When you’re doing these manual changes, does each new image have a new (and unique) URL?


No, I’m using the same url. Maybe this is the issue?

For my application, I only ever need one image and I was trying to keep things tight storage wise so I wanted to have the fileserver only ever have to store one image per device. So the ESP32 uploads each image with the same filename as the previous image (1.jpg) and the fileserver automatically overwrites the previous image with the new one.

Is this maybe an issue of Blynk not thinking it actually is a new image, and so holding the old one in a cache? Maybe I should have 2 images per device and it alternates between the two so that blynk sees a new url and refreshes?
For example:
-Uploade Image as “1.jpg”
-Set property to the url that points at 1.jpg
-Upload Image as “2.jpg”
-Set property to to the url that points at 2.jpg.

Yes, the app caches the images, so it needs a new filename to force a refresh.
It’s been discussed quite a few times before. The 3rd topic down in this list is worth a good read, but taker a look at the others too…


Oh perfect, looks like exactly the problem I’m running into, thanks for the links!
I’ll setup a url swapping function and just swap the file name between the two each time. I’ll try that tonight and see if it solves the problem but it sure looks promising.

Not sure if two will do it, but give it a go.


I guess I’ll just keep adding images until I reach the magic number but it gives me a path forward for sure. Even I end up needing 10 images to guarantee a refresh I should still be under 1Mb per device of needed storage.

Thanks again!

I feel I might not be properly understanding the setProperty feature.
For : Blynk.setProperty(V1, “url”, 1, “https://image1.jpg”);
Is the image updated when virtual pin 1 is toggled?
Or to clarify, do I need to have a button in my app linked to V1 in order to get a new image or can I call this like a function that will happen whenever called?
My assumptions have been that if I put it in /setup it will trigger on the virtual pin but if I put it in a function it will run every time that function is called?

If you mean “Is the image updated when the Blynk.setProperty(V1, “url”, 1, “https://image1.jpg”); command is executed” then yes, that’s how it works.


Yes, although putting it in void setup may not be a reliable solution. You’d probably be better adding a BLYNK_CONNECTED callback function that is executed when the Blynk connection actually occurs.

If you’re struggling with this still then why not go for the solution of having every image name unique, using a date/time stamp, so that there will never be an image cached against the current filename?


Morning Pete,

Thanks for the info, makes sense.

In terms of the struggling, I’m probably trying to bridge an even poorer understanding of PHP software with a rudimentary C++/Arduino knowledge level. I liked the auto-overwrite feature of writing over the same filenames as a nice way to prevent an ever growing file folder. But I would probably be better off figuring out how to write a PHP script in my file server to delete any image that is more than “x” days old as a better all round solution.

Thanks again, I’ll attack this from a slightly different direction.

I wasn’t sure if this forum valued updates on resolutions but I believe I have mostly solved this issue, while maintaining my original goal of having a very small quantity of saved images in the image server.

Ultimately, I ended up with a “switch image” function called everytime I took a photo combined with a set of 10 “loading” images.

When the Blynk App requests the ESP32-CAM to take a photo, it runs a Blynk timer to rotate through 10 images which look like a loading bar in 250 millsecond intervals. After 3 seconds of this, a second Blynk timer set as a timeout re-aims the Blynk app at the uploaded image.

On the image capture side, there is a boolean statement to switch between two image save/display locations in the image server and a Bylnk.setproperty statement to flush the cache everytime it switches images. I also dump the camera buffer in the ESP after every photo upload as I was having the previous photo end up stuck in the buffer.

The loading image swap, combined with the two available image locations for the actual desired display image has solved all previous problems. The end user now gets a loading animation to show them a photo is uploaded followed by the new photo after ~3 seconds.

I’m running into a bit of an issue with the device frequently disconnecting/reconnecting but I feel that is likely one last legacy function in my serial communication that uses millis based timers as opposed to the Blynk timer library.

Thanks again for all the help!


I am working also on a project where my mq2 sensor after detecting gas takes a phot. If you do not mind sharing your code, would be unbelievably helpful.

thank you