Tema 3

Optimización y Reconfiguración de Sistemas ESP32

ESP32 - Actualización OTA y Optimización

Duración: 20 horas | Evaluación: 1ª Evaluación

Resultados de Aprendizaje y Criterios de Evaluación

RA 3: Optimiza el funcionamiento de equipos y sistemas, ajustando elementos y reconfigurando sistemas.

Criterios de Evaluación:

Puntuación Total RA 3: 1,9 puntos

3.1 Actualización OTA (Over-The-Air)

3.1.1 Implementación de OTA Básico

Actualización de firmware sin conexión física:

#include "WiFi.h" #include "ArduinoOTA.h" const char* ssid = "MiRed"; const char* password = "miPassword"; void setup() { Serial.begin(115200); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.println("Conectando..."); } Serial.println("WiFi conectado"); Serial.println(WiFi.localIP()); // Configurar OTA ArduinoOTA.setHostname("ESP32_OTA"); ArduinoOTA.setPassword("ota123"); ArduinoOTA.onStart([]() { String type = (ArduinoOTA.getCommand() == U_FLASH) ? "sketch" : "filesystem"; Serial.println("Iniciando actualización OTA: " + type); }); ArduinoOTA.onEnd([]() { Serial.println("\nActualización OTA completada"); }); ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { Serial.printf("Progreso: %u%%\r", (progress / (total / 100))); }); ArduinoOTA.onError([](ota_error_t error) { Serial.printf("Error[%u]: ", error); if (error == OTA_AUTH_ERROR) Serial.println("Error de autenticación"); else if (error == OTA_BEGIN_ERROR) Serial.println("Error al comenzar"); else if (error == OTA_CONNECT_ERROR) Serial.println("Error de conexión"); else if (error == OTA_RECEIVE_ERROR) Serial.println("Error de recepción"); else if (error == OTA_END_ERROR) Serial.println("Error al finalizar"); }); ArduinoOTA.begin(); Serial.println("OTA listo"); } void loop() { ArduinoOTA.handle(); // Tu código aquí }

3.1.2 OTA Avanzado con Particiones

Gestión de múltiples particiones para rollback:

#include "esp_ota_ops.h" #include "esp_http_client.h" const esp_partition_t* running = esp_ota_get_running_partition(); const esp_partition_t* update_partition = esp_ota_get_next_update_partition(NULL); void actualizarFirmware() { esp_http_client_config_t config = { .url = "http://servidor.com/firmware.bin", .cert_pem = NULL, }; esp_http_client_handle_t client = esp_http_client_init(&config); esp_err_t err = esp_http_client_perform(client); if (err == ESP_OK) { esp_ota_handle_t update_handle = 0; esp_err_t err = esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &update_handle); if (err != ESP_OK) { Serial.println("Error al iniciar OTA"); return; } int content_length = esp_http_client_get_content_length(client); int data_read = 0; uint8_t data[1024]; while (data_read < content_length) { int data_received = esp_http_client_read(client, (char*)data, 1024); if (data_received <= 0) break; esp_ota_write(update_handle, data, data_received); data_read += data_received; } esp_ota_end(update_handle); esp_ota_set_boot_partition(update_partition); esp_restart(); } esp_http_client_cleanup(client); }

3.2 Configuración Dinámica

3.2.1 EEPROM y SPIFFS

Almacenamiento de configuraciones:

#include "EEPROM.h" #include "SPIFFS.h" struct Configuracion { char ssid[32]; char password[64]; int puerto; bool debug; }; void guardarConfiguracion() { Configuracion config; strcpy(config.ssid, "MiRed"); strcpy(config.password, "miPassword"); config.puerto = 8080; config.debug = true; EEPROM.begin(512); EEPROM.put(0, config); EEPROM.commit(); EEPROM.end(); } void cargarConfiguracion() { Configuracion config; EEPROM.begin(512); EEPROM.get(0, config); EEPROM.end(); Serial.println("SSID: " + String(config.ssid)); Serial.println("Puerto: " + String(config.puerto)); } void setupSPIFFS() { if (!SPIFFS.begin(true)) { Serial.println("Error montando SPIFFS"); return; } // Escribir archivo de configuración File file = SPIFFS.open("/config.json", "w"); if (file) { file.println("{\"ssid\":\"MiRed\",\"password\":\"miPassword\"}"); file.close(); } // Leer archivo file = SPIFFS.open("/config.json", "r"); if (file) { while (file.available()) { Serial.write(file.read()); } file.close(); } }

