Une bonne organisation des topics MQTT pour la maison connectée est ennuyeuse et prévisible : une branche par pièce, une par appareil et un jeu fixe de topics terminaux pour l'état, les commandes et la disponibilité. Cet article propose une organisation concrète à reprendre telle quelle, explique pourquoi commandes et état vivent sur des topics séparés, quels messages retenir et comment cette organisation se traduit dans le discovery de Home Assistant et les ACL du broker.
home/<room>/<device>/state current state, JSON, retained
home/<room>/<device>/set commands to the device, NOT retained
home/<room>/<device>/availability "online" / "offline", retained, Last Will
home/<room>/<device>/event one-off events (button press), NOT retainedRègles de nommage
Les noms de topics font partie de l'API de votre maison. Les changer plus tard oblige à toucher chaque automatisation : fixez les règles une fois pour toutes.
- ASCII en minuscules, mots reliés par
-:living-room,ceiling-light. Pas d'espaces, pas d'accents, pas d'emoji. - Pas de slash initial.
/home/kitchencrée un premier niveau vide et ne correspond pas àhome/#. - Ne jamais commencer par
$: ce préfixe est réservé aux topics du broker comme$SYS. - Des noms pour les lieux et les objets, des mots fixes pour les feuilles :
state,set,availability,event. Ne mélangez passtatus,cmdetcommandd'un appareil à l'autre. - Gardez une profondeur constante. Chaque appareil se trouve exactement en
home/<room>/<device>, donchome/+/+/statesignifie toujours « l'état de tous les appareils ».
Des conseils plus généraux sont disponibles dans bonnes pratiques de conception des topics MQTT.
Par pièce ou par identifiant ?
| Style | Exemple | Adapté à | Contrepartie |
|---|---|---|---|
| Par pièce | home/kitchen/ceiling-light/state | Maisons typiques, automatisations écrites à la main | Déplacer un appareil change ses topics |
| Par identifiant | home/device/shelly-a8032a/state | Installations grandes ou changeantes | La pièce doit être stockée ailleurs (métadonnées, zones HA) |
| Hybride | home/kitchen/shelly-a8032a/state | Noms uniques avec regroupement lisible | Change quand même lors d'un déplacement |
Dans la plupart des maisons, l'organisation par pièce l'emporte pour la lisibilité. Si certains appareils se déplacent (un radiateur d'appoint, un capteur sur un chariot), donnez-leur un identifiant stable et placez-les sous une branche neutre comme home/mobile/.
Commandes et état
La règle la plus importante : l'appareil publie l'état, tous les autres publient des commandes, et ils ne partagent jamais un topic.
app ──PUBLISH home/kitchen/ceiling-light/set {"state":"ON"} (not retained)
light ──applies the command, then
light ──PUBLISH home/kitchen/ceiling-light/state {"state":"ON","brightness":255} (retained)
app, HA ◀─ receive the new state and update the UIPourquoi les séparer ?
- Le topic d'état reflète toujours la réalité. Si la commande échoue (relais bloqué, appareil hors ligne), le topic d'état continue d'afficher la vérité et l'interface ne ment pas.
- Pas de boucles d'écho. Un appareil abonné à son propre topic d'état recevrait ses propres mises à jour. Avec
setetstateséparés, chaque partie ne s'abonne qu'à ce dont elle a besoin. - Des ACL sûres. Un utilisateur de dashboard peut être autorisé à écrire sur
.../setet à lire.../statesans pouvoir falsifier l'état de l'appareil.
L'appareil doit publier le nouvel état après avoir appliqué la commande, même si rien n'a changé. C'est cette confirmation qu'attendent les interfaces et les automatisations. Pour les schémas requête/réponse avec identifiants de corrélation, voir commande et contrôle via MQTT.
Que retenir ?
| Topic | Retenir ? | Pourquoi |
|---|---|---|
.../state | Oui | Un dashboard ouvert plus tard affiche immédiatement l'état actuel |
.../availability | Oui | Les nouveaux abonnés savent quels appareils sont hors ligne |
.../set | Non | Une commande retenue est rejouée à chaque reconnexion : allumages fantômes après une coupure |
.../event | Non | « Bouton appuyé » n'a de sens qu'au moment où cela se produit |
homeassistant/.../config | Oui | Les configurations de discovery doivent survivre aux redémarrages de Home Assistant |
Si une commande a été retenue par erreur, effacez-la en publiant un message retenu vide sur le même topic. Détails dans messages retenus.
Disponibilité avec Last Will
Chaque appareil définit un Last Will sur son topic availability à la connexion : payload offline, retenu, QoS 1. Juste après la connexion, il publie online, retenu. Si l'appareil perd l'alimentation ou le Wi-Fi, le broker publie offline à l'expiration du keep alive, soit environ 1,5 × l'intervalle de keep alive. Voir MQTT Last Will.
Un appareil en 40 lignes
Ce script Python (paho-mqtt 2.x) se comporte comme une lampe à variateur qui respecte l'organisation. Les exemples utilisent le préfixe testmqtt/ pour pouvoir tourner sans risque sur un endpoint de test partagé ; chez vous, supprimez-le.
import json, os
import paho.mqtt.client as mqtt
BASE = "testmqtt/home/kitchen/ceiling-light"
state = {"state": "OFF", "brightness": 0}
def publish_state(client):
client.publish(f"{BASE}/state", json.dumps(state), qos=1, retain=True)
def on_connect(client, userdata, flags, reason_code, properties):
client.publish(f"{BASE}/availability", "online", qos=1, retain=True)
client.subscribe(f"{BASE}/set", qos=1)
publish_state(client)
def on_message(client, userdata, msg):
try:
cmd = json.loads(msg.payload)
except ValueError:
cmd = {"state": msg.payload.decode(errors="replace").strip().upper()}
if not isinstance(cmd, dict):
cmd = {}
if cmd.get("state") in ("ON", "OFF"):
state["state"] = cmd["state"]
if isinstance(cmd.get("brightness"), int):
state["brightness"] = max(0, min(255, cmd["brightness"]))
publish_state(client) # always confirm with the real state
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="kitchen-ceiling-light")
if os.getenv("MQTT_USER"):
client.username_pw_set(os.environ["MQTT_USER"], os.getenv("MQTT_PASS"))
client.will_set(f"{BASE}/availability", "offline", qos=1, retain=True)
client.on_connect = on_connect
client.on_message = on_message
client.connect("broker.connectmq.com", 1883, keepalive=30)
client.loop_forever()Publier l'état dans on_connect est important : après chaque reconnexion, l'état retenu est rafraîchi et les abonnements sont recréés même si le broker n'a conservé aucune session.
Tester l'organisation
Observez l'état et la disponibilité de tous les appareils dans un terminal :
mosquitto_sub -h broker.connectmq.com -p 1883 -v \
-t 'testmqtt/home/+/+/state' -t 'testmqtt/home/+/+/availability'Envoyez des commandes depuis un autre :
mosquitto_pub -h broker.connectmq.com -p 1883 -q 1 \
-t 'testmqtt/home/kitchen/ceiling-light/set' -m '{"state":"ON","brightness":180}'
mosquitto_pub -h broker.connectmq.com -p 1883 -q 1 \
-t 'testmqtt/home/kitchen/ceiling-light/set' -m 'off'Vérifiez ensuite les comportements que promet l'organisation :
- Chaque commande produit exactement un message d'état.
- Relancez le terminal 1 : l'état actuel et
onlineapparaissent immédiatement (retenus). - Tuez le script avec
kill -9: après le délai du keep alive,offlineapparaît. - Relancez le script : aucune commande n'est rejouée, car
setn'a jamais été retenu.
Vous pouvez faire la même chose dans le navigateur avec le client MQTT en ligne, et vérifier n'importe quel motif d'abonnement contre vos topics avec le topic matcher.
Abonnements utiles
| Besoin | Abonnement |
|---|---|
| Toute la maison | home/# |
| Toute la cuisine | home/kitchen/# |
| L'état de chaque appareil | home/+/+/state |
| Les appareils hors ligne | home/+/+/availability |
| Tous les appuis sur des boutons | home/+/+/event |
Comme la profondeur est constante, les jokers à un niveau + suffisent et vous avez rarement besoin de # en dehors du débogage. Voir les jokers MQTT en détail.
Discovery de Home Assistant
Home Assistant peut créer des entités automatiquement à partir de messages de configuration retenus sous homeassistant/<component>/<object_id>/config. L'organisation se transpose directement sur les champs de discovery :
{
"name": "Kitchen ceiling light",
"unique_id": "kitchen_ceiling_light",
"state_topic": "home/kitchen/ceiling-light/state",
"value_template": "{{ value_json.state }}",
"command_topic": "home/kitchen/ceiling-light/set",
"payload_on": "ON",
"payload_off": "OFF",
"availability_topic": "home/kitchen/ceiling-light/availability"
}payload_available et payload_not_available valent par défaut online et offline, ce que publie justement le Last Will ci-dessus. Pour la configuration complète de l'intégration, voir Home Assistant et MQTT.
Des ACL qui suivent l'organisation
Une arborescence cohérente rend les permissions concises :
| Client | Écriture | Lecture |
|---|---|---|
| Chaque appareil | ses propres state, availability, event | son propre set |
| Home Assistant / hub d'automatisation | home/+/+/set, homeassistant/# | home/# |
| Tablette murale avec dashboard | home/+/+/set | home/+/+/state, home/+/+/availability |
| Utilisateur invité ou de supervision | rien | home/+/+/availability |
Un identifiant par appareil signifie qu'une prise connectée compromise ne peut mentir que sur elle-même, et non ouvrir la serrure de votre porte d'entrée.
Adopter cette organisation ne coûte rien et rapporte à chaque nouvel appareil : profondeur constante, state et set séparés, état et disponibilité retenus, commandes jamais retenues. Écrivez les règles, testez chaque nouvel appareil avec les quatre vérifications ci-dessus, et vos automatisations continueront de fonctionner à mesure que la maison s'agrandit.
Questions fréquentes
Les topics de commande MQTT doivent-ils être retenus ?
Non. Une commande retenue est renvoyée à l'appareil à chaque reconnexion : une lampe peut alors s'allumer toute seule après une coupure de courant. Retenez l'état et la disponibilité ; envoyez les commandes sans le flag retain.
La pièce doit-elle faire partie du topic ?
Dans une maison typique, oui : home/kitchen/ceiling-light se lit bien et permet de s'abonner à toute une pièce avec home/kitchen/#. L'inconvénient : déplacer un appareil change ses topics. Les grandes installations utilisent souvent des identifiants d'appareil stables et gardent la pièce comme métadonnée.
Un topic d'état JSON ou un topic par propriété ?
Un seul message d'état JSON par appareil est plus facile à garder cohérent et correspond à ce qu'attendent les templates JSON de Home Assistant. Les topics par propriété sont plus simples pour les très petits capteurs et certains outils. Choisissez un style et appliquez-le à tous les appareils.


