ical-Abfallkalender in openHAB verwenden
Ein Beitrag in einem Forum brachte mich auf die Idee, mich in openHAB über anstehendene Abholtermine für die verschiedenen Abfallbehälter informieren zu lassen. Viele örtliche Abfallunternehmen bieten den jährlichen Kalender mittlerweile auch digital im ICAL-Format an. Diesen können wir nutzen, um ihn zunächst via JavaScript zu parsen und anschließend die anstehenden Termine in openHAB anzuzeigen. Dazu musste das ursprüngliche Script ein wenig angepasst und verschiedene Bereiche in openHAB konfiguriert werden. Näheres zeigt dieser Beitrag.
Verfügbare Artikel zu openHAB
Quelle und Dank
Zunächst möchte ich jedoch den eigentlichen Ursprung dieser Methode erwähnen. Vielen Dank dazu an „seppy“, der den „icalendar JavaScript parser“ für seine Zwecke angepasst hat und die Umsetzung für diesen Beitrag deutlich erleichtert hat. Ich muss zugeben, dass meine Java(-Script)-Skills gegen Null tendieren.
http://openhabforum.de/viewtopic.php?t=11
Abfallkalender finden
Zunächst benötigen wir den Abfallkalender im bereits erwähnten ical-Format. Dazu bemühen wir am besten eine Suchmaschine unserer Wahl und kämpfen uns durch bis zu unserem örtlichen Entsorgungsunternehmen. In unserem Beispiel arbeiten wir mit der „Entsorgungswirtschaft Soest GmbH“. Wenn das Unternehmen vor Ort einen solchen Service nicht anbietet, könnte man vielleicht mit diesem Kontakt aufnehmen und die Idee der digitalen Bereitstellung vorschlagen. In unserem Fall haben wir die genaue Adresse (URL) für den ical-Abfallkalender herausgefunden:
http://esg-soest.de/ak_ics.php?ak_ortid=11&ak_strid=8033&ak_j=2016
Addon installieren
Falls wir das „HTTP-Addon“ bisher für kein anderes Item einsetzen und dieses folglich noch nicht „installiert“ wurde, holen wir dieses nun nach. Dazu verwenden wir einfach die apt-Paketverwaltung:
apt-get install openhab-addon-binding-http
In diesem Fall arbeite ich mit der Version 1.8.0. Das einbinden neuer Addons kann auch im laufenden Betrieb von openHAB durchgeführt werden.
Items definieren
Anschließend konfigurieren wir openHAB und beginnen mit dem Anlegen neuer Items. Dazu kann übersichtshalber eine neue .item-Datei verwendet werden. Für unsere Abfall-Events benötigen wir drei neue Items:
String ABFALL_ICAL { http="<[http://esg-soest.de/ak_ics.php?ak_ortid=11&ak_strid=8033&ak_j=2016:360000:JS(abfall_heute.js)]" Switch ABFALL_AKTIV String ABFALL_NAME
Das erste Item wird mit dem Inhalt des in der URL angegebenen Kalenders befüllt. Dieser Inhalt wird jedoch zuvor mit dem Script „abfall_heute.js“ geparst und erhält als Rückgabe entweder „false“ oder den Wert der heutigen Abholung. Im zweiten Item definieren wir einen Schalter, der später auf aktiv gesetzt wird, wenn heute eine Abholung ansteht. Und zu guter Letzt befindet sich im dritten Item der Name der Abholung, wenn diese aktiv sein sollte.
Das Transform-Script
Kommen wir zum eigentlichen Kernstück des heutigen Beitrages, dem JavaScript. Dieses basiert auf ein bereits veröffentlichtes Script, um ical-Kalender zu durchsuchen und bei bestimmten Übereinstimmungen den aktuellen Termin als Rückgabewert zu liefern. Dieses wurde leicht verändert, so dass ein Termin nur bei absoluter Übereinstimmung mit dem heutigen Tag ausgegeben wird. Dieses Script speichern wird beispielsweise unter dem Namen „abfall_heute.js“ im Ordner „/ect/openhab/configurations/transform„.
var abfall = false; (function(i) { var Ical = function Ical(){ this.version = ''; this.prodid = ''; this.events = []; this.todos = []; this.journals = []; this.freebusys = []; } var xprops = 'x-[^:;]+'; var ianaprops = '[\\w]+[^:;]+' var icalParser = { icals : [], propsList : { 'event':'(dtstamp|uid|dtstart|class|created|description|geo|last-mod|location|organizer|priority|seq|status|summary|transp|url|recurid|rrule|dtend|duration|attach|attendee|categories|comment|contact|exdate|rstatus|related|resources|rdate|'+xprops+'|'+ianaprops+')', 'freebusy':'(dtstamp|uid|contact|dtstart|dtend|organizer|url|attendee|comment|freebusy|rstatus|'+xprops+'|'+ianaprops+')', 'journal':'(dtstamp|uid|class|created|dtstart|last-mod|organizer|recurid|seq|status|summary|url|rrule|attach|attendee|categories|comment|contact|description|exdate|related|rdate|rstatus|'+xprops+'|'+ianaprops+')', 'todo':'(dtstamp|uid|class|completed|created|description|dtstart|geo|last-mod|location|organizer|percent|priority|recurid|seq|status|summary|url|rrule|due|duration|attach|attendee|categories|comment|contact|exdate|rstatus|related|resources|rdate|'+xprops+'|'+ianaprops+')' }, parseIcal : function(icsString){ var cals = icsString.match(/BEGIN:VCALENDAR\r?\n(.*\r?\n)+?END:VCALENDAR/ig); for(var index in cals){ //console.log("--->"+index+" "+cals[index]); var ical = new Ical(); ical.version = this.getValue('VERSION',cals[index]); ical.prodid = this.getValue('PRODID',cals[index]); cals[index] = cals[index].replace(/\r\n /g,''); cals[index] = cals[index].replace(/BEGIN:VCALENDAR\r?\n/ig,''); var reg = /BEGIN:(V.*?)\r?\n(.*\r?\n)+?END:\1/gi; matches = cals[index].match(reg); if(matches){ for(i=0;i<matches.length;i++){ //console.log('---------->'+matches[i]+"\n<------------"); this.parseVComponent(matches[i],ical); } } this.icals[this.icals.length] = ical; } }, parseVComponent : function(vComponent,ical){ var nameComponent = vComponent.match(/BEGIN:V([^\s]+)/i)[1].toLowerCase(); vComponent = vComponent.replace(/\r?\n[\s]+/igm,''); //unfolding vComponent = vComponent.replace(/(^begin|^end):.*/igm,''); //console.log(nameComponent+' ++++ '+vComponent); var props = vComponent.match(new RegExp(this.propsList[nameComponent]+'[:;].*','gim')); if(props){ var component=[]; for(var index in props){ var nom = props[index].replace(/[:;].*$/,''); //console.log("--vcompo "+index+" "+nom); var propKey = /*'prop_'+*/nom.toLowerCase(); if(component[propKey]===undefined) component[propKey] = []; component[propKey][component[propKey].length] = this.getValue(nom,props[index]); component['raw'] = vComponent; } if(ical[nameComponent+'s'] !== undefined) ical[nameComponent+'s'][ical[nameComponent+'s'].length] = component; } }, getValue: function(propName,line){ //console.log(line); var prop={}; line = line.replace(/^\s+/g,'').replace(/\s+$/gi,''); reg = new RegExp('('+propName+')((?:;[^=]*=[^;:\n]*)*):([^\n\r]*)','gi'); var matches = reg.exec(line); if(matches){ //on a trouvé la propriété cherchée //console.log(propName+' ==] params='+RegExp.$2+' / valeur='+RegExp.$3); var valeur = RegExp.$3; var tab_params=[]; if(RegExp.$2.length>0){ //il y a des paramètres associés var params = RegExp.$2.substr(1).split(';'); var pair; for(k=0;k<params.length;k++){ pair = params[k].split('='); if(!pair[1]) pair[1] = pair[0]; tab_params[pair[0]] = pair[1]; } } prop = { value:valeur,name:propName }; if(Object.keys(tab_params).length>0) prop.params = tab_params; } return prop; }, } /** * Funktion um ical Datumsstring (YYYYMMDD) in Date Objekt zu wandeln */ function calenDateFrom(icalStr) { var strYear = icalStr.substr(0,4); var strMonth = parseInt(icalStr.substr(4,2),10)-1; var strDay = icalStr.substr(6,2); var strHour = 0; var strMin = 0; var strSec = 0; var oDate = new Date(strYear,strMonth, strDay, strHour, strMin, strSec) return oDate; } function calenDateTo(icalStr) { var strYear = icalStr.substr(0,4); var strMonth = parseInt(icalStr.substr(4,2),10)-1; var strDay = icalStr.substr(6,2); var strHour = 23; var strMin = 59; var strSec = 59; var oDate = new Date(strYear,strMonth, strDay, strHour, strMin, strSec) return oDate; } icalParser.parseIcal(input); var event = icalParser.icals[0].events[0]; icalParser.icals[0].events.forEach(function (currentValue, index, originalArray) { /** * Prüfen ob das aktuelle Datum zwischen zwei Daten liegt, bzw. auf einen Tag fällt */ var dateFrom = calenDateFrom(currentValue.dtstart[0].value); var dateTo = calenDateTo(currentValue.dtend[0].value); var dateCheck = new Date(); if (dateCheck >= dateFrom && dateCheck <= dateTo){ abfall = currentValue.summary[0].value; } }); return abfall; //return dateCheck; })(input);
Möchten wir anstatt der heutigen lieber die morgigen Abholtermine anzeigen, ändern wir die Zeile 96 in
var strDay = icalStr.substr(6,2)-1;
und die Zeile 109 in
var strDay = icalStr.substr(6,2)-1;
Dieses Script speichern wir ebenfalls im Ordner „/etc/openhab/configurations/transform/“ unter dem Dateinamen „abfall_morgen.js„. Daraufhin erstellen wir zusätzliche Items für die morgige Abfallabholung.
openHAB-Regeln erstellen
Nun können wir bereits jetzt den openHAB-Stream beobachten und warten ab, ob ein etwaiger Termin in das Item „ABFALL_ICAL“ geschrieben wird. Für die weitere Verwendung benötigen wir jedoch noch ein paar Regeln, die wir wie gewohnt in einer „.rules„-Datei im Ordner „rules“ erstellen:
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
Hier wird geprüft, ob das Item „ABFALL_ICAL“ über einen Terminwert verfügt. In diesem Fall wird das Item „ABFALL_AKTIV“ auf aktiv gesetzt und der Name der Abfallabholung in das Textitem „ABFALL_NAME“ geschrieben. Ansonsten wird das Item „ABFALL_AKTIV“ deaktiviert und das Item „ABFALL_NAME“ geleert.
Ein interessanter Anwendungszweck ist, sich beim Verlassen der Wohnung an das Rausstellen des Mülls erinnern zu lassen. Dazu erweitern wir beispielsweise eine bereits bestehenden Präsenz-Regel wie folgt:
rule "Wohnung verlassen" when Item RFID_TINA changed from ON to OFF then if (ABFALL_AKTIV.state == ON) { if(BOXEN_EZ.state == OFF) { sendCommand(BOXEN_EZ, ON) Thread::sleep(1000) executeCommandLine("/scripts/vorlesen.sh@@236@@Du musst an den " + ABFALL_NAME.state + " denken, Tina.@@" + BOXEN_EZ_VOL.state, 2000) Thread::sleep(10000) sendCommand(BOXEN_EZ, OFF) } if(BOXEN_EZ.state == ON) { executeCommandLine("/scripts/vorlesen.sh@@236@@Du musst an den " + ABFALL_NAME.state + " denken, Tina.@@" + BOXEN_EZ_VOL.state, 2000) } } end
In diesem Beispiel, dass wie ich zugeben muss, stark an die eigenen Gegebenheiten angepasst werden muss, wird beim verlassen der Wohnung geprüft, ob ein Abholtermin vorliegt. In diesem Fall werden die Lautsprecher im Esszimmer aktiviert und ein entsprechender Warnhinweis vorgelesen. Das Vorlesen mittels Text-to-Speach auf dem Raspberry habe ich bereits in diesem Artikel vorgestellt.
Die Sitemap erweitern
Sollte kein Abholtermin vorliegen, fände ich es weniger interessant, darüber eine Meldung anzeigen zu lassen. Daher nutze ich in diesem Fall ein dynamisches Item. Unser oben gezeigtes Regelwerk erlaubt es, zunächst festzustellen, ob ein Termin vorliegt oder nicht (Item „ABFALL_AKTIV„). Wenn dem so sein sollte, können wir in unserer Sitemap an gewünschter Stelle den Namen der Abholung anzeigen lassen:
Text item=ABFALL_NAME label="Abholung heute: [%s]" icon="abfall" visibility=[ABFALL_AKTIV==ON]
Das Icon „abfall“ habe ich wie immer aus der Bildersuche einer Suchmaschine gefunden ( im Format 32×32 Pixel) und unter „/opt/openhab/webapps/images“ gespeichert. Für den Privatgebrauch sehe ich da kein Problem. Bei Fragen oder Anregungen kann gerne das Kommentarsystem genutzt werden.
Peter fragte mich, ob es auch möglich wäre, die nächsten zwei Abholtermine anzuzeigen. Auch wenn meine Java-Script Kenntnisse gegen Null tendieren, habe ich eine Möglichkeit gefunden
Wir benötigen für die Umsetzung vier neue Transform-Scripte:
- Script 1 für die Ausgabe des Namens der nächsten Abholung
- Script 2 für die Ausgabe des Datums der nächsten Abholung
- Script 3 für die Ausgabe des Namens der übernächsten Abholung
- Script 4 für die Ausgabe des Datums der übernächsten Abholung
Die Scripte speichern wir wie gewohnt unter „/etc/openhab/configurations/transform„.
Zudem definieren wir 4 neue Items…
String ABF1_NAME { http="<[http://ADRESSE_DES_KALENDERS.ics:360000:JS(script1.js)]" } DateTime ABF1_DATE { http="<[http://ADRESSE_DES_KALENDERS.ics:360000:JS(script2.js)]" } String ABF2_NAME { http="<[http://ADRESSE_DES_KALENDERS.ics:360000:JS(script3.js)]" } DateTime ABF2_DATE { http="<[http://ADRESSE_DES_KALENDERS.ics:360000:JS(script4.js)]" }
…und binden die beiden nächsten Termine wie folgt in die Sitemap ein:
Text item=ABF1_DATE label="Nächster Abholtermin: [%1$tA, %1$td.%1$tm.%1$tY]" icon="abfall" { Frame label="Nächster Abholtermin" { Text item=ABF1_DATE label="Datum: [%1$tA, %1$td.%1$tm.%1$tY]" icon="calendar" Text item=ABF1_NAME label="Abholung: [%s]" icon="abfall" } Frame label="Übernächster Abholtermin" { Text item=ABF2_DATE label="Datum: [%1$tA, %1$td.%1$tm.%1$tY]" icon="calendar" Text item=ABF2_NAME label="Abholung: [%s]" icon="abfall" } } //Abfall Ende
Wenn jemand mit JavaScript-Kenntnissen einen bessere Weg kennt, möge er sich bitte in den Kommentaren melden.
Daniel Wenzel
18.02.2016 @ 22:26
Sieht sehr gut aus, diese Oberfläche.
Wenn ich nicht genau wüsste, dass ich in etwa 1 1/2 Jahren auf ein anderes Automatisierungssystem setzen würde, würde ich mir das mal genauer anschauen.
Aber das lohnt sich leider nicht.
Peter
18.02.2016 @ 19:38
So, nach ein paar kleinen Änderungen, passt es perfekt. :-)
Da ich cometVISU als Backend für openHab nutze war mir diese Funktion wichtig.
Jetzt habe ich erst mal eine Grundlage um daraus mir eine Geburtstagsanzeige zu machen.
Super Arbeit von Daniel. daumen+++
Anbei ein Link mit einem Bild von meiner Infaseite. Wie man sieht ist Geburtstage noch leer, wird meine nächste Baustelle.
http://hacksaw.de/openhab/cometVISU.jpg
Gruß Peter
Peter
18.02.2016 @ 12:14
Sorry, hatte die ganze Zeit die Seite im Browser geöffnet und die Änderung garnicht mitbekommen.
Mit dieser Lösung kann ich erst mal leben. Vielen Dank für Ihre Geduld.
Einzig wie erwartet, ist die ical-Datei nicht nach Datum aufgebaut sondern den Events. Sprich es sind z.B.: bei uns erst alle Termine „Hausmüll“ drin, dann kommen die Termine „Gelber Sack“ dann die „Papiertonne“ etc. Ich werde die ical Datei erst mal von Hand sortieren, ist ja nur einmal pro Jahr.
Nochmals vielen Dank für Ihre Geduld.
Gruß Peter
Peter
18.02.2016 @ 11:07
es wurde die URL der Quelle nicht übernommen
http://www.forum-raspberrypi.de/Thread-shellscript-google-kalender-per-shellscript-auslesen-abfragen?pid=125430#pid125430
Peter
18.02.2016 @ 11:06
Also mit folgendem Shellscript von:
#! /bin/bash
#Datei definieren
datei="MyCal.ics"
#Termine (Zeitpunkte)
termine=$(grep "DTEND" $datei)
#Für jeden Termin ein paar Infos ausgeben
for i in $termine
do
#Informationen
jahr=$(echo "$i" | grep "DTEND" | cut -d ":" -f2 | cut -c1-4 )
monat=$(echo "$i" | grep "DTEND" | cut -d ":" -f2 | cut -c5-6)
tag=$(echo "$i" | grep "DTEND" | cut -d ":" -f2 | cut -c7-8)
stunde=$(echo "$i" | grep "DTEND" | cut -d ":" -f2 | cut -c10-11)
minute=$(echo "$i" | grep "DTEND" | cut -d ":" -f2 | cut -c12-13)
titel=$(grep -A6 "$i" "$datei" | tail -n1 | cut -d ":" -f2)
#Infos ausgeben
echo "$tag.$monat.$jahr Entsorgung: $titel"
done
bekomme ich schon mal alle Entsorgungstermine inklussive Datum angezeigt. Müsste man jetzt nur noch sortieren nach Datum und begrenzen auf z.B.: 2 Termine nach dem aktuellem Datum.
Gruß Peter
Daniel Wenzel
18.02.2016 @ 10:56
Bei mir ist es aufgrund fehlender Kenntnisse nicht anders, aber ich glaube, ich habe eine Quick&Dirty Lösung gefunden und den Beitrag entsprechend aktualisiert.
Mit PHP-Kenntnissen würdest du erst recht nicht weiter kommen, da die Kalender durch JavaScript geparst werden ;)
Ich hoffe, ich konnte damit helfen.
Peter
18.02.2016 @ 08:09
Ich werde es versuchen. Wird bei mir leider ein Try and Error werden, da ich nur über rudimentäre PHP-Kentnisse verfüge. ;-)
Gruß Peter
Daniel Wenzel
18.02.2016 @ 07:30
Ah, jetzt verstehe ich. Du möchtest dir die nächsten beiden Abholtermine anzeigen lassen, unabhängig von der heutigen Abholung. Ja, das hört sich interessant an. Ich werde mir das mal anschauen. Falls dir vorher eine Lösung einfällt, lass es mich wissen. Daran bin ich ebenfalls interessiert ;)
Peter
17.02.2016 @ 21:03
OK, ich meinte es aber anders.
Ich wollte einfach 2 aufeinanderfolgende Termine die in der Zukunft liegen haben inklussive des Datums.
Angezeigt werden soll dann im Backend zB: Hausmüll 16.03.2016 und darunter zb: Wertstoff 18.03.2016
Ich habe das bis jetzt umständlich gemacht, in dem ich die ics-Datei zerpflückt habe Hausmüll Termine in eine Datei Wertstoff Termine eine andere Datei als Textfile. Die Icons wechselt er dann nach dem geparsten Namen. in den Rules definiere ich dann wenn das Datum dem heutigen Datum entspricht schreibt er auch statt des Datumstrings das Wort Heute aus. ansonsten wie oben beschrieben.
Gruß Peter
Daniel Wenzel
17.02.2016 @ 20:12
Für eine morgige Abholung habe ich ja bereits im Artikel ein Beispiel genannt. Für übermorgen ersetzt du das „-1“ in den Zeilen 96 und 109 einfach durch ein „-2“, zumindest in der Theorie.
Das ganze als neue Transform speichern und das neue Item dementsprechend anpassen.
Anonymous
17.02.2016 @ 20:02
Super, daumen++++ jetzt geht es.
Sowohl mit Ihren Link, als auch mit der *ics-Datei auf meinem Server.
Besten Dank.
Eine Frage hätte ich noch. Wie müsste die Datumsabfrage sein, wenn ich 2 Termine in der Zukunft angezeigt bekommen will?
Die Zuordnung Morgen, Heute oder Datum würde ich in der Rules auswerten.
Gruß Peter
Daniel Wenzel
17.02.2016 @ 15:38
Hallo,
ich habe mir das Thema nochmal genauer angeschaut und das Script in veränderter Form in den Beitrag gestellt. Das Script basiert nun auf der Version 0.7 vom ical-Parser und hat bei mir mit beiden Kalendern (der aus dem Beitrag und den für meine Region) einwandfrei funktioniert. Probier es bitte nochmal mit dem neuen Script.
Peter
17.02.2016 @ 13:22
Nachtrag: wenn ich den „unangepassten“ „icalendar JavaScript parser“ nehme, bekomme ich als Ausgabe
null bei Ihren Link zu Ihrem Entsorgungsunternehmen.
Peter
17.02.2016 @ 11:03
Hallo,
Danke für die Anleitung. Leider habe ich bis jetzt noch keinen Bescheid bekommen von unserem Entsorgungsunternehmen, zwecks Direktlink.
Man kann die *.ics nur über einen Button downloaden. Habe versucht zum Testen die *.ics auf meinen Webspace gelegt und die URL in den items eingefügt. Im Screen wird mir angezeigt: 2016-02-17 10:56:48.106 [INFO ] [runtime.busevents ] – ABFALL_ICAL state updated to false Ich habe die *.ics im Googlekalender importiert und freigegeben und den ical-Link in den items eingefügt, gleiches Ergebnis.
Dachte es liegt daran, das bei der ical-Datei unseres Entsorgungsunternehmen der Punkt PRODID fehlt. Habe deshalb mal den Link von Ihnen in den items eingefügt. Trotzdem Heute am 17.02.2016 ein Termin ansteht, bekomme ich ein false. Was habe ich nicht bedacht, oder falsch gemacht.
Gruß Peter <–Anfänger :-)