Skip to Content

App-Bauplan

Diese Seite zeigt, wie Sie eine IronFlock-App bauen, die die Plattform so nutzt, wie es vorgesehen ist. Die Idee in einem Satz: Ihre Container laufen auf jedem Gerät und halten den gesamten Zustand der App in deren Tabellen, das Board ist die Benutzeroberfläche der App, und die beiden kommunizieren nie direkt miteinander. Eine Web-UI, die ein Gerät selbst bereitstellt, erreicht die Benutzer über einen Remote-Access-Tunnel und erscheint im Board.

Die einzelnen Bausteine sind auf eigenen Seiten ausführlich beschrieben. Diese Seite zeigt, wie sie zusammenspielen.

Die Teile einer App

Eine App läuft auf vielen Geräten gleichzeitig, typischerweise auf Industrie-PCs direkt an den Maschinen. Sie besteht aus:

TeilBeschreibungDefiniert in
Edge-CodeEin Container oder ein Docker-Compose-Stack auf jedem Gerät, auf dem die App installiert istDockerfile oder docker-compose.yml
SDKDie einzige Verbindung zwischen Ihrem Code und der Plattform: Tabellen, Echtzeit-Updates, Gerätefunktionen, DateienIronFlock SDK (Python, JavaScript, Go)
TabellenDer Zustand der App, in der eigenen Datenbank des Projektsdata-template.yml
BoardDie zentrale Benutzeroberfläche der App, live an die Tabellen gebunden, für alle Geräteboard-template.yml
ParameterEinstellungen eines Geräts oder einer Gerätegruppeenv-template.yml
Remote AccessTunnel zu Ports, auf denen Ihre Container Dienste anbieten, etwa eine Web-UIport-template.yml
KI-AgentenAgenten für den IronFlock-KI-Assistenten, mit Ihren Gerätefunktionen als Toolsai-template.yml

Wenn ein Benutzer Ihre App in einem Projekt installiert, legt IronFlock die Tabellen der App in der Datenbank dieses Projekts an, installiert ihr Board und öffnet einen privaten Messaging-Realm, der die Container der App auf allen Geräten, ihre Tabellen und ihr Board in Echtzeit verbindet. Das SDK tritt diesem Realm mit der Identität bei, die der Geräte-Agent injiziert, sodass Ihr Code weder eine Adresse noch Zugangsdaten braucht.

Die Regeln

  1. Die Tabellen sind der Zustand der App. Alles, was das Board, ein anderes Gerät, ein neu gestarteter Container oder ein KI-Agent braucht, wird in einer Tabelle gespeichert. Ein Container behält nur, was er aus den Tabellen wiederherstellen kann, etwa eine Kopie seiner Einstellungen im Arbeitsspeicher.
  2. Die Teile kommunizieren über die Tabellen, nicht miteinander. Ein Gerät schreibt, was es misst und in welchem Zustand es ist, und das Board zeigt es an. Das Board schreibt, was Benutzer entscheiden, und die Geräte wenden es an. Keine Seite ruft die andere auf: Das Board funktioniert daher auch, während Geräte offline sind, und die Geräte arbeiten weiter, während niemand das Board geöffnet hat.
  3. Ein Schreiber pro Tabelle. Jede Tabelle wird entweder von den Geräten geschrieben (jedes über sich selbst), vom Board oder von einem einzigen Dienst. Geben Sie in der description der Tabelle an, von wem. Zwei Schreiber derselben Zeilen überschreiben gegenseitig ihre Änderungen.
  4. Sollzustand statt Befehle. Um zu ändern, was Geräte tun, schreiben Sie das Ziel in eine Tabelle (einen Grenzwert, einen Sollwert, ein Rezept, einen Zeitplan) und lassen jedes Gerät die neueste Zeile anwenden. Ein Gerät, das offline war, wendet sie an, sobald es wieder online ist, und eine wiederholte Anwendung ändert nichts.
  5. Bei Änderung schreiben, live folgen, nie pollen. Schreiben Sie die Zeile einer Entität, wenn sich einer ihrer Werte ändert, und Messwerte im Takt ihrer Abtastrate. Alles, was liest, folgt den Tabellen in Echtzeit: die Datenbindungen des Boards, Tabellen-Subscriptions auf den Geräten, WebSockets in der Web-UI eines Geräts. Ein Timer, der eine Tabelle regelmäßig neu liest, ist immer der falsche Ansatz.
  6. Das Board ist die Benutzeroberfläche; die Web-UI eines Geräts ergänzt es. Stellen Sie eine Webseite auf einem Gerät nur für das bereit, was ein Board nicht darstellen kann: die eigene HMI einer Maschine, Video, eine Live-Ansicht mit hoher Aktualisierungsrate, einen Bedienbildschirm an der Maschine. Das Board bettet sie ein.

