Der Bot in der Maschine

Ein WebUI-Programm erweckt den CCU-Bot zum Leben

Nachdem die Vorbereitungen abgeschlossen sind, ist es Zeit, den CCU-Bot in ein WebUI-Programm zu gießen.

Veraltete wget-Version verhindert Verbindungsaufbau

Im Februar 2020 hat Telegram die TLS-Parameter für den Zugang zur Telegram-API geändert. Ältere Versionen von wget können damit nicht umgehen und deshalb auch keine Nachrichten mehr über Telegram absenden.

Aktualisieren Sie die Firmware Ihrer CCU, in der ein neueres wget enthalten ist, um dieses Problem zu beheben!

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.

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.

Systemvariablen

Zunächst erstelle ich wie immer meine Systemvariablen. Für mein Programm brauche ich zwei: Die nächste update_id und die Nachricht, die über Telegram an den CCU-Bot gesendet wurde – sofern vorhanden.

Die update_id nenne ich Telegram.Next_ID. Furchtbarer Name, aber nachdem ich mich einmal dafür entschieden hatte, gab es kein Zurück … Wie der Name schon sagt, wird hier die ID der jeweils nächsten abzurufenden Nachricht gespeichert. Ich verwende eine Zeichenkette, weil ich auf die Schnelle keine Informationen über den maximalen Wertebereich des Typs Zahl gefunden habe und der Datentyp an dieser Stelle egal ist.

Falls der CCU-Bot und Telegram aus irgendeinem Grunde irgendwann nicht mehr synchron sind, macht das nichts: Wird eine zu niedrige ID angegeben, so liefert Telegram die erste Nachricht mit der nächsthöheren vorhandenen ID. Gleiches gilt für den ersten Start des Programms, bei dem die Systemvariable leer sein wird. Auch hier liefert Telegram einfach die erste Nachricht. Das Script aktualisiert dann die Systemvariable Telegram.Next_ID.

Das Herzstück des Bots ist die Variable Telegram.Command. Hier legt das Script, das ich weiter unten erstelle, den jeweils nächsten über Telegram gesendeten Befehl ab. Wenn diese Systemvariable aktualisiert wird, können andere WebUI-Programme darauf reagieren, den Inhalt prüfen und – wenn sie für den Befehl zuständig sind – irgendetwas machen.

Wie immer sind hier auch andere Namen möglich, aber wie immer müssen das Script und die darauf aufbauenden Programme angepasst werden, wenn etwas umbenannt wird.

WebUI-Programm

Das Programm ist relativ simpel: Ich lasse es mit dem Systemtakt ausführen.

Da der CCU-Bot selbst keine Befehle sendet oder empfängt, hänge ich ihn auch nicht an den Duty Cycle. Falls ich eines Tages Befehle ausführen lasse, die reichlich Funkverkehr erzeugen, wäre das durchaus eine Option: Befehle an den Bot bleiben bei Telegram zwischengespeichert, bis der CCU-Bot sie abholt – und das könnte er bei Verknüpfung mit dem Duty Cycle stets nur dann tun, wenn noch Luft auf der Luftschnittstelle ist. So ginge nichts verloren.

Ebenfalls möglich wäre natürlich ein Programm über das Zeitmodul. Zickig, wie es ist: eine Ausführung alle drei bis fünf Minuten würde es schon hinbekommen. Bei Bedarf könnte man die Ausführung sogar minütlich starten, damit der CCU-Bot schneller auf Befehle reagiert – oder man erhöht die Abfrage-Frequenz auf 10, 15 oder mehr Minuten, um die Belastung der CCU und der Internetverbindung zu reduzieren (obwohl beides aufgrund der geringen Datenmengen ohnehin kaum messbar sein wird).

Für den Moment aber: Systemtakt.

Das Programm tut nichts weiter, als das folgende Script aufzurufen. Die Verzögerung habe ich eingebaut, um – wie üblich – die Aktivität der CCU ein wenig zu entzerren.

Script

Das Script ist das Herzstück. Hier werden die Nachrichten mit wget von Telegram abgeholt, mein vorbereitetes awk-Script ausgeführt und das Ergebnis in die passenden Systemvariablen sortiert.

! HomeMatic-Script
! DER BOT IN DER MASCHINE
! http://www.christian-luetgens.de/homematic/ccubot/webui/Script.htm

object o_seq = dom.GetObject ("Telegram.Next_ID");
integer seq = o_seq.Value().ToInteger();

string cmd="wget --output-document=- --quiet --no-check-certificate \"https://api.telegram.org/bot314321353:AAFKjr29dF940b-aTchoFJ_pb6oZKxzx8Zw/getUpdates?offset=" # seq # "&limit=1\"" #
  " | awk 'BEGIN { RS=\"\\\"*[,\[\{\}\\r\\n]+\\\"*\"; FS=\"\\\":\\\"*\"; } match ($2, /\136[\/[:alnum:] ]{1,32}$/) { res[$1] = $2; } END { printf res[\"update_id\"] \";\" res[\"id\"] \";\" res[\"text\"] ; }' ";

dom.GetObject ("CUxD.CUX2801001:3.CMD_SETS").State (cmd);
dom.GetObject ("CUxD.CUX2801001:3.CMD_QUERY_RET").State(1);
string x = dom.GetObject ("CUxD.CUX2801001:3.CMD_RETS").State();

seq = x.StrValueByIndex (";", 0).ToInteger();

if (seq != 0) {
  o_seq.State (seq + 1);
  if ((x.StrValueByIndex (";", 1) == "374629384") && (x.StrValueByIndex (";", 2) != "")) {
    dom.GetObject ("Telegram.Command").State (x.StrValueByIndex (";", 2));
  }
}

