Ausdrücke
Bedingungen, Rückfallbedingungen, der ausschlaggebende Wert und die Platzhalter in Alarmtexten sind alle dieselbe Sprache, ausgewertet im selben Kontext. Alles, was eine Bedingung lesen kann, kann auch ein Text nennen.
Die Sprache ist CEL (Common Expression Language) — eine kleine, ausdrucksstarke Sprache für genau solche Bedingungen. Ein Ausdruck hat keinen Zugriff auf Uhrzeit, Netzwerk oder Datenbank; er sieht ausschließlich, was auf dieser Seite steht. Genau das macht es unbedenklich, Bedingungen von Benutzern schreiben zu lassen.
Die goldene Regel
Prüfen Sie vor jedem Aggregat, ob das Fenster Werte enthält.
values.size() > 0 && values.max() > props.level_max
Ein Aggregat über ein leeres Fenster ist ein Fehler, kein Nullwert — und ein Fehler bedeutet: der Lauf hat für dieses Objekt kein Ergebnis, weckt also nicht und klärt nicht. Ohne den vorangestellten values.size() > 0-Wächter hört eine Regel bei jedem Datenausfall stillschweigend auf zu arbeiten.
Das ist Absicht: ein Mittelwert über nichts hat keinen Wert, und stillschweigend 0 zu antworten würde ein leeres Fenster wie eine gemessene Null aussehen lassen — also genau die Lücke verdecken, die die Plattform bemerken soll.
Verfügbare Variablen
| Variable | Typ | Inhalt |
|---|---|---|
values | Liste | Die Werte des Fensters, zeitlich aufsteigend. Für ungültig erklärte Punkte sind nicht enthalten |
times | Liste von Zeitpunkten | Die Zeitstempel dazu, gleiche Länge und Reihenfolge wie values |
window.from | Zeitpunkt | Beginn des Fensters (gehört dazu) |
window.to | Zeitpunkt | Ende des Fensters (gehört nicht dazu) |
window.duration | Dauer | Länge des Fensters |
preceding.value | Wert | Der letzte Messwert vor dem Fenster. Fehlt, wenn im Rückblick keiner liegt |
preceding.time | Zeitpunkt | Sein Zeitstempel |
params.<x> | beliebig | Konstanten der Regel |
props.<x> | beliebig | Effektive Eigenschaften des Signals, einschließlich geerbter |
asset.props.<x> | beliebig | Eigenschaften des Assets, an dem das Signal hängt |
siblings.<name>.props.<x> | beliebig | Eigenschaften eines Nachbarobjekts, über seinen Namen |
signal.name | Text | Name des überwachten Signals |
signal.unit | Text | Seine Einheit. Wird mitgeführt, aber nie geprüft oder umgerechnet |
Drei Dinge, die man dazu wissen muss:
- Ein leeres Feld ist nicht vorhanden. Eine Eigenschaft ohne Wert — auch ein leerer Text — erscheint nicht.
has(props.x)antwortet dann falsch. - Zahlen sind Kommazahlen. Werte aus
params,propsundasset.propskommen als Kommazahl an, damit ein als80gepflegter Grenzwert sich sauber gegen jedes Aggregat vergleicht. Texte und Wahrheitswerte bleiben unverändert. - Nachbarobjekte tragen nur Eigenschaften, keine Messreihen.
siblingsgibt Zugriff auf Stammdaten, nicht auf die Werte eines zweiten Signals.
props.x an einem Objekt, das x nicht trägt, beendet den Lauf für dieses Objekt ohne Ergebnis — es wird nicht zu false. Wollen Sie einen fehlenden Wert behandeln, fragen Sie mit has(props.x) danach. Objekte, denen ein von der Regel gelesener Schlüssel vollständig fehlt, schließt die Regel von vornherein aus, siehe Welche Objekte überwacht werden.
Funktionen auf der Messreihe
Aufgerufen als Methode auf einer Liste, in der Regel auf values:
| Funktion | Ergebnis | Bedeutung |
|---|---|---|
values.size() | Ganzzahl | Anzahl der Werte im Fenster |
values.first() | Wert | Erster Wert des Fensters |
values.last() | Wert | Letzter Wert des Fensters |
values.min() | Kommazahl | Kleinster Wert |
values.max() | Kommazahl | Größter Wert |
values.sum() | Kommazahl | Summe |
values.mean() | Kommazahl | Arithmetisches Mittel |
values.median() | Kommazahl | Median |
values.stddev() | Kommazahl | Empirische Standardabweichung (mit n-1) |
values.mad() | Kommazahl | Median der absoluten Abweichungen vom Median — das ausreißerrobuste Streuungsmaß |
values.range() | Kommazahl | Spannweite, also Maximum minus Minimum |
values.quantile(p) | Kommazahl | Quantil, p zwischen 0 und 1, linear interpoliert |
values.delta() | Kommazahl | Größter Betrag eines Sprungs zwischen zwei aufeinanderfolgenden Werten |
delta() | Kommazahl | Dieselbe Funktion ohne Empfänger geschrieben |
slope(dauer) | Kommazahl | Steigung der Regressionsgeraden über das Fenster, in Signaleinheit je angegebener Dauer, z. B. slope(duration("1h")) |
maxGap() | Dauer | Größter Abstand zweier aufeinanderfolgender Punkte, einschließlich der Ränder |
x.abs() | Kommazahl | Betrag einer Kommazahl, z. B. (values.last() - values.first()).abs() |
maxGap() zählt die Ränder mit
Der linke Rand ist der letzte Punkt vor dem Fenster, der rechte das Fensterende. Damit erkennt die Funktion auch eine Lücke, die vor dem Fenster begonnen hat, und ein Signal, das innerhalb des Fensters verstummt ist. Gibt es keinen vorangehenden Punkt, ist der linke Rand der Fensterbeginn — über die Zeit davor ist nichts bekannt.
maxGap() ist die einzige dieser Funktionen, die ein leeres Fenster nicht als Fehler behandelt: ohne jeden Punkt ist die Lücke die ganze betrachtete Strecke, und das ist genau die Antwort, die eine Lückenerkennung braucht.
Wann eine Funktion mit einem Fehler endet
| Fall | Betroffene Funktionen |
|---|---|
| Leeres Fenster | Alle Aggregate sowie first() und last() — nicht maxGap() und nicht size() |
| Weniger als zwei Werte | stddev(), delta(), slope() |
p außerhalb von 0 bis 1 | quantile(p) |
| Dauer nicht positiv | slope(dauer) |
| Alle Punkte auf demselben Zeitstempel | slope(dauer) |
| Nicht-numerische Werte im Fenster | Alle rechnenden Aggregate. Ein Statussignal mit dem Wert "OK" hat keinen Mittelwert, und ihn zu erfinden würde auf Unsinn alarmieren |
Jeder dieser Fälle beendet den Lauf für dieses Objekt ohne Ergebnis — er weckt nicht und klärt nicht.
Sprachmittel von CEL
Neben den obigen Funktionen stehen die Standardmittel von CEL zur Verfügung:
| Mittel | Beispiel |
|---|---|
| Vergleiche und Rechnen | values.max() > 80.0, values.mean() - props.offset |
| Logik | &&, ||, !, Klammern |
| Bedingter Ausdruck | has(props.limit) ? props.limit : 100.0 |
| Existenzprüfung | has(props.level_max) |
| Dauer aus Text | duration("900s"), duration("15m") |
| Enthalten in | props.state in ["Betrieb", "Anfahren"] |
| Zeichenketten | signal.name.startsWith("Pegel"), .contains(...), .endsWith(...), .matches(...) |
| Listen-Makros | values.exists(v, v > 100.0), values.all(v, v > 0.0), values.filter(v, v > 80.0).size(), values.map(v, v * 2.0) |
Die Makros exists, exists_one, all, filter und map sind absichtlich nicht durch eigene Funktionen ersetzt. Ihre Durchläufe sind auf die Punktgrenze des Fensters beschränkt, eine Schleife kann also nicht davonlaufen.
Was nicht verfügbar ist, meldet die Prüfung beim Speichern — probieren ist hier billiger als raten.
Was ein Ausdruck ergeben muss
| Feld | Erwartetes Ergebnis |
|---|---|
| Ausdruck einer Kritikalitätsstufe | Wahrheitswert. Eine Regel, deren Bedingung eine Zahl ergibt, lässt sich nicht speichern |
| Rückfallbedingung | Wahrheitswert |
| Ausschlaggebender Wert | Beliebig — Zahl oder Text |
| Platzhalter im Alarmtext | Beliebig; das Ergebnis wird als Text eingesetzt |
Platzhalter in Texten
Zwischen {{ und }} steht ein gewöhnlicher Ausdruck:
Pegel {{ signal.name }} liegt bei {{ values.max() }} cm
Sie werden einmal beim Auslösen aufgelöst und mit dem Alarm gespeichert. Lässt sich einer nicht auswerten, bleibt er als geschriebener Text stehen — der Alarm entsteht trotzdem. Der ausschlaggebende Wert wird genauso formatiert wie in den Platzhaltern, damit derselbe Wert nicht an einer Stelle als 92 und an der anderen als 92.0 erscheint.
Grenzen
| Grenze | Wert | Bei Überschreitung |
|---|---|---|
| Punkte im Fenster | 50.000 | Der Lauf endet für dieses Objekt ohne Ergebnis. Verkleinern Sie das Fenster |
| Zeichen je Ausdruck | 4.000 | Die Regel lässt sich nicht speichern |
| Rechenzeit je Auswertung | 100 ms | Der Lauf endet für dieses Objekt ohne Ergebnis |
Kleine Rezeptsammlung
| Aufgabe | Ausdruck |
|---|---|
| Grenzwert überschritten | values.size() > 0 && values.max() > props.level_max |
| Mittelwert über Grenzwert | values.size() > 0 && values.mean() > props.motor_temp_max |
| Unterschreitung | values.size() > 0 && values.min() < props.tank_level_min |
| Kein Wert oder große Lücke | values.size() == 0 || maxGap() > duration(params.max_gap) |
| Eingefrorenes Signal | values.size() > 1 && values.range() < 0.01 |
| Sprung zwischen zwei Werten | values.size() > 1 && values.delta() > props.max_step |
| Anhaltender Anstieg | values.size() > 1 && slope(duration("1h")) > props.max_rise_per_hour |
| Ausreißer gegen robuste Streuung | values.size() > 2 && (values.last() - values.median()).abs() > 3.0 * values.mad() |
| Grenzwert nur, wenn gepflegt | has(props.level_max) && values.size() > 0 && values.max() > props.level_max |
| Nur im Betrieb prüfen | asset.props.state == "Betrieb" && values.size() > 0 && values.max() > props.level_max |
Vollständige Regeln mit Zeitplan, Entprellung und Texten stehen unter Beispiele und Fehlersuche.