3.2.2 NVS (Non-Volatile Storage)

Sistema de almacenamiento no volátil:

#include "nvs_flash.h" #include "nvs.h" void configurarNVS() { esp_err_t ret = nvs_flash_init(); if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { ESP_ERROR_CHECK(nvs_flash_erase()); ret = nvs_flash_init(); } ESP_ERROR_CHECK(ret); } void guardarEnNVS() { nvs_handle_t my_handle; esp_err_t err = nvs_open("storage", NVS_READWRITE, &my_handle); if (err != ESP_OK) { Serial.println("Error abriendo NVS"); return; } // Guardar valores err = nvs_set_i32(my_handle, "contador", 42); err = nvs_set_str(my_handle, "nombre", "ESP32"); err = nvs_commit(my_handle); nvs_close(my_handle); } void leerDeNVS() { nvs_handle_t my_handle; esp_err_t err = nvs_open("storage", NVS_READONLY, &my_handle); if (err != ESP_OK) { Serial.println("Error abriendo NVS"); return; } int32_t contador; size_t required_size = 32; char nombre[32]; err = nvs_get_i32(my_handle, "contador", &contador); err = nvs_get_str(my_handle, "nombre", nombre, &required_size); Serial.printf("Contador: %d\n", contador); Serial.printf("Nombre: %s\n", nombre); nvs_close(my_handle); }

3.3 Optimización de Consumo

3.3.1 Modos de Bajo Consumo

Gestión de energía para aplicaciones IoT:

#include "esp_sleep.h" #include "esp_wifi.h" void configurarModoBajoConsumo() { // Configurar wake-up por timer esp_sleep_enable_timer_wakeup(10 * 1000000); // 10 segundos // Configurar wake-up por GPIO esp_sleep_enable_ext0_wakeup(GPIO_NUM_0, 0); // Configurar wake-up por touch esp_sleep_enable_touchpad_wakeup(); } void entrarEnDeepSleep() { Serial.println("Entrando en Deep Sleep..."); delay(1000); // Desactivar WiFi esp_wifi_stop(); // Entrar en deep sleep esp_deep_sleep_start(); } void entrarEnLightSleep() { Serial.println("Entrando en Light Sleep..."); // Configurar wake-up esp_sleep_enable_timer_wakeup(5 * 1000000); // 5 segundos // Entrar en light sleep esp_light_sleep_start(); Serial.println("Despertado de Light Sleep"); } void gestionarConsumo() { // Medir consumo actual int consumo = esp_get_free_heap_size(); Serial.printf("Memoria libre: %d bytes\n", consumo); // Optimizar frecuencia de CPU setCpuFrequencyMhz(80); // Reducir frecuencia para ahorrar energía // Desactivar periféricos no utilizados esp_wifi_stop(); esp_bt_controller_disable(); }

3.3.2 Gestión de Memoria

Optimización de uso de memoria:

#include "esp_heap_caps.h" void monitorearMemoria() { Serial.println("=== Estado de Memoria ==="); // Memoria total Serial.printf("Memoria total: %d bytes\n", ESP.getHeapSize()); Serial.printf("Memoria libre: %d bytes\n", ESP.getFreeHeap()); Serial.printf("Memoria mínima libre: %d bytes\n", ESP.getMinFreeHeap()); // Memoria PSRAM if (psramFound()) { Serial.printf("PSRAM total: %d bytes\n", ESP.getPsramSize()); Serial.printf("PSRAM libre: %d bytes\n", ESP.getFreePsram()); } // Memoria por capacidades Serial.printf("Memoria interna libre: %d bytes\n", heap_caps_get_free_size(MALLOC_CAP_INTERNAL)); Serial.printf("Memoria DMA libre: %d bytes\n", heap_caps_get_free_size(MALLOC_CAP_DMA)); } void optimizarMemoria() { // Limpiar memoria no utilizada esp_wifi_deinit(); // Reducir tamaño de buffers WiFi.setTxPower(WIFI_POWER_19_5dBm); // Liberar memoria de strings String temp = "Temporal"; temp = ""; // Liberar memoria // Usar PROGMEM para constantes const char PROGMEM mensaje[] = "Mensaje en flash"; }

