Dijital İkiz

Konveyör Dijital İkizi Eğitimi: MQTT ile Canlı Hız ve Sıkışma Tespiti

Silindirleri ve kolileri MQTT'den gelen gerçek bant hızıyla hareket eden, sıkışmaları vurgulayan, verimi sayan ve hatta başlat, durdur ve sıfırla komutları gönderen tarayıcı tabanlı bir konveyör dijital ikizi oluşturun.

Canlı MQTT verisiyle yönetilen silindirler, koliler ve kırmızı sıkışma uyarısı içeren konveyör dijital ikizi
Bunu kendi MQTT broker’ınızda çalıştırınFree planla özel, yönetilen bir MQTT broker alın ve hazır kod örnekleriyle başlayın.
Ücretsiz deneyin Kod örnekleri Kart gerekmez

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:

  1. 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.
  2. 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.
  3. Bölge numaralandırması. Bölge numaralarında kontrol mühendisiyle anlaşın ve bunları fiziksel olarak etiketleyin.
  4. 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.
  5. 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.
  6. 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.

İlgili yazılar

Tüm yazılar →

Özel MQTT broker’ınız birkaç tık uzağınızda

Hesabınızı oluşturun, broker kimlik bilgilerinizi alın ve ilk cihazınızı bağlayın — kurulacak, yamalanacak ya da ölçeklendirilecek sunucu yok.