Skip to Content
IoT Uygulama GeliştirmeUygulama Mimari Planı

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çaNedirTanımlandığı yer
Edge koduUygulamanın kurulu olduğu her cihazda bir container veya bir Docker Compose yığınıDockerfile veya docker-compose.yml
SDKKodunuz ile platform arasındaki tek bağlantı: tablolar, gerçek zamanlı güncellemeler, cihaz fonksiyonları, dosyalarIronFlock SDK (Python, JavaScript, Go)
TablolarProjenin kendi veritabanında tutulan uygulama durumudata-template.yml
PanoTablolara canlı olarak bağlı, tüm cihazlar için uygulamanın merkezi kullanıcı arayüzüboard-template.yml
ParametrelerBir cihazın veya bir cihaz grubunun ayarlarıenv-template.yml
Uzaktan erişimContainer’larınızın sunduğu portlara, örneğin bir web arayüzüne giden tünellerport-template.yml
AI ajanlarıCihaz fonksiyonlarınızı araç olarak kullanan, IronFlock asistanı için ajanlarai-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

  1. 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ı.
  2. 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.
  3. 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 description alanında belirtin. Aynı satırlara iki taraf yazarsa birbirlerinin değişikliklerinin üzerine yazarlar.
  4. 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.
  5. 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.
  6. 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

VeriNeredeYazanOkuyan
Zaman içindeki ölçümlertsp içeren bir tablopublishToTable, publishRowsToTableZaman pencereli ve toplulaştırmalı grafikler
Bir cihazın, makinenin veya üretim emrinin mevcut durumuBir varlık tablosu (maintainLatestFlagFor)appendToTable: anahtarı, tsp değerini ve değişen sütunları içeren bir satırlatest filtresi
Kullanıcıların belirlediği sınır değerler, set değerleri, reçetelerPanodaki bir formun yazdığı bir tabloFormAşağıda gösterildiği gibi her cihaz
Kullanıcıların oluşturduğu, düzenlediği ve sildiği kayıtlardeleted sütunu olan bir varlık tablosuSilme sırasında işareti ayarlayan formPanodaki listeler ve düzenleme sayfaları
Operatörler için hatalar ve bildirimlererror-logs tablosuuserMessage ile birlikte reportErrorPanonun bildirim (toast) widget’ı
Görseller, belgeler, ses ve video kayıtlarıUygulamanın dosya depolamasıfiles.putBir sütunda saklanan ve bir widget’ın gösterdiği dosya URL’si
Parolalar ve API anahtarlarısecret: true olan bir sütunHerhangi bir tablo yazma işlemiYalnızca uygulamanın kendi container’larında revealSecrets, verifySecret
Tek bir cihazın kurulumu: bir makinenin adresi, bir seri portUygulama parametreleriCihazın veya grubun uygulama ayarlarıOrtam değişkenleri
Parçalarınız arasında, sonradan kimsenin ihtiyaç duymadığı mesajlarKendi konularınızpublishsubscribe
Tek bir cihazdan hemen yanıt gerektiren bir istekBir cihaz fonksiyonuregisterDeviceFunctioncallDeviceFunction, 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:

  1. Okuma ile abonelik arasında hiçbir satırın kaybolmaması için tabloyu okumadan önce ona abone olun.
  2. Başlangıçta latest filtresiyle en yeni satırı bir kez okuyun ve uygulayın.
  3. Bir satırı yalnızca yürürlükteki satırdan daha yeniyse uygulayın (tsp değerine göre). Programınız başlarken tam o anda kaydedilen bir satır hem okumadan hem de abonelikten gelebilir.
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.yml iç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 callDeviceFunction ile ç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 (DEV veya PROD), TUNNEL_DOMAIN, DEVICE_LAN_IP ve DEVICE_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 defaultValue değerini alır. Geliştirme modunda çalışan bir uygulama yalnızca varsayılan değerleri alır. Parametrelere belirgin adlar verin (PLC_HOST gibi; HOST değil).
  • Depolama: tek imajlı bir uygulama, uygulamaya özel olan ve yeniden başlatmalar ile güncellemeler boyunca korunan /data dizinini (uygulamayla birlikte silinir) ve cihazdaki tüm uygulamaların paylaştığı /shared dizinini 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 /dev dizinine 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 (ENV değeri DEV olur). 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.
  • /data yoktur. 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.yml iç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, devices veya device_cgroup_rules ayarlarını kendiniz bildirin. Bir devices: girdisi var olması gereken bir donanımı belirtir; donanım yoksa container başlamaz. device_cgroup_rules ise bir /dev volume 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-stopped verin.

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:

  1. Sabit bir portta sunun (0.0.0.0 adresine bağlı olarak) ve bu portu port-template.yml içinde protocol: http ile bildirin. Bir üretim kurulumu tüneli açar ve sayfayı websocket’ler dahil HTTPS üzerinden sunar.
  2. 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.
  3. 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_key içinde saklar. Cihaz başına tablolarda device_key sütununu anahtar olarak kullanın, cihazın adını path: kwargs.DEVICE_NAME ile 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 deleted sütunu, panoya da bu sütunu ayarlayan bir form ekleyin.

Kontrol Listesi

  1. Tüm durum tablolardadır; her tablonun tek yazan tarafı açıklamasında belirtilmiştir ve kodun yazdığı her sütun data-template.yml içinde bildirilmiştir.
  2. Her satırın bir tsp değeri vardır ve durum satırları, değişiklik olduğunda yalnızca anahtar ve değişen sütunlarla yazılır.
  3. 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.
  4. 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 reportError ve bir userMessage ile raporlanır.
  5. Program SIGTERM geldiğinde sonlanır, exec biçiminde başlar ve günlüklerini arabelleğe almadan yazar.
  6. Hiçbir şey yoklama yapmaz: pano bağlanır, cihazlar abone olur, web arayüzleri iter.
  7. Uygulamanızın sunduğu her port port-template.yml içinde bildirilmiştir; portun URL’si getRemoteAccessUrlForPort ile alınıp bir tabloya yazılır ve pano onu oradan gömer.
  8. 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.

Last updated on