La documentation et les tableaux comparatifs vous disent ce qu'un broker sait faire. Ce qui compte, c'est ce que votre broker, avec votre configuration et vos identifiants, autorise réellement. Le moyen le plus rapide et le plus fiable de le savoir est de le sonder : se connecter en MQTT 5, lire les propriétés du CONNACK, puis essayer chaque fonctionnalité et observer les reason codes renvoyés.
Cet article explique quels signaux vérifier et fournit un script Python (paho-mqtt 2.x) qui affiche un rapport de capacités pour n'importe quel broker.
Ce que le broker annonce dans le CONNACK
Un broker MQTT 5 annonce ses limites dans le paquet CONNACK. La règle essentielle : une propriété n'est envoyée que si elle diffère de la valeur par défaut. L'absence de RetainAvailable ne signifie pas « inconnu », mais « oui ».
| Propriété du CONNACK | Valeur par défaut si absente | Ce qui casse si elle est restreinte |
|---|---|---|
| Maximum QoS | 2 | Publier au-dessus provoque 0x9B QoS not supported et la fermeture de la connexion |
| Retain Available | oui | Les publications retenues reçoivent 0x9A Retain not supported |
| Wildcard Subscription Available | oui | Les abonnements + / # reçoivent 0xA2 Wildcard Subscriptions not supported |
| Shared Subscription Available | oui | Les abonnements $share/... reçoivent 0x9E Shared Subscriptions not supported |
| Subscription Identifiers Available | oui | Les abonnements avec identifiant reçoivent 0xA1 |
| Receive Maximum | 65 535 | Combien de messages QoS 1/2 peuvent être en vol vers le broker |
| Maximum Packet Size | pas de limite (maximum du protocole) | Les paquets plus gros reçoivent 0x95 Packet too large |
| Topic Alias Maximum | 0 (alias refusés) | Utiliser un alias non autorisé donne 0x94 Topic Alias invalid |
| Server Keep Alive | votre valeur | Le broker remplace votre keep-alive ; votre client doit utiliser sa valeur |
| Assigned Client Identifier | — | Envoyé lorsque vous vous connectez avec un client ID vide |
MQTT 3.1.1 ne propose rien de tout cela. Un broker 3.1.1 indique seulement si la connexion est acceptée : tout le reste doit être vérifié en essayant. Vous pouvez retrouver chacun de ces codes dans la référence des reason codes.
Le script de sondage
Le script ci-dessous effectue six vérifications : versions du protocole, propriétés du CONNACK, QoS accordée par abonnement, abonnements avec wildcard et partagés, messages retenus et une livraison QoS 2 de bout en bout. Configurez-le avec des variables d'environnement et lancez-le sur chaque environnement qui vous intéresse.
"""Report which MQTT features a broker supports. Requires paho-mqtt >= 2.0."""
import os, threading, time, uuid
import paho.mqtt.client as mqtt
HOST = os.environ.get("MQTT_HOST", "broker.connectmq.com")
PORT = int(os.environ.get("MQTT_PORT", "1883"))
USER, PASS = os.environ.get("MQTT_USER"), os.environ.get("MQTT_PASS")
BASE = f"testmqtt/probe/{uuid.uuid4().hex[:8]}"
DEFAULTS = { # value that applies when the broker omits the property
"MaximumQoS": 2, "RetainAvailable": 1, "WildcardSubscriptionAvailable": 1,
"SharedSubscriptionAvailable": 1, "SubscriptionIdentifierAvailable": 1,
"ReceiveMaximum": 65535, "MaximumPacketSize": "no limit",
"TopicAliasMaximum": 0, "ServerKeepAlive": "client value",
}
def connect(protocol=mqtt.MQTTv5, timeout=10):
c = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2,
client_id=f"probe-{uuid.uuid4().hex[:8]}", protocol=protocol)
if USER:
c.username_pw_set(USER, PASS)
if PORT == 8883:
c.tls_set()
done, res = threading.Event(), {}
def on_connect(client, userdata, flags, reason_code, properties):
res.update(rc=reason_code, props=properties)
done.set()
c.on_connect = on_connect
c.connect(HOST, PORT, keepalive=30)
c.loop_start()
if not done.wait(timeout):
c.loop_stop()
raise TimeoutError("no CONNACK")
return c, res["rc"], res["props"]
def close(*clients):
for c in clients:
c.disconnect()
c.loop_stop()
def subscribe(c, topic_filter, qos=2, timeout=5):
done, res = threading.Event(), {}
def on_subscribe(client, userdata, mid, reason_code_list, properties):
res["rc"] = reason_code_list[0]
done.set()
c.on_subscribe = on_subscribe
c.subscribe(topic_filter, qos=qos)
return res["rc"] if done.wait(timeout) else "no SUBACK"
def show(label, value):
print(f" {label:34} {value}")
def main():
print(f"Broker {HOST}:{PORT} (test topics under {BASE})\n")
print("Protocol versions")
for name, proto in (("MQTT 3.1.1", mqtt.MQTTv311), ("MQTT 5", mqtt.MQTTv5)):
try:
c, rc, _ = connect(proto)
show(name, "OK" if not rc.is_failure else f"refused: {rc}")
close(c)
except Exception as e:
show(name, f"FAILED ({e})")
c, rc, props = connect()
if rc.is_failure:
raise SystemExit(f"MQTT 5 connect refused: {rc}")
print("\nCONNACK properties")
for name, default in DEFAULTS.items():
value = getattr(props, name, None)
show(name, value if value is not None else f"{default} (default)")
print("\nSubscriptions (granted QoS or reason code)")
for q in (0, 1, 2):
show(f"QoS {q} subscription", subscribe(c, f"{BASE}/qos/{q}", qos=q))
show("Wildcard filter (+)", subscribe(c, f"{BASE}/+/wild", qos=1))
show("Shared subscription ($share)", subscribe(c, f"$share/probe/{BASE}/shared", qos=1))
print("\nActive tests")
got = {}
received = threading.Event()
def on_message(client, userdata, msg):
got[msg.topic] = msg
received.set()
c.on_message = on_message
# End-to-end QoS 2: publish at 2 to a QoS 2 subscription, check delivered QoS
received.clear()
c.publish(f"{BASE}/qos/2", b"qos2-probe", qos=2)
if received.wait(5):
show("QoS 2 delivery", f"delivered at QoS {got[f'{BASE}/qos/2'].qos}")
else:
show("QoS 2 delivery", "NOT delivered")
# Retained message: publish, then subscribe with a fresh client
topic = f"{BASE}/retain"
try:
c.publish(topic, b"retained-probe", qos=1, retain=True).wait_for_publish(5)
sub, _, _ = connect()
hit = threading.Event()
sub.on_message = lambda client, u, m: m.retain and hit.set()
subscribe(sub, topic, qos=1)
show("Retained message", "OK" if hit.wait(5) else "NOT delivered")
close(sub)
c.publish(topic, b"", qos=1, retain=True).wait_for_publish(5) # clear it
except Exception as e:
show("Retained message", f"FAILED ({e})")
close(c)
if __name__ == "__main__":
main()Lancez-le avec vos paramètres de connexion :
pip install "paho-mqtt>=2.0"
MQTT_HOST=broker.connectmq.com MQTT_PORT=8883 \
MQTT_USER="$MQTT_USER" MQTT_PASS="$MQTT_PASS" python probe.pyLes QoS accordées s'affichent sous forme d'objets ReasonCode de paho : Granted QoS 2 signifie que le broker a accepté l'abonnement à ce niveau, tandis qu'un code 0x80 ou supérieur (par exemple Not authorized ou Shared Subscriptions not supported) est un refus. Si l'abonnement partagé est accepté et que vous voulez savoir si les messages sont réellement répartis, utilisez le test des abonnements partagés.
Lire le rapport
Constats fréquents, et que faire :
- MQTT 5 refusé, 3.1.1 OK — ancienne version du broker ou listener limité à 3.1.1. Votre client MQTT 5 doit se replier, ou le broker doit être mis à jour. Voir MQTT 5 vs 3.1.1.
- MaximumQoS 1 — courant sur les services IoT cloud et sur les brokers configurés pour le débit. Une publication QoS 2 déconnectera votre client avec
0x9B; concevez pour la QoS 1 avec des handlers idempotents. - Abonnement accordé avec une QoS inférieure à celle demandée — c'est autorisé. Le broker rétrograde au lieu de refuser, et les messages arrivent au niveau accordé. Vérifiez la QoS accordée dans votre client, pas seulement celle demandée.
- RetainAvailable 0 ou message retenu non livré — les appareils qui reposent sur un état retenu (dernière valeur à l'abonnement) sembleront cassés. Détails dans retain non pris en charge.
- TopicAliasMaximum 0 — le broker n'accepte pas les topic aliases des clients ; les bibliothèques qui attribuent des alias automatiquement doivent désactiver cette option.
- MaximumPacketSize défini — comparez-le à votre plus gros payload, topic et propriétés compris, avec le calculateur de taille de paquet.
- Not authorized sur un abonnement — ce n'est pas une fonctionnalité manquante mais une ACL. Lancez la sonde avec l'utilisateur de vos appareils et consultez tester les permissions ACL MQTT.
La même vérification en JavaScript
Avec MQTT.js 5, les propriétés du CONNACK se trouvent dans le paquet transmis à l'événement connect :
import mqtt from 'mqtt';
const client = mqtt.connect('mqtts://broker.connectmq.com:8883', {
protocolVersion: 5,
username: process.env.MQTT_USER,
password: process.env.MQTT_PASS,
});
client.on('connect', (connack) => {
// Absent keys mean the spec default applies
console.log(connack.properties ?? '(no properties: all defaults)');
client.subscribe('$share/probe/testmqtt/probe/js', { qos: 2 }, (err, granted) => {
console.log(err ?? granted); // granted[0].qos is the granted QoS or a reason code
client.end();
});
});
client.on('error', (err) => console.error('connect failed:', err.message));Les noms des propriétés sont ici en camelCase : maximumQoS, retainAvailable, maximumPacketSize, topicAliasMaximum, wildcardSubscriptionAvailable, sharedSubscriptionAvailable, subscriptionIdentifiersAvailable, receiveMaximum et serverKeepAlive.
Pas de code ? Utilisez mqtt test
La HiveMQ MQTT CLI intègre cette sonde : mqtt test -h broker.connectmq.com affiche les capacités MQTT 3 et MQTT 5, y compris la longueur maximale de topic et la taille de payload mesurées par essai. Le guide mqtt-cli explique la sortie. Pour une vérification manuelle rapide, connectez-vous avec le client MQTT en ligne en MQTT 5 et essayez une publication retenue et un abonnement $share/.
Intégrez-le à votre pipeline
Les capacités changent sans prévenir : une mise à jour du broker, un nouveau listener, une autre offre sur un service managé. Enregistrez la sortie de la sonde dans votre dépôt et comparez-la en CI :
python probe.py | sed -E 's#testmqtt/probe/[0-9a-f]+#testmqtt/probe/X#' > capabilities.txt
git diff --exit-code capabilities.txtLe sed supprime le suffixe aléatoire du topic pour que seuls les vrais changements apparaissent dans le diff. Associez cela aux vérifications de comment tester un broker MQTT et vous saurez qu'une fonctionnalité manque avant qu'un appareil sur le terrain ne le découvre.
Suivre les topics de la sonde dans le navigateur
Une sonde de cinq minutes vaut mieux qu'un après-midi passé à déboguer des déconnexions « aléatoires ». Lisez le CONNACK, testez chaque fonctionnalité dont vous dépendez et versionnez le rapport, pour que les différences entre brokers et environnements ne soient jamais une surprise.
Questions fréquentes
Comment savoir si un broker prend en charge MQTT 5 ?
Connectez-vous avec la version de protocole 5. Un broker compatible répond par un CONNACK MQTT 5, généralement avec des propriétés. Sinon, il renvoie 'unacceptable protocol version' (0x01 en 3.1.1, 0x84 en MQTT 5) ou ferme la connexion.
Si une propriété est absente du CONNACK, la fonctionnalité est-elle non prise en charge ?
Non, c'est l'inverse. En MQTT 5, une propriété absente signifie que la valeur par défaut s'applique : retain, wildcards, abonnements partagés et identifiants d'abonnement sont disponibles, la QoS maximale est 2 et le receive maximum vaut 65 535. Les brokers n'envoient que les propriétés qui restreignent ou modifient ces valeurs.
Un broker peut-il prendre en charge une fonctionnalité mais la refuser à mon client ?
Oui. Les ACLs, les réglages par listener et les offres des services managés peuvent restreindre la QoS, le retain, les wildcards ou certains topics pour des utilisateurs donnés. C'est pourquoi la sonde doit s'exécuter avec les mêmes identifiants et le même port que vos appareils.


