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

  • 7 min

Una página web servida desde un ESP32 puede comunicarse por MQTT usando WebSockets y una librería JavaScript como PAHO.

MQTT suele aparecer como una comunicación entre dispositivos o servicios backend. En este caso queremos llevar esos mensajes hasta el frontend, de forma que una página web pueda recibir datos publicados en el broker.

No siempre será la opción más simple, porque un ESP32 actuando como backend puede comunicarse con el frontend mediante llamadas Ajax, WebSockets propios u otras técnicas. Pero, si el sistema ya usa MQTT, puede ser útil emplear el mismo canal para informar a la web.

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 mantiene la misma base de comunicación MQTT que ya hemos usado en otros ejemplos. Aquí nos centraremos en las partes necesarias para servir la página web y conectarla con el broker.

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

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

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>
Copied!

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)
}
Copied!

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);
Copied!

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 el mensaje simplemente contiene el valor de millis(). En un proyecto real, los datos en formato JSON contendrían más información: estados, medidas, alarmas o comandos. La misma idea se puede llevar después a una interfaz más completa con VueJS o cualquier otro framework frontend.

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