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