Digitalisierung

openHAB V: Müllkalender per ical einbinden

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 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.

h4>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.

Series Navigation<< openHAB IV: Temperatur, Heizung und Raumklima mit Homematic IP

Schreiben Sie einen Kommentar