Cómo usar AJAX en un ESP32

En este tutorial aprenderás cómo usar ajax en un ESP32.

¿Por qué se usará AJAX en un webserver del ESP32?

Si viste los tutoriales anteriores de encender un led desde un webserver notarás que cada vez que presionábas el botón de encender el sitio iba a una nueva dirección y la página volvía a cargar.

En este tutorial encenderás dos diodos LED desde un webserver sin que tengas que recargar la página, esto usando AJAX.

¿Qué es AJAX?

AJAX (Asynchronous JavaScript and XML o JavaScript Asíncrono y XML) es un conjunto de técnicas usadas para realizar cambios asíncronos en una página web. Esto nos permite actualizar objetos de un sitio que pueden ser texto o números, por ejemplo, sin tener que recargar la página.

Esto da una experiencia más cómoda. De seguro lo notas cuando recibes un mensaje en la página de Facebook y no tienes que volver a cargar el sitio ni abrir otra ventana.

AJAX no es un lenguaje de programación, sin embargo se centra en JavaScript. En AJAX participa tanto el navegador del cliente como el servidor. Ahora veamos cómo usar ajax en un ESP32.

Conexión

Usaremos dos diodos LED para este ejercicio. Conecta sus catodos a un punto en común de GND. Después uno de los cátodos debe ir con una resistencia de 330 Ohms la cual irá a un pin digital del ESP32, escogimos los pines 22 y 23.

Guíate del siguiente gráfico.

Programación

Para usar ajax en un ESP32 usaremos dos pestañas, o archivos, en el editor de Arduino en los que estarán un archivo .ino para el código del ESP32 y un archivo .h en el que en esta ocasión usaremos para insertar código en HTML, CSS y JavaScript

Código de Arduino para el ESP32 y webserver

/*
  Usar AJAX en el webserver con el ESP32
  https://www.todomaker.com
*/

//Agregar librerías
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include "index.h"          

//Datos de la red Wifi
const char* ssid = "SSID";
const char* password = "Contraseña";

const char* PARAM_INPUT_1 = "output";
const char* PARAM_INPUT_2 = "state";

//Crear objeto llamado server
//El servidor se ejecutará en el puerto 80
AsyncWebServer server(80);


//Cambia la posición del boton en la web
String processor(const String& var) {
  if (var == "LECTURABOTON") {
    String buttons = "";
    buttons += "<h4>Led - GPIO 22</h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"22\" " + outputState(22) + "><span class=\"slider \"></span></label>";
    buttons += "<h4>Led - GPIO 23</h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"23\" " + outputState(23) + "><span class=\"slider \"></span></label>";
    return buttons;
  }
  return String();
}

String outputState(int output) {
  if (digitalRead(output)) {
    return "checked";
  }
  else {
    return "";
  }
}

void setup() {
  //Inicia el Monitor Serie
  //y configura los pines digitales para los LED
  Serial.begin(115200);
  pinMode(22, OUTPUT);
  digitalWrite(22, LOW);
  pinMode(23, OUTPUT);
  digitalWrite(23, LOW);

  // Conectividad wifi
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print(".");
  }
  Serial.println();
  Serial.print("Conectado, La dirección IP es: ");
  Serial.println(WiFi.localIP());                    // Imprimi la IP del ESP32

  //Llama y envía la página al cliente
  //con los valores obtenidos en processor
  server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
    request->send_P(200, "text/html", index_html, processor);
  });

  // Envia una solicitud GET a <ESP_IP>/update?output=<inputMessage1>&state=<inputMessage2>
  server.on("/update", HTTP_GET, [] (AsyncWebServerRequest * request) {
    String inputMessage1;
    String inputMessage2;

    // Obtiene el valor1 activado <ESP_IP>/update?output=<inputMessage1>&state=<inputMessage2>
    if (request->hasParam(PARAM_INPUT_1) && request->hasParam(PARAM_INPUT_2)) {
      inputMessage1 = request->getParam(PARAM_INPUT_1)->value();
      inputMessage2 = request->getParam(PARAM_INPUT_2)->value();
      digitalWrite(inputMessage1.toInt(), inputMessage2.toInt());
    }
    request->send(200, "text/plain", "OK");
  });

  //Inicia el servidor
  server.begin();
}

void loop() {

}

Explicación del código

Configuraciones iniciales

Añadir las dos librerías necesarias, la primera para usar las funciones de Wifi del ESP32 y la segunda librería para crear un webserver asíncrono.

Mira aquí como descargar e instalar la librería ESPAsyncWebServer.

En cambio index.h es para vincular el archivo de Arduino con la cabecera que está en la otra pestaña.

#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include "index.h"

Declaramos unos parámetros que son para el modo del pin, output, y para su estado.

const char* PARAM_INPUT_1 = "output";
const char* PARAM_INPUT_2 = "state";

Instanciamos el servidor con el nombre server y se ejecutará en el puerto 80.

AsyncWebServer server(80);

Función processor

