ESP32-CAM Take Photo and Display in Web Server

Learn how to build a web server with the ESP32-CAM board that allows you to send a command to take a photo and visualize the latest captured photo in your browser saved in SPIFFS. We also added the option to rotate the image if necessary.

ESP32-CAM Take Photo and Display in Web Server

We have other ESP32-CAM projects in our blog that you might like. In fact you can take this project further, by adding a PIR sensor to take a photo when motion is detected, a physical pushbutton to take a photo, or also include video streaming capabilities in another URL path.

Other ESP32-CAM projects and tutorials:

Watch the Video Demonstration

Watch the following video demonstration to see what you’re going to build throughout this tutorial.

Parts Required

To follow this project, you need the following parts:

You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price!

Project Overview

The following image shows the web server we’ll build in this tutorial.

ESP32-CAM Last photo Web Server Display Last Photo Captured

When you access the web server, you’ll see three buttons:

  • ROTATE: depending on your ESP32-CAM orientation, you might need to rotate the photo;
  • CAPTURE PHOTO: when you click this button, the ESP32-CAM takes a new photo and saves it in the ESP32 SPIFFS. Please wait at least 5 seconds before refreshing the web page to ensure the ESP32-CAM takes and stores the photo;
  • REFRESH PAGE: when you click this button, the web page refreshes and it’s updated with the latest photo.

Note: as mentioned previously the latest photo captured is stored in the ESP32 SPIFFS, so even if you restart your board, you can always access the last saved photo.

Installing the ESP32 add-on

We’ll program the ESP32 board using Arduino IDE. So, you need the Arduino IDE installed as well as the ESP32 add-on:

Installing Libraries

To build the web server, we’ll use the ESPAsyncWebServer library. This library also requires the AsyncTCP Library to work properly. Follow the next steps to install those libraries.

Installing the ESPAsyncWebServer library

Follow the next steps to install the ESPAsyncWebServer library:

  1. Click here to download the ESPAsyncWebServer library. You should have a .zip folder in your Downloads folder
  2. Unzip the .zip folder and you should get ESPAsyncWebServer-master folder
  3. Rename your folder from ESPAsyncWebServer-master to ESPAsyncWebServer
  4. Move the ESPAsyncWebServer folder to your Arduino IDE installation libraries folder

Alternatively, after downloading the library, you can go to Sketch > Include Library > Add .ZIP library… and select the library you’ve just downloaded.

Installing the Async TCP Library for ESP32

The ESPAsyncWebServer library requires the AsyncTCP library to work. Follow the next steps to install that library:

  1. Click here to download the AsyncTCP library. You should have a .zip folder in your Downloads folder
  2. Unzip the .zip folder and you should get AsyncTCP-master folder
  3. Rename your folder from AsyncTCP-master to AsyncTCP
  4. Move the AsyncTCP folder to your Arduino IDE installation libraries folder
  5. Finally, re-open your Arduino IDE

Alternatively, after downloading the library, you can go to Sketch > Include Library > Add .ZIP library… and select the library you’ve just downloaded.

ESP32-CAM Take and Display Photo Web Server Sketch

Copy the following code to your Arduino IDE. This code builds a web server that allows you to take a photo with your ESP32-CAM and display the last photo taken. Depending on the orientation of your ESP32-CAM, you may want to rotate the picture, so we also included that feature.

/*********
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/esp32-cam-take-photo-display-web-server/
  
  IMPORTANT!!! 
   - Select Board "AI Thinker ESP32-CAM"
   - GPIO 0 must be connected to GND to upload a sketch
   - After connecting GPIO 0 to GND, press the ESP32-CAM on-board RESET button to put your board in flashing mode
  
  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.
*********/

#include "WiFi.h"
#include "esp_camera.h"
#include "esp_timer.h"
#include "img_converters.h"
#include "Arduino.h"
#include "soc/soc.h"           // Disable brownour problems
#include "soc/rtc_cntl_reg.h"  // Disable brownour problems
#include "driver/rtc_io.h"
#include <ESPAsyncWebServer.h>
#include <StringArray.h>
#include <SPIFFS.h>
#include <FS.h>

// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

// Create AsyncWebServer object on port 80
AsyncWebServer server(80);

boolean takeNewPhoto = false;

// Photo File Name to save in SPIFFS
#define FILE_PHOTO "/photo.jpg"

// OV2640 camera module pins (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

const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <style>
    body { text-align:center; }
    .vert { margin-bottom: 10%; }
    .hori{ margin-bottom: 0%; }
  </style>
</head>
<body>
  <div id="container">
    <h2>ESP32-CAM Last Photo</h2>
    <p>It might take more than 5 seconds to capture a photo.</p>
    <p>
      <button onclick="rotatePhoto();">ROTATE</button>
      <button onclick="capturePhoto()">CAPTURE PHOTO</button>
      <button onclick="location.reload();">REFRESH PAGE</button>
    </p>
  </div>
  <div><img src="saved-photo" id="photo" width="70%"></div>
</body>
<script>
  var deg = 0;
  function capturePhoto() {
    var xhr = new XMLHttpRequest();
    xhr.open('GET', "/capture", true);
    xhr.send();
  }
  function rotatePhoto() {
    var img = document.getElementById("photo");
    deg += 90;
    if(isOdd(deg/90)){ document.getElementById("container").className = "vert"; }
    else{ document.getElementById("container").className = "hori"; }
    img.style.transform = "rotate(" + deg + "deg)";
  }
  function isOdd(n) { return Math.abs(n % 2) == 1; }
