Tutorial del Internet de las Cosas y Bluetooth con el ESP32
Juan Antonio Villalpando
Volver al índice del tutorial
____________________________
247.- ESP32-CAM WiFi con OV2640.
- Vamos a ver una cámara web con ESP32. Necesitamos un módulo llamado ESP32-CAM WiFi OV2640, lo puedes encontrar en Aliexpress por unos 6€. Este módulo tiene WiFi y Bluetooth.
160MHz clock speed
RAM 520KB SRAM + 4M PSRAM
Bluetooth classic y BLE
Wi-Fi 802.11 b/g/n/
Cámara:
OV2640
- El módulo también tiene un lector de microSdCard, por si queremos grabar foto o vídeo. Para este primer ejemplo no es necesario.
- En la siguiente imagen dos fotos a distinta escala.
- Como habrás observado, no tiene conector USB para introducirle el programa, así que también necesitaremos un FTDI FT232RL USB, unos 2 €
- Observa el tipo de conector USB que utiliza este módulo, es un Mini-B USB.
|
Cable necesario para conectar este módulo al ordenador. |
________________________________________________
1.- Vamos a cargar el programa CameraWebServer.
- Vamos al IDE de Arduino: Archivos / Ejemplos / ESP32 / Camera / CameraWebServer.
- En el código anterior desmarcamos:
#define CAMERA_MODEL_AI_THINKER
y marcamos // #define CAMERA_MODEL_WROVER_KIT
- Conectamos el FTDI al ESP32-CAM.
- Conectamos el cable USB al FTDI y al ordenador, para grabar el programa del Arduino al ESP32-CAM
- Vamos a Herramientas / Placa / localizamos AI Thinker ESP32-CAM. Ponemos el Puerto.
[Tambíen podría funcionar con ESP32 Wrover Module]
- Cargamos el programa.
- [Cada vez que vayamos a cargar un programa es conveniente quitar el cable USB y volver a ponerlo].
- [Cada vez que vayamos a cargar un programa debemos conectar el cable GND y IO0].
- Cuando haya terminado de cargar, quitamos el cable gris que conecta al GND y IO0.
- Observamos el Monitor Serie. Encontraremos la IP asignada.
- Vamos a un navegador web y escribimos esa IP.
- 192.168.1.7 obtendremos una página para realizar la configuración de la cámara.
- 192.168.1.7:81/stream para ver solo el vídeo.
- Una vez que sepamos la IP, solo son necesarios los cables de alimentación, es decir el GND y el de 5 V, los demás cables los podemos quitar.
- El FTDI no es necesario, podemos alimentar la tarjeta con una fuente de alimentación externa de 5V.
- Prueba utilizar varios modelo de Placa, por ejemplo la Wrover.
__________________________________________________________________
- Aquí tenemos otro modelo similar con el ESP32-S WiFi y la cam OV2640.
- Lo bueno que tiene este modelo es que contiene el conector para el cable USB, es decir no necesitamos la tarjeta FTDI del modelo anterior.
_____________________________________________
- Otros enlaces.
- Otro tutorial:
https://randomnerdtutorials.com/esp32-cam-video-streaming-face-recognition-arduino-ide/
- Otro tutorial con vídeo:
https://www.instructables.com/id/ESP-32-Camera-Streaming-Video-Over-WiFi-Getting-St/
- Comparativa de tarjetas.
https://makeradvisor.com/esp32-camera-cam-boards-review-comparison/
- Otros ejemplos
https://github.com/espressif/esp32-camera
- IP fija:
https://randomnerdtutorials.com/esp32-static-fixed-ip-address-arduino-ide/
- Hacer fotos y guardarlas en la SdCard.
https://randomnerdtutorials.com/esp32-cam-take-photo-save-microsd-card/
- Hacer fotos y mostrarla en Web (las fotos se guardan en la memoria interna, en esta página he puesto un código para guardarla también en la SdCard.)
https://randomnerdtutorials.com/esp32-cam-take-photo-display-web-server/
__________________________________________________________
- Antena.
- Hay modelos del ESP32-CAM que vienen con antena y otros sin antena.
- Debes consultar el puente que se encuentra cerca del conector de antena.
- Si ese puente está como en la imagen de la derecha, tu tarjeta está configurada para funcionar con la antena que se encuentra en la misma tarjeta.
- Si ese puente está como en la imagen de la izquierda, tu tarjeta está configurada para funcionar con la antena externa que debemos conectar.
- Ese puente se puede cambiar de lugar utilizando un soldador y un poco de paciencia.
__________________________________________________________
2.- App Inventor. VisorWeb.
- Error:
header fields are too long for server to interpret
- Con el VisorWeb (WebViewer) obtenemos el error "header fields are too long for server to interpert".
- Esto nos indica que la cabecera que envía la apliación es demasiado grande para poder ser interpretada por el Servidor instalado en el ESP32.
- Podemos abrir el navegador web que tengamos instalado en nuestro móvil mediante ActivityStarter y ver ahí el vídeo:
Action: android.intent.action.VIEW
DataUri: http://192.168.1.7:81/stream
__________________________________________________________
3.- App Inventor. VisorWeb. Utilizamos otra librería para evitar el error "header fields are too long for server to interpert".
- Si queremos verlo con el VisorWeb, vamos a esta página, bajamos e instalamos la librería en Arduino/libraries.
https://github.com/yoursunny/esp32cam
- esp32cam.zip
- Vamos a su carpeta examples y ejecutamos el WifiCam.ino
- Podemos ver el vídeo en VisorWeb mediante:
http://192.168.1.7
/cam.bmp
/cam-lo.jpg
/cam-hi.jpg
/cam.mjpeg
- http://192.168.1.7/cam.mjpeg
https://groups.google.com/forum/#!msg/mitappinventortest/jKLQBeKQ9TA/vQSc87hnAwAJ
- Código para utilizar con el VisorWeb.
|
#include <esp32cam.h>
#include <WebServer.h>
#include <WiFi.h>
const char* WIFI_SSID = "my-ssid";
const char* WIFI_PASS = "my-pass";
WebServer server(80);
static auto loRes = esp32cam::Resolution::find(320, 240);
static auto hiRes = esp32cam::Resolution::find(800, 600);
void handleBmp()
{
if (!esp32cam::Camera.changeResolution(loRes)) {
Serial.println("SET-LO-RES FAIL");
}
auto frame = esp32cam::capture();
if (frame == nullptr) {
Serial.println("CAPTURE FAIL");
server.send(503, "", "");
return;
}
Serial.printf("CAPTURE OK %dx%d %db\n", frame->getWidth(), frame->getHeight(),static_cast<int>(frame->size()));
if (!frame->toBmp()) {
Serial.println("CONVERT FAIL");
server.send(503, "", "");
return;
}
Serial.printf("CONVERT OK %dx%d %db\n", frame->getWidth(), frame->getHeight(), static_cast<int>(frame->size()));
server.setContentLength(frame->size());
server.send(200, "image/bmp");
WiFiClient client = server.client();
frame->writeTo(client);
}
void serveJpg()
{
auto frame = esp32cam::capture();
if (frame == nullptr) {
Serial.println("CAPTURE FAIL");
server.send(503, "", "");
return;
}
Serial.printf("CAPTURE OK %dx%d %db\n", frame->getWidth(), frame->getHeight(), static_cast<int>(frame->size()));
server.setContentLength(frame->size());
server.send(200, "image/jpeg");
WiFiClient client = server.client();
frame->writeTo(client);
}
void handleJpgLo()
{
if (!esp32cam::Camera.changeResolution(loRes)) {
Serial.println("SET-LO-RES FAIL");
}
serveJpg();
}
void handleJpgHi()
{
if (!esp32cam::Camera.changeResolution(hiRes)) {
Serial.println("SET-HI-RES FAIL");
}
serveJpg();
}
void handleJpg()
{
server.sendHeader("Location", "/cam-hi.jpg");
server.send(302, "", "");
}
void handleMjpeg()
{
if (!esp32cam::Camera.changeResolution(hiRes)) {
Serial.println("SET-HI-RES FAIL");
}
Serial.println("STREAM BEGIN");
WiFiClient client = server.client();
auto startTime = millis();
int res = esp32cam::Camera.streamMjpeg(client);
if (res <= 0) {
Serial.printf("STREAM ERROR %d\n", res);
return;
}
auto duration = millis() - startTime;
Serial.printf("STREAM END %dfrm %0.2ffps\n", res, 1000.0 * res / duration);
}
void setup()
{
Serial.begin(115200);
Serial.println();
{
using namespace esp32cam;
Config cfg;
cfg.setPins(pins::AiThinker);
cfg.setResolution(hiRes);
cfg.setBufferCount(2);
cfg.setJpeg(80);
bool ok = Camera.begin(cfg);
Serial.println(ok ? "CAMERA OK" : "CAMERA FAIL");
}
WiFi.persistent(false);
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
}
Serial.print("http://");
Serial.println(WiFi.localIP());
Serial.println(" /cam.bmp");
Serial.println(" /cam-lo.jpg");
Serial.println(" /cam-hi.jpg");
Serial.println(" /cam.mjpeg");
server.on("/cam.bmp", handleBmp);
server.on("/cam-lo.jpg", handleJpgLo);
server.on("/cam-hi.jpg", handleJpgHi);
server.on("/cam.jpg", handleJpg);
server.on("/cam.mjpeg", handleMjpeg);
server.begin();
}
void loop()
{
server.handleClient();
}
|
__________________________________________________________
4.- Encender el LED Flash.
p247_ESP32_Cam.aia
- Vuelvo a utilizar la librería: esp32cam.zip
- Vamos a modificar el código anterior para Encender/Apagar el LED4, este LED es la luz de Flash del módulo.
- Utilizo estas líneas para indicar si quiero encender o apagar el LED4:
server.on("/LED_on", handleJpg_on); // KIO4
server.on("/LED_off", handleJpg_off); // KIO4
- Estas líneas llaman a los procesos:
void handleJpg_on() {
Serial.println("LED ON");
digitalWrite(LED4,HIGH);
serveJpg(); }
void handleJpg_off() {
Serial.println("LED OFF");
digitalWrite(LED4,LOW);
serveJpg(); }
- También podemos activar el Stream mediante:
server.on("/cam.mjpeg", handleMjpeg);
pero una vez activado es más complicado pararlo, para ello lo vuelvo a llamar, al cabo de unos 35 segundos, para.
|
#include <esp32cam.h>
#include <WebServer.h>
#include <WiFi.h>
const char* WIFI_SSID = "my-ssid";
const char* WIFI_PASS = "my-pass";
#define LED4 4 // LED4 is a Built-in Flash LED.
WebServer server(80);
static auto loRes = esp32cam::Resolution::find(160, 120); // Cambio resolución.
static auto hiRes = esp32cam::Resolution::find(800, 600);
void handleBmp()
{
if (!esp32cam::Camera.changeResolution(loRes)) {
Serial.println("SET-LO-RES FAIL");
}
auto frame = esp32cam::capture();
if (frame == nullptr) {
Serial.println("CAPTURE FAIL");
server.send(503, "", "");
return;
}
Serial.printf("CAPTURE OK %dx%d %db\n", frame->getWidth(), frame->getHeight(),static_cast<int>(frame->size()));
if (!frame->toBmp()) {
Serial.println("CONVERT FAIL");
server.send(503, "", "");
return;
}
Serial.printf("CONVERT OK %dx%d %db\n", frame->getWidth(), frame->getHeight(), static_cast<int>(frame->size()));
server.setContentLength(frame->size());
server.send(200, "image/bmp");
WiFiClient client = server.client();
frame->writeTo(client);
}
void serveJpg()
{
auto frame = esp32cam::capture();
if (frame == nullptr) {
Serial.println("CAPTURE FAIL");
server.send(503, "", "");
return;
}
Serial.printf("CAPTURE OK %dx%d %db\n", frame->getWidth(), frame->getHeight(), static_cast<int>(frame->size()));
server.setContentLength(frame->size());
server.send(200, "image/jpeg");
WiFiClient client = server.client();
frame->writeTo(client);
}
void handleJpgLo()
{
if (!esp32cam::Camera.changeResolution(loRes)) {
Serial.println("SET-LO-RES FAIL");
}
serveJpg();
}
void handleJpgHi()
{
if (!esp32cam::Camera.changeResolution(hiRes)) {
Serial.println("SET-HI-RES FAIL");
}
serveJpg();
}
////////////////// Modified. KIO4.COM
void handleJpg_on()
{
Serial.println("LED ON");
digitalWrite(LED4,HIGH);
serveJpg();
}
void handleJpg_off()
{
Serial.println("LED OFF");
digitalWrite(LED4,LOW);
serveJpg();
}
void handleMjpeg_off()
{
handleMjpeg();
}
///////////////////////////
void handleMjpeg()
{
if (!esp32cam::Camera.changeResolution(hiRes)) {
Serial.println("SET-HI-RES FAIL");
}
Serial.println("STREAM BEGIN");
WiFiClient client = server.client();
auto startTime = millis();
int res = esp32cam::Camera.streamMjpeg(client);
if (res <= 0) {
Serial.printf("STREAM ERROR %d\n", res);
return;
}
auto duration = millis() - startTime;
Serial.printf("STREAM END %dfrm %0.2ffps\n", res, 1000.0 * res / duration);
}
void setup()
{
Serial.begin(115200);
Serial.println();
pinMode(LED4, OUTPUT);
{
using namespace esp32cam;
Config cfg;
cfg.setPins(pins::AiThinker);
cfg.setResolution(hiRes);
cfg.setBufferCount(2);
cfg.setJpeg(80);
bool ok = Camera.begin(cfg);
Serial.println(ok ? "CAMERA OK" : "CAMERA FAIL");
}
WiFi.persistent(false);
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
}
Serial.print("http://");
Serial.println(WiFi.localIP());
Serial.println(" /cam.bmp");
Serial.println(" /cam-lo.jpg");
Serial.println(" /cam-hi.jpg");
Serial.println(" /cam.mjpeg");
server.on("/cam.bmp", handleBmp);
server.on("/cam-lo.jpg", handleJpgLo);
server.on("/cam-hi.jpg", handleJpgHi);
server.on("/cam.mjpeg", handleMjpeg);
server.on("/cam.mjpeg_off", handleMjpeg_off); // KIO4
server.on("/LED_on", handleJpg_on); // KIO4
server.on("/LED_off", handleJpg_off); // KIO4
server.begin();
}
void loop()
{
server.handleClient();
}
|
___________________
- Aplicación.
- He puesto un Reloj con un Intervalor de 800 ms, cada ese tiempo, se hace una captura de pantalla mediante: server.on("/cam-lo.jpg", handleJpgLo);
- Si pulso el Botón de encendido, se realizará: server.on("/LED_on", handleJpg_on); // KIO4
- Si pulso el Botón de apagado, se realizará: server.on("/LED_off", handleJpg_off); // KIO4
- Con la resolución del código obtengo imágenes de: CAPTURE OK 160x120 1788b
- Si quiero otra resolución cambio: static auto loRes = esp32cam::Resolution::find(160, 120); // Cambio resolución.
- Si pulso el Stream ON, se verá en stream, pero no se activarán el LED. Para quitar el stream pulso Stream OFF, tarda unos 35 segundos en desconectarse.
___________________
- Diseño.
___________________
- Bloques.
-------------------------------------------------------------
- En el primer código que hemos visto en esta página, también puedes probar, en app_httpd.cpp
#undef CONFIG_HTTPD_MAX_REQ_HDR_LEN
#define CONFIG_HTTPD_MAX_REQ_HDR_LEN 1024
#undef HTTPD_MAX_REQ_HDR_LEN
#define HTTPD_MAX_REQ_HDR_LEN 1024
https://github.com/espressif/arduino-esp32/blob/master/tools/sdk/include/esp_http_server/esp_http_server.h
https://github.com/v12345vtm/CameraWebserver2SD/blob/master/CameraWebserver2SD/CameraWebserver2SD.ino
__________________________________________________________
5.- Subir capturas de imágenes al Google Drive.
esp32cam-gdrive.zip
- Vamos a enviar una foto cada minuto desde el ESP32-CAM a Google Drive.
- El código del ESP32 creará una carpeta llamada ESP32-CAM en nuestro Google Drive y subirá capturas de imágenes de la cámara cada minuto.
- Tenemos que hacer una buena configuración de Google Drive sobre todo en la asignación de permisos.
- En esta página he puesto el proceso para trabajar con Google Sheet, consulta la manera de crear el Script
136.- Google sheet. Hoja de Cálculo. Guardar. Borrar. Ver. Actualizar. Bajar.
- Entra en tu cuenta de Google y Google Script.
- En la parte del Script Recursos / Servicios Avanzados de Google debemos activar el Drive API.
- Si utilizas App Inventor es muy conveniente realizar este ejemplo que trata de subir un archivo al Google Drive y después actualizarlo. Una vez que te funcione significará que tienes bien puesto los permisos y puedes adaptarlo al ESP32-CAM.
https://community.appinventor.mit.edu/t/update-replace-existing-file-on-google-drive-keeping-the-same-file-id/34278 de TIMAI2 (Power User)
https://support.wix.com/en/article/setting-permissions-for-google-drive-files-and-folders
- Empecemos con nuestro proyecto:
- Nos vamos a basar en este tutorial:
https://www.gsampallo.com/2019/10/13/esp32-cam-subir-fotos-a-google-drive/
- Puedes bajar los archivos desde su git o desde este mirror: esp32cam-gdrive.zip
- Lo descomprime y debes obtener una carpeta de esta forma:
- Observa que el nombre del archivo y de la carpeta debe ser esp32cam-gdrive
- Observa también que las librerías de Base64 y el archivo esp32cam-gdrive.ino están en la misma carpeta, si estuvieran en otra carpeta daría el error:
error: "'base64_enc_len' was not declared in this scope"
- El contenido del archivo codigo.gs será el que subiremos al Script de Google:
- Recuerda que cada vez que modifiques este código del Script debes crear un Nuevo Project version., si no establece el Nuevo, no actualizará los cambios de este Script.
- El código de ESP32.
- Este código creará la carpeta ESP32-CAM y subirá las capturas de fotos cada minuto.
|
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include "soc/soc.h"
#include "soc/rtc_cntl_reg.h"
#include "Base64.h"
#include "esp_camera.h"
const char* ssid = "SSID"; //your network SSID
const char* password = "PASSWORD"; //your network password
const char* myDomain = "script.google.com";
String myScript = "/macros/s/XXXXXXXXXXXXXXXXXXXXXX/exec"; //Replace with your own url
String myFilename = "filename=ESP32-CAM.jpg";
String mimeType = "&mimetype=image/jpeg";
String myImage = "&data=";
int waitingTime = 30000; //Wait 30 seconds to google response.
#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
void setup()
{
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);
Serial.begin(115200);
delay(10);
WiFi.mode(WIFI_STA);
Serial.println("");
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(500);
}
Serial.println("");
Serial.println("STAIP address: ");
Serial.println(WiFi.localIP());
Serial.println("");
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;
config.frame_size = FRAMESIZE_VGA; // UXGA|SXGA|XGA|SVGA|VGA|CIF|QVGA|HQVGA|QQVGA
config.jpeg_quality = 10;
config.fb_count = 1;
esp_err_t err = esp_camera_init(&config);
if (err != ESP_OK) {
Serial.printf("Camera init failed with error 0x%x", err);
delay(1000);
ESP.restart();
}
}
boolean enviar = true;
void loop() {
//if(enviar) {
saveCapturedImage();
enviar = false;
delay(60000);
//}
}
void saveCapturedImage() {
Serial.println("Connect to " + String(myDomain));
WiFiClientSecure client;
if (client.connect(myDomain, 443)) {
Serial.println("Connection successful");
camera_fb_t * fb = NULL;
fb = esp_camera_fb_get();
if(!fb) {
Serial.println("Camera capture failed");
delay(1000);
ESP.restart();
return;
}
char *input = (char *)fb->buf;
char output[base64_enc_len(3)];
String imageFile = "";
for (int i=0;i<fb->len;i++) {
base64_encode(output, (input++), 3);
if (i%3==0) imageFile += urlencode(String(output));
}
String Data = myFilename+mimeType+myImage;
esp_camera_fb_return(fb);
Serial.println("Send a captured image to Google Drive.");
client.println("POST " + myScript + " HTTP/1.1");
client.println("Host: " + String(myDomain));
client.println("Content-Length: " + String(Data.length()+imageFile.length()));
client.println("Content-Type: application/x-www-form-urlencoded");
client.println();
client.print(Data);
int Index;
for (Index = 0; Index < imageFile.length(); Index = Index+1000) {
client.print(imageFile.substring(Index, Index+1000));
}
Serial.println("Waiting for response.");
long int StartTime=millis();
while (!client.available()) {
Serial.print(".");
delay(100);
if ((StartTime+waitingTime) < millis()) {
Serial.println();
Serial.println("No response.");
//If you have no response, maybe need a greater value of waitingTime
break;
}
}
Serial.println();
while (client.available()) {
Serial.print(char(client.read()));
}
} else {
Serial.println("Connected to " + String(myDomain) + " failed.");
}
client.stop();
}
//https://github.com/zenmanenergy/ESP8266-Arduino-Examples/
String urlencode(String str)
{
String encodedString="";
char c;
char code0;
char code1;
char code2;
for (int i =0; i < str.length(); i++){
c=str.charAt(i);
if (c == ' '){
encodedString+= '+';
} else if (isalnum(c)){
encodedString+=c;
} else{
code1=(c & 0xf)+'0';
if ((c & 0xf) >9){
code1=(c & 0xf) - 10 + 'A';
}
c=(c>>4)&0xf;
code0=c+'0';
if (c > 9){
code0=c - 10 + 'A';
}
code2='\0';
encodedString+='%';
encodedString+=code0;
encodedString+=code1;
//encodedString+=code2;
}
yield();
}
return encodedString;
}
|
- Ahora entra en tu Google Drive.
- Observamos la carpeta ESP32-CAM y las imágenes subidas.
_____________________________________________________
- Actualizar el archivo de captura.
- Ahora en vez de crear un archivo cada vez que hace una captura, tendremos siempre el mismo nombre de archivo: capture.jpg, es decir se actualizará el archivo.
- Solo cambiamos el código del Script:
- Observa que el archivo siempre se llamará: capture.jpg
|
function doPost(e) {
var data = Utilities.base64Decode(e.parameters.data);
// var nombreArchivo = Utilities.formatDate(new Date(), "GMT-3", "yyyyMMdd_HHmmss")+".jpg";
var filename = "capture.jpg";
var blob = Utilities.newBlob(data, e.parameters.mimetype, filename);
var folder, folders = DriveApp.getFoldersByName("ESP32-CAM");
if (folders.hasNext()) {
folder = folders.next();
} else {
folder = DriveApp.createFolder("ESP32-CAM");
}
var existing = folder.getFilesByName(filename);
if (existing.hasNext()) {
var file = existing.next();
if (file.getName() == filename) {
fileID = file.getId();
// Drive.Files.update({title: filename, mimeType: mimetype}, fileID, blob);
Drive.Files.update({title: filename, mimeType: file.getMimeType()}, fileID, blob);
}
} else {
blob = Utilities.newBlob(data, e.parameters.mimetype, filename);
fileID = folder.createFile(blob).getId();
}
// var file = folder.createFile(blob);
return ContentService.createTextOutput("Completo.")
}
|
- Necesitaremos el código del archivo, pulsamos sobre él con el botón derecho del ratón y obtenemos su código.
- También debemos darle permiso a la carpeta ESP32-CAM para que Cualquier persona con el enlace, pueda entrar en ella.
- Ahora en App Inventor, ponemos un botón, un Reloj con un intervalo de 10000 y un componente imagen.
_______________________________________________________
6.- Captura foto y la guarda en la tarjeta SdCard.
- Cuando pulsamos el botón de Reset, se captura una foto y se guarda en la SdCard, después el módulo para a Deep Sleep.
- He modificado el código de https://randomnerdtutorials.com/esp32-cam-take-photo-save-microsd-card/
|
#include "esp_camera.h"
#include "Arduino.h"
#include "FS.h" // SD Card ESP32
#include "SD.h" // SD Card ESP32
#include "SPI.h"
#include "soc/soc.h" // Disable brownour problems
#include "soc/rtc_cntl_reg.h" // Disable brownour problems
#include "driver/rtc_io.h"
#include <EEPROM.h> // read and write from flash memory
// define the number of bytes you want to access
#define EEPROM_SIZE 1
RTC_DATA_ATTR int bootCount = 0;
// Pin definition for 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
int pictureNumber = 0;
void setup() {
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector
Serial.begin(115200);
Serial.setDebugOutput(true);
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;
pinMode(4, INPUT);
digitalWrite(4, LOW);
rtc_gpio_hold_dis(GPIO_NUM_4);
if(psramFound()){
config.frame_size = FRAMESIZE_UXGA; // FRAMESIZE_ + QVGA|CIF|VGA|SVGA|XGA|SXGA|UXGA
config.jpeg_quality = 10;
config.fb_count = 2;
} else {
config.frame_size = FRAMESIZE_SVGA;
config.jpeg_quality = 12;
config.fb_count = 1;
}
// Init Camera
esp_err_t err = esp_camera_init(&config);
if (err != ESP_OK) {
Serial.printf("Camera init failed with error 0x%x", err);
return;
}
//set the camera parameters
sensor_t * s = esp_camera_sensor_get();
s->set_contrast(s, 2); //min=-2, max=2
s->set_brightness(s, 2); //min=-2, max=2
s->set_saturation(s, 2); //min=-2, max=2
delay(100); //wait a little for settings to take effect
Serial.println("Starting SD Card");
delay(500);
SPI.begin(14, 2, 15, 13);
if(!SD.begin(13)){
Serial.println("SD Card Mount Failed");
return;
}
uint8_t cardType = SD.cardType();
if(cardType == CARD_NONE){
Serial.println("No SD Card attached");
return;
}
camera_fb_t * fb = NULL;
// Take Picture with Camera
fb = esp_camera_fb_get();
if(!fb) {
Serial.println("Camera capture failed");
Serial.println("Exiting now");
while(1); //wait here as something is not right
}
// initialize EEPROM with predefined size
EEPROM.begin(EEPROM_SIZE);
pictureNumber = EEPROM.read(0) + 1;
// Path where new picture will be saved in SD Card
String path = "/picture" + String(pictureNumber) +".jpg";
fs::FS &fs = SD;
Serial.printf("Picture file name: %s\n", path.c_str());
//create new file
File file = fs.open(path.c_str(), FILE_WRITE);
if(!file){
Serial.println("Failed to open file in writing mode");
Serial.println("Exiting now");
while(1); //wait here as something is not right
}
else {
file.write(fb->buf, fb->len); // payload (image), payload length
Serial.printf("Saved file to path: %s\n", path.c_str());
EEPROM.write(0, pictureNumber);
EEPROM.commit();
}
file.close();
esp_camera_fb_return(fb);
delay(1000);
// Turns off the ESP32-CAM white on-board LED (flash) connected to GPIO 4
pinMode(4, OUTPUT); //GPIO for LED flash
digitalWrite(4, LOW); //turn OFF flash LED
rtc_gpio_hold_en(GPIO_NUM_4); //make sure flash is held LOW in sleep
esp_sleep_enable_ext0_wakeup(GPIO_NUM_13, 0);
Serial.println("Going to sleep now");
delay(3000);
esp_deep_sleep_start();
Serial.println("Now in Deep Sleep Mode");
}
void loop() {
}
|
_______________________________________________________
7.- Tomar una foto mostrarla en web.
(No lo guarda en la SdCard, la guarda en la SPIFFS, la memoria interna.)
-
https://randomnerdtutorials.com/esp32-cam-take-photo-display-web-server/
AsyncTCP.zip
|
/*********
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 = "Nombre_de_tu_Red_Wifi";
const char* password = "Constraseña_Wifi";
// Create AsyncWebServer object on port 80
AsyncWebServer server(80);
boolean takeNewPhoto = false;
// Photo File Name to save in SPIFFS
#define FILE_PHOTO "/photo.jpg"
// 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=1">
<style>
body { text-align:center; }
.vert { margin-bottom: 10%; }
.hori{ margin-bottom: 0%; }
</style>
</head>
<body>
<div id="container">
<h2>ESP32-CAM Last Photo</h2>
<p>It might take more than 5 seconds to capture a photo.</p>
<p>
<button onclick="rotatePhoto();">ROTATE</button>
<button onclick="capturePhoto()">CAPTURE PHOTO</button>
<button onclick="location.reload();">REFRESH PAGE</button>
</p>
</div>
<div><img src="saved-photo" id="photo" width="70%"></div>
</body>
<script>
var deg = 0;
function capturePhoto() {
var xhr = new XMLHttpRequest();
xhr.open('GET', "/capture", true);
xhr.send();
}
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);
// Connect to Wi-Fi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi...");
}
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);
});
// Start server
server.begin();
}
void loop() {
if (takeNewPhoto) {
capturePhotoSaveSpiffs();
takeNewPhoto = 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 );
}
|
- Este código es similar al anterior, la diferencia es que además de mostrar la foto en Web, la guarda en la SdCard.
- Esta placa tiene la particularidad de que cada vez que lee la SdCard enciende el flash.
|
/*********
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>
#include "SD_MMC.h" // SdCard
// Replace with your network credentials
const char* ssid = "Nombre_de_tu_Red_Wifi";
const char* password = "Contraseña_Wifi";
int pictureNumber = 0;
// Create AsyncWebServer object on port 80
AsyncWebServer server(80);
boolean takeNewPhoto = false;
// Photo File Name to save in SPIFFS
#define FILE_PHOTO "/photo.jpg"
// 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=1">
<style>
body { text-align:center; }
.vert { margin-bottom: 10%; }
.hori{ margin-bottom: 0%; }
</style>
</head>
<body>
<div id="container">
<h2>ESP32-CAM Last Photo</h2>
<p>It might take more than 5 seconds to capture a photo.</p>
<p>
<button onclick="rotatePhoto();">ROTATE</button>
<button onclick="capturePhoto()">CAPTURE PHOTO</button>
<button onclick="location.reload();">REFRESH PAGE</button>
</p>
</div>
<div><img src="saved-photo" id="photo" width="70%"></div>
</body>
<script>
var deg = 0;
function capturePhoto() {
var xhr = new XMLHttpRequest();
xhr.open('GET', "/capture", true);
xhr.send();
}
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() {
// SdCard
//Serial.println("Starting SD Card");
if(!SD_MMC.begin()){
Serial.println("SD Card Mount Failed");
return;
}
uint8_t cardType = SD_MMC.cardType();
if(cardType == CARD_NONE){
Serial.println("No SD Card attached");
return;
}
// Serial port for debugging purposes
Serial.begin(115200);
// Connect to Wi-Fi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi...");
}
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);
// request->send(SD_MMC, FILE_PHOTO, "image/jpg", false);
});
// Start server
server.begin();
}
void loop() {
if (takeNewPhoto) {
capturePhotoSaveSpiffs();
takeNewPhoto = 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;
}
//////////////// Guarda SdCard ////////////////////////
pictureNumber = pictureNumber + 1;
// Path where new picture will be saved in SD Card
String path = "/picture" + String(pictureNumber) +".jpg";
fs::FS &fs = SD_MMC;
Serial.printf("Picture file name: %s\n", path.c_str());
File file2 = fs.open(path.c_str(), FILE_WRITE);
if(!file2){
Serial.println("Failed to open file in writing mode");
}
else {
file2.write(fb->buf, fb->len); // payload (image), payload length
Serial.printf("Saved file to path: %s\n", path.c_str());
//EEPROM.write(0, pictureNumber);
// EEPROM.commit();
}
file2.close();
/////////////////////////////////////////////////////////////////////////
// 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 );
}
|
_________________________________________________________
8.- Cargar el Sketch mediante Arduino UNO, sin utilizar el módulo conversor USB FTDI.
-
- En vez de cargar el código mediante el módulo FTDI que vimos al principio de este tutorial, podemos cargarlo con un Ardunio UNO.
- El Arduino solo lo utilizaremos para cargar el código, una vez cargado no es necesario, bueno... lo podemos seguir utilizando pero para suministrarle la alimentación a la tarjeta ESP32-CAM.
- Esto es importante, para que funcione el siguiente proceso de carga es necesario un IDE de Arduino menor que el 1.8.13. Si lo intentas con un IDE igual o mayor a ese, verás que no funciona el Programador.
- Así que puedes ir a la página de descarga de IDE de Arduino y bajar una versión menor de la 1.8.13
- En mi caso he utilizado el IDE 1.8.2 y configurado la Placa "ESP32 Wrover Module" con los parámetros que indico en la imagen.
- Observa que ahora el Programador es el "AVR ISP"
- Una vez que tenemos realizada las conexiones y preparado la configuración de la placa, volvemos a cargar el programa de ejemplo que vimos al principio de este tutorial:
- No olvides habilitar la CAMERA_MODEL_AI_THINKER y deshabilitar la WROVER...
// #define CAMERA_MODEL_WROVER_KIT
//#define CAMERA_MODEL_ESP_EYE
//#define CAMERA_MODEL_M5STACK_PSRAM
//#define CAMERA_MODEL_M5STACK_WIDE
#define CAMERA_MODEL_AI_THINKER
- Una vez cargado el Sketch, quitamos el cable IO0 - GND y reiniciamos el módulo. Podemos quitar todos los cables menos los de alimentación.
_________________________________________________________
9.- Añadir un botón de Flash a la página web de la cámara.
- CameraWebServer_Index.zip
- Estamos en el código de CameraWebServer.
- Vamos a añadir a la página web un botón de Flash para encender/apagar el LED4, es el LED de flash.
- En el archivo app_httpd.cpp añadimos las líneas siguientes:
static int8_t flash_enabled = 0;
...
else if(!strcmp(variable, "flash")) {
#define LED_BUILTIN 4
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, flash_enabled = atoi(value));
}
...
p+=sprintf(p, "\"flash\":%u,", flash_enabled);
- Ahora vamos al archivo camera_index.h, ahí tenemos la página web, pero observamos que está codificada en hexadecimal.
- Para decodificarla vamos a:
https://gchq.github.io/CyberChef/#recipe=From_Hex('Auto')Gunzip()
- Pegamos el trozo de código hexadecimal correspondiente a
//File: index_ov2640.html.gz, Size: 3770
#define index_ov2640_html_gz_len 3770
- Obtendremos la decodificación de la página.
- Esta sería la decodificación de la página:
https://gitlab.com/diy_bloke/esp32camerahtml/-/blob/master/index_html
- añadimos:
<div class="input-group" id="flash-group">
<label for="flash">Flash</label>
<div class="switch">
<input id="flash" type="checkbox" class="default-action">
<label class="slider" for="flash"></label>
</div>
</div>
- Volvemos a codificarlo:
https://gchq.github.io/CyberChef/#recipe=Gzip('Dynamic%20Huffman%20Coding','index.html.gz','',false)To_Hex('0x',0)Split('0x',',%200x')&input=Cg
- Observa la longitud, en mi caso: 22620, la dividimos entre 6
22620 / 6 = 3770
- Ese código hexadecimal lo pegamos en la parte correspondiente del archivo camera_index.h
_________________________________________________________
10.- Nombre y contraseña para ver la cámara.
- El siguente código vuelve a utilizar la librería esp32cam.zip la aplicación es similar a la que vimos con esa librería: ver vídeo en stream cuando escribimos:
http://192.168.1.7/stream
- la diferencia es que ahora para entrar en la página nos pide un nombre y una contraseña, que puedes ver en el código.
- Este código es de: https://github.com/BugerDread/esp32-mjpeg-ipcam/blob/master/esp32-mjpeg-ipcam.ino
|
#include <esp32cam.h>
#include <WebServer.h>
#include <WiFi.h>
const char* WIFI_SSID = "Red_WiFi";
const char* WIFI_PASS = "Clave_WiFi";
const char* streamUsername = "esp32";
const char* streamPassword = "pass32";
const char* streamRealm = "ESP32-CAM, please log in!";
const char* authFailResponse = "Sorry, login failed!";
const char* streamPath = "/stream";
static auto hiRes = esp32cam::Resolution::find(800, 600);
const uint8_t jpgqal = 80;
const uint8_t fps = 10; //sets minimum delay between frames, HW limits of ESP32 allows about 12fps @ 800x600
WebServer server(80);
void handleMjpeg()
{
if(!server.authenticate(streamUsername, streamPassword)) {
Serial.println(F("STREAM auth required, sending request"));
return server.requestAuthentication(BASIC_AUTH, streamRealm, authFailResponse);
}
if (!esp32cam::Camera.changeResolution(hiRes)) {
Serial.println(F("SET RESOLUTION FAILED"));
}
struct esp32cam::CameraClass::StreamMjpegConfig mjcfg;
mjcfg.frameTimeout = 10000;
mjcfg.minInterval = 1000 / fps;
mjcfg.maxFrames = -1;
Serial.println(String (F("STREAM BEGIN @ ")) + fps + F("fps (minInterval ") + mjcfg.minInterval + F("ms)") );
WiFiClient client = server.client();
auto startTime = millis();
int res = esp32cam::Camera.streamMjpeg(client, mjcfg);
if (res <= 0) {
Serial.printf("STREAM ERROR %d\n", res);
return;
}
auto duration = millis() - startTime;
Serial.printf("STREAM END %dfrm %0.2ffps\n", res, 1000.0 * res / duration);
}
void setup()
{
Serial.begin(115200);
Serial.println();
{
using namespace esp32cam;
Config cfg;
cfg.setPins(pins::AiThinker);
cfg.setResolution(hiRes);
cfg.setBufferCount(2);
cfg.setJpeg(jpgqal);
bool ok = Camera.begin(cfg);
Serial.println(ok ? F("CAMERA OK") : F("CAMERA FAIL"));
}
Serial.println(String(F("JPEG quality: ")) + jpgqal);
Serial.println(String(F("Framerate: ")) + fps);
Serial.print(F("Connecting to WiFi"));
WiFi.persistent(false);
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) {
Serial.print(F("."));
delay(500);
}
Serial.print(F("\nCONNECTED!\nhttp://"));
Serial.print(WiFi.localIP());
Serial.println(streamPath);
server.on(streamPath, handleMjpeg);
server.begin();
}
void loop()
{
server.handleClient();
}
|
_________________________________________________________
11.- Otra librería. Puede transmitir por HTTP y por RTSP.
diy-e14.zip
- Aquí tenemos otro código con otra librería para ver vídeo en stream. Solo un cliente a la vez.
- Es una variación de la librería esp32cam que hemos visto anteriormente.
- Funciona con el componente VisorWeb de App Inventor.
- También podemos verlo mediante el programa VLC, retransmitiendo en RTS.
- Aquí tienes el tutorial en inglés:
- https://www.instructables.com/9-RTSP-Video-Streamer-Using-the-ESP32-CAM-Board/
- https://github.com/bnbe-club/rtsp-video-streamer-diy-14
- El nombre de la red WiFi y su contraseña lo escribiremos en el archivo: wifikeys.h
- Este código puede transmitir por HTTP y por
RTSP (Real Time Streaming Protocol).
#ifdef ENABLE_WEBSERVER
WebServer server(80);
#endif
#ifdef ENABLE_RTSPSERVER
WiFiServer rtspServer(8554);
#endif
- Si descomentamos la línea 32, puede funcionar como Punto de Acceso. Nombre de red: devcam. Clave: 12345678
- Podemos ver el RTSP con el VLC.
_________________________________________________________
11B.- Modificación del código anterior. Puede transmitir por HTTP y por RTSP.
- Es el mismo código que hemos visto anteriormente, pero he quitado la parte de OLED y he incluido la información de los archivos wifikeys.h y pin.h en el .ino
- Además es necesario el directorio src con sus archivos internos.
- Con este código podemos transmitir en HTTP, RTSP y tenemos la posibilidad de trabajar como cliente WiFi o como Punto de Acceso.
CameraWebServer_2.zip
|
#include "src/OV2640.h"
#include <WiFi.h>
#include <WebServer.h>
#include <WiFiClient.h>
#include "src/SimStreamer.h"
#include "src/OV2640Streamer.h"
#include "src/CRtspSession.h"
//#define SOFTAP_MODE // Descomentar para Punto de Acceso.
#define ENABLE_WEBSERVER
#define ENABLE_RTSPSERVER
#define 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
OV2640 cam;
#ifdef ENABLE_WEBSERVER
WebServer server(80);
#endif
#ifdef ENABLE_RTSPSERVER
WiFiServer rtspServer(8554);
#endif
#ifdef SOFTAP_MODE
IPAddress apIP = IPAddress(192, 168, 1, 1);
#else
const char* ssid = "Red_WiFi";
const char* password = "Clave_WiFi";
#endif
#ifdef ENABLE_WEBSERVER
void handle_jpg_stream(void)
{
WiFiClient client = server.client();
String response = "HTTP/1.1 200 OK\r\n";
response += "Content-Type: multipart/x-mixed-replace; boundary=frame\r\n\r\n";
server.sendContent(response);
while (1)
{
cam.run();
if (!client.connected())
break;
response = "--frame\r\n";
response += "Content-Type: image/jpeg\r\n\r\n";
server.sendContent(response);
client.write((char *)cam.getfb(), cam.getSize());
server.sendContent("\r\n");
if (!client.connected())
break;
}
}
void handle_jpg(void)
{
WiFiClient client = server.client();
cam.run();
if (!client.connected())
{
return;
}
String response = "HTTP/1.1 200 OK\r\n";
response += "Content-disposition: inline; filename=capture.jpg\r\n";
response += "Content-type: image/jpeg\r\n\r\n";
server.sendContent(response);
client.write((char *)cam.getfb(), cam.getSize());
}
void handleNotFound()
{
String message = "Server is running!\n\n";
message += "URI: ";
message += server.uri();
message += "\nMethod: ";
message += (server.method() == HTTP_GET) ? "GET" : "POST";
message += "\nArguments: ";
message += server.args();
message += "\n";
server.send(200, "text/plain", message);
}
#endif
void setup(){
Serial.begin(115200);
//while (!Serial); //wait for serial connection.
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;
config.frame_size = FRAMESIZE_SVGA;
config.jpeg_quality = 12;
config.fb_count = 2;
#if defined(CAMERA_MODEL_ESP_EYE)
pinMode(13, INPUT_PULLUP);
pinMode(14, INPUT_PULLUP);
#endif
cam.init(config);
IPAddress ip;
#ifdef SOFTAP_MODE
const char *hostname = "Punto_Acceso";
// WiFi.hostname(hostname); // FIXME - find out why undefined
WiFi.mode(WIFI_AP);
bool result = WiFi.softAP(hostname, "12345678", 1, 0);
delay(2000);
WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));
if (!result)
{
Serial.println("AP Config failed.");
return;
}
else
{
Serial.println("AP Config Success.");
Serial.print("AP MAC: ");
Serial.println(WiFi.softAPmacAddress());
ip = WiFi.softAPIP();
Serial.print("Stream Link: rtsp://");
Serial.print(ip);
Serial.println(":8554/mjpeg/1");
}
#else
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED)
{
delay(500);
Serial.print(F("."));
}
ip = WiFi.localIP();
Serial.println(F("WiFi connected"));
Serial.println("");
Serial.println(ip);
Serial.print("Stream Link: rtsp://");
Serial.print(ip);
Serial.println(":8554/mjpeg/1");
#endif
#ifdef ENABLE_WEBSERVER
server.on("/", HTTP_GET, handle_jpg_stream);
server.on("/jpg", HTTP_GET, handle_jpg);
server.onNotFound(handleNotFound);
server.begin();
#endif
#ifdef ENABLE_RTSPSERVER
rtspServer.begin();
#endif
}
CStreamer *streamer;
CRtspSession *session;
WiFiClient client; // FIXME, support multiple clients
void loop()
{
#ifdef ENABLE_WEBSERVER
server.handleClient();
#endif
#ifdef ENABLE_RTSPSERVER
uint32_t msecPerFrame = 100;
static uint32_t lastimage = millis();
// If we have an active client connection, just service that until gone
// (FIXME - support multiple simultaneous clients)
if(session) {
session->handleRequests(0); // we don't use a timeout here,
// instead we send only if we have new enough frames
uint32_t now = millis();
if(now > lastimage + msecPerFrame || now < lastimage) { // handle clock rollover
session->broadcastCurrentFrame(now);
lastimage = now;
// check if we are overrunning our max frame rate
now = millis();
if(now > lastimage + msecPerFrame)
printf("warning exceeding max frame rate of %d ms\n", now - lastimage);
}
if(session->m_stopped) {
delete session;
delete streamer;
session = NULL;
streamer = NULL;
}
}
else {
client = rtspServer.accept();
if(client) {
//streamer = new SimStreamer(&client, true); // our streamer for UDP/TCP based RTP transport
streamer = new OV2640Streamer(&client, cam); // our streamer for UDP/TCP based RTP transport
session = new CRtspSession(&client, streamer); // our threads RTSP session and state
}
}
#endif
}
|
_________________________________________________________
11C.- Modificación del código anterior. Puede transmitir por HTTP.
- He quitado la parte de RTSP.
- Necesita el directorio src que puedes bajar en el apartado anterior.
- Puede funcionar como cliente de red WiFi o como Punto de Acceso.
- Stream: 192.168.1.7
- Captura: 192.168.1.7/jpg
|
#include "src/OV2640.h"
#include "src/SimStreamer.h"
#include "src/OV2640Streamer.h"
#include <WiFi.h>
#include <WebServer.h>
#include <WiFiClient.h>
//#define SOFTAP_MODE // Descomentar para Punto de Acceso.
#define ENABLE_WEBSERVER
#define 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
OV2640 cam;
WebServer server(80);
#ifdef SOFTAP_MODE
IPAddress apIP = IPAddress(192, 168, 1, 1);
#else
const char* ssid = "Red_Wifi";
const char* password = "Clave_WiFi";
#endif
void handle_jpg_stream(void){
WiFiClient client = server.client();
String response = "HTTP/1.1 200 OK\r\n";
response += "Content-Type: multipart/x-mixed-replace; boundary=frame\r\n\r\n";
server.sendContent(response);
while (1)
{
cam.run();
if (!client.connected())
break;
response = "--frame\r\n";
response += "Content-Type: image/jpeg\r\n\r\n";
server.sendContent(response);
client.write((char *)cam.getfb(), cam.getSize());
server.sendContent("\r\n");
if (!client.connected())
break;
}
}
void handle_jpg(void){
WiFiClient client = server.client();
cam.run();
if (!client.connected())
{
return;
}
String response = "HTTP/1.1 200 OK\r\n";
response += "Content-disposition: inline; filename=capture.jpg\r\n";
response += "Content-type: image/jpeg\r\n\r\n";
server.sendContent(response);
client.write((char *)cam.getfb(), cam.getSize());
}
void handleNotFound(){
String message = "Server is running!\n\n";
message += "URI: ";
message += server.uri();
message += "\nMethod: ";
message += (server.method() == HTTP_GET) ? "GET" : "POST";
message += "\nArguments: ";
message += server.args();
message += "\n";
server.send(200, "text/plain", message);
}
void setup(){
Serial.begin(115200);
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;
config.frame_size = FRAMESIZE_SVGA;
config.jpeg_quality = 12;
config.fb_count = 2;
cam.init(config);
IPAddress ip;
#ifdef SOFTAP_MODE
const char *hostname = "Punto_Acceso";
WiFi.mode(WIFI_AP);
bool result = WiFi.softAP(hostname, "12345678", 1, 0);
delay(2000);
WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));
if (!result)
{
Serial.println("AP Config failed.");
return;
}
else
{
Serial.println("AP Config Success.");
Serial.print("AP MAC: ");
Serial.println(WiFi.softAPmacAddress());
ip = WiFi.softAPIP();
}
#else
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED)
{
delay(500);
Serial.print(F("."));
}
ip = WiFi.localIP();
Serial.println(F("WiFi connected"));
Serial.println("");
Serial.println(ip);
#endif
server.on("/", HTTP_GET, handle_jpg_stream);
server.on("/jpg", HTTP_GET, handle_jpg);
server.onNotFound(handleNotFound);
server.begin();
}
CStreamer *streamer;
WiFiClient client;
void loop(){
server.handleClient();
}
|
_________________________________________________________
12.- Capta una imagen y la clasifica con TensorFlow.
esp32_tensor.zip
- Este código está basado en este tutorial: https://www.survivingwithandroid.com/esp32-cam-tensorflow-js/
- Se trata de fotografiar una imagen con la cámara, pulsar un botón y nos dirá qué objeto hemos fotografiado. Comete errores.
- En el archivo page.h debemos poner la dirección del servidor web creado, en mi caso 192.168.1.7.
- Observa que el Serial.begin está a 9600
- Debe estar en internet ya que se comunica con:
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs"> </script>
<script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/mobilenet"> </script>
- Este es un ejemplo de la citada página web, fotografía un balón de fútbol y muestra qué objeto puede ser.
_________________________________________________________
13.- Servidor Web. La página web se encuentra en su SdCard.
- Este código es una modificación del que vimos en
130.- Servidor web. Página web. SdCard. Memoria interna SPIFFS.
- He realizado varios cambios.
- Debes tener en la SdCard un archivo html, por ejemplo index.htm o bien poner directamente imágenes.
- Copia el contenido de SdRoot en una SdCard, ahí está la web de ejemplo.
https://github.com/espressif/arduino-esp32/tree/master/libraries/WebServer/examples/SDWebServer
SdRoot.zip
|
#include <WiFi.h>
#include <WiFiClient.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include <SPI.h>
#include <SD.h>
#include "SD_MMC.h" // SdCard. // MODIFICADO.
// He cambiado SD. por SD_MMC
#define DBG_OUTPUT_PORT Serial
const char* ssid = "Red_Wifi";
const char* password = "Contraseña";
const char* host = "esp32sd";
WebServer server(80);
static bool hasSD = false;
File uploadFile;
void returnOK() {
server.send(200, "text/plain", "");
}
void returnFail(String msg) {
server.send(500, "text/plain", msg + "\r\n");
}
bool loadFromSdCard(String path) {
String dataType = "text/plain";
if (path.endsWith("/")) {
path += "index.htm";
}
if (path.endsWith(".src")) {
path = path.substring(0, path.lastIndexOf("."));
} else if (path.endsWith(".htm")) {
dataType = "text/html";
} else if (path.endsWith(".css")) {
dataType = "text/css";
} else if (path.endsWith(".js")) {
dataType = "application/javascript";
} else if (path.endsWith(".png")) {
dataType = "image/png";
} else if (path.endsWith(".gif")) {
dataType = "image/gif";
} else if (path.endsWith(".jpg")) {
dataType = "image/jpeg";
} else if (path.endsWith(".ico")) {
dataType = "image/x-icon";
} else if (path.endsWith(".xml")) {
dataType = "text/xml";
} else if (path.endsWith(".pdf")) {
dataType = "application/pdf";
} else if (path.endsWith(".zip")) {
dataType = "application/zip";
}
File dataFile = SD_MMC.open(path.c_str());
if (dataFile.isDirectory()) {
path += "/index.htm";
dataType = "text/html";
dataFile = SD_MMC.open(path.c_str());
}
if (!dataFile) {
return false;
}
if (server.hasArg("download")) {
dataType = "application/octet-stream";
}
if (server.streamFile(dataFile, dataType) != dataFile.size()) {
DBG_OUTPUT_PORT.println("Sent less data than expected!");
}
dataFile.close();
return true;
}
void handleFileUpload() {
if (server.uri() != "/edit") {
return;
}
HTTPUpload& upload = server.upload();
if (upload.status == UPLOAD_FILE_START) {
if (SD_MMC.exists((char *)upload.filename.c_str())) {
SD_MMC.remove((char *)upload.filename.c_str());
}
uploadFile = SD_MMC.open(upload.filename.c_str(), FILE_WRITE);
DBG_OUTPUT_PORT.print("Upload: START, filename: "); DBG_OUTPUT_PORT.println(upload.filename);
} else if (upload.status == UPLOAD_FILE_WRITE) {
if (uploadFile) {
uploadFile.write(upload.buf, upload.currentSize);
}
DBG_OUTPUT_PORT.print("Upload: WRITE, Bytes: "); DBG_OUTPUT_PORT.println(upload.currentSize);
} else if (upload.status == UPLOAD_FILE_END) {
if (uploadFile) {
uploadFile.close();
}
DBG_OUTPUT_PORT.print("Upload: END, Size: "); DBG_OUTPUT_PORT.println(upload.totalSize);
}
}
void deleteRecursive(String path) {
File file = SD_MMC.open((char *)path.c_str());
if (!file.isDirectory()) {
file.close();
SD_MMC.remove((char *)path.c_str());
return;
}
file.rewindDirectory();
while (true) {
File entry = file.openNextFile();
if (!entry) {
break;
}
String entryPath = path + "/" + entry.name();
if (entry.isDirectory()) {
entry.close();
deleteRecursive(entryPath);
} else {
entry.close();
SD.remove((char *)entryPath.c_str());
}
yield();
}
SD.rmdir((char *)path.c_str());
file.close();
}
void handleDelete() {
if (server.args() == 0) {
return returnFail("BAD ARGS");
}
String path = server.arg(0);
if (path == "/" || !SD_MMC.exists((char *)path.c_str())) {
returnFail("BAD PATH");
return;
}
deleteRecursive(path);
returnOK();
}
void handleCreate() {
if (server.args() == 0) {
return returnFail("BAD ARGS");
}
String path = server.arg(0);
if (path == "/" || SD_MMC.exists((char *)path.c_str())) {
returnFail("BAD PATH");
return;
}
if (path.indexOf('.') > 0) {
File file = SD_MMC.open((char *)path.c_str(), FILE_WRITE);
if (file) {
file.write(0);
file.close();
}
} else {
SD.mkdir((char *)path.c_str());
}
returnOK();
}
void printDirectory() {
if (!server.hasArg("dir")) {
return returnFail("BAD ARGS");
}
String path = server.arg("dir");
if (path != "/" && !SD_MMC.exists((char *)path.c_str())) {
return returnFail("BAD PATH");
}
File dir = SD_MMC.open((char *)path.c_str());
path = String();
if (!dir.isDirectory()) {
dir.close();
return returnFail("NOT DIR");
}
dir.rewindDirectory();
server.setContentLength(CONTENT_LENGTH_UNKNOWN);
server.send(200, "text/json", "");
WiFiClient client = server.client();
server.sendContent("[");
for (int cnt = 0; true; ++cnt) {
File entry = dir.openNextFile();
if (!entry) {
break;
}
String output;
if (cnt > 0) {
output = ',';
}
output += "{\"type\":\"";
output += (entry.isDirectory()) ? "dir" : "file";
output += "\",\"name\":\"";
output += entry.name();
output += "\"";
output += "}";
server.sendContent(output);
entry.close();
}
server.sendContent("]");
dir.close();
}
void handleNotFound() {
if (hasSD && loadFromSdCard(server.uri())) {
return;
}
String message = "SDCARD Not Detected\n\n";
message += "URI: ";
message += server.uri();
message += "\nMethod: ";
message += (server.method() == HTTP_GET) ? "GET" : "POST";
message += "\nArguments: ";
message += server.args();
message += "\n";
for (uint8_t i = 0; i < server.args(); i++) {
message += " NAME:" + server.argName(i) + "\n VALUE:" + server.arg(i) + "\n";
}
server.send(404, "text/plain", message);
DBG_OUTPUT_PORT.print(message);
}
void setup(void) {
// SdCard ///////////////////////////// // MODIFICADO.
//Serial.println("Starting SD Card");
if(!SD_MMC.begin()){
Serial.println("SD Card Mount Failed");
return;
}
uint8_t cardType = SD_MMC.cardType();
if(cardType == CARD_NONE){
Serial.println("No SD Card attached");
return;
}
/////////////////////////////////////////////////
DBG_OUTPUT_PORT.begin(115200);
DBG_OUTPUT_PORT.setDebugOutput(true);
DBG_OUTPUT_PORT.print("\n");
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
DBG_OUTPUT_PORT.print("Connecting to ");
DBG_OUTPUT_PORT.println(ssid);
// Wait for connection
uint8_t i = 0;
while (WiFi.status() != WL_CONNECTED && i++ < 20) {//wait 10 seconds
delay(500);
}
if (i == 21) {
DBG_OUTPUT_PORT.print("Could not connect to");
DBG_OUTPUT_PORT.println(ssid);
while (1) {
delay(500);
}
}
DBG_OUTPUT_PORT.print("Connected! IP address: ");
DBG_OUTPUT_PORT.println(WiFi.localIP());
if (MDNS.begin(host)) {
MDNS.addService("http", "tcp", 80);
DBG_OUTPUT_PORT.println("MDNS responder started");
DBG_OUTPUT_PORT.print("You can now connect to http://");
DBG_OUTPUT_PORT.print(host);
DBG_OUTPUT_PORT.println(".local");
}
server.on("/list", HTTP_GET, printDirectory);
server.on("/edit", HTTP_DELETE, handleDelete);
server.on("/edit", HTTP_PUT, handleCreate);
server.on("/edit", HTTP_POST, []() {
returnOK();
}, handleFileUpload);
server.onNotFound(handleNotFound);
server.begin();
DBG_OUTPUT_PORT.println("HTTP server started");
// if (SD_MMC.begin(SS)) { // MODIFICADO.
// DBG_OUTPUT_PORT.println("SD Card initialized.");
hasSD = true;
//}
}
void loop(void) {
server.handleClient();
}
|
_________________________________________________________
14.- Servidor Web en el ESP32. No está adaptado a ESP32-CAM. Archivos en SdCard y SPIFFS.
http://www.iotsharing.com/2019/07/how-to-turn-esp-with-sdcard-or-spiffs-a-web-file-server.html
- Conectamos el lector de SdCard (este tutorial es del ESP32 sin cam, por eso pone lo de conectar el lector SdCard.)
- Sube archivos que no tienen extensión.
- En el ESP32-CAM, parece que las conexiones de la SdCard son
IO14 - CLK
IO15 - CS
IO13 - MOSI (SDA)
IO12 - MISO
D2 - -
D3 SS gpio13
CMD MOSI gpio15
VSS GND gnd
VDD 3.3V 3.3v
CLK SCK gpio14
VSS GND gnd
D0 MISO gpio2
D1 - gpio4 + LED flash also :(
FLASHLED gpio4
led1 gpio33 (mini smd ledje below ESP32-controler)
SD card socket : pin 9 is SD ( = CARD DETECTION , is a card inserted ? )
|
#include <WiFiClient.h>
#include <ESP32WebServer.h>
#include <WiFi.h>
#include <ESPmDNS.h>
#include <SPI.h>
#include <mySD.h>
String serverIndex = "<script src='https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js'></script>"
"<form method='POST' action='#' enctype='multipart/form-data' id='upload_form'>"
"<input type='file' name='update'>"
"<input type='submit' value='Upload'>"
"</form>"
"<div id='prg'>progress: 0%</div>"
"<script>"
"$('form').submit(function(e){"
"e.preventDefault();"
"var form = $('#upload_form')[0];"
"var data = new FormData(form);"
" $.ajax({"
"url: '/update',"
"type: 'POST',"
"data: data,"
"contentType: false,"
"processData:false,"
"xhr: function() {"
"var xhr = new window.XMLHttpRequest();"
"xhr.upload.addEventListener('progress', function(evt) {"
"if (evt.lengthComputable) {"
"var per = evt.loaded / evt.total;"
"$('#prg').html('progress: ' + Math.round(per*100) + '%');"
"}"
"}, false);"
"return xhr;"
"},"
"success:function(d, s) {"
"console.log('success!')"
"},"
"error: function (a, b, c) {"
"}"
"});"
"});"
"</script>";
const char* ssid = "Red_Wifi";
const char* password = "Contraseña";
ESP32WebServer server(80);
File root;
bool opened = false;
String printDirectory(File dir, int numTabs) {
String response = "";
dir.rewindDirectory();
while(true) {
File entry = dir.openNextFile();
if (! entry) {
// no more files
//Serial.println("**nomorefiles**");
break;
}
for (uint8_t i=0; i<numTabs; i++) {
Serial.print('\t'); // we'll have a nice indentation
}
// Recurse for directories, otherwise print the file size
if (entry.isDirectory()) {
printDirectory(entry, numTabs+1);
} else {
response += String("<a href='") + String(entry.name()) + String("'>") + String(entry.name()) + String("</a>") + String("</br>");
}
entry.close();
}
return String("List files:</br>") + response + String("</br></br> Upload file:") + serverIndex;
}
void handleRoot() {
root = SD.open("/");
String res = printDirectory(root, 0);
server.send(200, "text/html", res);
}
bool loadFromSDCARD(String path){
path.toLowerCase();
String dataType = "text/plain";
if(path.endsWith("/")) path += "index.htm";
if(path.endsWith(".src")) path = path.substring(0, path.lastIndexOf("."));
else if(path.endsWith(".jpg")) dataType = "image/jpeg";
else if(path.endsWith(".txt")) dataType = "text/plain";
else if(path.endsWith(".zip")) dataType = "application/zip";
Serial.println(dataType);
File dataFile = SD.open(path.c_str());
if (!dataFile)
return false;
if (server.streamFile(dataFile, dataType) != dataFile.size()) {
Serial.println("Sent less data than expected!");
}
dataFile.close();
return true;
}
void handleNotFound(){
if(loadFromSDCARD(server.uri())) return;
String message = "SDCARD Not Detected\n\n";
message += "URI: ";
message += server.uri();
message += "\nMethod: ";
message += (server.method() == HTTP_GET)?"GET":"POST";
message += "\nArguments: ";
message += server.args();
message += "\n";
for (uint8_t i=0; i<server.args(); i++){
message += " NAME:"+server.argName(i) + "\n VALUE:" + server.arg(i) + "\n";
}
server.send(404, "text/plain", message);
Serial.println(message);
}
void setup(void){
Serial.begin(115200);
WiFi.begin(ssid, password);
Serial.println("");
// Wait for connection
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.print("Connected to ");
Serial.println(ssid);
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
//use IP or iotsharing.local to access webserver
if (MDNS.begin("iotsharing")) {
Serial.println("MDNS responder started");
}
// if (!SD.begin(26, 14, 13, 27)) {
// if (!SD.begin(5, 23, 19, 18)) {
if (!SD.begin()) {
Serial.println("initialization failed!");
return;
}
Serial.println("initialization done.");
//handle uri
server.on("/", handleRoot);
server.onNotFound(handleNotFound);
/*handling uploading file */
server.on("/update", HTTP_POST, [](){
server.sendHeader("Connection", "close");
},[](){
HTTPUpload& upload = server.upload();
if(opened == false){
opened = true;
root = SD.open((String("/") + upload.filename).c_str(), FILE_WRITE);
if(!root){
Serial.println("- failed to open file for writing");
return;
}
}
if(upload.status == UPLOAD_FILE_WRITE){
if(root.write(upload.buf, upload.currentSize) != upload.currentSize){
Serial.println("- failed to write");
return;
}
} else if(upload.status == UPLOAD_FILE_END){
root.close();
Serial.println("UPLOAD_FILE_END");
opened = false;
}
});
server.begin();
Serial.println("HTTP server started");
}
void loop(void){
server.handleClient();
}
|
_____________________________________________________________
15.- Proyecto. Carrito con Cam y servo.
esp32cam_car.zip
- En este sitio podemos ver el proyecto de un carrito movido por dos motores y controlados por WiFi. El carrito lleva una cámara y podremos ir viendo por donde va. No utiliza App Inventor, todo se realiza desde página web.
https://www.hackster.io/KDPA/esp32-cam-video-surveillance-robot-a22367#
_______________________________________
- ESP32 BLE
https://gist.github.com/youjunjer
_________________________________________________________
16.- Leer, Escribir en la SdCard del ESP32-Cam.
- Esto es un ejemplo de SdCard del ESP32-CAM, no utiliza Wifi.
|
// This post referred to this git. I just trimmed cam and wifi part.
// https://github.com/v12345vtm/CameraWebserver2SD/blob/master/CameraWebserver2SD/CameraWebserver2SD.ino
#include "FS.h"
#include "SD_MMC.h"
//List dir in SD card
void listDir(fs::FS &fs, const char * dirname, uint8_t levels){
Serial.printf("Listing directory: %s\n", dirname);
File root = fs.open(dirname);
if(!root){
Serial.println("Failed to open directory");
return;
}
if(!root.isDirectory()){
Serial.println("Not a directory");
return;
}
File file = root.openNextFile();
while(file){
if(file.isDirectory()){
Serial.print(" DIR : ");
Serial.println(file.name());
if(levels){
listDir(fs, file.name(), levels -1);
}
} else {
Serial.print(" FILE: ");
Serial.print(file.name());
Serial.print(" SIZE: ");
Serial.println(file.size());
}
file = root.openNextFile();
}
}
//Create a dir in SD card
void createDir(fs::FS &fs, const char * path){
Serial.printf("Creating Dir: %s\n", path);
if(fs.mkdir(path)){
Serial.println("Dir created");
} else {
Serial.println("mkdir failed");
}
}
//delete a dir in SD card
void removeDir(fs::FS &fs, const char * path){
Serial.printf("Removing Dir: %s\n", path);
if(fs.rmdir(path)){
Serial.println("Dir removed");
} else {
Serial.println("rmdir failed");
}
}
//Read a file in SD card
void readFile(fs::FS &fs, const char * path){
Serial.printf("Reading file: %s\n", path);
File file = fs.open(path);
if(!file){
Serial.println("Failed to open file for reading");
return;
}
Serial.print("Read from file: ");
while(file.available()){
Serial.write(file.read());
}
}
//Write a file in SD card
void writeFile(fs::FS &fs, const char * path, const char * message){
Serial.printf("Writing file: %s\n", path);
File file = fs.open(path, FILE_WRITE);
if(!file){
Serial.println("Failed to open file for writing");
return;
}
//fwrite(fb->buf, 1, fb->len, file);
if(file.print(message)){
Serial.println("File written");
} else {
Serial.println("Write failed");
}
}
//Append to the end of file in SD card
void appendFile(fs::FS &fs, const char * path, const char * message){
Serial.printf("Appending to file: %s\n", path);
File file = fs.open(path, FILE_APPEND);
if(!file){
Serial.println("Failed to open file for appending");
return;
}
if(file.print(message)){
Serial.println("Message appended");
} else {
Serial.println("Append failed");
}
}
//Rename a file in SD card
void renameFile(fs::FS &fs, const char * path1, const char * path2){
Serial.printf("Renaming file %s to %s\n", path1, path2);
if (fs.rename(path1, path2)) {
Serial.println("File renamed");
} else {
Serial.println("Rename failed");
}
}
//Delete a file in SD card
void deleteFile(fs::FS &fs, const char * path){
Serial.printf("Deleting file: %s\n", path);
if(fs.remove(path)){
Serial.println("File deleted");
} else {
Serial.println("Delete failed");
}
}
//Test read and write speed using test.txt file
void testFileIO(fs::FS &fs, const char * path){
File file = fs.open(path);
static uint8_t buf[512];
size_t len = 0;
uint32_t start = millis();
uint32_t end = start;
if(file){
len = file.size();
size_t flen = len;
start = millis();
while(len){
size_t toRead = len;
if(toRead > 512){
toRead = 512;
}
file.read(buf, toRead);
len -= toRead;
}
end = millis() - start;
Serial.printf("%u bytes read for %u ms\n", flen, end);
file.close();
} else {
Serial.println("Failed to open file for reading");
}
file = fs.open(path, FILE_WRITE);
if(!file){
Serial.println("Failed to open file for writing");
return;
}
size_t i;
start = millis();
for(i=0; i<2048; i++){
file.write(buf, 512);
}
end = millis() - start;
Serial.printf("%u bytes written for %u ms\n", 2048 * 512, end);
file.close();
}
void setup() {
Serial.begin(115200);
Serial.println("SDcard Testing....");
if(!SD_MMC.begin()){
Serial.println("Card Mount Failed");
return;
}
uint8_t cardType = SD_MMC.cardType();
if(cardType == CARD_NONE){
Serial.println("No SD_MMC card attached");
return;
}
Serial.print("SD_MMC Card Type: ");
if(cardType == CARD_MMC){
Serial.println("MMC");
} else if(cardType == CARD_SD){
Serial.println("SDSC");
} else if(cardType == CARD_SDHC){
Serial.println("SDHC");
} else {
Serial.println("UNKNOWN");
}
uint64_t cardSize = SD_MMC.cardSize() / (1024 * 1024);
Serial.printf("SD_MMC Card Size: %lluMB\n", cardSize);
listDir(SD_MMC, "/", 0);
createDir(SD_MMC, "/mydir");
listDir(SD_MMC, "/", 0);
removeDir(SD_MMC, "/mydir");
listDir(SD_MMC, "/", 2);
writeFile(SD_MMC, "/hello.txt", "Hello ");
appendFile(SD_MMC, "/hello.txt", "World!\n");
readFile(SD_MMC, "/hello.txt");
deleteFile(SD_MMC, "/foo.txt");
renameFile(SD_MMC, "/hello.txt", "/foo.txt");
readFile(SD_MMC, "/foo.txt");
testFileIO(SD_MMC, "/test.txt");
Serial.printf("Total space: %lluMB\n", SD_MMC.totalBytes() / (1024 * 1024));
Serial.printf("Used space: %lluMB\n", SD_MMC.usedBytes() / (1024 * 1024));
}
void loop() {
// put your main code here, to run repeatedly:
delay(10000);
}
|
- Si no te funciona el código anterior, carga el siguiente:
https://github.com/v12345vtm/ESP32-CAM-SD/blob/master/ESP32-CAM-SD.ino
- Observa que utiliza SD en vez de SD_MMC
|
/*
* Connect the SD card to the following pins:
*https://i.imgur.com/Pxa26nc.png
*edit by v12345vtm : https://www.youtube.com/user/v12345vtm please subscribe :)
*
*
* SD Card | ESP32 |esp32-cam
* D2 - -
* D3 SS gpio13
* CMD MOSI gpio15
* VSS GND gnd
* VDD 3.3V 3.3v
* CLK SCK gpio14
* VSS GND gnd
* D0 MISO gpio2
* D1 - -
*/
#include "FS.h"
#include "SD.h"
#include "SPI.h"
void listDir(fs::FS &fs, const char * dirname, uint8_t levels){
Serial.printf("Listing directory: %s\n", dirname);
File root = fs.open(dirname);
if(!root){
Serial.println("Failed to open directory");
return;
}
if(!root.isDirectory()){
Serial.println("Not a directory");
return;
}
File file = root.openNextFile();
while(file){
if(file.isDirectory()){
Serial.print(" DIR : ");
Serial.println(file.name());
if(levels){
listDir(fs, file.name(), levels -1);
}
} else {
Serial.print(" FILE: ");
Serial.print(file.name());
Serial.print(" SIZE: ");
Serial.println(file.size());
}
file = root.openNextFile();
}
}
void createDir(fs::FS &fs, const char * path){
Serial.printf("Creating Dir: %s\n", path);
if(fs.mkdir(path)){
Serial.println("Dir created");
} else {
Serial.println("mkdir failed");
}
}
void removeDir(fs::FS &fs, const char * path){
Serial.printf("Removing Dir: %s\n", path);
if(fs.rmdir(path)){
Serial.println("Dir removed");
} else {
Serial.println("rmdir failed");
}
}
void readFile(fs::FS &fs, const char * path){
Serial.printf("Reading file: %s\n", path);
File file = fs.open(path);
if(!file){
Serial.println("Failed to open file for reading");
return;
}
Serial.print("Read from file: ");
while(file.available()){
Serial.write(file.read());
}
file.close();
}
void writeFile(fs::FS &fs, const char * path, const char * message){
Serial.printf("Writing file: %s\n", path);
File file = fs.open(path, FILE_WRITE);
if(!file){
Serial.println("Failed to open file for writing");
return;
}
if(file.print(message)){
Serial.println("File written");
} else {
Serial.println("Write failed");
}
file.close();
}
void appendFile(fs::FS &fs, const char * path, const char * message){
Serial.printf("Appending to file: %s\n", path);
File file = fs.open(path, FILE_APPEND);
if(!file){
Serial.println("Failed to open file for appending");
return;
}
if(file.print(message)){
Serial.println("Message appended");
} else {
Serial.println("Append failed");
}
file.close();
}
void renameFile(fs::FS &fs, const char * path1, const char * path2){
Serial.printf("Renaming file %s to %s\n", path1, path2);
if (fs.rename(path1, path2)) {
Serial.println("File renamed");
} else {
Serial.println("Rename failed");
}
}
void deleteFile(fs::FS &fs, const char * path){
Serial.printf("Deleting file: %s\n", path);
if(fs.remove(path)){
Serial.println("File deleted");
} else {
Serial.println("Delete failed");
}
}
void testFileIO(fs::FS &fs, const char * path){
File file = fs.open(path);
static uint8_t buf[512];
size_t len = 0;
uint32_t start = millis();
uint32_t end = start;
if(file){
len = file.size();
size_t flen = len;
start = millis();
while(len){
size_t toRead = len;
if(toRead > 512){
toRead = 512;
}
file.read(buf, toRead);
len -= toRead;
}
end = millis() - start;
Serial.printf("%u bytes read for %u ms\n", flen, end);
file.close();
} else {
Serial.println("Failed to open file for reading");
}
file = fs.open(path, FILE_WRITE);
if(!file){
Serial.println("Failed to open file for writing");
return;
}
size_t i;
start = millis();
for(i=0; i<2048; i++){
file.write(buf, 512);
}
end = millis() - start;
Serial.printf("%u bytes written for %u ms\n", 2048 * 512, end);
file.close();
}
void setup(){
Serial.begin(115200);
SPI.begin(14, 2, 15, 13);
if(!SD.begin(13)){
Serial.println("Card Mount Failed");
return;
}
uint8_t cardType = SD.cardType();
if(cardType == CARD_NONE){
Serial.println("No SD card attached");
return;
}
Serial.print("SD Card Type: ");
if(cardType == CARD_MMC){
Serial.println("MMC");
} else if(cardType == CARD_SD){
Serial.println("SDSC");
} else if(cardType == CARD_SDHC){
Serial.println("SDHC");
} else {
Serial.println("UNKNOWN");
}
uint64_t cardSize = SD.cardSize() / (1024 * 1024);
Serial.printf("SD Card Size: %lluMB\n", cardSize);
listDir(SD, "/", 0);
createDir(SD, "/mydir");
listDir(SD, "/", 0);
removeDir(SD, "/mydir");
listDir(SD, "/", 2);
writeFile(SD, "/hello.txt", "Hello ");
appendFile(SD, "/hello.txt", "World!\n");
readFile(SD, "/hello.txt");
deleteFile(SD, "/foo.txt");
renameFile(SD, "/hello.txt", "/foo.txt");
readFile(SD, "/foo.txt");
testFileIO(SD, "/test.txt");
Serial.printf("Total space: %lluMB\n", SD.totalBytes() / (1024 * 1024));
Serial.printf("Used space: %lluMB\n", SD.usedBytes() / (1024 * 1024));
}
void loop(){
}
|
_________________________________________________________
17.- Tarjeta ESP32 con pantalla. MakePhyton ESP32 LCD color.
https://www.makerfabs.com/index.php?route=checkout/cart (18 €)
_______________________________
|