Automatisation IoT

MQTT Discovery dans Home Assistant : créer automatiquement les entités de vos appareils

Publiez des payloads de configuration de découverte pour que vos capteurs et interrupteurs apparaissent dans Home Assistant comme un appareil complet, avec disponibilité, identifiants uniques et suppression propre, grâce à un simulateur d'appareil en Python.

Appareil capteur personnalisé apparaissant automatiquement dans un tableau de bord domotique grâce à la découverte MQTT de Home Assistant
Essayez-le sur votre propre broker MQTTObtenez un broker MQTT privé et managé avec l’offre Free et partez d’exemples de code prêts à l’emploi.
Essayer gratuitement Exemples de code Aucune carte bancaire requise

La découverte MQTT de Home Assistant (MQTT discovery) permet à un appareil de votre maison connectée de se décrire lui-même afin que Home Assistant crée ses entités automatiquement, sans aucun YAML côté Home Assistant. Dans ce tutoriel, vous allez découvrir le format des topics de découverte, écrire des payloads de configuration pour des capteurs et un interrupteur, les regrouper dans un seul appareil, gérer la disponibilité et les redémarrages de Home Assistant, puis supprimer proprement les entités, le tout avec un simulateur d'appareil en Python connecté à un broker ConnectMQ.

Comment fonctionne la découverte MQTT

Au démarrage, l'intégration MQTT s'abonne aux topics situés sous un préfixe de découverte, homeassistant par défaut. Un appareil publie un message JSON retenu par entité sur un topic de configuration :

<discovery_prefix>/<component>/[<node_id>/]<object_id>/config

homeassistant/sensor/hvac-node-1/temperature/config
homeassistant/switch/hvac-node-1/relay/config
  • component est la plateforme de l'entité : sensor, binary_sensor, switch, light, button, number, select, climate et d'autres.
  • node_id est facultatif et sert uniquement à regrouper les topics ; il ne crée pas l'appareil.
  • object_id identifie l'entité au sein du nœud.

Le payload de configuration indique à Home Assistant où lire l'état, où envoyer les commandes, comment interpréter les valeurs et à quel appareil appartient l'entité. Comme le message est retenu, le broker le renvoie à chaque reconnexion de Home Assistant, de sorte que les entités réapparaissent après un redémarrage.

La découverte ne transporte que des métadonnées. Les vraies données circulent toujours sur vos propres topics d'état et de commande, que vous concevez comme pour toute intégration MQTT dans Home Assistant.

Prérequis

  • Home Assistant avec l'intégration MQTT configurée et la découverte activée (elle l'est par défaut).
  • Un broker ConnectMQ privé : créez-en un dans le portail et notez le nom d'utilisateur, le mot de passe et le client ID générés. Home Assistant et le simulateur ont chacun besoin de leurs propres identifiants.
  • Python 3.10+ avec pip install "paho-mqtt>=2.0".

Vous pouvez aussi faire des essais sur le serveur MQTT public gratuit, réservé aux tests et aux prototypes, mais deux points le rendent peu pratique pour la découverte. D'abord, les topics de configuration retenus sous le préfixe partagé homeassistant sont visibles par toutes les instances Home Assistant qui y sont connectées : définissez donc un préfixe de découverte personnalisé dans les options de l'intégration (par exemple cmqx7d2). Ensuite, le serveur MQTT public gratuit autorise 10 abonnements par client, alors que Home Assistant en a besoin de plusieurs pour la découverte, plus un par entité. Un broker privé évite ces deux problèmes.

Un payload de découverte, champ par champ

Voici la configuration d'un capteur de température :

