como-servir-un-api-rest-con-json-desde-esp8266

Cómo emplear un ESP8266 o ESP32 como servidor de un API Rest con Json

Continuamos con la serie de entradas dedicadas al ESP8266 y el ESP32 viendo cómo servir un API Rest correctamente estructurado desde un ESP8266.

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 la entrada anterior vimos cómo consumir un API Rest actuando como cliente. Recordamos que el API Rest se ha convertido en un estándar en la comunicación Web en el que, de forma muy resumida, las acciones se trasmiten a través de peticiones HTTP y usamos el formato Json para el intercambio de datos.

De igual forma que en la entrada anterior aprendimos a conectarnos a un API Rest existente, por ejemplo, en otro servidor, para poder realizar acciones desde el ESP8266, es interesante que cuando el ESP8266 actué como servidor proporcione su propio API Rest como forma de comunicación.

Es precisamente a lo que vamos a dedicar esta entrada, a configurar un ESP8266 como servidor de un API Rest, interpretar las peticiones y sus datos para ejecutar acciones, e intercambiar información empleando la librería Arduino Json que vimos en esta entrada.

Por ejemplo, podríamos devolver los datos de un sensor de temperatura o humedad. O ejecutar acciones como mover un robot, o accionar un mecanismo. O incluso, simplemente comunicarnos e intercambiar información con otro dispositivo, como por ejemplo una Raspberry PI o incluso otro ESP8266.

Básicamente vamos a emular el API Rest que usamos en todas las entradas, que tenéis disponible en este enlace en versión Node.js, y que empleamos como servidor en la entrada anterior donde el ESP8266.

El código aquí empieza a ser algo avanzado. En realidad, vamos a ver que no es tan difícil. Pero si que resulta interesante que tengáis leídas y bien leídas los tutoriales anteriores sobre el ESP8266.

Así que ¡Vamos allá!

Nuestro código del programa principal queda realmente sencillo, como viene siendo habitual

#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() 
{
  Serial.begin(115200);

  ConnectWiFi_STA();

  InitServer();
}

void loop() 
{
}

La mayor parte de la ‘magia’ ocurre en el fichero ‘Server.hpp’, donde asociamos los distintos verbos y acciones disponibles en el ejemplo a las funciones correspondientes de nuestro API.

AsyncWebServer server(80);

void homeRequest(AsyncWebServerRequest *request) {
  request->send(200, "text/plain", "Hello, world");
}

void notFound(AsyncWebServerRequest *request) {
  request->send(404, "text/plain", "Not found");
}

void InitServer()
{
  server.on("/", HTTP_GET, homeRequest);
  server.on("/item", HTTP_GET, getRequest);
  server.on("/item", HTTP_POST, [](AsyncWebServerRequest * request){}, NULL, postRequest);
  server.on("/item", HTTP_PUT, [](AsyncWebServerRequest * request){}, NULL, putRequest);
  server.on("/item", HTTP_PATCH, [](AsyncWebServerRequest * request){}, NULL, patchRequest);
  server.on("/item", HTTP_DELETE, deleteRequest);
  
  server.onNotFound(notFound);

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

Estas funciones del API las tenemos definidas en el fichero ‘API.hpp’ (lógico ¿verdad?).

#include "ESP8266_Utils_APIREST.hpp"

const char* PARAM_FILTER = "filter";

void getAll(AsyncWebServerRequest *request)
{
  String message = "Get All";
  Serial.println(message);
  request->send(200, "text/plain", message);
}

void getFiltered(AsyncWebServerRequest *request)
{
  String message = "Get filtered by " + request->getParam(PARAM_FILTER)->value();
  Serial.println(message);
  request->send(200, "text/plain", message);
}

void getById(AsyncWebServerRequest *request)
{
  int id = GetIdFromURL(request, "/item/");

  String message = String("Get by Id ") + id;
  Serial.println(message);
  request->send(200, "text/plain", message);
}

void getRequest(AsyncWebServerRequest *request) {
  
  if (request->hasParam(PARAM_FILTER)) {
    getFiltered(request);
  }
  else if(request->url().indexOf("/item/") != -1)
  {
    getById(request);
  }
  else {
    getAll(request);
  }
}

Finalmente, siguiendo la filosofía de la serie de realizar componentes reutilizables para mejorar la limpieza de nuestros proyectos, tenemos el fichero ‘ESP8266_Utils_APIREST.hpp’ que ya usamos en la entrada anterior, con funciones útiles para nuestros API Rest.

Este fichero contiene funciones reutilizables que podemos emplear en nuestro proyecto, y que son empleadas por el fichero ‘API.hpp’ para realizar el trabajo del API Rest.

void postRequest(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;}

  String string_data = doc["data"];
  String message = "Create " + string_data;
  Serial.println(message);
  request->send(200, "text/plain", message);
}

void patchRequest(AsyncWebServerRequest * request, uint8_t *data, size_t len, size_t index, size_t total)
{
  int id = GetIdFromURL(request, "/item/");
  String bodyContent = GetBodyContent(data, len);
  
  StaticJsonDocument<200> doc;
  DeserializationError error = deserializeJson(doc, bodyContent);
  if (error) { request->send(400); return;}

  String string_data = doc["data"];
  String message = String("Update ") + id + " with " + string_data;
  Serial.println(message);
  request->send(200, "text/plain", message);
}

void putRequest(AsyncWebServerRequest * request, uint8_t *data, size_t len, size_t index, size_t total)
{
  int id = GetIdFromURL(request, "/item/");
  String bodyContent = GetBodyContent(data, len);
   
  StaticJsonDocument<200> doc;
  DeserializationError error = deserializeJson(doc, bodyContent);
  if (error) { request->send(400); return;}

  String string_data = doc["data"];
  String message = String("Replace ") + id + " with " + string_data;
  Serial.println(message);
  request->send(200, "text/plain", message);
}

void deleteRequest(AsyncWebServerRequest *request) {
  int id = GetIdFromURL(request, "/item/");

  String message = String("Delete ") + id;
  Serial.println(message);
  request->send(200, "text/plain", message);
}

int GetIdFromURL(AsyncWebServerRequest *request, String root)
{
  String string_id = request->url();
  string_id.replace(root, "");
  int id = string_id.toInt();
  return id;
}

String GetBodyContent(uint8_t *data, size_t len)
{
  String content = "";
  for (size_t i = 0; i < len; i++) {
    content .concat((char)data[i]);
  }
  return content;
}

Resultado

Si cargamos este código en el ESP8266, y lanzamos las peticiones correspondientes a nuestro API Rest desde el Postman, veremos que obtenemos las respuestas correctamente.

esp8266-servir-api-rest-resultado

También podemos verificar que todo funciona correctamente desde la consola de puerto serie de Arduino.

ESP8266-servir-api-rest-serial-port

¡Ahí es nada! Ya hemos aprendido a configurar y servir un API Rest correctamente formateado desde un ESP8266 usando formato Json para el intercambio de datos. Podéis usar la base de este ejemplo en vuestros proyectos para proporcionar una forma normalizada de comunicación.

¿Entonces, ya hemos terminado? ¡Para nada! Consumir y, especialmente, servir API Rest es un importante hito en el camino, pero no es un final. Es más bien un buen comienzo. Aún tenemos que ver cómo emplearla en nuestros proyectos, por ejemplo, para controlar un robot o hacer un datalogger.

Y para eso, en las próximas entradas nos centraremos en interactuar con nuestros flamantes API Rest desde páginas web servida al cliente desde el ESP8266. ¡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