MQTT et IoT#
MQTT (Message Queuing Telemetry Transport) est un protocole de messagerie publish/subscribe conçu par Andy Stanford-Clark et Arlen Nipper en 1999 pour IBM. Initialement développé pour la télémétrie sur des liaisons satellite à faible bande passante, MQTT s’est imposé comme le protocole de référence de l’Internet des Objets (IoT) grâce à ses propriétés uniques : faible empreinte mémoire, faible consommation réseau, gestion robuste des connexions instables, et modèle publish/subscribe découplant producteurs et consommateurs de données.
Ce chapitre couvre l’architecture pub/sub, le protocole MQTT en détail, les niveaux de QoS, MQTT 5.0, et les cas d’usage IoT. Le code paho-mqtt est présenté en mode illustratif (broker requis) ; des simulations matplotlib illustrent le protocole.
Architecture publish/subscribe#
Modèle pub/sub vs requête-réponse#
Le modèle publish/subscribe (pub/sub) est fondamentalement différent du modèle requête-réponse de HTTP :
Requête-réponse (HTTP/REST) :
Couplage direct : le client connaît l’adresse du serveur
Synchrone : le client attend la réponse
1-à-1 : une requête produit une réponse
Publish/Subscribe (MQTT) :
Découplage total : producteurs et consommateurs ne se connaissent pas
Asynchrone : le producteur publie et continue ; les consommateurs reçoivent quand ils sont prêts
1-à-N ou N-à-N : un message peut atteindre zéro, un ou plusieurs consommateurs
Les trois acteurs du modèle MQTT :
Broker : serveur central qui reçoit tous les messages et les redistribue aux abonnés concernés (Mosquitto, HiveMQ, EMQX, AWS IoT Core)
Publisher (éditeur) : client qui envoie des messages sur un topic
Subscriber (abonné) : client qui s’inscrit à un ou plusieurs topics et reçoit les messages correspondants
Un client peut être à la fois publisher et subscriber
Dans MQTT, un même client peut publier sur certains topics et s’abonner à d’autres simultanément. Par exemple, un capteur intelligent publie ses mesures mais s’abonne à un topic de commandes pour recevoir des instructions de configuration.
Le protocole MQTT en détail#
Structure des paquets MQTT#
Chaque paquet MQTT commence par un en-tête fixe de 1 à 5 octets, suivi d’un en-tête variable et d’une payload.
En-tête fixe :
Octet 1 : [Type (4 bits)] [Flags (4 bits)]
Octets 2-5 : Remaining Length (1-4 octets, encodé varint MQTT)
Types de paquets MQTT principaux :
Code |
Nom |
Direction |
Description |
|---|---|---|---|
1 |
CONNECT |
C→S |
Établissement de connexion |
2 |
CONNACK |
S→C |
Accusé de connexion |
3 |
PUBLISH |
Bidirectionnel |
Publication d’un message |
4 |
PUBACK |
C↔S |
Accusé QoS 1 |
5 |
PUBREC |
C↔S |
Reçu QoS 2 (étape 1) |
6 |
PUBREL |
C↔S |
Relâcher QoS 2 (étape 2) |
7 |
PUBCOMP |
C↔S |
Complété QoS 2 (étape 3) |
8 |
SUBSCRIBE |
C→S |
Abonnement à des topics |
9 |
SUBACK |
S→C |
Accusé d’abonnement |
12 |
PINGREQ |
C→S |
Keepalive ping |
13 |
PINGRESP |
S→C |
Keepalive pong |
14 |
DISCONNECT |
C→S |
Déconnexion propre |
Connexion CONNECT/CONNACK#
Paquet CONNECT MQTT 3.1.1 :
============================================================
Taille totale : 123 octets
En-tête fixe : 10 79
→ Type : CONNECT (0x10)
→ Remaining Length : 121 octets
En-tête variable et payload :
Nom protocole : b'\x00\x04MQTT'
Version : 04 (MQTT 3.1.1 = 0x04)
Flags : 11001110
Clean Session: True
Will Flag : True
Username : True
Password : True
Keep Alive : 60s
Topics MQTT : hiérarchie et wildcards#
Structure des topics#
Un topic MQTT est une chaîne UTF-8 hiérarchique utilisant / comme séparateur de niveaux. Il n’y a pas de limite de longueur formelle (max 65535 caractères en pratique).
Exemples de hiérarchies de topics :
home/salon/temperature
home/salon/humidite
home/cuisine/temperature
home/exterieur/pluie
usine/ligne1/machine3/temperature
usine/ligne1/machine3/vibration
flotte/vehicule/V001/position
flotte/vehicule/V001/carburant
$SYS/broker/clients/connected
$SYS/broker/messages/received
Wildcards#
MQTT définit deux caractères wildcard pour les abonnements :
+ (Single-Level Wildcard) : remplace exactement un niveau de topic.
home/+/temperaturecorrespond àhome/salon/temperature,home/cuisine/temperature, mais pas àhome/salon/sous-sol/temperature
# (Multi-Level Wildcard) : remplace zéro ou plusieurs niveaux. Doit être le dernier caractère.
home/#correspond àhome/salon/temperature,home/cuisine/humidite,home/exterieur/pluie, etc.#seul correspond à tous les topics (sauf$SYS)
Topics système $SYS : topics réservés publiés par le broker pour son monitoring. Ils ne sont pas accessibles via # mais nécessitent un abonnement explicite à $SYS/#.
Niveaux de QoS#
MQTT définit trois niveaux de Qualité de Service (QoS) qui régissent la garantie de livraison des messages :
Retained messages et Last Will Testament#
Retained message (message retenu) : quand un publisher envoie un message avec le flag RETAIN=1, le broker conserve ce message. Tout nouvel abonné reçoit immédiatement le dernier message retenu dès son abonnement, sans attendre le prochain publish. Utile pour l’état actuel des capteurs.
# Publier avec rétention
publisher.publish("home/salon/temp", "23.4", retain=True)
# Tout nouvel abonné reçoit immédiatement "23.4"
# sans attendre la prochaine publication
Last Will Testament (Testament) : lors de la connexion, le client peut spécifier un message qui sera publié automatiquement par le broker si la connexion est perdue anormalement (perte réseau, crash) sans DISCONNECT propre.
# Configuration du testament lors de la connexion
client.will_set(
topic="home/capteurs/status",
payload='{"capteur": "salon-001", "statut": "offline"}',
qos=1,
retain=True,
)
Will vs DISCONNECT
Le testament n’est déclenché que si la connexion se perd de façon anormale (pas de DISCONNECT reçu par le broker). Une déconnexion propre via DISCONNECT supprime le testament. Cela permet de distinguer un arrêt prévu (maintenance) d’une panne.
MQTT 5.0 : nouvelles fonctionnalités#
MQTT 5.0 (OASIS, 2019) apporte des améliorations majeures par rapport à MQTT 3.1.1 :
Propriétés : chaque paquet peut transporter des propriétés supplémentaires (paires clé-valeur typées), permettant d’ajouter des métadonnées sans modifier le payload :
Message-Expiry-Interval: durée de validité d’un message retenuContent-Type: type MIME du payload (application/json, etc.)Correlation-Data: identifiant pour le pattern requête-réponseUser-Properties: propriétés personnalisées applicatives
Shared Subscriptions : plusieurs subscribers partagent la charge d’un topic sous la forme $share/{groupe}/{topic}. Le broker distribue les messages en round-robin ou load-balanced entre les membres du groupe.
# Subscribers dans le groupe "dashboard"
$share/dashboard/home/capteurs/+/temperature
Request/Response Pattern : MQTT 5.0 formalise le pattern requête-réponse avec les propriétés Response-Topic et Correlation-Data, permettant aux clients de recevoir des réponses sur un topic dédié.
Mosquitto : installation et configuration#
Eclipse Mosquitto est l’implémentation open-source de référence du broker MQTT.
Installation#
# Ubuntu/Debian
sudo apt install mosquitto mosquitto-clients
# macOS (Homebrew)
brew install mosquitto
# Docker
docker run -it -p 1883:1883 -p 9001:9001 eclipse-mosquitto
Configuration de base#
# /etc/mosquitto/mosquitto.conf
# Port d'écoute
listener 1883
# Port TLS
listener 8883
certfile /etc/mosquitto/certs/server.crt
keyfile /etc/mosquitto/certs/server.key
cafile /etc/mosquitto/certs/ca.crt
# Authentification par fichier de mots de passe
password_file /etc/mosquitto/passwd
allow_anonymous false
# ACL (contrôle d'accès aux topics)
acl_file /etc/mosquitto/acl
# Logs
log_dest file /var/log/mosquitto/mosquitto.log
log_type all
# Persistance des messages QoS 1/2 et retained
persistence true
persistence_location /var/lib/mosquitto/
Fichier ACL#
# /etc/mosquitto/acl
# L'utilisateur "capteurs" peut publier sur home/capteurs/# et lire $SYS
user capteurs
topic write home/capteurs/#
topic read $SYS/broker/uptime
# L'utilisateur "dashboard" peut lire home/#
user dashboard
topic read home/#
topic read $SYS/#
# Un utilisateur pattern (utilise %u pour le nom d'utilisateur)
pattern write maison/%u/#
Code Python avec paho-mqtt#
Les blocs suivants nécessitent un broker MQTT actif (Mosquitto local ou cloud MQTT).
Publisher avec paho-mqtt#
import paho.mqtt.client as mqtt
import json
import time
import random
import ssl
BROKER = "localhost"
PORT = 1883
# Pour TLS : PORT = 8883
def creer_client_mqtt(client_id: str, username: str = None,
password: str = None) -> mqtt.Client:
"""Crée et configure un client MQTT paho."""
client = mqtt.Client(
client_id=client_id,
protocol=mqtt.MQTTv5, # MQTT 5.0
callback_api_version=mqtt.CallbackAPIVersion.VERSION2,
)
# Authentification
if username and password:
client.username_pw_set(username, password)
# TLS (décommenter pour production)
# client.tls_set(
# ca_certs="/etc/ssl/certs/ca-bundle.crt",
# certfile=None,
# keyfile=None,
# tls_version=ssl.PROTOCOL_TLSv1_2,
# )
# Last Will Testament
client.will_set(
topic=f"maison/capteurs/{client_id}/status",
payload=json.dumps({"statut": "offline", "id": client_id}),
qos=1,
retain=True,
)
return client
def on_connect(client, userdata, flags, reason_code, properties):
if reason_code == 0:
print(f"Connecté au broker MQTT")
# Publier le statut en ligne
client.publish(
f"maison/capteurs/{client.client_id}/status",
json.dumps({"statut": "online"}),
qos=1,
retain=True,
)
else:
print(f"Connexion échouée : code {reason_code}")
def publisher_capteur(client_id: str = "capteur-salon-001"):
"""Simule un capteur IoT publiant des mesures périodiques."""
client = creer_client_mqtt(client_id)
client.on_connect = on_connect
client.connect(BROKER, PORT, keepalive=60)
client.loop_start()
try:
sequence = 0
while True:
sequence += 1
mesure = {
"id": client_id,
"seq": sequence,
"ts": time.time(),
"temperature": round(22 + random.gauss(0, 1.5), 2),
"humidite": round(55 + random.gauss(0, 5), 1),
"co2_ppm": int(400 + random.gauss(0, 30)),
}
# QoS 0 pour les mesures fréquentes (peut se perdre)
client.publish(
f"maison/capteurs/{client_id}/mesures",
json.dumps(mesure),
qos=0,
)
# QoS 1 pour les alertes (ne doit pas se perdre)
if mesure["co2_ppm"] > 1000:
alerte = {"capteur": client_id, "type": "co2_eleve",
"valeur": mesure["co2_ppm"]}
client.publish(
"maison/alertes",
json.dumps(alerte),
qos=1,
)
print(f"[{sequence}] T={mesure['temperature']}°C H={mesure['humidite']}% CO₂={mesure['co2_ppm']}ppm")
time.sleep(5)
except KeyboardInterrupt:
pass
finally:
client.publish(
f"maison/capteurs/{client_id}/status",
json.dumps({"statut": "offline_propre"}),
qos=1,
retain=True,
)
client.loop_stop()
client.disconnect()
publisher_capteur()
Subscriber avec gestion des QoS#
import paho.mqtt.client as mqtt
import json
import time
BROKER = "localhost"
PORT = 1883
def on_connect(client, userdata, flags, reason_code, properties):
print(f"Connecté, code={reason_code}")
# Abonnements avec différents QoS
abonnements = [
("maison/capteurs/#", 0), # Mesures : QoS 0
("maison/alertes", 1), # Alertes : QoS 1
("maison/commandes/#", 2), # Commandes : QoS 2 (exactement une fois)
("$SYS/broker/clients/connected", 0), # Monitoring broker
]
client.subscribe(abonnements)
def on_message(client, userdata, msg):
topic = msg.topic
try:
payload = json.loads(msg.payload.decode("utf-8"))
except (json.JSONDecodeError, UnicodeDecodeError):
payload = msg.payload.decode("utf-8", errors="replace")
print(f"[{topic}] QoS={msg.qos} retain={msg.retain}")
print(f" Payload : {json.dumps(payload, indent=2, ensure_ascii=False)[:200]}")
def on_subscribe(client, userdata, mid, reason_codes, properties):
for i, rc in enumerate(reason_codes):
print(f"Abonnement #{i} accordé avec QoS {rc}")
def on_disconnect(client, userdata, flags, reason_code, properties):
print(f"Déconnecté (code={reason_code}), reconnexion dans 5s...")
time.sleep(5)
client.reconnect()
client = mqtt.Client(
client_id="dashboard-principal",
protocol=mqtt.MQTTv5,
callback_api_version=mqtt.CallbackAPIVersion.VERSION2,
)
client.on_connect = on_connect
client.on_message = on_message
client.on_subscribe = on_subscribe
client.on_disconnect = on_disconnect
client.connect(BROKER, PORT, keepalive=30)
# Loop bloquant (utiliser loop_start() + loop_stop() pour non-bloquant)
client.loop_forever()
QoS 2 : publication avec garantie exacte#
import paho.mqtt.client as mqtt
import json
def on_publish(client, userdata, mid, reason_code, properties):
"""Appelé quand le publish QoS 2 est complètement acquitté (PUBCOMP reçu)."""
print(f"Message QoS 2 livré (mid={mid})")
client = mqtt.Client(
client_id="capteur-critique",
protocol=mqtt.MQTTv5,
callback_api_version=mqtt.CallbackAPIVersion.VERSION2,
)
client.on_publish = on_publish
client.connect("localhost", 1883)
client.loop_start()
# Publication QoS 2 : exactement une livraison garantie
# Utiliser pour les commandes critiques (ouvrir une porte, déclencher une alarme)
result = client.publish(
topic="maison/commandes/alarme",
payload=json.dumps({"action": "activer", "zone": "exterieur"}),
qos=2,
retain=False,
)
result.wait_for_publish(timeout=10.0)
print(f"Publish QoS 2 : rc={result.rc}, mid={result.mid}")
client.loop_stop()
client.disconnect()
Simulation MQTT sans broker#
Résumé de la simulation :
Messages publiés : 36
Messages retenus : 3
Alertes déclenchées : 0
Abonnements actifs : 3
Use cases IoT#
Capteurs et domotique#
MQTT est le protocole standard de la domotique moderne. Des plateformes comme Home Assistant utilisent MQTT pour intégrer des milliers de types d’appareils. Le protocole Zigbee2MQTT traduit les trames Zigbee en messages MQTT, permettant à n’importe quel logiciel de domotique de contrôler des appareils Zigbee sans gateway propriétaire.
Flotte de véhicules connectés#
Comparaison MQTT vs autres protocoles IoT#
Résumé#
MQTT est le protocole de référence de l’IoT pour de bonnes raisons : son overhead minimal (2 octets d’en-tête), ses niveaux de QoS adaptés aux réseaux instables, et son modèle pub/sub découplant producteurs et consommateurs sont parfaitement adaptés aux contraintes des objets connectés.
Points clés à retenir :
QoS 0 (fire and forget) : capteurs haute fréquence, pertes tolérées
QoS 1 (at least once) : alertes, notifications, garantie de réception mais doublons possibles
QoS 2 (exactly once) : commandes critiques, transactions, overhead le plus élevé
Retained messages : état actuel des capteurs immédiatement disponible pour les nouveaux abonnés
Last Will Testament : détection automatique des déconnexions anormales
MQTT 5.0 : propriétés riches, shared subscriptions, request/response natif
Mosquitto : broker de référence, configuration simple, ACL puissantes
L’écosystème MQTT s’étend bien au-delà de la domotique : véhicules connectés, industrie 4.0 (IIoT), agriculture de précision, smart cities, et toute application nécessitant une communication légère et robuste entre de nombreux appareils hétérogènes.