Update step-notifier.groovy
[smartapps.git] / third-party / loft.groovy
1 /**
2  *  Copyright 2015 Jesse Newland
3  *
4  *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5  *  in compliance with the License. You may obtain a copy of the License at:
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
9  *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
10  *  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
11  *  for the specific language governing permissions and limitations under the License.
12  *
13  */
14 definition(
15     name: "Loft",
16     namespace: "jnewland",
17     author: "Jesse Newland",
18     description: "All the business logic for my crappy loft lives here",
19     category: "SmartThings Labs",
20     iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
21     iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
22     iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png")
23
24 preferences {
25     page(name: "selectThings")
26 }
27
28 def selectThings() {
29     dynamicPage(name: "selectThings", title: "Select Things", install: true) {
30         section("Webhook URL"){
31             input "url", "text", title: "Webhook URL", description: "Your webhook URL", required: true
32         }
33         section("Things to monitor for events") {
34             input "monitor_switches", "capability.switch", title: "Switches", multiple: true, required: false
35             input "monitor_motion", "capability.motionSensor", title: "Motion Sensors", multiple: true, required: false
36             input "monitor_presence", "capability.presenceSensor", title: "Presence Sensors", multiple: true, required: false
37         }
38         section("Things to control") {
39             input "hues", "capability.colorControl", title: "Hue Bulbs", multiple: true, required: false
40             input "switches", "capability.switch", title: "Switches", multiple: true, required: false
41             input "dimmers", "capability.switchLevel", title: "Dimmers", multiple: true, required: false
42         }
43         section("Voice") {
44             input "voice", "capability.speechSynthesis", title: "Voice", required: false
45         }
46     }
47 }
48
49
50 def installed() {
51     initialize()
52 }
53
54 def updated() {
55     unsubscribe()
56     unschedule()
57     initialize()
58 }
59
60 def initialize() {
61     subscribe(monitor_switches, "switch", eventHandler1)
62     subscribe(monitor_motion, "motion", eventHandler1)
63     subscribe(monitor_presence, "presence", eventHandler1)
64     subscribe(location, "mode", eventHandler1)
65     subscribe(location, "sunset", eventHandler1)
66     subscribe(location, "sunrise", eventHandler1)
67     tick()
68 }
69
70 def eventHandler1(evt) {
71     def everyone_here = presense_is_after(monitor_presence, "present", 10)
72     def everyone_gone = presense_is_after(monitor_presence, "not present", 10)
73     def current_count = monitor_presence.findAll { it.currentPresence == "present" }.size()
74     webhook([
75       displayName:   evt.displayName,
76       value:         evt.value,
77       daytime:       is_daytime(),
78       mode:          location.mode,
79       current_count: current_count,
80       everyone_here: everyone_here,
81       everyone_gone: everyone_gone
82     ])
83
84     if (mode == "Pause") {
85         webhook([ at: 'paused' ])
86         log.info("No actions taken in Pause mode")
87     } else {
88         def lights = [switches, hues].flatten()
89         // turn on lights near stairs when motion is detected
90         if (evt.displayName == "motion@stairs" && evt.value == "active") {
91             webhook([ at: 'stair_motion_lights' ])
92             lights.findAll { s ->
93                 s.displayName == "stairs" ||
94                     s.displayName == "loft" ||
95                     s.displayName == "entry"
96             }.findAll { s ->
97                 s.currentSwitch == "off"
98             }.each { s ->
99                 if ("setLevel" in s.supportedCommands.collect { it.name }) {
100                     if (location.mode == "Sleep") {
101                         s.setLevel(50)
102                     } else if (location.mode == "Home / Night") {
103                         s.setLevel(75)
104                     } else {
105                         s.setLevel(100)
106                     }
107                 }
108                 s.on()
109             }
110         }
111
112         // Turn on some lights if one of us is up
113         if (evt.value == "Yawn") {
114             webhook([ at: 'yawn' ])
115             lights.findAll { s ->
116                 s.displayName == "loft" ||
117                     s.displayName == "entry" ||
118                     s.displayName == "chandelier"
119             }.findAll { s ->
120                 s.currentSwitch == "off"
121             }.each { s ->
122                 if ("setLevel" in s.supportedCommands.collect { it.name }) {
123                     s.setLevel(75)
124                 }
125                 s.on()
126             }
127         }
128
129         // turn on all lights when entering day mode
130         if (evt.value == "Home / Day") {
131             webhook([ at: 'home_day' ])
132             lights.each { s ->
133                 if (s.currentSwitch == "off") {
134                     s.on()
135                 }
136                  if ("setLevel" in s.supportedCommands.collect { it.name }) {
137                     s.setLevel(100)
138                 }
139             }
140         }
141
142         // turn on night mode at sunset
143         if (evt.displayName == "sunset" && current_count > 0 && location.mode == "Home / Day") {
144             webhook([ at: 'sunset' ])
145             changeMode("Home / Night")
146         }
147
148         // dim lights at night
149         if (evt.value == "Home / Night") {
150             webhook([ at: 'home_night' ])
151             lights.findAll { s ->
152                 "setLevel" in s.supportedCommands.collect { it.name }
153             }.each { s ->
154                 log.info("Night mode enabled, dimming ${s.displayName}")
155                 s.setLevel(75)
156             }
157
158             // turn off that light by the door downstairs entirely
159             lights.findAll { s ->
160                 s.displayName == "downstairs door"
161             }.each { s ->
162                 log.info("Night mode enabled, turning off ${s.displayName}")
163                 s.off()
164             }
165         }
166
167         // turn off all lights when entering sleep mode
168         if (evt.value == "Sleep") {
169             webhook([ at: 'sleep' ])
170             lights.findAll { s ->
171                 s.currentSwitch == "on"
172             }.each { s ->
173                 log.info("Sleep mode enabled, turning off ${s.displayName}")
174                 s.off()
175             }
176         }
177
178         // turn off all lights when everyone goes away
179         if (everyone_gone && location.mode != "Away") {
180             webhook([ at: 'away' ])
181             changeMode("Away")
182             lights.findAll { s ->
183                 s.currentSwitch == "on"
184             }.each { s ->
185                 log.info("Away mode enabled, turning off ${s.displayName}")
186                 s.off()
187             }
188         }
189
190         // switch mode to Home when we return
191         if (current_count > 0 && location.mode == "Away") {
192             webhook([ at: 'home' ])
193             changeMode("Home")
194         }
195
196         // Make home mode specific based on day / night
197         if (evt.value == "Home") {
198             webhook([ at: 'home_day_night' ])
199             if (is_daytime()) {
200                 changeMode("Home / Day")
201             } else {
202                 changeMode("Home / Night")
203             }
204         }
205
206         if (canSchedule()) {
207             runIn(61, tick)
208         } else {
209             webhook([ can_schedule: 'false' ])
210             log.error("can_schedule=false")
211         }
212     }
213 }
214
215 def changeMode(mode) {
216     //voice?.speak("changing mode to ${mode}")
217     setLocationMode(mode)
218     eventHandler1([
219         displayName: "changeMode",
220         value:       mode
221     ])
222 }
223
224 def tick() {
225     eventHandler1([
226       displayName: "tick",
227       value: "tock"
228     ])
229 }
230
231 def webhook(map) {
232     def successClosure = { response ->
233       log.debug "Request was successful, $response"
234     }
235
236     def json_params = [
237         uri: settings.url,
238         success: successClosure,
239         body: map
240     ]
241     httpPostJson(json_params)
242 }
243
244
245 private is_daytime() {
246     return true
247     /*def data = getWeatherFeature("astronomy")
248     def sunset = "${data.moon_phase.sunset.hour}${data.moon_phase.sunset.minute}"
249     def sunrise = "${data.moon_phase.sunrise.hour}${data.moon_phase.sunrise.minute}"
250     def current = "${data.moon_phase.current_time.hour}${data.moon_phase.current_time.minute}"
251     if (current.toInteger() > sunrise.toInteger() && current.toInteger() < sunset.toInteger()) {
252         return true
253     }
254     else {
255         return false
256     }*/
257 }
258
259 private presense_is_after(people, presence, minutes) {
260     def result = true
261     for (person in people) {
262         if (person.currentPresence != presence) {
263             result = false
264             break
265         } else {
266             def threshold = 1000 * 60 * minutes
267             def elapsed = now() - person.currentState("presence").rawDateCreated.time
268             if (elapsed < threshold) {
269               result = false
270               break
271             }
272         }
273     }
274     return result
275 }