samedi 1 février 2020

ESP32-CAM: enregistrer des photos sur la carte microSD

Je vous propose aujourd'hui une version légèrement remaniée du web server que je vous présentais la semaine dernière: la page web générée par l'ESP32-CAM comporte maintenant un bouton qui permet d'enregistrer l'image sous la forme d'un fichier jpeg dans la carte microSD insérée dans l'ESP32-CAM. L'ESP32-CAM devient ainsi un appareil photo pouvant être déclenché à distance par WiFi.

Nous programmons l'ESP32-CAM avec l'IDE Arduino (voir ce précédent article pour plus de détails).

Pour utiliser le lecteur de cartes microSD de l'ESP32-CAM, l'inclusion des fichiers d'entête "FS.h" et "SD_MMC.h" est nécessaire.


J'ai défini deux variables globales: "carte_presente", qui prendra la valeur "1" si une carte microSD est détectée au démarrage du programme, et "numero_fichier", qui sera utilisée pour numéroter les photos. Dans ce programme, numero_fichier prend la même valeur à chaque redémarrage de la caméra: dans une future version, on pourrait conserver le dernier numéro en EEPROM pour éviter d'effacer une photo déjà présente sur la carte SD.


Dans setUp(), quelques lignes sont consacrées à l'initialisation de la carte.


Dans web_handler(), si une carte SD a été détectée, on ajoute à la page web un bouton qui permettra la prise de la photo. Sinon, on affiche plutôt un court message indiquant l'absence de carte SD.


L'enregistrement du fichier jpeg s'effectue à l'intérieur de la routine enregistrer_photo().


Utilisation

Lors du démarrage, l'adresse IP de la page web est affichée dans le moniteur série.



On copie l'adresse dans un navigateur web: une page web affiche l'image vidéo ainsi que le bouton "Photo".



Chaque clic sur le bouton "photo" génère un nouveau fichier jpeg sur la carte microSD.



Si aucune carte n'a été détectée, la page web présente quand même l'image vidéo, mais elle indique qu'aucune carte microSD n'est présente.




Sketch

Voici le sketch complet:

