Hello there !
My husband and me are traveling often and often on short notice for work. In Florida cooling costs are a concern. And when we are rushing out the door to catch a flight, turning the AC to humidistat and making sure all the lights are turned off can be easily forgotten.
Our openHab set-up tracks separately if we are at home, just “away” for a short while (i.e. at work, shopping etc), or “gone” (i.e. on vacation or a work trip), combines this information and then controls AC, lights etc accordingly. It takes 26 hrs without a ping of our cellphones to go from at home to gone. So some time ago I wrote a rule that used filtered Caldav events for the events that google mail auto-generates for flight confirmations in our google mail accounts to speed up the process.
With us upgrading to openHAB 2.5.0.M2 Milestone Build, the CalDav binding finally stopped working completely for us so we switched to a slightly hacked version of @davorf 's Python script to get events from google calendar and a completely rebuilt version of the rule as the script doesn’t allow for the filtering function the CalDav binding provided.
So here it goes:
Calendar Script
The husband modified Davorf’s to include an additional field that indicates which calendar an event comes from in a multi calendar set-up. Here is the changed version on Git-hub. We followed the installation instructions in Davorf’s post. The new field takes the calendar name from the CalSyncHab.ini file
[CalendarIDs]
<Calendarname>=<calendar ID>
and puts it into an additional item for each event
String <ItemPrefix>Event<Number>_CalId
that needs to be created for each event up to your maximum count.
Note that having more than 9 events will potentially break the rule as sortBy(name) will sort Event10 in front of Event1. Our setup uses 9 events. Here our CalSyncHab.ini
[General]
ApplicationName: Openhab Calendar
[Calendar]
Scope: https://www.googleapis.com/auth/calendar.readonly
MaxEvents: 9
TimeZone: -04:00
ClientSecretFile: /etc/openhab2/scripts/CalSyncHAB/CalSyncHABSecret.json
[CalendarIDs]
Alex =xxx@gmail.com
Mat=xxx@gmail.com
[OpenHAB]
HostName: xxx.xxx.xxx.xxx
Port: 8080
ItemPrefix: gCal_
Item files
schedule.items
Group gCalEvent
Group gCal
String gCal_Event1 (gCal)
String gCal_Event2 (gCal)
String gCal_Event3 (gCal)
String gCal_Event4 (gCal)
String gCal_Event5 (gCal)
String gCal_Event6 (gCal)
String gCal_Event7 (gCal)
String gCal_Event8 (gCal)
String gCal_Event9 (gCal)
String gCal_Event1_Summary "Event1 Sum.: [%s]" <calendar> (gCalEvent)
String gCal_Event1_Location "Event1 Loc.: [%s]" <calendar> (gCalEvent)
String gCal_Event1_Description "Event1 Desc. [%s]" <calendar> (gCalEvent)
DateTime gCal_Event1_StartTime "Event1 Start [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> (gCalEvent)
DateTime gCal_Event1_EndTime "Event1 End [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> (gCalEvent)
String gCal_Event1_CalId "Event1 Source Calendar [%s]" <calendar> (gCalEvent)
String gCal_Event2_Summary "Event2 Sum.: [%s]" <calendar> (gCalEvent)
String gCal_Event2_Location "Event2 Loc.: [%s]" <calendar> (gCalEvent)
String gCal_Event2_Description "Event2 Desc. [%s]" <calendar> (gCalEvent)
DateTime gCal_Event2_StartTime "Event2 Start [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> (gCalEvent)
DateTime gCal_Event2_EndTime "Event2 End [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> (gCalEvent)
String gCal_Event2_CalId "Event2 Source Calendar [%s]" <calendar> (gCalEvent)
String gCal_Event3_Summary "Event3 Sum.: [%s]" <calendar> (gCalEvent)
String gCal_Event3_Location "Event3 Loc.: [%s]" <calendar> (gCalEvent)
String gCal_Event3_Description "Event3 Desc. [%s]" <calendar> (gCalEvent)
DateTime gCal_Event3_StartTime "Event3 Start [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> (gCalEvent)
DateTime gCal_Event3_EndTime "Event3 End [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> (gCalEvent)
String gCal_Event3_CalId "Event3 Source Calendar [%s]" <calendar> (gCalEvent)
String gCal_Event4_Summary "Event4 Sum.: [%s]" <calendar> (gCalEvent)
String gCal_Event4_Location "Event4 Loc.: [%s]" <calendar> (gCalEvent)
String gCal_Event4_Description "Event4 Desc. [%s]" <calendar> (gCalEvent)
DateTime gCal_Event4_StartTime "Event4 Start [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> (gCalEvent)
DateTime gCal_Event4_EndTime "Event4 End [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> (gCalEvent)
String gCal_Event4_CalId "Event4 Source Calendar [%s]" <calendar> (gCalEvent)
String gCal_Event5_Summary "Event5 Sum.: [%s]" <calendar> (gCalEvent)
String gCal_Event5_Location "Event5 Loc.: [%s]" <calendar> (gCalEvent)
String gCal_Event5_Description "Event5 Desc. [%s]" <calendar> (gCalEvent)
DateTime gCal_Event5_StartTime "Event5 Start [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> (gCalEvent)
DateTime gCal_Event5_EndTime "Event5 End [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> (gCalEvent)
String gCal_Event5_CalId "Event5 Source Calendar [%s]" <calendar> (gCalEvent)
String gCal_Event6_Summary "Event6 Sum.: [%s]" <calendar> (gCalEvent)
String gCal_Event6_Location "Event6 Loc.: [%s]" <calendar> (gCalEvent)
String gCal_Event6_Description "Event6 Desc. [%s]" <calendar> (gCalEvent)
DateTime gCal_Event6_StartTime "Event6 Start [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> (gCalEvent)
DateTime gCal_Event6_EndTime "Event6 End [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> (gCalEvent)
String gCal_Event6_CalId "Event6 Source Calendar [%s]" <calendar> (gCalEvent)
String gCal_Event7_Summary "Event7 Sum.: [%s]" <calendar> (gCalEvent)
String gCal_Event7_Location "Event7 Loc.: [%s]" <calendar> (gCalEvent)
String gCal_Event7_Description "Event7 Desc. [%s]" <calendar> (gCalEvent)
DateTime gCal_Event7_StartTime "Event7 Start [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> (gCalEvent)
DateTime gCal_Event7_EndTime "Event7 End [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> (gCalEvent)
String gCal_Event7_CalId "Event7 Source Calendar [%s]" <calendar> (gCalEvent)
String gCal_Event8_Summary "Event8 Sum.: [%s]" <calendar> (gCalEvent)
String gCal_Event8_Location "Event8 Loc.: [%s]" <calendar> (gCalEvent)
String gCal_Event8_Description "Event8 Desc. [%s]" <calendar> (gCalEvent)
DateTime gCal_Event8_StartTime "Event8 Start [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> (gCalEvent)
DateTime gCal_Event8_EndTime "Event8 End [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> (gCalEvent)
String gCal_Event8_CalId "Event8 Source Calendar [%s]" <calendar> (gCalEvent)
String gCal_Event9_Summary "Event9 Sum.: [%s]" <calendar> (gCalEvent)
String gCal_Event9_Location "Event9 Loc.: [%s]" <calendar> (gCalEvent)
String gCal_Event9_Description "Event9 Desc. [%s]" <calendar> (gCalEvent)
DateTime gCal_Event9_StartTime "Event9 Start [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> (gCalEvent)
DateTime gCal_Event9_EndTime "Event9 End [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> (gCalEvent)
String gCal_Event9_CalId "Event9 Source Calendar [%s]" <calendar> (gCalEvent)
Group Alex_Flight_Group
String Alex_Flight_Origin "Departure airport of Alex' next flight [%s]" (Alex_Flight_Group)
String Alex_Flight_Destination "Destination airport of Alex' next flight [%s]" (Alex_Flight_Group)
DateTime Alex_Flight_Departure "Departure time of Alex' next flight [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> (Alex_Flight_Group)
DateTime Alex_Flight_Landing "Arrival time of Alex' next flight [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> (Alex_Flight_Group)
String Alex_Flight_Type "Flight type of Alex' next flight [%s]" (Alex_Flight_Group)
String Alex_Flight_Number "Flight Code for Alex' next flight [%s]" (Alex_Flight_Group)
Group Mat_Flight_Group
String Mat_Flight_Origin "Departure airport of Mat' next flight [%s]" (Mat_Flight_Group)
String Mat_Flight_Destination "Destination airport of Mat' next flight [%s]" (Mat_Flight_Group)
DateTime Mat_Flight_Departure "Departure time of Mat' next flight [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> (Mat_Flight_Group)
DateTime Mat_Flight_Landing "Arrival time of Mat' next flight [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> (Mat_Flight_Group)
String Mat_Flight_Type "Flight type of Mat' next flight [%s]" (Mat_Flight_Group)
String Mat_Flight_Number "Flight Code for Mat' next flight [%s]" (Mat_Flight_Group)
Switch RefreshCalendar
occupancy.items
Group FamilyG
String Alex (FamilyG)
String Mat (FamilyG)
Number Alex_OSM "Alex Occupancy State [%d]" (House_OSM)
Number Mat_OSM "Mat Occupancy State [%d]" (House_OSM)
Number Guest_OSM (House_OSM)
Group:Number:MIN House_OSM
Rule
import org.eclipse.smarthome.model.script.ScriptServiceUtil
import java.util.Map
// LIST OF HOME AIRPORTS
//adjust this list to reflect which airports you are normally using as your "Home Airport".
//Due to the way google auto generates its events the first list has to include the Airport code the second is without.
val homeairports = newArrayList("Orlando MCU","Fort Lauderdale FLL","Miami MIA","Melbourne MLB")
val homeairports2= newArrayList("Orlando","Fort Lauderdale","Miami","Melbourne")
// map to keep track of the unnamed timers for all involved persons
val Map<String, Timer> timers = newHashMap
/***********************************************/
/* GetCalEvents retrieves your google calendar */
/* events via CalSynHAB.py every 30 minutes */
/***********************************************/
rule "GetCalEvents"
when
Time cron "30 0/30 * * * ?" or
Item RefreshCalendar changed to ON
then
var String results = executeCommandLine("/etc/openhab2/scripts/CalSyncHAB.sh",5*1000)
logInfo("GetCalEvents", results)
RefreshCalendar.postUpdate(OFF)
end
/************************************************/
/* Check for Flight Even */
/* every 30 minutes 1 minute after the calendar*/
/************************************************/
rule "Check Calendar for Special Events"
when
Time cron "31 0/30 * * * ?" or
Item testrule changed to ON
then
logInfo("Calender", "Calendar events updated. Starting search for special events")
// find first flight event for each calendar/person
FamilyG.members.forEach[ mem |
val event = gCal.members.filter[ i | gCalEvent.members.filter[ act |
act.name == i.name+"_Summary"].head.state.toString.split(" ").get(0) == "Flight"].sortBy[ name ].sortBy[name].findFirst[ gc |
gCalEvent.members.filter[ a | a.name == gc.name+"_CalId"].head.state.toString == mem.name.toString]
//create a shorthand variable for the origin calendar/person
val person = mem.name.toString
if (event !== null) {
// cancel previous timer for this person/calendar
if (timers.get(person) !== null) {
timers.get(person).cancel()
timers.remove(person)
logInfo("Calendar", "Cancelled previous timer")
}
// for each event describing a flight get the correct items
val eventSummary = gCalEvent.members.filter[ a |
a.name == event.name+"_Summary"].head.state
val origin = gCalEvent.members.filter[ a |
a.name == event.name+"_Location"].head.state
val departure = gCalEvent.members.filter[ a |
a.name == event.name+"_StartTime"].head.state
val landing = gCalEvent.members.filter[ a |
a.name == event.name+"_EndTime"].head.state
// val description = gCalEvent.members.filter[ a |
// a.name == event.name+"_Description"].head.state
//Description is currently unused but could be easily used to get the link to the original email etc.
// do some string manipulation to get the departure airport and flight number from event description
var String tempstring = eventSummary.toString.replace("(",",").split(",").get(0).trim()
var String flightNumber = eventSummary.toString.replace("(",",").split(",").get(1).replace(")"," ").trim()
var String destination = tempstring.substring(9,tempstring.length())
logInfo("Calendar", "Found Flight from "+ origin + " to " + destination + " Flight Number (" + flightNumber + ") for " + person)
//set flight items
ScriptServiceUtil.getItemRegistry.getItem(person+"_Flight_Origin").postUpdate(origin)
ScriptServiceUtil.getItemRegistry.getItem(person+"_Flight_Destination").postUpdate(destination)
ScriptServiceUtil.getItemRegistry.getItem(person+"_Flight_Departure").postUpdate(departure)
ScriptServiceUtil.getItemRegistry.getItem(person+"_Flight_Landing").postUpdate(landing)
ScriptServiceUtil.getItemRegistry.getItem(person+"_Flight_Number").postUpdate(flightNumber)
// Find out if the next flight is an outgoing flight (leaving from one of your home airports)
if (homeairports.contains(origin) == true){
ScriptServiceUtil.getItemRegistry.getItem(person+"_Flight_Type").postUpdate("out")
logDebug("Calendar","Found outgoing flight")
var DateTime starttime = parse(departure.toString)
//create execution timer
timers.put(person,createTimer(starttime, [ |
// Here your actions to be started with the departure of an outgoing flight
// In this case a message is logged and the occupancy state machine for the correct person set to 3 = gone.
ScriptServiceUtil.getItemRegistry.getItem(person+"_OSM").postUpdate(3)
timers.put(person, null)
logInfo("Calendar",person +"'s flight is departing from " + origin+". Setting their state to GONE")
]))
}
// If it was not an outgoing flight. Check if the flight goes to one of your home airports and is an incoming flight.
else if (homeairports2.contains(destination) == true){
ScriptServiceUtil.getItemRegistry.getItem(person+"_Flight_Type").postUpdate("in")
//create execution timer
var DateTime starttime = parse(landing.toString)
timers.put(person,createTimer(starttime, [ |
// Here your actions to be started with the landing of an incoming flight
// In this case a message is logged and the occupancy state machine for the correct person set to 2= away.
ScriptServiceUtil.getItemRegistry.getItem(person+"_OSM").postUpdate(2)
timers.put(person, null)
logInfo("Calendar",person +"'s flight is landing in " + destination+". Setting their state to AWAY")
]))
logDebug("Calendar","The flight is incoming")
}
// If no home airport is involved it must be a connecting flight. In this case no action is scheduled.
else {
ScriptServiceUtil.getItemRegistry.getItem(person+"_Flight_Type").postUpdate("connecting")
logDebug("Calendar","The flight is connecting")
}
}
//If no flight events are currently found on the calendar: clear old info.
else {
ScriptServiceUtil.getItemRegistry.getItem(person+"_Flight_Origin").postUpdate("none")
ScriptServiceUtil.getItemRegistry.getItem(person+"_Flight_Destination").postUpdate("none")
//ScriptServiceUtil.getItemRegistry.getItem(person+"_Flight_Departure").postUpdate("none")
//ScriptServiceUtil.getItemRegistry.getItem(person+"_Flight_Landing").postUpdate("none")
ScriptServiceUtil.getItemRegistry.getItem(person+"_Flight_Type").postUpdate("none")
ScriptServiceUtil.getItemRegistry.getItem(person+"_Flight_Number").postUpdate("none")
logInfo("Calendar","Cancelled old flight timer for "+ person.toString)
}
logDebug("Calendar", "finished for " + person)
]
logInfo("Calender", "End of search for special events")
end
The “GetCalEvents” Rule is from the example file that came with the CalSyncHab script.
The “check calendar for special events rules” is heavily based on various of @rlkoshak 's Design Patterns most importantly Working with Groups. Thanks for all those amazing explanations! I would be completely lost in Rules DSL without them.
Hope this helps somebody in their set-up and experiments. I am looking forward to hearing your thoughts.