openHAB V: Müllkalender per ical einbinden

This entry is part 5 of 10 in the series openHAB
Geschätzte Lesezeit: < 1 Minute.

Mein Haus soll mich erinnern, wann ich Mülltonnen herausrollen soll. Dazu möchte ich den Abfallkalender, den die Stadt Langenfeld freundlicherweise zur Verfügung stellt, in openHAB einbinden.

Tag 1: Einbindung der ical-Datei

Glücklicherweise gibt es für dieses Projekt eine Anleitung, die ich Schritt-für-Schritt befolgen werde. Als erstes organisiere ich den Link für den Kalender: https://www.langenfeld.de/city_info/display/ical/m_calendar_ical.cfm?region_id=138&year=2020&city_id=298&street_id=8415. Sofort fällt das Fragment year=2020 auf. Das muss ich wohl jährlich ändern, fürchte ich. Nun installiere ich das HTTP-Binding /(binding-http1 – 1.14.0). So weit, so einfach.

Als nächstes werden Items erstellt. Diese lege ich in einer neuen Datei abfall.items an:

String ABFALL_ICAL { http="<[https://www.langenfeld.de/city_info/display/ical/m_calendar_ical.cfm?region_id=138&year=2020&city_id=298&street_id=8415:360000:JS(abfall_heute.js)] }"
Switch ABFALL_AKTIV
String ABFALL_NAME

Nun wird es komplizierter. Also: Komplizierter für das Verständnis, nicht für die tatsächliche Einbindung. Ich kopiere das 138-Zeilen-JavaScript von obiger Anleitung in eine neue Datei /ect/openhab2/transform/abfall_heute.js. Weil ich neugierig bin, schaue ich in die Logs. Aber, oh weh, so einfach funktioniert das wohl doch nicht. Ich bekomme einen Fehler:

2020-05-06 16:19:48.054 [INFO ] [el.core.internal.ModelRepositoryImpl] - Loading model 'abfall.items'
2020-05-06 16:19:49.113 [WARN ] [el.core.internal.ModelRepositoryImpl] - Configuration model 'abfall.items' has errors, therefore ignoring it: [2,1]: missing '}' at 'Switch'

Okay, das kann ich korrigieren - ich habe das entsprechende Snippet (oben) bereits korrigiert. Bei reinem copy-und-paste von meiner Seite sollte dieser Fehler also nicht auftreten - in der Anleitung ist der Fehler (Stand heute) vorhanden. Trotzdem erhalte ich den nächsten Fehler:

[INFO ] [el.core.internal.ModelRepositoryImpl] - Loading model 'abfall.items'
[WARN ] [.core.transform.TransformationHelper] - Cannot get service reference for transformation service of type JS
[WARN ] [ab.binding.http.internal.HttpBinding] - Couldn't transform response because transformationService of type 'JS' is unavailable

Okay, kurzes Suchen verrät mir, dass ich den JavaScript Transformation Service (transformation-javascript - 2.5.3) installieren muss. Also Add-ons > Transformations, installiert.

Es folgen die Rules, wie immer in einer neuen Datei abfall.rules:

rule "Abfallkalender"
when
   Item ABFALL_ICAL changed
then
   //Prüfen ob Abfall abgeholt wird
	if (ABFALL_ICAL.state != "false" && ABFALL_ICAL.state != "Uninitialized"){
      postUpdate(ABFALL_AKTIV,ON)
      postUpdate(ABFALL_NAME,ABFALL_ICAL.state)
   } else {
      postUpdate(ABFALL_AKTIV,OFF)
      postUpdate(ABFALL_NAME,"false")
   }
end

Und dann folgt noch das Snippet für die Sitemap:

    Text item=ABFALL_NAME label="Abholung heute: [%s]" icon="abfall" visibility=[ABFALL_AKTIV==ON]

Als Icon "abfall" nutze ich dieses Icon, welches ich unter icons/classic/ speichere.

Nun sollte der Kalender also funktionieren. Mal sehen, wie es aussieht und was die Logs sagen.

[WARN ] [ab.binding.http.internal.HttpBinding] - Transformation 'JS(abfall_heute.js)' threw an exception. [response=]
org.openhab.core.transform.TransformationException: An error occurred while executing script. TypeError: Cannot read property "events" from undefined in <eval> at line number 120
	at org.openhab.core.transform.TransformationHelper$TransformationServiceDelegate.transform(TransformationHelper.java:71) ~[bundleFile:?]
	at org.openhab.binding.http.internal.HttpBinding.execute(HttpBinding.java:218) [bundleFile:?]
	at org.openhab.core.binding.AbstractActiveBinding$BindingActiveService.execute(AbstractActiveBinding.java:146) [bundleFile:?]
	at org.openhab.core.service.AbstractActiveService$RefreshThread.run(AbstractActiveService.java:169) [bundleFile:?]

Oh-oh. Das klingt nicht gut. Tja. Vielleicht liegt dieser Fehler daran, dass heute kein Müll-Termin vorhanden ist. Ich schaue mir das Ganze morgen nochmal an, denn dann müsste ein Termin angezeigt werden.

Tag 2: Neuer Tag, neuer Fehler

Es gibt gute und schlechte Neuigkeiten. Die gute Neuigkeit ist, dass ich heute den Hinweis auf "Abholung heute:" in der Sitemap sehe. Der zugehörige Sichtbarkeits-Switch ABFALL_AKTIV muss also auf ON gesprungen sein, was wiederum bedeutet, dass ein Kalendereintrag für heute gefunden wurde. Das ist schon mal sehr gut.

Leider ist der Fehler trotzdem noch vorhanden. Als Ergebnis bekomme ich nicht angezeigt, welcher Abfall denn heute abgeholt wird. Anscheinend kann das Event nicht ordentlich ausgelesen werden.

In der Anleitung wird als Quelle für das Javascript dieses Skript genannt. Also kopiere ich dieses ijp-0.6.js, und ändere in den abfall.items die erste Zeile:

String ABFALL_ICAL { http="<[https://www.langenfeld.de/city_info/display/ical/m_calendar_ical.cfm?region_id=138&year=2020&city_id=298&street_id=8415:360000:JS(ijp-0.6.js)]" }

Ein Blick in die Logs zeigt diese Zeilen:

[vent.ItemStateChangedEvent] - ABFALL_ICAL changed from  to null
[vent.ItemStateChangedEvent] - ABFALL_NAME changed from  to null

Naja, immerhin keine Exception mehr.

Tag 3: Andere Strategie - CalDAV

So richtig funktioniert das nicht. Ich befürchte, dass meine Stadt die ics-Datei irgendwie nicht so ganz sauber zum Download anbietet, und dass die obigen Skripte deshalb die ics-Datei nicht ordentlich parsen können. Dummerweise habe ich zu wenig Ahnung von Parsen und JavaScript, sodass ich es auch nicht reparieren kann. Also mache ich es anders.

Ich synchronisiere meine Kalender ja sowieso per CalDAV. Und es gibt ein CalDAV-Binding für obenHAB. Und eine Anleitung gibt es auch. Dies probiere ich jetzt. Ich installiere also binding-caldav-personal1 (1.14.0).

Analog zur Anleitung bearbeite ich die Datei services/caldavio.cfg und ergänze folgende Zeilen am Ende:

caldavio:Muellkalender:url=<url>
caldavio:Muellkalender:username=<Username>
caldavio:Muellkalender:password=<Passwort>
caldavio:Muellkalender:reloadInterval=1440
caldavio:Muellkalender:preloadTime=20000
caldavio:Muellkalender:disableCertificateVerification=true

Nun lösche ich den ganzen nicht funktionierenden Kram (d.h. alles) aus abfall.items und ergänze:

String CalDav_Muelltonne   "[%s]"  <calendar> { caldavPersonal="calendar:Muellkalender type:UPCOMING eventNr:1 value:NAME" }
DateTime CalDav_Date "Datum der Abholung [%1$td.%1$tm.%1$tY]"    <calendar&g t;   { caldavPersonal="calendar:Muellkalender type:UPCOMING eventNr:1 value:START" }

Und ein Snippet in die default.sitemap, welches den vorigen Kram ersetzt:

    Frame {
        Text item=CalDav_Muelltonne
        Text item=CalDav_Date
    }

Tja, nur leider funktioniert auch das nicht. Hier ist der Log:

[vent.ItemStateChangedEvent] - CalDav_Muelltonne changed from NULL to UNDEF
[vent.ItemStateChangedEvent] - CalDav_Date changed from NULL to UNDEF

Also nutze ich Google. Dabei finde ich einige Dinge heraus: Zuerst einmal bin ich nicht der Einzige mit diesem Fehler. Leider wird hier keine echte Lösung vorgeschlagen, aber zumindest stelle ich fest, dass ich unter /var/lib/openhab2/etc/caldav einen Ordner Muellkalender habe, indem sich viele .ics-Dateien befinden. Das Binding funktioniert also, das Problem muss in der Item-Definition sein.

Tag 4: Möglicherweise funktioniert es

Der Titel ist bewusst vorsichtig gewählt, aber ich glaube, es funktioniert jetzt. Kommen wir zuerst zu den vermeidbaren Fehlern meinerseits.

In der Datei caldavPersonal.cfg ergänze ich folgende Zeile:

caldavPersonal:usedCalendars=caldavio:Muellkalender

In der Datei caldavCommand.cfg ergänze ich folgende Zeile:

caldavCommand:readCalendars=caldavio:Muellkalender

Das hätte ich beides schon längst machen sollen. Anschließend spiele ich etwas mit der caldavio.cfg herum. Ich weiß nicht genau, was ich alles ausprobiert habe, aber hiermit funktioniert es (derzeit):

caldavio:Muellkalender:url=<Url>
caldavio:Muellkalender:username=<Username>
caldavio:Muellkalender:password=<Passwort>
caldavio:Muellkalender:reloadInterval=1440
caldavio:Muellkalender:preloadTime=7000
caldavio:Muellkalender:disableCertificateVerification=true
#caldavio:Muellkalender:lastModifiedFileTimeStampValid=false

Als nächstes werden Traces für org.openhab.binding.caldav_personal, org.openhab.binding.caldav_command, und org.openhab.io.caldav aktiviert (Anleitung hier). Dies führte mich zu folgendem Problem:

[TRACE] [caldav.internal.job.EventReloaderJob] - loadFrom = 2020-05-11T11:17:11.677+01:00
[TRACE] [caldav.internal.job.EventReloaderJob] - loadTo = 2020-05-25T08:37:11.678+01:00
[TRACE] [caldav.internal.job.EventReloaderJob] - loading event: e7f8b797-ac65-403f-806f-e99efcc870f0:Restmüll graue Tonne
[DEBUG] [caldav.internal.job.EventReloaderJob] - Processing event 'Restmüll graue Tonne'
[DEBUG] [caldav.internal.job.EventReloaderJob] - No periods exist for event 'Restmüll graue Tonne'
[DEBUG] [caldav.internal.job.EventReloaderJob] - changing eventcontainer last modified to 2020-05-06T16:02:33.000+01:00
[TRACE] [.io.caldav.internal.CalDavLoaderImpl] - listeners for events: 2
[TRACE] [caldav.internal.job.EventReloaderJob] - eventContainer found: true
[TRACE] [caldav.internal.job.EventReloaderJob] - last resource modification: 2020-05-06T16:02:13.000+01:00
[TRACE] [caldav.internal.job.EventReloaderJob] - last change of already loaded event: 2020-05-06T16:02:13.000+01:00
[DEBUG] [caldav.internal.job.EventReloaderJob] - loading resource: <Url> (FSchangedTS not valid)
[TRACE] [caldav.internal.job.EventReloaderJob] - Raw URL: <url>
[TRACE] [caldav.internal.job.EventReloaderJob] - URL after encoding: <url>

Da steht No periods exist for event , und das hat mich stutzig gemacht. Und dann habe ich viel Hexerei betrieben. Die genaue Reihenfolge weiß ich nicht mehr, aber ich habe unter anderem die Bindings deinstalliert, den Cache geleert, und die Bindings neu installiert (Anleitung hier). Und ich habe in meinem CalDAV-Server (ownCloud) einen neuen Kalender angelegt, einen Kalendereintrag dorthin verschoben, und das Binding auf den neuen Kalender verwiesen. Und ich habe für einige Kalendereinträge die Dauer von 0 Minuten auf 1 Stunde verändert. Und irgendwie hat es dann geklappt. Ich vermute, dass vor allem die letzte Änderung, den Events einen Zeitraum zu geben, sehr wichtig war. Jedenfalls sehen die Traces jetzt so aus:

[DEBUG] [.io.caldav.internal.CalDavLoaderImpl] - return event list for CalDavQuery [calendarIds=[Muellkalender], from=2020-05-11T11:52:45.848+01:00, to=null, sort=ASCENDING, filterName=null] with 1 entries
[DEBUG] [ldav_personal.internal.CalDavBinding] - found 1 events for config: CalDavPresenceConfig [calendar=[Muellkalender], type=UPCOMING, eventNr=1, value=START, filterName=null, categoriesFiltersAny=false]
[TRACE] [ldav_personal.internal.CalDavBinding] - found event 19874479-6f54-45db-8d44-ddeddf80d04c(Restmüll graue Tonne@14.05.2020/02:00-14.05.2020/03:00) for config CalDavPresenceConfig [calendar=[Muellkalender], type=UPCOMING, eventNr=1, value=START, filterName=null, categoriesFiltersAny=false]
[DEBUG] [ldav_personal.internal.CalDavBinding] - sending command 2020-05-14T02:00:00 for item CalDav_Date
[TRACE] [ldav_personal.internal.CalDavBinding] - command 2020-05-14T02:00:00 successfully sent
[DEBUG] [ldav_personal.internal.CalDavBinding] - found 1 events for config: CalDavPresenceConfig [calendar=[Muellkalender], type=EVENT, eventNr=1, value=NAME, filterName=null, categoriesFiltersAny=false]
[TRACE] [ldav_personal.internal.CalDavBinding] - found event 19874479-6f54-45db-8d44-ddeddf80d04c(Restmüll graue Tonne@14.05.2020/02:00-14.05.2020/03:00) for config CalDavPresenceConfig [calendar=[Muellkalender], type=EVENT, eventNr=1, value=NAME, filterName=null, categoriesFiltersAny=false]
[DEBUG] [ldav_personal.internal.CalDavBinding] - sending command Restmüll graue Tonne for item CalDav_Muelltonne
[TRACE] [ldav_personal.internal.CalDavBinding] - command Restmüll graue Tonne successfully sent
[DEBUG] [ldav_personal.internal.CalDavBinding] - found 1 events for config: CalDavPresenceConfig [calendar=[Muellkalender], type=UPCOMING, eventNr=1, value=NAME, filterName=null, categoriesFiltersAny=false]
[TRACE] [ldav_personal.internal.CalDavBinding] - found event 19874479-6f54-45db-8d44-ddeddf80d04c(Restmüll graue Tonne@14.05.2020/02:00-14.05.2020/03:00) for config CalDavPresenceConfig [calendar=[Muellkalender], type=UPCOMING, eventNr=1, value=NAME, filterName=null, categoriesFiltersAny=false]
[DEBUG] [ldav_personal.internal.CalDavBinding] - sending command Restmüll graue Tonne for item CalDav_NaechsteMuelltonne
[TRACE] [ldav_personal.internal.CalDavBinding] - command Restmüll graue Tonne successfully sent

Abschließend verschiebe ich alle Einträge in meinen neuen Kalender, und stelle sicher, dass alle auch eine echte Dauer haben.

Dann kann ich mich ja jetzt darum kümmern, dass das Ganze in meiner Sitemap auch so aussieht, wie ich es gerne hätte. Dazu installiere ich als erstes das NTP-Binding (binding-ntp - 2.5.3), da mir dieses die aktuelle Zeit ausgibt. Anschließend ergänze ich in der abfall.items:

Switch Abfall_Aktiv
String Abfall_Name
DateTime date_today "Heute [%1$td.%1$tm.%1$tY]"   <calendar>  { channel="ntp:ntp:local:dateTime" }

Es folgt abfall.rules, eine leicht modifizierte Variante von oben:

rule "Abfallkalender"
when
   Item CalDav_Date changed
then
   //Prüfen ob Abfall abgeholt wird
	if (CalDav_Date == date_today){
      postUpdate(Abfall_Aktiv,ON)
      postUpdate(Abfall_Name,CalDav_NaechsteMuelltonne)
   } else {
      postUpdate(Abfall_Aktiv,OFF)
      postUpdate(Abfall_Name,"false")
   }
end

Eigentlich habe ich nur den Vergleich angepasst. Hoffentlich führt dieser Vergleich dazu, dass mir immer dann Mülltonnen angezeigt werden, wenn im Kalender ein Eintrag für den aktuellen Tag vorhanden ist. Und nun noch die Sitemap:

    Frame {
	Text item=Abfall_Name label="Abholung heute: [%s]" icon="abfall" visibility=[Abfall_Aktiv==ON]
    }

Ob das alles wirklich so klappt, werde ich erst in ein paar Tagen sehen. Aber bisher sieht es gut aus.

Tag 4: Kontrolle - Bisher funktioniert es

Grundsätzlich funktioniert es. Ich bekomme regelmäßig die Abholtemine angezeigt - und zwar, wie gewünscht, immer den nächsten Termin.

Leider klappt es aber nicht, dass ich immer nur den aktuellsten Abholtermin an dem Abholtag angezeigt bekomme. Daran ist vermutlich mein Kalendereintrag Schuld. Alle Abholtermine sind früh morgens, von 03:00 bis 04:00 Uhr hinterlegt. Das bedeutet, dass der Vergleich eines UPCOMING-Events mit dem aktuellen Tag nur nachts von 00:00 bis 03:00 positiv ist, und nur um diese Zeit sichtbar ist. Das ist natürlich doof, aber ich wüsste nicht, wie es anders gehen soll. Vielleicht muss ich hier auf Warnhinweise oder Alerts umsteigen, aber das ist ein Problem für einen anderen Tag.

Tag 5: Notification

Wie bereits angesprochen, möchte ich Benachrichtigungen auf mein Handy erhalten, wenn eine Mülltonne abgeholt wird. Die einfachste Variante wäre, den Kalender auf dem Handy zu abonnieren und einfach dort Termin-Erinnerungen zu aktivieren, aber ich möchte es gerne via openHAB ausprobieren.

Es gibt, wie so oft, eine Anleitung für Benachrichtigungen. Dort wird als erster Schritt erwähnt, dass an sein openHAB mit myopenhab.com verbinden solle, mit einer genauen Anleitung hier. Also installiere ich das Binding openHAB Cloud Connector (misc-openhabcloud - 2.5.3). Anschließend navigiere ich zu Configuration > Services > openHAB Cloud > Configure und ändere den Zugriff auf Notifications. Remote-Zugriff möchte ich (noch) nicht.

Es folgt die Registrierung auf myopenhab.org. Dazu benötige ich UUID und ein Passwort, die ich aus /var/lib/openhab2/uuid und /var/lib/openhab2/openhabcloud/secret extrahiere - und natürlich einen Benutzernamen und ein Passwort. Nun wird mir meine openHAB-Instanz als offline angezeigt - wenig verwunderlich, da ich ja keinen Remote-Zugriff erlaube.

Weiter geht es. Ich möchte testen, ob die Benachrichtigungen funktionieren. Dazu lege ich mir testweise benachrichtigung.items an:

Switch  Send_via_App_Switch         "Benachrichtigung per App"      

Außerdem ergänze ich in der Sitemap:

    Frame {
        Switch item=Send_via_App_Switch
    }

Und nun lege ich benachrichtigung.rules mit einer Testnachricht an:

rule "Sende Benachrichtigung"
when
    Item Send_via_App_Switch changed
then
    if(Send_via_App_Switch.state == ON)
    {
        sendNotification("<E-Mail-Adresse>", "Dies ist eine Benachrichtigung.")

        Send_via_App_Switch.postUpdate(OFF)
    }
end

Natürlich funktioniert dies nicht direkt. Naja, ich habe vergessen, die openHAB-App auf meinem Smartphone mit dem Account auf myopencloud.org zu verbinden. Das hole ich nach, und schon funktioniert es.

Sehr gut. Aber eigentlich möchte ich ja etwas anderes. Eigentlich möchte ich ja, dass mir die nächste Mülltonne zur Abholung angezeigt wird. Also ändere ich Zeile 7 der benachrichtigung.rules:

        sendNotification("<E-Mail-Adresse>", "Abholung heute: " + CalDav_NaechsteMuelltonne.state)

Das funktioniert tatsächlich. Ich bin begeistert. Nun überarbeite ich meine abfall.rules:

rule "Abfallkalender"
when
   Item CalDav_Date changed
then
   //Prüfen ob Abfall abgeholt wird
    if (CalDav_Date == date_today){
        sendNotification("<E-Mail-Adresse>", "Abholung heute: " + CalDav_NaechsteMuelltonne.state)
   } 
end

Ob dies tatsächlich funktioniert, werde ich erst in ein paar Tagen wissen, wenn tatsächlich etwas abgeholt wird.

Tag 6: Notifications, Teil 2

Leider funktionieren die Benachrichtigungen nicht. Möglicherweise liegt es daran, dass die Events nachts um 03:00 Uhr aktiv werden, das Item aber mittags aktualisiert wird. Dann wäre CalDav_Date bereits der nächste Tag, und somit würde der Vergleich CalDav_Date == date_today niemals TRUE werden. Das ist nur eine Vermutung, jedenfalls probiere ich es jetzt anders.

Die Idee, die ich jetzt verfolge ist, dass ich eine Benachrichtigung erhalte, wenn ein Event auf aktiv geschaltet wird. Das sollte dazu führen, dass ich nachts eine Nachricht bekomme, und dann morgens sehe, dass ich die Mülltonne bereitstellen muss. Also ändere ich in der abfall.items die zweite Zeile:

String CalDav_Muelltonne   "[%s]"   { caldavPersonal="calendar:'Muellkalender' type:'ACTIVE' eventNr:'1' value:'NAME'" }

Und ich ändere die abfall.rules:

rule "Abfallkalender"
when
   Item CalDav_Muelltonne changed
then
   sendNotification("<E-Mail-Adresse>", "Abholung heute: " + CalDav_Muelltonne.state) 
end

Mal schauen, ob es diesmal klappt. Falls ja, muss ich die Sitemap noch bearbeiten. Und vielleicht ist die Bedingung Item CalDav_Muelltonne changed noch zu generisch, ich könnte mir vorstellen, dass ich hier zwei Benachrichtigungen erhalte: Eine zu Beginn und eine am Ende des Events. Wenn das so wäre, wäre dies vermutlich leicht zu beheben - ich würde mich freuen, wenn ich überhaupt Nachrichten bekomme.

Okay, direkt bei Upload erhalte ich eine Nachricht: Abholung heute: UNDEF. Okay, es hat also geklappt, aber diesen Fehler möchte ich direkt abfangen. Ich füge eine kleine Bedingung in meine abfall.rules ein:

rule "Abfallkalender"
when
   Item CalDav_Muelltonne changed
then
   if (CalDav_Muelltonne.state != UNDEF) {
      sendNotification("<E-Mail-Adresse>", "Abholung heute: " + CalDav_Muelltonne.state) 
   }
end

Erneut, mal sehen, ob dies funktioniert. Ich bin ausnahmsweise optimistisch.

Tag 7: Es funktioniert!

Folgende Meldung fand sich heute Morgen auf meinem Smartphone:

2020-07-12_abfallkalender-notification

Hurra, es funktioniert!

Schlussworte

Diese Implementierung funktionierte tatsächlich verlässlich, bis ich auf openHAB 3.0 umgestiegen bin. Dort gibt es das bisherige Caldav-Binding nicht mehr, also musste ich von vorne beginnen. Meine Bemühungen sind hier dokumentiert.

Series Navigation<< openHAB IV: Temperatur, Heizung und Raumklima mit Homematic IPopenHAB VI: Beleuchtung mit Philips Hue >>

Schreiben Sie einen Kommentar