Skip to Content
Tworzenie aplikacji IoTWzorzec aplikacji

Wzorzec aplikacji

Ta strona pokazuje, jak zbudować aplikację IronFlock, aby korzystała z platformy zgodnie z jej założeniami. Idea w jednym zdaniu: twoje kontenery działają na każdym urządzeniu i przechowują cały stan aplikacji w jej tabelach, panel jest interfejsem użytkownika aplikacji, a te dwie części nigdy nie komunikują się ze sobą bezpośrednio. Interfejs webowy serwowany przez samo urządzenie dociera do użytkowników przez tunel dostępu zdalnego i pojawia się wewnątrz panelu.

Poszczególne elementy są szczegółowo opisane na osobnych stronach. Ta strona pokazuje, jak łączą się w całość.

Części aplikacji

Aplikacja działa jednocześnie na wielu urządzeniach, zwykle na komputerach przemysłowych stojących przy maszynach. Składa się z następujących części:

CzęśćCzym jestDefiniowana w
Kod brzegowyKontener lub stos Docker Compose na każdym urządzeniu, na którym zainstalowano aplikacjęDockerfile lub docker-compose.yml
SDKJedyne połączenie między twoim kodem a platformą: tabele, aktualizacje w czasie rzeczywistym, funkcje urządzenia, plikiSDK IronFlock (Python, JavaScript, Go)
TabeleStan aplikacji, we własnej bazie danych projektudata-template.yml
PanelCentralny interfejs użytkownika aplikacji, powiązany na żywo z tabelami, wspólny dla wszystkich urządzeńboard-template.yml
ParametryUstawienia jednego urządzenia lub grupy urządzeńenv-template.yml
Dostęp zdalnyTunele do portów, na których twoje kontenery serwują np. interfejs webowyport-template.yml
Agenty AIAgenty dla asystenta IronFlock, które używają twoich funkcji urządzenia jako narzędziai-template.yml

Gdy użytkownik instaluje twoją aplikację w projekcie, IronFlock tworzy tabele aplikacji w bazie danych tego projektu, instaluje jej panel i otwiera prywatny obszar komunikatów (realm), który w czasie rzeczywistym łączy kontenery aplikacji na wszystkich urządzeniach, jej tabele i jej panel. SDK dołącza do tego realmu z tożsamością wstrzykiwaną przez agenta urządzenia, więc twój kod nie potrzebuje ani adresu, ani danych uwierzytelniających.

Zasady

  1. Tabele są stanem aplikacji. Wszystko, czego potrzebuje panel, inne urządzenie, zrestartowany kontener lub agent AI, jest przechowywane w tabeli. Kontener trzyma tylko to, co może odtworzyć z tabel, np. kopię swoich ustawień w pamięci.
  2. Części komunikują się przez tabele, a nie bezpośrednio ze sobą. Urządzenie zapisuje to, co mierzy, i stan, w którym się znajduje, a panel to pokazuje. Panel zapisuje decyzje użytkowników, a urządzenia je stosują. Żadna ze stron nie wywołuje drugiej, więc panel działa, gdy urządzenia są offline, a urządzenia pracują dalej, gdy nikt nie ma otwartego panelu.
  3. Jedno źródło zapisów na tabelę. Do każdej tabeli zapisują albo urządzenia (każde własne dane), albo panel, albo jedna usługa. Wskaż, które z nich, w description tabeli. Dwa źródła zapisujące te same wiersze nadpisują sobie nawzajem zmiany.
  4. Pożądany stan, a nie polecenia. Aby zmienić to, co robią urządzenia, zapisz w tabeli stan docelowy (limit, wartość zadaną, recepturę, harmonogram) i pozwól każdemu urządzeniu zastosować najnowszy wiersz. Urządzenie, które było offline, zastosuje go po powrocie, a dwukrotne zastosowanie niczego nie zmienia.
  5. Zapisuj przy zmianie, śledź na żywo, nigdy nie odpytuj. Zapisuj wiersz encji, gdy zmieni się jedna z jej wartości, a pomiary — z ich częstotliwością próbkowania. Wszystko, co odczytuje dane, śledzi tabele w czasie rzeczywistym: powiązania danych panelu, subskrypcje tabel na urządzeniach, połączenia WebSocket w interfejsie webowym urządzenia. Timer, który cyklicznie odczytuje tabelę od nowa, zawsze oznacza błędny projekt.
  6. Panel jest interfejsem użytkownika; interfejs webowy urządzenia go uzupełnia. Serwuj stronę webową z urządzenia tylko dla tego, czego panel nie potrafi pokazać: własnego HMI maszyny, wideo, widoku na żywo o wysokiej częstotliwości, ekranu operatora przy maszynie. Panel ją osadza.