La función processor nos ayuda a reemplazar código. Si encuentra el valor LECTURABOTON, en este caso está en el archivo vinculado index.h, procederá a reemplazar ese texto por el contenido de la variable buttons. Y buttons contiene el código HTML de los subtítulos en H4 y el de los botones o switch.

Lo que indica esta parte de HTML es que se creará una entrada de datos (input) de tipo checkbox, al cambiar su estado se llamará a la función toggleCheckbox que tomará como parámetro al elemento que estamos creando. Se le asigna un id a cada input, esta vez usamos el GPIO que le corresponde a cada LED.

Luego se agrega un atributo según el resultado de la función outputState(). Y se usa la etiqueta span con el atributo slider para que el input tenga ese aspecto.

String processor(const String& var) {
  if (var == "LECTURABOTON") {
    String buttons = "";
    buttons += "<h4>Led - GPIO 22</h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"22\" " + outputState(22) + "><span class=\"slider \"></span></label>";
    buttons += "<h4>Led - GPIO 23</h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"23\" " + outputState(23) + "><span class=\"slider \"></span></label>";
    return buttons;
  }
  return String();
}

La función outputState usa como parámetro el pin digital conectado al LED. Si el pin detectado está como salida la función devolverá el texto “checked”, de lo contrario no se escribirá algo.

Checked es un atributo que hace que un input esté preseleccionado al cargar el sitio web.

String outputState(int output) {
  if (digitalRead(output)) {
    return "checked";
  }
  else {
    return "";
  }
}

Iniciando la sección setup

Inicia la comunicación serial, declara los pines 22 y 23 como salidas en un estado bajo.

  Serial.begin(115200);
  pinMode(22, OUTPUT);
  digitalWrite(22, LOW);
  pinMode(23, OUTPUT);
  digitalWrite(23, LOW);

Inicia la conexión con la red Wifi y al conectarse imprime la IP local del ESP32.

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print(".");
  }
  Serial.println();
  Serial.print("Conectado, La dirección IP es: ");
  Serial.println(WiFi.localIP()); 

Operaciones con el servidor

Inicia la instancia server y cuando hay un petición exitosa (200) se responde con texto html que está en index_html[] del archivo index.h. Además de la función processor que como se indicó adicionará algunos elementos a la página.

server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
    request->send_P(200, "text/html", index_html, processor);
  });

Después cuando se detecta una petición a la dirección/update mediante GET se crean dos variables de tipo String.

  server.on("/update", HTTP_GET, [] (AsyncWebServerRequest * request) {
    String inputMessage1;
    String inputMessage2;

Las variables guardarán los valores de los parámetros como un entero, pero serán enviadas como un texto al responder la petición.

    if (request->hasParam(PARAM_INPUT_1) && request->hasParam(PARAM_INPUT_2)) {
      inputMessage1 = request->getParam(PARAM_INPUT_1)->value();
      inputMessage2 = request->getParam(PARAM_INPUT_2)->value();
      digitalWrite(inputMessage1.toInt(), inputMessage2.toInt());
    }
    request->send(200, "text/plain", "OK");
  });

Inicia el servidor. Ya que tiene las funciones asíncronas no tenemos que repetir el proceso poniéndolo en loop para detectar cambios.

server.begin();

Código de la pestaña index.h para el sitio web y AJAX

