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

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

Seguimos con nuestra ESP8266 y el ESP32 viendo cómo recibir datos y ejecutar acciones a través de peticiones AJAX con la información codificada en JSON.

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 ya habíamos llegado a ver cómo servir un API REST que reciba y devuelva información en JSON. Para ello, habíamos usado nuestro API Rest de ejemplo, formulado como vimos en esta entrada sobre NodeJs.

Ahora nos toca “bajar a tierra” todos estos conocimientos, y convertirlo en algo más concreto y aplicable al ESP8266, que nos permita interactuar con él, leyendo información, o realizando 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 entrada digital en el ESP8266. Por otro lado, también queremos obtener información, por ejemplo del estado de un pin, o de una variable, o la lectura de un sensor (o lo que fuera).

¿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 <ESP8266WiFi.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 "ESP8266_Utils.hpp"

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

  InitServer();
}

void loop(void)
{
}

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

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

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>

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();
}

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, efectivamente, en el puerto serie el ESP8266 parsea correctamente la información recibida, y muestra las variables en el puerto serie.

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 la “forma correcta” y limpia de encender un LED a través de Web en el ESP8266. Nada de peticiones GET con parámetros /?LED=ON ni cosas de esas sucias.

¿Complicado? Bueno, espero que si habéis seguido la serie hasta aquí, no lo haya sido tanto. ¡Pero aún podemos mejorarlo! En la próxima entrada veremos cómo hacer algo parecido a través 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