{
  "name": "Supply Air Temperature",
  "unique_id": "hvacnode1_supply_temp",
  "object_id": "hvac_node_1_supply_temp",
  "state_topic": "home/hvac-node-1/state",
  "value_template": "{{ value_json.supply_c }}",
  "unit_of_measurement": "°C",
  "device_class": "temperature",
  "state_class": "measurement",
  "availability_topic": "home/hvac-node-1/availability",
  "device": {
    "identifiers": ["hvacnode1"],
    "name": "HVAC Node 1",
    "manufacturer": "DIY",
    "model": "ESP32 duct sensor",
    "sw_version": "1.3.0"
  }
}
Champ Pourquoi il est important
unique_id Indispensable pour le registre des entités : renommage, pièces, désactivation. Doit rester stable pour toujours.
device.identifiers Regroupe toutes les entités partageant le même identifiant dans une seule fiche d'appareil.
state_topic + value_template Plusieurs entités peuvent lire différents champs d'un même message d'état JSON.
availability_topic Les entités deviennent indisponibles lorsque l'appareil publie offline (généralement via le last will).
device_class, state_class Icônes, unités et statistiques à long terme correctes.
object_id Influence l'identifiant d'entité généré.

Les payloads longs peuvent utiliser les abréviations documentées, par exemple stat_t pour state_topic, uniq_id pour unique_id et dev pour device. Elles sont utiles sur les microcontrôleurs aux tampons réduits ; sur un PC, les noms complets sont plus lisibles.

Un appareil Python complet avec découverte

Le simulateur ci-dessous se comporte comme un véritable appareil : il annonce trois entités, publie un état JSON toutes les dix secondes, obéit aux commandes de l'interrupteur, définit un last will pour la disponibilité et republie sa configuration lorsque Home Assistant revient en ligne.

import json
import random
import time

import paho.mqtt.client as mqtt

BROKER, PORT = "broker.connectmq.com", 8883
USERNAME, PASSWORD = "YOUR_USERNAME", "YOUR_PASSWORD"
CLIENT_ID = "YOUR_CLIENT_ID"

DISCOVERY_PREFIX = "homeassistant"
NODE = "hvac-node-1"
BASE = f"home/{NODE}"
STATE_T = f"{BASE}/state"
AVAIL_T = f"{BASE}/availability"
RELAY_CMD_T = f"{BASE}/relay/set"
RELAY_STATE_T = f"{BASE}/relay/state"

DEVICE = {
    "identifiers": ["hvacnode1"],
    "name": "HVAC Node 1",
    "manufacturer": "DIY",
    "model": "ESP32 duct sensor",
    "sw_version": "1.3.0",
}

ENTITIES = [
    ("sensor", "supply_temp", {
        "name": "Supply Air Temperature",
        "state_topic": STATE_T,
        "value_template": "{{ value_json.supply_c }}",
        "unit_of_measurement": "°C",
        "device_class": "temperature",
        "state_class": "measurement",
    }),
    ("sensor", "filter_dp", {
        "name": "Filter Pressure Drop",
        "state_topic": STATE_T,
        "value_template": "{{ value_json.filter_dp_pa }}",
        "unit_of_measurement": "Pa",
        "device_class": "pressure",
        "state_class": "measurement",
    }),
    ("switch", "relay", {
        "name": "Fan Relay",
        "command_topic": RELAY_CMD_T,
        "state_topic": RELAY_STATE_T,
        "payload_on": "ON",
        "payload_off": "OFF",
    }),
]

relay_on = False


def publish_discovery(client):
    for component, object_id, cfg in ENTITIES:
        payload = {
            **cfg,
            "unique_id": f"hvacnode1_{object_id}",
            "availability_topic": AVAIL_T,
            "device": DEVICE,
        }
        topic = f"{DISCOVERY_PREFIX}/{component}/{NODE}/{object_id}/config"
        client.publish(topic, json.dumps(payload), qos=1, retain=True)
    client.publish(AVAIL_T, "online", qos=1, retain=True)
    client.publish(RELAY_STATE_T, "ON" if relay_on else "OFF", qos=1, retain=True)


def on_connect(client, userdata, flags, reason_code, properties):
    print("connected:", reason_code)
    client.subscribe(RELAY_CMD_T, qos=1)
    client.subscribe("homeassistant/status", qos=1)   # message de naissance de Home Assistant
    publish_discovery(client)


def on_message(client, userdata, msg):
    global relay_on
    if msg.topic == "homeassistant/status" and msg.payload == b"online":
        time.sleep(random.uniform(0.5, 2.0))          # étaler la charge après un redémarrage de HA
        publish_discovery(client)
    elif msg.topic == RELAY_CMD_T:
        relay_on = msg.payload == b"ON"
        client.publish(RELAY_STATE_T, "ON" if relay_on else "OFF", qos=1, retain=True)


