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:
| Teil | Beschreibung | Definiert in |
|---|---|---|
| Edge-Code | Ein Container oder ein Docker-Compose-Stack auf jedem Gerät, auf dem die App installiert ist | Dockerfile oder docker-compose.yml |
| SDK | Die einzige Verbindung zwischen Ihrem Code und der Plattform: Tabellen, Echtzeit-Updates, Gerätefunktionen, Dateien | IronFlock SDK (Python, JavaScript, Go) |
| Tabellen | Der Zustand der App, in der eigenen Datenbank des Projekts | data-template.yml |
| Board | Die zentrale Benutzeroberfläche der App, live an die Tabellen gebunden, für alle Geräte | board-template.yml |
| Parameter | Einstellungen eines Geräts oder einer Gerätegruppe | env-template.yml |
| Remote Access | Tunnel zu Ports, auf denen Ihre Container Dienste anbieten, etwa eine Web-UI | port-template.yml |
| KI-Agenten | Agenten für den IronFlock-KI-Assistenten, mit Ihren Gerätefunktionen als Tools | ai-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
- 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.
- 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.
- 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
descriptionder Tabelle an, von wem. Zwei Schreiber derselben Zeilen überschreiben gegenseitig ihre Änderungen. - 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.
- 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.
- 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
| Daten | Wo | Geschrieben von | Gelesen von |
|---|---|---|---|
| Messwerte im Zeitverlauf | Eine Tabelle mit tsp | publishToTable, publishRowsToTable | Diagramme mit Zeitfenstern und Aggregation |
| Der aktuelle Zustand eines Geräts, einer Maschine oder eines Auftrags | Eine Entitätstabelle (maintainLatestFlagFor) | appendToTable: eine Zeile mit dem Schlüssel, tsp und den geänderten Spalten | Der latest-Filter |
| Grenzwerte, Sollwerte und Rezepte, die Benutzer festlegen | Eine Tabelle, in die das Formular des Boards schreibt | Das Formular | Jedes Gerät, wie unten gezeigt |
| Datensätze, die Benutzer anlegen, bearbeiten und löschen | Eine Entitätstabelle mit einer deleted-Spalte | Das Formular, das beim Löschen die Markierung setzt | Listen und Bearbeitungsseiten auf dem Board |
| Fehler und Hinweise für Bediener | Die error-logs-Tabelle | reportError mit einer userMessage | Das Toast-Widget des Boards |
| Bilder, Dokumente, Aufzeichnungen | Der Dateispeicher der App | files.put | Die URL der Datei in einer Spalte, angezeigt von einem Widget |
| Passwörter und API-Schlüssel | Eine Spalte mit secret: true | Jeder Schreibvorgang in die Tabelle | revealSecrets, verifySecret, nur in den eigenen Containern der App |
| Die Installation eines einzelnen Geräts: die Adresse einer Maschine, eine serielle Schnittstelle | App-Parameter | Die App-Einstellungen des Geräts oder der Gruppe | Umgebungsvariablen |
| Nachrichten zwischen Ihren Teilen, die später niemand mehr braucht | Ihre eigenen Topics | publish | subscribe |
| Eine Anfrage, die sofort die Antwort eines bestimmten Geräts braucht | Eine Gerätefunktion | registerDeviceFunction | callDeviceFunction, 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:
- Abonnieren Sie die Tabelle, bevor Sie sie lesen, damit keine Zeile in die Lücke zwischen Lesen und Subscription fällt.
- Lesen Sie die neueste Zeile einmal beim Start, mit dem
latest-Filter, und wenden Sie sie an. - 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.
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)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.ymlruft 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
callDeviceFunctionauf.
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(DEVoderPROD),TUNNEL_DOMAIN,DEVICE_LAN_IPundDEVICE_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
defaultValuedes jeweiligen Parameters. Eine Ausführung im Entwicklungsmodus erhält nur die Standardwerte. Geben Sie Parametern spezifische Namen (PLC_HOST, nichtHOST). - 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
/devdes 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 (
ENVistDEV). 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 inport-template.ymlden 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,devicesoderdevice_cgroup_rulesselbst. Eindevices:-Eintrag benennt Hardware, die vorhanden sein muss, sonst startet der Container nicht;device_cgroup_ruleszusammen 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:
- Lauschen Sie auf einem festen Port (gebunden an
0.0.0.0) und deklarieren Sie ihn inport-template.ymlmitprotocol: http. Eine Produktionsinstallation schaltet den Tunnel ein und stellt ihn über HTTPS bereit, WebSockets eingeschlossen. - 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. - 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 Siedevice_keyals Schlüssel für Tabellen pro Gerät, speichern Sie den Namen des Geräts in einer Spalte mitpath: 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
- 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.ymldeklariert. - Jede Zeile hat einen
tsp, und Zustandszeilen werden bei Änderung geschrieben, nur mit dem Schlüssel und den geänderten Spalten. - 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.
- 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
reportErrorund eineruserMessagegemeldet. - Das Programm beendet sich bei SIGTERM, startet in der Exec-Form und protokolliert ungepuffert.
- Nichts pollt: Das Board nutzt Datenbindungen, Geräte abonnieren, Web-UIs pushen.
- Jeder Port, den Ihre App bereitstellt, ist in
port-template.ymldeklariert, seine URL stammt vongetRemoteAccessUrlForPortund wird in eine Tabelle geschrieben, und das Board bettet sie von dort ein. - Eine Compose-App hat kein
env_file:, keineprofiles: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.