</script>
</html>)rawliteral";

void setup() {
  // Serial port for debugging purposes
  Serial.begin(115200);

  // Connect to Wi-Fi
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi...");
  }
  if (!SPIFFS.begin(true)) {
    Serial.println("An Error has occurred while mounting SPIFFS");
    ESP.restart();
  }
  else {
    delay(500);
    Serial.println("SPIFFS mounted successfully");
  }

  // Print ESP32 Local IP Address
  Serial.print("IP Address: http://");
  Serial.println(WiFi.localIP());

  // Turn-off the 'brownout detector'
  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);

  // OV2640 camera module
  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_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);
    ESP.restart();
  }

  // Route for root / web page
  server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
    request->send_P(200, "text/html", index_html);
  });

  server.on("/capture", HTTP_GET, [](AsyncWebServerRequest * request) {
    takeNewPhoto = true;
    request->send_P(200, "text/plain", "Taking Photo");
  });

  server.on("/saved-photo", HTTP_GET, [](AsyncWebServerRequest * request) {
    request->send(SPIFFS, FILE_PHOTO, "image/jpg", false);
  });

  // Start server
  server.begin();

}

void loop() {
  if (takeNewPhoto) {
    capturePhotoSaveSpiffs();
    takeNewPhoto = false;
  }
  delay(1);
}

// Check if photo capture was successful
bool checkPhoto( fs::FS &fs ) {
  File f_pic = fs.open( FILE_PHOTO );
  unsigned int pic_sz = f_pic.size();
  return ( pic_sz > 100 );
}

// Capture Photo and Save it to SPIFFS
void capturePhotoSaveSpiffs( void ) {
  camera_fb_t * fb = NULL; // pointer
  bool ok = 0; // Boolean indicating if the picture has been taken correctly

  do {
    // Take a photo with the camera
    Serial.println("Taking a photo...");

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

    // Photo file name
    Serial.printf("Picture file name: %s\n", FILE_PHOTO);
    File file = SPIFFS.open(FILE_PHOTO, FILE_WRITE);

    // Insert the data in the photo file
    if (!file) {
      Serial.println("Failed to open file in writing mode");
    }
    else {
      file.write(fb->buf, fb->len); // payload (image), payload length
      Serial.print("The picture has been saved in ");
      Serial.print(FILE_PHOTO);
      Serial.print(" - Size: ");
      Serial.print(file.size());
      Serial.println(" bytes");
    }
    // Close the file
    file.close();
    esp_camera_fb_return(fb);

    // check if file has been correctly saved in SPIFFS
    ok = checkPhoto(SPIFFS);
  } while ( !ok );
}

View raw code

How the Code Works

First, include the required libraries to work with the camera, to build the web server and to use SPIFFS.

#include "WiFi.h"
#include "esp_camera.h"
#include "esp_timer.h"
#include "img_converters.h"
#include "Arduino.h"
#include "soc/soc.h"           // Disable brownout problems
#include "soc/rtc_cntl_reg.h"  // Disable brownout problems
#include "driver/rtc_io.h"
#include <ESPAsyncWebServer.h>
#include <StringArray.h>
#include <SPIFFS.h>
#include <FS.h>

Next, write your network credentials in the following variables, so that the ESP32-CAM can connect to your local network.

const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

Create an AsyncWebServer object on port 80.

AsyncWebServer server(80);

The takeNewPhoto boolean variable indicates when it’s time to take a new photo.

boolean takeNewPhoto = false;

Then, define the path and name of the photo to be saved in SPIFFS.

#define FILE_PHOTO "/photo.jpg"

Next, define the camera pins for the ESP32-CAM AI THINKER module.

#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

Building the Web Page

Next, we have the HTML to build the web page:

const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <style>
    body { text-align:center; }
    .vert { margin-bottom: 10%; }
    .hori{ margin-bottom: 0%; }
  </style>
</head>
<body>
  <div id="container">
    <h2>ESP32-CAM Last Photo</h2>
    <p>It might take more than 5 seconds to capture a photo.</p>
    <p>
      <button onclick="rotatePhoto();">ROTATE</button>
      <button onclick="capturePhoto()">CAPTURE PHOTO</button>
      <button onclick="location.reload();">REFRESH PAGE</button>
    </p>
  </div>
  <div><img src="saved-photo" id="photo" width="70%"></div>
</body>
<script>
  var deg = 0;
  function capturePhoto() {
    var xhr = new XMLHttpRequest();
    xhr.open('GET', "/capture", true);
    xhr.send();
  }
  function rotatePhoto() {
    var img = document.getElementById("photo");
    deg += 90;
    if(isOdd(deg/90)){ document.getElementById("container").className = "vert"; }
    else{ document.getElementById("container").className = "hori"; }
    img.style.transform = "rotate(" + deg + "deg)";
  }
  function isOdd(n) { return Math.abs(n % 2) == 1; }
</script>
</html>)rawliteral";

We won’t go into much detail on how this HTML works. We’ll just take a quick overview.

Basically, create three buttons: ROTATE; CAPTURE PHOTO and REFRESH PAGE. Each photo calls a different JavaScript function: rotatePhoto(), capturePhoto() and reload().

<button onclick="rotatePhoto();">ROTATE</button>
<button onclick="capturePhoto()">CAPTURE PHOTO</button>
<button onclick="location.reload();">REFRESH PAGE</button>

