Electrónica

Páginas embebidas con ESP8266 (WebServer)

Nivel: Básico

¿Qué tengo que saber para este post?

  • Diseño básico de páginas con HTML y CSS (muy poquito)
  • Programación básica (Arduino) C++-17
  • Entender el funcionamiento básico del modelo Cliente-Servidor

—————————————

En está oportunidad y estrenando el dominio nuevo, les traigo una experiencia sacada directamente del aula con mis estudiantes. Mientras le enseñaba un poco de diseño web con HTML y casi nada de CSS a mis estudiantes de electrónica, pensado para un WebSever embebido en un ESP8266 nos encontramos con la tediosa tarea de meter la página diseñada dentro de un string para que luego el servidor pueda devolverla al cliente. Pero por suerte para todo existe una solución y en caso de que no existiera se inventa.

Primeros pasos

Como sabrán, si es que siguen el blog, yo insisto en el uso de PlatformIO para el desarrollo de embebidos, sea un mini-sistema hobbista con Arduino o uno grande con herramientas profesionales, es una excelente herramienta de desarrollo. En este caso, utilizamos ESP8266 que son los que teníamos a mano en el colegio donde inicialmente hicimos esta práctica. Acá les dejo un link a la página de los compas de Naylamp con descripción completa y montones de referencias sobre este módulo de desarrollo en particular.

La idea es la de conectar un display LCD con expander I²C al NodeMCU y que éste sea accesible para escribir desde una página web servida por un WebServer en una conexión Wi-Fi modo AP (Access Point). Para ello, arrancamos un proyecto nuevo seleccionando la plataforma adecuada, que en este caso sería el Espressif ESP8266 ESP-12E como se observa en la imagen a continuación y utilizamos el framework de Arduino para un desarrollo rápido. Y, una vez inicializado el proyecto, simplemente agregamos una nueva carpeta al mismo que llamamos simplemente html.

En esta nueva carpeta diseñaremos nuestras páginas, utilizando el VSCodium o su alternativa con binarios no libres (VSCode) 😉 el desarrollo web es muy ágil. Basta con crear un archivo .html y escribir html:5 para que ya nos cree una estructura básica con la cual ya podemos empezar a trabajar. Cambiando algunas cosas, y agregando algunas otras, como quería mantener simple el código y escribir lo menos posible hicimos un simple formulario con 2 inputs, uno para cada linea del LCD y limitamos el ingreso a 16 caracteres y también validamos recibir ASCII en la medida de lo posible utilizando un regex.

<!DOCTYPE html>
<html lang="es">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <style>
    input {
      font-family: monospace;
      margin: 5px;
      padding: 8px;
      border-radius: 8px;
      width: 16ch;
    }
  </style>
  <title>LCD WiFi</title>
</head>
<body>
  <h1>Escribí algo en el Display</h1>
  <form action="escribir" method="post">
    <input type="text" name="l0" pattern="[a-zA-Z0-9.,;:=\-_#$@ ]{0,16}" maxlength="16" placeholder="Línea 1"><br>
    <input type="text" name="l1" pattern="[a-zA-Z0-9.,;:=\-_#$@ ]{0,16}" maxlength="16" placeholder="Línea 2"><br>
    <input type="submit" value="Escribir">
  </form>
</body>
</html>
Preview del HTML en VSCodium

Antes de que un desarrollador web caiga acá y venga a poner el grito en el cielo porque el estilo está dentro del mismo HTML, le comento que esto es necesario, ya que la página en cuestión terminará siendo un string dentro del código en C++. Por lo tanto, no tenemos gestión de archivos ni nada parecido, al menos no lo tenemos sin complicar excesivamente el ejemplo.

Ya tengo la página, ¿ahora qué?

Entonces, una vez conformes con la página que habíamos desarrollado con mis estudiantes, nos vemos en una dificultad. Había que formatear este archivo para meterlo dentro del microcontrolador. Esta tarea es fácil de scriptear en bash, pero la mayoría de mis estudiantes ni siquiera saben con qué se come como utilizar la terminal, comandos, Linux y nada parecido. Por lo tanto, decidí armar algo que solucionara esta pequeña complicación. En mi nueva página van a poder encontrar esta herramienta ACÁ. La cual les generará algo como lo que se ve a continuación.

