ESP 32 Cam

            http://drsol.com/~deid/pi/esp32cam/index.html

Pictures to my web server

Examples

http://drsol.com/~deid/espcam/setCaptureInterval.php


This presentation is in three parts:

Hardware

Front side of the ESP32 Cam showing the ESP32 chip, WiFi antenna and reset switch(hidden behind the red wire).  The yellow and white wires connect IO0 to gnd when connected.  Thus allowing programs to be uploaded to the chip.


The back side of the ESP32 Cam showing the camera, ribbon cable and the camera connector.  The "flash" LED can also be seen (the yellow square in the lower left).

The silver metal bit under the camera is a micro SD Card socket.  Not being used at the moment.

An image of me taking the the above picture of the ESP32 Cam captured by the ESP32 Cam.




FTDI (Future technology Devices International) programmer and cables.  https://en.wikipedia.org/wiki/FTDI

This device goes from USB to RS232.  Many other ESP development boards have a built-in USB interface that provides both power and the USB to RS232 conversion for uploads.

The ESP32 Cam does not have this so we need to supply it with this FTDI device.


Where, from - Ali Express. 
And I doubt that it is an appropriately licenced FTDI device.  The ESP32 and the Raspberry Pi do not seem to care.



The ESP32 Cam also came from Ali Express.  $5.98 for the ESP32 Cam and the OV2640 Camera.

The camera is fixed focus and supports multiple resolutions from 320 x 240 to 1600 x 1200.  I am using 800 x 600 to create a reasonable picture without overwhelming my server.  Each .jpg image is between 20 and 60K. 


Wiring with thanks to https://randomnerdtutorials.com/esp32-cam-take-photo-display-web-server/
Note the IO0 to Ground gray connection.  This is used only for uploading.  The following sequence works for me:


Deployed



Arduino IDE

ESP32 Cam Code

ESP C Code to take a picture and upload it to my server.  Block diagram/spreadsheet

ESP C Code to take a picture and upload it to my server.

 Initial code thanks to https://robotzero.one/time-lapse-esp32-cameras

// Modified from https://robotzero.one/time-lapse-esp32-cameras
// By Deid Reimer 2020-10- ...

// For camera 1

#include <HTTPClient.h>
#include "esp_http_client.h"
#include "esp_camera.h"
#include <WiFi.h>
#include "Arduino.h"

const char* ssid = "lalala";
const char* password = "xxx";

// The following 3 values are updated by a request to giveTime.php
int capture_interval = 60000;   // Milliseconds between photo captures.
int delayNum = 60000;           // Default update delay
String takeNow = "N";           // Take a picture now when "T" which comes from

// Set up the URLs so that the mac address can be sent so we can differentiate between cameras.
String macAddr; 

// Script that receives images and mac address
char  post_url[80] = "http://deid:xxx@drsol.com/~deid/espcam/getPicture.php?11:11:11:11:11:11";
// Get capture time, delay time and take now and flash flags from server.

char  time_url[80] = "http://deid:xxx@drsol.com/~deid/espcam/giveTime.php?11:11:11:11:11:11";   
bool internet_connected = false;

// Timing for capturing a picture
unsigned long current_millis;
unsigned long last_capture_millis = 0;

// Flag for flash
bool doFlash = false;

// 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

//  --- Functions ---

// Function to connect to wifi
bool init_wifi() {
  int connAttempts = 0;
  Serial.println("\r\nConnecting to: " + String(ssid));
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED ) {
    delay(500);
    Serial.print(".");
    if (connAttempts > 10) return false;
    connAttempts++;
  }
  return true;
}