The capturePhoto() function sends a request on the /capture URL to the ESP32, so it takes a new photo.

function capturePhoto() {
  var xhr = new XMLHttpRequest();
  xhr.open('GET', "/capture", true);
  xhr.send();
}

The rotatePhoto() function rotates the photo.

function rotatePhoto() {
  var img = document.getElementById("photo");
  deg += 90;
  if(isOdd(deg/90)){ document.getElementById("container").className = "vert"; }
  else{ document.getElementById("container").className = "hori"; }
  img.style.transform = "rotate(" + deg + "deg)";
}
function isOdd(n) { return Math.abs(n % 2) == 1; }

We’re not sure what’s the “best” way to rotate a photo with JavaScript. This method works perfectly, but there may be better ways to do this. If you have any suggestion please share with us.

Finally, the following section displays the photo.

<div><img src="saved-photo" id="photo" width="70%"></div>

When, you click the REFRESH button, it will load the latest image.

setup()

In the setup(), initialize a Serial communication:

Serial.begin(115200);

Connect the ESP32-CAM to your local network:

WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
  delay(1000);
  Serial.println("Connecting to WiFi...");
}

Initialize SPIFFS:

if (!SPIFFS.begin(true)) {
  Serial.println("An Error has occurred while mounting SPIFFS");
  ESP.restart();
}
else {
  delay(500);
  Serial.println("SPIFFS mounted successfully");
}

Print the ESP32-CAM local IP address:

Serial.print("IP Address: http://");
Serial.println(WiFi.localIP());

The lines that follow, configure and initialize the camera with the right settings.

Handle the Web Server

Next, we need to handle what happens when the ESP32-CAM receives a request on a URL.

When the ESP32-CAM receives a request on the root / URL, we send the HTML text to build the web page.

server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
  request->send_P(200, "text/html", index_html);
});

When we press the “CAPTURE” button on the web server, we send a request to the ESP32 /capture URL. When that happens, we set the takeNewPhoto variable to true, so that we know it is time to take a new photo.

server.on("/capture", HTTP_GET, [](AsyncWebServerRequest * request) {
  takeNewPhoto = true;
  request->send_P(200, "text/plain", "Taking Photo");
});

In case there’s a request on the /saved-photo URL, send the photo saved in SPIFFS to a connected client:

server.on("/saved-photo", HTTP_GET, [](AsyncWebServerRequest * request) {
  request->send(SPIFFS, FILE_PHOTO, "image/jpg", false);
});

Finally, start the web server.

server.begin();

loop()

In the loop(), if the takeNewPhoto variable is True, we call the capturePhotoSaveSpiffs() to take a new photo and save it to SPIFFS. Then, set the takeNewPhoto variable to false.

void loop() {
  if (takeNewPhoto) {
    capturePhotoSaveSpiffs();
    takeNewPhoto = false;
  }
  delay(1);
}

Take a Photo

There are two other functions in the sketch: checkPhoto() and capturePhotoSaveSpiffs().

The checkPhoto() function checks if the photo was successfully saved to SPIFFS.

bool checkPhoto( fs::FS &fs ) {
  File f_pic = fs.open( FILE_PHOTO );
  unsigned int pic_sz = f_pic.size();
  return ( pic_sz > 100 );
}

The capturePhotoSaveSpiffs() function takes a photo and saves it to SPIFFS.

void capturePhotoSaveSpiffs( void ) {
  camera_fb_t * fb = NULL; // pointer
  bool ok = 0; // Boolean indicating if the picture has been taken correctly

  do {
    // Take a photo with the camera
    Serial.println("Taking a photo...");

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

    // Photo file name
    Serial.printf("Picture file name: %s\n", FILE_PHOTO);
    File file = SPIFFS.open(FILE_PHOTO, FILE_WRITE);

    // Insert the data in the photo file
    if (!file) {
      Serial.println("Failed to open file in writing mode");
    }
    else {
      file.write(fb->buf, fb->len); // payload (image), payload length
      Serial.print("The picture has been saved in ");
      Serial.print(FILE_PHOTO);
      Serial.print(" - Size: ");
      Serial.print(file.size());
      Serial.println(" bytes");
    }
    // Close the file
    file.close();
    esp_camera_fb_return(fb);

    // check if file has been correctly saved in SPIFFS
    ok = checkPhoto(SPIFFS);
  } while ( !ok );
}

This function was based on this sketch by dualvim.

ESP32-CAM Upload Code

To upload code to the ESP32-CAM board, connect it to your computer using an FTDI programmer. Follow the next schematic diagram:

ESP32-CAM FTDI Programmer Wiring Circuit Diagram 5V

Important: GPIO 0 needs to be connected to GND so that you’re able to upload code.

Many FTDI programmers have a jumper that allows you to select 3.3V or 5V. Make sure the jumper is in the right place to select 5V.

ESP32-CAMFTDI Programmer
GNDGND
5VVCC (5V)
U0RTX
U0TRX
GPIO 0GND

To upload the code, follow the next steps:

1) Go to Tools > Board and select AI-Thinker ESP32-CAM.

2) Go to Tools > Port and select the COM port the ESP32 is connected to.

3) Then, click the upload button to upload the code.

4) When you start to see these dots on the debugging window as shown below, press the ESP32-CAM on-board RST button.

After a few seconds, the code should be successfully uploaded to your board.

Demonstration

Open your browser and type the ESP32-CAM IP Address. Then, click the “CAPTURE PHOTO” to take a new photo and wait a few seconds for the photo to be saved in SPIFFS.

