📸 ESP32-CAM : une caméra IP DIY sur ipx800 V4

Bonjour Ă  tous,

Depuis longtemps je cherchais à domotiser ma boîte aux lettres. Les systèmes couramment utilisés, basés sur des capteurs d’ouverture, ne me satisfaisaient pas entièrement.
Impossible en effet de savoir si la porte de la boîte aux lettres avait été ouverte côté rue pour de la pub ou un colis ou encore une simple lettre, sans oublier les différents problèmes avec les colis
Il m’est arrivé de recevoir un SMS m’annonçant que mon colis était livré en mon absence. C’était une livraison importante, mais à mon retour au domicile, il n’y avait pas de colis dans la boîte aux lettres et il était trop tard pour contacter le transporteur.

Bref, plusieurs jours de perdus. De plus, je ne trouvais pas très WAF (Wife Acceptance Factor ) d’imposer un mode opératoire à quiconque ramasserait le courrier, afin d’acquitter les différents systèmes dans le bon ordre.

J’ai donc opté pour une solution vidéo DIY à 10.00€ :smiley: C’est le module ESP32-CAM OV2640 qui compose le coeur du système. Cette caméra Wifi, 2MPx, facilement programmable sera alimentée en 5V. Le montage tient sur une mini Breadboard car j’ai ajouté un interrupteur de marche/arrêt et des borniers pour les connexions extérieures (alim).
image image

La programmation et la mise en place ont demandé environ 2 heures de travail.

image image (boitier prototype dans de la goulotte Ă©lectrique)
La caméra est fixée à 45° côté jardin. De cette manière, elle ne se voit pas de la rue.
image

image image
résultat sur IPX ( flash éteint / flash allumé)

En partant du tutoriel de Random Nerd Tutorials (ce tutoriel vous guidera pour la mise en place des libraries nécessaires à la compilation), j’ai modifié le code afin de pouvoir piloter le flash à distance. Il s’éteint automatiquement après quelques secondes.
Dans le code C++ ci-dessous, ajustez vos paramètres wifi.
Compilez puis téléversez le code (pensez à relier IO0 à Gnd pour entrer en mode programmation)

