Jumeau numérique

Tutoriel jumeau numérique et messages retenus MQTT : les retained messages comme état du jumeau

Construisez un jumeau numérique léger où chaque propriété est un topic MQTT retained. Les visualiseurs obtiennent l'état complet du jumeau dès leur abonnement, sans base de données. Avec un simulateur Python, un visualiseur Node.js et les commandes de nettoyage.

Tutoriel jumeau numérique et messages retenus MQTT montrant des topics retained par propriété qui chargent instantanément l'état complet d'une machine dans un visualiseur
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

Utiliser les messages retenus MQTT (retained messages) pour un jumeau numérique est la façon la plus légère de doter un jumeau d'une mémoire : chaque propriété vit sur son propre topic retained, si bien que tout visualiseur qui s'abonne reçoit instantanément l'état actuel complet, sans base de données ni API. Dans ce tutoriel, vous construirez un petit jumeau de machine avec un simulateur Python, un visualiseur Node.js qui reconstitue l'état complet à la connexion, et les commandes nécessaires pour inspecter et nettoyer les topics retained.

Pour le concept dans son ensemble, consultez le guide de la technologie du jumeau numérique.

Comment fonctionnent les retained messages

Normalement, un broker MQTT transmet un message aux abonnés présents puis l'oublie. Lorsqu'un éditeur active le flag retain, le broker conserve aussi ce message comme dernière valeur connue du topic. Tout client qui s'abonne ensuite à un topic correspondant reçoit immédiatement le message stocké, marqué comme retained.

Règles clés :

  • Le broker conserve un seul retained message par topic : le plus récent remplace le précédent.
  • Les abonnements wildcard reçoivent le retained message de chaque topic correspondant.
  • Publier un payload vide avec le flag retain supprime le retained message.
  • Les retained messages sont livrés à la plus basse des deux QoS, celle de publication ou celle d'abonnement.

Cette combinaison est exactement ce dont un jumeau a besoin : un stockage clé-valeur de l'état actuel, adressé par topic et lisible par wildcard. Pour situer cette approche parmi les autres façons de gérer l'état, consultez l'architecture du jumeau numérique.

Concevoir l'arborescence : une propriété par topic

Vous pouvez mettre en retained un document JSON par actif ou une valeur par propriété. Ce tutoriel utilise un topic par propriété, ce qui présente de réels avantages :

Approche Avantages Inconvénients
Un JSON retained par actif Moins de messages, instantané atomique Chaque changement renvoie tous les champs ; des écrivains concurrents s'écrasent mutuellement
Un topic retained par propriété Petites mises à jour, écrivains indépendants, abonnement facile à une seule valeur Plus de topics ; pas d'instantané atomique entre propriétés

Pour une machine CNC sur le serveur MQTT public gratuit, l'arborescence ressemble à ceci (restez dans la limite de 8 niveaux de topic) :

connectmq/demo/f0c3/cell1/cnc1/spindle_rpm
connectmq/demo/f0c3/cell1/cnc1/feed_mm_min
connectmq/demo/f0c3/cell1/cnc1/tool
connectmq/demo/f0c3/cell1/cnc1/mode
connectmq/demo/f0c3/cell1/cnc1/alarm
connectmq/demo/f0c3/cell1/cnc1/status

Les payloads sont de simples valeurs, éventuellement accompagnées d'un horodatage. Ici, nous utilisons un petit objet JSON {"v": 8000, "ts": 1792152000000} pour que les visualiseurs sachent à quel point chaque valeur est récente.

Remplacez f0c3 par votre propre chaîne aléatoire. Le serveur MQTT public gratuit est lisible par tous et réservé aux tests et prototypes : ne publiez que des données de démonstration.

Étape 1 : publier les propriétés du jumeau en retained messages

Ce simulateur Python publie chaque propriété uniquement lorsqu'elle change, toujours avec retain=True, et utilise un Last Will pour marquer la machine hors ligne :

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

BASE = "connectmq/demo/f0c3/cell1/cnc1"
last_sent = {}

def publish_property(client, name, value):
    if last_sent.get(name) == value:
        return
    payload = json.dumps({"v": value, "ts": int(time.time() * 1000)})
    client.publish(f"{BASE}/{name}", payload, qos=1, retain=True)
    last_sent[name] = value

