como-comunicar-app-en-vuejs-con-esp32-a-traves-de-mqtt

Cómo comunicar App en VueJs con ESP32 a través de MQTT

  • 5 min

Una aplicación Vue puede consumir mensajes MQTT recibidos desde un ESP32 a través de WebSockets.

En este ejemplo partimos de una página web servida desde el propio ESP32 y usamos la librería PAHO para que el frontend se conecte al broker MQTT mediante WebSockets.

Hasta ahora podíamos resolverlo con JavaScript sencillo: leer el mensaje JSON y añadir el valor recibido a la página. Pero, si la interfaz empieza a crecer, tiene sentido integrarlo en una aplicación VueJs.

Ya que estamos, vamos a modificar el ejemplo para que, en vez de enviar simplemente el valor de millis(), publique información más interesante. Por ejemplo, el estado de un GPIO, simulando la recepción de una alarma.

En definitiva, vamos a hacer algo así.

esp32-mqtt-json-app-vue-resultado

No obstante, en este ejemplo el estado del GPIO va a ser simulado, porque únicamente nos interesa ilustrar la comunicación. Depende de vosotros adaptarlo al hardware y necesidades de vuestro proyecto.

Así que manos a la obra.

El bucle principal del programa mantiene la misma estructura básica que en los ejemplos MQTT anteriores.

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

Si ha cambiado es el fichero ‘MQTT.hpp’, en el que hemos separado la lógica asociada a la comunicación MQTT de nuestro programa. Tampoco hay grandes modificaciones.

Únicamente hemos cambiado el Json que enviamos para que contenga el Id del GPIO y el estado. Que, para el ejemplo, simplemente estamos rellenando como valores aleatorios.

#pragma once

const IPAddress MQTT_HOST(192, 168, 1, 150);
const int MQTT_PORT = 1883;

AsyncMqttClient mqttClient;

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

void SuscribeMqtt()
{
  uint16_t packetIdSub = mqttClient.subscribe("hello/world", 0);
  Serial.print("Subscribing at QoS 2, packetId: ");
  Serial.println(packetIdSub);
}

void PublishMqtt()
{
  String payload = "";

  // obtendriamos datos de GPIO, estado...
  StaticJsonDocument<300> jsonDoc;
  jsonDoc["id"] = random(0, 10);
  jsonDoc["status"] = random(0, 2);
  serializeJson(jsonDoc, payload);

  mqttClient.publish("hello/world", 0, true, (char*)payload.c_str());
}

void OnMqttReceived(char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total)
{
  Serial.print("Received on ");
  Serial.print(topic);
  Serial.print(": ");

  String content = GetPayloadContent(payload, len);

  StaticJsonDocument<200> doc;
  DeserializationError error = deserializeJson(doc, content);
  if(error) return;

  int id = doc["id"];
  bool ledStatus = doc["status"];

  Serial.print(" Id:");
  Serial.print(id);
  Serial.print(" Status:");
  Serial.println(ledStatus);
}
Copied!

Lo que sí va a cambiar sustancialmente es el frontend que servimos al cliente que, como hemos dicho, pasará a ser una App en VueJs.

El fichero ‘index.html’ pasa a ser el siguiente.

<!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>
  <link rel="stylesheet" href="css/main.css">
  <link href="./vendor/google-fonts.css" rel="stylesheet">
  <link href="./vendor/vuetify.min.css" rel="stylesheet">

  <v-app id="app">
    <v-toolbar app>ESP32 MQTT</v-toolbar>
    <v-content>
      <v-container fluid grid-list-md text-xs-center>

        <v-layout row wrap>
          <v-flex>
            <v-card>
              <v-toolbar color="blue" dark>
                <v-toolbar-title class="text-xs-center">Received</v-toolbar-title>
              </v-toolbar>

              <v-timeline
              align-top
              dense
            >
            <mqtt-message v-for="item in mqtt_message_list" :mqtt_message="item" />
            </v-timeline> 
            </v-card>
          </v-flex>

        </v-layout>
      </v-container>
    </v-content>
  </v-app>

  <!-- Desde CDN -->
  <script type="text/javascript" src="./vendor/vue.min.js"></script>
  <script type="text/javascript" src="./vendor/vuetify.min.js"></script>
  <script type="text/javascript" src="./vendor/nativeWs.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.1/mqttws31.min.js" type="text/javascript"></script>

  <!-- Cargamos el fichero que contiene nuestra App en Vue.JS -->
  <script type="text/javascript" src="./js/API.js"></script>
  <script type="text/javascript" src="./js/app.js"></script>
</body>

</html>
Copied!

Por su parte, tenemos el fichero ‘API.js’ que contiene la lógica del front asociada con la comunicación MQTT.

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 mqtt_message = new Object();
    mqtt_message.id = json.id;
    mqtt_message.status = json.status;
    mqtt_message.date = new Date().toISOString().replace("T", " ").replace("Z", " ");
    app.mqtt_message_list.unshift(mqtt_message);
  }
  
  function onSubFailure(message) {
    console.log(message)
  }
  
  function onSubSuccess(message) {
    console.log(message)
  }
  
  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);
    });
  }
Copied!

Finalmente, el fichero ‘App.js’ contiene la aplicación en Vue, y tiene la siguiente forma.

  Vue.component('mqtt-message', {
    props: ['mqtt_message'],
    template: `
        <v-timeline-item
        color="blue"
        small
        >
        <v-layout pt-3>
            <v-flex xs3>
            {{mqtt_message.date}}
            </v-flex>
            <v-flex>
            <strong>GPIO: {{mqtt_message.id}}</strong>
            <div class="caption">Status: {{mqtt_message.status}}</div>
            </v-flex>
        </v-layout>
        </v-timeline-item>
    `
})

var app = new Vue({
    el: '#app',
    data: function () {
        return {
            mqtt_message_list: [

            ]
        }
    },
    mounted() {
        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

Subimos todo esto a nuestro ESP32 y veremos que los mensajes simulados se reciben cada segundo y se añaden al listado de alertas.

esp32-mqtt-json-app-vue-resultado

De igual forma, en el puerto serie del ESP32 también vemos los mensajes recibidos. Que, en este caso, son enviados por el propio dispositivo, para evitarnos tener que usar varios. Pero, si tuviéramos varios dispositivos, todos recibirían simultáneamente.

esp32-mqtt-json-app-vue-serial

Con esto tenemos una aplicación Vue que consume datos MQTT desde el navegador. A partir de esta base podemos ampliar el panel con más estados, comandos y acciones, manteniendo MQTT como canal común entre el ESP32, la web y el resto del sistema.

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