Wohin welche Daten gehören

DatenWoGeschrieben vonGelesen von
Messwerte im ZeitverlaufEine Tabelle mit tsppublishToTable, publishRowsToTableDiagramme mit Zeitfenstern und Aggregation
Der aktuelle Zustand eines Geräts, einer Maschine oder eines AuftragsEine Entitätstabelle (maintainLatestFlagFor)appendToTable: eine Zeile mit dem Schlüssel, tsp und den geänderten SpaltenDer latest-Filter
Grenzwerte, Sollwerte und Rezepte, die Benutzer festlegenEine Tabelle, in die das Formular des Boards schreibtDas FormularJedes Gerät, wie unten gezeigt
Datensätze, die Benutzer anlegen, bearbeiten und löschenEine Entitätstabelle mit einer deleted-SpalteDas Formular, das beim Löschen die Markierung setztListen und Bearbeitungsseiten auf dem Board
Fehler und Hinweise für BedienerDie error-logs-TabellereportError mit einer userMessageDas Toast-Widget des Boards
Bilder, Dokumente, AufzeichnungenDer Dateispeicher der Appfiles.putDie URL der Datei in einer Spalte, angezeigt von einem Widget
Passwörter und API-SchlüsselEine Spalte mit secret: trueJeder Schreibvorgang in die TabellerevealSecrets, verifySecret, nur in den eigenen Containern der App
Die Installation eines einzelnen Geräts: die Adresse einer Maschine, eine serielle SchnittstelleApp-ParameterDie App-Einstellungen des Geräts oder der GruppeUmgebungsvariablen
Nachrichten zwischen Ihren Teilen, die später niemand mehr brauchtIhre eigenen Topicspublishsubscribe
Eine Anfrage, die sofort die Antwort eines bestimmten Geräts brauchtEine GerätefunktionregisterDeviceFunctioncallDeviceFunction, Widget-Aktionen, KI-Tools

Append oder Publish? appendToTable wartet auf ein Daten-Backend, das gerade neu startet, und teilt Ihnen mit, wenn es eine Zeile ablehnt. publishToTable wartet nur auf die Verbindung, daher geht eine Zeile verloren, die veröffentlicht wird, während das Daten-Backend neu startet. Schreiben Sie Zustandsänderungen und Datensätze per Append, hochfrequente Messwerte per Publish.

Parameter oder Einstellungstabelle? Parameter gehören zu einem Gerät oder einer Gerätegruppe: Wer das Gerät installiert oder konfiguriert, setzt sie in den App-Einstellungen, und Ihr Code liest sie aus seiner Umgebung. Verwenden Sie sie für das, was sich von Installation zu Installation unterscheidet. Was Benutzer ändern, während die App läuft — Grenzwerte, Rezepte, Zeitpläne, alles, was für viele Geräte gilt —, gehört in eine Tabelle, in die das Board schreibt. Im Board arbeiten die Benutzer mit der App, jedes Gerät folgt einer Änderung sofort, und die Tabelle bewahrt die Historie darüber, wer was wann geändert hat.

Verfolgen, was das Board schreibt

Ein Gerät wendet vom Board geschriebenen Zustand, etwa einen auf dem Board gesetzten Grenzwert, in drei Schritten an:

  1. Abonnieren Sie die Tabelle, bevor Sie sie lesen, damit keine Zeile in die Lücke zwischen Lesen und Subscription fällt.
  2. Lesen Sie die neueste Zeile einmal beim Start, mit dem latest-Filter, und wenden Sie sie an.
  3. Wenden Sie eine Zeile nur an, wenn sie neuer ist (tsp) als die gerade gültige. Eine Zeile, die genau beim Start Ihres Programms gespeichert wird, kann sowohl über das Lesen als auch über die Subscription eintreffen.
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)

Das ist alles, auch für Geräte, die offline gehen. Um die Verbindung kümmert sich das SDK, nicht Ihr Code: Es verbindet sich neu, stellt Ihre Subscriptions und Gerätefunktionen wieder her und liefert dann jeder Tabellen-Subscription die Zeilen, die gespeichert wurden, während das Gerät offline war — die ältesten zuerst, vor den Zeilen, die live eintreffen. Ein Grenzwert, der gespeichert wurde, während ein Gerät offline war, erreicht es, sobald es wieder online ist. Die Nachlieferung richtet sich nach tsp: Sie liefert die Zeilen, die neuer sind als die neueste, die Ihr Handler erhalten hat, höchstens die neuesten 10.000. Der Lesevorgang der Nachlieferung wartet wie jeder Verlaufslesevorgang auf das Daten-Backend, längstens für die Dauer des Neuverbindungsfensters; schlägt er fehl, werden diese Zeilen nicht zugestellt, und eine Warnung wird protokolliert. Die Nachlieferung ist ab Version 1.10.0 Teil des SDK.

Befehle, Gerätefunktionen und Topics

Zustand bevorzugen. Das meiste, was Benutzer „befehlen“, ist ein Sollzustand: ein Sollwert, Ein oder Aus, ein Rezept. Schreiben Sie es in eine Tabelle, wie oben beschrieben.

Befehle, die genau einmal ausgeführt werden müssen, etwa ein Etikett drucken oder einen Reinigungszyklus starten, sind Zeilen mit einer ID in einer Tabelle, in die das Board schreibt. Das Gerät vermerkt in einer eigenen Tabelle die IDs der Befehle, die es ausgeführt hat, sodass ein Befehl, den es zweimal erhält, nur einmal ausgeführt wird.

Gerätefunktionen sind Prozeduren, die ein Gerät mit registerDeviceFunction registriert. Verwenden Sie sie, wenn der Aufrufer die Antwort des Geräts sofort braucht:

  • KI-Agenten: Ein Tool in ai-template.yml ruft die Funktion auf dem Gerät auf, das der Assistent auswählt. Siehe KI-Agenten & Tools.
  • Switch- und SCADA-Widgets auf dem Board rufen sie auf, solange das Gerät online ist.
  • Ihre eigenen Container und Geräte rufen sich gegenseitig mit callDeviceFunction auf.

Geben Sie aus einer Gerätefunktion lesbaren Text oder kompaktes JSON zurück, und liefern Sie einen erwarteten Fehlschlag (eine ausgeschaltete Maschine) als Ergebnis zurück, statt einen Fehler auszulösen.

Topics transportieren Nachrichten zwischen Ihren eigenen Teilen, die später niemand mehr braucht: publish und subscribe auf Namen in Ihrem eigenen Namensraum, etwa myapp.events. Das Board kann keine Topics abonnieren: Alles, was es anzeigt, muss in einer Tabelle stehen. Nachrichten, die veröffentlicht werden, während ein Gerät offline ist, werden nicht gespeichert; verwenden Sie daher für alles, was ankommen muss, eine Tabelle.

Der Container auf dem Gerät

Was der Geräte-Agent Ihrem Container mitgibt und was er von ihm erwartet:

  • Identität: Der Agent setzt DEVICE_KEY, DEVICE_NAME, DEVICE_SERIAL_NUMBER, SWARM_KEY, APP_KEY, APP_NAME, ENV (DEV oder PROD), TUNNEL_DOMAIN, DEVICE_LAN_IP und DEVICE_PORT_FOR_<port> sowie die eigenen Zugangsdaten der App für das SDK. Das SDK liest, was es braucht; codieren Sie keinen dieser Werte fest.
  • Parameter: Eine Produktionsinstallation erhält die Werte, die Benutzer gesetzt haben, und für alle übrigen den defaultValue des jeweiligen Parameters. Eine Ausführung im Entwicklungsmodus erhält nur die Standardwerte. Geben Sie Parametern spezifische Namen (PLC_HOST, nicht HOST).
  • Speicher: Eine App aus einem einzelnen Image erhält /data, privat für die App und über Neustarts und Updates hinweg persistent (es wird mit der App gelöscht), und /shared, das sich alle Apps auf dem Gerät teilen. Halten Sie den Zustand der App trotzdem in Tabellen, und nutzen Sie diese Ordner für Dateien, die zum Gerät gehören, etwa ein heruntergeladenes Modell oder einen Cache.
  • Hardware: Ein Container aus einem einzelnen Image läuft privilegiert, mit dem /dev des Geräts.
  • Logs: Alles, was Ihr Programm nach stdout und stderr schreibt, ist das Log der App in IronFlock. Schreiben Sie es ungepuffert (python -u).
  • Start: Der Container muss 30 Sekunden nach seinem Start laufen, sonst gilt der Start als fehlgeschlagen.
  • Abstürze: Wenn sich eine Produktions-App beendet, startet der Agent sie erneut und wartet nach jedem Fehlschlag länger. Eine Ausführung im Entwicklungsmodus wird nicht neu gestartet. Behandeln Sie erwartete Fehler, etwa einen Sensor, der nicht antwortet, in Ihrem Programm, und lassen Sie es sich bei Fehlern beenden, von denen es sich nicht erholen kann.
  • Stopp: Ihr Programm ist der Hauptprozess des Containers (PID 1), der das Stoppsignal SIGTERM ignoriert, sofern er es nicht selbst behandelt. Das Programm muss sich bei SIGTERM selbst beenden, sonst wartet das Stoppen der App, bis Docker es zwangsweise beendet. Starten Sie das Programm in der Exec-Form (CMD ["python", "-u", "main.py"]), nicht über eine Shell.
  • Entwicklungsmodus: Wenn Sie die App aus der Cloud-IDE starten, läuft sie im Entwicklungsmodus (ENV ist DEV). Sie schreibt dann in die Entwicklungstabellen der App, erhält die Standardwerte der Parameter und keine Remote-Access-Tunnel.

Docker Compose

Verwenden Sie eine docker-compose.yml, wenn Ihre App mehrere Prozesse ausführt oder neben Ihrem eigenen Code Dienste von Drittanbietern mitbringt, etwa einen MQTT-Broker oder eine Datenbank. Die Grundlagen finden Sie unter Docker & Compose. Im Vergleich zu einem einzelnen Image gilt:

  • Jeder Dienst erhält die injizierte Umgebung. Der Agent gibt jedem Dienst eine eigene Umgebungsdatei mit den oben genannten Variablen; ein env_file:, das Sie selbst schreiben, wird ersetzt. Ein ${VAR} in der Datei wird gegen die injizierten Werte aufgelöst.
  • Kein /data. Dienste erhalten keinen App-Ordner. Legen Sie Dateien in benannten Volumes ab: Sie überstehen Stopps und Updates und werden gelöscht, wenn die App vom Gerät entfernt wird.
  • Veröffentlichte Ports werden neu abgebildet. Jeder veröffentlichte Port erhält einen freien Host-Port auf allen Schnittstellen des Geräts, und DEVICE_PORT_FOR_<port> teilt Ihrem Code den tatsächlichen mit. Verwenden Sie in port-template.yml den Host-Port so, wie er in der Compose-Datei steht. Veröffentlichen Sie nur, was Benutzer erreichen sollen, und lassen Sie Ihre Dienste über das Compose-Netzwerk per Dienstname miteinander kommunizieren. Ein als ${VAR} geschriebener Host-Port wird nicht verwaltet: Er wird weder neu abgebildet noch getunnelt.
  • Es wird nichts hinzugefügt. Der Agent gewährt Compose-Diensten keine Privilegien und keinen Zugriff auf Hardware. Deklarieren Sie privileged, devices oder device_cgroup_rules selbst. Ein devices:-Eintrag benennt Hardware, die vorhanden sein muss, sonst startet der Container nicht; device_cgroup_rules zusammen mit einem /dev-Volume funktioniert auf jedem Gerät.
  • Keine profiles:. Ein Dienst mit Profilen wird nicht in das Release aufgenommen.
  • Dieselbe Startregel. Innerhalb von 30 Sekunden muss jeder Container laufen oder sich mit Code 0 beendet haben, und mindestens einer muss laufen. Geben Sie lang laufenden Diensten restart: unless-stopped.

Web-UIs auf dem Gerät

Ein Webserver in Ihrem Container wird über den Remote-Access-Tunnel seines Ports von überall erreichbar, und das Board zeigt ihn an:

  1. Lauschen Sie auf einem festen Port (gebunden an 0.0.0.0) und deklarieren Sie ihn in port-template.yml mit protocol: http. Eine Produktionsinstallation schaltet den Tunnel ein und stellt ihn über HTTPS bereit, WebSockets eingeschlossen.
  2. Schreiben Sie die URL des Tunnels in eine Tabelle. getRemoteAccessUrlForPort(port) aus dem SDK liefert die URL. Schreiben Sie sie beim Start in die Zeile des Geräts in einer Gerätetabelle. Nur Produktionsinstallationen haben Tunnel, daher schreibt eine Ausführung im Entwicklungsmodus keine URL.
  3. Betten Sie sie in das Board ein. Binden Sie die URL des Embed-Widgets an diese Spalte, auf einer Seite pro Gerät, die nach dem Gerät filtert. Im UI Builder ist das eine Datenbindung wie jede andere.