client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=CLIENT_ID)
client.username_pw_set(USERNAME, PASSWORD)
client.tls_set()
client.will_set(AVAIL_T, "offline", qos=1, retain=True)
client.on_connect = on_connect
client.on_message = on_message
client.connect(BROKER, PORT, keepalive=30)
client.loop_start()

while True:
    state = {"supply_c": round(random.uniform(12.5, 14.5), 1),
             "filter_dp_pa": random.randint(150, 190)}
    client.publish(STATE_T, json.dumps(state), qos=0)
    time.sleep(10)

Lancez-le, puis ouvrez Paramètres > Appareils et services > MQTT. Un appareil nommé HVAC Node 1 apparaît avec deux capteurs et un interrupteur. Arrêtez le script avec Ctrl+C : dans l'intervalle du keep-alive, le broker publie le last will et les entités deviennent indisponibles.

La gestion du message de naissance (birth message) est importante. Home Assistant publie online sur homeassistant/status à son démarrage. Les configurations retenues rendent généralement la republication inutile, mais un appareil qui republie à la naissance se rétablit aussi si les messages retenus ont été effacés.

Découverte par appareil

Les versions récentes de Home Assistant acceptent également un message de configuration unique par appareil sur <prefix>/device/<object_id>/config, qui liste toutes les entités sous une clé components (abrégée cmps), chacune avec une platform (p). Ce message nécessite aussi un bloc origin (o) qui nomme le logiciel qui le publie :

{
  "dev": {"ids": ["hvacnode1"], "name": "HVAC Node 1", "mf": "DIY", "mdl": "ESP32 duct sensor"},
  "o": {"name": "hvac-node-firmware", "sw": "1.3.0"},
  "avty_t": "home/hvac-node-1/availability",
  "stat_t": "home/hvac-node-1/state",
  "cmps": {
    "supply_temp": {"p": "sensor", "dev_cla": "temperature", "unit_of_meas": "°C",
                    "val_tpl": "{{ value_json.supply_c }}", "uniq_id": "hvacnode1_supply_temp"},
    "filter_dp":   {"p": "sensor", "dev_cla": "pressure", "unit_of_meas": "Pa",
                    "val_tpl": "{{ value_json.filter_dp_pa }}", "uniq_id": "hvacnode1_filter_dp"}
  }
}

Un seul message retenu au lieu de plusieurs est intéressant pour les appareils qui exposent des dizaines d'entités. Utilisez un seul style par appareil ; n'annoncez pas les mêmes identifiants uniques par les deux méthodes.

Supprimer et modifier des entités

Pour supprimer une entité, publiez un payload vide et retenu sur son topic de configuration :

mosquitto_pub -h broker.connectmq.com -p 8883 --capath /etc/ssl/certs \
  -u YOUR_USERNAME -P YOUR_PASSWORD -i YOUR_CLIENT_ID -r -n \
  -t "homeassistant/sensor/hvac-node-1/filter_dp/config"

-n envoie un message de longueur nulle et -r le rend retenu, ce qui efface aussi la configuration stockée sur le broker. Pour modifier une entité, publiez la nouvelle configuration sur le même topic avec le même unique_id. Changer le unique_id crée une nouvelle entité et laisse l'ancienne orpheline.

Liste de contrôle de dépannage

  1. Rien n'apparaît. Vérifiez que le préfixe de découverte défini dans les options de l'intégration correspond à celui utilisé par votre appareil, et que la découverte est activée.
  2. Les entités apparaissent mais sont indisponibles. Le topic de disponibilité ne contient aucun message online retenu, ou le payload n'est pas exactement online.
  3. « Unique ID already exists » dans le journal. Deux configurations partagent le même unique_id. Rendez-les uniques à l'échelle globale, idéalement à partir d'une adresse MAC.
  4. Appareils en double après une mise à jour du firmware. La valeur de device.identifiers a changé. Gardez-la stable.
  5. Entités fantômes. D'anciennes configurations retenues restent sur le broker. Utilisez l'explorateur de topics de ConnectMQ pour les retrouver sous le préfixe de découverte et effacez-les avec des messages retenus vides.
  6. Configuration rejetée sans message. Validez votre JSON ; une virgule finale dans un payload écrit à la main est le coupable habituel, et Home Assistant journalise l'erreur sous l'intégration MQTT.

