En este post veremos la importancia del protocolo MQTT para IoT, y sus características principales.
MQTT es un protocolo de mensajería basado en estándares (lo cuál le da una compatibilidad muy alta con todos los dispositivos), el cuál se utiliza para la comunicación entre dos dispositivos. Su método de funcionamiento se basa en el método pub/sub, del inglés publisher/subscriber (editor/suscriptor). En este método uno de los dispositivos publica un mensaje (publisher), y el otro dispositivo lo recupera (subscriber). Todo esto se consigue gracias a un intermediario llamado broker, quien almacena los mensajes hasta que son consumidos.
Ventajas de MQTT
Ligero
Una de las ventajas que tiene este protocolo, es que es muy ligero. El encabezado fijo es de entre 2 y 5 bytes tan sólo, por lo que el overhead de red es muy bajo. Los mensajes de control pueden ser de apenas 2 bytes sólo, por lo que puede ser usado en zonas donde la conexión de internet sea limitada o inestable. También es óptimo para optimizar el ancho de banda en situaciones donde tengamos muchos dispositivos enviando mensajes.
Para mantener esta ligereza, un mensaje MQTT sólo puede realizar una de estas cuatro acciones en cualquier comunicación:
- Publicar: Es el envío de bloque de datos que dependen de la implementación. Puede ser un mensaje tan simple como un indicador de cambio de estado, los datos de un sensor…
- Suscribirse: Este tipo de mensaje lo envía un cliente que quiere suscribirse a un tema. En este caso el cliente envía un mensaje del tipo SUBSCRIBE, y el broker le responderá con otro de tipo SUBACK. En el caso de que haya mensajes retenidos, este nuevo suscriptor los recibirá.
- PING: Este tipo de mensaje se usa para verificar que la conectividad entre el cliente y el broker no haya sido cerrada de forma inesperada. El cliente envía un mensaje del tipo PINGREQ, y recibe a cambio uno del tipo PINGRESP.
- DESCONECTAR: Un cliente envía un mensaje del tipo DISCONNECT al broker. En el caso de un cliente editor informa al broker de que ya no va a publicar más mensajes, y en el caso de un suscriptor que ya no los va a recibir indicando al broker que no debe guardárselos. Este tipo de cierre permite reutilizar la identidad en las siguientes conexiones. En el caso de que un cliente se desconecte sin enviar este mensaje, provocará que el broker envíe sus «últimas voluntades» (de lo cual hablaremos más adelante), a sus suscriptores.
Alta compatibilidad
Al tratarse de un estándar, muchos dispositivos IoT utilizan este protocolo. Además, requiere de muy poco código, por lo que puede usarse incluso en microcontroladores con la memoria limitada como por ejemplo ATMega328. Es una ventaja clara porque podrás interconectar diferentes dispositivos sin importar el fabricante o si tiene poca memoria.
Fiable
El protocolo MQTT tiene tres niveles de QoS (Quality of Service o Calidad de servicio):
- Nivel 0: Este nivel es también conocido como lanzar y olvidar. Con este nivel los mensajes se envían a cada suscriptor una vez sin esperar confirmación. La ventaja es que se reduce al máximo el ancho de banda usado, pero no se asegura de que el mensaje se entregue al destinatario. Además, el mensaje no se almacena y los clientes que estén desconectados en ese momento no recibirán dicho mensaje.
Este nivel es muy útil para reducir el ancho de banda al mínimo, pero sólo es válido para situaciones donde perder mensajes no sea un problema. - Nivel 1: Este nivel se denomina también «entregado al menos una vez», y en él aumentamos la fiabilidad a cambio de aumentar también el uso de ancho de banda. En este nivel el cliente tiene que confirmar que ha recibido el mensaje, por lo que es útil para asegurarte de que se entregan todos al menos una vez. La desventaja es que el cliente puede recibir mensajes duplicados si tarda en confirmar su recepción, por lo que deberá ser tolerante a esta situación.
- Nivel 2: Por último tenemos este último nivel, el cuál es el que consume más ancho de banda de los tres. A cambio es también el más fiable de los tres, ya que no sólo espera confirmación por parte del cliente, sino que antes de volver a enviar el mensaje le pregunta si lo ha recibido. A este nivel se le denomina «entregar sólo una vez», y se asegura de que todos los clientes han recibido el mensaje sólo una vez. Las ventajas son las mismas que el Nivel 1, pero además sabrás con certeza de que los mensajes no se han duplicado.
Seguro
MQTT soporta métodos de autenticación modernos como OAuth o TLS 1.3, certificados (incluso administrados por el cliente)… por lo que es muy fácil securizar los mensajes. Esto hace que sea útil para usarse incluso en redes que no sean del todo seguras.
Estructura de un mensaje MQTT
La estructura de todo mensaje MQTT consta de tres partes principales:
Cabecera fija
Ocupa entre 2 y 5 bytes, y está compuesta por el byte de control (del cuál hablaremos luego), y entre 1 y 4 bytes que indicarán el tamaño del mensaje. Este tamaño sólo incluye el tamaño que ocuparán la cabecera variable y la carga útil, ignorando el tamaño de la cabecera fija. Dicho tamaño se codifica en formato Big Endian usando el primer bit de cada byte como bit de continuidad. Esto nos deja 28 bits disponibles con los que podemos codificar números hasta 256MB.
Supongamos que queremos codificar un tamaño de mensaje aleatorio, por ejemplo 47.832.455. Este número en binario sería 0010110110011101110110000111, por lo que para codificarlo tendremos que partir ese binario en grupos de 7 bits (lo disponible por byte). Esto nos dará como resultado los siguientes cuatro bloques de bits 0010110-1100111-0111011-0000111. Al tratarse de formato Big Endian, tendremos que invertir el orden de esos grupos de bits quedando 0000111-0111011-1100111-0010110, y por último añadiremos el bit de control al principio de cada grupo. Recuerda que tiene que ser 1 para indicar que el siguiente byte también es parte del número y 0 para indicar el final. Esto nos dejará los grupos de bits de la siguiente manera: 10000111-10111011-11100111-00010110 quedando el número hexadecimal 0x87BBE716.
Cabecera variable
El tamaño de esta cabecera dependerá del tipo de mensaje que se esté enviando, siendo opcional para algunos tipos. En ella se indicarán varios datos relativos al mensaje, como pueden ser el nivel de QoS, nombre del tema, usuario y contraseña… los cuales dependerán de la versión del protocolo.
Carga útil
La carga útil al igual que la cabecera variable, es opcional y dependerá del tipo de mensaje que se esté enviando. Por ejemplo, las cabeceras de control no incluyen datos, por lo que no dispondrán de este bloque. Esta parte del mensaje es básicamente los datos enviados con él y los cuales serán utilizados por el cliente.
Tipos de mensajes MQTT
El byte de control que incluye la cabecera fija, indica el tipo de mensaje que se está enviando. Este tipo puede ser uno de los siguientes:
Message | Code |
---|---|
CONNECT | 0x10 |
CONNACK | 0x20 |
PUBLISH | 0x30 |
PUBACK | 0x40 |
PUBREC | 0x50 |
PUBREL | 0x60 |
PUBCOMP | 0x70 |
SUSBSCRBE | 0x80 |
SUBACK | 0x90 |
UNSUSCRIBE | 0xA0 |
UNSUBACK | 0xB0 |
PINGREQ | 0xC= |
PINGRESP | 0xD0 |
DISCONNECT | 0xE0 |
¿Cómo funciona el protocolo MQTT?
El protocolo MQTT se basa en el principio de modelo de publicación y suscripción (pub/sub). En este modelo uno o varios clientes se suscriben a un tema, y luego un cliente publica mensajes en ese tema. El mensaje publicado es distribuido entre todos los cliente que se hayan suscritp al tema como si de una lista de distribución se tratara.
Un cliente puede al mismo tiempo actuar como editor y como suscriptor, así que un mismo dispositivo puede recibir y enviar datos usando la misma conexión.
Temas
Los temas se organizan de forma jerárquica como si de un árbol de carpetas se tratase y es posible suscribirse a un tema concreto, o a una rama completa o parte de ella utilizando comodines. En el caso de que un tema no exista en el broker, este será creado de forma automática a la hora de publicar.
Comodines
Los comodines son usados para suscribirse a varios temas al mismo tiempo sin tener que hacerlo uno a uno. Además dependiendo de cómo se gestionen los temas permiten suscribirse a nuevos temas tan pronto sean creados sin necesidad de ir a suscribirse manualmente. Dos comodines de los que disponemos son «+», el cuál actúa sobre un nivel, y «#» que actuará sobre todos los niveles por debajo.
Por ejemplo, supongamos que tenemos sensores de temperatura, humedad y presión en el salón y la habitación de nuestra casa. Si creamos los temas con la siguiente nomenclatura:
- sensores/salon/temperatura
- sensores/salon/humedad
- sensores/salon/presion
- sensores/habitacion/interior
- sensores/habitacion/humedad
- sensores/habitacion/presion
Podremos utilizar el comodín «+» para suscribirnos a todos los sensores de temperatura por ejemplo, suscribiéndonos a «sensores/+/temperatura». Si en lugar de esto nos queremos suscribir a todos los sensores, podremos utilizar el comodín multinivel «#» y suscribirnos a «sensores/#». Si mañana añadimos otro juego de sensores en el exterior con los siguiente temas:
- sensores/exterior/temperatura
- sensores/exterior/humedad
- sensores/exterior/presion
Tanto la primera suscripción a temperatura como la segunda a todos los sensores, recibirán los mensajes correspondientes.
Recomendaciones de temas MQTT
- Nunca empieces el nombre de un tema con una barra («/sensores/…»). Esto provocará que se cree un nivel de temas sin texto de forma completamente innecesaria.
- Nunca uses espacios en los nombres de los temas. Esto está permitido por el protocolo, pero los espacios son enemigos naturales de la programación y pueden dar lugar a problemas y dificultades a la hora de debugear.
- Utiliza nombres cortos y concisos, sobre todo en casos en los que se utilicen dispositivos limitados. Cada letra en el nombre del tema añade un byte extra al mensaje, aumentando el uso de ancho de banda, la carga del broker, en los suscriptores… Obviamente deben ser descriptivos, ya que un tema con el nombre «a/h/s» no será muy claro.
- Utiliza caracteres ASCII únicamente siempre que sea posible. A pesar de que el protocolo soporta caracteres UTF-8, esto puede dar lugar a problemas a la hora de encontrar errores tipográficos. Además ciertos caracteres UTF-8 utilizan más de un byte y por lo tanto aumentarán el tamaño del mensaje.
- Incluye el nombre del cliente en el nombre del tema. Esto ayudará a identificar al cliente que envió ese mensaje de forma fácil, además de permitir añadir restricciones que eviten que dicho cliente pueda escribir en temas que no empiecen por su nombre por ejemplo.
- No te suscribas a todos los temas usando «#». Puede que tengas la tentación de hacerlo para guardar por ejemplo todos los mensajes en una BBDD persistente, pero esto puede dar problemas de carga en el cliente. Es mejor utilizar plugins para este propósito.
- Piensa bien el nombre de los temas. Piensa que si en el futuro tienes que añadir más temas, esto no te obligue a rehacer todos los nombres para encajarlos.
- No uses nombres genéricos para los temas. No pienses en los temas como si de una cola de procesamiento se tratase. Si tienes por ejemplo varios sensores en el salón, crea un tema por cada uno en lugar de enviar todo a «sensores/salon».
Mensajes retenidos
En el protocolo MQTT el broker no guarda los mensajes enviados. Esto es así para mantener un consumo de recursos lo más reducido posible y al mismo tiempo mantener la ligereza. En el caso de que se quieran almacenar los mensajes, esto deberá hacerse por métodos externos al protocolo MQTT.
Esto es así por norma, pero hay una excepción: los mensajes retenidos. Este tipo de mensaje está diseñado para mantener una copia del último mensaje enviado, permitiendo así que los nuevos suscriptores de este tema reciban este mensaje de forma automática. Esto es muy útil para casos en los que el envío de mensajes es muy poco frecuente, pero se quiera que los suscriptores tengan el último dato lo antes posible.
Volviendo al ejemplo de los sensores, supongamos que tenemos un sensor que envía información cada 15 minutos. Los nuevos suscriptores tendrán que esperar un tiempo indeterminado entre 0 y 15 minutos hasta que el sensor envíe la información de nuevo. Si queremos que el suscriptor tenga la información lo antes posible, activaremos la retención y por lo tanto el suscriptor recibirá la última lectura del sensor tan pronto se conecte.
Última voluntad y testamento en MQTT
En el mundo de la informática nada es perfecto, por lo que puede darse el caso de que un editor se desconecte sin previo aviso ya sea por problemas de conectividad, un corte de corriente o problemas de internet… El protocolo MQTT dispone de un mecanismo para ayudar con esto, el cuál permite que un editor registre un mensaje (que será guardado en la caché del broker), el cuál será enviado a los suscriptores en el caso de que ocurra una desconexión inesperada del editor. Este mensaje normalmente contiene la información del editor, por lo que se puede usar para actuar en consecuencia.
Seguridad en MQTT
Aunque el protocolo no fue pensado con la seguridad en mente, hay ciertos mecanismos que pueden ser usados para securizar las conexiones. Algunos de estos métodos aumentarán la carga del protocolo, pero no es algo que por norma sea preocupante a día de hoy. Entre estos métodos se pueden destacar los siguientes:
- Usuario y contraseña: Este es el método más básico y está completamente desaconsejado su uso si no es en combinación con algún otro método. Esto es debido a que por mantener su simplicidad, el protocolo envía estos datos en texto claro y por lo tanto son muy fáciles de interceptar. Esto es debido a que hace años no era tan trivial interceptar las comunicaciones por satélite, por lo que no era necesaria tanta seguridad. Por ello este método ha quedado relevado a un uso más orientado a evitar conexiones involuntarias.
- Seguridad de Red: Si la red está protegida de por sí (por ejemplo con el uso de una VPN, túneles SSH o similares), la inseguridad del protocolo MQTT pierde relevancia y por lo tanto no será necesario proteger las comunicaciones a nivel de protocolo. Por supuesto, el uso de este tipo de seguridad de forma exclusiva no asegura que sea seguro, ya que pueden producirse accesos no autorizados desde dentro de la red que puedan comprometerla.
- SSL/TLS: Una de las mejores soluciones para proteger las comunicaciones, es el uso del cifrado SSL/TLS entre el broker y los clientes. Por desgracia esto añade una carga sustancial al protocolo, por lo que el consumo de ancho de banda y recursos se verá incrementado. Esto no debería ser preocupante salvo que la conexión sea altamente inestable.