01 · Punto de partida
Del servidor concreto al intermediario
En el bloque anterior cada dispositivo hablaba con un servidor determinado, con una dirección y una interfaz propias. Funciona, pero tiene un costo de acoplamiento: el firmware conoce la API del servidor; si querés que un segundo servicio también reciba la lectura, hay que enviarla dos veces o que el servidor la reenvíe; y para mandar una orden al dispositivo, el servidor tiene que esperar a que éste pregunte o mantener un canal como el WebSocket del tema 24.
MQTT (Message Queuing Telemetry Transport) resuelve esos tres problemas con una idea: poner un intermediario en el medio. Los dispositivos y los servicios no se conocen entre sí; todos hablan con el intermediario, que se llama broker. Nació en 1999 para vigilar oleoductos por satélite, donde cada byte costaba dinero y los enlaces se caían, y por eso es compacto y tolerante a las fallas. Hoy es un estándar abierto (OASIS) y lo usan desde sensores domésticos hasta flotas industriales.
Los números de costo del laboratorio son un modelo con valores didácticos, pero los tamaños de los paquetes MQTT se calculan con su codificación real, y podés verificarlos en el código.
02 · El modelo
Publicar y suscribirse
En MQTT hay tres actores:
- El publicador envía un mensaje a un tópico (una etiqueta con forma de ruta, como
casa/camara07/temperatura). No sabe quién lo va a recibir, ni si hay alguien. - El suscriptor le dice al broker en qué tópicos está interesado. Recibe todo lo que se publique en ellos.
- El broker es el servidor intermedio: recibe cada publicación y la entrega a todos los suscriptores que corresponda. Un mismo cliente puede ser publicador y suscriptor a la vez.
De esa estructura salen tres formas de desacople, que son el corazón del valor de MQTT:
| Desacople | Qué significa | Qué se gana |
|---|---|---|
| En el espacio | Publicador y suscriptor no se conocen ni necesitan saber la dirección del otro | Se agregan o quitan servicios sin tocar el firmware: un panel nuevo solo se suscribe |
| En el tiempo (con sesiones) | Un suscriptor desconectado puede recibir, al volver, lo que se publicó mientras tanto | Las caídas de red no pierden las órdenes (tema 28) |
| En la sincronización | El publicador no espera que el suscriptor procese el mensaje | El dispositivo publica y sigue con lo suyo |
Y una consecuencia que resuelve el problema de las órdenes: el dispositivo se suscribe a un tópico propio (por ejemplo, casa/camara07/comandos) y mantiene la conexión abierta. Cuando cualquier servicio publica un comando en ese tópico, el broker lo entrega al instante por la conexión que el dispositivo ya abrió hacia afuera, atravesando el NAT. Es el WebSocket del tema 24, pero con tópicos, garantías de entrega y un protocolo mucho más liviano.
03 · Direcciones de los mensajes
Tópicos y comodines
Un tópico es una cadena de texto organizada en niveles separados por barras, como una ruta de carpetas: casa/camara07/temperatura. Los niveles no son una estructura rígida; son una convención que permite agrupar. Se leen de izquierda a derecha, de lo general a lo particular. Los suscriptores pueden usar dos comodines en sus filtros:
| Comodín | Qué acepta | Ejemplo de filtro | Coincide con |
|---|---|---|---|
+ | Exactamente un nivel | casa/+/temperatura | casa/camara07/temperatura, casa/living/temperatura; no casa/camara07/puerta |
# | Cualquier cantidad de niveles que sigan (solo al final) | casa/camara07/# | casa/camara07/temperatura, casa/camara07/puerta, casa/camara07/bateria/nivel |
Los comodines se usan en la suscripción, nunca al publicar: se publica siempre a un tópico concreto. El diseño de los tópicos es una decisión de arquitectura que veremos en el tema 29; por ahora conviene saber que un buen esquema permite que un panel reciba «toda la temperatura de la casa» con una sola suscripción, y que una alarma reciba «todo lo de la cámara».
04 · El protocolo
Cómo es el protocolo por dentro
MQTT funciona sobre TCP (puerto 1883 sin cifrar, 8883 con TLS). El cliente abre la conexión, se identifica y la deja abierta. Los mensajes del protocolo se llaman paquetes de control, y cada uno tiene un tipo. Los esenciales:
| Paquete | Dirección | Para qué sirve |
|---|---|---|
| CONNECT | Cliente → broker | Abrir la sesión: identificador del cliente, usuario y clave, tiempo de keepalive, opciones |
| CONNACK | Broker → cliente | Aceptar o rechazar la conexión, indicando el motivo |
| PUBLISH | Ambos sentidos | Un mensaje: tópico y carga útil |
| SUBSCRIBE / SUBACK | Cliente → broker / respuesta | Pedir uno o varios tópicos o filtros, y confirmar |
| PINGREQ / PINGRESP | Cliente → broker / respuesta | Mantener viva la conexión cuando no hay tráfico |
| DISCONNECT | Cliente → broker | Cerrar de forma ordenada |
Su virtud más visible es lo pequeño que es. Todo paquete empieza con un encabezado fijo de apenas 2 bytes: uno que indica el tipo y las banderas, y uno o más (hasta cuatro) con la longitud del resto, codificada de forma variable (los valores menores que 128 caben en un byte). Un PINGREQ son 2 bytes. Una publicación del tópico casa/camara07/temperatura (25 caracteres) con una carga de 4 bytes ocupa 33 bytes en total: 1 de tipo, 1 de longitud, 2 de la longitud del tópico, 25 del tópico y 4 de la carga. Una petición HTTP equivalente supera los 250. El código de este tema arma los paquetes reales y mide sus tamaños.
La conexión se mantiene con el keepalive: el cliente declara en el CONNECT cada cuántos segundos hablará como máximo; si no tiene nada que publicar, envía un PINGREQ. Si el broker no oye nada en una vez y media ese tiempo, da al cliente por caído, y si es el cliente quien no recibe el PINGRESP, sabe que la conexión murió. Es la versión de MQTT del ping y pong del tema 24, y el intervalo debe ser menor que el olvido del NAT (tema 17).
#include <WiFi.h>
#include <PubSubClient.h> // biblioteca de cliente MQTT para Arduino
WiFiClient red; // en producción, WiFiClientSecure con el puerto 8883
PubSubClient mqtt(red);
void alMensaje(char *topico, byte *carga, unsigned int largo) {
String texto;
for (unsigned int i = 0; i < largo; i++) texto += (char)carga[i];
Serial.printf("Llegó %s: %s\n", topico, texto.c_str()); // acá se validaría y ejecutaría la orden
}
bool conectarMqtt() {
mqtt.setServer("broker.ejemplo.com", 1883);
mqtt.setCallback(alMensaje); // qué hacer cuando llega un mensaje suscrito
mqtt.setKeepAlive(60);
if (!mqtt.connect("camara07", USUARIO, CLAVE)) return false; // identificador único del cliente
mqtt.subscribe("casa/camara07/comandos"); // las órdenes llegan por este tópico
return true;
}
void loop() {
if (!mqtt.connected() && !conectarMqtt()) {
delay(5000); // reintentar con espera (mejor: creciente y con retardo aleatorio)
return;
}
mqtt.loop(); // atiende mensajes entrantes y el keepalive
static unsigned long ultimo = 0;
if (millis() - ultimo > 60000) {
ultimo = millis();
mqtt.publish("casa/camara07/temperatura", "4.3");
}
}
El esquema es el de todo cliente MQTT: se configura el broker y la función que atiende lo que llega, se conecta con un identificador único, se suscribe a los tópicos propios y, en el bucle, se llama a mqtt.loop() (que atiende los paquetes entrantes y el keepalive) y se publica cuando hace falta. Los nombres corresponden a la biblioteca PubSubClient, muy usada; verificalos contra la documentación de la versión que uses.
05 · Qué más ofrece
Las garantías, en resumen
Además de repartir mensajes, MQTT incluye mecanismos que en HTTP o WebSocket habría que construir a mano. Cada uno tiene su tema, pero conviene conocer el mapa:
| Mecanismo | Qué resuelve | Tema |
|---|---|---|
| Calidad de servicio (QoS 0, 1 y 2) | Cuánta garantía de entrega se pide para cada mensaje: como máximo una vez, al menos una vez o exactamente una vez | 28 |
| Sesiones persistentes | El broker recuerda las suscripciones y guarda los mensajes que llegaron mientras el cliente estaba desconectado | 28 |
| Mensajes retenidos (retained) | El broker guarda el último valor de un tópico y se lo entrega a quien se suscribe después | 28 |
| Última voluntad (last will) | Un mensaje que el broker publica solo si el cliente se cae sin despedirse, para avisar que está desconectado | 28 |
| Diseño de tópicos y cargas | Cómo nombrar y estructurar los mensajes para que escalen | 29 |
| Elección entre MQTT y HTTP | Cuándo cada uno y cómo combinarlos | 30 |
La combinación de sesión persistente y QoS 1 es la que contesta el problema que dejó abierto el tema 24: una orden publicada mientras el dispositivo estaba desconectado espera en el broker y se entrega al volver. El last will resuelve el otro problema: saber que un dispositivo calló. Son los dos mecanismos que justifican, en la práctica, elegir MQTT para una flota.
06 · Ecosistema
Versiones, brokers y seguridad
Existen dos versiones en uso. MQTT 3.1.1 es la más extendida y la que soportan prácticamente todos los brokers y bibliotecas. MQTT 5 agrega códigos de motivo detallados en las respuestas, propiedades de usuario en los mensajes (como cabeceras), vencimiento de mensajes y de sesiones, alias de tópicos para ahorrar bytes y suscripciones compartidas para repartir carga entre servicios. Para un dispositivo pequeño, lo importante es que el broker y la biblioteca soporten la versión que se elija; el 3.1.1 sigue siendo la elección segura.
El broker es el componente central y tiene opciones para todos los tamaños:
- Brokers de código abierto como Mosquitto (liviano, ideal para una Raspberry Pi o un servidor chico) o EMQX y HiveMQ (para decenas o cientos de miles de conexiones).
- Servicios en la nube que lo ofrecen administrado dentro de plataformas IoT, con autenticación de dispositivos y reglas.
- Un broker en el borde: dentro del edificio, en un gateway, de modo que los dispositivos sigan funcionando si cae Internet y el broker local reenvíe a la nube cuando vuelva.
La seguridad de MQTT se apoya en capas ya conocidas: TLS (puerto 8883) para cifrar y autenticar al broker; usuario y clave o certificado de cliente para autenticar a cada dispositivo (uno por equipo, nunca compartido); y listas de control de acceso por tópico, que son el equivalente de la autorización del tema 22: el dispositivo camara07 solo puede publicar en casa/camara07/# y solo suscribirse a su tópico de comandos. Sin esas listas, un dispositivo comprometido puede suscribirse a # y leer toda la flota. Para los navegadores, los brokers ofrecen MQTT sobre WebSocket, de modo que un panel web se suscribe a los mismos tópicos.
07 · Decisión
Cuándo conviene MQTT
| MQTT conviene cuando… | Conviene otra cosa (HTTP, por ejemplo) cuando… |
|---|---|
| Hay muchos dispositivos y varios servicios interesados en los mismos datos | Un dispositivo habla con un único servicio y la integración es simple |
| El dispositivo debe recibir órdenes con baja demora y tiene alimentación eléctrica | El dispositivo con pila envía de vez en cuando y duerme entre envíos |
| La red es inestable y los mensajes no deben perderse al caerse la conexión | Se necesita atravesar firewalls corporativos que solo dejan pasar HTTP |
| Los mensajes son pequeños y frecuentes | La integración es con servicios web existentes que solo exponen APIs REST |
| Se quiere agregar o quitar servicios sin tocar el firmware | Hay que descargar archivos grandes (firmware, imágenes) |
La fila de la batería merece atención y la miramos en el laboratorio: MQTT no ahorra energía por sí mismo. Su ventaja está en la conexión persistente, y una conexión persistente deja la radio atenta. Con pila y envíos esporádicos, un dispositivo que se conecta, publica y se desconecta paga el mismo apretón de manos de TCP y TLS que con HTTP, y la diferencia es casi nula. Por eso, en la práctica, muchos dispositivos con pila usan HTTP o CoAP para enviar y MQTT para el resto de la arquitectura, o aprovechan modos de bajo consumo del módulo celular que mantienen la sesión sin la radio encendida.
08 · Analogía con programación
Explorar MQTT con un mini broker en Python
El programa tiene tres partes. La primera es un broker mínimo en memoria con suscripciones, comodines + y # y reparto de mensajes: se suscriben un panel, una alarma y un archivo, y se publican cuatro mensajes para ver quién recibe cada uno. La segunda arma paquetes MQTT 3.1.1 reales (CONNECT, PUBLISH con QoS 0 y 1, PINGREQ) con la codificación del estándar, incluida la longitud variable, y mide sus bytes. La tercera calcula el costo por día de cuatro formas de enviar: HTTP con conexión nueva, HTTP con conexión abierta, MQTT con conexión abierta y MQTT reconectando en cada envío.
Python en tu navegador. Los bloques son editables: cambiá los valores y presioná «Ejecutar Python». La primera vez tarda unos segundos en cargar.
# ---------- Parte 1: un mini broker en memoria para ver cómo se reparten los mensajes ----------
class Broker:
def __init__(self):
self.suscripciones = [] # (filtro, nombre del suscriptor)
def suscribir(self, nombre, filtro):
self.suscripciones.append((filtro, nombre))
@staticmethod
def coincide(filtro, topico):
f, t = filtro.split("/"), topico.split("/")
for i, nivel in enumerate(f):
if nivel == "#":
return True # # acepta todo lo que sigue
if i >= len(t) or (nivel != "+" and nivel != t[i]):
return False # + acepta un nivel cualquiera
return len(f) == len(t)
def publicar(self, topico, carga):
recibieron = [n for f, n in self.suscripciones if self.coincide(f, topico)]
print(f"PUBLISH {topico} = {carga:<6} -> {', '.join(recibieron) if recibieron else 'nadie escucha'}")
broker = Broker()
broker.suscribir("panel", "casa/+/temperatura") # cualquier dispositivo, solo temperatura
broker.suscribir("alarma", "casa/camara07/#") # todo lo de la cámara 07
broker.suscribir("archivo", "casa/#") # todo
broker.publicar("casa/camara07/temperatura", "4.3")
broker.publicar("casa/camara07/puerta", "abierta")
broker.publicar("casa/living/temperatura", "22.1")
broker.publicar("oficina/sala/temperatura", "21.5")
# ---------- Parte 2: tamaño real de los paquetes MQTT 3.1.1 ----------
def longitud_restante(n):
"""Codificación de longitud variable del encabezado fijo: 1 a 4 bytes."""
salida = bytearray()
while True:
byte, n = n % 128, n // 128
salida.append(byte | (0x80 if n else 0))
if not n:
return bytes(salida)
def cadena(texto):
b = texto.encode()
return len(b).to_bytes(2, "big") + b
def paquete(tipo_y_banderas, cuerpo):
return bytes([tipo_y_banderas]) + longitud_restante(len(cuerpo)) + cuerpo
def connect(id_cliente, keepalive_s=60):
cuerpo = cadena("MQTT") + bytes([4, 0x02]) + keepalive_s.to_bytes(2, "big") + cadena(id_cliente)
return paquete(0x10, cuerpo)
def publish(topico, carga, qos=0):
cuerpo = cadena(topico) + (b"\x00\x01" if qos else b"") + carga
return paquete(0x30 | (qos << 1), cuerpo)
TOPICO = "casa/camara07/temperatura"
print()
print("paquete bytes")
print(f"CONNECT (id 'camara07') {len(connect('camara07')):>6}")
print(f"CONNACK {len(bytes([0x20, 2, 0, 0])):>6}")
for n in (4, 40, 400):
print(f"PUBLISH QoS 0, carga de {n:>3} bytes {len(publish(TOPICO, b'x' * n)):>6}")
print(f"PUBLISH QoS 1, carga de 40 bytes {len(publish(TOPICO, b'x' * 40, qos=1)):>6}")
print(f"PINGREQ + PINGRESP {len(bytes([0xC0, 0])) + len(bytes([0xD0, 0])):>6}")
# ---------- Parte 3: costo por día de cada forma de enviar ----------
BATERIA_MAH = 2000
SUENO_MAH_DIA = 0.24
MAH_CONEXION_NUEVA = 100 * 0.6 / 3600 # 0,6 s de radio por envío con conexión nueva
RADIO_ATENTA_MAH_DIA = 480 # conexión abierta: 20 mA durante 24 h
IP_TCP = 40 # encabezados de red de cada paquete
APRETON_B = 4700 # TCP y TLS 1.3
HTTP_B = 150 + 120 # petición y respuesta con cabeceras mínimas
PING_S = 60
def costo(forma, carga, envios_hora):
envios = envios_hora * 24
publicar = len(publish(TOPICO, b"x" * carga)) + IP_TCP
if forma == "HTTP, conexión nueva":
return envios * (APRETON_B + HTTP_B + carga), SUENO_MAH_DIA + envios * MAH_CONEXION_NUEVA
if forma == "HTTP, conexión abierta":
keepalive_tcp = 86400 / PING_S * 2 * IP_TCP
return envios * (HTTP_B + carga + 2 * IP_TCP) + keepalive_tcp, RADIO_ATENTA_MAH_DIA
if forma == "MQTT, conexión abierta":
pings = 86400 / PING_S * 2 * (2 + IP_TCP)
return envios * publicar + pings, RADIO_ATENTA_MAH_DIA
sobre = len(connect("camara07")) + 4 + 2 + 3 * IP_TCP # CONNECT, CONNACK, DISCONNECT
return envios * (APRETON_B + sobre + publicar), SUENO_MAH_DIA + envios * MAH_CONEXION_NUEVA
print()
print("forma de enviar carga envíos/h datos por día mAh/día batería")
for carga, envios in ((40, 12), (40, 120)):
for forma in ("HTTP, conexión nueva", "HTTP, conexión abierta", "MQTT, conexión abierta", "MQTT, reconectando"):
b, mah = costo(forma, carga, envios)
dias = BATERIA_MAH / mah
bat = f"{dias / 365:.1f} años" if dias >= 365 else f"{dias:.0f} días"
print(f"{forma:<26} {carga:>6} {envios:>9} {b / 1e6:>12.2f} MB {mah:>9.2f} {bat}")
Resultado con los valores iniciales. En el reparto: la temperatura de la cámara 07 llega al panel, la alarma y el archivo; la puerta de la cámara 07, solo a la alarma y al archivo; la temperatura del living, al panel y al archivo; y la de la oficina, a nadie. En los paquetes: CONNECT con el identificador camara07 pesa 22 bytes y el CONNACK 4; un PUBLISH QoS 0 del tópico de 25 caracteres pesa 33 bytes con 4 de carga, 69 con 40 y 430 con 400 (la longitud pasa a ocupar 2 bytes); con QoS 1 se suman 2 bytes del identificador del paquete (71); un PINGREQ más su PINGRESP son 4 bytes. En el costo con 40 bytes de carga y 12 envíos por hora: HTTP con conexión nueva, 1,44 MB por día, 5,04 mAh y 1,1 años; HTTP con conexión abierta, 0,23 MB y 480 mAh (4 días); MQTT con conexión abierta, 0,15 MB y 480 mAh; MQTT reconectando, 1,43 MB, 5,04 mAh y 1,1 años. Con 120 envíos por hora: 14,43; 1,24; 0,43 y 14,28 MB por día.
Tres conclusiones. Una: un mensaje MQTT cuesta unas 4 veces menos bytes que una petición HTTP sobre la misma conexión abierta (109 contra 390 por envío con 40 bytes de carga), y con 120 envíos por hora la conexión abierta mueve 0,43 MB por día contra 1,24 de HTTP abierto. Otra: la ventaja de MQTT depende de la conexión abierta: reconectando en cada envío, cuesta lo mismo que HTTP. Y la tercera: una conexión abierta cuesta 480 mAh por día, sea con MQTT o con HTTP: con pila, ninguna de las dos formas abiertas es viable.
09 · Exploración interactiva
MQTT frente a HTTP, de extremo a extremo
Este laboratorio ejecuta el mismo modelo del código anterior. Elegí cómo se envía, el tamaño de la carga útil y los envíos por hora. Las barras comparan las cuatro formas en datos por día (escala logarítmica), con la duración de una batería de 2000 mAh; las rojas duran menos de un año. Los tamaños de los paquetes MQTT son los reales, y el resto de los valores son didácticos.
Costo de enviar por MQTT o por HTTP
Elegí los parámetros y presioná «Calcular».
Recorrido guiado
- Con los valores iniciales (MQTT con conexión abierta, 40 bytes, 12 envíos por hora), calculá. 0,15 MB por día, 529 bytes por envío (incluye los pings) y 480 mAh por día: 4 días de batería, «Solo con red eléctrica».
- Cambiá a HTTP, conexión abierta: 0,23 MB por día (1,5 veces más que MQTT) y el mismo gasto de energía. La diferencia en bytes es real, pero la energía no cambia.
- Subí a 120 envíos por hora con las dos conexiones abiertas: MQTT 0,43 MB, HTTP 1,24 MB. A más frecuencia, mayor ventaja del protocolo liviano: casi tres veces menos tráfico.
- Elegí HTTP, conexión nueva con 12 envíos por hora: 1,44 MB por día, 5,04 mAh y 1,1 años: «Viable con pila», pero 9 veces más datos que MQTT abierto.
- Elegí MQTT, reconectando: 1,43 MB y 5,04 mAh, prácticamente lo mismo que HTTP con conexión nueva. Reconectar elimina la ventaja de MQTT, porque el apretón de manos de TCP y TLS domina el costo.
- Con 400 bytes de carga, el sobre pierde peso: MQTT abierto con 12 envíos por hora pasa a 0,26 MB y HTTP nuevo a 1,55 (HTTP abierto, 0,33). Cuanto más grande el mensaje, menos importa el protocolo y más importa la conexión.
Desafío: cuántos suscriptores son demasiados para HTTP
Un sensor publica su temperatura y la reciben tres servicios (un panel, una alarma y un archivo). Con MQTT, el dispositivo envía un PUBLISH de 69 bytes y el broker lo reparte a los tres: el costo para el dispositivo no cambia al agregar el cuarto servicio. Con HTTP puro, o el dispositivo envía tres veces (tres conexiones, tres veces la energía y los datos), o envía una vez a un servidor propio que reenvía a los demás, con el código y el mantenimiento que eso supone. En una flota de 10.000 dispositivos con 12 envíos por hora, tres POST por envío serían 10.000 × 12 × 24 × 3 = 8,6 millones de peticiones por día solo para repartir lo mismo. MQTT mueve el trabajo de repartir del firmware al broker, que está hecho para eso.
10 · Método de trabajo
Lista de verificación para empezar con MQTT
- Justificá la conexión persistente: ¿el dispositivo tiene alimentación eléctrica o puede sostener la radio atenta? Si no, evaluá HTTP o CoAP para enviar.
- Elegí el broker (propio, en el borde o en la nube) según la escala y la disponibilidad que necesitás.
- Un identificador de cliente único por dispositivo. Dos clientes con el mismo identificador se expulsan entre sí en un bucle.
- TLS en el puerto 8883, con verificación del certificado del broker, y credencial propia de cada dispositivo.
- Listas de acceso por tópico: cada dispositivo publica y se suscribe solo donde le corresponde.
- Diseñá el esquema de tópicos antes de escribir código (tema 29): jerarquía, nombres, uno para telemetría y otro para comandos.
- Keepalive menor que el olvido del NAT, y reconexión con espera creciente y retardo aleatorio.
- Validá cada mensaje que llega como si viniera de afuera: formato, rango, vigencia.
- Probá con un cliente de escritorio (como
mosquitto_subymosquitto_pub) antes de programar el firmware: así separás los problemas del broker de los del dispositivo. - Medí el tráfico y el consumo reales con tu broker y tu red.
Un primer experimento: instalá Mosquitto en tu computadora, suscribite con mosquitto_sub -t "casa/#" -v en una terminal y publicá con mosquitto_pub -t casa/prueba -m 23.5 en otra. En un minuto vas a ver el modelo de publicación y suscripción funcionando con tus propios ojos.
11 · Comprensión
Confusiones frecuentes
«MQTT es un servidor al que le mando mensajes»
MQTT es un protocolo; el servidor se llama broker y es solo un intermediario que reparte. Los mensajes no son para él sino para los suscriptores. Y el broker no interpreta el contenido: lo entrega tal cual.
«Si publico, el mensaje queda guardado hasta que alguien lo lea»
Por defecto, no: si no hay suscriptores en ese momento, el mensaje se descarta. Para que se conserve hay que usar mensajes retenidos y sesiones persistentes (tema 28).
«MQTT ahorra batería porque es liviano»
Ahorra bytes, no energía. Lo que consume es mantener la radio atenta. Con pila y envíos esporádicos, MQTT reconectando gasta lo mismo que HTTP.
«Los comodines sirven para publicar en varios tópicos a la vez»
Solo se usan al suscribirse. Publicar siempre es a un tópico concreto. Para llegar a varios destinos se publica a un tópico que muchos escuchan.
«Puedo usar el mismo identificador de cliente en todos los dispositivos»
Cada conexión debe tener un identificador único. Si dos clientes usan el mismo, el broker desconecta al anterior y ambos se reconectan una y otra vez.
«Con un broker en Internet sin usuario ni clave alcanza para probar»
Un broker abierto es descubierto en horas por escaneos y usado por cualquiera, y cualquiera puede leer todo lo publicado. Aun para pruebas, se usa autenticación y TLS.
12 · Práctica guiada
Predecí antes de comprobar
1. Comodines
Indicá cuáles de estos tópicos recibe quien se suscribe a casa/+/temperatura, y cuáles a casa/#: (a) casa/living/temperatura; (b) casa/living/humedad; (c) casa/camara07/bateria/nivel; (d) oficina/sala/temperatura.
Ver solución
Con casa/+/temperatura: solo (a). Con casa/#: (a), (b) y (c). El (d) no empieza con casa, así que no lo recibe ninguno de los dos.
2. Tamaño de un PUBLISH
Publicás en casa/camara07/temperatura (25 caracteres) una carga de 4 bytes con QoS 0. ¿Cuántos bytes tiene el paquete?
Ver solución
1 byte de tipo y banderas + 1 de longitud restante + 2 de la longitud del tópico + 25 del tópico + 4 de carga = 33 bytes. Con QoS 1 se suman 2 bytes del identificador de paquete: 35.
3. Longitud variable
La carga es de 200 bytes y el tópico de 25. ¿Cuánto vale la «longitud restante» y cuántos bytes ocupa su codificación? ¿Cuánto mide el paquete?
Ver solución
Restante = 2 + 25 + 200 = 227. Como es mayor o igual que 128, se codifica en 2 bytes. Paquete: 1 + 2 + 227 = 230 bytes.
4. Desacople
Un dispositivo publica su temperatura, y un cuarto servicio quiere recibirla. ¿Qué hay que cambiar en el firmware?
Ver solución
Nada. El nuevo servicio se suscribe a casa/camara07/temperatura (o a un filtro más amplio) y el broker le entrega los mismos mensajes. El firmware sigue publicando una sola vez.
5. Pila o red eléctrica
Un sensor a pila publica una vez por hora. ¿Conviene MQTT con conexión abierta? ¿Qué alternativa hay?
Ver solución
No: la conexión abierta gasta unos 480 mAh por día, 4 días de batería. Alternativas: conectar, publicar y desconectar (cuesta como HTTP, pero con la comodidad del broker), usar HTTP o CoAP para enviar, o un módulo con modos de ahorro que mantengan la sesión sin la radio atenta.
6. Seguridad por tópico
El dispositivo camara07 tiene credenciales válidas y, sin listas de acceso, se suscribe a #. ¿Qué ve y cómo se evita?
Ver solución
Ve todos los mensajes de todos los dispositivos y puede publicar en cualquier tópico, incluidos los comandos de otros equipos. Se evita con listas de control de acceso por tópico: camara07 solo publica en casa/camara07/# y solo se suscribe a su tópico de comandos.
13 · Síntesis
Ideas para recordar
- MQTT es un protocolo de publicación y suscripción con un intermediario, el broker, que desacopla a quien publica de quien recibe.
- Los mensajes se publican en tópicos jerárquicos (
casa/camara07/temperatura); los suscriptores usan los comodines+(un nivel) y#(el resto) para filtrar. - Funciona sobre TCP (puerto 1883, y 8883 con TLS) con una conexión persistente que el dispositivo abre hacia afuera, lo que permite al broker empujarle órdenes atravesando el NAT.
- Los paquetes son diminutos: un encabezado de 2 bytes, un PUBLISH de 25 caracteres de tópico y 4 de carga pesa 33 bytes; el PINGREQ, 2.
- El keepalive mantiene viva la conexión y debe ser menor que el olvido del NAT.
- MQTT agrega calidad de servicio, sesiones persistentes, mensajes retenidos y última voluntad, que resuelven las órdenes perdidas y los dispositivos que callan (tema 28).
- La seguridad combina TLS, una credencial por dispositivo y listas de acceso por tópico; sin ellas, un dispositivo comprometido puede leer toda la flota.
- Su ventaja depende de la conexión abierta, y ésta mantiene la radio atenta (480 mAh por día): con pila, MQTT reconectando cuesta lo mismo que HTTP.
- Conviene cuando hay muchos dispositivos, varios servicios interesados, órdenes con baja demora y alimentación eléctrica; no hay que usarlo por moda.
¿Listo para el tema 26?
Antes de avanzar, instalá un broker de prueba (Mosquitto), suscribite a casa/# y publicá tres mensajes en tópicos distintos. Anotá cuáles llegan con + y con #. Después medí con un capturador de red cuántos bytes tiene un PUBLISH real y compará con los 33 del ejemplo.
En el próximo tema estudiamos con más detalle el broker, los tópicos y las suscripciones: cómo se organiza un broker, qué reglas tienen los nombres de tópico, cómo funcionan los filtros y qué pasa con las suscripciones cuando el cliente se reconecta.