// Function to handle http events
esp_err_t _http_event_handler(esp_http_client_event_t *evt) {
  switch (evt->event_id) {
    case HTTP_EVENT_ERROR:
      Serial.println("HTTP_EVENT_ERROR");
      break;
    case HTTP_EVENT_ON_CONNECTED:
      Serial.println("HTTP_EVENT_ON_CONNECTED");
      break;
    case HTTP_EVENT_HEADER_SENT:
      Serial.println("HTTP_EVENT_HEADER_SENT");
      break;
    case HTTP_EVENT_ON_HEADER:
      Serial.println();
      Serial.printf("HTTP_EVENT_ON_HEADER, key=%s, value=%s", evt->header_key, evt->header_value);
      break;
    case HTTP_EVENT_ON_DATA:
      Serial.println();
      Serial.printf("HTTP_EVENT_ON_DATA, len=%d", evt->data_len);
      if (!esp_http_client_is_chunked_response(evt->client)) {
        // Write out data
        // printf("%.*s", evt->data_len, (char*)evt->data);
      }
      break;
    case HTTP_EVENT_ON_FINISH:
      Serial.println("");
      Serial.println("HTTP_EVENT_ON_FINISH");
      break;
    case HTTP_EVENT_DISCONNECTED:
      Serial.println("HTTP_EVENT_DISCONNECTED");
      break;
  }
  return ESP_OK;
}

// Function to take and send a photo
static esp_err_t take_send_photo() {
  Serial.println("Taking picture...");

  // Set up frame buffer
  camera_fb_t * fb = NULL;
  esp_err_t res = ESP_OK;

  // get a picture and punt if it fails
  if (doFlash) {
    digitalWrite(4, HIGH);
  }
  delay(10);
  fb = esp_camera_fb_get();
  digitalWrite(4, LOW);
  if (!fb) {
    Serial.println("Camera capture failed");
    return ESP_FAIL;
  }

  // Set up to send picture to the server
  // Get a handle
 
  // Decent docs here:
  // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/protocols/esp_http_client.html
  esp_http_client_handle_t http_client;

  //Set up an array/structure for the http config
  esp_http_client_config_t config_client = {0};
 
  // Set the URL
  config_client.url = post_url;                     
  // Set the event handler function
  config_client.event_handler = _http_event_handler;
  // Set the method e.g. POST, not GET
  config_client.method = HTTP_METHOD_POST; 
  // Initialize the http client
  http_client = esp_http_client_init(&config_client);

  // POST data comes from fb (the picture)
  esp_http_client_set_post_field(http_client, (const char *)fb->buf, fb->len);
 
// and it is an image. 
  esp_http_client_set_header(http_client, "Content-Type", "image/jpg"); 

  // and finally do the request.
  esp_err_t err = esp_http_client_perform(http_client);

  // If all is well print the result.
  if (err == ESP_OK) {
    Serial.print("esp_http_client_get_status_code: ");
    Serial.println(esp_http_client_get_status_code(http_client));
  }

  // Clean up
  esp_http_client_cleanup(http_client);
  esp_camera_fb_return(fb);
}

//Function to get capture time, delay time, takeNow and doFlash.
void getRepeat() {
  // I'm using a different http library as I know it and this
  // is my code now.
  HTTPClient http;

  // Request data
  http.begin(time_url);
  int stat = http.GET();

  // If the return status is OK get the returned data string.
  if (stat > 0) {
    String text = http.getString();
    Serial.println(text);
    // Parse the string into capture time, delay time and take immediate picture and flash flags
    int comma1Pos = text.indexOf(',');
    String capture = text.substring(0, comma1Pos);
    Serial.println(capture);
    int comma2Pos = text.indexOf(',', comma1Pos + 1);
    String delayString = text.substring(comma1Pos + 1, comma2Pos);
    takeNow = text.substring(comma2Pos + 1, comma2Pos + 2);
    // Set the flash flag
    if (text.substring(comma2Pos + 3, comma2Pos + 4) == "F") {
      doFlash = true;
    } else {
      doFlash = false;
    }
    delayNum = delayString.toInt() * 1000;
    capture_interval = capture.toInt() * 1000;

    // If the takeNow value is T then take a picture now.
    if (takeNow.compareTo("T") == 0) {
      take_send_photo();
    }

    Serial.print("time: ");
    Serial.println(capture_interval);
    Serial.print("delay: ");
    Serial.println(delayNum);
    Serial.println(takeNow);
    // and done
    http.end();
  }
}