3.4 Interfaz de Configuración Remota

3.4.1 Servidor Web de Configuración

Interfaz web para configuración remota:

#include "WebServer.h" #include "ArduinoJson.h" WebServer server(80); void setup() { Serial.begin(115200); WiFi.begin("SSID", "password"); while (WiFi.status() != WL_CONNECTED) { delay(1000); } // Página principal server.on("/", []() { String html = ""; html += "

Configuración ESP32

"; html += "
"; html += "SSID:
"; html += "Password:
"; html += "Puerto:
"; html += ""; html += "
"; html += ""; server.send(200, "text/html", html); }); // Endpoint de configuración server.on("/config", HTTP_POST, []() { String ssid = server.arg("ssid"); String password = server.arg("password"); int puerto = server.arg("puerto").toInt(); // Guardar configuración guardarConfiguracion(ssid, password, puerto); server.send(200, "text/plain", "Configuración guardada"); }); // API REST server.on("/api/status", HTTP_GET, []() { DynamicJsonDocument doc(1024); doc["wifi_connected"] = WiFi.status() == WL_CONNECTED; doc["ip"] = WiFi.localIP().toString(); doc["rssi"] = WiFi.RSSI(); doc["free_heap"] = ESP.getFreeHeap(); String response; serializeJson(doc, response); server.send(200, "application/json", response); }); server.begin(); }

3.4.2 Aplicación Móvil

Interfaz móvil para control remoto:

// Servidor para aplicación móvil void setupAppMovil() { // Endpoint para aplicación móvil server.on("/mobile/config", HTTP_POST, []() { if (server.hasArg("plain")) { DynamicJsonDocument doc(1024); deserializeJson(doc, server.arg("plain")); String ssid = doc["ssid"]; String password = doc["password"]; bool debug = doc["debug"]; // Aplicar configuración aplicarConfiguracion(ssid, password, debug); server.send(200, "application/json", "{\"status\":\"ok\"}"); } else { server.send(400, "application/json", "{\"error\":\"No data\"}"); } }); // Endpoint para telemetría server.on("/mobile/telemetry", HTTP_GET, []() { DynamicJsonDocument doc(1024); doc["temperature"] = 25.5; doc["humidity"] = 60.0; doc["rssi"] = WiFi.RSSI(); doc["uptime"] = millis() / 1000; String response; serializeJson(doc, response); server.send(200, "application/json", response); }); }

3.5 Monitoreo de Sistema

3.5.1 Telemetría y Logs

Sistema de monitoreo en tiempo real:

#include "esp_log.h" static const char* TAG = "SISTEMA"; void configurarLogs() { esp_log_level_set("*", ESP_LOG_INFO); esp_log_level_set("SISTEMA", ESP_LOG_DEBUG); } void enviarTelemetria() { ESP_LOGI(TAG, "Enviando telemetría"); DynamicJsonDocument doc(1024); doc["timestamp"] = millis(); doc["temperature"] = leerTemperatura(); doc["humidity"] = leerHumedad(); doc["wifi_rssi"] = WiFi.RSSI(); doc["free_heap"] = ESP.getFreeHeap(); doc["cpu_freq"] = getCpuFrequencyMhz(); // Enviar por MQTT String telemetria; serializeJson(doc, telemetria); clienteMQTT.publish("esp32/telemetry", telemetria.c_str()); ESP_LOGD(TAG, "Telemetría enviada: %s", telemetria.c_str()); } void generarLogs() { ESP_LOGI(TAG, "Sistema iniciado"); ESP_LOGW(TAG, "Memoria baja: %d bytes", ESP.getFreeHeap()); ESP_LOGE(TAG, "Error de conexión WiFi"); // Logs personalizados esp_log_write(ESP_LOG_INFO, TAG, "Evento personalizado: %d", 42); }