El script de PHP toma un archivo .html desde la PC y genera una salida en texto plano con una variable const char* con el mismo nombre del archivo subido. Opcionalmente se puede agregar la directiva de preprocesador PROGMEM, pero realmente no es necesario en la mayoría de los casos. Todas las comillas dobles son reemplazadas por simples y otros detalles. Lo bueno de esto es que respeta el identado, es decir, no ofusca el código, porque no es el objetivo del script el combinarlo en una única linea inentendible e imposible de modificar si fuera necesario.

Yo opté por copiar y pegar esto directamente en un html.h dentro de la carpeta include del proyecto.

#ifndef __HTML_H__
#define __HTML_H__
const char * lcdpage PROGMEM = 
"<!DOCTYPE html>"
"<html lang='es'>"
"<head>"
  "<meta charset='UTF-8'>"
  "<meta name='viewport' content='width=device-width, initial-scale=1.0'>"
  "<style>"
    "input {"
      "font-family: monospace;"
      "margin: 5px;"
      "padding: 8px;"
      "border-radius: 8px;"
      "width: 16ch;"
    "}"
  "</style>"
  "<title>LCD WiFi</title>"
"</head>"
"<body>"
  "<h1>Escribí algo en el Display</h1>"
  "<form action='escribir' method='post'>"
    "<input type='text' name='l0' pattern='[a-zA-Z0-9.,;:=\\-_#$@ ]{0,16}' maxlength='16' placeholder='Línea 1'><br>"
    "<input type='text' name='l1' pattern='[a-zA-Z0-9.,;:=\\-_#$@ ]{0,16}' maxlength='16' placeholder='Línea 2'><br>"
    "<input type='submit' value='Escribir'>"
  "</form>"
"</body>"
"</html>";
#endif // __HTML_H__

El WebServer

En esto no voy a detenerme demasiado, ya que hay miles de ejemplos en internet y en las mismas bibliotecas oficiales. El código es casi auto-explicativo, pero voy a aclarar algunas cuestiones por las dudas.

Declarar peticiones válidas y respuestas al cliente

El servidor es un objeto de la clase ESP8266WebServer, construido para atender peticiones a través del puerto 80. Entonces, debemos configurar cuales serán las URIs que vamos a servir. Esto se hace a través del método o función miembro on() que recibe un string de la URI y una función que será invocada al momento de la petición. Además hay otra función específica para cuando piden una URI no registrada onNotFound() la cual recibe unicamente la función que atenderá estas peticiones.

  server.on("/",escribir);
  server.on("/escribir",escribir);
  server.onNotFound([]{server.send(404,"text/plain","404 - Pagina no encontrada.");});

En este caso, solo tengo un solo html, y siempre voy a devolver la misma página, así que tanto sea la raíz «/» o el action del formulario («/escribir») yo llamaré a la misma función, que llame escribir(). En cuanto a la página no encontrada, decidí utilizar un lambda y escribir la función directamente sobre el argumento.

Para responder al clinte, como se observa necesitamos utilizar la función miembro send() la cual envía el código de estado HTTP, el formato de la respuesta y el cuerpo de la misma, estas dos últimas en formato string. Por ejemplo, para enviar la página que hicimos sería así:

server.send(200,"text/html",lcdpage);

Leer argumentos de los input de un formulario

Los argumentos son los valores que reciben los parámetros o campos del formulario. Estos se identifican con el atributo name, por ejemplo en el html de nuestra página teníamos dos input del tipo texto con los nombres l0 y l1.

 <input type="text" name="l0" ...>
 <input type="text" name="l1" ...>

Para poder leer u obtener los valores que fueron completados dentro del formulario existe el método o función miembro de la clase ESP8266WebServer llamada arg() la cual devuelve siempre un objeto de la clase String. Es por eso que podemos consultar si estos campos están vacíos que puede ocurrir por dos motivos. El primero que no exista esta variable, es decir no llegamos a la pagina a través del formulario, y la segunda, es que realmente estén vacíos. Por eso podemos chequear esto, y en el caso de que no estén ambos vacío escribirlos directamente en el LCD:

  if(not (server.arg("l0").isEmpty()) or 
     not (server.arg("l1").isEmpty())) {
    lcd.clear();
    lcd.print(server.arg("l0")); // texto del input con name='l0'
    lcd.setCursor(0,1);
    lcd.print(server.arg("l1")); // texto del input con name='l1'
  }