-
/*
Un bouton sur la page web permet d'enregistrer
une photo sur la carte microSD intégrée à l'ESP32-CAM
Plus d'infos:
https://electroniqueamateur.blogspot.com/2020/02/esp32-cam-enregistrer-des-photos-sur-la.html
*/
// inclusion des bibliothèques utile
#include "esp_camera.h"
#include <WiFi.h>
#include "esp_http_server.h"
#include "FS.h" // manipulation de fichiers
#include "SD_MMC.h" // carte SD
// paramètres de votre réseau WiFi
const char* ssid = "******";
const char* password = "******";
#define PART_BOUNDARY "123456789000000000000987654321"
static const char* _STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY;
static const char* _STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n";
static const char* _STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n";
httpd_handle_t stream_httpd = NULL;
httpd_handle_t camera_httpd = NULL;
int numero_port; // numéro du port du stream server
bool carte_presente = 0; // présence d'une carte micro SD
int numero_fichier = 10000; // numéro initial de la photo
// ********************************************************
// stream_handler: routine qui gère le streaming vidéo
static esp_err_t stream_handler(httpd_req_t *req) {
camera_fb_t * fb = NULL;
esp_err_t res = ESP_OK;
size_t _jpg_buf_len = 0;
uint8_t * _jpg_buf = NULL;
char * part_buf[64];
static int64_t last_frame = 0;
if (!last_frame) {
last_frame = esp_timer_get_time();
}
res = httpd_resp_set_type(req, _STREAM_CONTENT_TYPE);
if (res != ESP_OK) {
return res;
}
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
while (true) {
fb = esp_camera_fb_get();
if (!fb) {
Serial.println("Echec de la capture de camera");
res = ESP_FAIL;
} else {
if (fb->width > 400) {
if (fb->format != PIXFORMAT_JPEG) {
bool jpeg_converted = frame2jpg(fb, 80, &_jpg_buf, &_jpg_buf_len);
esp_camera_fb_return(fb);
fb = NULL;
if (!jpeg_converted) {
Serial.println("Echec de la compression JPEG");
res = ESP_FAIL;
}
} else {
_jpg_buf_len = fb->len;
_jpg_buf = fb->buf;
}
}
}
if (res == ESP_OK) {
size_t hlen = snprintf((char *)part_buf, 64, _STREAM_PART, _jpg_buf_len);
res = httpd_resp_send_chunk(req, (const char *)part_buf, hlen);
}
if (res == ESP_OK) {
res = httpd_resp_send_chunk(req, (const char *)_jpg_buf, _jpg_buf_len);
}
if (res == ESP_OK) {
res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY));
}
if (fb) {
esp_camera_fb_return(fb);
fb = NULL;
_jpg_buf = NULL;
} else if (_jpg_buf) {
free(_jpg_buf);
_jpg_buf = NULL;
}
if (res != ESP_OK) {
break;
}
}
last_frame = 0;
return res;
}
// ********************************************************
// web_handler: affichage de la page web
static esp_err_t web_handler(httpd_req_t *req) {
httpd_resp_set_type(req, "text/html");
httpd_resp_set_hdr(req, "Content-Encoding", "identity");
sensor_t * s = esp_camera_sensor_get();
char pageWeb[300] = "";
strcat(pageWeb, "<!doctype html> <html> <head> <title id='title'>ESP32-CAM</title> </head> <body> <img id='stream' src='http://");
// l'adresse du stream server (exemple: 192.168.0.145:81):
char adresse[20] = "";
sprintf (adresse, "%d.%d.%d.%d:%d", WiFi.localIP()[0], WiFi.localIP()[1], WiFi.localIP()[2], WiFi.localIP()[3], numero_port);
strcat(pageWeb, adresse);
strcat(pageWeb, "/stream'> <br> <br>");
if (carte_presente) {
// on ajoute le bouton
strcat(pageWeb, "<form action='/clic' method='GET'> <INPUT type='submit' value='Photo'></form> ");
}
else {
// on indique l'absence de la carte
strcat(pageWeb, "<p> Pas de carte microSD.</p>");
}
strcat(pageWeb, "</body> </html>");
int taillePage = strlen(pageWeb);
return httpd_resp_send(req, (const char *)pageWeb, taillePage);
}
// ********************************************************
/* enregistrer_photo: prise de la photo et création du fichier jpeg */
void enregistrer_photo (void)
{
char adresse[20] = ""; // chemin d'accès du fichier .jpeg
camera_fb_t * fb = NULL; // frame buffer
// prise de la photo
fb = esp_camera_fb_get();
if (!fb) {
Serial.println("Echec de la prise de photo.");
return;
}
numero_fichier = numero_fichier + 1;
// enregitrement du fichier sur la carte SD
sprintf (adresse, "/photo%d.jpg", numero_fichier);
fs::FS &fs = SD_MMC;
File file = fs.open(adresse, FILE_WRITE);
if (!file) {
Serial.println("Echec lors de la creation du fichier.");
}
else {
file.write(fb->buf, fb->len); // payload (image), payload length
Serial.printf("Fichier enregistre: %s\n", adresse);
}
file.close();
esp_camera_fb_return(fb);
}
// ********************************************************
/* clic_handler: Gestion du clic sur le bouton */
static esp_err_t clic_handler(httpd_req_t *req) {
if (carte_presente) {
enregistrer_photo();
}
return (web_handler(req));
}
// ********************************************************
// startCameraServer: démarrage du web server et du stream server
void startCameraServer() {
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
httpd_uri_t index_uri = {
.uri = "/",
.method = HTTP_GET,
.handler = web_handler,
.user_ctx = NULL
};
httpd_uri_t stream_uri = {
.uri = "/stream",
.method = HTTP_GET,
.handler = stream_handler,
.user_ctx = NULL
};
httpd_uri_t clic_uri = {
.uri = "/clic",
.method = HTTP_GET,
.handler = clic_handler,
.user_ctx = NULL
};
Serial.printf("Demarrage du web server sur le port: '%d'\n", config.server_port);
if (httpd_start(&camera_httpd, &config) == ESP_OK) {
httpd_register_uri_handler(camera_httpd, &index_uri);
httpd_register_uri_handler(camera_httpd, &clic_uri);
}
config.server_port += 1;
config.ctrl_port += 1;
Serial.printf("Demarrage du stream server sur le port: '%d'\n", config.server_port);
if (httpd_start(&stream_httpd, &config) == ESP_OK) {
httpd_register_uri_handler(stream_httpd, &stream_uri);
}
numero_port = config.server_port;
}
// ********************************************************
// initialisation de la caméra, connexion au réseau WiFi.
void setup() {
Serial.begin(115200);
Serial.println();
Serial.println("====");
// définition des broches pour le modèle AI Thinker - ESP32-CAM
camera_config_t config;
config.ledc_channel = LEDC_CHANNEL_0;
config.ledc_timer = LEDC_TIMER_0;
config.pin_d0 = 5;
config.pin_d1 = 18;
config.pin_d2 = 19;
config.pin_d3 = 21;
config.pin_d4 = 36;
config.pin_d5 = 39;
config.pin_d6 = 34;
config.pin_d7 = 35;
config.pin_xclk = 0;
config.pin_pclk = 22;
config.pin_vsync = 25;
config.pin_href = 23;
config.pin_sscb_sda = 26;
config.pin_sscb_scl = 27;
config.pin_pwdn = 32;
config.pin_reset = -1;
config.xclk_freq_hz = 20000000;
config.pixel_format = PIXFORMAT_JPEG; //YUV422|GRAYSCALE|RGB565|JPEG
config.frame_size = FRAMESIZE_VGA; // QVGA|CIF|VGA|SVGA|XGA|SXGA|UXGA
config.jpeg_quality = 10; // 0-63 ; plus bas = meilleure qualité
config.fb_count = 2; // nombre de frame buffers
// initialisation de la carte micro SD
if (SD_MMC.begin()) {
uint8_t cardType = SD_MMC.cardType();
if (cardType != CARD_NONE) {
carte_presente = 1;
}
}
if (!carte_presente) {
Serial.println("Pas de carte microSD dans l'ESP32-CAM");
}
// initialisation de la caméra
esp_err_t err = esp_camera_init(&config);
if (err != ESP_OK) {
Serial.printf("Echec de l'initialisation de la camera, erreur 0x%x", err);
return;
}
sensor_t * s = esp_camera_sensor_get();
Serial.println("");
Serial.print("Connexion au reseau WiFi: ");
Serial.println(ssid);
delay(100);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(250);
}
Serial.println("WiFi connecte");
Serial.println("");
delay(100);
startCameraServer();
Serial.print("La camera est prete. Utilisez 'http://");
Serial.print(WiFi.localIP());
Serial.println("' pour vous connecter.");
}
// ********************************************************
// loop ne fait rien!
void loop() {
delay(10000);
}
-