La découverte depuis un ESP32

Sur un microcontrôleur, la logique est identique : construisez chaque payload de configuration, publiez-le en mode retenu après chaque connexion réussie et abonnez-vous au topic de naissance. Trois détails diffèrent de Python. D'abord, le tampon de paquets par défaut de PubSubClient est petit : appelez mqtt.setBufferSize(1024) ou plus avant de vous connecter, sinon les longs payloads de découverte sont ignorés sans avertissement. Ensuite, dérivez unique_id et device.identifiers de l'adresse MAC de la puce pour que chaque carte obtienne des valeurs stables et uniques sans configuration manuelle. Enfin, publiez la disponibilité via le last will dans connect() afin que Home Assistant soit informé d'une coupure de courant sans que l'appareil ait quoi que ce soit à faire.

Les clés abrégées aident sur les petits appareils, et construire les payloads avec ArduinoJson évite les erreurs de guillemets dans les chaînes écrites à la main. Testez les payloads de votre firmware en vous abonnant à homeassistant/# avec n'importe quel client MQTT et en vérifiant que chaque configuration est un JSON valide avant de chercher les entités dans Home Assistant.

La place de la découverte

Des firmwares comme Tasmota, ESPHome en mode MQTT et Zigbee2MQTT publient déjà des payloads de découverte, c'est pourquoi leurs appareils apparaissent instantanément. Jeedom et Domoticz savent aussi communiquer en MQTT. Pour votre propre firmware, l'exemple Python ci-dessus se transpose directement en C++ avec PubSubClient ou AsyncMqttClient ; pensez simplement à augmenter la taille du tampon du client, car les payloads de configuration dépassent facilement la valeur par défaut. Lorsque des appareils se trouvent hors de votre réseau domestique, placez-les tous sur un même broker MQTT cloud, comme décrit dans utiliser un broker MQTT externe avec Home Assistant. Le guide ESPHome MQTT et notre guide d'automatisation IoT présentent d'autres appareils qui reposent sur la découverte.

Créez des appareils détectables avec ConnectMQ

Créez gratuitement un broker Serverless sur app.connectmq.com, sans carte bancaire, et générez des identifiants pour Home Assistant et pour chaque appareil. Avec le broker MQTT managé de ConnectMQ, les configurations de découverte retenues résident sur un broker que vous n'avez jamais à mettre à jour, et l'explorateur de topics en direct montre exactement ce que chaque appareil annonce.

Questions fréquentes

Les messages de découverte doivent-ils être retenus ?

C'est fortement recommandé. Les configurations retenues permettent à Home Assistant de reconstruire les entités après un redémarrage sans que l'appareil ait à republier ; republier à la réception du message de naissance constitue une sécurité supplémentaire utile.

Quelle est la différence entre node_id et l'appareil ?

node_id n'est qu'un niveau de topic destiné à l'organisation. La fiche d'appareil est créée à partir du bloc device, et les entités qui partagent les mêmes identifiers sont regroupées.

Comment supprimer une entité découverte ?

Publiez un message retenu de longueur nulle sur son topic de configuration. Home Assistant supprime l'entité et le broker oublie la configuration retenue.

Puis-je changer le préfixe de découverte ?

Oui, dans les options de l'intégration MQTT. Tous les appareils doivent alors publier leurs configurations sous le nouveau préfixe.

Articles liés

Guide d'installation de Zigbee2MQTT : du coordinateur au tableau de bord

Installez Zigbee2MQTT avec Docker, configurez le coordinateur et la connexion MQTT, appairez et renommez les appareils, pilotez-les via MQTT et affichez leurs états sur un tableau de bord, en testant d'abord sur le serveur MQTT public gratuit.

8 min de lecture

Tous les articles →

Votre broker MQTT privé, à quelques clics

Créez un compte, récupérez les identifiants de votre broker et connectez votre premier appareil — aucun serveur à installer, mettre à jour ou dimensionner.