Para poder probar el proyecto del Minisumo de Naranja que estamos haciendo en Twitch, que funciona sobre un ESP32, pensé en ponerle la funcionalidad OTA. OTA (Over-the-Air updates) es una función que permite actualizar el firmware de un dispositivo ESP32 de forma inalámbrica. Esto se puede hacer a través de una conexión Wi-Fi o Bluetooth, o la conexión que sea. Esto nos permite principalmente re-programar el robot sin necesidad de conectarlo a al PC.
La utilidad principal del OTA es poder actualizar el firmware de dispositivos que no están en sitios accesibles: imagina que tienes un robot en Marte, y te has dado cuenta de que hay un fallo en software y que hay que actualizar la versión. ¿Qué haces? ¿ Mandas a un astronauta con un pendrive ?; O imagina que tienes que actualizar el software de todos los molinos que están funcionando en el mar. En vez de ir uno por uno, puedes actualizar el código de todos los molinos a través de la red.
La explicación de como funciona el OTA tiene varios matices, pero básicamente, el ESP32 necesita dos particiones y una de control. Por defecto se programa el software de la primera partición, y cuando se necesita actualizar el firmware lo que se hace es escribir en la segunda partición, de esta manera, si el código no funciona, se puede revertir el estado al primer firmware. Si luego mandamos otra actualización de software, lo que sobrescribiríamos sería la partición 1, y así sucesivamente.
Para esta prueba estamos usando un ESP32 WROOM, enlace aquí.
Tenéis una recopilación del código en un gist
Ejemplo sencillo con ESP-IDF
Cuando busqué los ejemplos de OTA con ESP-IDF (Es decir SIN ARDUINO), no encontré ningún repositorio que fuera realmente sencillo, de hecho en el repositorio de gihub de ejemplos del esp-idf, el ejemplo “básico” incluía usar TLS y certificados, vamos una locura, para programar mi ESP32 a través de la Wifi de casa. Por ello aquí os dejo el ejemplo que programé en directo, y que parece que funciona.
Disclaimer: El código que os muestro aquí es horrible como código fuente para vuestros proyectos, no hay buenas prácticas en nada. Usadlo para aprender como funciona y no como un ejemplo a seguir. Os podéis basar en esto, pero tened en cuenta que no voy a usar ni un encabezado (fichero.h)…
Por tanto, el primer paso es conectarnos a la wifi, en nuestro caso vamos a usar un servidor HTTP desde el que el ESP32 se descargará el binario a programar, y una vez recibido el binario, se programa en la siguiente partición del ESP32.

