Update simple-sync-trigger.groovy
[smartapps.git] / official / simple-sync-trigger.groovy
1 /**\r
2  *  Simple Sync Trigger\r
3  *\r
4  *  Copyright 2015 Roomie Remote, Inc.\r
5  *\r
6  *      Date: 2015-09-22\r
7  */\r
8 definition(\r
9     name: "Simple Sync Trigger",\r
10     namespace: "roomieremote-ratrigger",\r
11     author: "Roomie Remote, Inc.",\r
12     description: "Trigger Simple Control activities when certain actions take place in your home.",\r
13     category: "My Apps",\r
14     iconUrl: "https://s3.amazonaws.com/roomieuser/remotes/simplesync-60.png",\r
15     iconX2Url: "https://s3.amazonaws.com/roomieuser/remotes/simplesync-120.png",\r
16     iconX3Url: "https://s3.amazonaws.com/roomieuser/remotes/simplesync-120.png")\r
17 \r
18 \r
19 preferences {\r
20         page(name: "agentSelection", title: "Select your Simple Sync")\r
21         page(name: "refreshActivities", title: "Updating list of Simple Sync activities")\r
22     page(name: "control", title: "Run a Simple Control activity when something happens")\r
23         page(name: "timeIntervalInput", title: "Only during a certain time", install: true, uninstall: true) {\r
24                 section {\r
25                         input "starting", "time", title: "Starting", required: false\r
26                         input "ending", "time", title: "Ending", required: false\r
27                 }\r
28         }\r
29 }\r
30 \r
31 def agentSelection()\r
32 {\r
33         if (agent)\r
34     {\r
35                 state.refreshCount = 0\r
36     }\r
37     \r
38         dynamicPage(name: "agentSelection", title: "Select your Simple Sync", nextPage: "control", install: false, uninstall: true) {\r
39                 section {\r
40                         input "agent", "capability.mediaController", title: "Simple Sync", required: true, multiple: false\r
41                 }\r
42         }\r
43 }\r
44 \r
45 // input "motion", "capability.motionSensor", title: "Motion Detected", required: false, multiple: true\r
46 // input "motionInactive", "capability.motionSensor", title: "Motion Stops", required: false, multiple: true\r
47 // input "contact", "capability.contactSensor", title: "Contact Opens", required: false, multiple: true\r
48 // input "contactClosed", "capability.contactSensor", title: "Contact Closes", required: false, multiple: true\r
49 // input "acceleration", "capability.accelerationSensor", title: "Acceleration Detected", required: false, multiple: true\r
50 // input "mySwitch", "capability.switch", title: "Switch Turned On", required: false, multiple: true\r
51 // input "mySwitchOff", "capability.switch", title: "Switch Turned Off", required: false, multiple: true\r
52 // input "arrivalPresence", "capability.presenceSensor", title: "Arrival Of", required: false, multiple: true\r
53 // input "departurePresence", "capability.presenceSensor", title: "Departure Of", required: false, multiple: true\r
54 // input "button1", "capability.button", title: "Button Press", required:false, multiple:true //remove from production\r
55 // input "triggerModes", "mode", title: "System Changes Mode", required: false, multiple: true\r
56 // input "timeOfDay", "time", title: "At a Scheduled Time", required: false\r
57 \r
58 def control()\r
59 {\r
60         def activities = agent.latestValue('activities')\r
61     \r
62     if (!activities || !state.refreshCount)\r
63     {\r
64             int refreshCount = !state.refreshCount ? 0 : state.refreshCount as int\r
65             state.refreshCount = refreshCount + 1\r
66             def refreshInterval = refreshCount == 0 ? 2 : 4\r
67                 \r
68             // Request activities every 5th attempt\r
69         if((refreshCount % 5) == 0)\r
70         {\r
71             agent.getAllActivities()\r
72                 }\r
73         \r
74         dynamicPage(name: "control", title: "Updating list of Simple Control activities", nextPage: "", refreshInterval: refreshInterval, install: false, uninstall: true) {\r
75                 section("") {\r
76                 paragraph "Retrieving activities from Simple Sync"\r
77             }\r
78         }\r
79         }\r
80         else\r
81     {\r
82         dynamicPage(name: "control", title: "Run a Simple Control activity when something happens", nextPage: "timeIntervalInput", install: false, uninstall: true) {\r
83                 def anythingSet = anythingSet()\r
84                 if (anythingSet) {\r
85                         section("When..."){\r
86                                 ifSet "motion", "capability.motionSensor", title: "Motion Detected", required: false, multiple: true\r
87                                 ifSet "motionInactive", "capability.motionSensor", title: "Motion Stops", required: false, multiple: true\r
88                                 ifSet "contact", "capability.contactSensor", title: "Contact Opens", required: false, multiple: true\r
89                                 ifSet "contactClosed", "capability.contactSensor", title: "Contact Closes", required: false, multiple: true\r
90                                 ifSet "acceleration", "capability.accelerationSensor", title: "Acceleration Detected", required: false, multiple: true\r
91                                 ifSet "mySwitch", "capability.switch", title: "Switch Turned On", required: false, multiple: true\r
92                                 ifSet "mySwitchOff", "capability.switch", title: "Switch Turned Off", required: false, multiple: true\r
93                                 ifSet "arrivalPresence", "capability.presenceSensor", title: "Arrival Of", required: false, multiple: true\r
94                                 ifSet "departurePresence", "capability.presenceSensor", title: "Departure Of", required: false, multiple: true\r
95                                 ifSet "button1", "capability.button", title: "Button Press", required:false, multiple:true //remove from production\r
96                                 ifSet "triggerModes", "mode", title: "System Changes Mode", required: false, multiple: true\r
97                                 ifSet "timeOfDay", "time", title: "At a Scheduled Time", required: false\r
98                         }\r
99                 }\r
100                 section(anythingSet ? "Select additional triggers" : "When...", hideable: anythingSet, hidden: true){\r
101                         ifUnset "motion", "capability.motionSensor", title: "Motion Here", required: false, multiple: true\r
102                         ifUnset "motionInactive", "capability.motionSensor", title: "Motion Stops", required: false, multiple: true\r
103                         ifUnset "contact", "capability.contactSensor", title: "Contact Opens", required: false, multiple: true\r
104                         ifUnset "contactClosed", "capability.contactSensor", title: "Contact Closes", required: false, multiple: true\r
105                         ifUnset "acceleration", "capability.accelerationSensor", title: "Acceleration Detected", required: false, multiple: true\r
106                         ifUnset "mySwitch", "capability.switch", title: "Switch Turned On", required: false, multiple: true\r
107                         ifUnset "mySwitchOff", "capability.switch", title: "Switch Turned Off", required: false, multiple: true\r
108                         ifUnset "arrivalPresence", "capability.presenceSensor", title: "Arrival Of", required: false, multiple: true\r
109                         ifUnset "departurePresence", "capability.presenceSensor", title: "Departure Of", required: false, multiple: true\r
110                         ifUnset "button1", "capability.button", title: "Button Press", required:false, multiple:true //remove from production\r
111                         ifUnset "triggerModes", "mode", title: "System Changes Mode", required: false, multiple: true\r
112                         ifUnset "timeOfDay", "time", title: "At a Scheduled Time", required: false\r
113                 }\r
114                 section("Run this activity"){\r
115                 input "activity", "enum", title: "Activity?", required: true, options: new groovy.json.JsonSlurper().parseText(activities ?: "[]").activities?.collect { ["${it.uuid}": it.name] }\r
116                 }\r
117         \r
118                 section("More options", hideable: true, hidden: true) {\r
119                         input "frequency", "decimal", title: "Minimum time between actions (defaults to every event)", description: "Minutes", required: false\r
120                         href "timeIntervalInput", title: "Only during a certain time", description: timeLabel ?: "Tap to set", state: timeLabel ? "complete" : "incomplete"\r
121                         input "days", "enum", title: "Only on certain days of the week", multiple: true, required: false,\r
122                                 options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]\r
123                         input "modes", "mode", title: "Only when mode is", multiple: true, required: false\r
124                         input "oncePerDay", "bool", title: "Only once per day", required: false, defaultValue: false\r
125                 }\r
126                 section([mobileOnly:true]) {\r
127                         label title: "Assign a name", required: false\r
128                         mode title: "Set for specific mode(s)"\r
129                 }\r
130         }\r
131     }\r
132 }\r
133 \r
134 private anythingSet() {\r
135         for (name in ["motion","motionInactive","contact","contactClosed","acceleration","mySwitch","mySwitchOff","arrivalPresence","departurePresence","button1","triggerModes","timeOfDay"]) {\r
136                 if (settings[name]) {\r
137                         return true\r
138                 }\r
139         }\r
140         return false\r
141 }\r
142 \r
143 private ifUnset(Map options, String name, String capability) {\r
144         if (!settings[name]) {\r
145                 input(options, name, capability)\r
146         }\r
147 }\r
148 \r
149 private ifSet(Map options, String name, String capability) {\r
150         if (settings[name]) {\r
151                 input(options, name, capability)\r
152         }\r
153 }\r
154 \r
155 def installed() {\r
156         subscribeToEvents()\r
157 }\r
158 \r
159 def updated() {\r
160         unsubscribe()\r
161         unschedule()\r
162         subscribeToEvents()\r
163 }\r
164 \r
165 def subscribeToEvents() {\r
166         log.trace "subscribeToEvents()"\r
167         subscribe(app, appTouchHandler)\r
168         subscribe(contact, "contact.open", eventHandler)\r
169         subscribe(contactClosed, "contact.closed", eventHandler)\r
170         subscribe(acceleration, "acceleration.active", eventHandler)\r
171         subscribe(motion, "motion.active", eventHandler)\r
172         subscribe(motionInactive, "motion.inactive", eventHandler)\r
173         subscribe(mySwitch, "switch.on", eventHandler)\r
174         subscribe(mySwitchOff, "switch.off", eventHandler)\r
175         subscribe(arrivalPresence, "presence.present", eventHandler)\r
176         subscribe(departurePresence, "presence.not present", eventHandler)\r
177         subscribe(button1, "button.pushed", eventHandler)\r
178 \r
179         if (triggerModes) {\r
180                 subscribe(location, modeChangeHandler)\r
181         }\r
182 \r
183         if (timeOfDay) {\r
184                 schedule(timeOfDay, scheduledTimeHandler)\r
185         }\r
186 }\r
187 \r
188 def eventHandler(evt) {\r
189         if (allOk) {\r
190                 def lastTime = state[frequencyKey(evt)]\r
191                 if (oncePerDayOk(lastTime)) {\r
192                         if (frequency) {\r
193                                 if (lastTime == null || now() - lastTime >= frequency * 60000) {\r
194                                         startActivity(evt)\r
195                                 }\r
196                                 else {\r
197                                         log.debug "Not taking action because $frequency minutes have not elapsed since last action"\r
198                                 }\r
199                         }\r
200                         else {\r
201                                 startActivity(evt)\r
202                         }\r
203                 }\r
204                 else {\r
205                         log.debug "Not taking action because it was already taken today"\r
206                 }\r
207         }\r
208 }\r
209 \r
210 def modeChangeHandler(evt) {\r
211         log.trace "modeChangeHandler $evt.name: $evt.value ($triggerModes)"\r
212         if (evt.value in triggerModes) {\r
213                 eventHandler(evt)\r
214         }\r
215 }\r
216 \r
217 def scheduledTimeHandler() {\r
218         eventHandler(null)\r
219 }\r
220 \r
221 def appTouchHandler(evt) {\r
222         startActivity(evt)\r
223 }\r
224 \r
225 private startActivity(evt) {\r
226         agent.startActivity(activity)\r
227 \r
228         if (frequency) {\r
229                 state.lastActionTimeStamp = now()\r
230         }\r
231 }\r
232 \r
233 private frequencyKey(evt) {\r
234         //evt.deviceId ?: evt.value\r
235         "lastActionTimeStamp"\r
236 }\r
237 \r
238 private dayString(Date date) {\r
239         def df = new java.text.SimpleDateFormat("yyyy-MM-dd")\r
240         if (location.timeZone) {\r
241                 df.setTimeZone(location.timeZone)\r
242         }\r
243         else {\r
244                 df.setTimeZone(TimeZone.getTimeZone("America/New_York"))\r
245         }\r
246         df.format(date)\r
247 }\r
248 \r
249 private oncePerDayOk(Long lastTime) {\r
250         def result = true\r
251         if (oncePerDay) {\r
252                 result = lastTime ? dayString(new Date()) != dayString(new Date(lastTime)) : true\r
253                 log.trace "oncePerDayOk = $result"\r
254         }\r
255         result\r
256 }\r
257 \r
258 // TODO - centralize somehow\r
259 private getAllOk() {\r
260         modeOk && daysOk && timeOk\r
261 }\r
262 \r
263 private getModeOk() {\r
264         def result = !modes || modes.contains(location.mode)\r
265         log.trace "modeOk = $result"\r
266         result\r
267 }\r
268 \r
269 private getDaysOk() {\r
270         def result = true\r
271         if (days) {\r
272                 def df = new java.text.SimpleDateFormat("EEEE")\r
273                 if (location.timeZone) {\r
274                         df.setTimeZone(location.timeZone)\r
275                 }\r
276                 else {\r
277                         df.setTimeZone(TimeZone.getTimeZone("America/New_York"))\r
278                 }\r
279                 def day = df.format(new Date())\r
280                 result = days.contains(day)\r
281         }\r
282         log.trace "daysOk = $result"\r
283         result\r
284 }\r
285 \r
286 private getTimeOk() {\r
287         def result = true\r
288         if (starting && ending) {\r
289                 def currTime = now()\r
290                 def start = timeToday(starting).time\r
291                 def stop = timeToday(ending).time\r
292                 result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start\r
293         }\r
294         log.trace "timeOk = $result"\r
295         result\r
296 }\r
297 \r
298 private hhmm(time, fmt = "h:mm a")\r
299 {\r
300         def t = timeToday(time, location.timeZone)\r
301         def f = new java.text.SimpleDateFormat(fmt)\r
302         f.setTimeZone(location.timeZone ?: timeZone(time))\r
303         f.format(t)\r
304 }\r
305 \r
306 private timeIntervalLabel()\r
307 {\r
308         (starting && ending) ? hhmm(starting) + "-" + hhmm(ending, "h:mm a z") : ""\r
309 }\r