Then, if you press the “REFRESH PAGE” button, the page will update with the latest saved photo. If you need to adjust the image orientation, you can always use the “ROTATE” button to do it so.

ESP32-CAM Web Server Display Last Photo Captured Demonstration

In your Arduino IDE Serial Monitor window, you should see similar messages:

ESP32-CAM Web Server Display Last Photo Captured Arduino IDE Serial Monitor

Troublehsooting

If you’re getting any of the following errors, read our ESP32-CAM Troubleshooting Guide: Most Common Problems Fixed

  • Failed to connect to ESP32: Timed out waiting for packet header
  • Camera init failed with error 0x20001 or similar
  • Brownout detector or Guru meditation error
  • Sketch too big error – Wrong partition scheme selected
  • Board at COMX is not available – COM Port Not Selected
  • Psram error: GPIO isr service is not installed
  • Weak Wi-Fi Signal
  • No IP Address in Arduino IDE Serial Monitor
  • Can’t open web server
  • The image lags/shows lots of latency

Learn how to program and build 17 projects with the ESP32-CAM using Arduino IDE DOWNLOAD »

Learn how to program and build 17 projects with the ESP32-CAM using Arduino IDE DOWNLOAD »

Wrapping Up

We hope you’ve found this example useful. We’ve tried to keep it as simple as possible so it is easy for you to modify and include it in your own projects. You can combine this example with the ESP32-CAM PIR Motion Detector with Photo Capture to capture and display a new photo when motion is detected.

For more ESP32-CAM projects you can subscribe to our newsletter. If you don’t have an ESP32-CAM yet, you can get one for approximately $6.

If you like this project, you may also like other projects with the ESP32-CAM:

Thank you for reading.



Build Web Server projects with the ESP32 and ESP8266 boards to control outputs and monitor sensors remotely. Learn HTML, CSS, JavaScript and client-server communication protocols DOWNLOAD »

Build Web Server projects with the ESP32 and ESP8266 boards to control outputs and monitor sensors remotely. Learn HTML, CSS, JavaScript and client-server communication protocols DOWNLOAD »


Enjoyed this project? Stay updated by subscribing our newsletter!

