iiOTESP32 Brasil
Modo de contingencia: las páginas públicas y las descargas siguen disponibles; el inicio de sesión, la comunidad y los datos del servidor pueden no estar disponibles.
Volver al catálogo de tutoriales
Ilustração: placa ESP32 recebendo dados de uma nuvem com um arquivo de firmware, ao lado de dois chips que representam duas partições, uma ativa e outra em atualização
Imagem ilustrativa gerada por IA
IntermedioOTALectura: 14 min

Actualización OTA (Over-The-Air) en el ESP32

Dos particiones, otadata y el estado pendiente de verificación: cómo actualizar el firmware por red sin convertir un error en una visita técnica.

Tecnologías:#ESP32#OTA#Update.h#esp_ota_ops#Rollback#Partições

Requisitos de hardware:

  • Placa ESP32 ou ESP32-S3 com pelo menos 4 MB de flash
  • Rede Wi-Fi de 2,4 GHz
  • Um servidor HTTP para hospedar o binário, mesmo que local

El problema que resuelve OTA y el que crea

Grabar firmware por cable es sencillo cuando la placa está en la mesa. Cuando está dentro de un cuadro eléctrico cerrado, en la tercera planta de una nave o detrás de una máquina en funcionamiento, cada actualización se convierte en una visita técnica.

OTA, actualización por el aire, resuelve eso. Pero crea un riesgo nuevo, y este es el punto que separa una implementación de tutorial de una que aceptarías en campo: si la actualización sale mal, el dispositivo puede quedar inaccesible exactamente por el motivo que llevó a usar OTA.

Este artículo trata las dos partes. Primero cómo actualizar. Después, y sobre todo, cómo asegurar que una actualización defectuosa no cueste la visita técnica que querías evitar.

Por qué deben existir dos particiones

Lo primero es entender que el ESP32 no sobrescribe el firmware que está ejecutando. Sería imposible: el código que está funcionando es el mismo que se estaría borrando.

La solución es tener dos espacios de aplicación. El firmware se ejecuta en uno, escribe el nuevo en el otro y solo después marca cuál debe utilizarse en el siguiente arranque. Una tercera partición pequeña, llamada otadata, guarda esa marca.

Esto tiene una consecuencia excelente: un corte de energía durante la actualización es seguro. Se escribe el slot inactivo, el slot activo permanece intacto y otadata sigue apuntando a él. La placa reinicia el firmware antiguo como si nada hubiera ocurrido.

También tiene una consecuencia incómoda: pierdes la mitad de la flash. En una placa de 4 MB cada slot queda en unos 1,9 MB. Es el precio y no hay forma de evitarlo.

La tabla de particiones

Esta es la tabla que usa el firmware del repetidor en una placa de 16 MB:

code.cpp
cpp
1# Name, Type, SubType, Offset, Size, Flags
2nvs, data, nvs, 0x9000, 0x5000,
3otadata, data, ota, 0xe000, 0x2000,
4app0, app, ota_0, 0x10000, 0x300000,
5app1, app, ota_1, , 0x300000,
6spiffs, data, spiffs, , 0x900000,

Lee los campos importantes. otadata ocupa 8 KB y registra qué slot es válido. app0 y app1 son los dos espacios de aplicación, de 3 MB cada uno. spiffs conserva los 9 MB restantes para configuración y archivos.

Observa que app1 y spiffs no declaran desplazamiento: el generador lo calcula a partir del tamaño anterior. Solo la primera partición necesita una dirección fija.

Si tu placa tiene 4 MB, utiliza la tabla preparada min_spiffs, que reserva unos 1,9 MB por slot. Es lo más cercano a un diseño razonable para OTA que ofrece Arduino.

Indica la tabla en platformio.ini:

code.cpp
cpp
1board_build.partitions = partitions.csv

Sin esto, el proyecto usa la tabla predeterminada de una sola partición y la actualización falla en el primer intento con un error de partición inexistente.

Push o pull: quién inicia la conversación

Existen dos modelos y la elección cambia la arquitectura.

Con push, el dispositivo expone un endpoint HTTP y tú le envías el binario. Es el modelo de ArduinoOTA y de las bibliotecas OTA web. Es sencillo de probar y funciona bien cuando tienes acceso a la red local del equipo.

Con pull, el dispositivo consulta periódicamente un servidor, compara la versión y descarga la novedad si existe. Requiere más infraestructura, pero es el único que escala: actualizar cincuenta gateways por push significa cincuenta operaciones manuales; por pull significa publicar un archivo.

