9.18. MQTT, byte a byte¶
Llegados a este punto, la cámara dispone de todas las piezas que necesita para comunicarse con un servicio real en la internet abierta: un socket TCP, TLS para envolverlo, DNS para nombrar al interlocutor y asyncio para que el mismo script pueda hacer otras tareas mientras la conexión está abierta. MQTT es el primer protocolo de comunicación que reúne todos esos elementos en algo que un producto desplegado realmente utiliza.
Esta página cubre el protocolo en sí – el formato en la red, los roles que desempeña cada participante y los compromisos de su diseño – con suficiente honestidad como para que el cliente mqtt incluido parezca un envoltorio evidente de lo que ya se conoce en lugar de un salto de fe.
9.18.1. Pub/sub frente a petición/respuesta¶
HTTP – el protocolo al que recurren primero la mayoría de los proyectos de cámara – es petición/respuesta. Un cliente solicita a un servidor concreto un recurso concreto; el servidor responde. Cada intercambio es uno a uno, y ambos extremos conocen de antemano la dirección del otro.
MQTT es publicación/suscripción. Los clientes se conectan a un tercero situado en el medio llamado broker. Un publicador envía un mensaje a un topic con nombre sin saber ni preocuparse de quién está escuchando. Un suscriptor le indica al broker qué topics desea y a partir de entonces recibe todos los mensajes publicados en esos topics. El broker es la difusión: una publicación en yard-cam/motion llega a todos los dispositivos suscritos a yard-cam/motion, ya sean cero, uno o cincuenta.
De ese cambio de modelo se derivan tres cosas:
Desacoplamiento. Los publicadores no tienen que saber que los suscriptores existen. Los suscriptores pueden aparecer y desaparecer sin que el publicador se entere. Añadir un segundo panel es una línea de código en el nuevo panel; la cámara no cambia.
Difusión. El broker se encarga de cada copia, de modo que la cámara envía un único paquete sin importar cuántos dispositivos lo lean. Ese es el caso de uso para el que se creó MQTT.
Asimetría. El broker es ahora una pieza de infraestructura obligatoria – sin uno, el protocolo no funciona. Para proyectos domésticos suele ser un broker público gratuito (
test.mosquitto.org,broker.hivemq.com) o uno pequeño que ejecutes tú mismo.
9.18.2. Topics¶
Los topics son cadenas separadas por barras. La convención es de lo más general a la izquierda a lo más específico a la derecha:
yard-cam/motion
yard-cam/temperature
workshop-cam/motion
workshop-cam/temperature/sensor-3
Dos comodines funcionan en las suscripciones (no en las publicaciones):
+coincide con un único nivel.+/motionse suscribe al topic de movimiento de cada cámara;yard-cam/+se suscribe a cada subtopic de yard-cam.#coincide con uno o más niveles finales.yard-cam/#se suscribe ayard-cam/motion,yard-cam/temperature,yard-cam/temperature/sensor-3y cualquier otra cosa bajoyard-cam/. Debe aparecer al final de la suscripción.
Las cadenas de topic distinguen mayúsculas y minúsculas. Según la especificación, un $ inicial marca los topics internos del broker ($SYS/...) en los que los publicadores no deberían escribir.
9.18.3. El formato del paquete¶
MQTT se ejecuta sobre TCP. Cada paquete de control comienza con un encabezado fijo de un byte seguido de un campo Remaining Length de longitud variable, luego un encabezado variable específico del tipo de paquete y, por último, la carga útil. El mismo formato exterior cubre todos los comandos – CONNECT, PUBLISH, SUBSCRIBE, PUBACK, DISCONNECT y los demás – y por eso un cliente MQTT puede escribirse en unos pocos cientos de líneas.
El encabezado fijo es de un byte:
Los bits 7..4 son el tipo de paquete de control.
0x3es PUBLISH (por eso el primer byte suele empezar por0x3?).0x1es CONNECT,0x2CONNACK,0x8SUBSCRIBE,0xCPINGREQ,0xEDISCONNECT, etc.Los bits 3..0 son flags específicas del tipo de paquete. Para PUBLISH, las flags codifican la flag de retransmisión DUP, el nivel de QoS (2 bits) y la flag RETAIN.
El Remaining Length es un entero de longitud variable de 1 a 4 bytes que cuenta todos los bytes posteriores a sí mismo. El bit más alto de cada byte es un marcador de continuación – 1 significa «sigue otro byte de longitud», 0 significa «este es el último». Una longitud inferior a 128 cabe en un byte; las cargas útiles mayores usan más. La longitud máxima codificada es de 256 MiB.
Para un PUBLISH, el encabezado variable es el nombre del topic – una longitud de 2 bytes, luego los bytes UTF-8 – seguido de un identificador de paquete de 2 bytes que solo existe cuando QoS es 1 o 2. Los bytes restantes son la carga útil, tratada como bytes opacos por el protocolo.
Un PUBLISH mínimo con QoS 0 de ok a a/b es:
30 07 00 03 'a' '/' 'b' 'o' 'k'
30– PUBLISH, todas las flags a cero.07– siguen 7 bytes.00 03– longitud del topic 3.'a' '/' 'b'– topic.'o' 'k'– carga útil.
Nueve bytes en la red y el mensaje llega a cada suscriptor de a/b en el broker.
9.18.4. Niveles de QoS¶
La calidad de servicio (Quality-of-Service) controla cuánto se esfuerzan el broker (y el cliente) por garantizar la entrega. Los tres niveles:
QoS 0 – como máximo una vez. Disparar y olvidar. El paquete PUBLISH se envía y nunca se confirma. Si TCP lo entrega, el broker lo reenvía. Si la conexión se cae a mitad del envío, el mensaje se pierde. La mayoría de la telemetría de sensores funciona bien con QoS 0 – que se pierda una sola lectura de temperatura en un flujo que emite cada 30 segundos no importa.
QoS 1 – al menos una vez. El publicador incluye un identificador de paquete y espera un PUBACK. Si no llega ningún PUBACK antes de un tiempo de espera, el publicador retransmite con la flag DUP activada. El broker puede acabar entregando el mismo mensaje dos veces a un suscriptor del mismo nivel; el suscriptor debe estar dispuesto a gestionar duplicados.
QoS 2 – exactamente una vez. Un protocolo de enlace de cuatro pasos (PUBREC / PUBREL / PUBCOMP) garantiza que el mensaje llegue exactamente una vez, incluso a través de reconexiones. Costoso en idas y vueltas y en estado del broker. Pocas aplicaciones de cámara lo necesitan.
El cliente mqtt incluido implementa QoS 0 y QoS 1; QoS 2 genera una excepción si lo solicitas. Para una cámara que reporta lecturas de sensores, QoS 0 es casi siempre la respuesta correcta.
9.18.5. Mensajes retenidos y testamento final¶
Hay dos características que conviene conocer porque cambian lo que el broker recuerda sobre tu topic.
RETAIN. Si un PUBLISH tiene la flag RETAIN activada, el broker almacena el mensaje y lo reenvía a cada futuro suscriptor en el momento en que se suscribe. Así es como MQTT gestiona «¿cuál es el valor actual?» – un sensor publica su última lectura como retenida, y un panel que se suscribe diez minutos después sigue recibiendo el valor más reciente en lugar de esperar a la siguiente publicación. Volver a publicar con el mismo topic sobrescribe el valor retenido; publicar una carga útil vacía lo borra.
Testamento final. Cuando un cliente se conecta puede darle al broker un «testamento final» (last will and testament): un topic, una carga útil, un QoS y una flag de retención. Si ese cliente se desconecta de forma no limpia – RESET de TCP, pérdida de alimentación, caída de red sin paquete DISCONNECT – el broker publica el testamento en nombre del cliente. Los suscriptores lo ven como la notificación de la cámara de que se ha desconectado. La cámara misma nunca envía el testamento; lo hace el broker, porque para entonces la cámara ya no está.
9.18.6. Keepalive y reconexión¶
CONNECT lleva un intervalo de keepalive en segundos. Si el cliente ha permanecido en silencio durante ese tiempo, el broker lo considera muerto. Para evitarlo, el cliente envía periódicamente un PINGREQ (un byte: 0xC0) y recibe a cambio un PINGRESP (0xD0) – el latido más pequeño y barato que el protocolo puede transportar. La mayoría de las aplicaciones de cámara fijan el keepalive en 30 o 60 segundos.
Si la conexión TCP se cae, ambos lados lo detectan y se reconectan desde cero. Las suscripciones realizadas antes de la caída se pierden a menos que el cliente haya usado una sesión persistente al conectar; para aplicaciones de cámara sencillas, el patrón de volver a suscribirse al reconectar es más corto e igual de bueno.
Esto basta para leer la especificación de MQTT o para escribir a mano un cliente sobre un socket.socket. El cliente incluido en mqtt hace exactamente eso, además de ofrecer una API sensata para el código de la aplicación.