Gdzie trafia każdy rodzaj danych

DaneGdzieZapisywane przezOdczytywane przez
Odczyty w czasieTabela z tsppublishToTable, publishRowsToTableWykresy z oknami czasowymi i agregacją
Aktualny stan urządzenia, maszyny lub zleceniaTabela encji (maintainLatestFlagFor)appendToTable: wiersz z kluczem, tsp i zmienionymi kolumnamiFiltr latest
Limity, wartości zadane, receptury ustawiane przez użytkownikówTabela zapisywana przez formularz paneluFormularzKażde urządzenie, jak pokazano poniżej
Rekordy, które użytkownicy tworzą, edytują i usuwająTabela encji z kolumną deletedFormularz, który przy usuwaniu ustawia flagęListy i strony edycji na panelu
Błędy i komunikaty dla operatorówTabela error-logsreportError z userMessageWidget powiadomień (toast) na panelu
Obrazy, dokumenty, nagraniaMagazyn plików aplikacjifiles.putAdres URL pliku w kolumnie, wyświetlany przez widget
Hasła i klucze APIKolumna secret: trueDowolny zapis do tabelirevealSecrets, verifySecret, wyłącznie we własnych kontenerach aplikacji
Instalacja jednego urządzenia: adres maszyny, port szeregowyParametry aplikacjiUstawienia aplikacji dla urządzenia lub grupyZmienne środowiskowe
Komunikaty między częściami aplikacji, potrzebne tylko w chwili dostarczeniaWłasne tematy (topics)publishsubscribe
Żądanie, które wymaga natychmiastowej odpowiedzi jednego urządzeniaFunkcja urządzeniaregisterDeviceFunctioncallDeviceFunction, akcje widgetów, narzędzia AI

Dołączać czy publikować? appendToTable czeka na restartujące się zaplecze danych i informuje cię, gdy odrzuci ono wiersz. publishToTable czeka tylko na połączenie, więc wiersz opublikowany podczas restartu zaplecza danych przepada. Zmiany stanu i rekordy dołączaj, a odczyty o wysokiej częstotliwości publikuj.

Parametry czy tabela ustawień? Parametry należą do urządzenia lub grupy urządzeń: wprowadza je w ustawieniach aplikacji ten, kto instaluje lub konfiguruje urządzenie, a twój kod odczytuje je ze swojego środowiska. Używaj ich do tego, co różni się między instalacjami. To, co użytkownicy zmieniają w trakcie działania aplikacji — limity, receptury, harmonogramy, wszystko, co dotyczy wielu urządzeń — należy umieścić w tabeli zapisywanej przez panel. To w panelu użytkownicy pracują z aplikacją, każde urządzenie od razu uwzględnia zmianę, a tabela przechowuje historię tego, kto, co i kiedy zmienił.

Śledzenie tego, co zapisuje panel

Urządzenie stosuje stan zapisany przez panel, np. limit ustawiony na panelu, w trzech krokach:

  1. Zasubskrybuj tabelę, zanim ją odczytasz, aby żaden wiersz nie przepadł między odczytem a subskrypcją.
  2. Odczytaj najnowszy wiersz raz przy starcie, z filtrem latest, i zastosuj go.
  3. Stosuj wiersz tylko wtedy, gdy jest nowszy (tsp) niż obowiązujący. Wiersz zapisany akurat w chwili startu programu może przyjść zarówno z odczytu, jak i z subskrypcji.
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)

Nic więcej nie trzeba, także w przypadku urządzeń, które bywają offline. Połączeniem zajmuje się SDK, a nie twój kod: łączy się ponownie, odtwarza twoje subskrypcje i funkcje urządzenia, a następnie dostarcza do każdej subskrypcji tabeli wiersze zapisane w czasie, gdy urządzenie było offline, od najstarszego, przed wierszami przychodzącymi na żywo. Limit zapisany, gdy urządzenie było offline, dociera do niego zaraz po jego powrocie. Nadrabianie zaległości opiera się na tsp: dostarczane są wiersze nowsze od najnowszego, który otrzymał twój handler, najwyżej 10 000 najnowszych. Odczyt wykonywany w ramach nadrabiania zaległości czeka na zaplecze danych jak każdy odczyt historii, w granicach okna ponownego połączenia; gdy się nie powiedzie, te wiersze nie zostają dostarczone, a w logu zapisywane jest ostrzeżenie. Nadrabianie zaległości jest częścią SDK od wersji 1.10.0.