Para un producto, pull. Para la mesa y el mantenimiento puntual, push. Este tutorial implementa pull, el caso más difícil y más útil.

La actualización en sí es la parte fácil

El código de grabación sorprende por su sencillez. La biblioteca Update, que ya viene en el core, se ocupa de lo esencial:

code.cpp
cpp
1bool aplicarAtualizacao(Stream &origem, size_t tamanho) {
2 if (!Update.begin(tamanho)) {
3 Serial.printf("Espaco insuficiente: %s\\n", Update.errorString());
4 return false;
5 }
6 size_t escritos = Update.writeStream(origem);
7 if (escritos != tamanho) {
8 Serial.printf("Escreveu %u de %u bytes\\n", (unsigned)escritos, (unsigned)tamanho);
9 Update.abort();
10 return false;
11 }
12 if (!Update.end(true)) {
13 Serial.printf("Falha ao finalizar: %s\\n", Update.errorString());
14 return false;
15 }
16 return true;
17}

Update.begin elige el slot inactivo y comprueba si cabe el binario. writeStream escribe directamente desde el flujo HTTP, sin un búfer intermedio del tamaño del firmware; sería imposible porque el binario es mayor que la RAM. end(true) valida la imagen y actualiza otadata.

Si falla cualquier etapa, nada cambia: otadata sigue apuntando al slot que está funcionando.

El firmware que arranca y no funciona

Aquí está el riesgo real y la razón de existir de este artículo.

El caso que todos imaginan es que la actualización falle a mitad. Es el caso fácil; ya vimos que es seguro por construcción.

El caso peligroso es otro: la actualización termina correctamente, el binario es válido, la placa reinicia con el firmware nuevo y entonces descubre un error que impide conectarse al Wi-Fi. O se bloquea en setup. O consume memoria hasta reiniciarse en bucle.

Desde el punto de vista de OTA todo salió bien. En la práctica, el dispositivo está inaccesible y has perdido precisamente la capacidad de enviar la corrección. Es la visita técnica que OTA quería evitar, agravada porque tú mismo la provocaste.

Ninguna comprobación de integridad del binario detecta esto. El archivo está intacto; lo incorrecto es su comportamiento.

Rollback: el estado pendiente de verificación

La solución es no confiar de inmediato en el firmware nuevo.

Con el rollback activado, una imagen recién grabada entra en un estado especial: pendiente de verificación. El bootloader la ejecuta con una condición: si la placa reinicia antes de que el firmware declare que funciona, el bootloader vuelve automáticamente a la imagen anterior.

El firmware nuevo debe demostrar que funciona:

code.cpp
cpp
1#include "esp_ota_ops.h"
2void confirmarSeSaudavel() {
3 const esp_partition_t *rodando = esp_ota_get_running_partition();
4 esp_ota_img_states_t estado;
5 if (esp_ota_get_state_partition(rodando, &estado) != ESP_OK) {
6 return;
7 }
8 if (estado != ESP_OTA_IMG_PENDING_VERIFY) {
9 return;
10 }
11 if (autodiagnosticoOk()) {
12 esp_ota_mark_app_valid_cancel_rollback();
13 Serial.println("[ota] imagem confirmada");
14 } else {
15 Serial.println("[ota] autodiagnostico falhou, voltando ao anterior");
16 esp_ota_mark_app_invalid_rollback_and_reboot();
17 }
18}

Qué cuenta como autodiagnóstico es una decisión de diseño y ahí está el valor. Conectarse al Wi-Fi en un máximo de sesenta segundos suele ser el mínimo. Si el dispositivo depende de MQTT o de un bus Modbus, exige también eso; no sirve confirmar una imagen que conecta a la red pero no habla con el equipo.

Llama a la confirmación después de que el diagnóstico haya tenido tiempo de ejecutarse, no al principio de setup. Confirmar demasiado pronto anula todo el mecanismo.

El detalle que invalida todo

El estado pendiente de verificación solo existe si el bootloader se compiló con rollback habilitado. Es una opción del bootloader, no del código:

code.cpp
cpp
1CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y

En ESP-IDF añádelo al sdkconfig y recompila. En Arduino el bootloader viene precompilado y la opción no está activada por defecto; llamar a las funciones anteriores no da error, pero tampoco protege nada, porque ninguna imagen entra en estado pendiente.

