Les options d'abonnement MQTT sont des flags propres à chaque abonnement, introduits dans MQTT 5.0, qui contrôlent la manière dont le broker vous livre les messages : recevez-vous vos propres publications, le flag retain est-il conservé, et les retained messages sont-ils rejoués lorsque vous vous abonnez ? À la fin de cet article, vous saurez ce que fait chaque option, quels problèmes concrets elle résout et comment la définir en JavaScript et en Python.
Où se trouvent les options d'abonnement
En MQTT 3.1.1, un paquet SUBSCRIBE contient un filtre de topic et un octet qui indique la QoS demandée. MQTT 5.0 conserve cet octet mais utilise les bits libres pour de nouvelles options. Chaque filtre de topic d'un SUBSCRIBE dispose de son propre octet d'options :
| Bits | Option | Valeurs |
|---|---|---|
| 0–1 | Maximum QoS | 0, 1 ou 2 |
| 2 | No Local | 0 = désactivé, 1 = activé |
| 3 | Retain As Published | 0 = désactivé, 1 = activé |
| 4–5 | Retain Handling | 0, 1 ou 2 |
| 6–7 | Réservés | doivent valoir 0 |
Comme les options sont définies par filtre, un même client peut, dans le même paquet, s'abonner à chat/room1 avec No Local activé et à config/# avec un comportement de rétention différent. Les définitions complètes figurent dans la spécification OASIS MQTT 5.0. Les options n'existent que si le client se connecte avec la version 5 du protocole ; une connexion 3.1.1 ne peut pas les exprimer. Pour le reste de cette version, consultez notre tour d'horizon des fonctionnalités de MQTT 5.
Maximum QoS
Il s'agit de la QoS bien connue de MQTT 3.1.1, renommée pour clarifier sa signification. C'est la QoS la plus élevée à laquelle le broker livrera les messages sur cet abonnement. Un message publié en QoS 2 sur un abonnement avec une Maximum QoS de 1 arrive en QoS 1. Un message publié en QoS 0 n'est jamais surclassé. Si les niveaux de QoS MQTT sont nouveaux pour vous, lisez d'abord ce guide, car la règle de déclassement est à l'origine de nombreuses surprises de livraison.
No Local : ne plus recevoir ses propres messages
Par défaut, si un client publie sur un topic auquel il est également abonné, le broker lui renvoie le message. Avec No Local activé, le broker ne transmet pas un message à la connexion qui l'a publié.
Cas typiques :
- Applications de chat et de collaboration. Chaque participant publie sur
chat/room1et s'y abonne. Sans No Local, chaque utilisateur voit son propre message deux fois : une fois dans l'interface locale et une fois renvoyé par le broker. - Synchronisation bidirectionnelle. Un service qui publie et écoute à la fois sur
devices/+/configtraiterait sinon ses propres mises à jour et pourrait boucler. - Ponts. Un pont qui s'abonne et republie sur le même broker peut créer des boucles de messages ; No Local côté pont les interrompt.
Deux règles à retenir. No Local fonctionne par connexion, identifiée par le client ID : deux clients différents exécutés par la même application reçoivent donc toujours les messages l'un de l'autre. Et activer No Local sur un abonnement partagé ($share/...) est une erreur de protocole, car « local » n'a pas de sens pour un groupe.
Retain As Published
Lorsque le broker transmet un message en direct aux abonnés, il efface par défaut le flag RETAIN, même si l'éditeur l'avait activé. Le flag n'est activé que sur les messages envoyés par le broker en raison d'un nouvel abonnement (le retained message stocké). Ce comportement par défaut permet aux abonnés de distinguer « voici la dernière valeur stockée » de « ceci vient de se produire ».
Avec Retain As Published activé, le broker conserve le flag RETAIN exactement tel que l'éditeur l'a défini. Vous en avez surtout besoin lorsque l'abonné republie les messages ailleurs et que le statut retenu doit être préservé :
- Ponts entre brokers qui répliquent l'état retenu.
- Passerelles et proxys qui transmettent à un second système qui gère lui-même les retained messages.
- Outils de débogage qui veulent montrer ce que l'éditeur a réellement envoyé.
Pour les abonnés applicatifs ordinaires, laissez-la désactivée.
Retain Handling : contrôler le rejeu des messages retenus
Les retained messages sont envoyés à un client lorsqu'il s'abonne. Retain Handling détermine quand cela se produit :
| Valeur | Comportement | À utiliser lorsque |
|---|---|---|
| 0 | Envoie les retained messages à chaque abonnement (par défaut) | Tableaux de bord et consommateurs qui ont besoin de l'état courant |
| 1 | Envoie les retained messages uniquement si l'abonnement n'existait pas déjà | Clients avec session persistante qui se réabonnent à la reconnexion |
| 2 | N'envoie jamais de retained messages pour cet abonnement | Processeurs d'événements qui ne s'intéressent qu'aux nouveaux messages |
La valeur 1 résout un problème subtil. Un appareil doté d'une session persistante se reconnecte et se réabonne par précaution. Avec la valeur 0, il reçoit à nouveau tous les retained messages, et s'il s'agit de messages de configuration, il risque de les réappliquer. Avec la valeur 1, le broker ne rejoue les retained messages que lorsque l'abonnement est réellement nouveau.
La valeur 2 est utile pour les services de journalisation et d'alerte. Si un service d'alerte s'abonne à alarms/# et que le broker conserve des alarmes retenues datant de plusieurs heures, la valeur 0 lui ferait déclencher d'anciennes alertes à chaque redémarrage.
Définir les options d'abonnement MQTT dans le code
Les deux exemples se connectent au serveur MQTT public gratuit broker.connectmq.com via TLS en MQTT 5. Utilisez votre propre préfixe de topic unique ; l'espace public est lisible par tous, gardez donc des données inoffensives.
MQTT.js (Node.js)
// npm install mqtt
const mqtt = require("mqtt");
const prefix = "connectmq/demo/c41e";
const client = mqtt.connect("mqtts://broker.connectmq.com:8883", {
clientId: "subopts-js-c41e",
protocolVersion: 5,
});
client.on("connect", () => {
// No Local : ne pas nous renvoyer nos propres messages de chat
client.subscribe(`${prefix}/chat/room1`, { qos: 1, nl: true }, (err, granted) => {
if (err) return console.error(err);
console.log("chat granted:", granted);
client.publish(`${prefix}/chat/room1`, "hello from js", { qos: 1 });
});
// Retain Handling 2 : ignorer les alarmes stockées, seulement les nouvelles
client.subscribe(`${prefix}/alarms/#`, { qos: 1, rh: 2 });
// Retain As Published : conserver le flag RETAIN tel qu'envoyé
client.subscribe(`${prefix}/config/#`, { qos: 1, rap: true, rh: 1 });
});
client.on("message", (topic, payload, packet) => {
console.log(`${topic} retain=${packet.retain} -> ${payload.toString()}`);
});
Lancez-le : vous ne verrez pas « hello from js » s'afficher, car No Local supprime l'écho. Publiez sur le même topic de chat depuis un autre client et le message apparaît.
Python paho-mqtt 2.x
import ssl
import paho.mqtt.client as mqtt
from paho.mqtt.subscribeoptions import SubscribeOptions
PREFIX = "connectmq/demo/c41e"
def on_connect(client, userdata, flags, reason_code, properties):
if reason_code.is_failure:
print("Connect failed:", reason_code)
return
client.subscribe([
(f"{PREFIX}/chat/room1", SubscribeOptions(qos=1, noLocal=True)),
(f"{PREFIX}/alarms/#", SubscribeOptions(
qos=1, retainHandling=SubscribeOptions.RETAIN_DO_NOT_SEND)),
(f"{PREFIX}/config/#", SubscribeOptions(
qos=1, retainAsPublished=True,
retainHandling=SubscribeOptions.RETAIN_SEND_IF_NEW_SUB)),
])
def on_subscribe(client, userdata, mid, reason_code_list, properties):
print("SUBACK:", [str(rc) for rc in reason_code_list])
def on_message(client, userdata, msg):
print(f"{msg.topic} retain={msg.retain} -> {msg.payload.decode()}")
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2,
client_id="subopts-py-c41e", protocol=mqtt.MQTTv5)
client.tls_set(cert_reqs=ssl.CERT_REQUIRED)
client.on_connect = on_connect
client.on_subscribe = on_subscribe
client.on_message = on_message
client.connect("broker.connectmq.com", 8883, keepalive=60)
client.loop_forever()
Vérifier le comportement
Un plan de test rapide à exécuter en quelques minutes :
- Publiez un retained message sur
connectmq/demo/c41e/alarms/pump1depuis n'importe quel client, par exemple le client MQTT intégré au navigateur du portail ConnectMQ oumosquitto_pub -r. - Lancez le script Python. L'abonnement aux alarmes utilise Retain Handling 2 : l'alarme retenue n'est donc pas livrée.
- Publiez une nouvelle alarme non retenue. Elle arrive immédiatement.
- Publiez un retained message sur
.../config/rate. Comme Retain As Published est activé, le script afficheretain=Truepour le message en direct. - Relancez le script sans rien changer d'autre et vérifiez que le comportement est reproductible.
Pièges courants
- Se connecter en MQTT 3.1.1. Les bibliothèques ignorent silencieusement les options d'abonnement sur une connexion 3.1.1. Définissez toujours
protocolVersion: 5ouprotocol=mqtt.MQTTv5. - No Local sur des abonnements partagés. Le broker considère cela comme une erreur de protocole et ferme la connexion.
- S'attendre à ce que Retain Handling 1 fonctionne avec des sessions propres. Si la session est nouvelle à chaque fois, chaque abonnement est nouveau : la valeur 1 se comporte alors comme la valeur 0.
- Utiliser Retain As Published dans les tableaux de bord. Le tableau de bord ne peut alors plus distinguer une valeur stockée d'une valeur fraîche. Gardez la valeur par défaut, sauf si vous retransmettez les messages.
- Ignorer le SUBACK. Un reason code comme
0x80ou0x87signifie que l'abonnement a été refusé. Journalisez les reason codes du SUBACK.
Choisir les options selon le rôle
| Rôle du client | Max QoS | No Local | Retain As Published | Retain Handling |
|---|---|---|---|---|
| Tableau de bord | 1 | désactivé | désactivé | 0 |
| Participant à un chat | 1 | activé | désactivé | 0 ou 2 |
| Service d'alerte | 1 | désactivé | désactivé | 2 |
| Appareil avec session persistante | 1 | désactivé | désactivé | 1 |
| Pont ou relais | 2 | activé | activé | 0 |
Ces options font partie de ce qui rend ConnectMQ et les autres brokers MQTT 5 plus faciles à exploiter que leurs prédécesseurs 3.1.1 : un comportement qui nécessitait autrefois un filtrage applicatif est désormais un simple flag dans le paquet SUBSCRIBE. Associez-les à une bonne conception des topics grâce aux wildcards MQTT, et gardez le tutoriel MQTT à portée de main si vous apprenez encore les bases.
Démarrer avec ConnectMQ
Testez les options d'abonnement sur le serveur MQTT public gratuit, puis passez à un broker MQTT managé privé avec MQTT 5.0, TLS et des identifiants générés. Créez un broker Serverless gratuit sur app.connectmq.com, sans carte bancaire.
Questions fréquentes
Les options d'abonnement MQTT sont-elles disponibles en MQTT 3.1.1 ?
Non. MQTT 3.1.1 ne transporte que la QoS demandée. No Local, Retain As Published et Retain Handling nécessitent une connexion MQTT 5.0.
Quelle est la valeur par défaut de chaque option d'abonnement ?
No Local et Retain As Published sont désactivés par défaut, et Retain Handling vaut 0 par défaut, ce qui envoie les retained messages à chaque abonnement. Cela correspond au comportement de MQTT 3.1.1.
Pourquoi est-ce que je reçois encore mes propres messages avec No Local activé ?
Vérifiez que vous êtes connecté en MQTT 5 et que l'éditeur et l'abonné sont la même connexion. Un second client, même dans le même processus, n'est pas local.
Puis-je modifier les options d'abonnement sans me désabonner ?
Oui. Envoyer un nouveau SUBSCRIBE pour le même filtre de topic remplace les options de l'abonnement existant. Avec Retain Handling 1, les retained messages ne sont pas renvoyés, car l'abonnement existe déjà.



