comunicar-una-pagina-web-con-un-esp8266-con-peticiones-ajax

Comunicar una página web con un ESP8266 o ESP32 con peticiones AJAX

Continuamos con los tutoriales del ESP8266 y el ESP32. Ahora sí, llegamos a los aspectos más interesantes de este interesante MCU. Esta vez nos toca ver cómo realizar peticiones AJAX.

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.

Llevamos varias entradas dedicadas al resolver cómo comunicar una página web servida al cliente con el ESP8266 actuando como backend.

Así, empezamos viendo varios ejemplos sobre formularios web, como forma sencilla de enviar datos desde el cliente al ESP8266. Esto nos ha permitido presentar conceptos como endpoint, backend, response, o el uso de Javascript. Sin embargo, ya adelantamos que no era la mejor opción disponible.

El principal problema con los formularios web es que, tras el envío el cliente tiene que refrescar la página web. Lo cual hace 20 años estaba muy bien, pero en esta época de aplicaciones web, no es demasiado tolerable.

Tenemos muchas tecnologías disponibles para resolver esta situación. La primera que vamos a ver es AJAX (Asynchronous JavaScript And XML) un mecanismo que permite realizar llamadas http en el “background” de una página web, sin necesitar de recargar la página.

Realmente AJAX es un conjunto de componentes, siendo el más importante el objeto ‘XMLHttpRequest’

Durante una petición AJAX, el cliente envía una solicitud al servidor enviando (opcionalmente) una serie de datos. A continuación, el cliente recibe una respuesta del servidor que contiene (opcionalmente) otra serie de datos.

Para ilustrar el funcionamiento de AJAX vamos a hacer un sencillo ejemplo en el que la página web servida va a mostrar el valor ‘millis()’ recibidos ESP8266. Por supuesto, en un proyecto real el ESP8266 haría algo con los datos recibidos (encender una luz, accionar un motor) o el cliente con los datos recibidos (el valor de una entrada, la medición de un sensor).

Con nuestro sencillo ejemplo de recibir ‘millis()’ es suficiente para ilustrar el mecanismo de peticiones AJAX, que es lo que nos interesa, sin complicarlo con cosas adicionales que no harían más que despistar.

Para hacer nuestras peticiones AJAX vamos a necesitar JavaScript (¿se veía venir no? por eso se llama JavaScript asíncrono). Vamos a usar un poco de Vanilla JavaScript, es decir, JavaScript ‘a pelo’, sin librerías o frameworks adicionales.

¿Suena bien verdad? ¡pues al trabajo!

Por un lado, el programa principal del programa queda exactamente igual que en los ejemplos anteriores.

#include <ESP8266WiFi.h>
#include <ESPAsyncWebServer.h>
#include <FS.h>

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

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

  InitServer();
}

void loop(void)
{
}

Por otro lado, tenemos el fichero ‘Server.hpp’, donde hemos definido un endpoint para /GetMillis bajo el método GET, que tiene la función GetMillis() como callback. Esta función simplemente devuelve el valor de millis() como cadena de texto.

AsyncWebServer server(80);

String GetMillis()
{
  return String(millis(), DEC);
}

void InitServer()
{
  server.serveStatic("/", SPIFFS, "/").setDefaultFile("index.html");

  server.on("/GetMillis", HTTP_GET, [](AsyncWebServerRequest *request) {
    request->send(200, "text/plain", GetMillis());
  });

  server.onNotFound([](AsyncWebServerRequest *request) {
    request->send(400, "text/plain", "Not found");
  });

  server.begin();
    Serial.println("HTTP server started");
}

Destacar que, hasta aquí, todo el código del servidor es casi igual a como lo hubiéramos hecho para un formulario web. El servidor expone ciertos endpoint

Por otro lado, tenemos nuestra página web ‘index.html’ dentro de la carpeta ‘data’

Una sencillísima página web donde, básicamente, únicamente tenemos un ‘div’ con id ‘counterDiv’, que usaremos para mostrar el valor de ‘millis()’ recibido.

<!DOCTYPE html>
<html class="no-js" lang="">
   <head>
      <meta charset="utf-8">
      <meta http-equiv="x-ua-compatible" content="ie=edge">
      <title>ESP8266 Ajax</title>
      <meta name="description" content="">
      <meta name="viewport" content="width=device-width, initial-scale=1">
   </head>
 
   <body>
    <h1>Millis</h1>
        <div id="counterDiv">---</div>
    </body>
  
    <script type="text/javascript" src="./js/main.js"></script>
