Enviant ordres a la placa ESP32-CAM, aquesta pren fotos. Amb el navegador podem veure la ultima foto capturada. Les fotos es guarden en el sistema de fitxers interns de la placa (SPIFFS).

El mòdul ESP32-CAM, porta incorporat el microcontrolador ESP32, la càmera OV2640, varis pins GPIOs per connectar perifèrics i un slot per targeta microSD.
ESP32-CAM no porta un connector USB. Necessitem un programador FTDI per pujar codi al microcontrolador a traves dels pins sèrie UOT i UOR.

Per mes informació consultar:
esp32-cam.php
ESP32-CAM amb càmera OV2640.
Programador FTDI.
IDE de Arduino.
Cable USB-miniUSB.

La pagina web te que mostrar tres botons: Captura foto, refresca pagina i rotar imatge. També un espai per mostrar la foto.
La ultima foto es guarda en el sistema de fitxers interns SPIIFS. Si reiniciem la placa, la ultima foto capturada sempre la tenim accessible.
URLs que accepta el servidor:
A la petició “192.168.1.123/”, el servidor envia el index.
A la petició “192.168.1.123/captura”, el servidor canvia la variable “novaCaptura” a true.
A la petició “192.168.1.123/foto-guardada”, el servidor envia la foto.
Al clicar en el botó CAPTURAR FOTO, el navegador envia al servidor el get “/captura”.
Per mostrar la foto en el navegador, es fa servir el get “foto-guardada”.
<div><img src="foto-guardada" id="foto" width="50%"/></div>

