Como ya he comentado, MQTT es un protocolo M2M y son siglas en inglés, Message Queue Telemetry Transport. Está basado en un protocolo de mensajería publicación/suscripción, al contrario que HTTP que es petición/respuesta.
https://programarfacil.com/esp8266/mqtt-esp8266-raspberry-pi/
Uno de sus puntos fuertes es que es extremadamente simple y ligero. Por este motivo es muy interesante para sistemas que requieren poco ancho de banda, tienen una alta latencia y requieren de poco consumo de los dispositivos.
Los objetivos del protocolo MQTT es minimizar el ancho de banda, comunicación bidireccional entre dispositivos, minimizar los requerimientos de los dispositivos tanto recursos como consumo y garantizar la fiabilidad y cierto grado de seguridad.
La latencia es el tiempo desde que se envía un mensaje hasta que llega a su destino. Cuanto más tiempo, más latencia.
En esta sección haré un repaso rápido de cómo se estructura un sistema que utilice el protocolo MQTT. Hablaré de la arquitectura típica.
Lo primero es ver cómo se conectan los dispositivos. Utilizan una topología en estrella es decir, todos los clientes se conectan directamente a un punto central que hace de servidor. En MQTT este servidor se llama Broker.
El Broker es la parte central de la arquitectura y todos los mensajes pasan por él.
Se trata de una arquitectura basada en eventos. Cada mensaje se envía a los receptores que se hayan suscrito a una publicación concreta. El Broker se encarga de distribuir los mensajes a los receptores.
El topic es el tema donde se suscriben los receptores para recibir el mensaje. Voy a hacer una comunicación típica para entender el concepto de arquitectura publicación/suscripción.
Un cliente se suscribe a un topic que es como el tema, a quién va dirigido el mensaje. Lo podemos asemejar como el asunto de un email. Un mensaje no tiene destinatario concreto, puede haber uno, muchos o ninguno.
El emisor, el que envía el mensaje, no sabe a quién va dirigido dicho mensaje, sólo el Broker lo sabe. Cuando llega un nuevo mensaje se lo envía a aquellos que se han suscrito al topic.
El mensaje puede ser cualquier cosa, una temperatura, un 1 o un 0, un texto, … El formato es totalmente libre, lo decide el programador y el tamaño máximo depende de la implementación del protocolo MQTT y no de la especificación.
Como ya he comentado, el topic es el tema del mensaje, a quién va dirigido ese mensaje. Es un concepto que tenemos que tener muy claro así que vamos a verlo con una analogía.
Imagínate una editorial que publica revistas sobre temas técnicos. Tiene dos categorías principales: electrónica y programación.
Dentro de estas categorías, la editorial define unas sub-categorías donde se tratan temas más específicos. Para electrónica tiene placas de desarrollo y componentes. Para programación tiene programación backend y programación frontend.
Por último, están las revistas que van destinadas a algo concreto. Dentro de electrónica/placas de desarrollo hay dos revistas una dedicada a Arduino y otra al ESP8266. En electrónica/componentes hay otras dos revistas dedicadas a los sensores DHT11 y DS18b20.
En la categoría de programación ocurre lo mismo. Dentro de programación/programación backend se publican dos revistas orientadas a Java y NodeJS. Por último en programación/programación frontend se publican otras dos revistas centradas en JavaScript y HTML.
El resumen de esta organización sería el siguiente.
Por ejemplo te puedes suscribir a la revista de Arduino y recibir sólo esa revista. Sin embargo, si te suscribes a la categoría de placas de desarrollo, recibirás todas las revistas de esa categoría: Arduino y ESP8266.
De igual forma, si te suscribes a programación recibirás todas las revistas: Java, NodeJS, JavaScript y HTML. Creo que el concepto de lo que es la publicación/suscripción con un topic o tema queda claro.
Pero los topic dentro de MQTT tienen su propia sintaxis. Por ejemplo, si quieres recibir sólo la revista de Arduino tienes que suscribirte a /editorial maker/electrónica/placas desarrollo/arduino.
El símbolo / es un separador de niveles. Siempre se pone para separar cada uno de los niveles.
Cuando trabajamos con un dispositivo del IoT utilizamos otros nombres para los topic. Por ejemplo, podríamos tener algo como esto para medir la temperatura:
Existen comodines como el símbolo + y #.
El símbolo + se sustituye por cualquier nivel. Si por ejemplo nos queremos suscribir a todos los topic de temperatura podemos hacerlo de la siguiente manera
/casa/+/temperatura
El símbolo + se sustituirá por cada nivel que tenga como nivel superior casa y como nivel inferior temperatura.
El símbolo # también es un comodín. Este símbolo cubre varios nieves aguas abajo. Por ejemplo si quieres suscribirte a todos los mensajes que se envían a casa sólo tienes que hacer lo siguiente
/casa/#
Donde estamos indicando que todos los mensajes que se envíen a cualquier nivel dentro del topic casa lo recibirás.
Lo más importante dentro del protocolo MQTT son los mensajes. Se envían de forma asíncrona es decir, no hay que esperar respuesta una vez que se envía un mensaje.
Cada mensaje consta de 3 partes:
Al ser uno de los objetivos consumir el menor ancho de banda, cada bit está estudiado cuidadosamente para que cumpla con este objetivo.
Una de las características más importantes es que los clientes o nodos no depende unos de otros ya que no tienen conocimiento de quién está al otro lado. Puede incluso que no haya nadie en el otro extremo.
Esto permite algo muy importantes en proyectos de este tipo: la escalabilidad.
Al contrario de lo que ocurre con el protocolo HTTP, no hay que hacer una petición para recibir información desde un cliente. Cada cliente MQTT abre una conexión permanente TCP con el Broker.
El Broker tiene la capacidad de hacer que los mensajes sean persistentes, guardando el mensaje hasta que se conecte el cliente al que va dirigido. Te recuerdo que el Broker es el único que sabe quién está suscrito a un topic.
He hablado en varias ocasiones de que MQTT es un protocolo fiable. Eso se debe a que tiene implementado un Servicio de Calidad o QoS (del inglés Quality of Service). Este servicio determina cómo se entrega el mensaje a los receptores.
El QoS se especifica en cada mensaje que se envía y puede haber 3 grados de calidad:
Utilizar un grado de calidad u otro dependerá de la fiabilidad que queramos tener en nuestro sistema. Sin embargo, debes tener en cuenta que cuanta más calidad menor será el rendimiento.
Ahora que ya tenemos los conocimientos técnicos, vamos a poner en práctica todo esto para montar nuestro sistema basado en el protocolo MQTT.
#include <Wire.h>
#include <SPI.h>
#include <WiFi.h>
#include <PubSubClient.h>
#include <DHT.h>
#define DHTPIN 2 // DHT Sensor connected to digital pin 2.
#define DHTTYPE DHT11 // Type of DHT sensor.
DHT dht(DHTPIN, DHTTYPE); // Initialize DHT sensor.
char ssid[] = "TP-LINK_Calderon"; // Change this to your network SSID (name).
char pass[] = "c@lderon14"; // Change this your network password
char mqttUserName[] = "TSArduinoMQTTDemo"; // Can be any name.
char mqttPass[] = "K27K5NB5MFO3RVHW"; // Change this your MQTT API Key from Account > MyProfile.
char writeAPIKey[] = "ZCLKYLX1MG9XI6NC"; // Change to your channel Write API Key.
long channelID = 798960;
static const char alphanum[] ="0123456789"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"; // For random generation of client ID.
WiFiClient client; // Initialize the Wifi client library.
PubSubClient mqttClient(client); // Initialize the PuBSubClient library.
const char* server = "mqtt.thingspeak.com";
unsigned long lastConnectionTime = 0;
const unsigned long postingInterval = 20L * 1000L; // Post data every 20 seconds.
void setup() {
dht.begin();
Serial.begin(115200);
int status = WL_IDLE_STATUS; // Set a temporary WiFi status.
// Attempt to connect to WiFi network
while (status != WL_CONNECTED)
{
status = WiFi.begin(ssid, pass); // Connect to WPA/WPA2 Wi-Fi network.
delay(5000);
}
Serial.println("Connected to wifi");
mqttClient.setServer(server, 1883); // Set the MQTT broker details.
}
void loop() {
// Reconnect if MQTT client is not connected.
if (!mqttClient.connected())
{
reconnect();
}
mqttClient.loop(); // Call the loop continuously to establish connection to the server.
// If interval time has passed since the last connection, publish data to ThingSpeak.
if (millis() - lastConnectionTime > postingInterval)
{
mqttpublish();
}
}
void reconnect()
{
char clientID[9];
// Loop until reconnected.
while (!mqttClient.connected())
{
Serial.print("Attempting MQTT connection...");
// Generate ClientID
for (int i = 0; i < 8; i++) {
clientID[i] = alphanum[random(51)];
}
clientID[8]='\0';
// Connect to the MQTT broker
if (mqttClient.connect(clientID,mqttUserName,mqttPass))
{
Serial.print("Connected with Client ID: ");
Serial.print(String(clientID));
Serial.print(", Username: ");
Serial.print(mqttUserName);
Serial.print(" , Passwword: ");
Serial.println(mqttPass);
} else
{
Serial.print("failed, rc=");
// Print to know why the connection failed.
// See https://pubsubclient.knolleary.net/api.html#state for the failure code explanation.
Serial.print(mqttClient.state());
Serial.println(" try again in 5 seconds");
delay(5000);
}
}
}
void mqttpublish() {
float t = dht.readTemperature(); // Read temperature from DHT sensor.
float h = dht.readHumidity(); // Read humidity from DHT sensor.
// Create data string to send to ThingSpeak
String data = String("field1=" + String(t, DEC) + "&field2=" + String(h, DEC));
int length = data.length();
char msgBuffer[length];
data.toCharArray(msgBuffer,length+1);
Serial.println(msgBuffer);
// Create a topic string and publish data to ThingSpeak channel feed.
String topicString ="channels/" + String( channelID ) + "/publish/"+String(writeAPIKey);
length=topicString.length();
char topicBuffer[length];
topicString.toCharArray(topicBuffer,length+1);
mqttClient.publish( topicBuffer, msgBuffer );
lastConnectionTime = millis();
}
// Use this function instead to publish to a single field directly. Don't forget to comment out the above version.
/*
void mqttpublish() {
float t = dht.readTemperature(true); // Read temperature from DHT sensor.
String data = String(t, DEC);
int length = data.length();
char msgBuffer[length];
data.toCharArray(msgBuffer,length+1);
Serial.println(msgBuffer);
// Create a topic string and publish data to ThingSpeak channel feed.
String topicString ="channels/" + String( channelID ) + "/publish/"+String(writeAPIKey);
length=topicString.length();
char topicBuffer[length];
topicString.toCharArray(topicBuffer,length+1);
mqttClient.publish( topicBuffer, msgBuffer );
lastConnectionTime = millis();
}
*/