En este artículo te voy a enseñar cómo programar y/o actualizar una ESP32-CAM a través del aire, o mejor dicho, usando el WiFi y la red local en conjunto con PlatformIO y VSCode.
Gracias a esto solo vas a necesitar conectar el programador físicamente una sola vez y a partir de ahí (suponiendo que no olvidas cargar el código de actualización OTA en la ESP32-CAM) todo será a través de la red local.
Esto es muy práctico sobre todo cuando la ESP32-CAM está soldada, pues no se puede conectar fácilmente a su programador
Configurando platformio.ini
Para que podamos habilitar actualizaciones OTA en la ESP32-CAM es muy importante configurar las particiones para que sean como lo dice el archivo min_spiffs.csv:
board_build.partitions = min_spiffs.csv
Mismo que reparte el espacio así:
| Name | Type | SubType | Offset | Size | Flags |
|---|---|---|---|---|---|
| nvs | data | nvs | 0x9000 | 0x5000 | |
| otadata | data | ota | 0xe000 | 0x2000 | |
| app0 | app | ota_0 | 0x10000 | 0x1E0000 | |
| app1 | app | ota_1 | 0x1F0000 | 0x1E0000 | |
| spiffs | data | spiffs | 0x3D0000 | 0x20000 | |
| coredump | data | coredump | 0x3F0000 | 0x10000 |
Lo he encontrado en: https://github.com/espressif/arduino-esp32/blob/master/tools/partitions/min_spiffs.csv
Entonces para que quede claro, mi platformio.ini se ve así:
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[env:esp32cam]
platform = espressif32
board = esp32cam
framework = arduino
monitor_speed = 115200
;build_flags = -DCORE_DEBUG_LEVEL=5 -DDEBUG_ESP_SSL
monitor_rts = 0
monitor_dtr = 0
lib_deps=
adafruit/Adafruit GFX Library@^1.11.9
adafruit/Adafruit SSD1306@^2.5.9
adafruit/DHT sensor library
adafruit/Adafruit Unified Sensor
bblanchon/ArduinoJson
upload_protocol=espota
upload_port=192.168.1.72
board_build.partitions = min_spiffs.csv
upload_flags =
--auth=muchasletrasynumerostalvez
Lo importante es el board_build.partitions, el upload_port, auth
y upload_protocol.
Código de gestión de actualizaciones OTA
El código que configura OTA en la ESP32-CAM queda así:
void configurarOTA()
{
ArduinoOTA.setHostname(host_name);
ArduinoOTA.onStart([]()
{ Serial.println("OTA onStart"); });
ArduinoOTA.onEnd([]()
{ Serial.println("OTA onEnd"); });
ArduinoOTA.onError([](ota_error_t error)
{
Serial.printf("Error [%u]: ", error);
if (error == OTA_AUTH_ERROR)
{
Serial.println("OTA_AUTH_ERROR");
}
else if (error == OTA_BEGIN_ERROR)
{
Serial.println("OTA_BEGIN_ERROR");
}
else if (error == OTA_CONNECT_ERROR)
{
Serial.println("OTA_CONNECT_ERROR");
}
else if (error == OTA_RECEIVE_ERROR)
{
Serial.println("OTA_RECEIVE_ERROR");
}
else if (error == OTA_END_ERROR)
{
Serial.println("OTA_END_ERROR");
} });
ArduinoOTA.begin();
}
Lo he encerrado en la función configurarOTA. Basta con invocar a esa función
en el setup para que quede configurada, y además de eso
debemos estar pendientes de cualquier actualización OTA
en el loop (o en un task de FreeRTOS) en donde
debemos invocar a ArduinoOTA.handle()
void loop()
{
ArduinoOTA.handle();
}
Y listo, mientras nunca remuevas la llamada a configurarOTA del setup
ni olvides invocar a ArduinoOTA.handle puedes poner cualquier otro código que
consideres necesario.
Revirtiendo OTA
Si quieres deshabilitar solo remueve upload_protocol, upload_port
y (ya que no necesitamos OTA) board_build.partitions
No olvides incluir las actualizaciones OTA en el nuevo código
Esto puede parecer obvio pero lo dejo claro: cada vez que actualices usando actualizaciones OTA con ESP32 CAM no te olvides de colocar el propio código que gestiona dichas actualizaciones.
Explicado con otras palabras:
La primera vez vas a usar un cable físico y el programador para cargar el código normal de tu tarjeta (el que lee sensores, crea servidores o lo que sea que hagas con tu esp32-cam) pero aparte de eso debes colocar en ese mismo código la gestión de actualizaciones OTA; eso va a permitir que a partir de ahí ya no necesites cable.
Pero cada vez que actualices (o sea, cuando cambies tu código normal) no debes olvidar colocar también el código que gestiona actualizaciones OTA
Básicamente nunca remuevas el código de gestión OTA por más que hagas actualizaciones en otros módulos.
Tampoco te olvides de configurar las credenciales de tu red WiFi si es que han cambiado. Cuando a mí me pasa eso simplemente hago un punto de acceso con mi teléfono con las credenciales de la red antigua.
Protegiendo con contraseña
En mi caso específico no uso contraseña (aunque mi platformio.ini
tiene una configurada), pero si tú quieres ponerle una configura
la opción --auth en platformio.ini y también
con ArduinoOTA.setPassword
Buscar IP de la ESP32-CAM para OTA
Debemos colocar la IP local de la ESP32-CAM en upload_port.
Esa IP puede cambiar pero podemos localizarla usando ping
y su hostname.
Definimos el hostname de la ESP32-CAM en el código.
Yo le he puesto esp32cam-ota:
const char *host_name = "esp32cam-ota";
Ahora hacemos lo siguiente con ping:
C:\Users\parzibyte>ping esp32cam-ota.local
Haciendo ping a esp32cam-ota.local [192.168.1.72] con 32 bytes de datos:
Respuesta desde 192.168.1.72: bytes=32 tiempo=60ms TTL=64
Respuesta desde 192.168.1.72: bytes=32 tiempo=5ms TTL=64
Estadísticas de ping para 192.168.1.72:
Paquetes: enviados = 2, recibidos = 2, perdidos = 0
(0% perdidos),
Tiempos aproximados de ida y vuelta en milisegundos:
Mínimo = 5ms, Máximo = 60ms, Media = 32ms
Básicamente es ping hostname_esp.local (no olvides el .local)
y con eso me ha dicho que la IP que tiene en mi caso es la 192.168.1.72
así que esa es la que he configurado en upload_port de platformio.ini
Ejemplo real
Para poner un ejemplo real y muy obvio vamos a usar un código muy simple que enciende y apaga el LED flash de la ESP32-CAM.
Recuerda que aunque el código sea muy corto siempre será necesario conectar al WiFi para poder cargar código “a través del aire”.
bool conectarWifi()
{
Serial.println("Conectando wifi...");
WiFi.mode(WIFI_STA);
WiFi.begin("Nombre de tu red", "Contraseña aquí");
WiFi.setTxPower(WIFI_POWER_19_5dBm);
unsigned long inicio = millis();
const unsigned long TIEMPO_LIMITE = 10000;
while (WiFi.status() != WL_CONNECTED)
{
delay(500);
Serial.print(".");
if (millis() - inicio > TIEMPO_LIMITE)
{
Serial.println("\nError: Tiempo de espera agotado.");
return false;
}
}
Serial.println("\nWiFi Conectado!");
Serial.print("IP: ");
Serial.println(WiFi.localIP());
if (!MDNS.begin(host_name))
{
Serial.println("Error al iniciar mDNS");
}
else
{
Serial.print("mDNS iniciado. Acceder via: http://");
Serial.print(host_name);
Serial.println(".local");
MDNS.addService("http", "tcp", 80);
}
return true;
}
También necesitamos mDNS para configurar y anunciarle a la red local nuestro hostname y así se nos pueda encontrar usando ping.
Nota importante hasta este punto no recuerdo si mDNS es estrictamente
necesario, ya que con ArduinoOTA.setHostname podría bastar, pero tengo miedo
de quitarlo en este momento. No hace daño dejarlo, pero lo aclaro.
Luego tenemos el parpadeo del LED:
void parpadear()
{
unsigned long ahora = millis();
if (ahora - ultimosMilisegundosFlash >= intervaloFlash)
{
ultimosMilisegundosFlash = ahora;
if (estadoFlash == LOW)
{
estadoFlash = HIGH;
}
else
{
estadoFlash = LOW;
}
digitalWrite(LED_FLASH, estadoFlash);
}
}
El código completo queda así:
#include <Arduino.h>
#include <WiFi.h>
#include <ArduinoOTA.h>
#include <WiFi.h>
#include <ESPmDNS.h>
#define LED_FLASH 4
const char *host_name = "esp32cam-ota";
// Ajustes propios del LED
unsigned long ultimosMilisegundosFlash = 0;
const long intervaloFlash = 500;
int estadoFlash = LOW;
void parpadear()
{
unsigned long ahora = millis();
if (ahora - ultimosMilisegundosFlash >= intervaloFlash)
{
ultimosMilisegundosFlash = ahora;
if (estadoFlash == LOW)
{
estadoFlash = HIGH;
}
else
{
estadoFlash = LOW;
}
digitalWrite(LED_FLASH, estadoFlash);
}
}
bool conectarWifi()
{
Serial.println("Conectando wifi...");
WiFi.mode(WIFI_STA);
WiFi.begin("Red", "Contraseña");
WiFi.setTxPower(WIFI_POWER_19_5dBm);
unsigned long inicio = millis();
const unsigned long TIEMPO_LIMITE = 10000;
while (WiFi.status() != WL_CONNECTED)
{
delay(500);
Serial.print(".");
if (millis() - inicio > TIEMPO_LIMITE)
{
Serial.println("\nError: Tiempo de espera agotado.");
return false;
}
}
Serial.println("\nWiFi Conectado!");
Serial.print("IP: ");
Serial.println(WiFi.localIP());
if (!MDNS.begin(host_name))
{
Serial.println("Error al iniciar mDNS");
}
else
{
Serial.print("mDNS iniciado. Acceder via: http://");
Serial.print(host_name);
Serial.println(".local");
MDNS.addService("http", "tcp", 80);
}
return true;
}
void configurarOTA()
{
ArduinoOTA.setHostname(host_name);
ArduinoOTA.onStart([]()
{ Serial.println("OTA onStart"); });
ArduinoOTA.onEnd([]()
{ Serial.println("OTA onEnd"); });
ArduinoOTA.onError([](ota_error_t error)
{
Serial.printf("Error [%u]: ", error);
if (error == OTA_AUTH_ERROR)
{
Serial.println("OTA_AUTH_ERROR");
}
else if (error == OTA_BEGIN_ERROR)
{
Serial.println("OTA_BEGIN_ERROR");
}
else if (error == OTA_CONNECT_ERROR)
{
Serial.println("OTA_CONNECT_ERROR");
}
else if (error == OTA_RECEIVE_ERROR)
{
Serial.println("OTA_RECEIVE_ERROR");
}
else if (error == OTA_END_ERROR)
{
Serial.println("OTA_END_ERROR");
} });
ArduinoOTA.begin();
}
void setup()
{
pinMode(LED_FLASH, OUTPUT);
Serial.begin(115200);
if (!conectarWifi())
{
ESP.restart();
}
configurarOTA();
}
void loop()
{
ArduinoOTA.handle();
parpadear();
}
Lo cargamos una vez con el programador. Y la siguiente vez si, por ejemplo, queremos cambiar el intervalo del parpadeo basta con volver a subir el código.
Otro ejemplo
Para mi proyecto de la OLED SSD1306 + DHT22 + PIR + Telegram + ESP32-CAM + Relevador
he usado actualizaciones OTA. Puse lo siguiente en el onProgress:
ArduinoOTA.onProgress([](unsigned int bytesTransferidos, unsigned int bytesTotales)
{
/*
100 % --> bytes totales
X --> bytesTransferidos
X =100*bytesTransferidos/bytesTotales
*/
double progresoComoPorcentaje = 100 * bytesTransferidos / bytesTotales;
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
// La barra completa vacía
// display.drawRect(0, 16, ANCHO_OLED, ALTO_OLED / 2, SSD1306_WHITE);
/*
100 % -> ANCHO_OLED
porcentaje -> x
barraRellena = porcentaje/ANCHO_OLED*100;
*/
int barraRellena = progresoComoPorcentaje * ANCHO_OLED / 100;
display.drawRect(0, 18, ANCHO_OLED, ALTO_OLED / 2, SSD1306_WHITE);
display.fillRect(0, 18, barraRellena, ALTO_OLED / 2, SSD1306_WHITE);
display.setTextSize(2);
display.setCursor((ANCHO_OLED / 2) - (6 * 12 / 2), 0);
display.printf("%6.2f%%", progresoComoPorcentaje);
display.display(); });
Y mira qué guapo se ve cuando recibe una actualización:

(Recuerda que en un vídeo se ven unas bandas de refresco de la OLED, por eso se ve esa diagonal negra).
Puedes ver el proyecto completo en: https://parzibyte.me/blog/posts/esp32-cam-pir-dht22-oled/