Bei der Zustandssynchronisation digitaler Zwillinge geht es darum, dass das, was der Zwilling annimmt, und das, was das Asset tatsächlich ist, übereinstimmen, auch über Verbindungsabbrüche, Verzögerungen und widersprüchliche Befehle hinweg. Dieser Artikel erklärt das Modell aus Reported und Desired State, Versionierung und Konfliktbehandlung sowie die Umsetzung mit Retained MQTT-Topics und endet mit einem lauffähigen Python-Gerät, das zum gewünschten Zustand konvergiert.
Für den breiteren Kontext beginnen Sie mit dem Leitfaden zur Digital-Twin-Technologie.
Warum Synchronisation schwierig ist
Ein Zwilling ist nur nützlich, wenn er stimmt. In der Praxis wirken mehrere Faktoren dagegen:
- Latenz: Der Zwilling sieht einen Wert erst einige Millisekunden oder Sekunden nach seiner Änderung.
- Verbindungsabbrüche: Ein Gerät geht offline und kommt mit einem anderen Zustand zurück.
- Gleichzeitige Schreiber: Ein Bediener und eine Automatisierung ändern beide einen Sollwert.
- Reihenfolge: Nachrichten über verschiedene Wege kommen in falscher Reihenfolge an.
- Teilausfälle: Ein Befehl wird zugestellt, aber die Bestätigung geht verloren.
Gutes Synchronisationsdesign beseitigt diese Probleme nicht, macht sie aber sichtbar und behebbar.
Das Modell aus Reported und Desired State
Das am weitesten verbreitete Muster, populär geworden durch Device-Shadow-Dienste und Zwillings-Frameworks wie Eclipse Ditto, teilt den Zustand in zwei Dokumente:
| Dokument | Geschrieben von | Bedeutung |
|---|---|---|
| Reported | Ausschließlich das Gerät | Was das Asset in diesem Moment tatsächlich ist |
| Desired | Apps, Bediener, Automatisierungen | Was das Asset werden soll |
| Delta (abgeleitet) | Berechnet | Felder, in denen Desired von Reported abweicht |
Die goldene Regel: Nur das Gerät schreibt den Reported State. Eine App, die eine Pumpe auf 1.450 U/min bringen will, schreibt desired.speed = 1450. Das Gerät sieht das Delta, handelt und meldet anschließend reported.speed = 1450. Die Oberfläche des Zwillings zeigt beides, sodass Bediener „angefordert" von „erreicht" unterscheiden können.
Diese Trennung beseitigt eine ganze Klasse von Fehlern, bei denen die Oberfläche einen Wert anzeigt, den die Maschine nie tatsächlich erreicht hat. Wie schlanke Shadows und vollständige Zwillinge das umsetzen, vergleicht Device Shadow vs. digitaler Zwilling.
Zustandssynchronisation digitaler Zwillinge mit MQTT umsetzen
MQTT lässt sich mit Retained Topics sauber auf dieses Modell abbilden:
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"
Im Werk hängen Sie die Zustands-Topics an den Knoten des Assets im Unified Namespace (UNS) an, dessen Topic-Struktur der ISA-95-Hierarchie folgt (Enterprise → Site → Area → Line → Cell), etwa acme/werk-stuttgart/versorgung/kuehlwasser/pumpe-7/state/reported. So liegen Reported und Desired State genau dort, wo MES, SCADA und Zwilling das Asset ohnehin suchen; siehe Sparkplug B und Unified Namespace.
Da beide Zustands-Topics retained sind, liefert der Broker die neueste Version an jeden, der abonniert:
- Ein Viewer, der den Zwilling öffnet, erhält sofort Reported und Desired State.
- Ein Gerät, das sich nach einem Ausfall wieder verbindet, erhält sofort den neuesten Desired State und kann konvergieren, selbst wenn die gewünschte Änderung während seiner Offline-Zeit vorgenommen wurde.
Diese zweite Eigenschaft ist der Schlüssel zu robuster Synchronisation. Für sollwertartige Änderungen brauchen Sie keinen separaten Speicher für „ausstehende Befehle"; das retained Desired-Dokument ist der ausstehende Befehl. Ein vollständiges Tutorial zum Retained-Zustand von Zwillingen finden Sie unter Digitaler Zwilling mit Retained MQTT.
Desired State vs. Befehle
Verwenden Sie Desired State für idempotente Ziele: Sollwerte, Modi, Konfiguration, Ein/Aus. Sie zweimal anzuwenden hat dieselbe Wirkung wie einmal. Verwenden Sie nicht retained Befehle für Aktionen: einen Zähler zurücksetzen, eine Tür für fünf Sekunden öffnen, eine Kalibrierung starten. Einen Aktionsbefehl zu retainen würde ihn nach jedem Reconnect erneut abspielen, was gefährlich ist.
Versionierung und Konfliktbehandlung
Jedes Zustandsdokument sollte eine Version und einen Zeitstempel tragen:
{
"version": 42,
"ts": 1792152000000,
"source": "operator:maria",
"speed_rpm": 1450,
"mode": "AUTO"
}
Nutzen Sie diese zur Konfliktbehandlung:
- Monoton steigende Versionen im Desired State. Schreiber lesen das aktuelle Desired-Dokument, erhöhen die Version und publizieren. Konsumenten ignorieren jede Desired-Nachricht mit einer niedrigeren Version als der bereits angewendeten.
- Der Reported State spiegelt die angewendete Desired-Version. Das Gerät nimmt
desired_version: 42in sein Reported-Dokument auf, damit der Zwilling weiß, welche Anforderung erfüllt wurde. - Last Writer Wins, aber explizit. Bei den meisten Sollwerten gewinnt die neueste Version. Halten Sie
sourcefest, damit die Oberfläche zeigen kann, wer die Änderung vorgenommen hat. - Merges auf Feldebene, wo nötig. Wenn eine App
speedverantwortet und eine anderemode, führen Sie auf Feldebene zusammen, statt das ganze Dokument zu ersetzen, oder teilen Sie die Felder auf separate Retained Topics auf. - Schutzmechanismen im Gerät. Das Gerät weist Werte außerhalb sicherer Grenzen zurück und meldet die Ablehnung mit Begründung, statt sie stillschweigend zu ignorieren.
Broker setzen Versionen nicht für Sie durch; das ist Logik in Ihren Publishern und im Gerät. MQTT-5-User-Properties sind ein bequemer Ort, um Versionsmetadaten zu transportieren, ohne den Payload anzufassen.
Reconnects und veraltete Zustände behandeln
Wenn sich ein Gerät wieder verbindet, folgen Sie einer festen Reihenfolge:
- Mit einem Last Will auf dem Status-Topic verbinden.
onlineretained auf das Status-Topic publizieren.- Den vollständigen aktuellen Reported State retained publizieren, aus den lokalen Sensoren, nicht aus der Erinnerung an das zuletzt Gesendete.
- Das Desired-Topic abonnieren und das retained Desired-Dokument empfangen.
- Das Delta berechnen, anwenden und den aktualisierten Reported State publizieren.
Dass Schritt 3 vor Schritt 4 kommt, ist wichtig: Der Zwilling erfährt zuerst die Wahrheit, dann konvergiert das Gerät zum Ziel. Umgekehrt könnte ein Viewer kurzzeitig einen veralteten Reported State sehen, der als aktuell gekennzeichnet ist.
Auf Seiten des Zwillings behandeln Sie den Reported State als veraltet, wenn das Status-Topic offline meldet. Stellen Sie ihn ausgegraut dar, statt alte Werte als live anzuzeigen.
Ein lauffähiges Gerät, das zum Desired State konvergiert
Dieses Python-Gerät verwendet den öffentlichen MQTT-Broker, der für Tests und Prototypen gedacht ist, und paho-mqtt 2.x. Ersetzen Sie 9c4e durch Ihr eigenes zufälliges Präfix.
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()
Setzen Sie in einem anderen Terminal einen Desired State:
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"}'
Stoppen Sie das Gerät, publizieren Sie Version 2 mit einer neuen Drehzahl und starten Sie das Gerät erneut. Es erhält das retained Desired-Dokument beim Abonnieren und konvergiert sofort. Beobachten Sie alles mit mosquitto_sub -t "connectmq/demo/9c4e/plant1/pump7/#" -v.
Den Synchronisationszustand in der Zwillingsoberfläche anzeigen
Die Synchronisationslogik ist nur die halbe Arbeit; Bediener müssen sie auch sehen. Eine gute Zwillingsoberfläche macht den Synchronisationszustand explizit, statt ihn zu verstecken:
- Ausstehend: Desired weicht von Reported ab, und das Gerät ist online. Zeigen Sie den Zielwert neben dem Istwert, zum Beispiel „1.450 U/min angefordert, 1.200 U/min Ist".
- Angewendet: Reported spiegelt die neueste Desired-Version. Zeigen Sie nur den Istwert.
- Abgelehnt: Das Gerät hat für diese Version einen Fehler gemeldet. Zeigen Sie die Begründung und behalten Sie den vorherigen Wert.
- Veraltet: Das Status-Topic meldet offline. Grauen Sie die Werte aus und zeigen Sie, wann sie zuletzt aktualisiert wurden.
Ergänzen Sie außerdem ein Timeout. Bleibt eine gewünschte Änderung länger ausstehend, als das Gerät normalerweise braucht, markieren Sie sie, damit jemand nachsieht, statt anzunehmen, dass die Maschine sie umgesetzt hat. Diese Zustände ergeben sich direkt aus den oben beschriebenen versionierten Reported- und Desired-Dokumenten und erfordern keine zusätzliche Infrastruktur.
Checkliste für die Zustandssynchronisation
- Reported State schreibt nur das Gerät, Desired State nur Apps.
- Beide Dokumente retained, mit Version und Zeitstempel.
- Reported spiegelt die zuletzt angewendete Desired-Version.
- Aktionen als nicht retained Befehle mit Bestätigungen senden.
- Last Will auf einem retained Status-Topic.
- Das Gerät publiziert beim Reconnect Reported, bevor es Desired anwendet.
- Das Gerät prüft Grenzwerte und meldet Ablehnungen.
- Die Oberfläche zeigt Desired und Reported und markiert Offline-Zwillinge als veraltet.
Synchronisieren Sie Ihre digitalen Zwillinge auf ConnectMQ
ConnectMQ ist ein verwalteter MQTT-Broker mit Unterstützung für Retained Messages, Last Will, QoS 0 bis 2 je nach Plan sowie MQTT-5-Funktionen wie User Properties, Message Expiry und Session Expiry. Mit dem Live-Topic-Explorer im Portal prüfen Sie Reported- und Desired-Dokumente bequem nebeneinander. Ihre Synchronisationslogik lebt in Geräten und Apps, die sich als reguläre MQTT-Clients verbinden, während der Cloud-MQTT-Broker die Zustellung übernimmt. Starten Sie kostenlos im Serverless-Plan unter app.connectmq.com, ganz ohne Kreditkarte.
Häufig gestellte Fragen
Was ist der Unterschied zwischen Reported und Desired State?
Der Reported State ist das, was das Gerät tatsächlich ist, und wird nur vom Gerät geschrieben. Der Desired State ist das, was Apps erreichen wollen. Die Differenz zwischen beiden ist das Delta, auf das das Gerät reagieren muss.
Warum Retained Messages für den Desired State verwenden?
Ein retained Desired-Dokument wird einem Gerät zugestellt, sobald es abonniert, sodass Änderungen aus seiner Offline-Zeit beim Reconnect automatisch angewendet werden.
Wie löse ich widersprüchliche Updates eines digitalen Zwillings auf?
Ergänzen Sie Desired-Dokumente um Versionen und Quellen, ignorieren Sie ältere Versionen, führen Sie auf Feldebene zusammen, wo die Verantwortung unterschiedlich ist, und lassen Sie das Gerät prüfen und melden, was es tatsächlich angewendet hat.
Sollten Befehle retained werden?
Nein. Retainen Sie nur idempotenten Desired State. Aktionsbefehle sollten nicht retained sein, idealerweise mit Message Expiry und einer Bestätigung.



