como-controlar-un-esp8266-con-websocket-y-json

Cómo controlar un ESP32 con WebSocket y Json

  • 5 min

WebSocket y JSON permiten controlar un ESP32 desde una página web con mensajes bidireccionales y estructurados.

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 combinar dos piezas que ya conocemos por separado: WebSockets para mantener una conexión abierta con la página web, y JSON para codificar acciones, estados y respuestas de forma cómoda.

En la práctica es una variante del enfoque basado en API REST y AJAX, pero sustituyendo las peticiones HTTP por WebSockets. No es que una opción sea siempre mejor que la otra: depende del proyecto, del refresco necesario y de cómo queramos gestionar la comunicación.

En este ejemplo mantendremos la lógica de acciones y lecturas simuladas, para centrarnos en el intercambio de mensajes.

Nuestro programa principal es muy parecido al de otros ejemplos de servidor web. Aquí cambian los includes y la inicialización de la parte WebSocket.

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

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

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

  InitServer();
  InitWebSockets();
}

void loop(void)
{
}
Copied!

Por su parte, el fichero ‘Server.hpp’ es incluso más sencillo que antes, ya que no necesitamos endpoints

AsyncWebServer server(80);

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

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

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

Por su parte, el fichero API.hpp contiene las funciones para recibir y enviar datos. En el ejemplo están simuladas, pero en un proyecto real aquí podríamos activar un pin, leer un sensor o ejecutar cualquier acción.

void setData(AsyncWebSocketClient * client, String request)
{
  StaticJsonDocument<200> doc;
  DeserializationError error = deserializeJson(doc, request);
  if (error) { return; }

  int id = doc["id"];
  bool ledStatus = doc["status"];
  Serial.println(id);
  Serial.println(ledStatus);
}

void getData(AsyncWebSocketClient * client, String request)
{
   String response;
   StaticJsonDocument<300> jsonDoc;
   jsonDoc["id"] = random(0,10);
   jsonDoc["status"] = random(0,2);
   serializeJson(jsonDoc, response);
   
   client->text(response);
}
Copied!

Y la gracia de nuestro nuevo código, el fichero ‘WebSockets.hpp’, donde hemos definido un AsyncWebSocket que escucha a peticiones del cliente. Estas están “wrapeadas” dentro de un objeto ‘commando’, que contiene el tipo de acción y el payload. En función del tipo de acción, lanzamos la función de callback del API oportuna.

AsyncWebSocket ws("/ws");

void ProcessRequest(AsyncWebSocketClient * client, String request)
{
  Serial.println(request);
  StaticJsonDocument<200> doc;
  DeserializationError error = deserializeJson(doc, request);
  if (error) { return; }
  
  String command = doc["command"];
  if(command == "Set")
  {
    setData(client, request);
  }
  if(command == "Get")
  {
    getData(client, request);
  }
}
Copied!

Lo que cambia es el fichero de Javascript ‘main.js’, donde hemos sustituido el código para realizar y recibir las peticiones AJAX, por el código necesario para los Websockets.

var connection = new WebSocket('ws://' + location.hostname + '/ws', ['arduino']);

connection.onopen = function () {
  console.log('Connected: ');
};

connection.onerror = function (error) {
  console.log('WebSocket Error ', error);
};

connection.onmessage = function (e) {
  console.log('Server: ', e.data);
  processData(e.data);
};

connection.onclose = function () {
  console.log('WebSocket connection closed');
};

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

  connection.send(json);
}

function getData()
{
  let data = {
    command : "Get"
  }
  let json = JSON.stringify(data);

  connection.send(json);
}

function processData(data)
{
  let json = JSON.parse(data); 
  console.log(json);
  let receivedMsg = 'Received: GPIO ' + json.id + ' ' + (json.status == 1 ? "ON" : "OFF");
  document.getElementById('receivedText').textContent = receivedMsg;
}
Copied!

Por parte del cliente

El fichero HTML ‘index.html’, es idéntico al ejemplo anterior

<!DOCTYPE html>
<html class="no-js" lang="">
   <head>
      <meta charset="utf-8">
      <meta http-equiv="x-ua-compatible" content="ie=edge">
      <title>ESP8266 Json Websocket</title>
      <meta name="description" content="">
      <meta name="viewport" content="width=device-width, initial-scale=1">
   </head>
 
   <body>
     <div>
      Radio:<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!

Resultado

Cargamos todo esto en el ESP32 y tendremos un comportamiento similar al caso de AJAX, pero usando WebSockets como canal de comunicación. esp8266-json-ajax-html

Es decir, al pulsar el botón “Send data” verificamos en el puerto serie que el ESP32 ha recibido correctamente la petición.

esp8266-json-ajax-serial

Mientras que al pulsar el botón “Get data” comprobamos en la consola de desarrollador del navegador que, efectivamente, recibimos la petición, y vemos como el label de la página se actualiza en consonancia.

esp8266-json-ajax-cliente

Este ejemplo sigue siendo deliberadamente sencillo, pero ya reúne los elementos importantes: una interfaz web, mensajes JSON y comunicación bidireccional. A partir de aquí podemos construir paneles de control más completos sin cambiar la idea de fondo.

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