como-comunicar-un-esp32-con-una-pagina-web-a-traves-de-mqtt

Cómo comunicar un ESP32 con una página web a través de MQTT

Nueva entrada sobre comunicación con el ESP8266 y ESP32. Esta vez nos toca ver cómo comunicar una página web servida desde el ESP32 a través de MQTT.

Ya hemos visto Cómo usar MQTT asíncrono y Cómo enviar texto en formato Json a través de MQTT. Estas comunicaciones eran entre dispositivos, entre backends. Ahora queremos comunicarlo con una web servida al cliente, con el frontend.

Esto no suele (o no debería) ser tan frecuente, porque tenemos muchas formas de comunicar un ESP8266/ESP32 actuando backend con el frontend. Así hemos visto llamadas Ajax y comunicación por Websockets.

Pero, si nuestro sistema está usando MQTT, puede ser útil emplear el mismo sistema para informar al frontend. Así que, en definitiva, queremos hacer un cliente web para el protocolo MQTT, y servir este cliente desde el propio ESP32.

Aquí es donde nos encontramos el primer problema, cómo podemos comunicar una página web, que vive en su “mundo web” de llamadas HTTP, con una comunicación por MQTT.

Comunicar una web por MQTT

Una página web se comunica a través de HTTP, que es un protocolo que actúa sobre la capa de Aplicación (capa 7 del modelo OSI). Esta, a su vez, opera sobre TCP que actúa sobre la capa de transporte (capa 4). Es decir, salvo ciertas excepciones o cambios en el futuro (HTTP 3?) una página web sólo puede comunicarse a través de peticiones HTTP, websockets, o WebRTC.

¿Y MQTT como encaja en esto? MQTT es un protocolo también de la capa de Aplicación (capa 7) que generalmente opera sobre TCP/IP. Resumiendo, MQTT tira directamente contra un socket y no va a entrar en una petición HTTP.

¿Entonces no podemos conectarlos? Bueno, aquí vienen los Websockets al rescate. Podemos configurar MQTT para que funcione sobre Websockets, que como hemos dicho, sí podemos manipular desde una página web.

Pero antes necesitamos configurar nuestro broker para que acepte Websockets. Si esto es posible, o cómo configurarlo, dependerá del broker que estemos usando. En el resto de la entrada vamos a verlo como si tuviéramos una instalación local de Mosquitto como vimos en esta entrada.

Y, por otro lado, vamos a necesitar una librería de JavaScript que nos permita leer esta comunicación MQTT a través de Websockets desde la página web servida.

Configurar Mosquitto para usar Websockets

Si estamos usando Mosquitto como broker MQTT, la comunicación por Websockets viene desactivada por defecto. Así que deberemos activarla en la configuración para podernos comunicar desde una página web.

mqtt-mosquitto

Para ello, editamos el fichero de ‘mosquitto.conf’, que tendréis en la carpeta donde hayáis instalado Mosquitto. Aquí buscad la sección “Extra listeners” y descomentáis las líneas de Websockets, quitando el símbolo ’#’ del principio de la línea.

## =================================================================

## Extra listeners

## =================================================================
...
listener 9001
protocol websockets
...

Así hemos añadido un listener extra para Mosquitto, a través de Websockets actuando en el puerto 9001.

Librería Paho JavaScript Client

El otro componente que necesitamos para nuestra comunicación es una librería de JavaScript para MQTT. Existen varias librerías, pero la más empleada es la librería Paho JavaScript Client de Eclipse.

Es una librería que está disponible en varios lenguajes, incluidos Java, JavaScript, Python, C/C++, Rust y Golang. Más información en la página del proyecto https://www.eclipse.org/paho

pago_logo

Ejemplo de código

Ahora que hemos configurado nuestro broker para aceptar comunicación por Websockets, y con nuestra librería Paho JS Client, toca juntarlo todo para ver un ejemplo.

El programa que subiremos al ESP32, encargado de hacer la comunicación MQTT, es básicamente igual al de la entrada anterior. Por tanto, únicamente vamos a comentar las diferencias, y en caso de dudas podéis consultar el tutorial anterior.

El código del bucle principal del programa queda de la siguiente forma. La diferencia es que hemos añadido una llamada a ‘InitServer()’ para poder servir la página web desde el ESP32. Además, hemos añadido los includes necesarios.

