Stern inaktivStern inaktivStern inaktivStern inaktivStern inaktiv
 

Mit dem folgenden Programm + Script ist es möglich, eine ganze Serie von Werten (z. B. Temperaturen) zu überwachen und bei Unterschreiten eines Schwellwerts eine Alarm-Pushnachricht (per Pushover) zu verschicken.

Dazu wird ein Programm erstellt, das zuerst einmal in einer großen ODER-Verknüpfung auf Unterschreiten von Werten (hier: Temperaturen) reagiert. Wichtig: immer den Modus "bei Aktualisierung auslösen" verwenden, denn der nachfolgende Prozess soll auch bei immer stärker werdenden Unterschreitung weiter reagieren und nicht nur einmal beim erstmaligen unterschreiten. Hier können beliebige und beliebig viele Werte in einem Rutsch das darauf folgende Script triggern:

Das in der DANN-Sektion auszuführende Script enthält dann noch einmal eine Konfiguration, in der alle zu prüfenden Messpunkte mit ihren Schwellwerten und weiteren Parametern aufgeführt werden. Diese sollten natürlich mit den oben genannten Bedingungen überinstimmen - allerdings MUSS das nicht der Fall sein. So könnte das Script z. B. aufgrund einer Temperaturunterschreitung des Geräts X aufgerufen werden, wenn im Script allerdings nicht hinterlegt ist, tatsächlich auf Gerät X zu reagieren, wird einfach nichts passieren.

! Send Alert                                                     

! Name of a string sysvar. This must exist and will contain a key/value pair list (separated by @), key and value separated by =
! For every measured value, the last value is stored in this list.
string SysVarKvps = "EMAIL_LAST_TEMPS";

! Configuration: list of config lines, separated by ;.
! Each config line containing fields separated by @:
! 0: name of a channel
! 1: name of a data point
! 2: name of the channel to be used in altert text
! 3: minimum value; only alert if actual value is lower
! 4: ID of this line to be used to memorize values in the SysVarKvps variable
string cfg = 
  "Heizung KUE 1@ACTUAL_TEMPERATURE@KUE - Heizung 1@15.0@1;" #
  "Heizung KUE 2@ACTUAL_TEMPERATURE@KUE - Heizung 2@15.0@2;" #
  "Heizung SZ3@ACTUAL_TEMPERATURE@SZ3 - Heizung@15.0@3;" #
  "Heizung OG BAD@ACTUAL_TEMPERATURE@OG BAD - Heizung@15.0@4;" #
  "Heizung SZ1@ACTUAL_TEMPERATURE@SZ1 - Heizung@15.0@5;" #
  "Heizung SZ2@ACTUAL_TEMPERATURE@SZ2 - Heizung@15.0@6;" #
  "Heizung WOZI links@ACTUAL_TEMPERATURE@WOZI - Heizung links@15.0@7;" #
  "Heizung WOZI rechts@ACTUAL_TEMPERATURE@WOZI - Heizung rechts@15.0@8;" #
  "Wandthermostat WOZI Messwerte@TEMPERATURE@WOZI - Wandthermostat@15.0@9;" #
  "Wandthermostat EG FLUR Messwerte@TEMPERATURE@EG FLUR - Wandthermostat@15.0@10;" #
  "Wandthermostat EG BAD Messwerte@TEMPERATURE@EG BAD - Wandthermostat@15.0@11;" #
  "Heizung EG BAD@ACTUAL_TEMPERATURE@EG BAD - Heizung@15.0@12";


! if already warned about a value before, next warning will only be sent if actual value lower by this number:
real NextWarnOffset = 0.5;

! number of seconds to use the saved last alert history
integer MaxHistoryAge = 900;

string NewHistory = "";
string alert_message = "";
string cfg_line;

object o_SysVarHistory = dom.GetObject(SysVarKvps);
integer t_SysVarHistory = o_SysVarHistory.Timestamp().ToInteger();
integer t_current = system.Date("%F %X").ToTime().ToInteger();

if ((t_current - t_SysVarHistory) > MaxHistoryAge) {
  string History = "";
  } else {
  string History = o_SysVarHistory.Value();
  }


