Jumeau numérique

Synchronisation d'état du jumeau numérique : état rapporté, état désiré et gestion des conflits

Comment garder un jumeau numérique et son actif physique synchronisés : état rapporté et état désiré, numéros de version, gestion des conflits, reprise après reconnexion et topics MQTT retained, avec un appareil Python fonctionnel.

Schéma de synchronisation d'état d'un jumeau numérique avec des documents d'état désiré et rapporté circulant entre une application, un broker MQTT et un appareil
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 synchronisation d'état du jumeau numérique consiste à maintenir l'accord entre ce que croit le jumeau et ce qu'est réellement l'actif, malgré les déconnexions, les délais et les commandes contradictoires. Cet article explique le modèle état rapporté / état désiré, le versionnage et la gestion des conflits, ainsi que sa mise en œuvre avec des topics MQTT retained, et se termine par un appareil Python fonctionnel qui converge vers l'état désiré.

Pour le contexte général, commencez par le guide de la technologie du jumeau numérique.

Pourquoi la synchronisation est difficile

Un jumeau n'est utile que s'il est juste. En pratique, plusieurs facteurs s'y opposent :

  • La latence : le jumeau voit une valeur quelques millisecondes ou secondes après son changement.
  • Les déconnexions : un appareil passe hors ligne et revient dans un état différent.
  • Les écrivains concurrents : un opérateur et un automatisme modifient tous deux une consigne.
  • L'ordre : des messages empruntant des chemins différents arrivent dans le désordre.
  • Les défaillances partielles : une commande est livrée mais l'acquittement est perdu.

Une bonne conception de la synchronisation n'élimine pas ces problèmes ; elle les rend visibles et récupérables.

Le modèle état rapporté et état désiré

Le pattern le plus répandu, popularisé par les services de device shadow et des frameworks de jumeau comme Eclipse Ditto, divise l'état en deux documents :

Document Écrit par Signification
Rapporté (reported) L'appareil uniquement Ce qu'est réellement l'actif à cet instant
Désiré (desired) Applications, opérateurs, automatismes Ce que l'actif doit devenir
Delta (dérivé) Calculé Les champs où l'état désiré diffère de l'état rapporté

La règle d'or : seul l'appareil écrit l'état rapporté. Une application qui veut une pompe à 1 450 tr/min écrit desired.speed = 1450. L'appareil voit le delta, agit, puis rapporte reported.speed = 1450. L'interface du jumeau affiche les deux, pour que les opérateurs distinguent « demandé » et « atteint ».

Cette séparation élimine toute une catégorie de bugs où l'interface affiche une valeur que la machine n'a jamais réellement atteinte. Comparez la façon dont les shadows légers et les jumeaux complets l'appliquent dans device shadow vs jumeau numérique.

Mettre en œuvre la synchronisation d'état du jumeau numérique avec MQTT

MQTT se prête parfaitement à ce modèle grâce aux topics retained :

connectmq/demo/<id>/plant1/pump7/state/reported   retained, written by device
connectmq/demo/<id>/plant1/pump7/state/desired    retained, written by apps
connectmq/demo/<id>/plant1/pump7/status           retained, last will "offline"

Comme les deux topics d'état sont retained, le broker livre la dernière version à quiconque s'abonne :

  • Un visualiseur qui ouvre le jumeau reçoit immédiatement l'état rapporté et l'état désiré.
  • Un appareil qui se reconnecte après une coupure reçoit immédiatement le dernier état désiré et peut converger, même si le changement a été demandé pendant qu'il était hors ligne.

Cette seconde propriété est la clé d'une synchronisation robuste. Vous n'avez pas besoin d'un stockage séparé de « commandes en attente » pour les changements de type consigne : le document désiré retained est la commande en attente. Consultez jumeau numérique et MQTT retained pour un tutoriel complet sur l'état retained du jumeau.

État désiré ou commandes

Utilisez l'état désiré pour les cibles idempotentes : consignes, modes, configuration, marche/arrêt. Les appliquer deux fois produit le même effet qu'une seule. Utilisez des commandes non retained pour les actions : remettre un compteur à zéro, ouvrir une porte pendant cinq secondes, lancer un étalonnage. Mettre en retained une commande d'action la rejouerait après chaque reconnexion, ce qui est dangereux.