Atender a los clientes

Pero nada de lo anterior funcionaria si nosotros no le pedimos al servidor que atienda a las peticiones de los clientes. Eso se logra invocando continuamente a la función miembro handleClient(). Y como es necesario llamarla todo el tiempo, entonces la pondremos en el loop() infinito del Arduino.

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

Código completo y demo

Entonces, el código completo, con comentarios y la configuración del WiFi se ve de la siguiente manera. Y les dejo algunas imágenes de cómo se ve.

#include <Arduino.h>
#include <LiquidCrystal_I2C.h>
#include <ESP8266WebServer.h>
#include "html.h" // acá está declarada lcdpage
LiquidCrystal_I2C lcd{0x3F, 16, 2};
ESP8266WebServer server{80};
void escribir() {
  // Si los campos no están ambos vacíos, se escribe su contenido
  if(not (server.arg("l0").isEmpty()) or not (server.arg("l1").isEmpty())) {
    lcd.clear(); // Se borra el display y se posiciona en (0,0)
    lcd.print(server.arg("l0")); // texto del input con name='l0'
    lcd.setCursor(0,1);
    lcd.print(server.arg("l1")); // texto del input con name='l1'
  }
  server.send(200,"text/html",lcdpage); // se envia el html
}
void setup() {
  // Inicialización del Display
  lcd.init();
  lcd.backlight();
  // Inicialización del WiFi en modo AP
  WiFi.mode(WIFI_AP);
  while(!WiFi.softAP("El LCD Wi-Fi")) {
    lcd.print('.');
    delay(100);
  }
  // Se muestra la IP local del ESP8266
  lcd.setCursor(0, 0);
  lcd.print("IP:");
  lcd.print(WiFi.softAPIP());
  
  // Se configura el WebServer para atender peticiones en '/' 
  //  y '/escribir' (misma respuesta)
  server.on("/",escribir);
  server.on("/escribir",escribir);
  server.onNotFound([]{server.send(404,"text/plain","404 - Pagina no encontrada.");});
  server.begin();
  // Se muestra el mensaje de que el sevidor está funcionado
  lcd.setCursor(0,1);
  lcd.print(" Server Iniciado");
}
void loop() {
  // Atendiendo a las peticiones
  server.handleClient();
}

Podés clonarte o descargarte el proyecto completo desde mi gitlab.

¡Saludos y hasta la próxima!

msavalos

Soy un hobbista: toco la guitarra, mandolina, descompongo en partituras; también soy aficionado a la fotografía, he hecho algún cursillo por ahí; me encanta la programación, sea de un programa de PC para procesar datos, una interfaz gráfica o un microcontrolador; evidentemente linuxero; y, cayéndose de maduro, geek. Hincha del más grande, técnico en electrónica, ex-estudiante de ingeniería en electrónica en la UTN FRBA, Técnico Superior en Informática y Profesor. Doy clases en el nivel medio en el Gobierno de la Ciudad de Buenos Aires e instituciones privadas y a nivel terciario en una sede dependiente de la UTN. Sobre mi afinidad política, bueno, podría decir que soy militante del Software Libre y medio zurdito (no me ofende).

Entradas recientes

Modificación de fuente ATX para laboratorio.

Nivel: Avanzado. La modificación de fuentes ATX para su utilización en laboratorio o comunicaciones es…

3 años hace

FLISoL Shield ARDUINO

El pasado 23 de abril se celebró la 18° edición del FLISoL en la que…

3 años hace

Regulador de tensión MOSFET para Moto.

Circuito del regulador, con los elementos de simulación. Típicamente, las motocicletas de baja cilindrada utilizan…

3 años hace

Review Analizador Lógico de menos de u$s10 en Linux

¿Qué puedo decir? Siempre quise tener un analizador lógico, había visto estos pequeños y baratos…

4 años hace

Oversampling con bajos recursos (+Bits ADC)

Nivel: Intermedio ¿Qué tengo que saber para este post? Entender el uso de un ADC.Programación…

4 años hace

Blue Pill STM32 Digital I/O Board v3.0

Este es uno de los motivos por los cuales decidí no poner este PCB en…

4 años hace

Esta web usa cookies.