In der Adventszeit wurde es Zeit für ein neues Projekt. Ich möchte gerne Lichterketten in mein Smart Home integrieren. Grundsätzlich sind mir dafür zwei Strategien eingefallen:
- Lichterketten an schaltbare Steckdosen anschließen, dann kann ich sie an- und ausschalten
- Smarte Lichterketten nutzen, die vielleicht noch mehr können als „nur“ an oder aus
Ich habe mich für die zweite Variante entschieden und zwei Lichterketten von Twinkly, genauer gesagt Twinkly Strings 400 RBG,1einmal für 98,99€ bei click-licht.de, und einmal für 129,90€ bei lampenwelt.de organisiert.
Tag 1: Die Einbindung
Grundsätzlich ist die Installation der Twinkly-Lichterketten simpel. Es sollte aber erwähnt werden, dass eine App und ein Account benötigt werden. Möglicherweise ist diese Lösung daher nicht datensparsam – damit habe ich mich noch nicht auseinander gesetzt. Jedenfalls kann man die Lichterketten über eine spezielle App ein- und ausschalten, Farben ändern, Animationen wählen, das ist schon ziemlich cool. Es gibt auch eine Zeitschalt-Funktion, allerdings kann man damit die Lichterkette nur einmal pro Tag an- und einmal ausschalten. Und es ist auch eine feste Uhrzeit, zum Beispiel geht die Lichterkette jeden tag um 16:30 Uhr an.
Ich möchte aber mehr.
Ich möchte, dass die Lichterkette morgens an- und ausgeht, und abends ebenfalls an und aus. Und außerdem soll der Beginn und das Ende helligkeitsabhängig sein. Also werde ich die Twinkly-Lichterkette in openHAB einbinden. Leider gibt es kein Binding dafür, aber trotzdem ein gutes Tutorial. Hinter diesem Link versteckt sich ein Python-Skript, das ich jetzt erst einmal hier dokumentieren möchte.
import sys import json import urllib.request import codecs ARG_ON = 'on' ARG_OFF = 'off' ARG_STATE = 'state' ARG_BRIGHT = 'brightness' ARG_IP = sys.argv[1] ARG_ACTION = sys.argv[2] URL = "http://" + ARG_IP + "/xled/v1/" LOGIN_URL = URL + "login" VERIFY_URL = URL + "verify" MODE_URL = URL + "led/mode" BRIGHT_URL = URL + "led/out/brightness" AUTH_HEADER = 'X-Auth-Token' AUTHENTICATION_TOKEN = 'authentication_token' CHALLENGE_RESPONSE = 'challenge-response' MODE = 'mode' MODE_ON = 'movie' MODE_OFF = 'off' HEADERS = {'Content-Type': 'application/json'} LOGIN_DATA = {'challenge': 'AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8='} TURN_ON_DATA = {MODE: MODE_ON} TURN_OFF_DATA = {MODE: MODE_OFF} if len(sys.argv) > 3: #check if the thirth argument is given, save it in a variable and create the brightness data variable ARG_ACTION2 = int(sys.argv[3]) BRIGHTNESS_DATA = {'value': ARG_ACTION2, 'type': 'A'} def formatData(data): return json.dumps(data).encode('utf8') def processRequest(request): return urllib.request.urlopen(request) def processRequestJSON(request): loginResponse = processRequest(request) reader = codecs.getreader("utf-8") return json.load(reader(loginResponse)) # login to api - get challenge response and auth token loginRequest = urllib.request.Request(url = LOGIN_URL, headers = HEADERS, data = formatData(LOGIN_DATA)) loginData = processRequestJSON(loginRequest) challengeResponse = loginData[CHALLENGE_RESPONSE] authToken = loginData[AUTHENTICATION_TOKEN] HEADERS[AUTH_HEADER] = authToken verifyData = {CHALLENGE_RESPONSE: challengeResponse} # verify token by responding with challenge response verifyRequest = urllib.request.Request(url = VERIFY_URL, headers = HEADERS, data = formatData(verifyData)) verifyData = processRequestJSON(verifyRequest) def turnOn(): onRequest = urllib.request.Request(url = MODE_URL, headers = HEADERS, data = formatData(TURN_ON_DATA)) processRequest(onRequest) print(1) def turnOff(): offRequest = urllib.request.Request(url = MODE_URL, headers = HEADERS, data = formatData(TURN_OFF_DATA)) processRequest(offRequest) print(0) def setBrightness(): brightRequest = urllib.request.Request(url = BRIGHT_URL, headers = HEADERS, data = formatData(BRIGHTNESS_DATA)) processRequest(brightRequest) def getState(): modeRequest = urllib.request.Request(url = MODE_URL, headers = HEADERS) modeData = processRequestJSON(modeRequest) if modeData[MODE] != MODE_OFF: print(1) else: print(0) if ARG_ACTION == ARG_ON: turnOn() elif ARG_ACTION == ARG_OFF: turnOff() elif ARG_ACTION == ARG_STATE: getState() elif ARG_ACTION == ARG_BRIGHT: setBrightness()
Ganz ehrlich, ich kann kein Python, ich weiß nicht genau, was dieses Skript macht. Ich kann nur sagen, dass es funktioniert. Wie? Dazu kommen wir jetzt.
Als erstes habe ich dieses Skript als twinkly.py
im Ordner /var/lib/openhab/
abgespeichert. Dann habe ich mir über das openHAB-Benutzerinterface ein neues Item Lichterketten
vom Typ Switch
angelegt.
Und dann habe ich mir folgende lichterkette.rules
gebastelt:
rule "Twinkly Christmas Tree Power State" when Item Lichterketten received command then if (receivedCommand == ON) { executeCommandLine("python3","twinkly.py","x.x.x.37","on") executeCommandLine("python3","twinkly.py","x.x.x.43","on") } else { executeCommandLine("python3","twinkly.py","x.x.x.37","off") executeCommandLine("python3","twinkly.py","x.x.x.43","off") } end
Hier sind zwei Dinge zu beachten. Erstens benötigt man die IP von jeder Lichterkette. Ich habe in meinem Fritzbox-Router feste IP-Adressen für die Lichterketten hinterlegt, damit dieser hart gecodede Befehl funktioniert. Zweitens ist bei dieser Rule die Syntax extrem wichtig. Ursprünglich habe ich die folgende, nicht funktionierende Zeile benutzt:
executeCommandLine("python3 twinkly.py 192.168.0.209 on")
Aber damit erhält man eine Fehlermeldung: File or directory not found when running executeCommandLine
. Man muss wirklich darauf achten, dass die einzelnen Befehls-Elemente mit ","
voneinander getrennt sind. Das ist ganz wichtig!
So, abschließend habe ich noch den Switch auf meiner openHAB-Startseite eingebunden. Damit bin ich erst einmal zufrieden.
Tag 2: Zeitgesteuertes Ein- und Ausschalten
ich möchte ja, dass die Lichterketten morgens und abends jeweils an- und wieder aus gehen. Dazu nutze ich Sonnenauf- und -untergang, die zugehörigen Daten fallen aus dem Astro-Binding. Das muss ich noch dokumentieren, hier gibt es erst einmal nur die Code-Schnipsel.
Für die lichterkette.rules
rule "Lichterketten morgens an" when Item Tageszeit changed to "Morgen" then Lichterketten.sendCommand(ON) end rule "Lichterketten morgens aus" when Item Tageszeit changed to "Tag" then Lichterketten.sendCommand(OFF) end rule "Lichterketten abends an" when Item Tageszeit changed to "Vorabend" then Lichterketten.sendCommand(ON) end rule "Lichterketten abends aus" when Item Tageszeit changed to "Nacht" then Lichterketten.sendCommand(OFF) end
Und dann gibt es noch die tageszeit.rules
, welches das oben genutzte Item Tageszeit
verändert:
rule "Es wird morgen" when Item Sonne_Hohenwinkel changed then if (Sonne_Hohenwinkel.state == NULL || Sonne_Hohenwinkel.state > -12|"°") { if (Tageszeit.state == "Nacht") { Tageszeit.sendCommand("Morgen") } } end rule "Es wird Vormittag" when Channel "astro:sun:local:rise#event" triggered START then Tageszeit.sendCommand("Vormittag") end rule "Es wird Tag" when Item Sonne_Hohenwinkel changed then if (Sonne_Hohenwinkel.state == NULL || Sonne_Hohenwinkel.state > 3|"°") { if (Tageszeit.state == "Vormittag") { Tageszeit.sendCommand("Tag") } } end rule "Es ist Tag" when Time cron "0 0 12 ? * * *" then Tageszeit.sendCommand("Tag") end rule "Es wird Vorabend" when Item Sonne_Hohenwinkel changed then if (Sonne_Hohenwinkel.state == NULL || Sonne_Hohenwinkel.state < 3|"°") { if (Tageszeit.state == "Tag") { Tageszeit.sendCommand("Vorabend") } } end rule "Es wird Abend" when Item Sonne_Hohenwinkel changed then if (Sonne_Hohenwinkel.state == NULL || Sonne_Hohenwinkel.state < -2|"°") { if (Tageszeit.state == "Vorabend") { Tageszeit.sendCommand("Abend") } } end rule "Es ist Nacht 1" when Time cron "0 15 22 ? * * *" then Tageszeit.sendCommand("Nacht") end rule "Es ist Nacht 2" when Time cron "0 0 4 ? * * *" then Tageszeit.sendCommand("Nacht") end rule "Debugging" when Item Tageszeit changed then logWarn("Tageszeit", "Tageszeit changed. Ist jetzt: {}", Tageszeit.state) end
Und es funktioniert. Wie durch Magie gehen die Lichterketten in der Morgen- und Abenddämmerung an und wieder aus. Jeden Tag zu einer leicht anderen Uhrzeit, immer so, dass es gerade dunkel ist.
Tag 3: Nächstes Jahr
Die obigen Schritte habe ich im Jahr 2021 durchgeführt (aber den Artikel nie veröffentlicht). Irgendwann nach Weihnachten habe ich damals die Lichterketten wieder abgebaut und die lichterkette.rules
entfernt. Jetzt ist November 2022, und ich habe die Lichterketten wieder aufgehängt. Und sie sollen auch bitte wieder funktionieren, also lade ich die unveränderte lichterkette.rules
wieder auf meinen openHAB-Server. Und siehe da, ich kann die Lichterketten immer noch ausschalten.
Aber nur ausschalten. Anschalten funktioniert nicht mehr. Das ist sehr seltsam.
Ich gehe in den Debugging-Modus, versuche herauszufinden, ob das if-else-Konstrukt der lichterkette.rules
funktioniert. Und was soll ich sagen, daran liegt es nicht, denn es funktioniert. Also muss ja die twinkly.py
defekt sein, denn was soll es sonst sein? Aber, oh Schreck, ich kann ja immer noch kein Python, was mache ich denn jetzt?
Naja, ich nutze Google. Ich finde andere Menschen mit dem gleichen Problem, aber leider ohne Lösung. Das wäre ja auch zu einfach gewesen.
Ich finde ein (inoffizielles) Twinkly-Binding, hier. Das ist doch vielversprechend, aber leider unterstützt es erst openHAB ab Version 3.2, und ich bin ja noch bei 3.0.1. - ich sollte sowieso updaten. Wie das geht, beschreibe ich in einem anderen Artikel, denn hier habe ich wieder ziemlich geflucht. Aber jetzt kommt die Magie: Nach dem Update auf openHAB 3.3 funktioniert twinkly.py
wieder problemlos. Anscheinend hat das Update alles gelöst, und ich muss das Binding nicht ausprobieren. Und mein Garten erstrahlt wieder im Twinkly-Glanz.