Subir fotos a un servidor FTP con ESP32-CAM

ESP32-CAM es un excelente dispositivo para utilizarlo para generar timelapse, tanto por su tamaño como por su precio.

Uno de los temas que se debe resolver al trabajar con los timelapse, es el almacenamiento de las fotografías que obtenemos. Si bien el ESP32-CAM tiene la posibilidad de almacenar las fotografías en una tarjeta de memoria, la conveniencia de que las suba automáticamente a un servidor FTP simplifica mucho la tarea.

En este articulo vemos como realizar esta tarea de manera extremadamente simple, gracias a la librería ESP32_FTPClient

La misma permite varias operaciones con el servidor FTP, entre las que se encuentra cambiar de carpeta, listar el contenido, subir y descargar archivos. Nos enfocaremos en subir un archivo, que sera la fotografía que hayamos obtenido.

Comenzaremos descargando e instalando la librería mencionada en el IDE de Arduino. Para ello una vez que se haya descargado el archivo, vamos al menú Programa – Incluir Liberia – Añadir Liberia .ZIP y seleccionamos el archivo que descargamos.

A continuación, se detalle como funciona:

#include "esp_camera.h"
#include "soc/soc.h"           // Disable brownour problems
#include "soc/rtc_cntl_reg.h"  // Disable brownour problems
#include "driver/rtc_io.h"
#include <WiFi.h>
#include <WiFiClient.h>   
#include "ESP32_FTPClient.h"

#include <NTPClient.h> //For request date and time
#include <WiFiUdp.h>
#include "time.h"

char* ftp_server = "SERVER";
char* ftp_user = "USER";
char* ftp_pass = "PASSWORD";
char* ftp_path = "/USER/;

const char* WIFI_SSID = "NETWORK";
const char* WIFI_PASS = "PASSWORD";

WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org", (-3600*3), 60000);

ESP32_FTPClient ftp (ftp_server,ftp_user,ftp_pass, 5000, 2);

Ademas de importar las librerías dedicadas a la cámara y a operación de las redes wifi, también necesitamos importar la nueva librería para trabajar con FTP.

NTPClient nos permite consultar la fecha y la hora, uno de los parámetros que le pasamos es la diferencia horaria, expresado en segundos; en mi caso son -3 horas por lo que se pasa como parámetro (-3600*3).

Incorpore dos funciones a la libreria NTPClient para que sea mas sencilla de tomar los datos de fecha/hora y poder utilizarlos en este caso, pueden descargar aquí.

Luego instanciamos las variables que contendrán los datos del servidor ftp, así como usuario/clave y la ruta donde subiremos el archivo.

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

camera_config_t config;

Se definen los GPIO que seran utilizados para la cámara y la variable config, que utilizaremos para configurar los parametros de la cámara.

void setup() {
  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector
 
  Serial.begin(115200);

  WiFi.begin(WIFI_SSID, WIFI_PASS);
  
  Serial.println("Connecting Wifi...");
  while (WiFi.status() != WL_CONNECTED) {
      delay(500);
      Serial.println("Connecting to WiFi..");
      
  }
  Serial.println("IP address: ");
      
  Serial.println(WiFi.localIP());

  initCamera();

  timeClient.begin();
  timeClient.update();

  ftp.OpenConnection();
}

En setup(), ademas de las clásicas rutinas se llama a initCamera(), que inicializara la cámara (se lo hace de esta manera para separar el código en bloques y que sea mas fácil la lectura); y luego se abre la conexión con el servidor FTP; esto podría hacerse en el momento de subir el archivo, pero se realiza en este punto para mejorar los tiempos entre cada imagen.

void initCamera() {
  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;//FRAMESIZE_UXGA; // FRAMESIZE_ + QVGA|CIF|VGA|SVGA|XGA|SXGA|UXGA
    config.jpeg_quality = 10;
    config.fb_count = 2;
  } else {
    config.frame_size = FRAMESIZE_UXGA;
    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;
  }  
}

En la función initCamera() inicializamos la cámara con los parámetros establecidos. Podemos cambiar la resolución de las imágenes reemplazando el valor de:

config.frame_size = FRAMESIZE_UXGA;

Por otro tamaño (QVGA|CIF|VGA|SVGA|XGA|SXGA|UXGA):


config.frame_size = FRAMESIZE_VGA; //para bajar la resolución de las imágenes
void loop() {
  timeClient.update();
  takePhoto();
  delay(60000);
}

En la función loop() solo tenemos dos acciones, tomar la fotografía con takePhoto() y esperar por 60 segundos.