! loop through the configuration
foreach (cfg_line, cfg.Split(";"))  {
  ! divide the config line and get the columns:
  string cfg_chan = cfg_line.StrValueByIndex("@",0);
  string cfg_dp = cfg_line.StrValueByIndex("@",1);
  string cfg_name = cfg_line.StrValueByIndex("@",2);
  real cfg_minvalue = cfg_line.StrValueByIndex("@",3).ToFloat();
  string cfg_id = cfg_line.StrValueByIndex("@",4);

  ! get channel-datapoint's current value (as float and string)
  real r_current_val = dom.GetObject(cfg_chan).DPByHssDP(cfg_dp).Value();
  string s_current_val = r_current_val.ToString(1);

  ! get known old value if it exists (if not, use some maximum value)
  string s_lastalert_val = History.StrValueByIndex("@" # cfg_id # "=",1).StrValueByIndex("@",0).StrValueByIndex("=",1);
  real r_lastalert_val = 999999;
  if (s_lastalert_val <> "") { r_lastalert_val = s_lastalert_val.ToFloat(); }

  ! filled later if an alert was found
  string s_alerted_val = "";

  ! condition to alert: only if
  ! - current value lower than configured minimum value AND
  ! - current value lower than last alerted value reduced by the "next warning offset"
  if ((r_current_val < cfg_minvalue) && (r_current_val < (r_lastalert_val - NextWarnOffset))) {
    s_alerted_val = s_current_val;
    alert_message = alert_message # "" # cfg_name # ": " # s_alerted_val # "\n";
  }

  ! create new alert History; only update values which were alerted
  if (s_alerted_val == "") {
    NewHistory = NewHistory # "@" # cfg_id # "=" # s_lastalert_val;
  } else {
    NewHistory = NewHistory # "@" # cfg_id # "=" # s_alerted_val;
  }
}

if (alert_message <> "") {
  dom.GetObject(SysVarKvps).State(NewHistory);
  
  string stdout;
  string stderr;
  alert_message = "WIB: Temperaturwarnung!\n" # alert_message;
  system.Exec("wget --quiet --no-check-certificate --post-data 'token=XXX&user=YYY&html=1&message=" # alert_message # "' -O - https://api.pushover.net/1/messages.xml", &stdout, &stderr);
}

 

Im oberen Teil des Scripts müssen einige Dinge konfiguriert werden:

  • die Variable SysVarKvps muss den Namen einer tatsächlich existierenden Systemvariable vom Typ "Zeichenkette" enthalten, in der das Script pro konfiguriertem Datenpunkt den letzten alarmierten Wert speichert
  • die Variable cfg ist ein langer String, der zuerst einmal Konfigurationszeilen enthält, die mit einem Semikolon-Zeichen getrennt werden. Jede Zeile entspricht der Definition eines zu prüfenden und ggf. alarmauslösenden Datenpunkts. Dazu werden mehrere Werter angegeben, die jeweils mit einem @-Zeichen getrennt werden: der Name eines Channels, der Name seines auszulesenden Datenpunkts (es MÜSSEN nicht unbedingt Temperaturen sein, aber das Script kann momentan nur mit Float-Werten arbeiten und reagiert nur auf Unter-, nicht auf Überschreitungen)., dann der Klartextname des Kanals/Datenpunkts, wie er in der Alarmmeldung ausgegeben wird, der zu unterschreitende Schwellwert, und dann noch eine beliebige ID der Zeile - am besten von oben nach unten fortlaufend durchnummerieren
  • die Variable NextWarnOffset enthält einen weiteren Schwellwert. Liegt ein aktuell gemessener Wert noch einmal um diesen Betrag niedriger als der Wert, der zuletzt Alarm ausgelöst hat, wird nun erneut alarmiert
  • die Variable MaxHistoryAge enthält die Anzahl Sekunden, die die zuletzt alarmierten Werte gespeichert werden, bevor diese vergessen werden

Im weiteren Verlauf wird zeilenweise die Konfiguration ausgelesen, der Datenpunkt gemessen, ein eventuell vorheriger Messwert der letzten Alarmierung ausgelesen und dann ggf. (erneut) alarmiert.

Ganz unten muss in der Pushover-URL noch XXX und YYY durch das API-Token und den Pushover-Usercode ersetzt werden.

Wer nur einmalig bei Unterschreitung und nicht erneut bei noch weiterer Unterschreitung informiert werden möchte: kann erheblich Performance optimieren: oben die ganzen Bedingungen auf "bei Änderung auslösen" einstellen - dann wird das Script schon einmal nur noch einmal bei Unterschreitung aufgerufen - ein weiteres mal erst, wenn erst der Sollwert wieder erreicht, und DANN erneut unterschritten würde. Zusätzlich können dann die ganzen History-Programmteile entfernt werden.

Performance: es muss klar sein, dass JEDE Aktualisierung von Werten ALLER Datenpunkte, die unterhalb der Schwellwerte liegen, zu einem KOMPLETTEN Scriptdurchlauf führen. Hier opfere ich bewußt Performance, um ein einfaches Programmdesign zu haben. Man könnte z. B. einzelne Programme mit eigener Bedingung und eigenem Script verwenden - dann wird niemals etwas sinnlos geprüft. Oder man verwendet das $src-Objekt, das dem Script mitteilt, WELCHER Kanal der auslösende ist - allerdings bin ich mir nicht sicher, was passiert, wenn theoretisch zwei Kanäle gleichzeitig auslösen - ich vermute, alle bis auf den ersten würden dann unterschlagen werden. Also... so wie hier funktioniert es jedenfalls sehr gut, nichts wird unterschlagen und es werden alle zutreffenden Alarme in einer einzigen Alarmnachricht zusammengefasst.