Versionnage et gestion des conflits

Chaque document d'état doit porter une version et un horodatage :

{
  "version": 42,
  "ts": 1792152000000,
  "source": "operator:maria",
  "speed_rpm": 1450,
  "mode": "AUTO"
}

Servez-vous-en pour gérer les conflits :

  1. Des versions monotones sur l'état désiré. Les écrivains lisent le document désiré actuel, incrémentent la version et publient. Les consommateurs ignorent tout message désiré dont la version est inférieure à celle déjà appliquée.
  2. L'état rapporté renvoie la version désirée appliquée. L'appareil inclut desired_version: 42 dans son document rapporté, pour que le jumeau sache quelle demande a été satisfaite.
  3. Le dernier écrivain l'emporte, explicitement. Pour la plupart des consignes, la version la plus récente gagne. Enregistrez source pour que l'interface puisse indiquer qui a fait la modification.
  4. Des fusions champ par champ si nécessaire. Si une application gère speed et une autre mode, fusionnez au niveau des champs au lieu de remplacer tout le document, ou séparez-les dans des topics retained distincts.
  5. Des garde-fous côté appareil. L'appareil rejette les valeurs hors des limites de sécurité et signale le rejet avec un motif, plutôt que de les ignorer silencieusement.

Les brokers n'appliquent pas les versions à votre place : cette logique se trouve dans vos éditeurs et dans l'appareil. Les user properties de MQTT 5 sont un endroit pratique pour transporter des métadonnées de version sans toucher au payload.

Gérer les reconnexions et l'état périmé

Lorsqu'un appareil se reconnecte, suivez une séquence fixe :

  1. Se connecter avec un Last Will sur le topic de statut.
  2. Publier online sur le topic de statut, en retained.
  3. Publier l'état rapporté complet et actuel, en retained, à partir des capteurs locaux, et non de la mémoire de ce qui a été envoyé en dernier.
  4. S'abonner au topic désiré et recevoir le document désiré retained.
  5. Calculer le delta, l'appliquer et publier l'état rapporté mis à jour.

L'étape 3 avant l'étape 4 est importante : le jumeau apprend d'abord la vérité, puis l'appareil converge vers la cible. Si vous les inversez, un visualiseur pourrait brièvement voir un état rapporté périmé présenté comme actuel.

Côté jumeau, considérez l'état rapporté comme périmé lorsque le topic de statut indique offline. Grisez-le plutôt que d'afficher d'anciennes valeurs comme si elles étaient en temps réel.

Un appareil fonctionnel qui converge vers l'état désiré

Cet appareil Python utilise le serveur MQTT public gratuit, réservé aux tests, et paho-mqtt 2.x. Remplacez 9c4e par votre propre préfixe aléatoire.

import json
import time
import paho.mqtt.client as mqtt

BASE = "connectmq/demo/9c4e/plant1/pump7"
LIMITS = {"speed_rpm": (0, 3000)}

reported = {"version": 0, "desired_version": 0, "speed_rpm": 0, "mode": "MANUAL"}

def publish_reported(client):
    reported["version"] += 1
    reported["ts"] = int(time.time() * 1000)
    client.publish(f"{BASE}/state/reported", json.dumps(reported), qos=1, retain=True)

def on_connect(client, userdata, flags, reason_code, properties):
    client.publish(f"{BASE}/status", "online", qos=1, retain=True)
    publish_reported(client)
    client.subscribe(f"{BASE}/state/desired", qos=1)

def on_message(client, userdata, msg):
    if not msg.payload:
        return
    desired = json.loads(msg.payload)
    if desired.get("version", 0) <= reported["desired_version"]:
        return
    errors = []
    for key, value in desired.items():
        if key in ("version", "ts", "source"):
            continue
        low, high = LIMITS.get(key, (None, None))
        if low is not None and not (low <= value <= high):
            errors.append(f"{key} out of range")
            continue
        reported[key] = value
    reported["desired_version"] = desired["version"]
    reported["errors"] = errors
    publish_reported(client)

client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="pump7-9c4e")
client.will_set(f"{BASE}/status", "offline", qos=1, retain=True)
client.on_connect = on_connect
client.on_message = on_message
client.connect("broker.connectmq.com", 1883)
client.loop_forever()

