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

Cómo servir contenido desde memoria Flash en el ESP8266 o ESP32

En esta entrada de la sección sobre el ESP8266 y el ESP32 vamos a ver cómo servir contenido desde la memoria Flash, para evitar un consumo innecesario de RAM.

Haremos referencia al ESP8266, pero el mismo código es compatible para el ESP32, ajustando el nombre de las librerías. Al final tenéis el código tanto para el ESP8266 como para el ESP32.

En entradas anteriores hemos visto cómo hacer un servidor sencillo, cómo distinguir entre peticiones y leer los parámetros, y cómo servir contenido dinámico generando un String en la petició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 el SPIFFS, que veremos en la siguiente entrada.

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 <ESP8266WiFi.h>
#include <ESP8266WebServer.h>

bool ledStatus = false;  // Variable de ejemplo

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

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

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

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

ESP8266WebServer 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");
}

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>
)=====";

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, siguiendo con el ejemplo de la entrada anterior, queremos servir una página Web que muestre el estado de una variable cualquiera. En este ejemplo la variable será ‘ledStatus’.

En la entrada anterior devolvíamos 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 página Web servida.

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’.

ESP8266WebServer 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");
}

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>
)=====";

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 lo veremos dentro de poco. En la próxima entrada aprenderemos a usar el sistema de ficheros SPIFFS para servir contenido estático. ¡Hasta pronto!

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