void takePhoto() {
   
  camera_fb_t * fb = NULL;
  
  // Take Picture with Camera
  fb = esp_camera_fb_get();  
  if(!fb) {
    Serial.println("Camera capture failed");
    return;
  }

  /*
   * Upload to ftp server
   */
  ftp.ChangeWorkDir(ftp_path);
  ftp.InitFile("Type I");
  
  String nombreArchivo = timeClient.getFullFormattedTimeForFile()+".jpg";
  Serial.println("Subiendo "+nombreArchivo);
  int str_len = nombreArchivo.length() + 1; 
 
  char char_array[str_len];
  nombreArchivo.toCharArray(char_array, str_len);
  
  ftp.NewFile(char_array);
  ftp.WriteData( fb->buf, fb->len );
  ftp.CloseFile();
  
  /*
   * Free buffer
   */
  esp_camera_fb_return(fb); 
}

En la función takePhoto() se realizan tres operaciones: primero se toma una fotografía y se mantiene en el buffer fb.

En estas cinco lineas se realiza la subida al servidor ftp; primero cambiamos el directorio al directorio de trabajo, especificado en la variable ftp_path.

timeClient.getFullFormattedTimeForFile() nos devuelve un string con la fecha y hora en formato: AAAAMMDD_HHMMSS, concatenamos la extensión .jpg y lo convertimos a un array de char, que es el tipo de datos que toma la función NewFile de ftp.

Escribimos el buffer y cerramos el archivo.

La ultima tarea es liberar el buffer.

Una vez cargado el programa, las imágenes comienzan a subir de inmediato:

ESP32-Cam: Subir fotos a Google Drive

En el articulo anterior sobre el ESP32-CAM vimos cuales son los primeros pasos para comenzar a utilizarlo y como obtener imágenes desde el dispositivo con un sencillo script en Python; en esta ocasión veremos como tomar fotos y subirlos a una carpeta de Google Drive de manera de que nos sirva de punto de partida para otras aplicaciones.

El primer paso consiste en utilizar el script de Google para construir un sencillo programa que reciba la imagen y la almacene dentro de una carpeta determinada, para ello debemos ir a Google Drive – Nuevo – Más – Google Apps Scripts

Se va a presentar un editor para que escribamos el código como el siguiente::

Reemplazaremos el contenido total por el siguiente Reem:

function doPost(e) {
  var data = Utilities.base64Decode(e.parameters.data);
  var nombreArchivo = Utilities.formatDate(new Date(), "GMT-3", "yyyyMMdd_HHmmss")+".jpg";
  var blob = Utilities.newBlob(data, e.parameters.mimetype, nombreArchivo );
  
  
  var folder, folders = DriveApp.getFoldersByName("ESP32-CAM");
  if (folders.hasNext()) {
    folder = folders.next();
  } else {
    folder = DriveApp.createFolder("ESP32-CAM");
  }
  var file = folder.createFile(blob); 
  
  return ContentService.createTextOutput("Completo.")
}

El programa lo que hace es recibir tres parámetros:
1. La data en si, que consiste en la imagen, codificada en base64
2. El tipo de dato que se envía, en este caso sera image/jpeg
3. También recibe el nombre del archivo, aunque no lo utilizamos porque tomamos la fecha y hora para darle el nombre, de manera que quede organizado.

El paso siguiente es guardar el script y publicarlo como aplicación web, esto ultimo lo hacemos desde Publicar – Implementar como aplicación web; debemos elegir que se ejecute como nuestro usuario y que cualquier persona incluso los anónimos tengan acceso a la aplicación; luego confirmamos las opciones que se nos presenta.

En la ultima opción Google nos mostrar una url donde estará publicada nuestra aplicación; debemos copiar esta url, la utilizaremos en el programa del ESP32-CAM.

La url provista tendra el siguiente formato:

https://script.google.com/macros/s/XXXXXXXXXXXXXX/exec

Donde las XXXXXXXXXXXXXX serán reemplazadas por una secuencia alfanumérica que identifica a la aplicación.

En este punto estamos listos para cargar el programa en el ESP32-CAM, crearemos un nuevo programa con el IDE de Arduino llamado ESP32-CAM_Drive, de manera que nos cree la carpeta donde esta el archivo ESP32-CAM_Drive .ino; en dicha carpeta copiaremos los archivos Base64.cpp y Base64.h.

Dentro del archivo ESP32-CAM_Drive.ino al comienzo debemos reemplazar tres parámetros importantes: el nombre y contraseña de la red WiFiy la url del script que creamos anteriormente.

const char* ssid     = "SSID";   //your network SSID
const char* password = "PASSWORD";   //your network password
String myScript = "/macros/s/XXXXXXXXXXXXXX/exec";    //Replace with your own url

La lineas anteriores debemos reemplazar el código por los propios.

Para cargar el programa puede repasar el articulo anterior o ver video. Luego que se cargue el programa, desconectamos el GND de IO0 y reiniciamos el ESP32, al cabo de unos instantes se conectara a la red inalambrica, obtendra la foto y la subira al Drive, si vemos en el Monitor serie veremos algo similar a esto:

El programa creara una carpeta llamada ESP32-CAM dentro de Google Drive y en la misma guardara las imagenes, si revisamos la carpeta tendremos las fotos que saco el dispositivo:

La foto del Nano Osciloscopio.

Pueden encontrar el repositorio en GitHub.