como-controlar-un-esp8266-con-api-rest-ajax-y-json

Cómo controlar un ESP32 con API Rest, Ajax y Json

  • 5 min

AJAX y JSON permiten controlar un ESP32 desde una página web usando un API REST sencillo.

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

La idea es servir un API REST desde el ESP32, recibir peticiones AJAX desde la página web y codificar tanto las acciones como las respuestas en formato JSON.

Ahora vamos a convertirlo en algo concreto y aplicable al ESP32, que nos permita interactuar con él, leer información o realizar acciones.

Por tanto ¡tranquilos!. Esta entrada va a ser más sencilla que la anterior, así que si entendisteis la entrada del API REST, esta debería ser coser y cantar.

Supongamos que quisiéramos activar o desactivar una salida digital en el ESP32. Por otro lado, también queremos obtener información, por ejemplo el estado de un pin, una variable o la lectura de un sensor.

¿Cómo hacemos un API Rest funcional, que reciba peticiones AJAX con información en Json? Venga, manos a la obra.

Nuestro programa principal es bastante sencillo, e idéntico al que vimos en la entrada del API Rest.

#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <FS.h>
#include <ArduinoJson.h>

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

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

  InitServer();
}

void loop(void)
{
}
Copied!

Por otro lado, nuestro fichero ‘Server.hpp’ que contiene la definición del servidor, sí ha cambiado. Hemos definido dos endpoint a la URI ‘/LED’ para peticiones GET y POST respectivamente, y hemos asociado las funciones de callback correspondientes

AsyncWebServer server(80);

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

    server.on("/LED", HTTP_GET, getData);
    server.on("/LED", HTTP_POST, [](AsyncWebServerRequest * request){}, NULL, setData);
  
    server.onNotFound([](AsyncWebServerRequest *request) {
        request->send(400, "text/plain", "Not found");
    });

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

Las funciónes relativas al API están definidas en nuestro fichero ‘API.hpp’. Como vemos, tenemos únicamente dos funciones de callback, ‘getData’ y ‘setData’.

En la función ‘getData’ devolvemos una cierta información en JSON con Id y Status. En el ejemplo usamos un número aleatorio pero en nuestro proyecto real leeríamos el estado de un pin, un sensor (o lo que fuera).

Por otro lado, la función ‘setData’ realiza acciones sobre el ESP8266. También recibe un JSON con Id y Status. En el ejemplo, simplemente lo mostramos por pantalla pero, nuevamente, en un ejemplo real actuaríamos sobre una salida digital, un actuador (o lo que quisiéramos).

#include "ESP32_Utils_APIREST.hpp"

void getData(AsyncWebServerRequest *request)
{
   AsyncResponseStream *response = request->beginResponseStream("application/json");
    
   // obtendríamos datos de GPIO, estado...
   StaticJsonDocument<300> jsonDoc;
   jsonDoc["id"] = random(0,10);
   jsonDoc["status"] = random(0,2);
   serializeJson(jsonDoc, *response);
   
   request->send(response);
}

void setData(AsyncWebServerRequest * request, uint8_t *data, size_t len, size_t index, size_t total)
{
  String bodyContent = GetBodyContent(data, len);

  StaticJsonDocument<200> doc;
  DeserializationError error = deserializeJson(doc, bodyContent);
  if (error) { request->send(400); return;}
 
  int id = doc["id"];
  bool ledStatus = doc["status"];
  Serial.println(id);
  Serial.println(ledStatus);
  // hacer acciones con los datos recibidos

  request->send(200);
}
Copied!

Por su parte, nuestra página web es muy sencilla para este ejemplo,

En nuestro ‘index.html’ únicamente tenemos un textbox para introducir el número de pin sobre el que queremos actuar (simulado), y un radio button para elegir el estado ON/OFF. Por otro lado, un botón para solicitar información al ESP8266, y un label para el texto recibido.

<!DOCTYPE html>
<html class="no-js" lang="">
   <head>
      <meta charset="utf-8">
      <meta http-equiv="x-ua-compatible" content="ie=edge">
      <title>ESP8266 Json Ajax</title>
      <meta name="description" content="">
      <meta name="viewport" content="width=device-width, initial-scale=1">
   </head>
 
   <body>
     <div>
      Value:<br>
      <input type="text" id="ledNumber" name="ledNumber" value="10" checked><br>
      <input type="radio" id="ledOn" name="status" value="true" checked>
      <label for="ledOn">ON</label><br>
      <input type="radio" id="ledOff" name="status" value="false">
      <label for="ledOff">OFF</label><br>
      <button type="button" onclick="sendData()">Send data</button>
    </div>
    <div>
      <br>
      <button type="button" onclick="getData()">Get Data!</button>
      <label id="receivedText"/>
    </div>
  </body>
  
    <script type="text/javascript" src="./js/main.js"></script>
</html>
Copied!

Para que todo funcione, necesitamos un pequeño fichero Javascript ‘main.js’, donde usamos un poco de Vanilla Javascript (Javascript “a pelo”, sin librerías) para enviar y recibir la información al ESP8266.

function sendData()
{
  var xhttp = new XMLHttpRequest();

  let ledNumber = document.getElementById('ledNumber');
  let ledStatus = document.querySelector('input[name="status"]:checked');
  let ledData = {
    id: ledNumber.value,
    status: ledStatus.value
  }
  let data = JSON.stringify(ledData);

  xhttp.addEventListener('load', function(event) {
    console.log('OK', xhttp);
  });

  xhttp.addEventListener('error', function(event) {
    console.log('error', xhttp);
  });

  xhttp.open('POST', 'LED');
  xhttp.setRequestHeader('Content-Type', 'application/json');
  xhttp.send(data);
}

function getData()
{
    var xhttp = new XMLHttpRequest();

    xhttp.onreadystatechange = function() {
        if (xhttp.readyState == XMLHttpRequest.DONE) {
           if (xhttp.status == 200) {
        console.log((xhttp.responseText));
        let json = JSON.parse(xhttp.responseText);
              console.log();
        
        let receivedMsg = 'Received: GPIO ' + json.id + ' ' + (json.status == 1 ? "ON" : "OFF");
        document.getElementById('receivedText').textContent = receivedMsg;
           }
           else {
              console.log('error', xhttp);
           }
        }
    };

    xhttp.open("GET", "LED", true);
    xhttp.send();
}
Copied!

Resultado

Ahora subimos todo a nuestro ESP8266 y ejecutamos. Si accedemos a la IP del ESP8266 veremos nuestra sencilla página web.

esp8266-json-ajax-html

Al pulsar en los botones veremos que, en el puerto serie, el ESP32 parsea correctamente la información recibida y muestra las variables.

esp8266-json-ajax-serial

Por otro lado, si pulsamos en el botón, veremos en la consola del desarrollador del navegador que recibimos y parseamos correctamente la información, y que se actualiza el label oportuno en la página web.

esp8266-json-ajax-cliente

Esta es una forma limpia de controlar un LED a través de una web en el ESP32. Nada de peticiones GET con parámetros /?LED=ON ni soluciones improvisadas difíciles de mantener.

El ejemplo sigue siendo pequeño, pero ya tiene la estructura importante: frontend, API REST, JSON y una separación razonable entre datos y presentación.

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