!  Ende des Scripts

Schritt für Schritt läuft das Script folgendermaßen ab:

  1. Die Systemvariable Telegram.Next_ID wird abgefragt. Der aktuelle Wert wird als Integer in der Variablen seq (kurz für „Sequenz“ – wie komme ich nur immer auf diese Namen?) gespeichert.
  2. Der Befehlsstring wird zusammengesetzt: wget mit passenden Parametern und passender URL, gefolgt von meinem awk-Script.
  3. Über den CUx-Daemon wird der Befehl abgesetzt. Ich habe hier das dritte Gerät für den CCU-Bot reserviert und nutze die Befehlsausführung, bei der die Antwort des ausgeführten Programms an das Script zurückgegeben wird. Diese wird in der Variablen x gespeichert.
  4. Der erste Wert im Ausgabestring des awk-Scriptes ist die aktuelle update_id, die ich in einen Integer umwandle und als neuen Wert seq zuweise. Wenn seq einen gültigen Wert hat – also größer ist als 0 –, hat Telegram eine Nachricht zurückgeliefert und ich werte sie aus.
  5. Telegram.Next_ID wird auf seq + 1 gesetzt, damit das Script beim nächsten Durchlauf die nächste Nachricht abholt.
  6. Wenn jetzt noch die Chat-ID der Nachricht mit der Chat-ID meines normalen Telegram-Accounts übereinstimmt und das awk-Script einen Befehl übermittelt hat, wird dieser Befehl schließlich in Telegram.Command gespeichert.

Das awk-Script hat im Vergleich zu der Version, die ich direkt per ssh getestet habe, ebenfalls einige Änderungen erfahren: Da es komplett in einem WebUI-Script als String definiert wird, musste ich sämtliche Anführungszeichen " mit einem umgekehrtem Schrägstrich \ kennzeichnen. Außerdem kommt die WebUI nicht mit einem Zirkumflex ^ im Script klar – hier nutze ich die Fähigkeit von awk, beliebige ASCII-Zeichen als (oktale) Zahl darzustellen: \136.

Mit der Firmwareversion 3.63.9 hat die Script-Engine der CCU eine Änderung erfahren, die eine Anpassung der oben gezeigten Implementierung des awk-Scriptes erforderlich machte.

Oder kurz: Bitte 3.63.9 oder neuer installieren, sonst läuft es nicht.

Falls an irgendeiner Stelle unerwartete Daten zurückgeliefert werden, sollte der CCU-Bot das halbwegs herausfiltern:

Jetzt kann ich mein Programm speichern und es passiert … nichts. Gutes Ergebnis bisher.

Unter Beobachtung

Tatsächlich passiert natürlich schon etwas: Wenn das Programm läuft und ein gültiger Befehl an den CCU-Bot gesendet wurde, werden die beiden Systemvariablen aktualisiert, die ich oben eingerichtet habe.

Unter Status und Bedienung / Systemvariable kann ich sie mir direkt anzeigen lassen:

Sobald der Systemtakt zuschlägt, bekommen die Variablen einen frischen Timestamp, Telegram.Command enthält meinen „Befehl“ (im Screenshot oben „/welt“) und Telegram.Next_ID enthält die nächste Update-ID.

Zeit für ein erstes Mini-Programm.

Hallo! Echo!

Zum Testen lege ich ein neues WebUI-Programm an, dessen einfache Aufgabe es sein wird, jeden Befehl zurückzusenden, der an den CCU-Bot abgeschickt wird.

Das Programm reagiert auf Telegram.Command: Sobald das CCU-Bot-Script hier etwas einträgt, wird es ausgeführt.

Das Programm soll bei jedem Wert ausgeführt werden, der keine leere Zeichenkette ist. Daher vergleiche ich Telegram.Command mit einem leeren String, lasse den Dann…-Teil weg und füge unter Sonst… ein einfaches Script ein.

! HomeMatic-Script
! DER BOT IN DER MASCHINE
! http://www.christian-luetgens.de/homematic/ccubot/webui/Script.htm

object t = dom.GetObject ("Telegram");
object b = dom.GetObject ("Telegram.Command");

if (b.Value() != "") {
  t.State ("*Echo*" # "\n" # b.Value());
  b.State ("");
}

!  Ende des Scripts

Das Script sucht sich die Systemvariablen Telegram für das T-Framework und Telegram.Command für den CCU-Bot heraus. Wenn in Telegram.Command ein Wert steht, wird dieser zusammen mit einer fettgedruckten Überschrift 1:1 an das T-Framework zurückgegeben und die Systemvariable zurückgesetzt.

Die Überprüfung auf einen Leerstring und die Rücksetzung sind hier eigentlich überflüssig, aber die Grundstruktur werde ich auch bei weiteren Scripten für den CCU-Bot anwenden. Darum also auch hier.

Programm speichern, zwei Befehle absenden und nach endlicher Wartezeit erhalte ich meine Antworten:

Gut zu erkennen: Ich habe die Befehle nahezu zeitgleich abgesendet. Eine Minute später wird der erste abgearbeitet und zurückgespiegelt, drei Minuten später beim nächsten Taktzyklus der zweite.

Bevor es an die Programmierung von echten Bot-Befehlen geht, muss dieses Echo-Script natürlich deaktiviert oder gelöscht werden. Anderenfalls reagiert es auf alle Befehle und setzt jedes Mal die Systemvariable zurück; kein anderes Programm würde je ausgeführt.

Navigation