Knowing how to build a digital twin is less about picking a platform and more about following a disciplined sequence: scope, model, connect, visualize, store, improve. This guide walks through seven practical steps and includes working code for each technical one, so by the end you will have a live twin of an asset streaming over MQTT into a browser view, plus a clear path to production.
For definitions and the big picture, see the digital twin technology guide.
Before You Start
You need very little to follow along:
- Python 3.9 or newer with
pip install "paho-mqtt>=2.0". - Node.js for the viewer, with
npm install mqtt. - Optional: the Mosquitto command-line clients for quick tests.
We use the public MQTT test broker at broker.connectmq.com for the hands-on parts. It needs no account, but everything published there is readable by others, so use demo data and a unique topic prefix. Replace bdt-4w8 below with your own random ID.
Step 1: Define the Decision and the Scope
Start with the decision the twin will improve, not with the technology. "Reduce unplanned stops on the filling line" is a good goal. "Build a factory twin" is not, because it has no clear user or measure of success.
Write down:
- The user: who will look at or act on the twin.
- The decision: what they will do differently with it.
- The asset scope: one machine or a small group to start.
- The success measure: a KPI with a current baseline.
Most first twins should be asset twins, one machine with its key signals. The guide to types of digital twins explains why that scope usually delivers fastest.
Step 2: Model the Asset
List what the twin must know about the asset and classify each item:
| Item | Example | Changes | Source |
|---|---|---|---|
| Identity and metadata | Model, serial number, location | Rarely | Asset register |
| State | Running, idle, fault, mode | On events | PLC |
| Process values | Temperature, pressure, speed | Continuously | PLC or sensors |
| Counters | Cycles, good parts, rejects | Continuously | PLC |
| Alarms | Overtemperature, low level | On events | PLC |
| Commands (later) | Setpoint, start, stop | On request | Twin or operator |
Then place the asset in a hierarchy. ISA-95 levels (enterprise, site, area, line, cell) are a strong default because they match how plants are organized and how people search for equipment. For this tutorial our asset is a filler at site1/filling/line2/filler1.
Step 3: Design Topics and Payloads
Topics turn the model into an address space. A clean pattern separates kinds of data so consumers subscribe only to what they need:
connectmq/demo/bdt-4w8/site1/filling/line2/filler1/state retained, QoS 1
connectmq/demo/bdt-4w8/site1/filling/line2/filler1/telemetry not retained, QoS 0
connectmq/demo/bdt-4w8/site1/filling/line2/filler1/status Last Will, retained
On a private broker you would drop the connectmq/demo/bdt-4w8 prefix and start at your enterprise name. Payload rules that save pain later:
- JSON with explicit units in field names (
temp_c,flow_lpm). - A source timestamp in every message.
- State messages contain the full current state, not just changes, so a retained message is self-sufficient.
If you plan to grow into a plant-wide namespace, read the Sparkplug B and Unified Namespace guide now; it is much easier to start consistent than to rename later.
Step 4: Connect the Data Source
On a real line, an edge gateway reads the PLC (often through OPC UA or a vendor protocol) and publishes to MQTT. For this tutorial, a Python script simulates the filler and publishes exactly what a gateway would.
import json
import random
import time
from datetime import datetime, timezone
import paho.mqtt.client as mqtt
BASE = "connectmq/demo/bdt-4w8/site1/filling/line2/filler1"
CLIENT_ID = f"bdt-4w8-filler1-{random.randint(1000, 9999)}"
def now():
return datetime.now(timezone.utc).isoformat(timespec="seconds")
def on_connect(client, userdata, flags, reason_code, properties):
print("Connected:", reason_code)
client.publish(f"{BASE}/status", "online", qos=1, retain=True)
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=CLIENT_ID)
client.will_set(f"{BASE}/status", "offline", qos=1, retain=True)
client.on_connect = on_connect
client.connect("broker.connectmq.com", 1883, keepalive=30)
client.loop_start()
good, rejects, mode = 0, 0, "running"
try:
while True:
if random.random() < 0.02:
mode = "fault" if mode == "running" else "running"
if mode == "running":
good += random.randint(4, 6)
rejects += 1 if random.random() < 0.05 else 0
telemetry = {"ts": now(), "fill_temp_c": round(random.gauss(21.5, 0.3), 2),
"flow_lpm": round(random.gauss(118, 2), 1) if mode == "running" else 0.0}
state = {"ts": now(), "mode": mode, "good_count": good, "reject_count": rejects}
client.publish(f"{BASE}/telemetry", json.dumps(telemetry), qos=0)
client.publish(f"{BASE}/state", json.dumps(state), qos=1, retain=True)
time.sleep(1)
except KeyboardInterrupt:
client.publish(f"{BASE}/status", "offline", qos=1, retain=True)
client.loop_stop()
client.disconnect()
Verify the stream from another terminal:
mosquitto_sub -h broker.connectmq.com -p 1883 -i bdt-4w8-check -t "connectmq/demo/bdt-4w8/#" -v
Step 5: Choose and Secure the Broker
The broker is the hub every twin component connects to, so treat it as production infrastructure from day one. Requirements to check:
- TLS for every connection, plus WebSocket over TLS for browser viewers.
- Separate credentials per gateway, viewer and service.
- MQTT 5 support if you want message expiry, reason codes or shared subscriptions.
- Enough connection and message capacity for your growth path.
- Someone responsible for uptime, patches and certificates.
Running your own broker means owning all five. A fully managed MQTT service such as ConnectMQ covers them: you create a broker in the portal and receive a generated username, password and client ID, with TLS on port 8883 and Secure WebSocket on 8084. Moving the simulator to it is a three-line change:
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="YOUR_CLIENT_ID")
client.username_pw_set("YOUR_USERNAME", "YOUR_PASSWORD")
client.tls_set()
Then connect to your broker host on port 8883. The MQTT security best practices guide covers the rest of the hardening checklist.
Step 6: Visualize the Twin
A twin earns its keep when people look at it. Start with a simple live view, then add 3D when the data is trustworthy. This Node.js script is a terminal "twin view" that keeps the latest state and flags stale data, the same logic a browser dashboard would use:
import mqtt from "mqtt";
const BASE = "connectmq/demo/bdt-4w8/site1/filling/line2/filler1";
const twin = { status: "unknown", state: {}, telemetry: {}, lastSeen: 0 };
const client = mqtt.connect("mqtts://broker.connectmq.com:8883", {
clientId: "bdt-4w8-view-" + Math.random().toString(16).slice(2, 8),
protocolVersion: 5,
});
client.on("connect", () => client.subscribe(`${BASE}/#`, { qos: 1 }));
client.on("message", (topic, payload) => {
const kind = topic.split("/").pop();
twin.lastSeen = Date.now();
if (kind === "status") twin.status = payload.toString();
else twin[kind] = JSON.parse(payload.toString());
});
setInterval(() => {
const ageS = twin.lastSeen ? Math.round((Date.now() - twin.lastSeen) / 1000) : null;
const stale = ageS === null || ageS > 10;
const { mode = "-", good_count = 0, reject_count = 0 } = twin.state;
console.clear();
console.log(`Filler1 [${twin.status}]${stale ? " STALE" : ""}`);
console.log(`Mode: ${mode} Good: ${good_count} Rejects: ${reject_count}`);
console.log(`Fill temp: ${twin.telemetry.fill_temp_c ?? "-"} C Flow: ${twin.telemetry.flow_lpm ?? "-"} l/min`);
console.log(`Last update: ${ageS ?? "-"} s ago`);
}, 1000);
Save it as view.mjs and run node view.mjs. In a browser, connect with wss://broker.connectmq.com:8084/mqtt instead and render the same state into a page or a 3D scene; the Three.js digital twin tutorial shows how to bind values to a 3D model, and the MQTT over WebSocket guide explains the browser connection.
Step 7: Add History, Alerts and Iterate
A live view answers "what is happening". To answer "why" and "what next", add:
- History: a subscriber such as Telegraf or a small script writing telemetry into a time-series database.
- Alerts: threshold rules on key values. The ConnectMQ portal includes threshold alert rules and an alerts log, handy before you build your own alerting.
- Feedback: once data quality is proven, command topics with acknowledgements to change setpoints from the twin.
Then measure the KPI from step 1 and decide the next scope. The digital twin maturity model helps plan that progression, and the digital twin architecture guide covers the components you will add along the way.
Common Pitfalls
| Pitfall | Consequence | Avoid it by |
|---|---|---|
| Starting with a whole factory | Months of integration before any value | Scoping one asset first |
| Inconsistent topic names | Wildcard subscriptions break, rework later | Fixing the hierarchy in step 2 |
| No timestamps or units | Untrustworthy history and analytics | Payload rules in step 3 |
| Shared credentials | Clients kick each other off, no audit trail | One identity per client |
| No stale-data indicator | Users trust old values | Tracking last-seen time and Last Will |
Build Your Digital Twin on ConnectMQ
You now know how to build a digital twin from scope to live view. Prototype on broker.connectmq.com, then move to private credentials when real data arrives. Create a free ConnectMQ Serverless broker, no credit card required, and connect your first asset today.
Frequently Asked Questions
How long does it take to build a digital twin?
A first asset twin with live data and a simple view can be running in days once data access is sorted. History, analytics and 3D add weeks, and scaling to many assets depends on integration effort.
Do I need a 3D model to build a digital twin?
No. Many effective twins are dashboards. 3D helps with spatial understanding and communication, but it can be added after the data foundation works.
What protocol should a digital twin use?
MQTT is a common choice for distributing live data because it is lightweight and lets many consumers share one stream. Gateways often read OPC UA or PLC protocols and publish to MQTT.
Can I build a digital twin without a platform license?
Yes. A standard MQTT broker, open-source tools for history and visualization, and a clear topic design are enough for a capable twin.



