Learn how to use the ESP32-CAM board to create a web server that allows you to send a command to take a photo and view it in your browser as a SPIFFS file. If necessary, we also included the ability to rotate the image.
Parts Required
To follow this project, you need the following parts:
- ESP32-CAM with OV2640 –The ESP32 CAM WiFi Module Bluetooth with OV2640 Camera Module 2MP For Face Recognization has a very competitive small-size camera module that can operate independently as a minimum system with a footprint of only 40 x 27 mm; a deep sleep current of up to 6mA and is widely used in various IoT applications.
- Female-to-female jumper wires
- FTDI programmer
- 5V power supply or power bank
Project overview
There are three buttons when we access a web server.
ROTATE: You may need to rotate the shot depending on the orientation of your ESP32-CAM
CAPTURE PHOTO: This button causes the ESP32-CAM to snap a fresh photo and save it to the ESP32 SPIFFS. To ensure that the ESP32-CAM shoots and stores the shot, please wait at least 5 seconds before reloading the web page.
REFRESH PAGE: When you click this button, the web page refreshes and the most recent photo is added.
Installing Libraries
We’ll utilise the ESPAsyncWebServer library to create the web server. The AsyncTCP Library is also required for this library to function properly. Install the libraries by following the steps below.
Installing the ESPAsyncWebServer library
Follow the next steps to install the ESPAsyncWebServer library:
- Click here to download the ESPAsyncWebServer library.In your Downloads area, there should be a.zip folder.
- You should get the ESPAsyncWebServer-master folder after unzipping the.zip package.
- ESPAsyncWebServer-master should be renamed to ESPAsyncWebServer.
- Place the ESPAsyncWebServer folder in the Arduino IDE’s 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:
- Click here to download the AsyncTCP library. You should have a .zip folder in your Downloads folder
- Unzip the .zip folder and you should get AsyncTCP-master folder.
- Rename your folder from AsyncTCP-master to AsyncTCP
- Move the AsyncTCP folder to your Arduino IDE installation libraries folder
- 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 Send Image to Server
In your Arduino IDE, paste the following code. This code creates a web server that allows you to take and display photos taken with an ESP32-CAM. You may want to rotate the picture depending on the orientation of your ESP32-CAM, therefore we provided that option as well.
#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 ); }
How the code works
#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 );
}
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:
You Can Also Check on: