Trigger actions with google calendar

Hey there=)

Today I want to show you how to send commands to devices using a google calendar:
With this you can add a appointment in Google Calendar and OpenHAB will check your calendar and will send commands correspondingly. Moreover you can trigger rules with this by using a proxy item.
This can be used to automatically turn of the TV at a certain time, turn on some lights when you’re not at home or turn a light red when you have to bring out the garbage due to the garbage truck coming soon.

Configuration


This requires the iCalendar Binding.


I added the calendar with PaperUI:

You can find the URL by going to the calendar settings under https://calendar.google.com/


Calendar.items

String      Kalender_AktuellesEvent_Name     "Aktuelles Event Name"     <calendar>    {channel="icalendar:calendar:1eaedae2:current_title"}
DateTime    Kalender_AktuellesEvent_Start    "Aktuelles Event Start"    <calendar>    {channel="icalendar:calendar:1eaedae2:current_start"}
DateTime    Kalender_AktuellesEvent_Ende     "Aktuelles Event Ende"     <calendar>    {channel="icalendar:calendar:1eaedae2:current_end"}

Calendar.rules

import java.time.format.DateTimeFormatter

val String Dateiname = "Kalender.rules"

var Timer timerTillAlarm = null
var item = ""
var command_on = ""
var command_off = ""


rule "Items durch Kalendereinträge steuern"
when
    Time cron "0 * * * * ?"
then
    Thread::sleep(3000) //Wait for 3 seconds to let Kalender_AktuellesEvent_Start update

    if (Kalender_AktuellesEvent_Name.state.toString == "UNDEF" ) { return } //Stopp if there is no current event

    var kalender_start = Kalender_AktuellesEvent_Start.getStateAs(DateTimeType).zonedDateTime.withZoneSameInstant((new DateTimeType).zonedDateTime.getZone).format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm"))
    var kalender_ende = new DateTime(Kalender_AktuellesEvent_Start.state.toString)

    command_on="ON"
    command_off="OFF"

    switch (Kalender_AktuellesEvent_Name.state.toString) //Itemnamen setzten
    {
        //Add the devices with a trailing space since Android likes to add them when used with autocompletion
        case "Ventilator", case "Ventilator ":          {item = "Steckdosen_Ventilator"}
        case "Heizdecke", case "Heizdecke ":            {item = "Steckdosen_Heizdecke"}
        case "Fussventilator", case "Fussventilator ":  {item = "Steckdosen_Fussventilator"}
        case "Fernseher", case "Fernseher ":            {item = "Multimedia_Aktuell" command_on="Fernseher" command_off="PowerOff"}
        case "Licht", case "Licht ":                    {item = "Sprachassistent_Licht"}
        default: return //Stopp if no device matched
    }

    if (kalender_start.toString == now.toString("yyyy-MM-dd'T'HH:mm")) {
        logInfo("Rule triggered", Dateiname + ": \"Items durch Kalendereinträge steuern\": An")
        sendCommand(item,command_on)
    }

    timerTillAlarm = createTimer(kalender_ende,
        [ |
            logInfo("Rule triggered", Dateiname + ": \"Items durch Kalendereinträge steuern\": Aus")
            sendCommand(item,command_off)
        ]
    )
end


//Rule which reschules the timer if the time of the end of the event changes
rule "Timer neustarten, wenn sich das Ende des Kalendereintrags verändert hat"
when
    Item Kalender_AktuellesEvent_Ende changed
then
    if (Kalender_AktuellesEvent_Start.state.toString == "UNDEF" ) { return }  //Stopp if there is no current event
    if (timerTillAlarm !== null) {
        logInfo("Rule triggered", Dateiname + ": \"Timer neustarten, wenn sich das Ende des Kalendereintrags verändert hat\": Timer wird neu eingestellt")
        timerTillAlarm.reschedule(new DateTime(Kalender_AktuellesEvent_Start.state.toString))
    }
end

You can add and change the devices and the commands you want to send to them by editing the switch-case switch (Kalender_AktuellesEvent_Name.state.toString) and the variables command_on and command_off

