Last active
April 16, 2021 12:08
-
-
Save ypelletier/92f0f6b7ada72f2a7a40f99011ca1553 to your computer and use it in GitHub Desktop.
Timelapse avec ESP32-CAM. Les photos s'enregistrent sur une carte SD. On démarre la prise de vue et on contrôle le délai entre 2 images consécutives grâce à une page web.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Timelapse avec ESP32-CAM. Les photos s'enregistrent sur une carte SD. | |
On démarre la prise de vue et on contrôle le délai entre 2 images | |
consécutives grâce à une page web. | |
Plus d'infos: | |
https://electroniqueamateur.blogspot.com/2020/03/time-lapse-avec-lesp32-cam.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 | |
unsigned long photoPrecedente = 0; // prise de la dernière photo | |
long intervalle = 10000; // nombre de millisecondes entre 2 photos. | |
bool timeLapseActif = 0; // devient vrai pendant l'enregistrement de la séquence | |
// ******************************************************** | |
// 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[450] = ""; | |
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 | |
if (timeLapseActif) { | |
strcat(pageWeb, "<form action='/clic' method='GET'> <label for='intervalle'> Intervalle (en secondes):</label> <br> <input type='text' id='intervalle' name='intervalle' value = '10'><br><br><INPUT type='submit' value='Arrêter'></form> "); | |
} | |
else { | |
strcat(pageWeb, "<form action='/clic' method='GET'> <label for='intervalle'> Intervalle (en secondes):</label> <br> <input type='text' id='intervalle' name='intervalle' value = '10'><br><br><INPUT type='submit' value='Démarrer'></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) { | |
// récupérons le contenu du champ texte (temps entre 2 poses successives) | |
char* buf; | |
size_t buf_len; | |
char valeur[32] = {0,}; | |
buf_len = httpd_req_get_url_query_len(req) + 1; | |
if (buf_len > 1) { | |
buf = (char*)malloc(buf_len); | |
if (!buf) { | |
httpd_resp_send_500(req); | |
return ESP_FAIL; | |
} | |
if (httpd_req_get_url_query_str(req, buf, buf_len) == ESP_OK) { | |
if (httpd_query_key_value(buf, "intervalle", valeur, sizeof(valeur)) == ESP_OK ) { | |
} else { | |
free(buf); | |
httpd_resp_send_404(req); | |
return ESP_FAIL; | |
} | |
} else { | |
free(buf); | |
httpd_resp_send_404(req); | |
return ESP_FAIL; | |
} | |
free(buf); | |
} else { | |
httpd_resp_send_404(req); | |
return ESP_FAIL; | |
} | |
intervalle = atoi(valeur) * 1000; | |
timeLapseActif = !(timeLapseActif); | |
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."); | |
} | |
// ******************************************************** | |
void loop() { | |
// enregistrement d'une photo, si c'est le bon moment | |
unsigned long maintenant = millis(); | |
if (maintenant - photoPrecedente >= intervalle) { | |
photoPrecedente = maintenant; | |
if (timeLapseActif) { | |
enregistrer_photo(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment