Push-Benachrichtigungen für Servicemeldungen

Neue Servicemeldungen umgehend auf dem Smartphone – eine perfekte Aufgabe für den Telegram-Bot

Ich habe ja schon Erfahrung damit, Servicemeldungen in der Weltgeschichte herumzuschicken. Mein E-Mail-Script erzeugt eine übersichtliche Liste, hat aber zwei gravierende Nachteile: Es läuft nur täglich und es bringt wegen exzessiver String-Benutzung meine CCU irgendwann zum Absturz.

Daher hier eine neue Variante: Push-Benachrichtigungen per Telegram.

Variablen in HomeMatic-Scripten

Die CCU hatte früher ein Limit von maximal 200 Variablen, das mit Firmware-Version 2.29.18 aufgehoben wurde. Dieses Limit bezog sich auf alle Variablen, die in allen Scripten verwendet werden. Gemeint sind Variablen, die direkt im Script definiert werden: object x; object y; var z;

Wenn Scripte nicht mehr funktionieren und bei der Prüfung unerklärliche Syntax-Fehler auftreten, sollte versuchsweise dieses Programm wieder gelöscht oder deaktiviert werden – oder, noch besser, auf die aktuelle Firmware-Version aktualisiert werden.

Strings in HomeMatic-Scripten

Durch String-Verwendung in HomeMatic-Scripten kann die CCU fehlerhaft arbeiten, instabil werden oder sogar abstürzen. Grundsätzlich gilt: Je öfter mit Strings hantiert wird, desto eher führt dies zu Problemen.

Ich empfehle daher, nach Umsetzung dieser Anleitung die CCU unter Beobachtung zu halten.

String-Verlängerung