Es la misma frontera que NAPT: Arduino sirve para el camino sencillo y el producto que necesita garantías debe migrar a ESP-IDF. Conviene saberlo antes de prometer rollback a un cliente.

Verificación de integridad

Con independencia del rollback, valida lo que llegó. Lo mínimo es comparar el tamaño anunciado con el escrito, algo que ya hace el código anterior.

Un paso mejor es comprobar el primer byte: todo binario de aplicación del ESP32 empieza con el valor 0xE9. Si el servidor devolvió una página HTML de error en lugar del archivo, se detecta antes de escribir nada.

El paso definitivo es un SHA-256 publicado junto al binario y comprobado después de grabarlo. Cuesta unos segundos y elimina toda la clase de corrupción silenciosa en una red defectuosa.

Tutorial: de cero a la primera actualización por el aire

Paso 1: preparar la tabla de particiones

Crea el archivo partitions.csv en la raíz del proyecto con la tabla anterior, ajustando los tamaños a tu flash. Indícalo en platformio.ini:

code.cpp
cpp
1[env:esp32-ota]
2platform = espressif32
3board = esp32dev
4framework = arduino
5monitor_speed = 115200
6board_build.partitions = partitions.csv

Compila y comprueba el informe de tamaño. Si aparece un error de imagen demasiado grande, los slots son pequeños para tu firmware.

Paso 2: grabar la primera versión por cable

La primera grabación siempre es por USB; no existe OTA sin un firmware funcionando. Graba una versión con el número de versión visible en el log de arranque, porque así confirmarás el cambio.

code.cpp
cpp
1pio run -t upload
2pio device monitor -b 115200

Paso 3: publicar el binario nuevo

Cambia el número de versión, compila sin grabar y localiza el archivo:

code.cpp
cpp
1pio run

El binario queda en .pio/build/esp32-ota/firmware.bin. Cópialo a un servidor HTTP accesible desde la red de la placa. Para probar, basta el servidor integrado de Python:

code.cpp
cpp
1python -m http.server 8000

Paso 4: iniciar la actualización

Con el firmware del kit, indica la URL del binario y observa el monitor serie. Debes ver el tamaño anunciado, el progreso de la grabación y la confirmación antes del reinicio.

Si falla la descarga, prueba la URL en el navegador de un ordenador de la misma red; un error de ruta es mucho más común que un error de código.

Paso 5: confirmar el cambio

Después del reinicio, el log de arranque debe mostrar el número de versión nuevo. Si muestra el antiguo, la grabación no terminó o no se actualizó otadata.

Paso 6: probar el fallo a propósito

Este paso casi nadie lo hace y es el que da confianza en el mecanismo.

Compila una versión deliberadamente rota, por ejemplo con la contraseña Wi-Fi equivocada, y actualiza a ella. Con rollback activo, la placa debe intentarlo, fallar el autodiagnóstico y volver sola a la versión anterior.

Si se queda en la versión rota, el rollback no funciona y es mucho mejor descubrirlo en la mesa.

Errores comunes y cómo resolverlos

Error de partición inexistente. El proyecto usa la tabla predeterminada de una sola partición. Indica partitions.csv en platformio.ini.

Sketch too big después de habilitar OTA. Es normal: tienes la mitad de la flash. En una placa de 4 MB usa min_spiffs; si todavía no cabe, considera una placa mayor.

La actualización termina pero la placa vuelve a la versión antigua. No se actualizó otadata; casi siempre falló end(true). Lee errorString.

Descarga interrumpida en una red mala. Implementa reintentos y compara el tamaño anunciado en la cabecera con el escrito. Sin esa comparación, una descarga truncada se convierte en una imagen inválida grabada como si hubiera tenido éxito.

Se grabó una página HTML como firmware. El servidor devolvió un error y el código grabó la respuesta. Comprobar el primer byte 0xE9 lo detecta antes.

Llamé a las funciones de rollback y no pasa nada. El bootloader no se compiló con la opción activada. Con el bootloader estándar de Arduino el rollback no está disponible.

La placa entró en un ciclo de reinicio después de la actualización. Sin rollback la recuperación es por cable. Con rollback, eso es precisamente lo que existe para evitar; por eso hay que probar el fallo a propósito antes de ir a campo.

Ver más tutoriales técnicosIIoT ESP32 Brasil — Documentación práctica