Per programar el ESP32 fen servir el IDE de Arduino, aquest es te que configurar amb el ESP32 add-on.
Consultar en:
https://esp32-cam.php#instalarPer connectar el IDE amb la placa per programar-la, es necessita el programador FTDI
Consultar en:
https://esp32-cam.php#conexioPer construir el servidor WEB necessitem la llibreria ESPAsyncWebServer . També es requereix la llibreria Async TCP.
Passos a seguir per instal·lar la llibreria ESPAsyncWebServer.
1. Pagina de descarrega :
https://github.com/me-no-dev/ESPAsyncWebServer2. Des comprimir el .zip i obtenim una carpeta nomenada: ESPAsyncWebServer-master.
3. Re nombrar la carpeta a ESPAsyncWebServer, treure el mot “-master”.
4. Moure la carpeta ESPAsyncWebServer a la nostre carpeta libraries de la carpeta de Arduino.
Com alternativa podem instal·lar la llibreria de la següent manera: En el IDE de Ardino anar a Esbós/Inclou biblioteca/Afegeix una biblioteca .ZIP... i seleccionar la carpeta descarregada.
Passos a seguir per instal·lar la llibreria Async TCP.
1. Pagina de descarrega :
https://github.com/me-no-dev/AsyncTCP/archive/master.zip2. Des comprimir el .zip i obtenim una carpeta nomenada: AsyncTCP-master.
3. Re nombrar la carpeta a AsyncTCP, treure el mot “-master”.
4. Moure la carpeta AsyncTCP a la nostre carpeta libràries de la carpeta de Arduino.
Com alternativa podem instal·lar la llibreria de la següent manera: En el IDE de Arduino anar a Esbós/Inclou biblioteca/Afegeix una biblioteca .ZIP... i seleccionar la carpeta descarregada.
Guardem la imatge en el sistema intern de fitxers del ESP32, SPIFFS (Serial Peripheral Interface Flsh File System). SPIFFS es un sistema de fitxers lleuger en el chip de la memòria flash que incorpora el ESP32.
Consultar en:
https://spiffs.phpesp32-foto-webV2.ino
/********* Rui Santos Complete project details at https://RandomNerdTutorials.com/esp32-cam-take-photo-display-web-server/ Codi modificat per tresavis https://tresavis.cat The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. IMPORTANT!!! - Placa "AI Thinker ESP32-CAM" - GPIO 0 connectat a GND per programar ESP32 - Despres de connectar GPIO 0 a GND, prem el RESET de ESP32-CAM per posar la placa en flashing mode *********/ // llibreries #include "WiFi.h" #include "esp_camera.h" #include "esp_timer.h" #include "img_converters.h" #include "Arduino.h" #include "soc/soc.h" // Disable brownout problems #include "soc/rtc_cntl_reg.h" // Disable brownout problems #include "driver/rtc_io.h" #include <ESPAsyncWebServer.h> #include <StringArray.h> #include <SPIFFS.h> #include <FS.h> // WiFi const char* ssid = "mirablau"; const char* password = "**********"; // Creem un objecte de la classe AsyncWebServer en el port 80 AsyncWebServer server(80); boolean novaCaptura = false; // Nom i ruta de la foto per ser guardada en SPIFFS #define RUTA_FOTO "/foto.jpg" // Pins de la camara OV2640 (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 //Pagina WEB const char index_html[] PROGMEM = R"rawliteral( <!DOCTYPE HTML><html> <head> <meta name="viewport" content="width=device-width, initial-scale=1"> <style> body { text-align:center; } .vertical { margin-bottom: 10%; } .horizontal{ margin-bottom: 0%; } </style> </head> <body> <div id="contanidor"> <h1>TresAvis</h1> <h2>ESP32-CAM. Ultima foto</h2> <p>Esperar mes de 5 segons per capturar i guardar la foto.</p> <p> <button onclick="rotarFoto();">ROTAR FOTO</button> <button onclick="capturarFoto();">CAPTURAR FOTO</button> <button onclick="location.reload();">REFRESCAR PAGINA</button> </p><hr><hr> </div> <div><img src="foto-guardada" id="foto" width="50%"/></div> </body> <script> // Envia la URL /captura al servidor i ESP32-CAM dispara la camara. function capturarFoto() { var xhr = new XMLHttpRequest(); xhr.open('GET', "/captura", true); xhr.send(); } // Suma 90 graus i compara si graus/90 es impar o par // 90/90=1, 180/90=2, .... // Si es impar agafa la classe vertical que canvia el margin-botton, perque la imatge no se sobre-posi als botons // La imatge rota cada vegada 90º en sentit del rellotge. var graus = 0; function rotarFoto() { var imatge = document.getElementById("foto"); graus += 90; if(esImpar(graus/90)){ document.getElementById("contanidor").className = "vertical"; } else{ document.getElementById("contanidor").className = "horizontal"; } imatge.style.transform = "rotate(" + graus + "deg)"; } function esImpar(n) { return Math.abs(n % 2) == 1; } </script> </html>)rawliteral"; //////////////////// setpup() /////////////////////////////////////////// void setup() { // Serial port 115200 Serial.begin(115200); // Connectar a la Wi-Fi WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.println("Connectant a la WiFi..."); } // Montar el sistema de fitxers SPIFFS if (!SPIFFS.begin(true)) { Serial.println("Error al montar SPIFFS"); ESP.restart(); } else { delay(500); Serial.println("SPIFFS montat satisfactoriament"); } // ESP32 imprimeix la IP local Serial.print("IP Adressa: http://"); Serial.println(WiFi.localIP()); // Turn-off the 'brownout detector' detector de apagades WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); // Configurar la camara OV2640 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; } // Iniciar la Camara esp_err_t error = esp_camera_init(&config); if (error != ESP_OK) { Serial.printf("Camara la fallat amb error 0x%x", error); ESP.restart(); } // URLs que accepta el servidor: // 192.168.1.123/ // 192.168.1.123/captura // 192.168.1.123/foto-guardada // A la petició "/" el servidor envia el index server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) { request->send_P(200, "text/html", index_html); }); // A la petició "/captura" el servidor posa la variable novaCaptura a true i envia un missatge server.on("/captura", HTTP_GET, [](AsyncWebServerRequest * request) { novaCaptura = true; request->send_P(200, "text/plain", "Capturant foto"); }); // A la petició "/foto-guardada" el servidor envia la foto server.on("/foto-guardada", HTTP_GET, [](AsyncWebServerRequest * request) { request->send(SPIFFS, RUTA_FOTO, "image/jpg", false); }); // Inicia el servidor server.begin(); //Llista de les fotos guardades en SPIFFS File root = SPIFFS.open("/"); File file = root.openNextFile(); while(file){ Serial.print("FILE: "); Serial.println(file.name()); file = root.openNextFile(); } } ///////////////////////// loop() ////////////////////////////////// void loop() { if (novaCaptura) { capturarFotoGuardarSpiffs(); novaCaptura = false; } delay(1); } // Comproba si la captura ha sigut exitosa // retorna true si el tamnay de la foto es mes gran de 100 bytes bool checkFoto( fs::FS &fs ) { File f_foto = fs.open( RUTA_FOTO ); unsigned int foto_tamany = f_foto.size(); return ( foto_tamany > 100 ); } // Captura una foto i la guarda en SPIFFS void capturarFotoGuardarSpiffs( void ) { camera_fb_t * fb = NULL; // pointer bool ok = 0; // Boolean que indica si la foto ha estat presa correctament // Bucle do while que es repetirà mentres la variable ok sigui falsa do { // Captura la foto Serial.println("Capturant foto..."); // ESP32-CAM fa la foto fb = esp_camera_fb_get(); if (!fb) { Serial.println("La captura ha fallat"); return; } // Guarda la foto en el sistema de fitxers SPIFFS Serial.printf("El nom de la foto es: %s\n", RUTA_FOTO); File fitxer = SPIFFS.open(RUTA_FOTO, FILE_WRITE); // Inserta les dades de la foto en el fitxer if (!fitxer) { Serial.println("Ha fallat obrir el fitxer en mode escritura"); } else { fitxer.write(fb->buf, fb->len); // payload (image), payload length Serial.print("La foto ha estat guardada en: "); Serial.print(fitxer.name()); Serial.print(" - Tamany: "); Serial.print(fitxer.size()); Serial.println(" bytes"); } // Tancar el fitxer fitxer.close(); esp_camera_fb_return(fb); // Comproba si el fitxer que gurda la foto no esta buit. ok = checkFoto(SPIFFS); } while ( !ok ); }
Primer de tot el codi te de incloure les llibreries necessàries per la càmera, el servidor web i el sistema de fitxers SPIFFS:
// Llibreries: #include "WiFi.h" #include "esp_camera.h" #include "esp_timer.h" #include "img_converters.h" #include "Arduino.h" #include "soc/soc.h" // Desactiva apagades per falta de potencia #include "soc/rtc_cntl_reg.h" // Desactiva apagades per falta de potencia #include "driver/rtc_io.h" #include <ESPAsyncWebServer.h> #include <StringArray.h> #include <SPIFFS.h> #include <FS.h>
Les dades de la WiFi, perquè el ESP32-CAM pugui connectar a la xarxa local:
// WiFi const char* ssid = "mirablau"; const char* password = "*********";
Creem un objecte AsyncWebServer en el port 80, pot ser un altre port, però al connectar amb el navegador al servidor, el tenim que especificar:
// Creem un objecte de la classe AsyncWebServer en el port 80 AsyncWebServer server(80);
Quan la variable novaCaptura es true es dispara la nova foto:
boolean novaCaptura = false;
La constant RUTA_FOTO defineix el lloc i el nom on es guarda la ultima foto:
// Nom i ruta de la foto per ser guardada en SPIFFS #define RUTA_FOTO "/foto.jpg"
Pagina web:
La web conté tres botons, CAPTURAR FOTO, ROTAR FOTO I REFRESCAR PAGINA:
<button onclick="rotarFoto();">ROTAR FOTO</button> <button onclick="capturarFoto();">CAPTURAR FOTO</button> <button onclick="location.reload();">REFRESCAR PAGINA</button>
Al clicar sobre els botons s’executen les funcions capturarFoto(), rotarFoto() i reload():
// Envia la URL /captura al servidor i ESP32-CAM dispara la camara. function capturarFoto() { var xhr = new XMLHttpRequest(); xhr.open('GET', "/captura", true); xhr.send(); } // Suma 90 graus i compara si graus/90 es impar o par // 90/90=1, 180/90=2, .... // Si es impar agafa la classe vertical que canvia el margin-botton, perque la imatge no se sobre-posi als botons // La imatge rota cada vegada 90º en sentit del rellotge. var graus = 0; function rotarFoto() { var imatge = document.getElementById("foto"); graus += 90; if(esImpar(graus/90)){ document.getElementById("contanidor").className = "vertical"; } else{ document.getElementById("contanidor").className = "horizontal"; } imatge.style.transform = "rotate(" + graus + "deg)"; } function esImpar(n) { return Math.abs(n % 2) == 1; }
SPIFFS:
// Montar el sistema de fitxers SPIFFS if (!SPIFFS.begin(true)) { Serial.println("Error al montar SPIFFS"); ESP.restart(); } else { delay(500); Serial.println("SPIFFS montat satisfactoriament"); }
ESP32 desposa de un sistema de interrupciones que es dispara per diferents motius. Per exemple quan la bateria es baixa, la placa s’apaga:
// Des-activa the 'brownout detector' detector de apagades per falta de potencia WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);
Configurar i iniciar la cámara:
La configuració dels pins i paràmetres de la càmera es igual sempre, es copiar i enganxar:
// Iniciar la Camara esp_err_t error = esp_camera_init(&config); if (error != ESP_OK) { Serial.printf("Camara la fallat amb error 0x%x", error); ESP.restart(); }
Peticions que enviem al servidor:
// URLs que accepta el servidor: // 192.168.1.123/ // 192.168.1.123/captura // 192.168.1.123/foto-guardada // A la petició "/" el servidor envia el index server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) { request->send_P(200, "text/html", index_html); }); // A la petició "/captura" el servidor posa la variable novaCaptura a true i envia un missatge server.on("/captura", HTTP_GET, [](AsyncWebServerRequest * request) { novaCaptura = true; request->send_P(200, "text/plain", "Capturant foto"); }); // A la petició "/foto-guardada" el servidor envia la foto server.on("/foto-guardada", HTTP_GET, [](AsyncWebServerRequest * request) { request->send(SPIFFS, RUTA_FOTO, "image/jpg", false); }); // Inicia el servidor server.begin();
loop(), si novaCaptura es true s’executa la funció capturarFotoGuardarSpiffs() i torna a posar la variable novaCaptura a false:
void loop() { if (novaCaptura) { capturarFotoGuardarSpiffs(); novaCaptura = false; } delay(1); }
La funció “capturarFotoGuardarSpiffs()”:
En un bucle do-While que s’executa sempre que la variable ok sigui false, dispara la foto i la guarda en el sistema de fitxers SPIFFS. Comprova si la captura ha sigut exitosa, si es ok s’acaba el bucle.
// Captura una foto i la guarda en SPIFFS void capturarFotoGuardarSpiffs( void ) { // Punter al buffer *fb camera_fb_t * fb = NULL; // pointer bool ok = 0; // Boolean que indica si la foto ha estat presa correctament // Bucle do while que es repetirà mentres la variable ok sigui falsa do { // Captura la foto Serial.println("Capturant foto..."); // ESP32-CAM fa la foto // Escriu el buffer fb = esp_camera_fb_get(); if (!fb) { Serial.println("La captura ha fallat"); return; } // Guarda la foto en el sistema de fitxers SPIFFS Serial.printf("El nom de la foto es: %s\n", RUTA_FOTO); File fitxer = SPIFFS.open(RUTA_FOTO, FILE_WRITE); // Inserta les dades de la foto en el fitxer if (!fitxer) { Serial.println("Ha fallat obrir el fitxer en mode escritura"); } else { fitxer.write(fb->buf, fb->len); // payload (image), payload length Serial.print("La foto ha estat guardada en: "); Serial.print(fitxer.name()); Serial.print(" - Tamany: "); Serial.print(fitxer.size()); Serial.println(" bytes"); } // Tancar el fitxer fitxer.close(); // Buida el buffer esp_camera_fb_return(fb); // Comproba si el fitxer que gurda la foto no esta buit. ok = checkFoto(SPIFFS); } while ( !ok );
La funció checkFoto retorna true si la captura ha sigut exitosa, altrament retorna false. Comprova la grandària del fitxer, si es mes gran de 100 Bytes:
// Comproba si la captura ha sigut exitosa // retorna true si el tamnay de la foto es mes gran de 100 bytes bool checkFoto( fs::FS &fs ) { File f_foto = fs.open( RUTA_FOTO ); unsigned int foto_tamany = f_foto.size(); return ( foto_tamany > 100 ); }
Recordar que es té que desfer el pont entre GND i GPOI0 fet servir per pujar el codi a la placa.
Fer un reset a la placa.
El monitor sèrie mostra la IP que te el servidor WEB. En el nostre cas es:
192.168.1.123

