├── README.md ├── code └── esp32-cam │ └── esp32-cam.ino └── wiring ├── ESP32-CAM.fzz └── ESP32-CAM_bb.jpg /README.md: -------------------------------------------------------------------------------- 1 | # esp32-cam 2 | Customisable camera control script for ESP32-CAM module - timelapse, flash, trigger control 3 | 4 | Video tutorial: 5 | https://www.youtube.com/watch?v=FmlxC0goKew 6 | 7 | Further resources: 8 | https://www.patreon.com/posts/esp32-camera-50512098 9 | -------------------------------------------------------------------------------- /code/esp32-cam/esp32-cam.ino: -------------------------------------------------------------------------------- 1 | /********* 2 | ESP32-CAM - a cheap ESP-powered portable camera with programmable behaviour. Copyright (c) Playful Technology 2021 3 | 4 | - "TRIGGER" mode captures photo capture based on GPIO input - (which could be a physical button, a sensor such as PIR signal, or trigger from another Arduino) 5 | - "TIMELAPSE" mode automatically takes pictures every x seconds (configurable) 6 | - When not taking photo, deep sleep mode activated, which conserves battery life. Can run off single 3.7V Li-Po for a day. 7 | - Optional front-facing LED can be turned on to provide a flash 8 | - Photos stored to local SD card 9 | - Photos incrementally numbered 10 | - Retrieve timestamp from NTP internet time server (requires Wi-Fi) 11 | - eMail photo as attachment (requires Wi-Fi) 12 | 13 | For accompanying video tutorial, see: 14 | https://youtu.be/FmlxC0goKew 15 | 16 | The following sources provided helpful references (though none of them provided the functionality I wanted!): 17 | https://RandomNerdTutorials.com/esp32-cam-pir-motion-detector-photo-capture/ 18 | https://github.com/easytarget/esp32-cam-webserver 19 | https://github.com/bnbe-club/video-recording-with-esp32-cam-diy-11/blob/master/diy-e11/diy-e11.ino 20 | https://sites.google.com/site/pcusbprojects/5-custom-projects/sc-esp32-cam-based-secure-flash-led-camera 21 | 22 | IMPORTANT!!! 23 | - Select Board "AI Thinker ESP32-CAM" 24 | - GPIO 0 must be connected to GND to upload a sketch 25 | - After connecting GPIO 0 to GND, press the ESP32-CAM on-board RESET button to put your board in flashing mode 26 | 27 | Hardware: 28 | ESP32-CAM: https://www.banggood.com/custlink/33KEli3R0U 29 | AM312 PIR sensor: https://www.ebay.co.uk/itm/202625567146 30 | ESP32-CAM programmer board: https://www.aliexpress.com/item/1005001872947921.html 31 | FTDI programmer: https://www.banggood.com/custlink/3KKYoIGR6f 32 | 33 | *********/ 34 | 35 | // INCLUDES 36 | #include "Arduino.h" // General functionality 37 | #include "esp_camera.h" // Camera 38 | #include // SD Card 39 | #include "FS.h" // File System 40 | #include "soc/soc.h" // System settings (e.g. brownout) 41 | #include "soc/rtc_cntl_reg.h" 42 | #include "driver/rtc_io.h" 43 | #include // EEPROM flash memory 44 | #include // WiFi 45 | #include "time.h" // Time functions 46 | // "ESP Mail Client" by Mobizt, tested with v1.6.4 47 | #include "ESP_Mail_Client.h" // e-Mail 48 | 49 | // DEFINES 50 | //#define USE_INCREMENTAL_FILE_NUMBERING //Uses EEPROM to store latest file stored 51 | #define USE_TIMESTAMP // Uses Wi-Fi to retrieve current time value 52 | #define SEND_EMAIL // Uses Wi-Fi to email photo attachment 53 | //#define TRIGGER_MODE // Photo capture triggered by GPIO pin rising/falling 54 | #define TIMED_MODE // Photo capture automated according to regular delay 55 | 56 | // Wi-Fi settings 57 | #define WIFI_SSID "YOUR WIFI SSID" 58 | #define WIFI_PASSWORD "YOUR WIFI PASSWORD" 59 | #define SMTP_HOST "smtp.office365.com" 60 | #define SMTP_PORT 25 61 | #define AUTHOR_EMAIL "xxxx@outlook.com" 62 | #define AUTHOR_PASSWORD "xxxxx" 63 | 64 | // CONSTANTS 65 | // GPIO Pin 33 is small red LED near to RESET button on the back of the board 66 | const byte ledPin = GPIO_NUM_33; 67 | // GPIO Pin 4 is bright white front-facing LED 68 | const byte flashPin = GPIO_NUM_4; 69 | // When using TRIGGER_MODE, this pin will be used to initiate photo capture 70 | const byte triggerPin = GPIO_NUM_13; 71 | // Flash strength (0=Off, 255=Max Brightness) 72 | // Setting a low flash value can provide a useful visual indicator of when a photo is being taken 73 | const byte flashPower = 1; 74 | #ifdef TIMED_MODE 75 | const int timeLapseInterval = 30; // seconds between successive shots in TIMELAPSE mode 76 | #endif 77 | const int startupDelayMillis = 3000; // time to wait after initialising camera before taking photo 78 | 79 | // GLOBALS 80 | // Keep track of number of pictures taken for incremental file naming 81 | int pictureNumber = 0; 82 | // Full path of filename of the last photo saved 83 | String path; 84 | #ifdef SEND_EMAIL 85 | // SMTP session used for eMail sending 86 | SMTPSession smtp; 87 | // Function fired on email success/failure 88 | void smtpCallback(SMTP_Status status); 89 | 90 | // Callback function after eMail sending 91 | void smtpCallback(SMTP_Status status) { 92 | // Print the current status 93 | Serial.println(status.info()); 94 | // Show details of successful delivery 95 | if (status.success()) { 96 | Serial.println("----------------"); 97 | Serial.printf("Message sent success: %d\n", status.completedCount()); 98 | Serial.printf("Message sent failed: %d\n", status.failedCount()); 99 | Serial.println("----------------\n"); 100 | struct tm dt; 101 | for (size_t i=0; iset_gain_ctrl(s, 1); // Auto-Gain Control 0 = disable , 1 = enable 208 | s->set_agc_gain(s, 0); // Manual Gain 0 to 30 209 | s->set_gainceiling(s, (gainceiling_t)0); // 0 to 6 210 | // Exposure 211 | s->set_exposure_ctrl(s, 1); // Auto-Exposure Control 0 = disable , 1 = enable 212 | s->set_aec_value(s, 300); // Manual Exposure 0 to 1200 213 | // Exposure Correction 214 | s->set_aec2(s, 0); // Automatic Exposure Correction 0 = disable , 1 = enable 215 | s->set_ae_level(s, 0); // Manual Exposure Correction -2 to 2 216 | // White Balance 217 | s->set_awb_gain(s, 1); // Auto White Balance 0 = disable , 1 = enable 218 | s->set_wb_mode(s, 0); // White Balance Mode 0 to 4 - if awb_gain enabled (0 - Auto, 1 - Sunny, 2 - Cloudy, 3 - Office, 4 - Home) 219 | s->set_whitebal(s, 1); // White Balance 0 = disable , 1 = enable 220 | s->set_bpc(s, 0); // Black Pixel Correction 0 = disable , 1 = enable 221 | s->set_wpc(s, 1); // White Pixel Correction 0 = disable , 1 = enable 222 | s->set_brightness(s, 0); // Brightness -2 to 2 223 | s->set_contrast(s, 0); // Contrast -2 to 2 224 | s->set_saturation(s, 0); // Saturation -2 to 2 225 | s->set_special_effect(s, 0); // (0 - No Effect, 1 - Negative, 2 - Grayscale, 3 - Red Tint, 4 - Green Tint, 5 - Blue Tint, 6 - Sepia) 226 | // Additional settings 227 | s->set_lenc(s, 1); // Lens correction 0 = disable , 1 = enable 228 | s->set_hmirror(s, 0); // Horizontal flip image 0 = disable , 1 = enable 229 | s->set_vflip(s, 0); // Vertical flip image 0 = disable , 1 = enable 230 | s->set_colorbar(s, 0); // Colour Testbar 0 = disable , 1 = enable 231 | s->set_raw_gma(s, 1); // 0 = disable , 1 = enable 232 | s->set_dcw(s, 1); // 0 = disable , 1 = enable 233 | 234 | // We want to take the picture as soon as possible after the sensor has been triggered, so we'll do that first, before 235 | // setting up the SD card, Wifi etc. 236 | // Initialise a framebuffer 237 | camera_fb_t *fb = NULL; 238 | // But... we still need to give the camera a few seconds to adjust the auto-exposure before taking the picture 239 | // Otherwise you get a green-tinged image as per https://github.com/espressif/esp32-camera/issues/55 240 | // Two seconds should be enough 241 | delay(startupDelayMillis); 242 | // Take picture 243 | fb = esp_camera_fb_get(); 244 | // Check it was captured ok 245 | if(!fb) { 246 | Serial.println("Camera capture failed"); 247 | sleep(); 248 | } 249 | 250 | // Turn flash off after taking picture 251 | ledcWrite(7, 0); 252 | 253 | // Build up the string of the filename we'll use to save the file 254 | path = "/pic"; 255 | 256 | // Following section creates filename based on increment value saved in EEPROM 257 | #ifdef USE_INCREMENTAL_FILE_NUMBERING 258 | // We only need 2 bytes of EEPROM to hold a single int value, but according to 259 | // https://arduino-esp8266.readthedocs.io/en/latest/libraries.html#eeprom 260 | // Minimum reserved size is 4 bytes, so we'll use that 261 | EEPROM.begin(4); 262 | // Read the value from the EEPROM cache 263 | EEPROM.get(0, pictureNumber); 264 | pictureNumber += 1; 265 | // Path where new picture will be saved in SD Card 266 | path += String(pictureNumber) + "_"; 267 | // Update the EEPROM cache 268 | EEPROM.put(0, pictureNumber); 269 | // And then actually write the modified cache values back to EEPROM 270 | EEPROM.commit(); 271 | #endif 272 | 273 | // Connect to Wi-Fi if required 274 | #if defined(SEND_EMAIL) || defined(USE_TIMESTAMP) 275 | WiFi.mode(WIFI_STA); 276 | WiFi.setHostname("BirdFeederCam"); 277 | int connAttempts = 0; 278 | WiFi.begin(WIFI_SSID, WIFI_PASSWORD); 279 | while (WiFi.status() != WL_CONNECTED && connAttempts < 10) { 280 | Serial.print("."); 281 | delay(500); 282 | connAttempts++; 283 | } 284 | if(WiFi.isConnected()){ 285 | Serial.println(""); 286 | Serial.println("WiFi connected."); 287 | Serial.println("IP address: "); 288 | Serial.println(WiFi.localIP()); 289 | Serial.print(" Signal Level: "); 290 | Serial.println(WiFi.RSSI()); 291 | Serial.println(); 292 | } 293 | else { 294 | Serial.println(F("Failed to connect to Wi-Fi")); 295 | sleep(); 296 | } 297 | #endif 298 | 299 | #ifdef USE_TIMESTAMP 300 | // Following section creates filename based on timestamp 301 | const long gmtOffset_sec = 0; 302 | const int daylightOffset_sec = 0; 303 | // Synchronise time from specified NTP server - e.g. "pool.ntp.org", "time.windows.com", "time.nist.gov" 304 | // From https://github.com/espressif/arduino-esp32/blob/master/libraries/ESP32/examples/Time/SimpleTime/SimpleTime.ino 305 | configTime(gmtOffset_sec, daylightOffset_sec, "pool.ntp.org"); 306 | struct tm timeinfo; 307 | if(!getLocalTime(&timeinfo)){ 308 | Serial.println("Failed to obtain time"); 309 | sleep(); 310 | } 311 | else { 312 | Serial.print("Current time is "); 313 | Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S"); 314 | char timeStringBuff[50]; //50 chars should be enough 315 | strftime(timeStringBuff, sizeof(timeStringBuff), "%Y%m%d_%H%M%S", &timeinfo); 316 | path += (String)timeStringBuff; 317 | } 318 | #endif 319 | 320 | // Add the file extension 321 | path += ".jpg"; 322 | 323 | // Next, we need to start the SD card 324 | Serial.println("Starting SD Card"); 325 | if(!MailClient.sdBegin(14, 2, 15, 13)) { 326 | Serial.println("SD Card Mount Failed"); 327 | sleep(); 328 | } 329 | 330 | // Access the file system on the SD card 331 | fs::FS &fs = SD; 332 | // Attempt to save the image to the specified path 333 | File file = fs.open(path.c_str(), FILE_WRITE); 334 | if(!file){ 335 | Serial.printf("Failed to save to path: %s\n", path.c_str()); 336 | sleep(); 337 | } 338 | else { 339 | file.write(fb->buf, fb->len); // payload (image), payload length 340 | Serial.printf("Saved file to path: %s\n", path.c_str()); 341 | } 342 | file.close(); 343 | 344 | // Now that we've written the file to SD card, we can release the framebuffer memory of the camera 345 | esp_camera_fb_return(fb); 346 | // And breathe for a moment... 347 | delay(1000); 348 | 349 | #ifdef SEND_EMAIL 350 | // Get verbose output of emailing process 351 | smtp.debug(1); 352 | // Assign the callback function called after sending 353 | smtp.callback(smtpCallback); 354 | // Define the session config data which used to store the TCP session configuration 355 | ESP_Mail_Session session; 356 | session.server.host_name = SMTP_HOST; 357 | session.server.port = SMTP_PORT; 358 | session.login.email = AUTHOR_EMAIL; 359 | session.login.password = AUTHOR_PASSWORD; 360 | session.login.user_domain = "mydomain.net"; 361 | 362 | // Define the SMTP_Message class variable to hold the config of the eMail itself 363 | SMTP_Message message; 364 | //message.enable.chunking = true; // Enable chunked data transfer for large messages if server supported 365 | message.sender.name = "ESP32-CAM"; 366 | message.sender.email = AUTHOR_EMAIL; 367 | message.subject = "Motion Detected - ESP32-CAM"; 368 | message.addRecipient("Me", "nameofrecipient@gmail.com"); 369 | //message.addRecipient("name2", "email2"); 370 | //message.addCc("email3"); 371 | //message.addBcc("email4"); 372 | // Set the message content 373 | message.text.content = "This is simple plain text message"; 374 | message.text.transfer_encoding = Content_Transfer_Encoding::enc_base64; 375 | 376 | // Now define the attachment properties 377 | SMTP_Attachment att; 378 | att.descr.filename = "photo.jpg"; 379 | att.descr.mime = "application/octet-stream"; //binary data 380 | att.file.path = path.c_str(); 381 | att.file.storage_type = esp_mail_file_storage_type_sd; 382 | att.descr.transfer_encoding = Content_Transfer_Encoding::enc_base64; 383 | // Add attachment to the message 384 | message.addAttachment(att); 385 | 386 | // Connect to server with the session config 387 | Serial.println("Connecting to SMTP"); 388 | if(!smtp.connect(&session)) { 389 | Serial.println("Couldn't connect"); 390 | sleep(); 391 | } 392 | // Start sending Email and close the session 393 | Serial.println("Sending Mail"); 394 | if(!MailClient.sendMail(&smtp, &message)) { 395 | Serial.println("Error sending Email, " + smtp.errorReason()); 396 | sleep(); 397 | } 398 | #endif 399 | 400 | // Now that email is sent, we can turn the Wi-Fi off 401 | WiFi.disconnect(true); 402 | WiFi.mode(WIFI_OFF); 403 | 404 | // And go to bed until the next time we are triggered to take a photo 405 | sleep(); 406 | } 407 | 408 | void loop() { 409 | } 410 | -------------------------------------------------------------------------------- /wiring/ESP32-CAM.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfultechnology/esp32-cam/951f9d835b6aa7e127a8a32800cbc7dd52aac011/wiring/ESP32-CAM.fzz -------------------------------------------------------------------------------- /wiring/ESP32-CAM_bb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playfultechnology/esp32-cam/951f9d835b6aa7e127a8a32800cbc7dd52aac011/wiring/ESP32-CAM_bb.jpg --------------------------------------------------------------------------------