Lanzando el Servidor
Lo primero que necesitamos es configurar el servidor HTTP desde el cual nuestro ESP32 se descargará el binario a flashear. En nuestro caso vamos a usar el servidor HTTP proporcionado por python3. Lo primero es tener a mano el binario del proyecto que queramos que se flashee. Si estais usando platformio lo podeis encontrar en '.pio\build\esp32doit-devkit-v1\firmware.bin
‘. Así que teniendo a mano el fichero firmware.bin solo teneis que seguir estos pasos:
- Instalar Python3
- (Opcional) Crear una carpeta donde lanzareis vuestro servidor y donde guardareis vuestros ficheros.
- Lanzar desde la carpeta del servidor, el servidor http de python:
python3 -m http.server
- Implementar el código que explico aquí abajo.
- Cuando arranque el ESP32, se descargará el fichero fimrware.bin de vuestro servidor y se autoprogramará.
Configurando platformIO
Lo único que tenemos que cambiar en el platformIO es que tenemos que definir el mapa de particiones del ESP32 para que genere las dos particiones de aplicación, para ello solo tenemos que añadir la siguiente línea dentro de nuestro entorno en el platformio.ini
board_build.partitions=partitions_two_ota_coredump.csv
Conexión wifi en el ESP32
El siguiente paso es conectarnos a nuestro WiFi, cómo no estamos centrados en esto, os dejo el trozo de código que deberíais escribir en wifi.c
, lo único que hace es inicializar el wifi y conectarse a la red requerida. Tened en cuenta que tenéis que cambiar el WIFI_SSID
y el WIFI_PWD
.
#include "freertos/FreeRTOS.h" #include "freertos/event_groups.h" #include "esp_wifi.h" #include "esp_log.h" #include "secret.h" static EventGroupHandle_t s_wifi_event_group; static int s_retry_num = 0; #define WIFI_CONNECTED_BIT BIT0 #define WIFI_FAIL_BIT BIT1 static const char *TAG = "WIFI"; static void event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) { if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) { esp_wifi_connect(); } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { if (s_retry_num < 5) { esp_wifi_connect(); s_retry_num++; ESP_LOGI(TAG, "retry to connect to the AP"); } else { xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT); } ESP_LOGI(TAG,"connect to the AP fail"); } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data; ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip)); s_retry_num = 0; xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT); } } void wifi_init_sta(void) { s_wifi_event_group = xEventGroupCreate(); ESP_ERROR_CHECK(esp_netif_init()); ESP_ERROR_CHECK(esp_event_loop_create_default()); esp_netif_create_default_wifi_sta(); wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); ESP_ERROR_CHECK(esp_wifi_init(&cfg)); esp_event_handler_instance_t instance_any_id; esp_event_handler_instance_t instance_got_ip; ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL, &instance_any_id)); ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL, &instance_got_ip)); wifi_config_t wifi_config = { .sta = { .ssid = WIFI_SSID, .password = WIFI_PWD, .threshold.authmode = WIFI_AUTH_WPA2_PSK, }, }; ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) ); ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config) ); ESP_ERROR_CHECK(esp_wifi_start() ); ESP_LOGI(TAG, "wifi_init_sta finished."); EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, pdFALSE, pdFALSE, portMAX_DELAY); }
Por fin, el código para el ESP32 y OTA con HTTP
Y aquí tenéis el código. Lo importante está en la línea 68 (funcion simple_ota_example_task
), configuramos de donde queremos descargar el binario (linea 27) y lo programamos, fin, no tiene mayor misterio.
/******************************************* * * Code created by Laboratorio Gluon * www.laboratoriogluon.com * ********************************************/ #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_system.h" #include "esp_event.h" #include "esp_log.h" #include "esp_ota_ops.h" #include "esp_http_client.h" #include "esp_https_ota.h" #include "esp_wifi.h" #include "esp_event.h" #include "string.h" #include "nvs.h" #include "nvs_flash.h" #include <sys/socket.h> /********** HAY QUE CAMBIAR SOLO ESTO ************/ #define BIN_URL "http://192.168.1.133:8000/firmware.bin" /*************************************************/ extern void wifi_init_sta(); static const char *TAG = "simple_ota_example"; #define OTA_URL_SIZE 256 esp_err_t _http_event_handler(esp_http_client_event_t *evt) { switch (evt->event_id) { case HTTP_EVENT_ERROR: ESP_LOGD(TAG, "HTTP_EVENT_ERROR"); break; case HTTP_EVENT_ON_CONNECTED: ESP_LOGD(TAG, "HTTP_EVENT_ON_CONNECTED"); break; case HTTP_EVENT_HEADER_SENT: ESP_LOGD(TAG, "HTTP_EVENT_HEADER_SENT"); break; case HTTP_EVENT_ON_HEADER: ESP_LOGD(TAG, "HTTP_EVENT_ON_HEADER, key=%s, value=%s", evt->header_key, evt->header_value); break; case HTTP_EVENT_ON_DATA: ESP_LOGD(TAG, "HTTP_EVENT_ON_DATA, len=%d", evt->data_len); break; case HTTP_EVENT_ON_FINISH: ESP_LOGD(TAG, "HTTP_EVENT_ON_FINISH"); break; case HTTP_EVENT_DISCONNECTED: ESP_LOGD(TAG, "HTTP_EVENT_DISCONNECTED"); break; case HTTP_EVENT_REDIRECT: ESP_LOGD(TAG, "HTTP_EVENT_REDIRECT"); break; } return ESP_OK; } void simple_ota_example_task(void *pvParameter) { ESP_LOGI(TAG, "Starting OTA example task"); esp_http_client_config_t config = { .url = BIN_URL, .event_handler = _http_event_handler, .keep_alive_enable = true, }; esp_https_ota_config_t ota_config = { .http_config = &config, }; ESP_LOGI(TAG, "Attempting to download update from %s", config.url); esp_err_t ret = esp_https_ota(&ota_config); if (ret == ESP_OK) { ESP_LOGI(TAG, "OTA Succeed, Rebooting..."); esp_restart(); } else { ESP_LOGE(TAG, "Firmware upgrade failed"); } while (1) { vTaskDelay(1000 / portTICK_PERIOD_MS); } } void app_main(void) { ESP_LOGI(TAG, "OTA example app_main start"); // Initialize NVS. esp_err_t err = nvs_flash_init(); if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) { // 1.OTA app partition table has a smaller NVS partition size than the non-OTA // partition table. This size mismatch may cause NVS initialization to fail. // 2.NVS partition contains data in new format and cannot be recognized by this version of code. // If this happens, we erase NVS partition and initialize NVS again. ESP_ERROR_CHECK(nvs_flash_erase()); err = nvs_flash_init(); } ESP_ERROR_CHECK(err); // Initialize and connect to Wifi wifi_init_sta(); xTaskCreate(&simple_ota_example_task, "ota_example_task", 8192, NULL, 5, NULL); }
Ejecutamos el código del ESP32 y listo, cuando arranque, se conectará a nuestro servidor, se descargará el fichero que haya, se autoprogramará y se lanzará el nuevo software.