90 thoughts on “ESP32-CAM Take Photo and Display in Web Server”

  1. Thanks for this tutorial!
    I have a question.
    This photo can be accessed over internet around the world or only can be see the picture over local net?

    Thanks a lot!

    Reply
  2. Thanks guys, it really looks like an interesting project.
    One thing is not clear to me, and that is, the last shot remains in memory ready to be reviewed, but does not save everything on the SD card?

    Grazie ragazzi, sembra davvero un progetto interessante.
    Non mi è chiara una cosa, e cioè, l’ultimo scatto resta in memoria pronto per essere rivisto, ma non salva tutto sulla scheda SD?

    Reply
  3. Great tutorial! I wish these things came with better cameras. This program works great, but the photos are all out of focus.

    Reply
  4. Hi Sara,
    I have an ESP-EYE so needed to change the pins.
    Espressif’s github has a file with several camera models, so anyone using other devices may like to look at that.
    Usage is simply:
    #define CAMERA_MODEL_ESP_EYE
    #include “camera_pins.h”
    instead of the whole list of defines that could introduce typing mistakes.

    I selected ESP32 Dev Module in the IDE and the ESP-EYE works.

    Dave

    Reply
  5. Hi Sara,

    The ESP32-CAM works fine for a while and after some time, it stop working. I have to reset the ESP32-CAM module to make it to work again.

    Ong Kheok Chin

    Reply
  6. HI !
    Nice tutorial once again. 🙂
    I combined it with softAccess point so it is standalone without router.
    Gonna be interesting to build on….

    Reply
  7. You can power it with a 9v battery but use a voltage regulator to power down from 9v to 5v (eg a 5v mosfet). Otherwise you’ll fry it if powering directly to the ESP32. There is always the problem of the battery running out very quickly (1 to 2 hours).
    So, it is much better to use a wall plug 5v power supply. Although this doesn’t make it portable.
    I would be interested in anyone who can power the ESP32 using a battery which will last for a long time.

    Reply
    • Hi.
      Make sure you have the latest version of the ESP32 boards installed and that you’ve selected an ESP32-CAM board in Tools>Board.
      Regards,
      Sara

      Reply
  8. An error occurred while uploading the sketch
    Traceback (most recent call last):
    esptool.py v2.6
    File “esptool.py”, line 2959, in
    Serial port COM7
    File “esptool.py”, line 2952, in _main
    File “esptool.py”, line 2652, in main
    File “esptool.py”, line 222, in __init__
    File “site-packages\serial\__init__.py”, line 88, in serial_for_url
    File “site-packages\serial\serialwin32.py”, line 62, in open
    serial.serialutil.SerialException: could not open port ‘COM7’: WindowsError(2, ‘The system cannot find the file specified.’)
    Failed to execute script esptool

    hii.. this error i am getting while uploading code.. can you please help me out regarding the same.

    Reply
  9. I am getting this error while uploading the code

    Arduino: 1.8.12 (Windows 10), Board: “AI Thinker ESP32-CAM”

    Sketch uses 864762 bytes (27%) of program storage space. Maximum is 3145728 bytes.
    Global variables use 41384 bytes (12%) of dynamic memory, leaving 286296 bytes for local variables. Maximum is 327680 bytes.
    esptool.py v2.6
    Serial port COM7
    Connecting…….._____….._____….._____….._____….._____….._____….._____

    A fatal error occurred: Failed to connect to ESP32: Timed out waiting for packet header
    A fatal error occurred: Failed to connect to ESP32: Timed out waiting for packet header

    This report would have more information with
    “Show verbose output during compilation”
    option enabled in File -> Preferences.

    Please help me to find the solution for this

    Reply
  10. I get the following display in the monitor. The web page works fine. 2 problems, 1 jpg is corrupt, 2 the code never comes back from
    fb = esp_camera_fb_get(); Please help.

    12:23:33.566 -> ets Jun 8 2016 00:22:57
    12:23:33.566 ->
    12:23:33.566 -> rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
    12:23:33.566 -> configsip: 0, SPIWP:0xee
    12:23:33.566 -> clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
    12:23:33.566 -> mode:DIO, clock div:1
    12:23:33.566 -> load:0x3fff0018,len:4
    12:23:33.566 -> load:0x3fff001c,len:1100
    12:23:33.566 -> load:0x40078000,len:9232
    12:23:33.566 -> load:0x40080400,len:6400
    12:23:33.566 -> entry 0x400806a8
    12:23:36.375 -> Connecting to WiFi…
    12:23:36.949 -> SPIFFS mounted successfully
    12:23:36.949 -> IP Address: http://192.168.1.7
    12:26:08.080 -> Taking a photo…

    Reply
      • Hi.
        Try lowering the jpg quality (choose a higher number) and see if you can get a better image (not black)
        config.jpeg_quality = 10;

        Regards,
        Sara

        Reply
  11. Hello, I recently had problems with this error and it led me to discover something. When one clicks on the button “See raw code”, this code opens in a new tab but the spaces of the words are altered … For example what should be #inlcude “soc / soc.h” , becomes #inlcude “soc / soc.h”.

    Reply
  12. Hi…..Does it have to be done in the same network? can it be done remotely outside the city and with a different network connection?

    Reply
  13. This was an amazing tutorial!
    For those using ESP EYE, you will get a recurring crash when you run the camera init unless you add

    pinMode(13, INPUT_PULLUP);
    pinMode(14, INPUT_PULLUP);

    To your setup. I found this in the ESP32 github, not a hardware engineer, so I have a limited idea of how it works, but it does work 🙂

    Reply
  14. How can I add a Pir motion sensor to this particular project ? I want the module to capture a picture whenever motion is detected. Meaning, the ‘Capture’ button will be removed in this case.

    Reply
  15. This code works perfect. But when i try adding additional html code. It fails to compile with error ‘capturephotosavespiffs’ is not decleared in this scope

    Reply
  16. I was struggling to do a basic camera setup since few months until i started afresh and followed this instructions. Was finally able to solve it in the first go..Thanks Rui and Sara…!!

    Reply
  17. Hello and Thanks for this tutorial, which works perfectly.

    I’d like to port this feature (displaying on a web page the last picture saved to SPIFFS) in an existing project, that unfortunately is based on “WebServer” instead of “AsyncWebServer”:

    The server is declared as follows:
    WebServer web_server(80);

    I included the image line in the web page:

    and I can confirm that the server running on “saved-photo” calls the related handler “handleSavedPhoto” every time the page is loaded:
    web_server.on(“/saved-photo”, HTTP_GET, handleSavedPhoto);

    but in the ‘handleSavedPhoto’ function I can’t find a way to provide the image data to the client.

    For example, other handlers are using this syntax for text:
    web_server.send(200, “text/html”, serverLogin); (this sends a web page in the specified String)

    or

    web_server.send(200, “text/plain”, “Click ‘Save and Restart’ to Apply'”);
    (this sends the specified message).

    I can’t find a way to port your AsyncWebServer-based code:
    request->send(SPIFFS, FILE_PHOTO, “image/jpg”, false);
    to the ‘WebServer’ object.

    Is there any way you can point me to?

    Thanks a lot and Best Regards!
    Alessandro

    Reply
  18. Is it normal for the analog ports not to work when when using this example?
    It stops working after SPIFFS starts in the if loop

    if (!SPIFFS.begin(true))

    Reply
  19. I can’t get this to work. Something in your code is making the app select the wrong file. It wants “pyserial” and is looking for “serial”. What and where can I change something in your code to fix this?

    Reply
  20. Hi and thanks for the code, this works on ESP-EYE, after fixing the dreaded “Multiple libraries were found for “WiFi.h” issue.

    Anyone wishing to assign a hostname to avoid dup names and latency delays on multiple espressif projects on the local lan router can add a line at 102 as shown below after // Connect to Wi-Fi

    WiFi.setHostname(“Camera-Webserver”);

    Reply
  21. Hi Pat

    I realise it’s a year since you posted this but just in case you get to see this message – would you be able to share how you found out the camera module was “bad”? Did you just replace it with another one and it worked? I’m asking because I have 4 (2 from a vendor, 2 from another) and none of them seems to be working. I don’t want to keep ordering cameras and waiting a month or more for them to be delivered only to find out that they don’t work. I am hoping there could be a test, something like a “health check” for the camera module to decide if it’s working properly or not.

    Many thanks

    Reply
  22. Greetings Sara and Rui and thanks for all the info and examples that you share, and all the support you offer, too – very nice of you!

    Could you please help me with something? I am trying to get the ESP32-CAM module to take pictures or videos and it keeps failing with the “esp_camera_fb_get(): Failed to get the frame on time!” error.

    I have 2 ESP32-CAM modules from a vendor and 2 more ESP32-CAM modules from another vendor and they all behave the same. I reckon the modules are AI Thinker clones, they have the Espressif logo on the metal shield, not the AI Thinker one but I guess/hope this doesn’t really matter. Power is perfectly fine (ESP32-CAM powered on the 3.3V pin from a breadboard power supply which is itself powered from a 7V/2A wall plug. With other circuits the same power supply also powers a few sensors, transistors, LEDs without any issues or brownout alerts. The serial console is via an FTDI232 adapter connected to the serial pins GPIO1 & GPIO3 and GND and it works perfectly fine both for flashing the ESP32 modules and for the serial console. Everything else works fine on the ESP32-CAM modules (WiFi, SD card, camera LED, GPIO pins).

    I tried your sketch here many many times, and I also tried the default example CameraWebServer, and also another sketch from github (ov2640_timelapse) and they all fail each time with the same error “esp_camera_fb_get(): Failed to get the frame on time!”. On any of the 4 ESP32-CAM modules.

    I spent the entire Saturday yesterday searching the web and trying various things but I still get the same error. I also read your “ESP32-CAM Troubleshooting Guide” however the advice under point “11. esp_camera_fb_get(): Failed to get the frame on time!” is not relevant for me as I don’t have an M5STACK module and all my modules have psram and it is successfully detected and used.

    I was able to get the camera to capture some data by using your sketch here and manually forcing config.pixel_format = PIXFORMAT_YUV422 but the colors were waaay off, and YUV isn’t anyway what I need (JPEG). Full description here (including pictures of the very simple circuit) if you’re interested:
    https://github.com/espressif/esp32-camera/issues/43#issuecomment-808745113
    https://github.com/raduprv/esp32-cam_ov2640-timelapse/issues/1#issuecomment-808766448

    At this moment I can’t think of anything else to try except this one small request to you:

    Would you please try to recompile and reflash your sketch on one of your ESP32-CAM modules and if it still works, share the version of the Arduino IDE, ESP32 json file, ESP32 library and ESP32 camera library? I suspect it might be a software issue rather than a hardware one and I am curious to see if your sketch would still work now, with the updated libraries and board definitions. After all more than a year has passed since you published this (the youtube video is dated 8 Nov 2019) and I suppose many libraries have been updated since then. Maybe something was broken along the way, that’s why I’d be very interested to know if you can still successfully recompile, reflash and rerun this sketch now.

    Many, many thanks,
    Dan

    Reply
    • Hi.
      I just tested this example.
      It is working just fine for me.
      I’m using Arduino IDE 1.8.13.
      ESP32 Boards version 1.0.5
      Do you have a good wifi connection?
      Regards,
      Sara

      Reply
      • Hi Sara and thanks a lot for confirming it’s working fine with those versions.

        I am also using Arduino IDE 1.8.13, with ESP32 boards version 1.0.6.
        The WiFi signal is good, the router reports signal quality around 70% and there are no disconnects.
        Anyway, the problem is not with the WiFi signal, it is with the camera modules, I can’t take any pictures or videos whatsoever – it keeps reporting “esp_camera_fb_get(): Failed to get the frame on time!” every time, even when I don’t turn on the WiFi.

        I have 2 cameras labelled “AF2569 0927XB” and 2 more labelled “DCX-OV2640-V2” – none of them work (on 4 ESP32-CAM modules from 2 different vendors). I ordered 2 more OV2640 cameras but I’ll have to wait for a few more weeks for them to arrive. And I don’t really know the exact model the vendor shipped, it’s not unusual for the item description to say something and the actual item to be slightly different…

        At this point I don’t know what else I could possibly try to understand if all these 4 cameras are indeed defective – it seems quite unusual to me.

        Best regards,
        Dan

        Reply
        • Hi Dan.
          Yes, it is an unusual situation.
          All cameras that I ordered worked well. So, it is unlikely that you have 4 defective cameras.
          There must be something wrong with your setup or something, but I can’t tell what it is 😐
          Regards,
          Sara

          Reply
        • Hi Dan, did you manage to get it to work in the end?
          I have ordered 4 ESP-32CAM boards of which 2 work flawlessly.
          The third ESP board gives an error on initialization: esp_camera_init(): Camera probe failed with error 0x20004
          which I believe is the default error that the board gives when a camera is not properly connected. I have tried different cameras, checked the connectors, etc. and I have also tried playing with the settings without luck, so I suspect that maybe there is a loose connection on the PCB (I noticed that they use very little solder directly under the ESP32 module). Perhaps a reflow will solve the issue?

          The last ESP-32CAM board that I have initializes the camera correctly, but reports the [E][camera.c:1483] esp_camera_fb_get(): Failed to get the frame on time! error whenever I want to pull data from the sensor. I have tried different cameras, different clocks, different powersupplies, but nothing seems to work.

          I have already given up on the third ESP32 board, but I still have some hope for the fourth board since it at least initializes the camera without errors. Please let me know if you managed to get it to work.

          Reply
      • Thanks James but it’s not that – my modules have psram and it’s detected ok.
        I even reduced the size of the picture to 800×600 and reduced the jpeg quality to 50 and used only one framebuffer so no psram would be needed and it’s still the same result.

        Reply
          • Ha, original or clone! I think they’re all “clones” in the end, it reminds me on this: youtu.be/bifOI4MbHVU?t=5 🙂
            “Components. American components, Russian components, ALL MADE IN TAIWAN!”

            Thanks for the telegram v5 project URL, I’ll have a closer look at it later, I took a glance and I’d have to weed out anything telegram-related to see if the camera part works. Not difficult but I’m in a hurry right now.

            As for the “make sure you have the latest firmware in the camera” (your second message, below, without a “reply” button) – please excuse the silly question, but does the tiny camera module itself also have upgradable firmware, or were you referring to the ESP32-CAM module? For the latter, esptool.py identifies it as ESP32-D0WD rev 1 (manufacturer 20, device 4016) with 4MB of flash. Now, if I only knew the real manufacturer, I could search for the correct firmware for it, but with the “original or clone” question unanswered I’m not really sure where to get the latest firmware from. Any hints?

            Cheers!

          • OK, I just cleaned ESP32-Cam-Telegram/v5/ of all telegram things leaving only the camera related stuff and I got the same error:
            22:29:21.469 -> [E][camera.c:1483] esp_camera_fb_get(): Failed to get the frame on time!
            22:29:25.485 -> [E][camera.c:1483] esp_camera_fb_get(): Failed to get the frame on time!
            22:29:29.496 -> [E][camera.c:1483] esp_camera_fb_get(): Failed to get the frame on time!

          • Hi.
            One of our readers suggested the following for that error:

            “I found out the solution for my case, I hope many others can read this message because I have seen many people frustrated on the Internet with the error ” esp_camera_fb_get(): Failed to get the frame on time!”.

            Everything was solved just changing some parameters:


            config.xclk_freq_hz = 5000000;

            config.frame_size = FRAMESIZE_SVGA;

            I hope this helps.

            Regards,
            Sara

          • Hi Sara and thanks a lot for your help.

            I played with the frequency, the JPEG image size & quality and the number of frame buffers before I even posted here 🙂 I tried different combinations but in the end I went so far as freq=5M, size=VGA, quality=50 and only 1 fb and it still didn’t work.

            I ordered 2 more ESP32-CAM modules from 2 more vendors and I’m going to try again once they arrive (probably in 4-6 weeks) as I think I’ve exhausted all options for now. If neither of those 2 modules will work I’ll just call it a day as I can’t sink any more time in this. It’s been nothing but a frustration festival.

            Thanks again, much appreciated!
            Dan

          • Hi James and thanks for the pointers.

            Yes, I think I have a pretty stable power supply for this dev board, I am using one of those breadboard power supplies. I am supplying it with 7V from a 7V/2A PSU and it is in turn supplying 5V to the ESP32-CAM 5V pin. The breadboard power supply I understood is rated for 0.7A output. The ESP32-CAM module doesn’t give any brownout warnings.

            WRT to the firmware – I see espressif published versions for the ESP32-WROOM-32 Series, the ESP32-WROVER-32 Series, the ESP32-PICO Series and the ESP32-SOLO Series.

            The ESP32-CAM dev board has a ESP-32S chip on it – at least that’s what’s printed on the metal shield. As I previously mentioned, esptool.py identifies it as ESP32-D0WD rev 1 (manufacturer 20, device 4016) with 4MB of flash.

            http://esp32.net/ lists ESP32-WROOM-32D (previously ESP-WROOM-32D) as a “Revision of the ESP-WROOM-32 module which uses an ESP32-D0WD chip instead of an ESP32-D0WDQ6 chip”.

            I suppose that would mean I could flash the firmware for the ESP32-WROOM-32 Series then.

            I downloaded the file download.espressif.com/esp_at/firmware/ESP32/ESP32_WROOM/ESP32-WROOM-32_AT_Bin_V2.1.0.0.zip and I’ll have to figure how to flash it. I saw in docs.espressif.com/_/downloads/esp-at/en/latest/pdf/ that I’d need not just one but two serial connections from the computer running esptool.py to the dev board. Considering ESP32-CAM modules do not have an USB port that would mean I would have to use 2 FTDI serial to USB adapters. And here is my issue: while I do have a 2nd serial to USB adapter, I’m not sure if it can work with 3.3V, I have a feeling it might only work with 5V levels, which would (likely) damage the ESP32-CAM.

            I’ll try to look further into this tomorrow, I have a feeling I’m going to break something in the process 🙂 I only hope it’s not going to be on the computer side!

            Dan

  23. Hi Sara,

    Thanks for this great tutorial. I want to do some modifications i.e. to capture and refresh at the same time. i tried to put reload into the capture

    function capturePhoto() {
    var xhr = new XMLHttpRequest();
    xhr.open(‘GET’, “/capture”, true);
    xhr.send();
    location.reload(); //<———–but it still doesn’t reload after capturing
    }

    but it still doesn’t reload after capturing. Any Solution???

    Reply
  24. Hi Dan, yes 3.3v is the max on the FTDI. It should have a high current rating (at least 250mA, once you enable WiFi you probably want to be closer to 500mA.). I found a firmware test process to get you sorted learn.adafruit.com/upgrading-esp32-firmware?view=all

    Reply
  25. Hello,

    I do not know much about ESP32, so I am confused. The code works perfectly, but where are the pictures stored?

    Thanks!

    Reply
  26. hello Sara and Rui
    I worked your Remote Controlled Car
    Robot with Camera (Web Server) project and add ip static for control robot out of home
    but video does not exec and only buttoms inhibited how change this program that can access succesfully it by internet.
    thanks vm,
    areza

    Reply
  27. Hello Sara, hello Rui,
    how does the power supply with the 5 volt power supply (no breadboard) work with the ESP 32 Cam without usb connection?

    Reply
    • Hi.
      You just need to connect the positive supply to the 5V pin and the negative to a GND pin.
      REgards,
      Sara

      Reply
  28. Hi Sara, hi Rui,
    have already tried a bit.
    See error message
    But no picture arrives.
    Sketch compiled without errors

    19:36:19.559 -> Connecting to WiFi…
    19:36:20.107 -> SPIFFS mounted successfully
    19:36:20.107 -> IP Address: http://192.168.178.53
    19:36:39.525 -> Taking a photo…
    19:36:43.685 -> [E][camera.c:1344] esp_camera_fb_get(): Failed to get the frame on time!
    19:36:43.685 -> Camera capture failed
    19:53:10.305 -> Taking a photo…
    19:53:10.305 -> Picture file name: /photo.jpg
    19:53:10.706 -> The picture has been saved in /photo.jpg – Size: 38784 bytes
    19:54:10.300 -> Taking a photo…
    19:54:10.300 -> Picture file name: /photo.jpg
    19:54:10.654 -> The picture has been saved in /photo.jpg – Size: 38784 bytes

    Reply
  29. Hello,
    thanks for the great tutorial. Do you have an example of this server where the image that is captured is also posted to a cloud hosted site in addition to saving it locally?
    Thanks!

    Reply
  30. Hi,
    at first, I have to thank for your fantastic website. it is a couple of day I am familiar with your tutorials and I join to your YouTube channel and subscribe that.
    I have two question:
    1- how can I set my WiFi module as a Access Point(AP) and then after that I connected to the module via cell phone, I send my router SSID and Password. in this way my project would be flexible in any home.
    2- how can I send photo to the server without using SPIFF? I mean I do not want to save picture in SD memory and I just want to send image to the server.

    do you have any tutorial related to? if any, thank you to share them with me.

    Reply
    • Hi.
      Search for “ESP32 Wi-Fi Manager”. It allows you to connect to the board via an access point to set up the network credentials.
      At the moment, we don’t have any examples for that.
      Regards,
      Sara

      Reply
  31. Hi,

    First, thanks a lot for your great work that has helped me so much.

    Now that I have spent some time with the ESP32-cam, I have a few suggestions to share that may be useful to others :

    In the sketch above, I found it unnecessary to retake another picture if “checkPhoto(SPIFFS)” fails. It is enough to re-save the file until success. This will speed up the process, and it works fine every time for me. Like so:

    uint8_t i;
    do {
    file.write(fb->buf, fb->len); // payload (image), payload length
    ok = checkPhoto(SPIFFS); // check if file is saved ok
    Serial.print(“.”);
    i++;
    if (i > 10) break; // give up
    } while ( !ok ); // else retry

    When I ping:ed the ESP from my pc, the ping response times looked very strange. They slowly increased from 25 ms to 120 ms, then suddenly dropped to 25 again and started to rise again (repeated forever), but it could be cured by adding “WiFi.setSleep(false);” in setup. I found that solution here : https://github.com/espressif/arduino-esp32/issues/1484
    After this fix, ping times were constantly low and wifi performance much better.
    This may be good to add in other ESP32 (-cam ?) projects as well.

    I noticed that the OV2640 camera (yes, the camera itself) is transmitting RF noise as soon as camera init occurs with “esp_camera_init(&config);”
    This noise has great impact on wifi performance, so I ended up with another sketch design that is too complex to describe here, but the trick was to use the camera OR wifi – never both at the same time. I do this by putting the ESP in deep sleep between active sessions and wake it up from a “HC-12” 433mhz transceiver module, connected to my Home Automation System. I can send HC-12 messages to snap a picture (w/o starting wifi) OR starting wifi and web server so I can have a look at the latest picture taken. The HC-12 needs just a few uA when idle so this setup can run for a few weeks with a small battery if used sparesly.

    The very first picture taken may have some greenish tint. This can be fixed with a delay after the above camera init:

    uint32_t camWarmupTimer = millis();
    while (millis() – camWarmupTimer < 1000) { // wait here for camera warmup
    yield();
    delay(50);
    }

    For better night performance, these lines (after camera init) will make a big difference:

    s->set_exposure_ctrl(s, 0); // auto exposure off
    s->set_aec_value(s, 1200); // set exposure manually (0-1200)
    s->set_gain_ctrl(s, 0); // auto gain off
    s->set_agc_gain(s, 12); // set gain manually (0 - 30)

    In my setup, this will only be executed during darkness.

    By using a static ip address, there is no need for name mapping etc. Just make a note on what ip each cam has. Also, the wifi connect process will gain some milliseconds 😉

    Replacing the factory camera with one of these
    alibaba.com/product-detail/Smart-Electronics-OV2640-ESP32-MCU-camera_62313648026.html
    should improve both day and night performance, but I am still waiting for delivery so have not yet tested.

    /Ray

    Reply
    • Hi.
      Thank you so much for sharing these tips and in such great detail.
      This will definitely be useful for a lot of our readers.
      And I’ll also have to test them and see the results.
      Thanks once again.
      Regards,
      Sara

      Reply

Leave a Reply to GG Cancel reply

Download our Free eBooks and Resources

Get instant access to our FREE eBooks, Resources, and Exclusive Electronics Projects by entering your email address below.