Définissez un état désiré depuis un autre terminal :

mosquitto_pub -h broker.connectmq.com -p 1883 -i pump7-ops-9c4e -q 1 -r \
  -t "connectmq/demo/9c4e/plant1/pump7/state/desired" \
  -m '{"version": 1, "source": "operator", "speed_rpm": 1450, "mode": "AUTO"}'

Arrêtez l'appareil, publiez une version 2 avec une nouvelle vitesse, puis redémarrez l'appareil. Il reçoit le document désiré retained à l'abonnement et converge immédiatement. Observez le tout avec mosquitto_sub -t "connectmq/demo/9c4e/plant1/pump7/#" -v.

Afficher l'état de synchronisation dans l'interface du jumeau

La logique de synchronisation ne représente que la moitié du travail : les opérateurs doivent la voir. Une bonne interface de jumeau rend l'état de synchronisation explicite au lieu de le cacher :

  • En attente : l'état désiré diffère de l'état rapporté et l'appareil est en ligne. Affichez la valeur cible à côté de la valeur réelle, par exemple « 1 450 tr/min demandés, 1 200 tr/min réels ».
  • Appliqué : l'état rapporté renvoie la dernière version désirée. N'affichez que la valeur réelle.
  • Rejeté : l'appareil a signalé une erreur pour cette version. Affichez le motif et conservez la valeur précédente.
  • Périmé : le topic de statut indique hors ligne. Grisez les valeurs et indiquez quand elles ont été mises à jour pour la dernière fois.

Ajoutez aussi un délai d'expiration. Si un changement désiré reste en attente plus longtemps que ce dont l'appareil a normalement besoin, signalez-le pour que quelqu'un enquête au lieu de supposer que la machine a obéi. Ces états découlent directement des documents rapporté et désiré versionnés décrits plus haut ; ils ne nécessitent donc aucune infrastructure supplémentaire.

Checklist de synchronisation d'état

  1. État rapporté écrit uniquement par l'appareil, état désiré uniquement par les applications.
  2. Les deux documents retained, avec version et horodatage.
  3. L'état rapporté renvoie la dernière version désirée appliquée.
  4. Les actions envoyées sous forme de commandes non retained avec acquittements.
  5. Un Last Will sur un topic de statut retained.
  6. L'appareil publie l'état rapporté avant d'appliquer l'état désiré lors d'une reconnexion.
  7. L'appareil valide les limites et signale les rejets.
  8. L'interface affiche état désiré et état rapporté et marque les jumeaux hors ligne comme périmés.

Synchronisez vos jumeaux numériques sur ConnectMQ

ConnectMQ est un broker MQTT managé qui prend en charge les retained messages, le Last Will, les QoS 0 à 2 selon l'offre, ainsi que des fonctionnalités MQTT 5 comme les user properties, l'expiration des messages et l'expiration de session. L'explorateur de topics en temps réel du portail permet d'inspecter facilement côte à côte les documents rapporté et désiré. Votre logique de synchronisation réside dans les appareils et les applications qui se connectent comme des clients MQTT ordinaires, tandis que le broker MQTT dans le cloud assure la livraison. Commencez gratuitement avec l'offre Serverless sur app.connectmq.com, sans carte bancaire.

Questions fréquentes

Quelle est la différence entre état rapporté et état désiré ?

L'état rapporté est ce qu'est réellement l'appareil, écrit uniquement par l'appareil. L'état désiré est ce que les applications veulent qu'il devienne. La différence entre les deux est le delta sur lequel l'appareil doit agir.

Pourquoi utiliser des retained messages pour l'état désiré ?

Un document désiré retained est livré à l'appareil dès qu'il s'abonne : les changements effectués pendant qu'il était hors ligne sont donc appliqués automatiquement à la reconnexion.

Comment résoudre des mises à jour contradictoires d'un jumeau numérique ?

Ajoutez des versions et des sources aux documents désirés, ignorez les versions plus anciennes, fusionnez au niveau des champs lorsque la responsabilité diffère, et laissez l'appareil valider et rapporter ce qu'il a réellement appliqué.

Les commandes doivent-elles être retained ?

Non. Ne mettez en retained que l'état désiré idempotent. Les commandes d'action doivent être non retained, idéalement avec expiration des messages et acquittement.

Articles liés

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.