In den Scripten auf dieser Seite werden Strings verlängert (x = x # y). Dies führt oft zu Störungen bei der Ausführung von Programmen:

  • Scripte in Programmen werden nicht mehr ausgeführt
  • bei der Fehlerprüfung erscheinen unerklärliche Syntax-Fehler

Durch einen Neustart werden diese Probleme (vorübergehend) behoben. Auch hier hängt die Dauer, bis es zu Störungen kommt, davon ab, wie häufig diese Programmschritte ausgeführt werden.

Benennung von Systemvariablen

Prinzipiell kann man Systemvariablen – so wie allen Objekten in der CCU – beliebige Namen geben, also z. B. auch Umlaute und Sonderzeichen verwenden. Ich empfehle jedoch, sich auf reguläre Buchstaben (a-z, A-Z) zu beschränken: Bei Umlauten und Sonderzeichen besteht die Gefahr, dass Systemvariablen in Scripten nicht überall gefunden werden.

Programmablauf

Die besondere Herausforderung besteht darin, dass Telegram-Nachrichten sehr kurz sein sollten, mehrere Servicemeldungen also in Einzelnachrichten aufgeteilt werden müssen. Der Programmablauf ist daher folgender:

Klingt kompliziert, ist aber kein Hexenwerk.

Systemvariable

Als erstes erstelle ich wie so oft eine Systemvariable, in der mein Programm abspeichern kann, was schon gesendet wurde.

Servicemeldungen Telegram sollte eindeutig genug sein, um den Zweck auszudrücken. Letztlich werde ich mit der Variablen und ihrem Inhalt ohnehin praktisch keinen Kontakt haben.

Ja, es ist ein String, aber ich werde ihn nur ganz vorsichtig benutzen. Versprochen! Eine richtig stabile CCU ohne all die Eigenheiten wäre manchmal schon echt knorke.

WebUI-Programm

Das WebUI-Programm ist etwas origineller aufgebaut als üblich, weil es in der WebUI keine Möglichkeit gibt, die Bedingung „wenn der String nicht leer ist“ einzugeben. Eigenheiten der CCU und so.

Der Wenn…-Teil wird ausgeführt, wenn keine Servicemeldungen vorhanden sind und Servicemeldungen Telegram leer ist – also eigentlich nie. Wichtig ist die Auswahl bei Aktualisierung ausführen für beide Variablen. Entsprechend der Logik von WebUI-Programmen würde das Programm sonst nicht laufen, wenn der Servicemeldungszähler sich verändert.

Der Sonst…-Teil wird ausgeführt, wenn Servicemeldungen vorliegen oder wenn Servicemeldungen Telegram nicht leer ist – also eigentlich immer. Die gesamte Intelligenz des Programms steckt im Script.

! HomeMatic-Script
! PUSH-BENACHRICHTIGUNGEN FüR SERVICEMELDUNGEN
! http://www.christian-luetgens.de/homematic/telegram/servicemeldungen/Service-Push.htm

object o_service = dom.GetObject ("Servicemeldungen Telegram");
string s_service_alt = o_service.Value();
string s_service_neu = ";";

string s_al_item; object o_al_item; boolean b_sent = false;
string s_error_type; integer i_error_num; string s_error_message = "";
object o_dp; object o_channel; string s_device;

foreach (s_al_item, dom.GetObject(ID_SERVICES).EnumUsedIDs()) {
  o_al_item = dom.GetObject (s_al_item);
  if (o_al_item.AlState() == asOncoming) {
    if (s_service_alt.Find (";" # s_al_item # ";") < 0) {
      if (!b_sent) {
        s_error_type = o_al_item.Name().StrValueByIndex (".", 1).StrValueByIndex ("-", 0);
        i_error_num = o_al_item.Name().StrValueByIndex (".", 1).StrValueByIndex ("-", 1).ToInteger();
        o_dp = dom.GetObject(o_al_item.AlTriggerDP());
        o_channel = dom.GetObject (o_dp.Channel());
        s_device = o_channel.Name().StrValueByIndex (":", 0);

        if ((s_error_type == "ERROR_SABOTAGE") || ((s_error_type == "ERROR") && ((o_channel.HssType() == "MOTION_DETECTOR") || (o_channel.HssType() == "ROTARY_HANDLE_SENSOR") || (o_channel.HssType() == "SHUTTER_CONTACT")))) {
          s_error_message = "Sabotage erkannt";
        }

        if (s_error_type == "STATE") {
          if (o_channel.HssType() == "WATERDETECTIONSENSOR") {
            s_error_message = ".:Feuchtigkeit erkannt:Nässe erkannt".StrValueByIndex (":", i_error_num);
          }
          if (o_channel.HssType() == "SMOKE_DETECTOR_TEAM") {
            s_error_message = "Alarm ausgelöst;";
          }
          if (o_channel.HssType() == "SENSOR_FOR_CARBON_DIOXIDE") {
            s_error_message = ".:CO2-Belastung erhöht:CO2-Belastung stark erhöht".StrValueByIndex (":", i_error_num);
          }
        }

        if (s_error_type == "ERROR") {
          if (o_channel.HssType() == "CLIMATECONTROL_VENT_DRIVE") {
            s_error_message = ".:Ventil blockiert:Ventil falsch montiert:Stellbereich zu klein:Batterie leer, Störposition angefahren".StrValueByIndex (":", i_error_num);
          }
          if ((o_channel.HssType() == "DIMMER") || (o_channel.HssType() == "VIRTUAL_DIMMER")) {
            s_error_message = "Lastfehler";
          }
          if (o_channel.HssType() == "WINMATIC") {
            s_error_message = ".:Fehler Drehgriff:Fehler Kippantrieb".StrValueByIndex (":", i_error_num);
          }
          if (o_channel.HssType() == "KEYMATIC") {
            s_error_message = ".:Fehler einkuppeln:Abbruch Motorlauf".StrValueByIndex (":", i_error_num);
          }
        }

        if (s_error_type == "ERROR_OVERHEAT") {
          s_error_message = "Überhitzung";
        }
        if (s_error_type == "ERROR_OVERLOAD") {
          s_error_message = "Überlast";
        }
        if (s_error_type == "ERROR_REDUCED") {
          s_error_message = "Last zu gering";
        }
        if (s_error_type == "ERROR_POWER") {
          s_error_message = "Stromversorgung ausgefallen";
        }
        if (s_error_type == "ERROR_BATTERY") {
          s_error_message = "Batterie defekt";
        }
        if (s_error_type == "U_SOURCE_FAIL") {
          s_error_message = "Netzteil ausgefallen";
        }
        if (s_error_type == "USBH_POWERFAIL") {
          s_error_message = "USB-Host deaktiviert";
        }
        if (s_error_type == "FAULT_REPORTING") {
          s_error_message = ".:Ventil blockiert:Ventil falsch montiert:Stellbereich zu klein:Kommunikationsfehler:.:Batteriestand niedrig:Batterie leer, Störposition angefahren".StrValueByIndex (":", i_error_num);
        }
        if (s_error_type == "LOWBAT") {
          s_error_message = "Batteriestand niedrig";
        }
        if (s_error_type == "DEVICE_IN_BOOTLOADER") {
          s_error_message = "Gerät startet neu";
        }
        if (s_error_type == "UPDATE_PENDING") {
          s_error_message = "Update verfügbar";
        }
        if (s_error_type == "CONFIG_PENDING") {
          s_error_message = "Konfigurationsdaten stehen zur Übertragung an";
        }

        if (s_error_type == "STICKY_UNREACH") {
          s_error_message = "Kommunikation war gestört";
        }
        if (s_error_type == "UNREACH") {
          s_error_message = "Kommunikation ist zur Zeit gestört";
        }

        if (s_error_message != "") {
          dom.GetObject ("Telegram").State (s_device # ": " # s_error_message);
        }
        b_sent = true;
        s_service_neu = s_service_neu # s_al_item # ";";
      }
    } else {
      s_service_neu = s_service_neu # s_al_item # ";";
    }
  }
}

if (s_service_neu != s_service_alt) {
  o_service.State (s_service_neu);
}

!  Ende des Scripts

Das Script ist lang, aber eigentlich recht gut verständlich.

Systemvariable

Als erstes wird Servicemeldungen Telegram gesucht, in der die Liste der bereits bearbeiteten Meldungen steht. Die alte Liste wird in s_service_alt gespeichert, für eine neue Liste wird s_service_neu erzeugt und mit einem führenden Semikolon belegt.

Variablen

Für den Programmlauf brauche ich allerlei Variablen. Diese werden als nächstes festgelegt und nach Bedarf mit Werten initalisiert.

Schleife

Servicemeldungen liegen in der CCU als lange Liste vor: Alles, was als Servicemeldung erscheinen kann, hat einen Eintrag in ID_SERVICES. Wenn die zugehörige Servicemeldung aktiv ist, prüft das Script, ob ihre ID schon in s_service_alt steht, die Meldung also schon gesendet wurde. Wenn nicht, wird geprüft, ob während dieses Script-Aufrufs schon etwas gesendet wurde – ich möchte ja nur eine Meldung pro Programmlauf absetzen.

Wenn noch nichts gemeldet wurde, geht es an die Fehlermeldung.

Fehlermeldung suchen

Die Servicemeldungen, die in ID_SERVICES stehen, enthalten relativ wenig Informationen. Daten zum betroffenen Gerät muss man herausfinden, indem man das jeweilige Gerät selbst identifiziert und abfragt. Das geschieht als nächstes.

Danach geht das Script die Liste der möglichen Fehlermeldungen durch. Bezeichnungen und Datenpunkte sind leider arg inkonsistent vergeben, deswegen gibt es einen großen Block mit allen möglichen Kombinantionen. Jaja, Eigenheiten der CCU.

Senden und abhaken

Wurde eine Fehlermeldung gefunden, so wird sie gesendet. Anschließend wird die ID in s_service_neu gespeichert, damit das Script beim nächsten Aufruf diese Meldung nicht noch einmal sendet.

s_service_neu wird auch aktualisiert, wenn keine Fehlermeldung gefunden wurde. Das ist dann der Fall, wenn die entsprechenden Details des Gerätes (noch) nicht erfasst sind oder die Benachrichtigung unterdrückt wird – mehr dazu unten.

In s_service_neu werden die zugehörigen IDs trotzdem eingetragen, denn beim nächsten Programmlauf wird das Script immer noch nichts zu senden haben – und dann kann man sich die aufwendige Prüfung auch sparen.

Schließlich wird auch dann, wenn es um eine schon gesendete Meldung geht, die ID in s_service_neu gespeichert.

Systemvariable aktualisieren

Wenn s_service_neu sich im Vergleich zu s_service_alt geändert hat, dann wird Servicemeldungen Telegram jetzt aktualisiert. Durch die Aktualisierung wird das Programm erneut ausgelöst und das Script nach 15 Sekunden wieder gestartet, um bei Bedarf die nächste zu sendende Servicemeldung zu finden.

Gibt es keine Änderungen, wird auch die Systemvariable nicht aktualisiert. Das Script läuft dann erst wieder, wenn sich am Servicemeldungszähler etwas ändert.

Durch die Verzögerung von 15 Sekunden haben andere Scripte genug Zeit, beispielsweise überflüssige Meldungen zu Kommunikationsstörungen zu bestätigen. Dadurch wird nicht nur die Anzahl der Benachrichtigungen etwas reduziert, sondern das Script wird auch seltender unnötig ausgeführt – da es mit Strings hantiert, ist Zurückhaltung hier Trumpf.

Ich springe mal durchs Haus und fummel an ein paar Geräten rum. Das Ergebnis ist wie erwartet: alle offenen Servicemeldungen werden der Reihe nach gepusht. Auch alle späteren Servicemeldungen werden zeitnah gemeldet.

Nach welchen Kriterien Telegram meine Gerätebezeichnungen in (kaputte) Links umwandelt, entzieht sich meinem Verständnis – aber Hauptsache, die Information ist da.

Auch Sabotagemeldungen kommen umgehend rein; mein Programm zur Benachrichtigung bei Sabotage kann ich also endgültig in Rente schicken. Instant Messaging ist so viel cooler als E-Mail! Oder? Oder?!

Meldungen filtern

Ich habe einige Aktoren, die regelmäßig kurzzeitig nicht erreichbar sind. Das liegt an ihrem Installationsort: Im abschüssigen Garten in Bodennähe ist die Funkversorgung nicht so knorke. Andererseits ist es auch nicht dramatisch, wenn sie mal nicht erreichbar sind. Sonst könnte ich vielleicht einen Repeater auf die Terrasse hängen oder so. Hmmm. Die Steckdose benutze ich eh für nichts und sie ist halbwegs geschützt. Ich müsste das Ding nur einstöpseln, das ist ja kein Aufwand.

Hat Conrad eigentlich noch auf?

Wo war ich stehengeblieben? Ach ja: Etwas nervig ist es freilich schon, wenn mein Script dann die ganze Nacht durch alle paar Minuten eine Servicemeldung raushaut.

Eine mögliche Lösung wäre, die 15-Sekunden-Verzögerung zu erhöhen. Wenn das Script mit dem Versand so lange wartet, bis möglicherweise das Problem sich erledigt hat, hätte ich eine ruhige Nacht. Die Verzögerung bezieht sich jedoch auf alle Servicemeldungen – und Sabotagemeldungen hätte ich dann doch gerne halbwegs sofort.

Die Lösung – für mich jedenfalls – lautet, die unerwünschten Meldungen einfach von der Benachrichtigung auszuschließen. Ich habe das hier mal gemacht:

        [...]
        if (s_error_type == "CONFIG_PENDING") {
          s_error_message = "Konfigurationsdaten stehen zur Übertragung an";
        }

        ! if (s_error_type == "STICKY_UNREACH") {
        !   s_error_message = "Kommunikation war gestört";
        ! }
        ! if (s_error_type == "UNREACH") {
        !   s_error_message = "Kommunikation ist zur Zeit gestört";
        ! }

        if (s_error_message != "") {
          dom.GetObject ("Telegram").State (s_device # ": " # s_error_message);
        }
        [...]

Das ist natürlich nur ein Ausschnitt. Wie man sieht, habe ich sämtliche Servicemeldungen zur Kommunikation einfach auskommentiert – Zeilen werden ab dem „!“ nicht ausgeführt. Ich hätte sie auch löschen können, aber falls ich sie doch wieder haben möchte, habe ich auf diesem Weg weniger Aufwand.

Die Meldungen werden trotzdem in Servicemeldungen Telegram eingetragen, damit beim nächsten Durchlauf das Script weniger Arbeit hat, aber die (für mich) überflüssigen Benachrichtigungen unterbleiben.

Die Liste der Servicemeldungen auf der CCU wird natürlich nicht durch meine Push-Benachrichtigungen gefiltert.

Navigation