A diferencia del STM32 donde manipulamos la memoria Flash borrando "Sectores" enteros manualmente (Día 219), ESP-IDF nos abstrae de esa brutalidad matemática proporcionando una librería de alto nivel llamada NVS.
El NVS funciona exactamente igual que un Diccionario (Key-Value Pair). Tú no necesitas saber en qué dirección hexadecimal se guardan los datos. Simplemente le dices: "Guarda el texto 'MiWiFiSecreto' bajo la llave 'SSID'". El sistema NVS se encarga de ubicar un sector vacío, escribir el dato y protegerlo.
"config_red" para el WiFi, y quizá otro llamado "config_aws" para los certificados en el futuro.nvs_set_str(), el dato solo se actualiza en un caché de la RAM rápida. Si se va la luz ahí, el dato se pierde. Siempre debes llamar a nvs_commit() para obligar a que los electrones se quemen en el silicio permanentemente.Circuito: El uso de NVS no requiere componentes externos, utiliza la misma memoria Flash QSPI montada en el módulo ESP32 (generalmente de 4MB u 8MB).
Ejercicio 1: Crearemos una librería interna llamada nvs_manager.c. Programaremos una función para inyectar (escribir) las credenciales recibidas desde el Portal Cautivo, y otra función para extraerlas (leerlas) durante el arranque de la máquina, determinando si debemos encender la antena en modo Access Point o Modo Estación.
/**
* @file nvs_manager.c
* @brief Gestor de Almacenamiento Persistente para configuración IoT (ESP-IDF)
*/
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "nvs_flash.h"
#include "nvs.h"
// =======================================================
// ESCRITURA: GUARDAR CREDENCIALES (Llamado por el Portal)
// =======================================================
/**
* @brief Guarda el SSID y el Password en la memoria permanente del ESP32
* @retval bool: true si la escritura en Flash fue exitosa
*/
bool Guardar_Credenciales_WiFi(const char* ssid, const char* password) {
nvs_handle_t my_handle;
esp_err_t err;
// 1. Abrir (o crear) el Namespace "config_red" en Modo Lectura/Escritura
err = nvs_open("config_red", NVS_READWRITE, &my_handle);
if (err != ESP_OK) {
printf("[NVS ERROR] No se pudo abrir la Bóveda NVS.\n");
return false;
}
// 2. Escribir las cadenas (Key, Value)
// Nota: Las llaves máximo pueden tener 15 caracteres.
err = nvs_set_str(my_handle, "ssid", ssid);
err |= nvs_set_str(my_handle, "pass", password); // Operador OR para acumular errores
if (err != ESP_OK) {
printf("[NVS ERROR] Falló la escritura en caché RAM.\n");
nvs_close(my_handle);
return false;
}
// 3. EL COMMIT OBLIGATORIO
// Sin esta instrucción, al irse la luz los datos desaparecerán.
err = nvs_commit(my_handle);
if (err != ESP_OK) {
printf("[NVS ERROR] Falló la quema en Silicio (Commit).\n");
} else {
printf("[NVS SUCCESS] ¡Credenciales selladas en la memoria Flash!\n");
}
// 4. Cerrar el manejador para liberar recursos de memoria
nvs_close(my_handle);
return (err == ESP_OK);
}
// =======================================================
// LECTURA: EXTRAER CREDENCIALES (Llamado al arrancar)
// =======================================================
/**
* @brief Extrae las credenciales. Si no existen, retorna false (La máquina es virgen).
* @param ssid_out: Buffer donde se copiará el nombre de la red
* @param pass_out: Buffer donde se copiará la contraseña
* @retval bool: true si existen credenciales, false si la memoria está vacía.
*/
bool Leer_Credenciales_WiFi(char* ssid_out, char* pass_out, size_t max_len) {
nvs_handle_t my_handle;
esp_err_t err;
// 1. Abrir en Modo Solo Lectura (Protege contra escrituras accidentales)
err = nvs_open("config_red", NVS_READONLY, &my_handle);
if (err != ESP_OK) {
// ESP_ERR_NVS_NOT_FOUND significa que el Namespace nunca ha sido creado.
printf("[NVS INFO] Bóveda vacía. Dispositivo Virgen.\n");
return false;
}
// 2. Extracción. NVS requiere que pases una variable con el tamaño disponible
// de tu buffer, y NVS lo modificará al tamaño real leído.
size_t required_size_ssid = max_len;
size_t required_size_pass = max_len;
err = nvs_get_str(my_handle, "ssid", ssid_out, &required_size_ssid);
if (err != ESP_OK) {
nvs_close(my_handle);
return false;
}
err = nvs_get_str(my_handle, "pass", pass_out, &required_size_pass);
if (err != ESP_OK) {
nvs_close(my_handle);
return false;
}
// 3. Cerrar
nvs_close(my_handle);
printf("[NVS SUCCESS] Credenciales leídas. SSID: %s\n", ssid_out);
return true;
}
// =======================================================
// LÓGICA DE ARRANQUE EN EL MAIN
// =======================================================
/*
void app_main(void) {
// 1. Inicializar la Bóveda Global (Día 221)
ESP_ERROR_CHECK(nvs_flash_init());
char ssid[32];
char pass[64];
// 2. Árbol de Decisión Inicial
if (Leer_Credenciales_WiFi(ssid, pass, sizeof(ssid))) {
printf("[BOOT] Modo Estación (Cliente). Conectando a %s...\n", ssid);
Iniciar_WiFi_STA(ssid, pass);
// ... (Iniciar MQTT) ...
} else {
printf("[BOOT] Modo Access Point. Iniciando Portal Cautivo...\n");
Iniciar_WiFi_AP("DISPENSADOR_SETUP");
iniciar_servidor_web(); // (Día 224)
// La máquina se quedará aquí hasta que el usuario guarde datos
// y el Portal Cautivo ejecute esp_restart().
}
}
*/
Objetivo del día: Ciclo de Vida Autónomo y Persistencia de Configuración Dinámica.
El pecado capital del desarrollo IoT amateur es poner la contraseña de tu red doméstica quemada directamente en el código fuente de C (`#define WIFI_PASS "mi_perro_123"`). Si haces eso, el código de tu repositorio no es genérico, y si llevas la máquina a una exposición comercial, no podrás conectarla al internet del salón sin llevar tu laptop, abrir el entorno de desarrollo y recompilar todo el proyecto. Totalmente inviable para la industria.
Al implementar **NVS (Non-Volatile Storage)** combinado con el **Portal Cautivo**, tu firmware ahora es 100% agnóstico al lugar de instalación. Puedes compilar tu archivo `.bin` una sola vez y grabar miles de tarjetas electrónicas en la fábrica con el mismo software genérico. Cuando el Despachador de Gas arranca por primera vez en la gasolinera, la función `Leer_Credenciales_WiFi` falla lógicamente, la máquina comprende que "nació ayer", levanta su Portal Cautivo, absorbe las credenciales del técnico, y ejecuta la sagrada operación `nvs_commit()`. A partir de ese reset, la máquina es "adulta", lee su configuración del silicio, se conecta a la red de la empresa y comienza a producir dinero. Tu sistema ha adquirido memoria a largo plazo y autonomía de despliegue absoluto.
nvs_set_str() o nvs_set_i32() es Volátil por naturaleza. Solo modifica los registros en la memoria caché RAM. Si el microcontrolador se reinicia en ese estado, el dato se desvanece.nvs_commit(), la cual desencadena la escritura física (quema de electrones) en los sectores de la Flash.