Polecenia, funkcje urządzenia i tematy

Preferuj stan. Większość tego, co użytkownicy „wydają jako polecenia”, to w rzeczywistości pożądany stan: wartość zadana, stan włączony lub wyłączony, receptura. Zapisuj to w tabeli, jak powyżej.

Polecenia, które mają zostać wykonane tylko raz, np. wydrukowanie etykiety lub uruchomienie cyklu czyszczenia, to wiersze z identyfikatorem w tabeli zapisywanej przez panel. Urządzenie zapisuje identyfikatory wykonanych poleceń we własnej tabeli, więc polecenie otrzymane dwukrotnie wykonuje się tylko raz.

Funkcje urządzenia to procedury, które urządzenie rejestruje za pomocą registerDeviceFunction. Używaj ich, gdy wywołujący potrzebuje odpowiedzi urządzenia od razu:

  • Agenty AI: narzędzie w ai-template.yml wywołuje funkcję na urządzeniu wybranym przez asystenta. Zobacz Definiowanie agentów i narzędzi.
  • Widgety Przełącznik i SCADA na panelu wywołują je, gdy urządzenie jest online.
  • Twoje własne kontenery i urządzenia wywołują się nawzajem za pomocą callDeviceFunction.

Zwracaj z funkcji urządzenia czytelny tekst lub niewielki JSON, a spodziewane niepowodzenie (wyłączoną maszynę) zwracaj jako wynik, zamiast zgłaszać błąd.

Tematy przenoszą między częściami twojej aplikacji komunikaty potrzebne tylko w chwili dostarczenia: publish i subscribe na nazwach we własnej przestrzeni nazw, np. myapp.events. Panel nie może subskrybować tematów: wszystko, co pokazuje, musi znajdować się w tabeli. Komunikaty opublikowane, gdy urządzenie jest offline, nie są przechowywane, więc do wszystkiego, co musi dotrzeć, używaj tabeli.

Kontener na urządzeniu

Co agent urządzenia daje twojemu kontenerowi i czego od niego oczekuje:

  • Tożsamość: agent ustawia DEVICE_KEY, DEVICE_NAME, DEVICE_SERIAL_NUMBER, SWARM_KEY, APP_KEY, APP_NAME, ENV (DEV lub PROD), TUNNEL_DOMAIN, DEVICE_LAN_IP i DEVICE_PORT_FOR_<port>, a do tego własne poświadczenie aplikacji dla SDK. SDK odczytuje to, czego potrzebuje; nigdy nie wpisuj żadnej z tych wartości na sztywno w kodzie.
  • Parametry: instalacja produkcyjna otrzymuje wartości ustawione przez użytkowników, a dla pozostałych parametrów ich defaultValue. Uruchomienie deweloperskie otrzymuje wyłącznie wartości domyślne. Nadawaj parametrom konkretne nazwy (PLC_HOST, a nie HOST).
  • Przechowywanie danych: aplikacja z pojedynczym obrazem otrzymuje katalog /data, prywatny dla aplikacji i zachowywany między restartami i aktualizacjami (usuwany razem z aplikacją), oraz /shared, współdzielony przez wszystkie aplikacje na urządzeniu. Mimo to przechowuj stan aplikacji w tabelach, a tych katalogów używaj do plików należących do urządzenia, takich jak pobrany model lub pamięć podręczna.
  • Sprzęt: kontener z pojedynczym obrazem działa w trybie uprzywilejowanym, z dostępem do /dev urządzenia.
  • Logi: wszystko, co twój program wypisuje na stdout i stderr, jest logiem aplikacji w IronFlock. Wypisuj bez buforowania (python -u).
  • Start: 30 sekund po uruchomieniu kontener musi nadal działać, inaczej start uznaje się za nieudany.
  • Awarie: gdy aplikacja produkcyjna zakończy działanie, agent uruchamia ją ponownie, czekając dłużej po każdym niepowodzeniu. Uruchomienie deweloperskie nie jest restartowane. Spodziewane błędy, np. czujnik, który nie odpowiada, obsługuj w programie, a przy błędach, po których nie da się wrócić do normalnej pracy, pozwól mu się zakończyć.
  • Zatrzymanie: twój program jest głównym procesem kontenera (PID 1), który ignoruje sygnał zatrzymania SIGTERM, chyba że go obsługuje. Niech program kończy działanie po otrzymaniu SIGTERM, inaczej zatrzymanie aplikacji będzie trwało, dopóki Docker nie zabije procesu. Uruchamiaj program w formie exec (CMD ["python", "-u", "main.py"]), a nie przez powłokę.
  • Uruchomienia deweloperskie: uruchomienie aplikacji ze środowiska IDE w chmurze to uruchomienie deweloperskie (ENV ma wartość DEV). Zapisuje ono do deweloperskich tabel aplikacji, otrzymuje wartości domyślne parametrów i nie dostaje żadnych tuneli dostępu zdalnego.

