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


Continuamos con la serie de entradas dedicadas a comunicación con el ESP8266 / ESP32, viendo cómo comunicar una aplicación en Vue a través de MQTT.

Recordamos que en la entrada anterior hemos visto cómo comunicar un ESP8266 o ESP32 con una página web servida desde el mismo a través de MQTT gracias a la librería PAHO.

En este tutorial empleamos vanila JavaScript para leer el formato Json y añadir el valor recibido a la página. Pero, conociéndonos, sabíamos que el siguiente paso iba a ser integrarlo en una App en VueJs.

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

Anuncio:

En definitiva, vamos a hacer algo así.

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. Por un lado, el bucle principal del programa no se ha modificado respecto al ejemplo anterior. Recordamos que era el siguiente.

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

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.

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

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

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. Así, 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>

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

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

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

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.

Hasta aquí la entrada de hoy, y la penúltima de esta serie de comunicación. En la siguiente y última entrada, veremos cómo ampliar este proyecto para hacer un interface web completo a través de MQTT. Nos vemos en la siguiente.

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

Anuncio:

Previous Partes de una pieza impresa en 3D
Next Sensor de calidad ambiental con Arduino y BME680