Digital Twin

Digital Twin MQTT Guide: Streaming Real-Time State to Your Twin

Why MQTT is the natural transport for digital twins, and how to design topics, retained state, presence and commands so a twin stays in sync with real assets, with runnable Python and JavaScript.

Digital twin MQTT data flow with machines publishing live state through a broker to a 3D factory model in a browser
Run this on your own MQTT brokerGet a private, managed MQTT broker on the Free plan and start from ready-made code samples.
Try it free Code samples No card required

Combining a digital twin with MQTT is the most direct way to keep a virtual model in step with real equipment. In this guide you will learn why MQTT's publish/subscribe model fits twins so well, how to design topics, retained state, presence and commands, and how to stream live state into a browser-based twin with runnable Python and JavaScript.

If you are new to the twin concept, start with the digital twin technology guide.

Why MQTT Fits Digital Twins

A twin needs three things from its transport: low-latency updates, a way for late joiners to get the current state, and loose coupling so new consumers can be added without touching devices. MQTT provides all three in the protocol itself.

Twin requirement MQTT feature Why it helps
Live updates Persistent connections, push delivery Values arrive in milliseconds, no polling
Current state on load Retained messages A new viewer gets the latest value per topic instantly
Asset hierarchy Hierarchical topics and wildcards plant1/line2/# gives a whole line in one subscription
Online/offline status Last will and testament The broker announces when a device drops
Reliability control QoS 0, 1, 2 Fast telemetry at QoS 0, important events at QoS 1
Many consumers Publish/subscribe decoupling Dashboards, 3D views and analytics share one stream
Browser viewers MQTT over WebSocket 3D scenes connect directly with no extra gateway

HTTP polling, by contrast, forces every viewer to hammer an API and still leaves gaps between polls. Message queues designed for back-end processing often lack retained state and browser connectivity. That is why so many twin platforms put an MQTT broker at the centre.

Designing Topics for a Digital Twin with MQTT

The topic tree is your twin's addressing scheme. Mirror the physical hierarchy, then end with a message class:

connectmq/demo/<id>/factory1/line1/cnc1/telemetry   stream, not retained
connectmq/demo/<id>/factory1/line1/cnc1/state       current state, retained
connectmq/demo/<id>/factory1/line1/cnc1/status      online/offline, retained + last will
connectmq/demo/<id>/factory1/line1/cnc1/cmd         commands, never retained
connectmq/demo/<id>/factory1/line1/cnc1/cmd/ack     command results

Rules that save pain later:

  • Keep topic levels stable identifiers, not display names. Names change; IDs do not.
  • Put units and timestamps in the payload, not in the topic.
  • Keep each payload small and focused on one asset.
  • Never retain commands. A retained cmd would re-execute every time a device reconnects.

For a deeper discussion of how topics map to the asset model, see digital twin architecture.

Retained Messages as the Twin's Memory

When a viewer opens the twin, it should not wait for the next telemetry tick to show every machine. Publishing state with the retain flag means the broker stores the last message per topic and delivers it immediately on subscribe. The retained MQTT state tutorial builds a complete twin this way; here is the core idea in Python with paho-mqtt 2.x:

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

PREFIX = "connectmq/demo/b72e/factory1/line1/cnc1"

client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="cnc1-sim-b72e")
client.will_set(f"{PREFIX}/status", payload="offline", qos=1, retain=True)
client.connect("broker.connectmq.com", 1883)
client.loop_start()

client.publish(f"{PREFIX}/status", "online", qos=1, retain=True)

try:
    while True:
        state = {
            "ts": int(time.time() * 1000),
            "spindle_rpm": random.randint(7800, 8200),
            "feed_mm_min": random.randint(900, 1100),
            "tool": 4,
            "mode": "AUTO",
        }
        client.publish(f"{PREFIX}/state", json.dumps(state), qos=0, retain=True)
        time.sleep(1)
except KeyboardInterrupt:
    client.publish(f"{PREFIX}/status", "offline", qos=1, retain=True)
    client.loop_stop()
    client.disconnect()

The last will on the status topic is important. If the simulator crashes or the network drops, the broker publishes offline for you, and because it is retained, any viewer that opens later also sees the machine as offline.

Streaming the Twin into a Browser

A browser-based 3D or 2D twin connects over secure WebSocket on port 8084 with the /mqtt path. This MQTT.js example subscribes to the whole line and updates an in-memory twin model that your rendering code can read each frame:

import mqtt from "mqtt";

const prefix = "connectmq/demo/b72e/factory1/line1";
const twin = new Map();

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

client.on("connect", () => {
  client.subscribe(`${prefix}/+/state`);
  client.subscribe(`${prefix}/+/status`);
});

