Trigger
Was ist das
Ein Trigger löst eine bestehende Aktion aus, ohne dass ein Bediener sie anstößt. Es gibt fünf Arten. Ein schedule-Trigger läuft nach einem Cron-Zeitplan mit fünf Feldern. Ein event-Trigger reagiert auf ein Ereignis im System, wahlweise mit einer zusätzlichen Bedingung. Ein webhook-Trigger wartet auf einen Aufruf von außen: Ein externes System ruft POST /api/hooks/{name} auf. Ein signal-Trigger reagiert auf einen Telemetrie-Wert einer Maschine. Ein file-Trigger reagiert auf eine neue Datei in einem überwachten Ordner.
Jeder Lauf eines Triggers ist im Audit-Protokoll nachvollziehbar, als Systemakteur trigger:<name>. So lässt sich auch ein automatisch ausgelöster Lauf noch einer Ursache zuordnen.
Wann einsetzen
- Fertigmeldung ans Zielsystem. Sobald der letzte Schritt eines Auftrags gebucht ist, meldet ein
event-Trigger das fertige Los automatisch weiter. Der Bediener muss dafür keine eigene Schaltfläche betätigen. - Nachtläufe. Ein
schedule-Trigger räumt regelmäßig auf, etwa indem er zu einer festen Uhrzeit noch offene Vorgänge schließt. - Eingehende Meldungen externer Systeme. Ein Rendering ist fertig, ein Auftrag wurde im ERP angelegt: Ein
webhook-Trigger nimmt solche Meldungen von außen entgegen und stößt die passende Aktion an. - Dateien aus einem Vorsystem. Ein System, das keine Schnittstelle anbietet, legt seine Daten als Datei in einem Ordner ab. Ein
file-Trigger holt jede neue Datei dort ab und übergibt sie an die passende Aktion.
Anatomie der Definition
Eine Trigger-Definition ist ein flaches JSON-Objekt:
name: der Bezeichner des Triggers; er erscheint auch als Systemakteurtrigger:<name>im Audit-Protokoll.summary: eine menschenlesbare Zeile, die den Trigger beschreibt.type:schedule,event,webhook,signaloderfile.schedule(nur beitype: schedule): ein Cron-Ausdruck mit fünf Feldern, zum Beispiel0 2 * * *für jede Nacht um zwei Uhr.match(nur beitype: event): ein Objekt mitevent_type(der Ereignistyp, auf den der Trigger reagiert) und optionalcondition(ein Bedingungsbaum in derselben Bedingungssyntax wie daswheneiner Aktion). Ohneconditionreagiert der Trigger auf jedes Ereignis dieses Typs.webhook(nur beitype: webhook): ein Objekt mitsecret, dem Namen eines Secrets, nie dessen Wert. Der Aufrufer schickt den Wert dieses Secrets als Bearer-Header mit.file(nur beitype: file): ein Objekt mitdir, dem überwachten Unterordner des Datei-Eingangs, und optionalpattern, einem Dateimuster wie*.pdf. Ohnepatterngilt jede Datei im Ordner.action: der Name der Aktion, die der Trigger auslöst. Diese Aktion muss bereits existieren.inputs: die Zuordnung der Aktions-Parameter zu ihren Werten. Bei einemwebhook-Trigger stammen diese Werte oft aus dem Body des HTTP-Requests, über Platzhalter wie${body.order_number}.run_as: die Rolle, mit der die Aktion läuft (operator,planneroderadmin).enabled: schaltet den Trigger ein oder aus.
Ruft ein externes System den Webhook ohne den passenden Bearer-Header oder mit einem falschen Wert auf, lehnt WERKSPUR den Aufruf ab. Ein Aufruf ohne gültiges Secret läuft nie durch.
Beispiele
Der erste Trigger stammt aus der gebündelten Beispielkonfiguration, die beiden anderen sind Definitionen zum Nachbauen.
{
"name": "fertigmeldung-trigger",
"summary": "Meldet jedes fertige Los automatisch an das ERP.",
"type": "event",
"match": {
"event_type": "StepCompleted",
"condition": {
"and": [
{
"cmp": {
"left": {
"param": "data.done"
},
"op": "eq",
"right": true
}
},
{
"cmp": {
"left": {
"param": "data.qty_good"
},
"op": "gt",
"right": 0
}
}
]
}
},
"action": "fertigmeldung",
"inputs": {
"lot": "${event.data.lot}",
"order": "${event.data.order_number}",
"qty": "${event.data.qty_good}"
},
"run_as": "operator",
"enabled": true
}Ein event-Trigger auf StepCompleted. Die Bedingung verlangt data.done == true und zusätzlich data.qty_good > 0: Ein Los, das mit Ausschuss endet (qty_good gleich null), gilt zwar auch als `done`, soll aber keine Fertigmeldung auslösen. Der Trigger ruft die Aktion fertigmeldung auf und übergibt Los, Auftrag und Menge aus den Ereignisdaten.
{
"name": "nachtabschluss",
"summary": "Schließt jede Nacht alle noch offenen Chargen ab",
"type": "schedule",
"schedule": "0 2 * * *",
"action": "chargen-abschliessen",
"run_as": "planner",
"enabled": true
}Ein schedule-Trigger mit dem Cron-Ausdruck 0 2 * * *, läuft also jede Nacht um zwei Uhr. Er ruft eine Aktion auf, die jedes noch offene Los abschließt und den Abschluss an ein Zielsystem meldet.
{
"name": "erp-auftrag-webhook",
"summary": "Externes System stößt die Fertigmeldung eines Loses an",
"type": "webhook",
"webhook": {
"secret": "erp-webhook-secret"
},
"action": "fertigmeldung",
"inputs": {
"lot": "${body.lot}",
"order": "${body.order_number}",
"qty": "${body.qty}"
},
"run_as": "planner",
"enabled": true
}Ein webhook-Trigger. webhook.secret verweist auf ein Secret nur über dessen Namen, nie über den Wert selbst. Die Aktion fertigmeldung braucht drei Parameter, lot, order und qty; inputs liest deshalb alle drei aus dem Body des HTTP-Requests, ${body.lot}, ${body.order_number} und ${body.qty}. Fehlt einer davon in den inputs, scheitert jeder Aufruf des Webhooks.
Mit dem Assistenten arbeiten
Jedes Beispiel oben zeigt den Prompt, mit dem Sie denselben Trigger über den Assistenten anlegen lassen können, mit type, schedule oder match oder webhook, der Zuordnung der inputs und einer passenden run_as-Rolle. Besonders bei einem Webhook-Trigger erfindet der Assistent das Secret nie selbst: Er referenziert nur einen bereits im Secret-Speicher vorhandenen Namen. Wie der Assistent grundsätzlich arbeitet, beschreibt das Kapitel Der KI-Assistent.
Fallstricke
- Body des HTTP-Requests und Platzhalter sind getrennt. WERKSPUR liest höchstens die ersten 1 MiB und dekodiert einen nicht leeren gelesenen Body als JSON-Objekt; Arrays, Strings, Zahlen und Boolean-Werte erhalten 400.
nullverhält sich wie ein leeres Objekt. Ein größerer Body wird nicht über einen eigenen 413-Pfad abgelehnt. Seine abgeschnittene JSON-Darstellung ergibt in der Regel 400, doch Aufrufer dürfen sich oberhalb der Grenze nicht auf ein bestimmtes Ergebnis verlassen. Ein Status 422 entsteht, wenn ein${body.<pfad>}in der gespeicherten Triggerdefinition fehlt oder fehlerhaft ist. Eine solche Zeichenfolge in einem gewöhnlichen Datenwert löst für sich allein keinen Fehler aus. - Ereignis-Schleifen. Ereignisse, die aus einem Trigger-Lauf stammen, lösen selbst keine
event-Trigger mehr aus. Diese Schleifen-Sperre gilt für alle Folge-Ereignisse eines Trigger-Laufs, nicht nur für solche desselben Typs. run_asersetzt nichtmin_role.run_aslegt fest, mit welcher Rolle die Aktion läuft. Diemin_roleder Aktion gilt trotzdem: Ein Trigger kann keine Aktion mit einer Rolle laufen lassen, die unter derenmin_roleliegt.enabled: falselöscht nichts. Ein deaktivierter Trigger bleibt als Definition bestehen, reagiert aber nicht mehr auf Auslöser. Entfernen Sie die Definition, wenn der Trigger dauerhaft nicht mehr benötigt wird.
Signal-Trigger
Ein signal-Trigger beobachtet einen einzelnen Telemetrie-Wert einer Maschine und löst eine bestehende Aktion aus, sobald der Wert eine Bedingung erfüllt. Der beobachtete Tag muss eine vorhandene Telemetrie-Bindung dieser Maschine sein, über OPC UA oder Modbus. Jeder Signal-Trigger überwacht genau einen Tag genau einer Maschine.
Es gibt drei Bedingungsarten.
Die Boolesche Flanke feuert im Moment des Übergangs. Sie wählen steigend, fallend oder beide. Ein Trigger auf die steigende Flanke eines Fertig-Bits feuert einmal, sobald das Bit von 0 auf 1 wechselt.
Der Schwellwert feuert, wenn der Wert eine Grenze überschreitet oder unterschreitet. Eine Hysterese verhindert wiederholtes Feuern am Rand. Bei Wert 80, wieder scharf ab 75 feuert der Trigger, sobald der Wert 80 überschreitet, und wird erst wieder scharf, wenn der Wert unter 75 fällt. Ohne eigenen Wert liegt der Punkt zum erneuten Scharfstellen auf dem Feuerwert. Optional verlangt „muss anliegen (Sekunden)“, dass der Wert die Grenze für eine Haltezeit überschreitet, bevor der Trigger feuert.
Wert geändert feuert bei jedem Unterschied zum vorherigen Wert. Das funktioniert auch für Zeichenketten, etwa einen gescannten Barcode. Denselben Wert zweimal hintereinander sieht diese Bedingung nicht.
Nach dem Start, nach einer Änderung der Definition und nach einer wiederhergestellten Verbindung setzt der erste Wert nur den Zustand. In diesem Moment feuert nichts.
Schreiben Sie in der SPS zuerst die Nutzdaten und zuletzt den auslösenden Tag. Legen Sie also erst den Barcode ab und erhöhen Sie danach den Scan-Zähler. Feuern Sie auf einen Scan-Zähler oder ein Umschalt-Bit, nie auf den Barcode selbst. Denselben Barcode zweimal hintereinander sieht die Bedingung „Wert geändert“ nicht, ein Zähler dagegen ändert sich bei jedem Scan.
In den inputs der Aktion steht der auslösende Wert als ${signal.value} bereit. Über ${tags.<name>} erreichen Sie die übrigen aktuellen Tag-Werte derselben Maschine, etwa ${tags.barcode} für den zuletzt abgelegten Barcode.
Scheitert ein Feuern, erscheint der Lauf in der Lauf-Liste des Triggers. Einen erneuten Versuch gibt es nicht.
Datei-Trigger
Ein file-Trigger überwacht einen Unterordner des Datei-Eingangs. Jede neue Datei in diesem Ordner löst die hinterlegte Aktion einmal aus. So kommt auch ein Vorsystem ohne eigene Schnittstelle an WERKSPUR heran: Es legt eine Datei ab, den Rest übernimmt die Aktion.
Der Datei-Eingang ist ein Verzeichnis auf dem Server, das der Compose-Stack in die Anwendung einhängt. FOUNDRY_INBOX_DIR in deploy/.env nennt dieses Verzeichnis, standardmäßig ./inbox. Die Anwendung läuft nicht als root. Das Verzeichnis muss deshalb für die Benutzer-ID 10001 beschreibbar sein, sonst kann WERKSPUR die abgelegten Dateien weder verschieben noch aufräumen.
Im Trigger-Editor wählen Sie als Art „Datei“ und tragen zwei Werte ein: den Ordner im Eingang, etwa druckauftraege, und ein Dateimuster, etwa *.pdf. Bleibt das Muster leer, gilt jede Datei. Der Ordner darf verschachtelt sein, etwa werk1/druckauftraege, muss aber innerhalb des Eingangs bleiben. Die Namen done, error und processing sind für den Trigger reserviert und dürfen im Ordnerpfad nicht vorkommen. Zwei eingeschaltete Datei-Trigger dürfen nicht denselben Ordner überwachen; WERKSPUR lehnt die zweite Definition ab.
Der Weg einer Datei führt über drei Ordner. Der Trigger sieht in kurzen Abständen im überwachten Ordner nach und greift eine Datei erst auf, wenn Größe und Änderungszeitpunkt seit dem vorherigen Durchgang gleich geblieben sind. Eine Datei, die gerade geschrieben wird, wartet er auf diese Weise ab. Danach verschiebt er sie nach processing/, ruft die Aktion auf und legt sie anschließend in done/ ab. Scheitert die Aktion, landet die Datei stattdessen in error/. Alle drei Ordner legt WERKSPUR selbst im überwachten Ordner an.
Einen automatischen zweiten Versuch gibt es nicht. Eine Datei aus error/ wiederholen Sie, indem Sie sie erneut in den überwachten Ordner legen. Scheitert ein Lauf, nennt die Fehlermeldung in der Lauf-Liste des Triggers den Dateinamen.
FOUNDRY_INBOX_RETENTION_DAYS legt fest, wie alt eine Datei in done/ oder error/ werden darf, bevor WERKSPUR sie löscht, standardmäßig 30 Tage. Maßgeblich ist der Änderungszeitpunkt der Datei selbst, nicht der Zeitpunkt der Verarbeitung. Eine Datei, die mit einem alten Zeitstempel ankommt, kann deshalb schon beim nächsten Aufräumen gelöscht werden. Der Wert 0 behält sie unbegrenzt. Dateien im überwachten Ordner selbst und in processing/ werden davon nie gelöscht.
In den inputs der Aktion stehen vier Werte der aufgegriffenen Datei bereit:
${file.path}: der vollständige Pfad der Datei, solange sie inprocessing/liegt. Diesen Wert erwartet das Kommandofile.send.${file.name}: der Dateiname, unter dem die Datei abgelegt wurde.${file.size}: die Größe in Bytes.${file.ext}: die Endung samt Punkt, etwa.pdf.
Ein Platzhalter mit einem anderen Feldnamen lässt den Lauf scheitern, und die Datei wandert nach error/.
Das folgende Beispiel schickt jedes PDF aus dem Ordner druckauftraege an ein Drucksystem. Zuerst der Trigger (kein lauffähiges Beispiel, nur die Form):
{
"name": "druckauftraege",
"summary": "PDFs aus dem Datei-Eingang an das Drucksystem schicken.",
"type": "file",
"file": {"dir": "druckauftraege", "pattern": "*.pdf"},
"action": "druckauftrag-senden",
"inputs": {"datei": "${file.path}"},
"run_as": "operator",
"enabled": true
}
Die Aktion druckauftrag-senden hat einen Param datei und einen einzigen Schritt. Das Kommando file.send überträgt die Bytes der Datei unverändert an ein registriertes Zielsystem:
{
"command": "file.send",
"bind": {
"dest": {"literal": "drucksystem"},
"method": {"literal": "POST"},
"path": {"literal": "/print"},
"file": {"param": "datei"}
},
"no_compensation": "Ein abgeschickter Druckauftrag lässt sich nicht zurückholen."
}
file.send nimmt nur Pfade unterhalb des Datei-Eingangs an. Jeden anderen Pfad lehnt es ab, bevor es etwas sendet.
Drei Punkte für den Betrieb:
- Nicht jede Datei wird abgeholt. Dateien mit der Endung
.tmpoder.partund Dateien, deren Name mit einem Punkt beginnt, übergeht der Trigger. Ein Vorsystem, das unter einem solchen Namen schreibt und erst am Ende umbenennt, kann deshalb keine halbe Datei durchreichen. - Ein ausgeschalteter Trigger holt nichts ab. Bei
enabled: falsebleiben die Dateien im Ordner liegen. Sobald Sie den Trigger wieder einschalten, holt er sie ab. - Nach einem Absturz ist unklar, ob der Lauf durchgegangen ist. Endet die Anwendung zwischen Aufruf und Ablage, bleibt die Datei in
processing/liegen. Beim nächsten Start verschiebt WERKSPUR sie nacherror/: Ob das Zielsystem sie bekommen hat, lässt sich hier nicht feststellen. Prüfen Sie das dort nach, bevor Sie die Datei erneut ablegen.
Wie eine Aktion selbst aufgebaut ist, zeigt das Kapitel Aktionen. Wie ein Zielsystem eine solche Fertigmeldung entgegennimmt, zeigt das Kapitel Zielsysteme. Eingehende Queue-Ereignisse beschreibt AMQP-Listener. Webhook-Bearer und Sitzungstoken grenzt API & Authentifizierung voneinander ab. Wie eine Ansicht Daten aus einer gespeicherten Abfrage anzeigt, beschreibt Ansichten. Wie eine gespeicherte Abfrage selbst funktioniert, erklärt Gespeicherte Abfragen.