#include <WiFi.h>
#include <SPIFFS.h>
#include <ESPAsyncWebServer.h>
#include <AsyncMqttClient.h>
#include <ArduinoJson.h>

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

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

  delay(500);

  WiFi.onEvent(WiFiEvent);
  InitMqtt();

  ConnectWiFi_STA();
  InitServer();
}

void loop()
{
  PublishMqtt();

  delay(1000);
}

La otra diferencia es el fichero ‘Server.hpp’, donde hemos metido la lógica asociada a servir la página web. Simplemente estamos sirviendo una página web desde SPIFFS, algo que ya hemos visto en el resto de entradas de la serie.

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

Ahora vamos a lo realmente nuevo, que es la página que estamos sirviendo, y que debe actuar como un cliente de comunicación MQTT.

En fichero ‘index.html’ tendría la siguiente forma.

<!doctype html>
<html lang="">

<head>
  <title>ESP32 MQTT</title>
  <meta charset="utf-8">
  <meta http-equiv="x-ua-compatible" content="ie=edge">
  <title></title>
  <meta name="description" content="">
  <meta name="viewport" content="width=device-width, initial-scale=1">
</head>

<body>
  <div id="app">
  </div>

  <script type="text/javascript" src="./vendor/nativeWs.min.js"></script>
  <script type="text/javascript" src="./vendor/mqttws31.min.js" ></script>

  <script type="text/javascript" src="./js/API.js"></script>
  <script type="text/javascript" src="./js/app.js"></script>
</body>

</html>

En el fichero ‘API.js’ tenemos encapsuladas las funciones asociadas al flujo de trabajo de nuestra aplicación. A destacar, en el método onConnect() nos suscribimos al topic ‘hello/world’.

Por su parte, el método onMessageArrived recibe un texto formateado como JSON, lo parsea, y usa un poquito de Vanilla JavaScript para añadir el contenido del mensaje a la página web. El resto de funciones de este ejemplo únicamente muestran por consola los datos para debug.

function onConnect() {

  var options = {
    qos: 0,
    onSuccess: onSubSuccess,
    onFailure: onSubFailure
  };
  client.subscribe('hello/world', options);
}

function onFailure(message) {
  console.log(message)
}

function onConnectionLost(responseObject) {
  if (responseObject.errorCode !== 0) {
    console.log("onConnectionLost:" + responseObject.errorMessage);
  }
}

function onMessageArrived(message) {
  console.log(message)
  var topic = message.destinationName;
  var payload = message.payloadString;

  let json = JSON.parse(payload);

  var element = document.getElementById('app');
  var newElement = document.createElement('div');
  newElement.appendChild(document.createTextNode(json.data));
  element.appendChild(newElement);
}

function onSubFailure(message) {
  console.log(message)
}

function onSubSuccess(message) {
  console.log(message)
}

Finalmente, la lógica de nuestra página la tenemos en el fichero ‘app.js’. Definimos un cliente de MQTT con la librería Paho, donde especificamos la dirección de nuestro broker, el puerto de escucha para Websockets, y un identificador único para el cliente.

Para el identificador hemos usado una función que genera GUID de forma aleatoria, aunque podríamos haber usado cualquier otra, siempre que no repitamos identificador de cliente. Por último, hemos asociado cada los eventos del cliente de MQTT a nuestras funciones del fichero ‘API.js’

function createGuid() {
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
    var r = Math.random() * 16 | 0,
      v = c === 'x' ? r : (r & 0x3 | 0x8);
    return v.toString(16);
  });
}

client = new Paho.MQTT.Client("192.168.1.150", 9001, createGuid())

var options = {
  onSuccess: onConnect,
  onFailure: onFailure
};

client.onConnectionLost = onConnectionLost;
client.onMessageArrived = onMessageArrived;

client.connect(options);

Resultado

Con esto, subimos tanto el programa como la web al ESP32, ejecutamos todo, y accedemos a la página web servida desde el ESP32. Si todo ha salido bien veremos aparecer los mensajes recibidos que se irán añadiéndola la página web servida.

esp32-mqtt-web-resultado

En este ejemplo simplemente contiene el valor de Millis(). Lógicamente, en un proyecto real los datos en formato JSON contendrían más información. Esto lo veremos en la última entrada de la serie. Pero antes, en la próxima veremos cómo hacer esto mismo usando VueJS. ¡Hasta la próxima!

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