El resultat es el següent:


Afegir un sensor detector de moviment i fer la foto al detectar canvis.
Enviar les fotos a un servidor FTP.
Afegir la possibilitat de fer vídeo Streaming.
Fer servir un BOT de Telegram per veure les imatges.
El mar.
Havaneres.
Lloret antic.
Lloret avui.
De cala Morisca a Sa Carabera.
De Sa Carabera a Treumal.
Lloret en kayak.
Vígilancia remota.
Caixa-tpv.
Cambrer-app.
Client webApp.
Relats del banc del si no fos
Històries d'ahir
Vídeos antics de Lloret
El caiac
Contes de l'avi.
Control d’accés amb contrasenya.
Orgue de llums.
Comparativa de editors Arduino.
Projecte bon Nadal.
Control de la qualitat del aire interior (CO2) amb semàfor LED.
NodeMCU-ThingSpeak
ThingSpeak.
NodeMCU com AccessPoint.
Control de la llum de casa per web.
Control de un led per el mòbil.
Sistema de fitxers en ESP8266.
Altura del nivell d'aigua.
Avis d’alarma a Telegram.
Control de la intensitat de llum de un LED per Internet.
Magatzematge de temperatura i humitat en Thinger.
Publicar temperatura i humitat en Google Sheets.
ESP8266 i Firebase.
La bàscula. ESP8266 NodeMCU amb cèl·lula de càrrega.
ESP32-CAM
ESP32-CAM captura fotos per ser vistes en la WEB.
Pujar fotos a un servidor FTP amb ESP32-CAM.
MicroPython en ESP8266.
Visual Estudio Code per Arduino.
Classificador d’imatges.
Instal·lar i configurar Apache en Ubuntu.
Accés a un servidor Linux remotament.
Client i Servidor FTP.
Màquina virtual UBUNTU.
WindowsXP en Virtualbox.
Multisim, simulador de circuits electrònics.
Proteus, simulador de circuits electrònics.
MySQL. Instal·lació.
MySQL. Aplicacions.
Netbeans Maven MySQL.
Java+Netbeans+Maven+EclipseLink+Mysql
Aplicació d’escriptori JavaFX
App JavaFX amb SceneBuilder
App Java-Swing