Conclusion

There are some tutorials on how to this however I didn’t find one complete one.
Have fun with this. If you have any questions feel free to post them below =)

2 Likes

Thank you for posting this example.
Isn’t there a mix up of iCalendar and CalDAV? Why do I need caldavio.cfg for iCalendar and what about a Calendar.things file?

I did some research and found out: the caldavio.cfg file is not used. However I added the calendar in the PaperUI. I updated the original post

Hi Felix (or others :slight_smile: ),

first of all thanks a lot for the tutorial!
I am running OH3 since a few days and - I guess as many of us - I now need to switch the garbage calendar from CalDAV to iCal. Actually it was quite easy to configure the bridge. Also the items (current event and next event - as far as I know the forecast is limited to these two) are created and basically running. But here is my problem: the “current” event is shown as UNDEF, the “next” event is is the first in the calendar considering from now which is shown (correctly shown).

Maybe my understanding from current and next is different to what is defined in OH? Does current mean today’s event or something like that?

Does anybody can give me a hint on how to get the next two events to be shown in habpanel? With CalDAV it was quite easy - even with more than two upcoming events.

BR,
wosch

“Current” is the event right NOW, the event that is currently running. If there is no event running right now the current event is UNDEF. The next event is the next event coming up.

Example: golfing from 3-4:
Current at 2o’clock: UNDEF
Next at 2’clock: Golfing

Current at 3:30: Golfing
Next at 3:30: 2nd event


The next event is the next event. For the second event I think you can edit the next event and replace the 1 with a 2 In the channel config or something similar.
Maybe you want to add a rule that sets the next event as the current event while a currently a event is going and set the second event as the next.

Yeah, I also thought that this is the definition of “current” since this is the only explanation. Unfortunately this is not what I wanted to achieve… I am showing the next two events in habpanel no matter if they are right now, tomorrow or in 5 weeks, you know what I mean?

In CalDAV it was easy to setup the next x events in changing from “1” to “2” and so on like you mentioned. iCal does only have channels for “current” and “next” events. However, you’re remark pointed me to the right direction: an eventfilter thing created within iCal binding can exactly do what I want (and actually what was possible in CalDAV also). Depending on the configuration of the eventfilter (Start, End, max. events, …) it is possible to create as much items you want by #0, #1, …

If so. has the same problem as I when updating to OH3 and will read this tutorial to stup iCal: eventfilter is the solution! :wink:

Thanks Felix!

Can you post your configuration for other people that might have the same problem. Thanks😊

Sure :slight_smile:

  1. After setting up the bridge in the main UI (basically as described in the tutorial above), go to Things ==> the blue + in the right corner ==> choose iCalendar binding ==> eventfilter

  2. As OH3 offers the possibility to change the identifier while creating a new thing and it’s not possible afterwards, it makes sense to rename it (“Cal”, “Muell”, …).

  3. Chose the bridge ==> iCal bridge of course

  4. chose number of max events depending on how many next events are interesting for you.
    Remark 1: this will define the number of items, each item may get 3 channels (start, end and title).
    Remark 2: I thought event number 0 will maybe the current event (see discussion above), but actually all events in the eventfilter are entries in your calendar, no matter if it’s right now, tomorrow or next week. So 3 events as max will lead to exactly 3 next events as long as they are within the range of chosen start and end (at least this is what I am expecting, I did not try). If I remember correctly this is like it is (was) in CalDAV.

  5. choose the base of the filter (minute, hour, day, or …) and start and end depending on what / how many events you need as item with a value in it. I choose day as base and start 0 (=now) and end = 50 since I was not sure what is the maximum between the three events in my case as I do only have the garbage scheduled and nothing else.

  6. It is also possible to setup the filter looking for text (event title, comment, location, …). I do not use this.