Docker Compose

Użyj pliku docker-compose.yml, gdy twoja aplikacja uruchamia kilka procesów albo dołącza obok twojego kodu usługi firm trzecich, takie jak broker MQTT lub baza danych. Podstawy opisano na stronie Docker i Compose. W porównaniu z pojedynczym obrazem:

  • Każda usługa otrzymuje wstrzyknięte środowisko. Agent daje każdej usłudze własny plik środowiskowy ze zmiennymi wymienionymi powyżej; napisany przez ciebie env_file: zostaje zastąpiony. Wyrażenie ${VAR} w pliku jest rozwijane na podstawie wstrzykniętych wartości.
  • Brak /data. Usługi nie dostają katalogu aplikacji. Przechowuj pliki w nazwanych wolumenach: przetrwają zatrzymania i aktualizacje, a są kasowane, gdy aplikacja zostanie usunięta z urządzenia.
  • Opublikowane porty są przemapowywane. Każdy opublikowany port dostaje wolny port hosta na wszystkich interfejsach urządzenia, a DEVICE_PORT_FOR_<port> podaje twojemu kodowi rzeczywisty port. W port-template.yml używaj portu hosta w takiej postaci, w jakiej zapisano go w pliku compose. Publikuj tylko to, do czego użytkownicy mają mieć dostęp, a usługi niech komunikują się ze sobą przez sieć compose po nazwie usługi. Port hosta zapisany jako ${VAR} nie jest zarządzany: nie jest przemapowywany i nie dostaje tunelu.
  • Nic nie jest dodawane. Agent nie przyznaje usługom compose żadnych uprawnień ani urządzeń. Zadeklaruj privileged, devices lub device_cgroup_rules samodzielnie. Wpis devices: wskazuje sprzęt, który musi istnieć, inaczej kontener się nie uruchomi; device_cgroup_rules razem z wolumenem /dev działa na każdym urządzeniu.
  • Bez profiles:. Usługa z profilami jest pomijana w wydaniu.
  • Ta sama reguła startu. W ciągu 30 sekund każdy kontener musi działać lub zakończyć się z kodem 0, a co najmniej jeden musi działać. Długo działającym usługom nadaj restart: unless-stopped.

Interfejsy webowe na urządzeniu

Serwer webowy w twoim kontenerze staje się osiągalny z dowolnego miejsca przez tunel dostępu zdalnego swojego portu, a panel go wyświetla:

  1. Serwuj na stałym porcie (nasłuchując na 0.0.0.0) i zadeklaruj go w port-template.yml z protocol: http. Instalacja produkcyjna włącza tunel i serwuje go przez HTTPS, łącznie z połączeniami WebSocket.
  2. Zapisz adres URL tunelu w tabeli. Metoda SDK getRemoteAccessUrlForPort(port) zwraca ten adres. Przy starcie zapisz go w wierszu urządzenia w tabeli urządzeń. Tunele mają tylko instalacje produkcyjne, więc uruchomienie deweloperskie nie zapisuje adresu URL.
  3. Osadź go w panelu. Powiąż adres URL widgetu Osadź z tą kolumną, na stronie przeznaczonej dla jednego urządzenia, która filtruje dane według urządzenia. W UI Builder jest to powiązanie danych jak każde inne.