const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
  <title>WebEsp32-Smelpro</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" href="data:,">
  <style>
    html {font-family: Arial; display: inline-block; text-align: center;}
    h2 {font-size: 3.0rem;}
    p {font-size: 3.0rem;}
    body {max-width: 600px; margin:0px auto; padding-bottom: 25px;}
    
    .switch {
      position: relative; 
      display: inline-block; 
      width: 60px; 
      height: 34px} 
       
    .switch input { 
      opacity: 0;
      width: 0;
      height: 0;
    }
    
    .slider {
     position: absolute; 
     cursor: pointer;
     top: 0; 
     left: 0; 
     right: 0; 
     bottom: 0; 
     background-color: #ccc; 
     -webkit-transition: .4s;
     transition: .4s;}
    
    .slider:before {
     position: absolute; 
     content: ""; 
     height: 26px; 
     width: 26px; 
     left: 4px; 
     bottom: 4px; 
     background-color: #fff; 
     -webkit-transition: .4s; 
     transition: .4s; }
     
     input:checked+.slider {
     background-color: #2196F3}

     input:focus + .slider {
     box-shadow: 0 0 1px #2196F3;}
      
     input:checked+.slider:before {
     -webkit-transform: translateX(26px); 
     -ms-transform: translateX(26px); 
     transform: translateX(26px)}
     
  </style>
</head>
<body>

  <h2>Web Server Embebido AJAX</h2>
  %LECTURABOTON%
  
<script>function toggleCheckbox(element) {
  var xhr = new XMLHttpRequest();
  if(element.checked){ xhr.open("GET", "/update?output="+element.id+"&state=1", true); }
  else { xhr.open("GET", "/update?output="+element.id+"&state=0", true); }
  xhr.send();
}
</script>
</body>
</html>
)rawliteral";

Explicación del código del sitio web

Variables para almacenar texto

Para usar AJAX en un ESP32 se inicia declarando una matriz de tipo char llamada index_html. En ella almacenáramos el sitio web que estará conformado los estilos CSS, los elementos HTML y los códigos de JavaScript.

El modificador PROGMEM indica que se almacene en la memoria flash junto con el programa en lugar de hacerlo en la SRAM del ESP32.

Mientras la R es para que todo lo que esté entre los limitadores rawliteral sea tratado como una cadena de texto. De esta forma podemos escribir el código HTML, CSS y JavaScript del sitio web incluso con los saltos de línea.

const char index_html[] PROGMEM = R"rawliteral(

Estructura HTML y CSS

Ahora si entraremos a ver el código del sitio web, principalmente lo que interpreta el navegador. Inicia declarando que el documento es HTML.

Después se abre la etiqueta para la cabecera del sitio, aquí suelen ir configuraciones o datos que no se ven directamente, a excepción del sitio que se mostrará en la pestaña.

<!DOCTYPE HTML><html>
<head>
  <title>WebEsp32-Smelpro</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" href="data:,">

Dentro de la cabecera también podemos agregar los estilos CSS. Para ello abre la etiqueta <style> y dentro se coloca el código que definirá el diseño visual. No vamos a profundizar en su funcionamiento en este tutorial.

Con eso finaliza la configuración de la cabecera.

<style>
    html {font-family: Arial; display: inline-block; text-align: center;}
    h2 {font-size: 3.0rem;}
    p {font-size: 3.0rem;}
    body {max-width: 600px; margin:0px auto; padding-bottom: 25px;}
    
    .switch {
      position: relative; 
      display: inline-block; 
      width: 60px; 
      height: 34px} 
       
    .switch input { 
      opacity: 0;
      width: 0;
      height: 0;
    }
    
    .slider {
     position: absolute; 
     cursor: pointer;
     top: 0; 
     left: 0; 
     right: 0; 
     bottom: 0; 
     background-color: #ccc; 
     -webkit-transition: .4s;
     transition: .4s;}
    
    .slider:before {
     position: absolute; 
     content: ""; 
     height: 26px; 
     width: 26px; 
     left: 4px; 
     bottom: 4px; 
     background-color: #fff; 
     -webkit-transition: .4s; 
     transition: .4s; }
     
     input:checked+.slider {
     background-color: #2196F3}

     input:focus + .slider {
     box-shadow: 0 0 1px #2196F3;}
      
     input:checked+.slider:before {
     -webkit-transform: translateX(26px); 
     -ms-transform: translateX(26px); 
     transform: translateX(26px)}
     
  </style>
</head>

Sigue el cuerpo o body del sitio. Empieza escribiendo un título que se verá en el sitio.

<body>

  <h2>Web Server Embebido AJAX</h2>

Después usamos este indicador en el que irá el resultado de la función processor que está en el archivo principal.

%LECTURABOTON%

Función en JavaScript

Con la etiqueta <script> añadimos código de JavaScript.

Creamos una función llamada toggleCheckbox.

Luego creamos una instancia XMLHttpRequest que será llamada con el nombre xhr. El objeto XMLHttpRequest nos permitirá obtener la información sin tener que recargar la página y usar AJAX en un ESP32

En resumen funciona de la siguiente manera: Si el elemento checkbox tiene el atributo de checked xhr iniciará una petición mediante el método GET hacia la dirección /update?output=”+element.id+”&state=1 de forma asíncrona, esto último se declara con el parámetro true. En cambio si el elemento no tiene el atributo checked la petición se realizará a /update?output=”+element.id+”&state=0.

Posteriormente xhr envía la petición.

Recuerda que element.id hace referencia al id que se le puso a cada switch, este id puede ser 22 o 23 según el pin que se le asignó a cada uno.

<script>function toggleCheckbox(element) {
  var xhr = new XMLHttpRequest();
  if(element.checked){ xhr.open("GET", "/update?output="+element.id+"&state=1", true); }
  else { xhr.open("GET", "/update?output="+element.id+"&state=0", true); }
  xhr.send();
}
</script>

Con esto termina el cuerpo del sitio y el documento HTML. Adicionalmente se vuelve a usar el delimitador rawliteral.

</body>
</html>
)rawliteral";

Demostración

Abre el navegador de tu preferencia en tu teléfono, computadora de escritorio o cualquier otro dispositivo conectado a la misma red a la que conectaste el ESP32. Ingresa a la IP local que obtuviste cuando el ESP32 se conectó a la red Wifi.

Deberías ver lo siguiente, es el sitio web alojado en el webserver del ESP32.

Presiona cualquiera de los siwtch en pantalla y observa como se enciende el led que tiene asignado.

Como notarás la página no tiene que recargar y tampoco se ha cambiado la dirección en la que estamos a diferencia de los ejercicios anteriores.

Video tutorial

También puedes seguir los pasos con este video tutorial que preparamos para ti.

Muchas gracias por leer. Comparte esta publicación con tus amigos y síguenos para más tutoriales.

Te invitamos a tomar el curso de Introducción al ESP32: Introducción al ESP32 | TodoMaker’s School

Previous Post
Next Post