Last but not least you can link the channels within the UI or create items in VSC. Since I am using textbased items since years, I also created the items in an *.items file in this case.
Here is the code for the next event (#0) - all further events will be set up accordingly depending on the max. number of events in the evetnfilter thing (#1, #2, …):

DateTime Muell_0_start          "Start aktueller Abholtermin [%1$td.%1$tm.%1$tY]"       {channel="icalendar:eventfilter:Muellkalender:Muell:result_0#begin"}   
DateTime Muell_0_end            "Ende aktueller Abholtermin [%1$td.%1$tm.%1$tY]"        {channel="icalendar:eventfilter:Muellkalender:Muell:result_0#end"}  
String Muell_0_title            "Aktueller Abholtermin [%s]"                            {channel="icalendar:eventfilter:Muellkalender:Muell:result_0#title"}
1 Like

Thanks for your example, Felix!

I dont understand this delay though.

It does not look like we are triggering anything at Calendar’s side. Why do we expect it to start updating at the beginning of the Rule? Also, even if it did – we will just pick its state at the next Cron invocation, no big deal.

Am I missing something?

Okay, so at first I didn’t have this delay and had the following issue. That’s why I implemented it.

You have to keep in mind that the state of Kalender_AktuellesEvent_Name is UNDEF until the second of the start of the appointment. Only then the state changes to the name of the appointment.


Imagine this:
Your appointment starts at 14:00:00.
The rule starts at 14:00:00.
However the item updates a bit lately at 14:00:01.
Since the rule started at 14:00:00 it thinks there is no appointment since at that point in time Kalender_AktuellesEvent_Name still was UNDEF. Now the rule won’t do anything since it thinks there is no appointment.


No. You missed someting there:

This only runs in the minute of the starting of the appointment. In the next minute the if statement is false and the on-command would not be sent. The if statement would be false because the appointment start was at 14:00 and now it’s 14:01.


As a summary: I noticed that the item may take 0-2 seconds to update properly and I implemented this waiting to catch that. Questions?

Oh, I see.

By the way, don’t you think the Current Event Presence channel could be used instead of polling?

1 Like

Probably. I don’t use channel triggers at all so I didn’t think about that. However this should be possible and will remove the cron check every minute

I create a separate calendar for each device I want to control. In that calendar, I create an event for the entire duration that I’d like the switch flipped “ON”.

Creating a separate calendar for each device solves a lot of problems with overlapping events (ie. only one will show as the current_event, the other will be silently ignored). It’s also way easier than trying to parse through names and eventfilter results.

Also, because that separate calendar controls only that single device, we don’t really need to look at anything other than current_presence … no need to Thread::sleep(3000).

/*
// icalendar.things:
Bridge  icalendar:calendar:88ad9f2c26  "Calendar" @ "Internet" [ url="https://calendar.google.com/calendar/ical/<stuff>/basic.ics", refreshTime=20 ]

// icalendar.items:
Switch  current_presence  "current presence" <calendar> { channel="icalendar:calendar:88ad9f2c26:current_presence" }

// tplinksmarthome.things:
Thing  tplinksmarthome:hs110:5DF851  "StdyLamp" @ "Study"  [ deviceId="800<stuff>851" ]

// tplinksmarthome.items
Switch  TP_StdyLamp_Switch  "Switch"  { channel="tplinksmarthome:hs110:5DF851:switch" }
*/
val String FileName = "StudyLamp.rules"

rule "Control Switch via calendar entries"
when
    Item current_presence changed
then

    if (current_presence.state.toString == "ON") {
        logInfo(FileName, TP_StdyLamp_Switch.toString + " -> ON")
        sendCommand(TP_StdyLamp_Switch, "ON")
    }
    else {
        logInfo(FileName, TP_StdyLamp_Switch.toString + " -> OFF")
        sendCommand(TP_StdyLamp_Switch, "OFF")
    }

end

Interesting! So I assume you have one of these rules per item ( → per calendar), right?

I like this approach and think it is much easier than mine, espacially for triggering multiplay/many items

Correct. Each calendar will only handle a single Item. I was running into problems when I’d want multiple items to trigger at the same time, or events would overlap. current_event can only handle a single event, and multiple calendars was much easier to set up than parsing through all the eventFilter entries.

1 Like