client.on("message", (topic, payload) => {
  const parts = topic.split("/");
  const asset = parts[parts.length - 2];
  const kind = parts[parts.length - 1];
  const current = twin.get(asset) || {};
  if (kind === "status") {
    current.online = payload.toString() === "online";
  } else {
    Object.assign(current, JSON.parse(payload.toString()));
  }
  twin.set(asset, current);
  console.log(asset, current);
});

Decouple message handling from rendering. Messages update the model; the render loop reads the model. This keeps frame rates smooth even when messages arrive in bursts.

Payload Design for Twin Messages

Topics tell consumers where a value belongs; payloads tell them what it means. A few conventions keep a growing twin manageable:

  • Include a source timestamp (ts in milliseconds) in every state and telemetry message. Viewers can then show the age of each value and ignore out-of-order updates.
  • Use consistent field names and units across assets of the same type, ideally documented in a schema or a modelling language such as DTDL.
  • Send full state on the state topic, not partial patches. A retained partial patch would leave a new viewer with half a picture.
  • Keep payloads compact. JSON is easy to debug and fine for most twins; switch to a binary encoding only when bandwidth is genuinely constrained.
  • Add a schema version field when payloads may evolve, so old and new devices can coexist during rollouts.

These rules cost nothing at the start and save significant rework once dozens of asset types and several consuming applications depend on the same stream.

Commands: Closing the Loop

An observing twin becomes an operating twin once it can send commands. The safe pattern:

  1. The app publishes a command with a correlation ID to .../cmd at QoS 1, not retained.
  2. The device validates and executes it, then publishes the result to .../cmd/ack with the same ID.
  3. The device updates its retained state, which every viewer sees.
  4. The app times out if no acknowledgement arrives.

MQTT 5 makes this cleaner with the response topic and correlation data properties, plus message expiry so a command that sat too long is discarded rather than executed late. Read MQTT 5 features for details. For desired versus reported state handling, see digital twin state synchronization.

Choosing QoS for Twin Data

  1. Fast telemetry for animation: QoS 0. A missed frame is replaced by the next one.
  2. State changes and alarms: QoS 1, so they are not lost on brief disconnects.
  3. Commands: QoS 1 with idempotent handling and correlation IDs. QoS 2 is rarely needed if commands are idempotent.

The MQTT QoS levels explained article covers the trade-offs in depth.

How ConnectMQ Powers a Live 3D Digital Twin

ConnectMQ runs a 3D digital twin web demo of a small factory with a CNC machine, robot arm, furnace, tank, conveyor and pump. Simulated assets publish state to the broker, and the 3D scene subscribes over secure WebSocket, using exactly the patterns above: hierarchical topics, retained state for instant load, and live updates driving animation.

For your own twin, ConnectMQ provides a hosted MQTT broker with MQTT 3.1.1 and 5.0, TLS on 8883 and WSS on 8084 with a Let's Encrypt certificate, so browser viewers connect without certificate warnings. The portal's live topic explorer and devices view by ISA-95 levels help you check that every asset is publishing where it should. Start experimenting on the free public MQTT broker with demo data, then move to your own credentials for real assets.

Troubleshooting Digital Twin MQTT Setups

  • Viewer shows nothing until the first update. State is not being published with the retain flag.
  • A device keeps executing an old command after reboot. A command was retained. Clear it by publishing an empty retained payload to that topic, and stop retaining commands.
  • Assets stay "online" after power loss. No last will configured, or keepalive is too long.
  • Browser cannot connect. Check the wss:// URL, port 8084 and the /mqtt path.
  • Stuttering animation. Rendering is coupled to message handling, or telemetry rate is too low for smooth motion; interpolate between samples.

Connect Your Digital Twin to ConnectMQ

Create a broker on the free Serverless plan at app.connectmq.com with no credit card, copy the generated username, password and client ID, and point your simulator and viewer at it. Within minutes you will have a live twin streaming over MQTT.

Frequently Asked Questions

Why use MQTT instead of REST for a digital twin?

MQTT pushes updates as they happen over a persistent connection and keeps the latest state in retained messages, while REST needs polling and adds latency and load.

Can a browser connect directly to MQTT for a 3D twin?

Yes. Browsers connect over MQTT over WebSocket, for example wss://broker.connectmq.com:8084/mqtt, using a library such as MQTT.js.

Should I retain telemetry or only state?

Retain the current state topic so viewers load instantly; leave high-rate telemetry streams unretained to avoid confusion between the stream and the snapshot.

How do I show when a device goes offline in the twin?

Configure a last will message on a retained status topic. The broker publishes it automatically when the device disconnects unexpectedly.

Related articles

All articles →

Your private MQTT broker is a few clicks away

Create an account, get your broker credentials and connect your first device — no servers to install, patch or scale.