Uygulama Mimari Planı
Bu sayfa, bir IronFlock uygulamasını platformu amaçlandığı şekilde kullanacak biçimde nasıl oluşturacağınızı gösterir. Fikir tek cümleyle şöyle özetlenebilir: container’larınız her cihazda çalışır ve uygulamanın tüm durumunu uygulamanın tablolarında tutar, pano uygulamanın kullanıcı arayüzüdür ve ikisi hiçbir zaman birbiriyle doğrudan iletişim kurmaz. Bir cihazın kendi sunduğu bir web arayüzü kullanıcılara bir uzaktan erişim tüneli üzerinden ulaşır ve panonun içinde görünür.
Tek tek parçalar kendi sayfalarında ayrıntılı olarak açıklanmıştır. Bu sayfa ise bunların nasıl bir araya geldiğini gösterir.
Bir Uygulamanın Parçaları
Bir uygulama aynı anda birçok cihazda çalışır; bunlar genellikle makinelerin yanındaki endüstriyel PC’lerdir. Uygulama şu parçalardan oluşur:
| Parça | Nedir | Tanımlandığı yer |
|---|---|---|
| Edge kodu | Uygulamanın kurulu olduğu her cihazda bir container veya bir Docker Compose yığını | Dockerfile veya docker-compose.yml |
| SDK | Kodunuz ile platform arasındaki tek bağlantı: tablolar, gerçek zamanlı güncellemeler, cihaz fonksiyonları, dosyalar | IronFlock SDK (Python, JavaScript, Go) |
| Tablolar | Projenin kendi veritabanında tutulan uygulama durumu | data-template.yml |
| Pano | Tablolara canlı olarak bağlı, tüm cihazlar için uygulamanın merkezi kullanıcı arayüzü | board-template.yml |
| Parametreler | Bir cihazın veya bir cihaz grubunun ayarları | env-template.yml |
| Uzaktan erişim | Container’larınızın sunduğu portlara, örneğin bir web arayüzüne giden tüneller | port-template.yml |
| AI ajanları | Cihaz fonksiyonlarınızı araç olarak kullanan, IronFlock asistanı için ajanlar | ai-template.yml |
Bir kullanıcı uygulamanızı bir projeye kurduğunda IronFlock, uygulamanın tablolarını o projenin veritabanında oluşturur, panosunu kurar ve uygulamanın tüm cihazlardaki container’larını, tablolarını ve panosunu gerçek zamanlı olarak birbirine bağlayan özel bir mesajlaşma alanı (realm) açar. SDK bu alana cihaz ajanının enjekte ettiği kimlikle katılır; bu yüzden kodunuzun bir adrese veya kimlik bilgilerine ihtiyacı yoktur.
Kurallar
- Tablolar uygulamanın durumudur. Panonun, başka bir cihazın, yeniden başlatılan bir container’ın veya bir AI ajanının ihtiyaç duyduğu her şey bir tabloda saklanır. Bir container yalnızca tablolardan yeniden oluşturabileceği şeyleri tutar, örneğin ayarlarının bellekteki bir kopyasını.
- Parçalar birbirleriyle değil, tablolar aracılığıyla iletişim kurar. Bir cihaz ölçtüklerini ve içinde bulunduğu durumu yazar, pano da bunları gösterir. Pano kullanıcıların kararlarını yazar, cihazlar da bunları uygular. İkisi de birbirini çağırmaz; böylece cihazlar çevrimdışıyken de pano çalışır, panoyu kimse açmamışken de cihazlar çalışmaya devam eder.
- Her tabloya tek bir taraf yazar. Bir tabloya ya cihazlar (her biri kendisi hakkında), ya pano ya da tek bir servis yazar. Hangisinin yazdığını tablonun
descriptionalanında belirtin. Aynı satırlara iki taraf yazarsa birbirlerinin değişikliklerinin üzerine yazarlar. - Komutlar değil, istenen durum. Cihazların yaptığı şeyi değiştirmek için hedefi bir tabloya yazın (bir sınır değer, bir set değeri, bir reçete, bir zaman çizelgesi) ve her cihazın en yeni satırı uygulamasını sağlayın. Çevrimdışı olan bir cihaz geri geldiğinde bu satırı uygular; satırı iki kez uygulamak hiçbir şeyi değiştirmez.
- Değişince yazın, canlı izleyin, asla yoklama yapmayın. Bir varlığın satırını değerlerinden biri değiştiğinde, ölçümleri ise örnekleme hızlarında yazın. Okuyan her şey tabloları gerçek zamanlı olarak izler: panonun bağlamaları, cihazlardaki tablo abonelikleri, bir cihazın web arayüzündeki websocket’ler. Bir tabloyu yeniden okuyan bir zamanlayıcı her zaman yanlış bir tasarımdır.
- Kullanıcı arayüzü panodur; bir cihazın web arayüzü onu tamamlar. Bir cihazdan yalnızca bir panonun gösteremeyeceği şeyler için web sayfası sunun: bir makinenin kendi HMI’si, video, yüksek hızlı bir canlı görünüm, makinenin başındaki bir operatör ekranı. Pano bu sayfayı gömer.
Hangi Veri Nereye Gider
| Veri | Nerede | Yazan | Okuyan |
|---|---|---|---|
| Zaman içindeki ölçümler | tsp içeren bir tablo | publishToTable, publishRowsToTable | Zaman pencereli ve toplulaştırmalı grafikler |
| Bir cihazın, makinenin veya üretim emrinin mevcut durumu | Bir varlık tablosu (maintainLatestFlagFor) | appendToTable: anahtarı, tsp değerini ve değişen sütunları içeren bir satır | latest filtresi |
| Kullanıcıların belirlediği sınır değerler, set değerleri, reçeteler | Panodaki bir formun yazdığı bir tablo | Form | Aşağıda gösterildiği gibi her cihaz |
| Kullanıcıların oluşturduğu, düzenlediği ve sildiği kayıtlar | deleted sütunu olan bir varlık tablosu | Silme sırasında işareti ayarlayan form | Panodaki listeler ve düzenleme sayfaları |
| Operatörler için hatalar ve bildirimler | error-logs tablosu | userMessage ile birlikte reportError | Panonun bildirim (toast) widget’ı |
| Görseller, belgeler, ses ve video kayıtları | Uygulamanın dosya depolaması | files.put | Bir sütunda saklanan ve bir widget’ın gösterdiği dosya URL’si |
| Parolalar ve API anahtarları | secret: true olan bir sütun | Herhangi bir tablo yazma işlemi | Yalnızca uygulamanın kendi container’larında revealSecrets, verifySecret |
| Tek bir cihazın kurulumu: bir makinenin adresi, bir seri port | Uygulama parametreleri | Cihazın veya grubun uygulama ayarları | Ortam değişkenleri |
| Parçalarınız arasında, sonradan kimsenin ihtiyaç duymadığı mesajlar | Kendi konularınız | publish | subscribe |
| Tek bir cihazdan hemen yanıt gerektiren bir istek | Bir cihaz fonksiyonu | registerDeviceFunction | callDeviceFunction, widget eylemleri, AI araçları |
Ekleme mi, yayımlama mı? appendToTable yeniden başlatılmakta olan bir veri arka ucunu bekler ve veri arka ucu bir satırı reddettiğinde bunu size bildirir. publishToTable yalnızca bağlantıyı bekler; bu yüzden veri arka ucu yeniden başlarken yayımlanan bir satır kaybolur. Durum değişikliklerini ve kayıtları ekleyin; yüksek frekanslı ölçümleri yayımlayın.
Parametreler mi, bir ayarlar tablosu mu? Parametreler bir cihaza veya bir cihaz grubuna aittir: cihazı kuran ya da yapılandıran kişi bunları uygulama ayarlarında belirler, kodunuz da bunları ortam değişkenlerinden okur. Parametreleri bir kurulumdan diğerine değişen şeyler için kullanın. Kullanıcıların uygulama çalışırken değiştirdiği şeyler — sınır değerler, reçeteler, zaman çizelgeleri, birçok cihaz için geçerli olan her şey — panonun yazdığı bir tabloya aittir. Kullanıcılar uygulamayla panoda çalışır, her cihaz bir değişikliği anında uygular ve tablo, kimin neyi ne zaman değiştirdiğinin geçmişini tutar.
Panonun Yazdıklarını İzleme
Bir cihaz, panoda belirlenen bir sınır değer gibi panonun yazdığı durumu üç adımda uygular:
- Okuma ile abonelik arasında hiçbir satırın kaybolmaması için tabloyu okumadan önce ona abone olun.
- Başlangıçta
latestfiltresiyle en yeni satırı bir kez okuyun ve uygulayın. - Bir satırı yalnızca yürürlükteki satırdan daha yeniyse uygulayın (
tspdeğerine göre). Programınız başlarken tam o anda kaydedilen bir satır hem okumadan hem de abonelikten gelebilir.
Python
await ironflock.subscribe_to_table("settings", lambda row, *args, **kwargs: apply(row))
rows = await ironflock.getHistory("settings", {"limit": 1, "filterAnd": [{"latest": True}]})
for row in rows or []:
apply(row)Hepsi bu kadar; çevrimdışı kalan cihazlar için de. Bağlantıyı SDK yönetir, kodunuzun bununla uğraşması gerekmez: SDK yeniden bağlanır, aboneliklerinizi ve cihaz fonksiyonlarınızı geri yükler, ardından cihaz çevrimdışıyken saklanan satırları her tablo aboneliğine, canlı gelen satırlardan önce ve en eskiden başlayarak teslim eder. Bir cihaz çevrimdışıyken kaydedilen bir sınır değer, cihaz geri döner dönmez ona ulaşır. Kaçırılan satırların teslimi tsp değerine göre yapılır: işleyicinizin aldığı en yeni satırdan daha yeni satırlar, en fazla en yeni 10.000 satır olmak üzere teslim edilir. Bu teslim için yapılan okuma, her geçmiş okuması gibi en fazla yeniden bağlanma penceresi süresince veri arka ucunu bekler; okuma başarısız olursa bu satırlar teslim edilmez ve günlüğe bir uyarı kaydedilir. Kaçırılan satırların teslimi SDK’da 1.10.0 sürümünden itibaren bulunur.
Komutlar, Cihaz Fonksiyonları ve Konular
Durumu tercih edin. Kullanıcıların “komut” olarak verdiği şeylerin çoğu aslında istenen durumdur: bir set değeri, açık ya da kapalı, bir reçete. Bunları yukarıdaki gibi bir tabloya yazın.
Etiket yazdırmak ya da bir temizleme döngüsü başlatmak gibi yalnızca bir kez çalışması gereken komutlar, panonun yazdığı bir tabloda kimliği (id) olan satırlardır. Cihaz, yerine getirdiği komutların kimliklerini kendine ait bir tabloya kaydeder; böylece iki kez aldığı bir komut yalnızca bir kez çalışır.
Cihaz fonksiyonları, bir cihazın registerDeviceFunction ile kaydettiği prosedürlerdir. Çağıranın cihazın yanıtına hemen ihtiyaç duyduğu durumlarda bunları kullanın:
- AI ajanları:
ai-template.ymliçindeki bir araç, fonksiyonu asistanın seçtiği cihazda çağırır. Bkz. AI Ajanları & Araçları. - Panodaki Anahtar ve SCADA widget’ları, cihaz çevrimiçiyken bunları çağırır.
- Kendi container’larınız ve cihazlarınız birbirlerini
callDeviceFunctionile çağırır.
Bir cihaz fonksiyonundan okunabilir bir metin veya küçük bir JSON döndürün; beklenen bir başarısızlığı (örneğin kapalı olan bir makineyi) hata fırlatmak yerine bir sonuç olarak döndürün.
Konular, kendi parçalarınız arasında sonradan kimsenin ihtiyaç duymadığı mesajları taşır: myapp.events gibi kendi ad alanınızdaki adlarla publish ve subscribe kullanırsınız. Pano konulara abone olamaz: gösterdiği her şey bir tabloda olmalıdır. Bir cihaz çevrimdışıyken yayımlanan mesajlar saklanmaz; bu yüzden ulaşması gereken her şey için bir tablo kullanın.
Cihazdaki Container
Cihaz ajanının container’ınıza sağladıkları ve ondan bekledikleri:
- Kimlik: ajan
DEVICE_KEY,DEVICE_NAME,DEVICE_SERIAL_NUMBER,SWARM_KEY,APP_KEY,APP_NAME,ENV(DEVveyaPROD),TUNNEL_DOMAIN,DEVICE_LAN_IPveDEVICE_PORT_FOR_<port>değişkenlerini, ayrıca SDK için uygulamanın kendi kimlik bilgisini ayarlar. SDK ihtiyaç duyduklarını okur; bu değerlerin hiçbirini sabit kodlamayın. - Parametreler: bir üretim kurulumu kullanıcıların ayarladığı değerleri, geri kalanlar için ise her parametrenin
defaultValuedeğerini alır. Geliştirme modunda çalışan bir uygulama yalnızca varsayılan değerleri alır. Parametrelere belirgin adlar verin (PLC_HOSTgibi;HOSTdeğil). - Depolama: tek imajlı bir uygulama, uygulamaya özel olan ve yeniden başlatmalar ile güncellemeler boyunca korunan
/datadizinini (uygulamayla birlikte silinir) ve cihazdaki tüm uygulamaların paylaştığı/shareddizinini alır. Yine de uygulamanın durumunu tablolarda tutun; bu klasörleri indirilmiş bir model veya bir önbellek gibi cihaza ait dosyalar için kullanın. - Donanım: tek imajlı bir container, cihazın
/devdizinine erişimle ayrıcalıklı (privileged) modda çalışır. - Günlükler: programınızın stdout ve stderr akışlarına yazdığı her şey, IronFlock’taki uygulama günlüğüdür. Çıktıyı arabelleğe almadan yazın (
python -u). - Başlatma: container, başladıktan 30 saniye sonra çalışıyor olmalıdır; aksi hâlde başlatma başarısız sayılır.
- Çökmeler: bir üretim uygulaması sonlandığında ajan onu yeniden başlatır ve her başarısızlıktan sonra daha uzun bekler. Geliştirme modunda çalışan bir uygulama yeniden başlatılmaz. Yanıt vermeyen bir sensör gibi beklenen başarısızlıkları programınızda ele alın ve programın, kurtaramayacağı hatalarda sonlanmasına izin verin.
- Durdurma: programınız container’ın ana sürecidir (PID 1) ve PID 1, durdurma sinyali SIGTERM’i kendisi ele almadıkça yok sayar. Programınızı SIGTERM geldiğinde sonlanacak şekilde yazın; aksi hâlde uygulamayı durdurmak, Docker programı zorla sonlandırana kadar bekler. Programı bir kabuk üzerinden değil, exec biçiminde başlatın (
CMD ["python", "-u", "main.py"]). - Geliştirme modu: uygulamayı Cloud IDE’den çalıştırdığınızda uygulama geliştirme modunda çalışır (
ENVdeğeriDEVolur). Bu modda uygulama kendi geliştirme tablolarına yazar, parametrelerin varsayılan değerlerini alır ve uzaktan erişim tüneli almaz.
Docker Compose
Uygulamanız birden fazla süreç çalıştırıyorsa ya da kendi kodunuzun yanında bir MQTT broker’ı veya bir veritabanı gibi üçüncü taraf servisleri de birlikte paketliyorsa bir docker-compose.yml kullanın. Temel bilgiler için bkz. Docker & Compose. Tek bir imajla karşılaştırıldığında farklar şunlardır:
- Her servis enjekte edilen ortamı alır. Ajan her servise yukarıdaki değişkenleri içeren kendi ortam dosyasını verir; sizin yazdığınız bir
env_file:bununla değiştirilir. Dosyadaki bir${VAR}, enjekte edilen değerlere göre çözümlenir. /datayoktur. Servisler bir uygulama klasörü almaz. Dosyaları adlandırılmış volume’lerde tutun: bunlar durdurma ve güncellemelerden sonra da korunur, uygulama cihazdan kaldırıldığında ise silinir.- Yayımlanan portlar yeniden eşlenir. Yayımlanan her port, cihazın tüm arayüzlerinde boş bir ana makine portu alır; gerçek portu kodunuza
DEVICE_PORT_FOR_<port>bildirir.port-template.ymliçinde ana makine portunu compose dosyasında yazıldığı şekliyle kullanın. Yalnızca kullanıcıların erişmesi gerekenleri yayımlayın; servislerinizin birbirleriyle compose ağı üzerinden servis adıyla konuşmasını sağlayın.${VAR}olarak yazılmış bir ana makine portu yönetilmez: yeniden eşlenmez ve tünel almaz. - Hiçbir şey eklenmez. Ajan compose servislerine hiçbir ayrıcalık ve hiçbir cihaz vermez.
privileged,devicesveyadevice_cgroup_rulesayarlarını kendiniz bildirin. Birdevices:girdisi var olması gereken bir donanımı belirtir; donanım yoksa container başlamaz.device_cgroup_rulesise bir/devvolume bağlamasıyla birlikte her cihazda çalışır. profiles:yoktur. Profilleri olan bir servis sürüme dahil edilmez.- Aynı başlatma kuralı geçerlidir. 30 saniye içinde her container çalışıyor ya da 0 koduyla sonlanmış olmalı ve en az biri çalışıyor olmalıdır. Uzun süre çalışan servislere
restart: unless-stoppedverin.
Cihazdaki Web Arayüzleri
Container’ınızdaki bir web sunucusu, portunun uzaktan erişim tüneli üzerinden her yerden erişilebilir hâle gelir ve pano onu gösterir:
- Sabit bir portta sunun (
0.0.0.0adresine bağlı olarak) ve bu portuport-template.ymliçindeprotocol: httpile bildirin. Bir üretim kurulumu tüneli açar ve sayfayı websocket’ler dahil HTTPS üzerinden sunar. - Tünelin URL’sini bir tabloya yazın. URL’yi SDK’nın
getRemoteAccessUrlForPort(port)metodu verir. Başlangıçta bu URL’yi bir cihaz tablosunda cihazın satırına yazın. Yalnızca üretim kurulumlarının tünelleri vardır; bu yüzden geliştirme modunda çalışan bir uygulama URL yazmaz. - Panoya gömün. Cihaza göre filtreleyen, cihaz başına bir sayfada Gömülü widget’ın URL’sini bu sütuna bağlayın. UI Builder’da bu, diğerleri gibi sıradan bir veri bağlamasıdır.
Sayfanın kendisi için:
- Kimler açabilir: bir http tüneli özeldir. Bir kullanıcı portu genel olarak ayarlamadıkça sayfayı yalnızca IronFlock’ta oturum açmış ve cihaza erişimi olan kişiler açabilir. Cihazın kendi ağında ise sayfa herkese açıktır; bu yüzden orada tesis ağının görmemesi gereken hiçbir şeyi göstermeyin ve makinenin durumunu değiştiren her şey için kendinize ait bir oturum açma adımını zorunlu kılın.
- Göreli URL’ler: statik dosyaları yüklemek, API’nizi çağırmak ve websocket açmak için sayfaya göreli URL’ler kullanın; böylece sayfa cihazın ağında, tünel üzerinden ve bir ters proxy arkasında çalışır.
- İtin, asla yoklama yapmayın: güncellemeleri bir websocket üzerinden gönderin.
- Tarayıcıda platform kimlik bilgisi olmaz: sayfa container’daki kendi sunucusuyla konuşur ve SDK bağlantısını o sunucu tutar.
Kendi sayfanız yerine tesis ağındaki bir makinenin web HMI’sini göstermek için bu HMI’yi container’ınızdan ters proxy ile sunun. Bkz. Harici HMI’leri Gömme.
Birden Fazla Cihaz
- Her satır kendi cihazını taşır: IronFlock, bir satırı yazan cihazın anahtarını
device_keyiçinde saklar. Cihaz başına tablolardadevice_keysütununu anahtar olarak kullanın, cihazın adınıpath: kwargs.DEVICE_NAMEile bir sütunda saklayın ve grafikleri bu ada göre bölün. - Bir cihaz listesi ve cihaz başına bir sayfa: cihaz tablosundan oluşturulan bir yan gezinme ve her cihaz için çalışan, widget’larını rotadaki cihaza göre filtreleyen tek bir sayfa. Bkz. Pano Oluşturma.
- Cihaz veya makine başına ayarlar: ayarlar tablosunda hedefi adlandıran bir sütunu anahtar olarak kullanın (panonun yazdığı satırların kendilerine ait bir cihazı yoktur) ve bu sütunu hedefin sayfasındaki formda doldurun. Her cihaz kendi anahtarı için en yeni satırı uygular.
- Ayrılan bir cihazın satırları kalır. Kullanıcıların onu listelerden kaldırabilmesi gerekiyorsa cihaz tablosuna bir
deletedsütunu, panoya da bu sütunu ayarlayan bir form ekleyin.
Kontrol Listesi
- Tüm durum tablolardadır; her tablonun tek yazan tarafı açıklamasında belirtilmiştir ve kodun yazdığı her sütun
data-template.ymliçinde bildirilmiştir. - Her satırın bir
tspdeğeri vardır ve durum satırları, değişiklik olduğunda yalnızca anahtar ve değişen sütunlarla yazılır. - Panonun yazdığı durum, SDK 1.10.0 veya sonrasıyla yukarıdaki üç adımda izlenir ve kod hiçbir zaman bağlantıyla veya oturumlarıyla uğraşmaz.
- Uzun süre çalışan döngülerdeki tablo yazmaları, platforma bir dakika boyunca ulaşılamadığında bir yazma işleminin fırlattığı hatayı ele alır; beklenen başarısızlıklar
reportErrorve biruserMessageile raporlanır. - Program SIGTERM geldiğinde sonlanır, exec biçiminde başlar ve günlüklerini arabelleğe almadan yazar.
- Hiçbir şey yoklama yapmaz: pano bağlanır, cihazlar abone olur, web arayüzleri iter.
- Uygulamanızın sunduğu her port
port-template.ymliçinde bildirilmiştir; portun URL’sigetRemoteAccessUrlForPortile alınıp bir tabloya yazılır ve pano onu oradan gömer. - Bir compose uygulamasında
env_file:,profiles:ve${VAR}biçiminde ana makine portları yoktur; dosyalar adlandırılmış volume’lerde tutulur ve yalnızca kullanıcıların ihtiyaç duyduğu portlar yayımlanır.
Başlangıç Uygulamasıyla Başlayın
Claude Code için IronFlock eklentisi, bu mimari plana uyan bir başlangıç uygulaması içerir: her cihazda bir makineyi okuyan, her cihazın durumunu ve ölçümlerini tablolarda tutan, kullanıcıların panoda belirlediği sınır değeri uygulayan ve panonun her cihazın sayfasına gömdüğü canlı bir görünüm sunan bir program. Claude Code bundan yeni bir uygulama oluşturur ve onu sizin kullanım senaryonuza uyarlar.