İçindekiler
Problem
Factory I/O ve TIA Portal harika araçlar — ama ikisi de Windows, lisans ve kurulum istiyor. Bir öğrencinin ya da işe dönen bir mühendisin "hemen şimdi ladder deneyeyim" demesinin önünde ciddi bir sürtünme var.
Hedefim netti: tarayıcıda açılan, kurulum istemeyen, ama oyuncak olmayan bir öğrenme aracı. "Oyuncak olmamak" burada kritik — çünkü asıl mesele ladder sembollerini çizebilmek değil, PLC'nin nasıl düşündüğünü kavramak: tarama döngüsü, mühürleme, kenar tetikleme, el sıkışma sinyalleri, güvenli duruş.
Bunun için tek bir tasarım kararı her şeyi belirledi:
Saha ölü doğar. Konveyör dönmez, robot kıpırdamaz, hiçbir şey çalışmaz — ta ki kullanıcının yazdığı ladder programı çıkışları sürene kadar. Sahne bir animasyon değil, programın sonucudur. Yanlış mantık yazarsan kutular yanlış yere gider ve bunu 3B olarak görürsün.
Mimari
Sistem üç katmana ayrıldı ve bu katmanlar birbirini yalnızca tag tablosu üzerinden görür. Gerçek bir tesiste PLC ile saha arasındaki tek köprü de I/O kartlarıdır — mimariyi kasten buna benzettim.
Bu ayrımın pratik kazancı büyük oldu: yeni bir senaryo eklemek için tarama motoruna hiç dokunmadım. Trafik ışığı, dolum istasyonu ve arıza senaryosu — üçü de sadece yeni sahne kurucuları ve yeni tag'ler. Motor aynı kaldı.
Ladder Tarama Motoru
Bir PLC saniyede yüzlerce kez aynı şeyi yapar: girişleri oku → mantığı yukarıdan aşağı çöz → çıkışları yaz. Motorun tamamı bu döngüyü taklit eder.
Veri modeli kasten yalın tutuldu. Bir network (rung), paralel dalların listesi ve bir bobinden oluşur. Dallar arasında OR, dal içinde AND vardır — ladder mantığının tamamı budur:
rung = {
branches: [ // dallar arası: OR
[ {k:'NO', tag:'I1.0'}, {k:'NC', tag:'I1.1'} ], // dal içi: AND
[ {k:'NO', tag:'M0.0'}, {k:'NC', tag:'I1.1'} ] // ← mühürleme dalı
],
coil: {k:'OUT', tag:'M0.0'}
}
Yukarıdaki tek network, elektrik panolarındaki klasik seal-in (mühürleme) devresidir: START'a basınca birinci dal iletir, bobin çeker; parmağını çekince ikinci dal (bobinin kendi kontağı) devreyi ayakta tutar. STOP her iki dalı da keser.
Tarama fonksiyonunun özü:
for (const rung of program) {
let rungPow = false;
for (const br of rung.branches) {
let pow = true;
for (const el of br) {
const v = tagVal(el.tag);
if (el.k === 'NO') pow = pow && v;
if (el.k === 'NC') pow = pow && !v;
if (el.k === 'P') { // yükselen kenar
const fire = v && !el._pv; // yalnızca 0→1 anında iletir
el._pv = v;
pow = pow && fire;
}
// TON / TOF: dt burada tüketilir
}
rungPow = rungPow || pow; // dallar OR'lanır
}
writeCoil(rung.coil, rungPow);
}
Bir kutu fotoseli 0,4 saniye kesiyorsa ve tarama 20 ms'de bir dönüyorsa, düz bir kontakla sayaç 20 kez artar. Öğrencinin bunu kendi gözüyle görmesi, "P kontağı"nı anlatan on slayttan daha etkili. Görev 2 tam olarak bu tuzağın üzerine kuruldu.
Bulduğum Hata: Bobin Ne Zaman Yazılır?
Bu, projenin en öğretici anıydı — ve simülatörün doğruluğunu doğrudan ilgilendiren gerçek bir mühendislik hatasıydı.
İlk yazdığım motorda OUT bobinleri bir ara tabloda biriktiriliyor, tarama bittikten sonra tag tablosuna işleniyordu. "Zaten aynı kare içinde, ne fark eder" diye düşünmüştüm. Ta ki çift robot senaryosunu test edene kadar.
Görev 8'de iki robot aynı paleti paylaşıyor. Çarpışmayı önlemek için klasik interlock yazılır — ikinci robotun başlama şartına "birincinin komutu aktif DEĞİL" koşulu eklenir:
// Network 6
I0.3 · I2.0 · /I2.3 ──────────( Q1.0 ) // Tomris-1 çevrim başlat
// Network 7
I0.3 · I2.2 · /I2.1 · /Q1.0 ───( Q1.1 ) // Tomris-2 — Q1.0 kilidi
Testte iki robot da aynı anda başladı. Sebep: Network 7, /Q1.0 kontağını okurken Q1.0 henüz tag tablosuna yazılmamıştı — bir önceki taramanın değerini (false) görüyordu. Kilit hiçbir zaman tutmadı.
Gerçek bir PLC'de durum böyle değildir. Süreç görüntüsü (process image) girişte bir kez okunur ve çıkışta bir kez yazılır — ama program içinde bir bobin çektiği anda o bit güncellenir ve programın devamındaki kontaklar yeni değeri görür. Ladder'ın yukarıdan aşağı, sıralı yürütülmesinin bütün anlamı budur. Benim motorum bu semantiği ihlal ediyordu.
Bobinleri yazıldıkları anda tag tablosuna işledim. Aynı tag'e birden fazla OUT bobini konursa (çift bobin hatası) bunları OR'layarak yeni başlayanlara tolerans bıraktım — ama sıralı görünürlük artık gerçek PLC ile birebir aynı.
const written = {}; // bu taramada yazılan tag'ler
if (coil.k === 'OUT') {
if (!written[coil.tag]) {
TAGS[coil.tag].v = rungPow; // ANINDA yaz →
written[coil.tag] = true; // sonraki network'ler görür
} else {
TAGS[coil.tag].v = TAGS[coil.tag].v || rungPow; // çift bobin → OR
}
}
Bu düzeltmeden sonra interlock ilk denemede çalıştı ve iki robot T-1 → T-2 → T-1 → T-2 düzeninde sırayla devretmeye başladı. Aynı testte ikinci bir hata da yakalandı: trafik ışığı sekansı zinciri sıfırlarken bir tarama boyunca tüm lambaları söndürüyordu. Kırmızı bobinine paralel bir dal eklenerek giderildi.
Saha: Fizik ve Sensör Modeli
Saha katmanı, tag tablosundaki Q bitlerini okur; kutuları hareket ettirir; sonucu I bitleri olarak geri yazar. Arada başka hiçbir bağ yoktur.
Sensör modeli kasten fiziksel tutuldu — "kutu şu koordinata gelince bit 1 olsun" değil, "ışın demeti kesildi mi?" sorusu soruluyor:
// Fotosel: ışın demetinin üzerinde bir kutu var mı?
TAGS['I0.0'].v = boxes.some(b => aktif(b) && yakin(b.x, gateX, 0.34));
// Yükseklik fotoseli: yalnızca UZUN kutular demeti keser
TAGS['I0.1'].v = boxes.some(b => aktif(b) && b.tall && yakin(b.x, gateX, 0.34));
Bu yaklaşımın bedeli var — her karede kutu listesi taranıyor — ama kazancı çok daha büyük: sinyal süresi kutunun hızına ve boyuna bağlı hale geliyor. Bant hızlanınca sinyal kısalıyor, kenar tetikleme problemi kendiliğinden doğuyor. Bu, yapay olarak üretilemeyecek bir öğrenme fırsatı.
Senaryo çeşitliliği
| Senaryo | Öğrettiği kavram | Ceza mekanizması |
|---|---|---|
| Mühürleme | Seal-in, paralel dal, latch | — |
| Sayıcı | P kenarı, CTU, parti kontrolü | 5. kutu geçerse hata |
| Robot el sıkışma | Hazır/meşgul/başlat protokolü | — |
| Ayırma hücresi | Bellek biti, TON, entegrasyon | Yanlış kasa = hata |
| Trafik ışığı | Zincirleme TON sekansı | Kırmızıda geçiş = hata |
| Dolum istasyonu | Seviye kontrolü, chattering önleme | Tank taşması = hata |
| Arıza yönetimi | Alarm biti, güvenli duruş | 3 sn içinde durmazsan ihlal |
| Çift robot | Interlock, sıra biti | Çarpışma = hata |
Her senaryoda bir ceza mekanizması olması tesadüf değil. Hatasız çalışan bir simülasyon, öğrenciye yanlış yaptığını asla söylemez. Kutunun yanlış kasaya düştüğünü görmek, kırmızı bir hata mesajından çok daha kalıcıdır.
Tomris: Kinematik ve Hareket Kalitesi
Robot ilk versiyonda "çalışıyordu" ama bir çizgi filmden fırlamış gibiydi. Üç müdahale onu inandırıcı kıldı.
1 · Bilek telafisi
İlk sürümde kol eğildikçe tutucu da eğiliyordu — gerçek bir 6 eksen robotta bu asla olmaz, çünkü bilek eksenleri uç efektörü sabit tutar. Tek satırlık bir telafi, sahnenin inandırıcılığını tamamen değiştirdi:
// tutucu, omuz ve dirsek ne yaparsa yapsın dik kalır
wrist.rotation.z = pose.sh - pose.el;
2 · Hareket profili
Lineer interpolasyon, servo motorların yapamayacağı ani hız değişimleri üretiyordu. Quintic smootherstep ile ivme profili yumuşatıldı — hareket yavaş başlıyor, hızlanıyor, hedefe yaklaşırken yavaşlıyor:
const smooth = k => k*k*k*(k*(k*6 - 15) + 10);
// birinci VE ikinci türevi uçlarda sıfır → sıçramasız ivme
3 · Boşta nefes alma
Bekleyen robot tamamen donuk duruyordu. Servo tutuş salınımını taklit eden çok küçük bir sinüs hareketi (~0,02 radyan) eklendi. Fark eden olmuyor — ama olmadığında herkes fark ediyor.
Sonradan eklenen Jog modu, robotu eksen eksen elle sürmeyi sağlıyor ve o sırada PLC komutlarını bloke ediyor (I2.0 = 0) — gerçek bir robot manuel moddayken tam olarak böyle davranır. Teach modu ise elle konumlandırılan noktaları kaydedip PTP bir program olarak oynatıyor. Endüstriyel robot programlamanın temeli budur ve tarayıcıda deneyimlenebilir hale geldi.
Test: Örnek Çözümleri Gerçekten Çalıştırmak
Bir eğitim aracının en utanç verici hatası, "örnek çözüm" düğmesinin çalışmayan bir program yüklemesidir. Bunu garanti altına almanın tek yolu var: örnekleri test etmek.
Ladder motorunu 3B sahneden bağımsız olarak Node.js altında başsız (headless) çalıştıran bir test koşucusu yazdım. Her görevin örnek çözümü yükleniyor, sanal saniyeler ilerletiliyor ve çıkışlar doğrulanıyor:
=== GÖREV 5 TRAFİK SEKANS TESTİ ===
0.0s -> K // kırmızı
4.9s -> Y // yeşil
10.0s -> S // sarı
11.9s -> K // döngü kapandı, karanlık kare yok ✓
=== GÖREV 7 ARIZA TESTİ ===
Normal çalışma -> Q0.0(bant): true | Q1.2(korna): false
ARIZA geldi -> Q0.0(bant): false | Q1.2(korna): true
Sonuç: GÜVENLİ DURUŞ ✓
=== GÖREV 8 SIRALI DEVRETME TESTİ ===
Çevrim sırası: T-1 → T-2 → T-1 → T-2 → T-1 → T-2
Sonuç: Robotlar sırayla çalışıyor, çarpışma yok ✓
Yukarıda anlattığım bobin yazma hatası da, trafik ışığındaki karanlık kare de bu test tarafından yakalandı — ikisi de gözle bakarak fark edilebilecek şeyler değildi. Test yazmasaydım simülatör "çalışıyor gibi" görünecek, ama yanlış bir PLC modeli öğretecekti.
Otomasyonda devreye alma (commissioning) da böyledir: sistem "dönüyor" diye doğru çalıştığı sonucu çıkmaz. Kenar durumlarını kasten tetikleyip beklenen çıkışı doğrulamak gerekir. Simülatörü test etme biçimim, bir sahayı devreye alma biçimimin aynısıydı.
Sonuç
Ortaya çıkan şey, tek bir HTML dosyasında çalışan; ladder yazabildiğin, HMI ekranı tasarlayabildiğin, robotu elle sürüp nokta öğretebildiğin ve OEE'sini izleyebildiğin bir eğitim hücresi oldu. Hiçbir kurulum, lisans veya işletim sistemi bağımlılığı yok.
Kullanılan teknolojiler
Bundan sonrası
- SCL / ST editörü — ladder'ın yanına yapılandırılmış metin; TIA Portal'daki asıl profesyonel yol.
- Analog sinyaller — 0–27648 skalası ve gerçek bir PID sıcaklık kontrol görevi.
- Çapraz referans tablosu — bir tag'e tıkla, hangi network'lerde kullanıldığını gör.
Otomasyon alanına dönerken, "TIA Portal biliyorum" demenin kanıtı bir sertifika değil, çalışan bir sistem olmalı diye düşündüm. Bu simülatör bir PLC'nin nasıl düşündüğünü baştan inşa etmemi gerektirdi — tarama döngüsü, süreç görüntüsü, kenar tetikleme, el sıkışma protokolleri, güvenli duruş. Bir aracı kullanmayı öğrenmenin en iyi yolu, o aracın bir benzerini yazmakmış.
Simülatörü kendin dene
Kurulum yok, kayıt yok. Tarayıcında açılır, ilerlemen kaydedilir.