/*********
  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 = "MonSSID";
const char* password = "123456789ABC";

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

boolean takeNewPhoto = false;
boolean isFlashOn = false;
boolean lightingLed=false;
unsigned long tempsInit=0;

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

// FLASH LED GPIO 4
#define LED_BUILTIN 4


// 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=0.6">

</head>
<body>
  <div id="container">
      <button onclick="capturePhoto()">CAPTURE PHOTO</button>
      <button onclick="location.reload();">REFRESH</button>
      <button onclick="piloteFlash();">FLASH</button>
    </p>
  </div>
  <div><img src="saved-photo" id="photo" width="70%"></div>
</body>
<script>
  var deg = 0;
  var autoR=0;
  var x;

  function myRefresh(){
    location.reload();
  }

  function piloteFlash(){
    var xhr = new XMLHttpRequest();
    xhr.open('GET', "/flash", true);
    xhr.send();
    val4=digitalRead(LED_BUILTIN);
    if (isFlashOn){digitalWrite(LED_BUILTIN, HIGH);}
    else {digitalWrite(LED_BUILTIN, HIGH);}
  }
  function capturePhoto() {
   // digitalWrite(LED_BUILTIN, HIGH);
    var xhr = new XMLHttpRequest();
    xhr.open('GET', "/capture", true);
    xhr.send();
    setTimeout(myRefresh,2000);
  }
  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);
  pinMode (LED_BUILTIN, OUTPUT); 

  // Connect to Wi-Fi
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi...");
    digitalWrite(LED_BUILTIN,HIGH);
    delay(500);
    digitalWrite(LED_BUILTIN,LOW);
  }
  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);
  });

    server.on("/flash", HTTP_GET, [](AsyncWebServerRequest * request) {
    lightingLed = true;
    request->send_P(200, "text/plain", "Lighting flash");
  });

  // Start server
  server.begin();

}

void loop() {
  if (millis()-tempsInit>=10000 && isFlashOn){
  digitalWrite(LED_BUILTIN,LOW);
  isFlashOn=!isFlashOn;}  
  
  if (takeNewPhoto) {
    capturePhotoSaveSpiffs();
    takeNewPhoto = false;
  }
    if (lightingLed) {
    piloteFlash();
    lightingLed = 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 );
}
void piloteFlash( void ) {
  if(!isFlashOn){digitalWrite(LED_BUILTIN, HIGH);}
  else{digitalWrite(LED_BUILTIN,LOW);}
  isFlashOn=!isFlashOn;
  tempsInit=millis();
  }

Sur ipx800 v4, il suffit de créer un widget type HTML et coller ce code

<style>
    #wrap { width: 1920px; height: 1080px; padding: 0; overflow: hidden; }
    #frame { width: 1920px; height: 1080px; border: 1px solid black; }
    #frame {
       -ms-zoom: 0.25;
       -moz-transform: scale(0.25);
       -moz-transform-origin: 0 0;
       -o-transform: scale(0.25);
       -o-transform-origin: 0 0;
       -webkit-transform: scale(0.25);
       -webkit-transform-origin: 0 0;
       }
</style>
<body bgcolor=0 scroll="no" style="overflow: hidden">
    <div id="wrap">
       <iframe allowtransparency="true style="background-color: 0;" id="frame" src="http://192.168.0.77/"></iframe>
    </div>
</body>

il suffira d’ajuster le zoom (0.25 dans l’exemple) et l’adresse ip de la caméra (192.168.0.77 dans l’exemple)

cdt

11 J'aime

Bonjour fgtoul,

super montage, qui aurait sa place dans les wikis :wink:

Trois questions :

  • ne crains-tu pas les pb d’humiditĂ© et d’oxydation de contacts avec un BreadBoard en extĂ©rieur (sauf si c’est seulement pour le proto)?
  • est-il possible, avec un bout de logiciel, que la camĂ©ra dĂ©tecte un changement dans la BĂ L et remonte une alerte?
  • enfin (on n’est plus dans la BĂ L) pourrais-tu faire une variante qui prend une photo toutes les secondes pour ceux qui voudraient utiliser la camĂ©ra en surveillance?

Encore bravo

Bonne journée

1 J'aime

merci :blush:

pour les pb d’humidité : le boitier final sera étanche et le breadboard n’est effectivement là que pour le proto. Comme on peut le voir, le schéma est simple, , il sera aisé de supprimer la platine d’essais.
Je ferai plus tard (je ne sais pas quand) une variante avec prise de photo sur activation d’un PIR ou contact magnétique. Mais il existe déjà tellement de tuto sur internet :thinking:

Une variante également possible : prise de vue sur activation d’un contact, envoi photo sur un serveur FTP, envoi notification, mise en veille profonde jusqu’à prise de vue suivante.

Bref, les possibilités sont illimitées.

4 J'aime

Super idée !!! Bravo et merci pour le partage !

1 J'aime

Geeeek! Excellent :wink:

1 J'aime

bonjour et merci de l’info
une simple question : comment programmer la carte ?

Bonjour
Tout est dit dans le tuto original dont je fournis le lien.
Cdt

1 J'aime

merci de la réponse. c’est bien ce que je pensais, il faut 1FTDI programmer (que je n’ai :innocent: pas)

vous pouvez aussi utiliser un Arduino UNO si vous en avez un, il remplacera le convertisseur FTDI

EDIT : voici un lien pour la méthode :wink:
Program ESP32-CAM using Arduino UNO - Electronics Projects (easyelectronicsproject.com)

Super, merci de l’info.Je te tiens au courant du résultat

Bonjour suite du projet: j’ai une erreur de compilation . j’utilise la carte AI Thinker ESP32-cam
est-ce le bon type de carte ?
merci de ton aide

bonjour,

oui, le GPIO dans le code est celui de cette carte.
quelle erreur de compil ?

justement pas de détail juste "Erreur de compilation pour la carte AI Thinker ESP32-CAM

j 'ai recopié le code d’origine et l’erreur est identique

tu utilises arduino IDE pour téléverser ?

vérifie la mise en place des librairies

oui IDE Arduino, sans pb pour les autres projets en cours et les bibliotheques sont installées.
il doit y avoir qq chose dans le code qui ne lui plait pas. je continuerai mes recherches demain
. merci de ton aide

Arduino ide indique normalement le detail de toutes les erreurs de compilation rencontrees.

Tu as essayé avec le code du tuto que j’ai mis en lien ?

bonjour. je continue mes recherches: j’ai changé de pc, idem; je suis passé par 1 arduino , au lieu du FTDI, idem.Les erreurs de compil: plusieurs bilbliotheques Wifi.h, mais c’est pa le pb .Reste EspasyncWebServer = Fatal Error; No Such File or directory.
il semble donc que cette librairie est défectueuse ou mal installée.
je continue Ă  chercher

les libraries ne sont pas correctement installées

Bonjour
en fait il manque plusieurs librairies : ESPAsyncTCP & ASyncTCP, entre autres
Est-il possible de trouver « ESPAsyncWebServer Â» complet ? sinon il faut mettre les mains dans le cambouis…