Continuamos con la segunda entrega de nuestro montaje, en el que estamos aprendiendo a usar el protocolo de comunicaciones MQTT. Nos centraremos ahora en el suscriptor, que será el elemento encargado de recibir los mensajes de telemetría. Este suscriptor puede ser una pantalla que muestre datos (como en este montaje) o, también, un dispositivo que actúe de una manera u otra según los datos recibidos. Un ejemplo podría ser una electroválvula para riego que se abre o cierra cuando una sonda detecta un cierto nivel de humedad en el terreno.
¡Os animo a que os pongáis manos a la obra y hagáis el montaje es muy gratificante y os dejo todo lo necesario para hacerlo!
El suscriptor MQTT, desde el punto de vista de la programación, es de naturaleza asíncrona. ¿Qué quiero decir con esto? Pues que podrá realizar acciones como cambiar el texto del display, accionar un relé o guardar valores en una base de datos cuando reciba un mensaje, y esto podría ocurrir en cualquier momento. La forma más común y elegante de hacer esto es mediante una función de ‘callback’. Dependiendo del lenguaje de programación con el que estemos trabajando, a esta función también se le podría llamar ‘evento’, ‘acción’, entre otros términos, pero en esencia es un parámetro, propiedad o variable que aloja un método que se invoca cuando sucede algo específico, desencadenando así la ejecución de un bloque de código.
El montaje del display de Temperatura y Humedad.
Para este montaje usaremos un ESP32 que lo vamos a programar con ayuda de platformio en c++ con las librerías:
WiFi.h
: para la conexión WiFi.PubSubClient.h
: para la conexión y operaciones MQTT.Wire.h
: para comunicación I2C.LiquidCrystal_I2C.h
: para manejar un LCD que utiliza I2C.
El montaje, a nivel de esquema eléctrico, consta de un display LCD de dos líneas y 16 caracteres cada una (16×2). Este se puede conectar de dos maneras: la primera, enviando datos de forma paralela, y la segunda, utilizando un adaptador I2C de serie a paralelo. Con el adaptador I2C, solo necesitamos dos cables para la comunicación con el ESP32, además de otros dos cables para la alimentación eléctrica, uno positivo y otro negativo.
Para el ESP32 con el que estoy trabajado tiene la señal i2c de reloj en el pin GPIO 22 y la señal de datos en el pin GPIO 21 (aunque en realidad no están funcionando aquí como GPIO que es una salida de propósito general sino como puerto I2C). Por otro lado el pin morado es está conectado a 3V3 (3,3 Voltios) positivo y el blanco a GND o negativo.
El código con el framework de arduino:
Ahora pasamos a describir el código fuente que lo podéis encontrar en https://github.com/elrincondeada/mqttdemo/blob/master/LCDMqttClient/src/main.cpp
Lo primero que hacemos es importar las bibliotecas:
#include <WiFi.h> #include <PubSubClient.h> #include <Wire.h> #include <LiquidCrystal_I2C.h>
Aquí se importan las bibliotecas necesarias para la conexión WiFi, el protocolo MQTT, la comunicación I2C y el manejo del LCD.
Definición de constantes y variables globales
#define TOPIC_TEMPERATURA "esp32/temperatura" #define TOPIC_HUMEDAD "esp32/humedad"
Esto se hace para evitar errores al escribir un elemento de código que es constante y se va a usar varias veces. Es este caso el tópico así sabemos que siempre lo vamos a escribir igual y evitamos errores.
// Información de WiFi y MQTT const char* ssid = "XXXXXXXXXXXXXXXXXXX"; const char* password = "XXXXXXXXXXXXXXX"; const char* mqttServer = "XXXXXXXXXXXXX"; const int mqttPort = 1883; //Protipado de funciones void callback(char*, byte*, unsigned int); // Inicializa el cliente WiFi y MQTT WiFiClient espClient; PubSubClient client(espClient); // Inicializa la LCD con dirección I2C, columnas y filas LiquidCrystal_I2C lcd(0x27, 16, 2);
Empezamos con la declaración de variables globales, donde encontrarás ‘char*’. No es un simple arreglo de caracteres, es un puntero en C. Si te interesa profundizar en los fundamentos de C y qué es un puntero, te recomiendo este vídeo https://youtu.be/AUAX9xNenuo
Luego tenemos algo llamado ‘prototipado de función’. Se trata simplemente de un adelanto de la función que jugará un rol principal en este código, la función ‘callback’.
Finalmente, instanciamos el cliente WiFi para establecer la conexión con el exterior, el cliente MQTT para la comunicación de mensajes y el controlador del display LCD 16×2, todo ello gestionado mediante el protocolo I2C.
la función setup()
void setup() { // Inicializa el LCD lcd.init(); lcd.backlight(); // Conexión WiFi Serial.begin(115200); WiFi.begin(ssid, password); // Conexión WiFi while (true) { // Bucle infinito if (WiFi.status() == WL_CONNECTED) { Serial.println("Conectado a WiFi!"); break; // Sale del bucle cuando se conecta } Serial.print("."); delay(200); } // Conexión MQTT client.setServer(mqttServer, mqttPort); client.setCallback(callback); while (!client.connected()) { Serial.println("Conectando a MQTT..."); if (client.connect("ESP32")) { Serial.println("Conectado"); } else { Serial.print("Error de conexión: "); Serial.print(client.state()); delay(2000); } } Serial.println("Suscripción a tópicos"); // Se suscribe al tópico client.subscribe(TOPIC_TEMPERATURA); client.subscribe(TOPIC_HUMEDAD); }
«La función void setup()
es donde toda la magia inicial tiene lugar. Esta función se ejecuta una sola vez al inicio y configura todo lo necesario para que nuestro código funcione de manera óptima.
- Inicialización del LCD: Con las instrucciones
lcd.init()
ylcd.backlight()
, inicializamos el display LCD y encendemos su retroiluminación para hacerlo visible. - Conexión WiFi: Primero, iniciamos la comunicación serie con
Serial.begin(115200)
. Luego, la instrucciónWiFi.begin(ssid, password)
intenta conectar el ESP32 a la red WiFi utilizando las credenciales almacenadas en las variablesssid
ypassword
. - Verificación de Conexión WiFi: Aquí entra en juego un bucle infinito (
while(true)
) que comprueba constantemente si se ha establecido la conexión WiFi. Si la conexión es exitosa (WiFi.status() == WL_CONNECTED
), se imprime un mensaje y se rompe el bucle conbreak
. - Conexión MQTT: Las instrucciones
client.setServer(mqttServer, mqttPort)
yclient.setCallback(callback)
configuran el servidor MQTT y la función de callback que se llamará cuando lleguen mensajes al tópico suscrito. Es el corazón de este desarrollo. - Verificación de Conexión MQTT: Similar al WiFi, hay un bucle
while
que intenta conectar con el servidor MQTT hasta que lo logra. Si hay un error, imprime el estado del intento fallido. - Suscripción a Tópicos: Finalmente,
client.subscribe(TOPIC_TEMPERATURA)
yclient.subscribe(TOPIC_HUMEDAD)
suscriben el cliente MQTT a los tópicos de temperatura y humedad, respectivamente.»
La función de callback:
void callback(char* topic, byte* payload, unsigned int length) { String payloadStr = ""; Serial.println("mensaje recibido"); // Convierte los bytes del payload a String for (int i = 0; i < length; i++) { payloadStr += (char)payload[i]; } Serial.println(payloadStr); // Actualiza el LCD con el mensaje recibido if(strstr(topic,TOPIC_TEMPERATURA)) { lcd.setCursor(0, 0); lcd.print("Temp. "); } if(strstr(topic,TOPIC_HUMEDAD)) { lcd.setCursor(0, 1); lcd.print("Humd. "); } lcd.print(payloadStr); }
La función callback
es el núcleo de cualquier suscriptor MQTT, y aquí es donde reside toda la lógica que determina qué hacer cuando llega un mensaje a los tópicos a los que se ha suscrito el cliente. Vamos a desglosarla:
- Parámetros: La función acepta tres parámetros:
char* topic
,byte* payload
, yunsigned int length
. Eltopic
indica el tópico al cual pertenece el mensaje recibido,payload
es el cuerpo del mensaje en forma de bytes ylength
es la longitud del payload. - Inicialización:
String payloadStr = "";
inicializa una cadena de texto vacía donde almacenaremos el payload convertido. - Mensaje Recibido:
Serial.println("mensaje recibido");
es una forma de debugging que nos indica que ha llegado un nuevo mensaje. - Conversión de Bytes a String: El bucle
for
toma cada byte del payload y lo convierte a su representación de carácter, acumulando todo enpayloadStr
. - Impresión del Payload:
Serial.println(payloadStr);
imprime el payload recibido para verificación. - Actualización de la LCD: Aquí es donde el programa se pone interesante. Si el tópico corresponde a la temperatura (
strstr(topic, TOPIC_TEMPERATURA)
), el cursor de la LCD se posiciona en la primera fila y escribe «Temp.». De forma similar, si el tópico es de humedad, escribe «Humd.» en la segunda fila. - Imprimir Valor: Finalmente,
lcd.print(payloadStr);
imprime el valor del payload en la LCD, justo después de «Temp.» o «Humd.», dependiendo del tópico al que pertenece el mensaje.
función loop
void loop() { client.loop(); }
Esta línea se encarga de mantener el cliente MQTT en funcionamiento y es crucial para el correcto funcionamiento de todo el programa. ¿Por qué? Porque client.loop()
se encarga de procesar cualquier mensaje entrante y saliente, y de mantener la conexión con el servidor MQTT viva. Si no se ejecutara esta función de forma continua en el bucle principal, no se recibirían ni procesarían mensajes MQTT, y el cliente podría incluso desconectarse del servidor.
Por lo tanto, aunque pueda parecer una parte trivial del código, no subestimes su importancia. Actúa como el corazón palpitante de tu cliente MQTT, asegurándose de que todo funcione como un reloj.
Conclusión:
Y ahí lo tienes, la segunda entrega de nuestra serie sobre MQTT, donde nos hemos centrado en el rol del suscriptor. No es magia, es tecnología bien aplicada. Hemos desentrañado el código que corre en un ESP32 y que le permite actuar como un suscriptor MQTT. Desde establecer una conexión Wi-Fi hasta interpretar los mensajes MQTT que le llegan, este pequeño dispositivo hace de todo.
Nuestra función callback
es el cerebro del operativo: espera en silencio hasta que llega un mensaje relevante y luego se pone en acción para mostrarlo en nuestro LCD. Y no olvidemos nuestro persistente void loop()
, el pulso del programa que asegura que el cliente MQTT está siempre listo para actuar.
Si te ha picado el gusanillo de la programación y quieres ir más allá, hay un mundo de posibilidades que puedes explorar. ¿Qué tal integrar una base de datos para guardar los datos recibidos? ¿O quizás quieras controlar otros dispositivos en función de los mensajes recibidos?
Espero que este tutorial te haya proporcionado una buena base para entender cómo funciona un suscriptor MQTT y cómo puedes empezar a jugar con estas tecnologías. Estamos en una era donde los objetos cotidianos se están volviendo cada vez más inteligentes y conectados, y entender estos conceptos básicos te coloca un paso adelante en este emocionante mundo del Internet de las Cosas.
Te animo a que realices tu mismo el montaje. Puedes cambiar el sensor, el display, conectarlo a un asistente domótico, a alexa… Seguiremos experimentando.
¡Hasta la próxima entrega!
Referencias:
- https://github.com/elrincondeada/mqttdemo
- pines del ESP32. http://wiki.fluidnc.com/en/hardware/esp32_pin_reference
- libreria arduino para liquidCrystal 16×2 con i2c. https://github.com/johnrickman/LiquidCrystal_I2C
- cliente mqtt arduino. https://www.arduino.cc/reference/en/libraries/pubsubclient/