client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="cnc1-twin-f0c3")
client.will_set(f"{BASE}/status", json.dumps({"v": "offline"}), qos=1, retain=True)
client.connect("broker.connectmq.com", 1883)
client.loop_start()

publish_property(client, "status", "online")
publish_property(client, "tool", 3)

try:
    while True:
        running = random.random() > 0.1
        publish_property(client, "mode", "AUTO" if running else "HOLD")
        publish_property(client, "spindle_rpm", random.choice([7950, 8000, 8050]) if running else 0)
        publish_property(client, "feed_mm_min", 1000 if running else 0)
        publish_property(client, "alarm", None if running else "E-STOP")
        time.sleep(2)
except KeyboardInterrupt:
    publish_property(client, "status", "offline")
    time.sleep(0.5)
    client.loop_stop()
    client.disconnect()

Publier sur changement plutôt qu'à intervalle fixe limite le trafic et respecte la limite du serveur MQTT public gratuit de 10 messages par seconde et par client. L'état retained garantit que les visualiseurs voient toujours les valeurs inchangées.

Étape 2 : construire un visualiseur qui charge l'état complet instantanément

Le visualiseur s'abonne avec un wildcard, rassemble les retained messages dans un objet jumeau, puis continue d'appliquer les mises à jour en temps réel. Avec MQTT.js dans Node.js :

const mqtt = require("mqtt");

const base = "connectmq/demo/f0c3/cell1/cnc1";
const twin = {};

const client = mqtt.connect("mqtt://broker.connectmq.com:1883", {
  clientId: "cnc1-viewer-" + Math.random().toString(16).slice(2, 10),
  protocolVersion: 5,
});

client.on("connect", () => {
  client.subscribe(`${base}/+`, { qos: 1 });
});

client.on("message", (topic, payload, packet) => {
  const property = topic.slice(base.length + 1);
  if (payload.length === 0) {
    delete twin[property];
    return;
  }
  const { v, ts } = JSON.parse(payload.toString());
  twin[property] = { value: v, ts, fromRetained: packet.retain };
  console.clear();
  console.table(twin);
});

Lancez le visualiseur après le démarrage du simulateur. Le tableau se remplit d'un coup avec toutes les propriétés, car chaque retained message est livré à l'abonnement avec packet.retain à true. Les mises à jour suivantes arrivent comme messages en temps réel. Dans un navigateur, le même code fonctionne avec wss://broker.connectmq.com:8084/mqtt : c'est ainsi que les visualiseurs de jumeau 3D chargent une scène entière en un seul aller-retour ; jumeau numérique avec MQTT présente la variante navigateur.

Étape 3 : inspecter et nettoyer les topics retained

Les retained messages restent en place jusqu'à leur remplacement ou leur effacement, ce qui est une fonctionnalité… jusqu'au jour où vous renommez une propriété. Inspectez tout ce qui se trouve sous votre préfixe :

mosquitto_sub -h broker.connectmq.com -p 1883 -i inspect-f0c3 \
  -t "connectmq/demo/f0c3/#" -v

Effacez une propriété que vous n'utilisez plus en publiant un retained message vide :

mosquitto_pub -h broker.connectmq.com -p 1883 -i cleanup-f0c3 \
  -t "connectmq/demo/f0c3/cell1/cnc1/alarm" -r -n

Le flag -n envoie un payload nul (de longueur zéro) ; combiné à -r, il supprime le retained message.

Étendre le pattern à de nombreux actifs

Une machine, c'est facile ; une usine entière exige quelques conventions supplémentaires. Gardez les mêmes noms de propriétés pour les actifs d'un même type, afin qu'un visualiseur puisse afficher n'importe quelle CNC à partir du même modèle. Placez la hiérarchie des actifs dans le chemin du topic, par exemple site/area/line/asset/property, pour que les visualiseurs s'abonnent exactement à la branche qu'ils affichent : une vue de ligne s'abonne à .../line2/+/+, la page d'une seule machine à .../line2/cnc4/+. Sur le broker public, n'oubliez pas les limites de 8 niveaux de topic et de 10 abonnements par client, largement suffisantes pour une démo, mais une bonne raison d'utiliser votre propre broker pour de vrais déploiements.

