MQTT
Le protocole MQTT (Message Queuing Telemetry Transport) est un protocole de type publish/subscribe développé en 1999 par Andy Stanford-Clark (IBM) et Arlen Nipper (Arcom, Eurotech et Cirrus Link). À l’origine, ce protocole servait à surveiller un oléoduc dans le désert et comme la liaison satellite qu’ils utilisaient à l’époque était très chère, le protocole devait être efficace en bande passante.
Les Brokers
Pour que des systèmes puissent communiquer entre eux, MQTT a besoin d’un broker. Le broker joue le rôle de “serveur” et c’est lui qui reçoit et distribue les messages conformément aux abonnements (subscriptions) des clients.
Plusieurs brokers sont librement disponibles et vous n’avez pas besoin d’écrire le vôtre pour utiliser MQTT. Les plus populaires d’entre eux sont :
- RabbitMQ : écrit en Erlang et qui implémente plusieurs protocoles
- HiveMQ Community Edition : écrit en Java
- Mosquitto : écrit en C
Ces brokers sont aussi disponible sous la forme d’une image Docker facile à mettre en œuvre.
Si c’est juste pour tester, vous pouvez aussi utiliser des brokers dans le cloud :
Les Clients
Les clients sont les participants aux échanges. Un client peut être un publisher (éditeur), un subscriber (abonné), ou les deux à la fois.
Chaque client doit avoir un identificateur unique appelé Client ID. Il est important que chaque client puisse être identifié de manière univoque grâce à cet ID. Si un deuxième client venait à se connecter au broker avec le même ID, alors il “volerait” la session du premier.
Une possibilité pour garantir l’unicité des ID consiste à utiliser l’adresse MAC de la carte réseau, ou tout autre identifiant unique déjà disponible sur le système.
Attention
Notez qu’avec cette pratique, si vous devez remplacer un appareil défectueux, vous changerez son identifiant et vous risquez alors de devoir reconfigurer votre système!
Les Topics
Les topics permettent d’organiser les messages dans MQTT. Lorsqu’un client publie un message, il l’associe à un topic. C’est un peu comme une clé ou un URI qui donne une identité au message.
Les topics sont des chaînes de caractères codés en UTF8 et qui sont structurés
en plusieurs niveaux séparés par des barres obliques /
. Un topic ressemble
donc beaucoup à un path pour un fichier. C’est une structure hiérarchique
qui n’a pas forcément une racine commune.
Voici quelques exemples de topics :
appartement/rez-de-chaussée/salon/température
CH/Fribourg/Pérolles/80
44EE0753-0CFD-4F5C-970D-DE8D31738FA6/etat
Notez que les topics sont sensibles aux majuscules et minuscules et qu’ils
ne peuvent pas être vides. Un topic
ne peut pas non plus commencer par un
dollar ($
) car ces topics sont réservés pour le système lui-même.
Lorsqu’un client publie un message, il utilise toujours un topic complet, mais lorsqu’il s’abonne à certains topics, il peut utiliser des caractères “jokers” (wildcard character). MQTT permet deux types de jokers :
- Un plus (
+
) permet de laisser passer n’importe quoi sur un niveau donné. Par exempleappartement/rez-de-chaussée/+/température
recevra toutes les températures de toutes les chambres du rez-de-chaussée. - Un dièse (
#
) permet de laisser passer n’importe quoi sur plusieurs niveaux. Il ne peut y avoir qu’un seul#
dans un topic et ça doit être le dernier caractère. Par exempleappartement/#
s’abonne à tous les messages descendants de l’appartement`.
Les bonnes pratiques pour définir les topics sont les suivantes
- Ne commencez pas le topic par un slash (
/
). Le premier niveau définit la racine d’une arborescence. - N’utilisez que les caractères ASCII simples. Choisissez plutôt des topics en anglais et évitez les caractères accentués, les caractères spéciaux et les espaces.
- Choisissez des topics courts et concis. En effet, les topics sont transmis dans chaque paquet et des topics longs gaspillent inutilement de la bande passante.
- Il est parfois utile de connaître l’expéditeur d’un message. Si c’est le cas, placez le client ID dans un niveau du topic.
- Ne vous abonnez pas à la racine
#
. La plupart des brokers l’interdisent et vous risquez de recevoir plus de messages que votre système est capable de gérer.
Exercice MQTT/1
Concevez des topics pour les applications suivantes:
- Station météo connectée
- Contrôle d’une habitation (domotique)
- Messagerie de type WhatsApp
Les Messages
Lorsqu’un client publie sur un topic, il publie également du contenu. MQTT ne définit pas la structure de ce contenu et vous pouvez envoyer ce que vous voulez (du texte, du JSON, du Protocol Buffer, des images, …). On dit que MQTT est agnostique en matière de données (data agnostic)
Qualité de Service
La qualité de service (ou QoS pour Quality of Service) garantit la livraison des paquets entre les participants. On pourrait se demander pourquoi nous avons besoin de faire quelque chose de spécial car MQTT est basé sur TCP qui garantit déjà un transport fiable, mais le QoS de MQTT va plus loin. Il garantit la livraison des paquets même si le client subit une déconnexion plus longue (par exemple quand un véhicule traverse un tunnel) ou s’il change de réseau (par exemple de WiFi à LTE)
MQTT définit 3 niveaux de qualité de service
- Niveau 0 (At most once) : Le client envoie le message, mais le broker ne quittance pas la réception et il n’y a aucune garantie que le broker a bien reçu le message. Vous pouvez utiliser ce niveau si la perte d’un message n’a pas vraiment d’importance pour votre système. Par exemple, pour une station météo qui envoie la température toutes les 10 secondes, on peut sans problème tolérer la perte d’un paquet.
- Niveau 1 (At least once) : Cette fois le broker quittance la réception du message, mais si cette quittance est perdue, alors le client décide de renvoyer le message une seconde fois. On est donc sûr que le message a bien été reçu par le broker, mais il se peut que les abonnées reçoivent plusieurs fois ce message. Pour la plupart des applications, le niveau 1 est le niveau de QoS recommandé.
- Niveau 2 (Exactly once) : Dans de rares cas où on doit être sûr que le broker reçoive tous les messages et qu’on ne peut pas tolérer des duplicatas, alors on utilise le niveau 2. Dans ce niveau, les échanges sont plus compliqués, plus lents, et consomment plus de bande passante.
La bonne pratique est d’utiliser le niveau 1 par défaut et de choisir un autre niveau seulement si c’est nécessaire.
Les Messages Conservés
Lorsqu’un client publie un message, ce message est normalement ajouté dans la liste (queue) de chaque client. Un nouveau client qui se connecte au broker et qui s’abonne à un topic commence avec une liste vide et il doit attendre qu’un client publie quelque chose pour recevoir un premier message.
Dans certains cas, ce n’est pas vraiment idéal. Reprenons le cas de notre station météo. Si, pour économiser l’énergie, le système n’envoie la température qu’une fois par minute. Un nouveau client doit attendre en moyenne 30 secondes avant d’afficher quoi que ce soit. Ca serait plus pratique si le broker se souvenait du dernier message reçu et qu’il pouvait directement l’envoyer au nouveau client. MQTT a prévu ce cas, et quand un client publie un message, il peut le marquer comme retained message. Le broker se souvient donc de ce message et peut directement l’envoyer aux nouveaux clients.
Dernière Volonté et Testament
La dernière fonctionnalité pour améliorer la fiabilité d’un système que nous étudions avec MQTT c’est la “dernière volonté”. Lorsqu’un client se connecte au broker, il peut mentionner une dernière volonté sous la forme d’un topic associé à un message. Dès que le broker constate la perte brutale de son client, alors il publie sa dernière volonté.
Cette fonctionnalité permet de diffuser l’information relative à la perte d’un client et permet aux autres participants de prendre les mesures qui s’imposent.
Exercice
Exercice MQTT/2
- Installez un client MQTT sur votre PC. Je vous recommande MQTT Explorer pour commencer, mais vous pouvez installer des outils en ligne de commande (https://hivemq.github.io/mqtt-cli/, https://mosquitto.org/download/)
- Par groupe de deux, choisissez un broker de test (par exemple
test.mosquitto.org
) ainsi qu’un topic (par exempleheiafr/isc-rs-2/group/1/msg
) - Le premier étudiant se prépare à recevoir les messages (par exemple
en s’abonnant à
heiafr/isc-rs-2/#
)1 - Le second étudiant publie un message dans le topic choisi
Défi : Envoyer “I Love Embedded Systems” sur le topic
heiafr/isc/edu/<student name>/msg
en remplaçant <student name>
par votre nom
Format des Paquets MQTT
Le format des paquets MQTT est très simple. Il est composé de 3 parties :
- Fixed header : Un entête fixe présent dans tous les paquets MQTT
- Variable header : Un entête “variable” présent dans certains paquets
- Payload : Le payload présent lui aussi dans certains paquets
Le fixed header est lui aussi composé de trois parties :
- Le Control Packet Type indique le type de paquet (voir la table ci-dessous)
- Les Flags sont spécifiques aux Control Packet Type. Par exemple, avec un message de type PUBLISH, les flags permettent de stocker le niveau de QoS ou d’indiquer si le message doit être de type “conservé” (retained).
- Le Packet Length indique la taille du reste du paquet. Cette taille est codée de manière compacte sur 1, 2, 3 ou 4 bytes. Les petits paquets (moins de 128 Byte) sont codés sur un Byte, et avec 4 Byte, la taille maximum représentable est 268‘435‘455 (256 MByte). Les paquets MQTT sont donc limités à 256 MByte).
Type de paquet :
Name | Value | Description |
---|---|---|
Reserved | 0 | Reserved |
CONNECT | 1 | Client request to connect to Server |
CONNACK | 2 | Connect acknowledgment |
PUBLISH | 3 | Publish message |
PUBACK | 4 | Publish acknowledgment |
PUBREC | 5 | Publish received (assured delivery part 1) |
PUBREL | 6 | Publish release (assured delivery part 2) |
PUBCOMP | 7 | Publish complete (assured delivery part 3) |
SUBSCRIBE | 8 | Client subscribe request |
SUBACK | 9 | Subscribe acknowledgment |
UNSUBSCRIBE | 10 | Unsubscribe request |
UNSUBACK | 11 | Unsubscribe acknowledgment |
PINGREQ | 12 | PING request |
PINGRESP | 13 | PING response |
DISCONNECT | 14 | Client is disconnecting |
Reserved | 15 | Reserved |
Le pseudocode suivant est utilisé pour encoder une taille \(X\) (source : Spécification MQTT Version 3.1.1) :
do
encodedByte = X MOD 128
X = X DIV 128
// if there are more data to encode, set the top bit of this byte
if ( X > 0 )
encodedByte = encodedByte OR 128
endif
'output' encodedByte
while ( X > 0 )
Exercice MQTT/3
Comment est codée une taille de 1700 Byte ?
Solution
a4 0d
(\(13 \cdot 128 + 36\))
Exercice MQTT/4
Écrivez le pseudo-code de la méthode qui permet de décoder une taille
Solution
multiplier = 1
value = 0
do
encodedByte = 'next byte from stream'
value += (encodedByte AND 127) * multiplier
if (multiplier > 128*128*128)
throw Error(Malformed Remaining Length)
multiplier *= 128
while ((encodedByte AND 128) != 0)
Exercice MQTT/5
Quel est le numéro de port utilisé par défaut par MQTT ?
Solution
1883
Exercice MQTT/6
Démarrez “Wireshark” sur votre PC, capturez les paquets MQTT et publiez un message de plus de 128 Bytes vers un broker. Étudiez la capture regardez comment est codée la taille du paquet.
Faites le codage à la main et vérifiez que vous obtenez la même chose
Solution
if=/dev/urandom bs=1 count=1672 | \
mosquitto_pub -h test.mosquitto.org \
-t heiafr/isc-2rs/group/1/msg -s
Alternatives à MQTT
MQTT est souvent un très bon choix pour les systèmes embarqués, mais si vous avez besoin d’autre chose, voici quelques alternatives possibles :
- CoAP : voir les autres chapitres de ce cours
- AMQP : moins populaire dans les systèmes embarqués
- XMPP : basé sur XML, donc assez lourd pour les systèmes embarqués
Pour implémenter le modèle publisher/subscriber on peut aussi utiliser les systèmes suivants :
- ZeroMQ : Un système performant ne nécessitant pas de broker.
- Redis : Redis est plutôt un système de stockage de données (en RAM) souvent utilisé pour implémenter un cache, mais il offre également des fonctions de publisher/subscriber.
- NATS : Un système de messaging écrit en Go avec des librairies clients disponibles pour la plupart des langages.
- Kafka : Un système plutôt adapté au cloud et aux plus gros systèmes.
-
Avec MQTT Explorer, lorsque vous configurez la connexion, vous devez cliquer sur “ADVANCED” et configurer l’abonnement (Subscription) ↩