Für die Seite selbst:

  • Wer sie öffnen kann: Ein http-Tunnel ist privat. Nur Personen, die bei IronFlock angemeldet sind und Zugriff auf das Gerät haben, können ihn öffnen, es sei denn, ein Benutzer schaltet den Port auf Public. Im eigenen Netzwerk des Geräts ist die Seite für alle offen; zeigen Sie dort also nichts, was das Werksnetz nicht sehen darf, und verlangen Sie für alles, was den Zustand der Maschine ändert, eine eigene Anmeldung.
  • Relative URLs: Laden Sie Assets, rufen Sie Ihre API auf und öffnen Sie WebSockets relativ zur Seite, damit sie im Netzwerk des Geräts, über den Tunnel und hinter einem Reverse-Proxy funktioniert.
  • Pushen, nie pollen: Senden Sie Updates über einen WebSocket.
  • Keine Plattform-Zugangsdaten im Browser: Die Seite spricht mit ihrem eigenen Server im Container, und dieser Server hält die SDK-Verbindung.

Um statt einer eigenen Seite die Web-HMI einer Maschine im Werksnetz anzuzeigen, leiten Sie sie über einen Reverse-Proxy in Ihrem Container weiter. Siehe Externe HMIs einbetten.

Mehrere Geräte

  • Jede Zeile trägt ihr Gerät: IronFlock speichert den Schlüssel des Geräts, das eine Zeile geschrieben hat, in device_key. Verwenden Sie device_key als Schlüssel für Tabellen pro Gerät, speichern Sie den Namen des Geräts in einer Spalte mit path: kwargs.DEVICE_NAME, und teilen Sie Diagramme nach diesem Namen auf.
  • Eine Geräteliste und eine Seite pro Gerät: eine Seitennavigation, die aus der Gerätetabelle aufgebaut wird, mit einer einzigen Seite, die für jedes Gerät funktioniert und ihre Widgets nach dem Gerät in der Route filtert. Siehe Boards erstellen.
  • Einstellungen pro Gerät oder pro Maschine: Verwenden Sie als Schlüssel der Einstellungstabelle eine Spalte, die das Ziel benennt (Zeilen, die das Board schreibt, tragen kein eigenes Gerät), und füllen Sie sie im Formular auf der Seite des Ziels aus. Jedes Gerät wendet die neueste Zeile für seinen eigenen Schlüssel an.
  • Ein Gerät, das ausscheidet, behält seine Zeilen. Wenn Benutzer es aus den Listen entfernen können sollen, geben Sie der Gerätetabelle eine deleted-Spalte und dem Board ein Formular, das sie setzt.

Checkliste

  1. Der gesamte Zustand liegt in Tabellen, jede mit einem einzigen Schreiber, der in ihrer Beschreibung genannt ist, und jede Spalte, die der Code schreibt, ist in data-template.yml deklariert.
  2. Jede Zeile hat einen tsp, und Zustandszeilen werden bei Änderung geschrieben, nur mit dem Schlüssel und den geänderten Spalten.
  3. Vom Board geschriebener Zustand wird mit den drei Schritten oben verfolgt, mit SDK 1.10.0 oder neuer, und der Code befasst sich nie mit der Verbindung oder ihren Sessions.
  4. Schreibvorgänge in Tabellen innerhalb lang laufender Schleifen behandeln den Fehler, den ein Schreibvorgang auslöst, wenn die Plattform eine Minute lang nicht erreichbar war, und erwartete Fehler werden mit reportError und einer userMessage gemeldet.
  5. Das Programm beendet sich bei SIGTERM, startet in der Exec-Form und protokolliert ungepuffert.
  6. Nichts pollt: Das Board nutzt Datenbindungen, Geräte abonnieren, Web-UIs pushen.
  7. Jeder Port, den Ihre App bereitstellt, ist in port-template.yml deklariert, seine URL stammt von getRemoteAccessUrlForPort und wird in eine Tabelle geschrieben, und das Board bettet sie von dort ein.
  8. Eine Compose-App hat kein env_file:, keine profiles: und keine ${VAR}-Host-Ports, legt Dateien in benannten Volumes ab und veröffentlicht nur die Ports, die Benutzer brauchen.

Mit der Starter-App beginnen

Das IronFlock-Plugin für Claude Code enthält eine Starter-App, die diesem Bauplan folgt: ein Programm, das auf jedem Gerät eine Maschine ausliest, den Zustand und die Messwerte jedes Geräts in Tabellen hält, den Grenzwert anwendet, den Benutzer auf dem Board setzen, und eine Live-Ansicht bereitstellt, die das Board auf der Seite jedes Geräts einbettet. Claude Code erstellt daraus eine neue App und passt sie an Ihren Anwendungsfall an.

Last updated on