Les visualiseurs qui ouvrent une grande branche reçoivent d'un coup un retained message par propriété. C'est généralement sans problème, mais analysez-les et appliquez-les par lot avant le premier rendu, plutôt que de redessiner la scène à chaque message. Le résultat : un jumeau qui apparaît complet dès la première image.

Les options MQTT 5 qui améliorent les jumeaux retained

MQTT 5 ajoute des réglages qui rendent l'état retained plus précis :

  • Message expiry interval : définissez une expiration sur les propriétés volatiles pour qu'une valeur retained disparaisse si elle n'est pas rafraîchie, au lieu d'afficher indéfiniment une lecture périmée.
  • Retain handling à l'abonnement : 0 envoie les retained messages à chaque abonnement, 1 uniquement pour les nouveaux abonnements, 2 jamais. Utilisez 2 pour les consommateurs qui ne veulent que les changements en temps réel.
  • Retain as published : permet à un bridge ou à un relais de savoir si le message d'origine était retained.

Dans MQTT.js, un abonnement avec { qos: 1, rh: 2 } ignore les retained messages, et une publication avec { retain: true, properties: { messageExpiryInterval: 300 } } fait expirer une valeur au bout de cinq minutes. Consultez les fonctionnalités de MQTT 5 pour aller plus loin.

Pièges et dépannage

  1. Propriétés fantômes : les topics renommés laissent derrière eux d'anciennes valeurs retained. Effacez-les explicitement.
  2. Commandes retained : ne mettez jamais en retained les topics de commande ; les appareils les réexécuteraient à la reconnexion.
  3. Valeurs périmées affichées comme actuelles : incluez des horodatages et tenez compte du topic de statut.
  4. Payloads retained trop volumineux : gardez chaque valeur petite ; le broker public accepte jusqu'à 64 Ko par message et l'offre Serverless gratuite 256 Ko.
  5. Pas d'instantané atomique : si plusieurs propriétés doivent changer ensemble, publiez-les plutôt sous forme d'un seul document JSON retained.
  6. Redémarrages du broker : prévoyez que l'état retained soit republié par les appareils à leur reconnexion, plutôt que de supposer qu'il survivra indéfiniment aux redémarrages du broker. Des appareils qui publient leur état complet à la connexion rendent le jumeau auto-réparateur.

Quand les topics retained ne suffisent plus

Les topics retained sont idéaux pour l'état actuel. Ajoutez d'autres composants lorsque vous avez besoin d'un historique (un abonné de séries temporelles), de requêtes sur plusieurs actifs ou d'un contrôle d'accès par jumeau (un service de jumeau comme Eclipse Ditto avec MQTT), ou de la gestion désiré/rapporté à grande échelle. Pour le pattern désiré/rapporté sur topics retained, lisez la synchronisation d'état du jumeau numérique.

Faites tourner votre jumeau à état retained sur ConnectMQ

Pour de vrais actifs, passez du serveur MQTT public gratuit à un broker privé sur ConnectMQ, un broker MQTT managé compatible MQTT 3.1.1 et 5.0, avec retained messages, expiration des messages et TLS. Remplacez l'hôte par celui de votre broker, ajoutez YOUR_USERNAME, YOUR_PASSWORD et YOUR_CLIENT_ID depuis le portail, et passez au port 8883, ou 8084 pour les navigateurs. Inscrivez-vous sur app.connectmq.com avec l'offre Serverless gratuite, sans carte bancaire, et observez les topics retained de votre jumeau dans l'explorateur de topics en temps réel.

Questions fréquentes

Qu'est-ce qu'un retained message dans MQTT ?

C'est un message que le broker conserve comme dernière valeur connue de son topic et qu'il livre immédiatement à tout nouvel abonné à ce topic.

Comment supprimer un retained message MQTT ?

Publiez un payload de longueur zéro avec le flag retain sur le même topic, par exemple mosquitto_pub -t <topic> -r -n.

Chaque propriété du jumeau doit-elle avoir son propre topic retained ?

Cela fonctionne bien pour des propriétés indépendantes et de petites mises à jour. Utilisez un document JSON retained par actif lorsque plusieurs valeurs doivent changer ensemble de façon atomique.

Les retained messages peuvent-ils remplacer une base de données pour un jumeau numérique ?

Pour l'état actuel, souvent oui. Pour l'historique, les requêtes multi-actifs ou les pistes d'audit, ajoutez un abonné base de données à côté du broker.

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.