Co do samej strony:

  • Kto może ją otworzyć: tunel http jest prywatny. Mogą go otworzyć tylko osoby zalogowane do IronFlock, które mają dostęp do urządzenia, chyba że użytkownik przełączy port na publiczny. W sieci samego urządzenia strona jest otwarta dla wszystkich, więc nie pokazuj tam niczego, czego nie powinna widzieć sieć zakładu, a dla wszystkiego, co zmienia stan maszyny, wymagaj własnego logowania.
  • Względne adresy URL: ładuj zasoby, wywołuj swoje API i otwieraj połączenia WebSocket względem strony, aby działała w sieci urządzenia, przez tunel i za reverse proxy.
  • Wypychaj, nigdy nie odpytuj: wysyłaj aktualizacje przez WebSocket.
  • Żadnych poświadczeń platformy w przeglądarce: strona komunikuje się z własnym serwerem w kontenerze, a to ten serwer utrzymuje połączenie SDK.

Aby zamiast własnej strony pokazać webowy HMI maszyny z sieci zakładu, udostępnij go przez reverse proxy ze swojego kontenera. Zobacz Osadzanie zewnętrznych interfejsów HMI.

Wiele urządzeń

  • Każdy wiersz wskazuje swoje urządzenie: IronFlock zapisuje w device_key klucz urządzenia, które zapisało wiersz. Jako klucza tabel per urządzenie używaj device_key, nazwę urządzenia zapisuj w kolumnie z path: kwargs.DEVICE_NAME i dziel wykresy według tej nazwy.
  • Lista urządzeń i strona dla każdego urządzenia: nawigacja boczna zbudowana z tabeli urządzeń oraz jedna strona, która działa dla każdego urządzenia i filtruje swoje widgety według urządzenia wskazanego w trasie. Zobacz Budowanie paneli.
  • Ustawienia per urządzenie lub per maszyna: jako klucza tabeli ustawień użyj kolumny, która wskazuje cel (wiersze zapisywane przez panel nie mają własnego urządzenia), i wypełniaj ją w formularzu na stronie celu. Każde urządzenie stosuje najnowszy wiersz dla własnego klucza.
  • Usunięte urządzenie zachowuje swoje wiersze. Jeśli użytkownicy mają móc usuwać je z list, dodaj do tabeli urządzeń kolumnę deleted, a do panelu formularz, który ją ustawia.

Lista kontrolna

  1. Cały stan znajduje się w tabelach, z których każda ma jedno źródło zapisów wskazane w swoim opisie, a każda kolumna zapisywana przez kod jest zadeklarowana w data-template.yml.
  2. Każdy wiersz ma tsp, a wiersze stanu są zapisywane przy zmianie, tylko z kluczem i zmienionymi kolumnami.
  3. Stan zapisywany przez panel jest śledzony w trzech opisanych wyżej krokach, z SDK 1.10.0 lub nowszym, a kod nigdy nie zajmuje się połączeniem ani jego sesjami.
  4. Zapisy do tabel w długo działających pętlach obsługują błąd zgłaszany przez zapis, gdy platforma jest nieosiągalna od minuty, a spodziewane niepowodzenia są zgłaszane przez reportError z userMessage.
  5. Program kończy działanie po otrzymaniu SIGTERM, startuje w formie exec i loguje bez buforowania.
  6. Nic nie odpytuje: panel wiąże dane, urządzenia subskrybują, interfejsy webowe wypychają aktualizacje.
  7. Każdy port serwowany przez twoją aplikację jest zadeklarowany w port-template.yml, jego adres URL pochodzi z getRemoteAccessUrlForPort i jest zapisywany w tabeli, a panel osadza go stamtąd.
  8. Aplikacja compose nie ma env_file:, profiles: ani portów hosta ${VAR}, przechowuje pliki w nazwanych wolumenach i publikuje tylko porty potrzebne użytkownikom.

Zacznij od aplikacji startowej

Wtyczka IronFlock dla Claude Code zawiera aplikację startową zgodną z tym wzorcem: program, który na każdym urządzeniu odczytuje dane z maszyny, przechowuje stan i odczyty każdego urządzenia w tabelach, stosuje limit ustawiony przez użytkowników na panelu i serwuje widok na żywo, który panel osadza na stronie każdego urządzenia. Claude Code tworzy na jej podstawie nową aplikację i dostosowuje ją do twojego przypadku użycia.

Last updated on