como-servir-contenido-desde-memoria-flash-en-el-esp8266

Cómo servir contenido desde memoria Flash en ESP32

  • 5 min

Servir contenido desde la memoria Flash permite ahorrar RAM al guardar textos largos, HTML o recursos estáticos fuera de la memoria de trabajo.

El ejemplo está orientado a ESP32. En muchos casos también puede adaptarse a ESP8266 cambiando librerías y algunos detalles de pines.

En un ESP8266 la RAM es limitada, así que conviene reservarla para lo que realmente cambia durante la ejecución. Todo lo que sea contenido fijo puede vivir mucho más feliz en Flash (y nuestra RAM también).

Ya dijimos que uno de los problemas de generar el contenido dinámicamente es que supone una carga al procesador (frente a generar un fichero estático) y que, si la respuesta es larga, ocupa mucha memoria en el espacio de variables.

Una forma de evitar esto último es almacenar las partes estáticas de la respuesta en la memoria Flash y combinarlas con aquellas que realmente van a cambiar. Esto alivia en parte el problema del uso de memoria.

Remarcar que esto debemos usarlo únicamente para contenido dinámico, es decir, que va a cambiar entre peticiones. Para servir contenido estático tenemos alternativas mejores como SPIFFS o LittleFS.

Servir contenido desde Flash

Servir el contenido desde la memoria Flash es realmente sencillo.

Nuestro Sketch es prácticamente el mismo, únicamente hemos incluido un nuevo fichero ‘index.h’ que contendrá el String a servir.

#include <WiFi.h>
#include <WebServer.h>

bool ledStatus = false;  // Variable de ejemplo

#include "config.h"  // Sustituir con datos de vuestra red
#include "index.h"
#include "ESP32_Utils.hpp"
#include "Server.hpp"

void setup(void) 
{
   Serial.begin(115200);

   ConnectWiFi_STA();
   
   InitServer();
}
 
void loop()
{
   server.handleClient();
}
Copied!

Finalmente, nuestro fichero ‘Server.hpp’ queda de la siguiente forma.

WebServer server(80);

void handleRoot() {
   String response = MAIN_page;
   server.send(200, "text/html", response);
}

void handleNotFound() {
   server.send(404, "text/plain", "Not found");
}

void InitServer()
{
   server.on("/", handleRoot);
   server.onNotFound(handleNotFound);
   server.begin();
   Serial.println("HTTP server started");
}
Copied!

Por otro lado, creamos el fichero ‘index.h’, donde simplemente hemos creado un array de Char que contiene la página web de “Hola mundo” que queremos servir.

Para guardarlo en la memoria flash hemos empleado ‘PROGMEN’, de forma que esta constante no se almacene en la memoria dedicada a variables.

const char MAIN_page[] PROGMEM = R"=====(
<!DOCTYPE html>
<html>
<body>

<div>
   <h1>Hola mundo!</h1>
</div>

</body>
</html>
)=====";
Copied!

En este ejemplo sencillo, únicamente hemos ruteado la URL root a la variable MAIN_page anterior. El contenido servido es una constante y no varía (es estático). A continuación, veremos un ejemplo ligeramente más realista.

Recordar que tenemos métodos mejores para servir contenidos estáticos, y que los veremos en las siguientes entradas.

Servir contenido dinámico desde Flash

Entonces, ¿en qué circunstancias puede ser esto útil? Para servir contenido ‘pseudo-dinámico’, es decir, para alojar las partes que no varían de una respuesta dinámica reduciendo la memoria dedicada a variables empleada.

Por ejemplo, supongamos que queremos servir una página web que muestre el estado de una variable cualquiera. En este ejemplo la variable será ledStatus.

En un ejemplo mínimo podríamos devolver un escueto String informando del estado de la variable. Pero si queremos servir una página web, hay mucho contenido que es similar, y únicamente cambia una parte de la respuesta.

En este caso, puede tener sentido almacenar las partes ‘estáticas’ de la respuesta en variables PROGMEM.

Modificamos el fichero ‘Server.hpp’ para servir ‘HTML_PART_1’, luego nuestro contenido dinámico, y terminar con ‘HTML_PART_2’.

WebServer server(80);

void handleRoot() {
   String response = ledStatus ? "ON" : "OFF";

   server.setContentLength(sizeof(HTML_PART_1) + sizeof(response) + sizeof(HTML_PART_2));
   server.send(200, "text/html", HTML_PART_1);
   server.sendContent(response);
   server.sendContent(HTML_PART_2);
}

void handleNotFound() {
   server.send(404, "text/plain", "Not found");
}

void InitServer()
{
   server.on("/", handleRoot);
   server.onNotFound(handleNotFound);
   server.begin();
   Serial.println("HTTP server started");
}
Copied!

Modificamos ‘index.h’ para que contenga

const char HTML_PART_1[] PROGMEM = R"=====(
<!DOCTYPE html>
<html>
<body>

<div>
   <h1>Hola mundo!</h1>
   <p>LED: 
)=====";

const char HTML_PART_2[] PROGMEM = R"=====(
   </p>
</div>

</body>
</html>
)=====";
Copied!

Y si ahora accedemos con el navegador veremos que, en lugar de un simple “LED: OFF” hemos servido una página Web completa en la que únicamente modificamos el contenido que queremos.

esp8266-server-from-flash

Por supuesto, tenemos otras formas de realizar una aplicación de este tipo. Normalmente serviremos una página Web estática, y el frontend realizara una consulta a un Endpoint para obtener el contenido de la variable.

Sin embargo, aunque en próximas entradas veremos cómo conseguir un resultado igual al del ejemplo de forma más conveniente, lo importante es ilustrar que es posible emplear la memoria Flash para servir contenido y aligerar el consumo de RAM.

Todo esto nos permite reducir el consumo de RAM y mantener el programa más ordenado. Para proyectos con muchos ficheros estáticos, la siguiente opción natural es usar un sistema de ficheros como SPIFFS o LittleFS.

Descarga el código

Todo el código de esta entrada está disponible para su descarga en Github.

github-full

Versión para el ESP8266: https://github.com/luisllamasbinaburo/ESP8266-Examples

Versión para el ESP32: https://github.com/luisllamasbinaburo/ESP32-Examples