|     Inicio    |   |         |  |   FOROS      |  |      |      
   Elastix - VoIP B4A (Basic4Android) App inventor 2 PHP - MySQL
  Estación meteorológica B4J (Basic4Java) ADB Shell - Android Arduino
  Raspberry Pi Visual Basic Script (VBS) FireBase (BD autoactualizable) NodeMCU como Arduino
  AutoIt (Programación) Visual Basic Cosas de Windows Webs interesantes
Translate:
Búsqueda en este sitio:


.

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.

WifiCam.ino


#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.

WifiCam_LED4.ino


#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.

esp32cam-gdrive.ino


#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

codigo.gs


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/

Foto_SdCard.ino


#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

Foto_Web.ino


/*********
  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.

Foto_SdCard_Web.ino


/*********
  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

esp32_usuario_clave.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

CameraWebServer_2.ino


#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

CameraWebServer_3.ino


#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

Servidor_Web_SdCard.ino


#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 ? )   

Servidor_Web_SdCard.ino


#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.

SdCard.ino


// 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

SdCard.ino


/*
 * 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 €)

 

_______________________________

- Mi correo:
juana1991@yahoo.com
- KIO4.COM - Política de cookies. Textos e imágenes propiedad del autor:
© Juan A. Villalpando
No se permite la copia de información ni imágenes.
Usamos cookies propias y de terceros que entre otras cosas recogen datos sobre sus hábitos de navegación y realizan análisis de uso de nuestro sitio.
Si continúa navegando consideramos que acepta su uso. Acepto    Más información