Balleu-me amb la vostra bellesa amb un violí ardent Dance me to your beauty with a burning violin Balleu-me pel pànic fins que estigueu reunits amb seguretat Dance me through the panic till I'm gathered safely in Aixeca’m com una branca d’olivera i fes-me el meu colom cap a casa Lift me like an olive branch and be my homeward dove Balla amb mi fins al final de l'amor Dance me to the end of love Balla amb mi fins al final de l'amor Dance me to the end of love Ah, deixeu-me veure la vostra bellesa quan els testimonis se n’hagin anat Oh, let me see your beauty when the witnesses are gone Deixa’m sentir-te en moviment com ho fan a Babilònia Let me feel you moving like they do in Babylon Mostra’m a poc a poc el que només sé dels límits Show me slowly what I only know the limits of Balla amb mi fins al final de l'amor Dance me to the end of love Balla amb mi fins al final de l'amor Dance me to the end of love Ballar-me fins al casament ara, ballar-me sense parar Dance me to the wedding now, dance me on and on Balla’m molt tendrament i balla’m molt llarg Dance me very tenderly and dance me very long Tots dos estem per sota del nostre amor, estem tots dos per sobre We're both of us beneath our love, we're both of us above Balla amb mi fins al final de l'amor Dance me to the end of love Balla amb mi fins al final de l'amor Dance me to the end of love Balleu-me amb els nens que demanen que neixin Dance me to the children who are asking to be born Balleu-me a través de les cortines que han perdut els nostres petons Dance me through the curtains that our kisses have outworn Aixequeu ara una tenda de refugi, tot i que cada fil està esquinçat Raise a tent of shelter now, though every thread is torn Balla amb mi fins al final de l'amor Dance me to the end of love Balleu-me amb la vostra bellesa amb un violí ardent Dance me to your beauty with a burning violin Balleu-me pel pànic fins que estigueu reunits amb seguretat Dance me through the panic till I'm gathered safely in Toca’m amb la mà nua o toca’m amb el guant Touch me with your naked hand or touch me with your glove Balla amb mi fins al final de l'amor Dance me to the end of love Balla amb mi fins al final de l'amor Dance me to the end of love Balla amb mi fins al final de l'amor Dance me to the end of love Font: LyricFind Compositors: Leonard Cohen Lletra de Dance Me to the End of Love © Sony/ATV Music Publishing LLC
https://strava.app.link/nDpMEjvqegb



Noves tecnologies WEB Arduino Domótica MycroPython NodeMCU IoT Java ESP8266 Lora C++ Programació Server-Client