À lire également:

Première utilisation de l'ESP32-CAM avec l'IDE Arduino , un web server minimalisteles LEDs de l'ESP32-CAM, time-lapse avec l'ESP32-CAM, mouvement panoramique (ESP32-CAM et servomoteur).


Yves Pelletier (TwitterFacebook)

6 commentaires:

  1. Bonjour,
    Merci pour votre partage et pour sa clarté.
    Est-il possible d'enregistrer l'image aussi sur un disque dur partagé sur le réseau ?
    Comment est-il possible de récupérer la date/heure de l'ordinateur pour l'intégrer dans l'image et le nom du fichier ?
    Avez-vous étudié la possibilité d'enregistrer l'image à chaque fois qu'elle change avec un intervalle prédéfini ?
    Merci.

    RépondreSupprimer
    Réponses
    1. Bonjour,
      Merci pour ce tuto. J'ai installé le sketch qui démarre très bien mais je n'ai que le bouton sur une page blanche avec un petit carré en guise d'image.Cordialement.

      Supprimer
  2. Le serveur minimaliste fonctionne bien mais avec ce script pas de photo mais seulement le bouton

    RépondreSupprimer
  3. Bjr,
    Vous seriez vous penché sur le problème que constitue un enregistrement sur carteSD..... de nuit ?
    (donc avec l'utilisation conjointe d'un flash de la power led ET d'un enregistrement de la prise de vue.)

    RépondreSupprimer
  4. Bonjour , merci pour ces explications sur l'esp32-cam. J'aimerais savoir si c'est possible de récupérer l'image capturer directement dans une base de données MySQL ?
    Si oui , comment le faire ?
    Aidez moi svp. Merci bien

    RépondreSupprimer