Bir konveyör dijital ikizi, bir hattın hareket edip etmediğini, ne kadar hızlı aktığını ve nerede takıldığını tek bakışta gösterir. Bu eğitimde Three.js ile tarayıcıda bir konveyör ikizi oluşturacaksınız: silindirler döner ve koliler MQTT üzerinden bildirilen gerçek bant hızında ilerler, bir sıkışma sinyali kolileri durdurup bandı kırmızıya boyar ve operatörler hatta başlat, durdur ve sıfırla komutları gönderebilir. Bir Python simülatörü broker.connectmq.com adresine gerçekçi konveyör verileri yayınlar; böylece bir PLC bağlamadan önce her şeyi oluşturup test edebilirsiniz.
Konveyörü İyi Bir Dijital İkiz Adayı Yapan Nedir?
Konveyörler üretimde, depolamada ve lojistikte her yerdedir ve görünür, maliyetli şekillerde arıza yaparlar: bir birleşme noktasında sıkışan tek bir koli, tüm yukarı akışı durdurabilir. Sinyaller genellikle PLC'de zaten mevcuttur ve doğal olarak harekete eşlenir:
| Sinyal | Tipik kaynak | Birim | İkiz davranışı |
|---|---|---|---|
| Bant hızı | Sürücü geri beslemesi veya enkoder | m/s | Silindirler döner, koliler hareket eder |
| Çalışıyor | Motor kontaktörü veya sürücü durumu | boolean | Bant rengi, hareket açık/kapalı |
| Sıkışma | Bir eşik süresinden uzun kapalı kalan fotosel | boolean ve bölge | Bant kırmızıya döner, koliler sıkışma bölgesinde durur |
| Koli sayısı | Çıkıştaki fotosel | adet | Verim sayacı |
| Motor akımı | Sürücü | A | İsteğe bağlı, aşırı yük için erken uyarı |
Görsel davranış doğrudan fizikten türediği için (r yarıçaplı bir silindir saniyede v / r radyan döner), bir konveyör ikizi animasyonu rastgele sayılarla değil gerçek birimlerle yönetmeyi öğrenmenin de iyi bir yoludur. Büyük resim için dijital ikiz teknolojisi rehberimize bakın.
Topic Düzeni
Benzersiz bir prefix seçin; örnekte connectmq/demo/conv-7f3a kullanılıyor:
| Topic | Yön | Payload |
|---|---|---|
.../line1/conv1/telemetry |
Hattan ikize | {"speed":0.8,"running":true,"jam":false,"jamZone":null,"count":1532,"current":4.1} |
.../line1/conv1/cmd |
İkizden hatta | START, STOP, RESET |
.../line1/conv1/status |
Hat, retained, last will | online / offline |
Her sinyal için ayrı bir topic yerine tek bir birleşik telemetri nesnesi göndermek, ikizin asla tutarsız bir karışım görmemesini garanti eder (örneğin bir mesajdan running: true, daha eski bir mesajdan speed: 0). Birleşik ve sinyal başına topic'ler arasındaki artı ve eksiler dijital ikizler için MQTT yazımızda ele alınıyor.
Adım 1: Konveyör Simülatörü
Simülatör, 0,8 m/s hızla çalışan üç bölgeli bir konveyörü modeller, kolileri sayar, zaman zaman rastgele bir bölgede sıkışır ve komutlara uyar. paho-mqtt 2.x kullanır (pip install "paho-mqtt>=2.0").
import json
import random
import time
import paho.mqtt.client as mqtt
P = "connectmq/demo/conv-7f3a/line1/conv1"
NOMINAL_SPEED = 0.8 # m/s
state = {"running": True, "jam": False, "jamZone": None, "speed": 0.0, "count": 0}
def on_connect(client, userdata, flags, reason_code, properties):
print("connected:", reason_code)
client.publish(f"{P}/status", "online", qos=1, retain=True)
client.subscribe(f"{P}/cmd", qos=1)
def on_message(client, userdata, msg):
cmd = msg.payload.decode().strip().upper()
print("command:", cmd)
if cmd == "START" and not state["jam"]:
state["running"] = True
elif cmd == "STOP":
state["running"] = False
elif cmd == "RESET":
state["jam"], state["jamZone"] = False, None
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="conv-sim-7f3a")
client.will_set(f"{P}/status", "offline", qos=1, retain=True)
client.on_connect = on_connect
client.on_message = on_message
client.connect("broker.connectmq.com", 1883, keepalive=30)
client.loop_start()
while True:
if state["running"] and not state["jam"] and random.random() < 0.01:
state["jam"], state["jamZone"] = True, random.choice([1, 2, 3])
state["running"] = False # the PLC stops the belt on a jam
target = NOMINAL_SPEED if state["running"] else 0.0
state["speed"] += (target - state["speed"]) * 0.5 # ramp like a drive would
if state["speed"] > 0.05 and random.random() < state["speed"] / 1.6:
state["count"] += 1
payload = {
"speed": round(state["speed"], 3),
"running": state["running"],
"jam": state["jam"],
"jamZone": state["jamZone"],
"count": state["count"],
"current": round(1.0 + state["speed"] * 4 + random.uniform(-0.1, 0.1), 2),
}
client.publish(f"{P}/telemetry", json.dumps(payload), qos=0, retain=True)
time.sleep(0.5)
Saniyede iki mesaj, client başına saniyede 10 mesaja kadar izin veren public broker'da rahatça çalışır.
Adım 2: Konveyör Dijital İkizi Sahnesini Oluşturun
index.html olarak kaydedin ve npx serve . ile sunun. Silindirler bir InstancedMesh ile render edilir; bu da uzun bir konveyörün çizim maliyetini düşük tutar.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Conveyor Digital Twin</title>
<style>
body { margin: 0; overflow: hidden; background: #0f172a; font-family: system-ui, sans-serif; }
#ui { position: absolute; top: 10px; left: 10px; color: #fff; background: rgba(0,0,0,.6);
padding: 10px 12px; border-radius: 6px; font-size: 14px; }
#ui button { margin-right: 6px; margin-top: 8px; }
</style>
<script type="importmap">
{ "imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/",
"mqtt": "https://unpkg.com/mqtt@5.10.1/dist/mqtt.esm.js"
} }
</script>
</head>
<body>
<div id="ui">
<div id="txt">Connecting...</div>
<button data-cmd="START">Start</button><button data-cmd="STOP">Stop</button><button data-cmd="RESET">Reset jam</button>
</div>
<script type="module">
import * as THREE from "three";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
import mqtt from "mqtt";
const P = "connectmq/demo/conv-7f3a/line1/conv1";
const LENGTH = 12, WIDTH = 1.2, ROLLER_R = 0.06, ROLLER_PITCH = 0.25, ZONES = 3;
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x0f172a);
const camera = new THREE.PerspectiveCamera(45, innerWidth / innerHeight, 0.1, 100);
camera.position.set(4, 5, 9);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
new OrbitControls(camera, renderer.domElement);
scene.add(new THREE.HemisphereLight(0xffffff, 0x334155, 1.3));
const sun = new THREE.DirectionalLight(0xffffff, 1.2); sun.position.set(3, 10, 4); scene.add(sun);
// Frame
const frameMat = new THREE.MeshStandardMaterial({ color: 0x64748b });
for (const z of [-WIDTH / 2 - 0.05, WIDTH / 2 + 0.05]) {
const rail = new THREE.Mesh(new THREE.BoxGeometry(LENGTH, 0.15, 0.08), frameMat);
rail.position.set(0, 0.8, z); scene.add(rail);
}
// Rollers as instances
const count = Math.floor(LENGTH / ROLLER_PITCH);
const rollerGeo = new THREE.CylinderGeometry(ROLLER_R, ROLLER_R, WIDTH, 16);
rollerGeo.rotateX(Math.PI / 2); // axis along Z
const rollerMat = new THREE.MeshStandardMaterial({ color: 0x94a3b8, metalness: 0.6, roughness: 0.4 });
const rollers = new THREE.InstancedMesh(rollerGeo, rollerMat, count);
const dummy = new THREE.Object3D();
const rollerX = (i) => -LENGTH / 2 + ROLLER_PITCH / 2 + i * ROLLER_PITCH;
scene.add(rollers);
// Zone markers (light up red on jam)
const zoneMats = [];
for (let z = 0; z < ZONES; z++) {
const m = new THREE.MeshStandardMaterial({ color: 0x1e293b, emissive: 0x000000 });
const strip = new THREE.Mesh(new THREE.BoxGeometry(LENGTH / ZONES - 0.1, 0.02, 0.1), m);
strip.position.set(-LENGTH / 2 + (z + 0.5) * (LENGTH / ZONES), 0.89, WIDTH / 2 + 0.05);
scene.add(strip); zoneMats.push(m);
}
// Parcels
const parcelMat = new THREE.MeshStandardMaterial({ color: 0xc08a4a });
const parcels = [];
for (let i = 0; i < 8; i++) {
const p = new THREE.Mesh(new THREE.BoxGeometry(0.6, 0.4, 0.6), parcelMat);
p.position.set(-LENGTH / 2 + i * 1.5, 0.8 + ROLLER_R + 0.2, 0);
scene.add(p); parcels.push(p);
}
// ---------- State ----------
const s = { speed: 0, running: false, jam: false, jamZone: null, count: 0, online: false, at: 0 };
let shownSpeed = 0, rollerAngle = 0;
// ---------- MQTT ----------
const client = mqtt.connect("wss://broker.connectmq.com:8084/mqtt", {
clientId: "conv-twin-" + Math.random().toString(16).slice(2, 10),
protocolVersion: 5,
});
const dec = new TextDecoder();
client.on("connect", () => client.subscribe(`${P}/#`, { qos: 1 }));
client.on("message", (topic, payload) => {
const text = dec.decode(payload);
if (topic.endsWith("/status")) { s.online = text === "online"; return; }
if (!topic.endsWith("/telemetry")) return;
try { Object.assign(s, JSON.parse(text), { at: performance.now() }); } catch {}
});
document.querySelectorAll("button").forEach((b) =>
b.addEventListener("click", () => client.publish(`${P}/cmd`, b.dataset.cmd, { qos: 1 })));
// ---------- Animation ----------
const clock = new THREE.Clock();
const txt = document.getElementById("txt");
renderer.setAnimationLoop(() => {
const dt = Math.min(clock.getDelta(), 0.1);
const stale = performance.now() - s.at > 3000;
const v = stale ? 0 : s.speed;
shownSpeed += (v - shownSpeed) * (1 - Math.exp(-dt * 6));
// Rollers: angular velocity = v / r
rollerAngle -= (shownSpeed / ROLLER_R) * dt;
for (let i = 0; i < count; i++) {
dummy.position.set(rollerX(i), 0.8, 0);
dummy.rotation.set(0, 0, rollerAngle);
dummy.updateMatrix();
rollers.setMatrixAt(i, dummy.matrix);
}
rollers.instanceMatrix.needsUpdate = true;
// Parcels move at belt speed, wrap at the end
for (const p of parcels) {
p.position.x += shownSpeed * dt;
if (p.position.x > LENGTH / 2) p.position.x -= LENGTH;
}
// Jam visualisation
zoneMats.forEach((m, i) => {
const jammed = s.jam && s.jamZone === i + 1;
m.emissive.setHex(jammed ? 0xff2222 : 0x000000);
m.emissiveIntensity = jammed ? 0.6 + 0.4 * Math.sin(performance.now() / 150) : 0;
});
rollerMat.color.setHex(!s.online || stale ? 0x475569 : s.jam ? 0xdc2626 : 0x94a3b8);
txt.textContent = `${s.online ? "Online" : "Offline"} | ${shownSpeed.toFixed(2)} m/s | ` +
`${s.jam ? `JAM in zone ${s.jamZone}` : s.running ? "Running" : "Stopped"} | ` +
`count ${s.count} | ${s.current ?? "-"} A${stale ? " | no data" : ""}`;
renderer.render(scene, camera);
});
addEventListener("resize", () => {
camera.aspect = innerWidth / innerHeight; camera.updateProjectionMatrix();
renderer.setSize(innerWidth, innerHeight);
});
</script>
</body>
</html>
Simülatörü başlatın, sayfayı açın ve kolilerin 0,8 m/s hızla ilerleyişini izleyin. Bir iki dakika içinde bir sıkışma oluşur: bant yavaşlar, silindirler kırmızıya döner ve etkilenen bölge şeridi yanıp söner. Reset jam ve ardından Start butonuna tıklayın; hat yeniden hızlanır.
Hareketi Gerçek Birimlerle Yönetmek
İkizdeki en önemli satır rollerAngle -= (shownSpeed / ROLLER_R) * dt satırıdır. Sahne metre kullandığı ve silindir yarıçapı modelle eşleştiği için silindirlerin yüzey hızı koli hızına, o da ölçülen bant hızına eşittir. İzleyiciler, silindirlerin koliler için "fazla hızlı" döndüğünü bilinçaltında fark eder; bir ikizi güvenilir hissettiren şey fiziksel olarak doğru harekettir.
Hız yine de yumuşatılır, çünkü gerçek sürücüler kademeli olarak hızlanıp yavaşlar ve telemetri saniyede yalnızca iki kez gelir. Yumuşatma faktörü kısadır (yaklaşık altıda bir saniye), bu yüzden ani bir duruş yine duruş gibi görünür. Simülatörün de tıpkı bir PLC gibi sıkışmada bandı durdurduğuna dikkat edin: ikiz kolileri kendi kararıyla durdurmaz, hattın bildirdiğini gösterir.
Gerçek Hatlar İçin Konveyör Dijital İkizi Kontrol Listesi
Simülatörü gerçek PLC verisiyle değiştirdiğinizde şu noktaları kontrol edin:
- Hız kaynağı. Set değeri yerine sürücü geri beslemesini veya bir enkoderi tercih edin. Aşınmış bir tambur üzerinde kayan bir bant, komut verilenden daha yavaş çalışır ve ikiz bunu göstermelidir.
- Sıkışma tanımı. Sıkışma genellikle bir fotoselin, bir kolinin geçmesi için gereken süreden daha uzun kapalı kalmasıdır. Tüm tüketicilerin aynı fikirde olması için bunu tarayıcıda değil, PLC'de veya gateway'de uygulayın.
- Bölge numaralandırması. Bölge numaralarında kontrol mühendisiyle anlaşın ve bunları fiziksel olarak etiketleyin.
- Komut güvenliği. Bir konveyörün uzaktan başlatılması bir iş güvenliği meselesidir. Birçok tesis bir ikizden yalnızca durdur ve sıfırla komutlarına izin verir ya da yerel onay ister. Makinenin güvenlik sistemini asla devre dışı bırakmayın.
- Gateway. Etiketleri genellikle bir PLC gateway'i veya Node-RED akışı okur ve yukarıdaki JSON'u yayınlar; Node-RED ile Modbus yaygın bir yoldur.
- Güncelleme hızı. Görselleştirme için konveyör başına saniyede iki ile beş mesaj fazlasıyla yeterlidir. Daha yüksek hızlar görünür bir fayda olmadan yük ekler.
Sorun Giderme
Koliler hareket ediyor ama silindirler donmuş ya da titriyor görünüyor. Yüksek hızlarda karedeki silindir dönüşü, silindirin tam bir segmentine yaklaşabilir ve bu bir araba tekerleği (wagon-wheel) etkisi yaratır. Hareketin doğru algılanması için görünür bir şerit dokusu ekleyin veya segment sayısını azaltın.
Hat durduktan sonra ikiz hareket etmeye devam ediyor. Gateway'iniz büyük olasılıkla geri besleme yerine hız set değerini yayınlıyor ya da yayını tamamen durdurdu. Bayat veri kontrolü, mesaj gelmeyen üç saniyenin ardından hızı sıfırlar; durum satırında "no data" yazmasının nedeni budur.
Butonlar hiçbir şey yapmıyor. Simülatörün veya gateway'in tam olarak doğru cmd topic'ine abone olduğunu kontrol edin ve komutun ulaştığını doğrulamak için mosquitto_sub ile izleyin.
Sayfa bağlanıyor, ardından birkaç saniyede bir kopuyor. İki sekme veya iki izleyici aynı client ID'yi paylaşıyor. Örnekteki rastgele son ek bunu public broker'da önler; client ID'lerin verilen kimlik bilgilerine bağlı olduğu özel bir broker'da her izleyiciye kendi kimlik bilgilerini verin.
İkizi Genişletme
- Konveyörü farklı prefix'lerle tekrarlayıp birleştiriciler ve yönlendiriciler ekleyerek bütün bir hat oluşturun; IoT verisini bir 3D modele bağlama yazısındaki konfigürasyon odaklı bağlamalar bunu yönetilebilir kılar.
- Dijital ikiz paneli eğitiminde gösterildiği gibi 3D görünümün yanına saatlik verim, vardiya başına sıkışma sayısı ve kullanılabilirlik için KPI kutucukları ekleyin.
- Web tabanlı 3D dijital ikiz oluşturma yazısındaki yapıyı kullanarak kutuları gerçek konveyörünüzün glTF modeliyle değiştirin.
- Bir robot veya paletleyiciyle birleştirerek bir hücre oluşturun; bu, bir CNC tezgâhı, robot kol, fırın, tank ve pompanın yanında bir konveyör de içeren ConnectMQ'nun 3D dijital ikiz web demosundaki sahneye benzer.
Üretim Ortamında Çalıştırma
Hat verileri public bir broker üzerinden taşınmamalıdır. Kimlik bilgileri ve TLS içeren özel bir broker oluşturun, ardından mqtt.connect seçeneklerine username, password ve verilen clientId değerini ekleyin (YOUR_USERNAME, YOUR_PASSWORD, YOUR_CLIENT_ID) ve gateway'de TLS ile 8883 portunu kullanın. ConnectMQ gibi tam yönetilen bir MQTT hizmeti, tarayıcı ikizleri için 8084 portunda güvenli WebSocket dahil broker tarafını üstlenir; böylece ekibiniz PLC entegrasyonuna ve görselleştirmeye odaklanır.
ConnectMQ ile Başlayın
Konveyör dijital ikizinizi bugün public broker üzerinde oluşturun, ardından ConnectMQ'daki kendi özel broker'ınıza taşıyın. Kredi kartı gerektirmeyen ücretsiz Serverless plan 25 bağlantıya kadar destek verir ve uyarı günlüğüyle birlikte uyarı kuralları içerir; böylece bir sıkışma portalda bir eşik uyarısı da tetikleyebilir. app.connectmq.com adresinden ücretsiz broker'ınızı oluşturun. Önce denemeye devam etmek için public MQTT test broker'ını kullanın.
Sık Sorulan Sorular
Bir konveyör, dijital ikiz için telemetriyi ne sıklıkta yayınlamalıdır?
Saniyede iki ile beş mesaj genellikle yeterlidir. İkiz mesajlar arasında enterpolasyon yapar; bu yüzden daha yüksek hızlar az bir görsel fayda karşılığında ağ yükü ekler.
Sıkışma tespiti ikizde mi yoksa PLC'de mi yapılmalı?
PLC'de veya gateway'de. İkiz, aldığı sıkışma durumunu göstermelidir; böylece panellerden uyarılara kadar her sistem aynı olayı görür.
Konveyörü dijital ikizden kontrol edebilir miyim?
Teknik olarak evet; gateway'in PLC'ye ilettiği komutları yayınlayarak. Bunu yapmanız gerekip gerekmediği tesisinizin güvenlik kurallarına bağlıdır; durdur ve sıfırla yaygındır, uzaktan başlatma ise çoğu zaman yerel onay gerektirir.
Tarayıcıyı yavaşlatmadan çok sayıda konveyörü nasıl gösteririm?
Silindirler ve koliler için instanced mesh'ler kullanın, materyalleri paylaştırın ve bütün hat için wildcard abonelikli tek bir MQTT bağlantısı tutun.



