Funktionen

Jeder berechnete Datenpunkt hat eine Funktion, die aus den Werten seiner Eingänge seinen eigenen Wert berechnet. Diese Seite zeigt, wie Funktionen berechnet werden, welche Regeln deine Daten sauber halten, und enthält Vorlagen für die häufigsten Aufgaben.

Eine Funktion ist ein kurzer Ausdruck, zum Beispiel:

get(UpstreamValues, DependencyIds[0]) * 0.001

Kopiere eine Vorlage in das Feld Funktion des Datenpunkts und passe die Zahlen an, die als Parameter beschrieben sind. Wie du einen berechneten Datenpunkt erstellst, steht unter Daten verknüpfen.


Wie Funktionen berechnet werden

  • Einmal pro Minute und Eingang. Messwerte werden in Minuten zusammengefasst. Meldet ein Zähler mehrmals in derselben Minute, zählt der letzte Wert.
  • Der vorherige Wert ist der letzte bekannte. Hat ein Zähler eine Stunde lang nicht gemeldet, ist sein vorheriger Wert der von vor einer Stunde.
  • Nur wenn sich etwas ändert. Ein Datenpunkt wird berechnet, wenn in dieser Minute mindestens eine Datenquelle gemeldet hat, von der er abhängt. Meldet in derselben Minute ein weiterer Eingang, wird er erneut berechnet.
  • «Kein Wert» ist ein gültiges Ergebnis. Eine Funktion liefert eine Zahl oder NaN(), also keinen Wert. Mit NaN() wird für diese Minute nichts gespeichert. Jede Zahl wird gespeichert, auch 0.
  • Änderungen gelten ab jetzt. Eine geänderte Funktion gilt für frühere Zeiträume erst nach einer Nachberechnung. Wende dich dafür an den ROOCKiE Support.

Was eine Funktion verwenden kann

NameBedeutung
DependencyIdsDie Eingänge in der Reihenfolge, in der du sie gewählt hast. DependencyIds[0] ist der erste.
UpstreamValuesDer aktuelle Wert jedes Eingangs.
PreviousUpstreamValuesDer letzte bekannte Wert jedes Eingangs vor dieser Minute.
get(UpstreamValues, DependencyIds[0])Der aktuelle Wert des ersten Eingangs.
IsNaN(x)Wahr, wenn x keinen Wert hat.
NaN()Kein Wert.

Funktionen sind in der Sprache Expr geschrieben: Rechenzeichen, Vergleiche, Bedingung ? a : b, Variablen mit let und Listenfunktionen wie filter, map, sum, all und any. Dezimalzahlen schreibst du mit Punkt: 0.001.


Regeln für saubere Daten

Eingänge über ihre Position lesen. Schreibe get(UpstreamValues, DependencyIds[0]). Eine fest eingetragene ID funktioniert nur, solange genau dieser Eingang angehängt ist, und UpstreamValues[…] liefert für einen fehlenden Eingang 0 statt «kein Wert».

Die Bedingung für den Wert schreiben, den du behältst. Schreibe Bedingung ? Wert : NaN(). Ein fehlender Eingang erfüllt keinen Vergleich und landet so immer bei NaN(). Die Form wert > 0 ? wert : 0 macht aus einem fehlenden Messwert eine gespeicherte 0.

0 nur für eine gemessene Null. Alles Unplausible, etwa negativer Verbrauch oder ein Wert ausserhalb des Messbereichs, wird zu NaN(). Eine fehlende Minute lässt die Summen unverändert; ein falscher Wert landet in jeder Summe, die darauf aufbaut.

Die Einheit entscheidet, was ein fehlender Eingang bedeutet.

Art des WertsEinheitenEin fehlender Eingang bedeutet
Menge pro IntervallEnergie, Volumen, Dauer, StreckeEs wurde nichts gezählt, sofern der Eingang selbst eine Menge ist, etwa ein Verbrauch.
Zustand oder LeistungLeistung, Prozent, Strom, Spannung, DurchflussUnbekannt. Nie als 0 behandeln.

Ein Zählerstand, der nur steigt, ist keines von beiden. Mach daraus zuerst einen Verbrauch mit Verbrauch aus Zählerstand.

Plausibilität dort prüfen, wo die Daten hereinkommen, nicht wo sie verrechnet werden. Eingänge melden selten in derselben Minute; ein Viertelstundenzähler meldet einmal pro Viertelstunde. Ein Datenpunkt, der mehrere Mengen addiert oder abzieht, wird deshalb meist nur aus einem Teil seiner Eingänge berechnet. Seine Minuten ergeben erst über das Intervall das richtige Total. Meldet der Gesamtzähler in Minute 2 10 kWh und ein Unterzähler in Minute 9 4 kWh, speichert der Rest 10 und −4, zusammen 6 kWh für die Viertelstunde. Würde die −4 verworfen, wären es 10. Prüfungen gehören deshalb auf die Datenpunkte, die eine Datenquelle lesen. Summen und Differenzen bleiben ohne Prüfung.


Vorlagen

Wert übernehmen

Der Eingang unverändert, wenn er schon die Einheit und Bedeutung des Datenpunkts hat. Auch für denselben Wert in einer zweiten Bilanz.

get(UpstreamValues, DependencyIds[0])

Fehlend ergibt keinen Wert. 0 und negative Werte bleiben.

Einheit umrechnen

Der Eingang mal einen Faktor.

get(UpstreamValues, DependencyIds[0]) * 0.001

Parameter: 0.001 rechnet W in kW, Wh in kWh und l in m³ um, 1000 umgekehrt. Ein Anteil wie 0.4 teilt einen Zähler auf; die Anteile eines Zählers ergeben zusammen 1. Ein negativer Faktor dreht das Vorzeichen eines verkehrt eingebauten Zählers um.

Fehlend ergibt keinen Wert. 0 und negative Werte bleiben, umgerechnet.

Negative Werte verwerfen

Für Werte, die nicht negativ sein können, etwa Verbrauchsleistung oder Durchfluss.

let wert = get(UpstreamValues, DependencyIds[0]);
wert >= 0 ? wert : NaN()

Manche Sensoren melden im Leerlauf kleine negative Werte. Um diese zu 0 zu machen und echte Fehler trotzdem zu verwerfen:

let wert = get(UpstreamValues, DependencyIds[0]);
wert >= 0 ? wert : wert > -0.05 ? 0.0 : NaN()
EingangErste FunktionMit Toleranz −0.05
12.512.512.5
000
−0.01kein Wert0
−5kein Wertkein Wert
fehltkein Wertkein Wert

Verwende nicht max(wert, 0): Damit werden echte Fehler zu Nullen.

Plausibler Bereich

Verwirft Messwerte ausserhalb der physikalischen Grenzen.

let wert = get(UpstreamValues, DependencyIds[0]);
wert >= 0 && wert <= 100 ? wert : NaN()

Parameter: der kleinste und der grösste plausible Wert, beide eingeschlossen.

Verbrauch aus Zählerstand

Macht aus einem steigenden Zählerstand (Wh, kWh, m³) den Verbrauch pro Minute. Für die Energiebilanz ist das der Datenpunkt, den du zuordnest: Sie braucht die Energie pro Intervall, nicht den Zählerstand.

let aktuell = get(UpstreamValues, DependencyIds[0]);
let vorher = get(PreviousUpstreamValues, DependencyIds[0]);
let delta = aktuell - vorher;
aktuell > 0 && vorher > 0 && delta > 0 ? delta * 0.001 : NaN()

Parameter:

  • 0.001 rechnet die Einheit des Zählers in die des Datenpunkts um: Wh in kWh 0.001; für kWh in kWh lässt du * 0.001 weg; für Gas in m³ nach kWh nimmst du Brennwert mal Zustandszahl aus der Gasrechnung (rund 10.5).
  • delta > 0 lässt Minuten ohne Verbrauch aus. Mit delta >= 0 wird stattdessen eine 0 gespeichert (siehe unten).
  • Optionale Grenze: Mit && delta <= 720000 werden unplausible Sprünge verworfen (siehe unten).
SituationErgebnis
Zählerstand steigt von 1000 auf 1500 Wh0.5 kWh
Zählerstand unverändertkein Wert (0 mit delta >= 0)
Erster Messwert überhauptkein Wert
Zähler zurückgesetzt, 1000 auf 10kein Wert
Zähler meldet kurz 0kein Wert, auch in der Minute danach
Zählerwechsel, Stand springt auf 900000899 kWh (kein Wert mit Grenze)

Warum die Prüfungen nötig sind. Zähler melden gelegentlich einen Stand von 0. Ohne aktuell > 0 && vorher > 0 würde die nächste Minute den ganzen Zählerstand als Verbrauch zeigen, eine einzelne Spitze von vielen Megawattstunden. Ohne delta > 0 würde ein zurückgesetzter oder ersetzter Zähler einen grossen negativen Verbrauch speichern. Der Preis: Der Verbrauch der ein bis zwei Minuten rund um eine gemeldete 0 fehlt.

Grenze für Sprünge. Ein neuer Zähler kann mit einem viel höheren Stand beginnen; das erscheint als ein riesiger Verbrauch. Eine Grenze verwirft solche Sprünge, aber auch echten Verbrauch nach einem Ausfall, denn der erste Messwert nach einer Lücke enthält alles, was während der Lücke verbraucht wurde. Bemiss sie als höchste Leistung × längster Ausfall, den du aufholen willst: 30 kW während 24 Stunden sind 720 kWh, also 720000 bei einem Zähler in Wh. Verbrauch über der Grenze fehlt in allen Summen.

Minuten ohne Verbrauch auslassen oder als 0 speichern? Beides ergibt dieselben Summen. Auslassen (delta > 0) speichert weniger und ist die Vorgabe; im Diagramm erscheinen dafür nachts Lücken. Mit delta >= 0 entsteht eine durchgehende Linie, und eine gespeicherte 0 bedeutet dann «der Zähler hat gemeldet, verbraucht wurde nichts», eine Lücke weiterhin «kein Messwert». Speichere Nullen, wenn der Datenpunkt in eine Summe vollständiger Eingänge einfliesst.

Einspeisung aus Zweirichtungszähler

Für einen einzelnen Zählerstand, der rückwärts zählt, während Energie ins Netz eingespeist wird. Für den Bezug auf derselben Datenquelle nimmst du Verbrauch aus Zählerstand mit Grenze.

let aktuell = get(UpstreamValues, DependencyIds[0]);
let vorher = get(PreviousUpstreamValues, DependencyIds[0]);
let delta = vorher - aktuell;
aktuell > 0 && vorher > 0 && delta > 0 && delta <= 50000 ? delta * 0.001 : NaN()

Die Grenze (50000) ist hier Pflicht: Ein zurückgesetzter Zähler sieht genau wie Einspeisung aus, und nur die Grenze verhindert, dass daraus eine riesige Einspeisung wird. Sie fängt aber nur grosse Sprünge ab. Fällt der Stand beim Zurücksetzen oder bei einem Zählerwechsel um weniger als die Grenze, zum Beispiel von 1000 auf 10 Wh, zählt die Funktion das als Einspeisung. Prüfe deshalb nach jedem Zurücksetzen und jedem Zählerwechsel die Einspeisung dieser Minute. Ist sie falsch, wende dich an den ROOCKiE Support, um sie zu korrigieren. Hat dein Zähler getrennte Zählerstände für Bezug und Einspeisung, nimm zwei Datenquellen und zweimal Verbrauch aus Zählerstand.

Verbrauch aus Füllstand

Verbrauch aus dem Füllstand eines Tanks, etwa Heizöl, Pellets oder Wasser. Befüllungen werden ignoriert.

let aktuell = get(UpstreamValues, DependencyIds[0]);
let vorher = get(PreviousUpstreamValues, DependencyIds[0]);
let verbraucht = vorher - aktuell;
aktuell > 0 && vorher > 0 && verbraucht > 0 && verbraucht <= 500 ? verbraucht : NaN()

Parameter: 500 ist die grösste plausible Abnahme zwischen zwei Messwerten. Abnahmen darüber, etwa ein Sensor, der kurz einen viel zu tiefen Stand meldet, werden verworfen.

Ein Füllstand von 0 wird immer verworfen, weil ein Sensor ihn auch bei einer Störung meldet und die Funktion das nicht unterscheiden kann. Läuft der Tank wirklich leer, fehlt deshalb der Verbrauch der letzten Messung bis ganz leer, und ebenso die erste Abnahme nach der Messung von 0.

Energie aus Leistung

Energie pro Minute aus einem Leistungswert.

get(UpstreamValues, DependencyIds[0]) / 60

Parameter: 60 rechnet kW in kWh pro Minute um, 60000 W in kWh.

Summe

Addiert alle Eingänge, die einen Wert haben. Das ist die vorgeschlagene Funktion, wenn ein Datenpunkt mehrere Datenpunkte als Eingänge hat.

let filtered = filter(values(UpstreamValues), {!IsNaN(#)}); len(filtered) > 0 ? sum(filtered) : NaN()
EingängeErgebnis
2, 35
2, fehlt2
alle fehlenkein Wert

Für Mengen wie Energieverbrauch, bei denen ein fehlender Eingang bedeutet, dass nichts verbraucht wurde. Für Leistungen und andere Zustände wird das Total zu klein, sobald ein Eingang fehlt; nimm dafür die Summe vollständiger Eingänge. Um das Total umzurechnen, schreibst du sum(filtered) * 0.001.

Summe vollständiger Eingänge

Die Summe nur dann, wenn jeder Eingang einen Wert hat.

none(values(UpstreamValues), {IsNaN(#)}) ? sum(values(UpstreamValues)) : NaN()
EingängeErgebnis
2, 35
2, fehltkein Wert

Für Zustände und Leistungen, etwa die Gesamtleistung mehrerer Abgänge. Jeder Eingang muss jede Minute melden.

Gewichtete Summe

Jeder Eingang mit seinem eigenen Faktor; fehlende Eingänge werden übersprungen.

let filtered = filter([get(UpstreamValues, DependencyIds[0]) * 0.4, get(UpstreamValues, DependencyIds[1])], {!IsNaN(#)}); len(filtered) > 0 ? sum(filtered) : NaN()

Schreibe einen Listeneintrag pro Eingang, in der Reihenfolge der Eingänge. Der Datenpunkt braucht mindestens so viele Eingänge, wie die Liste Einträge hat.

Rest

Der erste Eingang minus alle anderen, zum Beispiel der nicht gemessene Rest eines Gebäudes. Die Funktion liest jeden Eingang (val0, val1, …), macht daraus einen Wert, der nie fehlt (safe0, safe1, …), und rechnet erst dann (res). Mit drei Eingängen:

let val0 = get(UpstreamValues, DependencyIds[0]);
let val1 = get(UpstreamValues, DependencyIds[1]);
let val2 = get(UpstreamValues, DependencyIds[2]);
let safe0 = IsNaN(val0) ? 0.0 : val0;
let safe1 = IsNaN(val1) ? 0.0 : val1;
let safe2 = IsNaN(val2) ? 0.0 : val2;
let res = safe0 - safe1 - safe2;
res
Eingänge (erster − andere)Ergebnis
10 − 3 − 25
10 − fehlt − 28
fehlt − 3 − 2−5
alle ohne Wert0

Für Mengen pro Intervall. Der Datenpunkt braucht genau so viele Eingänge, wie die Funktion val-Zeilen hat: Mit weniger Eingängen wird er nicht mehr berechnet, zusätzliche Eingänge werden ignoriert. Kommt ein Unterzähler dazu, hängst du ihn als Eingang an und ergänzt val3, safe3 und - safe3.

Negative Werte in einzelnen Minuten sind normal, wenn die Eingänge zu verschiedenen Zeiten melden, und heben sich über die Viertelstunde auf (siehe Regeln für saubere Daten). Beurteile das Ergebnis an Viertelstunden- oder Tageswerten.

Die Funktion läuft nur in Minuten, in denen mindestens ein Zähler hinter ihren Eingängen gemeldet hat; in den übrigen Minuten speichert sie nichts. Läuft sie, und keiner ihrer Eingänge hat einen Wert, etwa weil ein Verbrauch aus Zählerstand eine Minute ohne Verbrauch auslässt, ergibt sie 0, für eine Menge also «nichts gezählt». Willst du Minuten mit dem Ergebnis 0 auslassen, schreibe als letzte Zeile res == 0.0 ? NaN() : res. Die Summen bleiben gleich, im Diagramm erscheinen dafür Lücken.

Nur wenn alle Eingänge in derselben Minute melden, etwa alle vom selben Gerät, darfst du negative Ergebnisse verwerfen. Schreibe dann als letzte Zeile res > 0 ? res : NaN().

Für Zustände und Leistungen verlangst du jeden Eingang und rechnest direkt mit den val-Werten. Der Datenpunkt hat dann nur in Minuten einen Wert, in denen alle Eingänge melden:

let val0 = get(UpstreamValues, DependencyIds[0]);
let val1 = get(UpstreamValues, DependencyIds[1]);
let val2 = get(UpstreamValues, DependencyIds[2]);
let res = val0 - val1 - val2;
res >= 0 ? res : NaN()

Plus und Minus

Beliebige Additionen und Subtraktionen, zum Beispiel zwei Paare aus Produktion und Einspeisung. Fehlende Eingänge zählen als 0.

let val0 = get(UpstreamValues, DependencyIds[0]);
let val1 = get(UpstreamValues, DependencyIds[1]);
let val2 = get(UpstreamValues, DependencyIds[2]);
let val3 = get(UpstreamValues, DependencyIds[3]);
let safe0 = IsNaN(val0) ? 0.0 : val0;
let safe1 = IsNaN(val1) ? 0.0 : val1;
let safe2 = IsNaN(val2) ? 0.0 : val2;
let safe3 = IsNaN(val3) ? 0.0 : val3;
let res = (safe0 - safe1) + (safe2 - safe3);
res

Passe die Zeile res an deine Formel an und halte je eine val- und safe-Zeile pro Eingang. Nur für Mengen pro Intervall; wie der Rest behält die Funktion negative Minuten, damit die Viertelstunde aufgeht.

Zustand

1, wenn jeder Eingang, der gemeldet hat, in Ordnung ist (1), 0, wenn einer es nicht ist, kein Wert, wenn keiner gemeldet hat.

let gemeldet = filter(values(UpstreamValues), {!IsNaN(#)});
len(gemeldet) > 0 ? (all(gemeldet, {# == 1}) ? 1 : 0) : NaN()

Die kürzere Form all(values(UpstreamValues), # == 1) ? 1 : 0 meldet einen Eingang, der nicht gemeldet hat, als gestört.

Was this page helpful?