Cómo controlar un ESP8266 o ESP32 con Websocket y Json


Seguimos jugando con nuestro amigo el ESP8266 y el ESP32. Esta vez nos toca ver cómo realizar acciones y recibir información a través de información en JSON a través de websockets.

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 como hacer esto mismo a través de peticiones AJAX contra un API REST. Por otro lado, también hemos visto el uso de websockets en el ESP8266.

Ahora toca modificar la entrada anterior, sustituyendo las peticiones AJAX por Websockets. Lo cual, como ya vimos en la entrada sobre websockets, tiene sus ventajas y desventajas (no os quedéis con que esta forma es "mejor", es "mejor/peor" depende del proyecto).

Y como a estas alturas de la sección ya somos avezados programadores del ESP8266, nos evitamos más introducciones innecesariamente largas y nos metemos "en el atún". Nuestro programa principal es, básicamente, el mismo que las entradas anteriores.

#include <ESP8266WiFi.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 "ESP8266_Utils.hpp"
#include "ESP8266_Utils_AWS.hpp"

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

	InitServer();
	InitWebSockets();
}

void loop(void)
{
}

Únicamente cambian los 'includes' de AJAX por los del websockets, como ya vimos en sus entradas. Por su parte, el fichero 'server.h' queda de la siguiente forma.

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

Incluso más sencillo, que antes, ya que no necesitamos endpoints porque en este ejemplo vamos a funcionar únicamente por websockets.

Por su parte, el fichero 'API.h' idéntico al que teníamos en la entrada anterior, dado que vamos a tener las mismas funciones para recibir y enviar datos (simuladas, en un proyecto activaríamos un pin, leeríamos un sensor, lo que fuera)

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

Y la gracia de nuestro nuevo código, el fichero 'WebSockets.h', que queda así

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

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.

Por parte del cliente, el fichero HTML 'index.html', también 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>

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

Cargamos todo esto en el ESP8266 y, cómo era de esperar, tendremos un comportamiento similar al que teníamos en el caso de AJAX. Es decir, al pulsar el botón "Send data" verificamos en el puerto serie que el ESP8266 ha recibido correctamente la petición.

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.

Bastante divertido ¿verdad? Aunque, seguramente, estaréis un poco cansados de ejemplos "simulados" y de nuestras feísimas páginas de ejemplo. Por cierto, ¿no habíamos visto cómo hacerlas más bonitas con Material Design?

Paciencia. Ahora ya tenemos todos (casi todos) los ladrillos básicos, y en el próximo ejemplo los combinaremos viendo cómo hacer un completo interface Web en Material Design para nuestro ESP8266. 

Descarga el código

Todo el código de esta entrada está disponible para su descarga en Github.

Versión para el ESP8266: https://github.com/luisllamasbinaburo/ESP8266-Examples

Versión para el ESP32: https://github.com/luisllamasbinaburo/ESP32-Examples

Si te ha gustado esta entrada y quieres leer más sobre ESP8266 o el ESP32 puedes consultar la sección tutoriales de ESP8266/32
0 0 votes
Article Rating
Previous Cómo usar Arduino y el IMU 9 ejes L3GD20 + LSM303D
Next Qué son las capas de una PCB
2 Comments
oldest
newest
Inline Feedbacks
View all comments
agustin Rodriguez
1 year ago

hola, estuve tratando de implementar el codigo pero no puedo encontrar la libreria correcta en donde se encuetran los paquetes:
ESP8266WiFi.h
ESPAsyncWebServer.h
FS.h
me gustaria saber cuales son los repositorios de github que las contienen
saludos!