// ---- Setup ----
void setup() {
  Serial.begin(9600);
  pinMode(4, OUTPUT);

  // Attempt to connect to WiFi and display result
  if (init_wifi()) {
    internet_connected = true;
    Serial.println("Internet connected");
  }
  // Pick up mac address as it is unique so we know which camera this is.
  macAddr = WiFi.macAddress();

  // Append the mac address to the urls
  macAddr.toCharArray(post_url+57, 18);
  macAddr.toCharArray(time_url+55, 18);
  Serial.println(post_url);
  Serial.println(time_url);

  // Set up 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;

  // If pseudo ram is available use larger frame buffer and better jpeg quality
  // The following are various choices.
  // jpeg quality is from 0 to 63 with 0 being best and probably too much for the esp.
  /*
    FRAMESIZE_UXGA (1600 x 1200)
    FRAMESIZE_QVGA (320 x 240)
    FRAMESIZE_CIF (352 x 288)
    FRAMESIZE_VGA (640 x 480)
    FRAMESIZE_SVGA (800 x 600)
    FRAMESIZE_XGA (1024 x 768)
    FRAMESIZE_SXGA (1280 x 1024)
  */


  /* This if has been commented out to force svga
     so as to not have a stupid amount of space used for no good reason
     A future enhancement might be to have this switched by data from giveTime.php

      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;

  // }

  // Initilize the camera with the configuration defined above.
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    return;
  }
  // Take a photo when we first start.
  take_send_photo();
}

// ---- Loop ----

void loop() {
  // Check if it is time to take another picture - and if so do it.
  current_millis = millis();
  Serial.println(current_millis);
  if (current_millis - last_capture_millis > capture_interval) {
    last_capture_millis = millis();
    take_send_photo();
  }
  // Get the delay time etc/ from the web.  This should be a loop, not just a delay.
  if (WiFi.status() != WL_CONNECTED) {
    ESP.restart();
  }
  getRepeat();
 
  delay(delayNum);
}

Server Code

Control and Display

Server PHP Scripts

setCaptureInterval.php  - Get The Data - Block Diagram/flowchart

setCaptureInterval.php  - get the data  - Code

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<?php
// http://drsol.com/~deid/esp/setCaptureInterval.php
// Display images and change the parameters of image capture

// Has a camera been choosen?  If not display camera choice,
// otherwise display the camera images and form
if (! isset ($_GET['camera'])) {
  ?>
<!-- Display the camera choice form -->
<html>
<head>
<meta name=viewport content="width=device-width, initial-scale=1.0">
</head>
<body>
<h1>ESP32 Cam</h1>
<h2>Choose Camera</h2>
  <form method="get" action="setCaptureInterval.php">
 Camera 1:  <input type="radio" name="camera" value=1><br>
 Camera 2:  <input type="radio" name="camera" value=2><br>
 <input type="submit">
 </form>
  </body></html>
 <?php
  exit();

// Display the Camera images and form
} else {
  $camera = $_GET['camera'] ;
}
// Define file locations and get a reverse sorted list of all the picture files
$files =  glob ("picts".$camera . "/*.jpg");
rsort($files);
$fileName = "/home/deid/esp32camCaptureInfo.txt";

// Read the file that contains the time and flags so we can populate the html with existing values.
$captureInfo = file($fileName);
list($time, $delay, $take, $flash) = explode(",", $captureInfo[$camera-1]);

// Should the flash box be pre-checked?
if (trim($flash) == 'F') {
  $checked = 'checked';
} else {
  $checked = '';
}
?>

<!---
Display a form for gathering the data. Prefilled with the current status read from the file above.
--->
<html>
  <head>
    <meta name=viewport content="width=device-width, initial-scale=1.0">
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <meta http-equiv="refresh" content="60; URL=http://drsol.com/~deid/esp/setCaptureInterval.php?camera=<?php echo $camera ?>" />
    <title>Set Capture Intervals etc.</title>
  </head>
  <body>
    <h1>ESP32&nbsp; Cam <?php echo $camera ?></h1>
    <form method="post" action="setCaptureIntervalFile.php?camera=<?php echo $camera; ?>">
      Capture Interval in seconds: <input type="text" size="5" name="time" value=<?php echo $time; ?>> <br>
      Check for request delay: <input type="text" size="5" name="delay" value=<?php echo $delay; ?>><br>
      Take a picture now: <input type="checkbox" name="take" value="T"><br>
      Flash on: <input type="checkbox" name="flash" value="F" <?php echo $checked; ?>><br>
      <input type="submit" value="Update">
    </form><br>

<!-- Change Camera Link -->
    <a href="setCaptureInterval.php"><b>Change Camera</b></a>
    <h3>Recent pictures:</h3><p>

<table width="80%"><tr>
<?php
// Display the recent pictures and links to older pictures.
// Put the images and captions in a table. Both linked to the larger image.
for ($j=0; $j<4; $j=$j+3) {
  // Add the pictures
  for($i=0; $i<3; $i++) {
    echo '<td><a href="' . $files[$i+$j] . '">';
    echo '<img width="100%"  src="' . $files[$i+$j] . '"></a></td> '; 
  }
  echo "</tr><tr>";
  // Add the captions
  for($i=0; $i<3; $i++) {
    echo '<td style="text-align: center";><a href="' . $files[$i+$j] . '">';
    echo date('Y-m-d H:i', filemtime($files[$i+$j])) . "</td>";
  }
  echo "</tr><tr>";
}
echo "</tr></table>";

// Display all the rest of the links to the pictures
echo "<p><b>The rest of the pictures</b><br>";
for ($i=6; $i<count($files); $i++){
  echo '<a href="' . $files[$i] . '">'. date('Y-m-d H:i', filemtime($files[$i])) . '</a><br>';
  echo "\n";
}
?>
  <br>
Done:
    </body>
</html>

setCaptureIntervalFile.php - put the data into a file - Block diagram/flowchart

setCaptureIntervalFile.php - put the data into a file - Code

<?php
// Put the form data into the data file. 
// http://drsol.com/~deid/esp/setCaptureIntervalFile.php

// Set data file name
$fileName = "/home/deid/esp32camCaptureInfo.txt";

// Get the camera number
$camera = $_GET['camera'];

echo "<html><head>";
// Send us back to the input form page after a few seconds
echo "<meta http-equiv=\"refresh\" content=\"4; URL=http://drsol.com/~deid/esp/setCaptureInterval.php?camera=$camera\" />";
echo "</head><body>";

// Read the the data file into an array
$Ptr = fopen($fileName, 'r+');
if (!$Ptr) {
  exit("Could not open data file");
}
// Lock and read the file into an array;
flock($Ptr, LOCK_EX);
while (!feof($Ptr)) {
  $line = fgets($Ptr);
  if ($line) {
    $captureInfo[]  = $line;
  }
}

// Get the data from the input form
// Number of seconds between pictures
$time = $_POST['time'];

// Get take picture now and flash on. They will be respectively  T or F if checked and nothing if not.
//  So set the "nothing" to N for both take and flash
if (isset($_POST['take'])) {
  $take = $_POST['take'];
} else {
  $take = 'N';
}
if (isset($_POST['flash'])) {
  $flash = $_POST['flash'];
} else {
  $flash = 'N';
}
$delay = $_POST['delay'];

// Update the correct array entry;
$captureInfo[$camera-1] = "$time,$delay,$take,$flash";

// Empty the file
ftruncate($Ptr, 0);
rewind($Ptr);
// Write the data to the file.
echo "<h2>Thanks</h2>Wrote the data:<br>";
foreach ($captureInfo as $i) {
  if (fwrite($Ptr, trim($i) . "\n")) {
    echo "$i<br>";
  } else {
    echo "Oops.  Did not write. <br>$i<br>";
  }
}

// Unlock and close the file and we are done
fflush($Ptr);
flock($Ptr, LOCK_UN);
fclose($Ptr);
?>
</body></html>

giveTime.php called by the ESP32 Cam to get times etc.  Block diagram/flowchart

giveTime.php called by the ESP32 Cam to get times etc. - Code

<?php
// giveTime.php
// This script is requested by the ESP32 Cameras
// It give the capture and delay times and "immediate" and flash request to the ESP 32 Cam when requested.

require("whichcamera.php");
// Set minimum times. Seconds
$min = 60;

// Read the data file into an array.
$fileName = "/home/deid/esp32camCaptureInfo.txt";
$captureInfo = file($fileName);

// Get the camera number
$mac = $_SERVER['QUERY_STRING'];
$camera = trim(camera($mac));

// If we have a new camera;
if (count($captureInfo) < $camera) {
  $time = 3600;
  $delay = 300;
  $take = 'N';
  $flash = 'N';
  $captureInfo[$camera] = "$time,$delay,$take,$flash";
}

// Split the entry for this camera into the four variables.
$line = $captureInfo[$camera-1];
list($time, $delay, $take, $flash) = explode(",", $line);

// Check the numbers for validity.
if ($time < $min) {
  $time = $min;
}
if ($delay < $min) {
  $delay = $min;
}

// write the data out thus sending a reply to the ESP32 Cam
// and close the file for read.
echo "$time,$delay,$take,$flash";

// Open the file for write and lock.
// and clear the take "immediate" picture now flag.
$Ptr = fopen($fileName, 'w') or die("no open");
flock($Ptr, LOCK_EX);

$take = 'N';
$line = "$time,$delay,$take,$flash";
$captureInfo[$camera-1] = $line;

foreach($captureInfo as $i) {
  fwrite($Ptr, $i);
}
fflush($Ptr);
flock($Ptr, LOCK_UN);
fclose($Ptr);

getPicture.php called by the ESP32 Cam to send a picture block diagram/flowchart

getPicture.php called by the ESP32 Cam to send a picture - Code

<?php
// Get a picture from the ESP32 Cam and save it.
// load the camera function
require('whichcamera.php');

// Read the file from the POST buffer.
$received = file_get_contents('php://input');

$mac = $_SERVER['QUERY_STRING'];
$camera = camera($mac);

// Create a name using the epoch to make it a unique name.
$fileToWrite = "/home/deid/public_html/espcam/picts$camera/ddpload-".time().".jpg";

// Write the file.
// Note one has to mess with permissions to allow the web server to do this.
file_put_contents($fileToWrite, $received);

Function camera();

<?php
function camera ($mac) {
  // Open the data file and lock it
  $FH = fopen("/home/deid/cameras.dat", 'a+');
  flock ($FH, LOCK_EX);
  // Read the file into an asociative array
  while (!feof($FH)) {
    $line = fgets($FH);
    if ($line == "") {
      break;
    }
    list($k, $v) = explode (',', $line);
    $acam[$k] = $v;
  }
  // Is our mac in there?  Return the value if yes.
  if (isset($acam[$mac])) {
    flock ($FH, LOCK_UN);
    fclose($FH);
    return trim($acam[$mac]);
  } else {
    // Not there - add a new camera
    $v  = count($acam) + 1;
    fwrite($FH, "$mac,$v\n");
    flock ($FH, LOCK_UN);
    fclose($FH);
    return trim($v);
  }
}
?>

Data File camera.dat - used to determine which camera this is

1,1
2,2
AC:67:B2:2E:14:D0,3

Data File esp32camCaptureInfo.txt - contains time etc. settings for the cameras

3600,61,N,F
1800,62,N,F
1800,60,N,N

this space intentionally left blank
2021-03-22