← Índice del curso

Internet de las Cosas · Tema 25

Qué es MQTT

Con HTTP el dispositivo habla con un servidor concreto, y con un WebSocket mantiene un canal con él. MQTT cambia la pregunta: en lugar de «¿a quién le hablo?», el dispositivo dice «publico esto en este tema» y quien quiera saberlo se suscribe. Ese desacople, sumado a un protocolo diminuto, lo convirtió en el idioma estándar de los dispositivos conectados.

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.
Publicación y suscripción con un broker A la izquierda, dos dispositivos publican: la cámara 07 publica en casa/camara07/temperatura y el medidor publica en casa/medidor/consumo. En el centro, el broker recibe las publicaciones. A la derecha, tres suscriptores reciben según sus suscripciones: un panel suscrito a casa/+/temperatura, una alarma suscrita a casa/camara07/# y un archivo suscrito a casa/#. Una flecha de retorno muestra que un comando publicado por el panel llega al dispositivo suscrito. Cámara 07publica temperatura Medidorpublica consumo Brokerrecibe y repartepor tópicos Panelcasa/+/temperatura Alarmacasa/camara07/# Archivocasa/# Los publicadores no conocen a los suscriptores Un mensaje publicado una vez llega a todos los interesados

De esa estructura salen tres formas de desacople, que son el corazón del valor de MQTT:

Tres desacoples que aporta el modelo de publicación y suscripción.
DesacopleQué significaQué se gana
En el espacioPublicador y suscriptor no se conocen ni necesitan saber la dirección del otroSe 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 tantoLas caídas de red no pierden las órdenes (tema 28)
En la sincronizaciónEl publicador no espera que el suscriptor procese el mensajeEl 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:

Comodines de los filtros de suscripción.
ComodínQué aceptaEjemplo de filtroCoincide con
+Exactamente un nivelcasa/+/temperaturacasa/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:

Paquetes principales de MQTT 3.1.1.
PaqueteDirecciónPara qué sirve
CONNECTCliente → brokerAbrir la sesión: identificador del cliente, usuario y clave, tiempo de keepalive, opciones
CONNACKBroker → clienteAceptar o rechazar la conexión, indicando el motivo
PUBLISHAmbos sentidosUn mensaje: tópico y carga útil
SUBSCRIBE / SUBACKCliente → broker / respuestaPedir uno o varios tópicos o filtros, y confirmar
PINGREQ / PINGRESPCliente → broker / respuestaMantener viva la conexión cuando no hay tráfico
DISCONNECTCliente → brokerCerrar 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:

Mecanismos de MQTT y el tema donde se estudian.
MecanismoQué resuelveTema
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 vez28
Sesiones persistentesEl broker recuerda las suscripciones y guarda los mensajes que llegaron mientras el cliente estaba desconectado28
Mensajes retenidos (retained)El broker guarda el último valor de un tópico y se lo entrega a quien se suscribe después28
Última voluntad (last will)Un mensaje que el broker publica solo si el cliente se cae sin despedirse, para avisar que está desconectado28
Diseño de tópicos y cargasCómo nombrar y estructurar los mensajes para que escalen29
Elección entre MQTT y HTTPCuándo cada uno y cómo combinarlos30

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

Situaciones en que MQTT es una buena opción y situaciones en que conviene otra.
MQTT conviene cuando…Conviene otra cosa (HTTP, por ejemplo) cuando…
Hay muchos dispositivos y varios servicios interesados en los mismos datosUn 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éctricaEl 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ónSe necesita atravesar firewalls corporativos que solo dejan pasar HTTP
Los mensajes son pequeños y frecuentesLa integración es con servicios web existentes que solo exponen APIs REST
Se quiere agregar o quitar servicios sin tocar el firmwareHay 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

Bytes por envío (promedio)—
Datos por día—
Consumo por día—
Duración de la batería (2000 mAh)—
Datos frente a HTTP con conexión nueva—
Resultado—

Elegí los parámetros y presioná «Calcular».

Recorrido guiado

  1. 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».
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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

  1. 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.
  2. Elegí el broker (propio, en el borde o en la nube) según la escala y la disponibilidad que necesitás.
  3. Un identificador de cliente único por dispositivo. Dos clientes con el mismo identificador se expulsan entre sí en un bucle.
  4. TLS en el puerto 8883, con verificación del certificado del broker, y credencial propia de cada dispositivo.
  5. Listas de acceso por tópico: cada dispositivo publica y se suscribe solo donde le corresponde.
  6. Diseñá el esquema de tópicos antes de escribir código (tema 29): jerarquía, nombres, uno para telemetría y otro para comandos.
  7. Keepalive menor que el olvido del NAT, y reconexión con espera creciente y retardo aleatorio.
  8. Validá cada mensaje que llega como si viniera de afuera: formato, rango, vigencia.
  9. Probá con un cliente de escritorio (como mosquitto_sub y mosquitto_pub) antes de programar el firmware: así separás los problemas del broker de los del dispositivo.
  10. 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.