</html>

Además, hemos incluido la referencia a nuestro fichero de Javascript ‘main.js’, dentro de la carpeta ‘js’. En este fichero ‘main.js’ es donde reside “la magia” de nuestro Ajax

var counterDiv = document.getElementById('counterDiv');

function updateCounterUI(counter)
{
  counterDiv.innerHTML = counter; 
}

function ajaxCall() {
    var xmlhttp = new XMLHttpRequest();

    xmlhttp.onreadystatechange = function() {
        if (xmlhttp.readyState == XMLHttpRequest.DONE) {
           if (xmlhttp.status == 200) {
              updateCounterUI(xmlhttp.responseText);
           }
           else {
              console.log('error', xmlhttp);
           }
        }
    };

    xmlhttp.open("GET", "GetMillis", true);
    xmlhttp.send();
}

(function scheduleAjax() {
    ajaxCall();
    setTimeout(scheduleAjax, 1000);
})();

Vamos a analizar este fichero de JavaScript. De abajo arriba, tenemos al final la función ‘scheduleAjax()‘. El objetivo de esta función es simplemente llamar cada segundo a la función ‘ajaxCall()‘. En un proyecto “normal”, no tendríamos porque tener este componente (o sí, depende). La llamada AJAX se podría desencadenar ante una acción del usuario (como pulsar un botón).

La petición AJAX en si se realiza dentro de esta función ‘ajaxCall()‘. Por un lado, instanciamos un objeto ‘XMLHttpRequest’. A continuación, asociamos los callback a ejecutar en caso de éxito (updateCounterUI) y en caso de error (mostrar el error por la consola).

A continuación, se ejecuta la llamada AJAX con el método ‘Open’ al endpoint ‘/GetMillis’ bajo el método GET, y ejecutamos la función ‘Send()‘. En este ejemplo no estamos enviando información, pero podríamos enviar un String en el método ‘Send()‘.

Finalmente, tenemos la función ‘updateCounterUI’, que se ejecuta si la petición AJAX ha tenido éxito, y a la que pasamos el contenido de la respuesta ‘xmlhttp.responseText’. Esta función simplemente coge el texto recibido (el valor de ‘Millis()’) y sustituye el contenido de ‘counterDiv’ por la respuesta recibida.

Por su parte hemos asociado ‘counterDiv’ con el ‘div’ correspondiente del DOM (la representación de la página web cargada) con la función ‘document.getElementById(‘counterDiv’)’

Ahora, si cargamos la página web, veremos que el valor aparece el contador de Millis() refrescándose cada segundo.

esp8266-ajax-resultado

Repasemos

Repasemos lo que está pasando aquí:

  • Hemos servido una página web, que contiene un JavaScript
  • Esta JavaScript realiza una llamada AJAX a un endpoint del servidor cada segundo
  • El servidor está respondiendo con el contenido de Millis()
  • El JavaScript recibe la respuesta, y cambia el DOM dinámicamente para mostrar el valor recibido

En resumen, en una petición AJAX el cliente inicia la comunicación y lanza una petición HTTP “en el fondo”, enviando una serie de información, y recibiendo otra, sin necesidad de actualizar la página.

¿Y si quisiéramos que fuera el servidor el que iniciara la comunicación? Pues, básicamente, no podríamos hacerlo con AJAX. Tendríamos que ir a otras tecnologías como Websocket o MQQT, que veremos en próximas entradas.

En realidad, hay técnicas de hacer algo aproximado con AJAX, como Comets/Long pooling, pero no son convenientes para usarse en un ESP8266.

Sin embargo, que haya otras tecnologías como Websocket o MQTT no significa que sean mejores o sustituyan a AJAX. Son tecnologías complementarias y cada una tiene su uso. Por ejemplo, ambas alternativas conllevan un uso mayor de recursos (podemos atender a menos clientes).

Muchos proyectos (la mayoría) el servidor ofrece una serie de endpoint bajo ciertos tipos de petición configurando un API. Unicamente es necesario recibir peticiones (normalmente AJAX) a estos endpoint para bien obtener información, o realizar acciones.

Donde no será adecuado usar AJAX es donde necesitemos altas velocidades de respuesta. Es decir, cuando necesitemos reaccionar rápidamente a cambios en el backend (p.e. detectar la pulsación en un botón) o una alta velocidad de transmisión (p.e. efectos de animaciones en LED)

Pero profundizaremos en todo esto en la próxima entrada sobre el ESP8266, donde veremos el uso de Websockets. ¡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