3.5.2 Métricas de Rendimiento

Medición y análisis de rendimiento:

unsigned long tiempoInicio; unsigned long tiempoEjecucion; void medirRendimiento() { tiempoInicio = micros(); // Código a medir procesarDatos(); tiempoEjecucion = micros() - tiempoInicio; Serial.printf("Tiempo de ejecución: %lu microsegundos\n", tiempoEjecucion); // Enviar métricas enviarMetrica("tiempo_procesamiento", tiempoEjecucion); } void monitorearRendimiento() { static unsigned long ultimaMedicion = 0; static int contador = 0; if (millis() - ultimaMedicion > 10000) { // Cada 10 segundos float cpu_usage = calcularUsoCPU(); int memoria_libre = ESP.getFreeHeap(); int wifi_rssi = WiFi.RSSI(); Serial.printf("CPU: %.2f%%, Memoria: %d bytes, RSSI: %d dBm\n", cpu_usage, memoria_libre, wifi_rssi); // Almacenar métricas almacenarMetrica("cpu_usage", cpu_usage); almacenarMetrica("memory_free", memoria_libre); almacenarMetrica("wifi_rssi", wifi_rssi); ultimaMedicion = millis(); contador++; } }

3.6 Cumplimiento Normativo

3.6.1 Certificaciones CE y FCC

Verificación de cumplimiento normativo:

void verificarCumplimientoNormativo() { Serial.println("=== Verificación Normativa ==="); // Verificar potencia de transmisión int8_t potenciaMaxima; esp_wifi_get_max_tx_power(&potenciaMaxima); if (potenciaMaxima <= 20) { // 20 dBm máximo Serial.println("✓ Potencia de transmisión: CUMPLE"); } else { Serial.println("✗ Potencia de transmisión: NO CUMPLE"); } // Verificar canales permitidos int canal = WiFi.channel(); if (canal >= 1 && canal <= 13) { Serial.println("✓ Canal WiFi: CUMPLE"); } else { Serial.println("✗ Canal WiFi: NO CUMPLE"); } // Verificar frecuencia uint32_t frecuencia = WiFi.getChannel(); if (frecuencia >= 2412 && frecuencia <= 2484) { Serial.println("✓ Frecuencia: CUMPLE"); } else { Serial.println("✗ Frecuencia: NO CUMPLE"); } } void configurarParametrosNormativos() { // Configurar potencia según normativa esp_wifi_set_max_tx_power(20); // 20 dBm máximo // Configurar canales permitidos esp_wifi_set_channel(6, WIFI_SECOND_CHAN_NONE); // Configurar modulación esp_wifi_set_protocol(WIFI_IF_STA, WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G | WIFI_PROTOCOL_11N); }

Materiales Necesarios

Actividades Teóricas

Prácticas de Laboratorio

Práctica 3.1: Actualización OTA

Objetivo: Implementar sistema de actualización OTA con rollback.

Duración: 4 horas

Entregables: Sistema OTA completo, servidor de firmware, documentación

Práctica 3.2: Configuración Dinámica

Objetivo: Implementar sistema de configuración con múltiples almacenamientos.

Duración: 3 horas

Entregables: Sistema de configuración, interfaz web, pruebas de funcionamiento

Práctica 3.3: Optimización de Consumo

Objetivo: Optimizar el consumo energético del ESP32.

Duración: 3 horas

Entregables: Mediciones de consumo, código optimizado, informe técnico

Práctica 3.4: Interfaz de Configuración

Objetivo: Crear interfaz web y móvil para configuración remota.

Duración: 4 horas

Entregables: Interfaz web, aplicación móvil, API REST

Práctica 3.5: Sistema de Monitoreo

Objetivo: Implementar sistema completo de telemetría y logs.

Duración: 3 horas

Entregables: Sistema de monitoreo, dashboard, análisis de rendimiento

Práctica 3.6: Verificación Normativa

Objetivo: Verificar cumplimiento de normativas CE y FCC.

Duración: 3 horas

Entregables: Mediciones de emisiones, informe de cumplimiento, certificación

Criterios de Evaluación

Instrumentos de evaluación:

Entregables obligatorios:

Recursos Adicionales