Missing one official app.
[smartapps.git] / official / step-notifier.groovy
1 /**
2  *  Step Notifier
3  *
4  *  Copyright 2014 Jeff's Account
5  *
6  *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
7  *  in compliance with the License. You may obtain a copy of the License at:
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
12  *  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
13  *  for the specific language governing permissions and limitations under the License.
14  *
15  */
16 definition(
17     name: "Step Notifier",
18     namespace: "smartthings",
19     author: "SmartThings",
20     description: "Use a step tracker device to track daily step goals and trigger various device actions when your goals are met!",
21     category: "SmartThings Labs",
22         iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/jawbone-up.png",
23         iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/jawbone-up@2x.png"
24 )
25
26 preferences {
27         page(name: "setupNotifications")
28     page(name: "chooseTrack", title: "Select a song or station")
29         page(name: "timeIntervalInput", title: "Only during a certain time") {
30                 section {
31                         input "starting", "time", title: "Starting", required: false
32                         input "ending", "time", title: "Ending", required: false
33                 }
34         }
35 }
36
37 def setupNotifications() {
38     
39     dynamicPage(name: "setupNotifications", title: "Configure Your Goal Notifications.", install: true, uninstall: true) {      
40     
41                 section("Select your Jawbone UP") {
42                         input "jawbone", "device.jawboneUser", title: "Jawbone UP", required: true, multiple: false
43                 }
44            
45         section("Notify Me When"){
46                         input "thresholdType", "enum", title: "Select When to Notify", required: false, defaultValue: "Goal Reached", options: [["Goal":"Goal Reached"],["Threshold":"Specific Number of Steps"]], submitOnChange:true
47             if (settings.thresholdType) {
48                 if (settings.thresholdType == "Threshold") {
49                         input "threshold", "number", title: "Enter Step Threshold", description: "Number", required: true
50                 }
51             }
52                 }
53         
54                 section("Via a push notification and/or an SMS message"){
55             input("recipients", "contact", title: "Send notifications to") {
56                 input "phone", "phone", title: "Phone Number (for SMS, optional)", required: false
57                 input "notificationType", "enum", title: "Select Notification", required: false, defaultValue: "None", options: ["None", "Push", "SMS", "Both"]
58             }
59                 }
60         
61         section("Flash the Lights") {
62                 input "lights", "capability.switch", title: "Which Lights?", required: false, multiple: true
63                 input "flashCount", "number", title: "How Many Times?", defaultValue: 5, required: false           
64         }
65         
66         section("Change the Color of the Lights") {
67                 input "hues", "capability.colorControl", title: "Which Hue Bulbs?", required:false, multiple:true
68             input "color", "enum", title: "Hue Color?", required: false, multiple:false, options: ["Red","Green","Blue","Yellow","Orange","Purple","Pink"]
69                         input "lightLevel", "enum", title: "Light Level?", required: false, options: [[10:"10%"],[20:"20%"],[30:"30%"],[40:"40%"],[50:"50%"],[60:"60%"],[70:"70%"],[80:"80%"],[90:"90%"],[100:"100%"]]
70                         input "duration", "number", title: "Duration in Seconds?", defaultValue: 30, required: false
71         }
72         
73         section("Play a song on the Sonos") {
74                         input "sonos", "capability.musicPlayer", title: "On this Sonos player", required: false, submitOnChange:true
75             if (settings.sonos) {
76                                 input "song","enum",title:"Play this track or radio station", required:true, multiple: false, options: songOptions()  
77                                 input "resumePlaying", "bool", title: "Resume currently playing music after notification", required: false, defaultValue: true                
78                 input "volume", "number", title: "Temporarily change volume", description: "0-100%", required: false
79                 input "songDuration", "number", title: "Play for this many seconds", defaultValue: 60, description: "0-100%", required: true
80             }
81
82                 }
83     }
84 }
85
86 def chooseTrack() {
87         dynamicPage(name: "chooseTrack") {
88                 section{
89                         input "song","enum",title:"Play this track", required:true, multiple: false, options: songOptions()
90                 }
91         }
92 }
93
94 private songOptions() {
95
96         // Make sure current selection is in the set
97
98         def options = new LinkedHashSet()
99         if (state.selectedSong?.station) {
100                 options << state.selectedSong.station
101         }
102         else if (state.selectedSong?.description) {
103                 // TODO - Remove eventually? 'description' for backward compatibility
104                 options << state.selectedSong.description
105         }
106
107         // Query for recent tracks
108         def states = sonos.statesSince("trackData", new Date(0), [max:30])
109         def dataMaps = states.collect{it.jsonValue}
110         options.addAll(dataMaps.collect{it.station})
111
112         log.trace "${options.size()} songs in list"
113         options.take(20) as List
114 }
115
116 private saveSelectedSong() {
117         try {
118                 def thisSong = song
119                 log.info "Looking for $thisSong"
120                 def songs = sonos.statesSince("trackData", new Date(0), [max:30]).collect{it.jsonValue}
121                 log.info "Searching ${songs.size()} records"
122
123                 def data = songs.find {s -> s.station == thisSong}
124                 log.info "Found ${data?.station}"
125                 if (data) {
126                         state.selectedSong = data
127                         log.debug "Selected song = $state.selectedSong"
128                 }
129                 else if (song == state.selectedSong?.station) {
130                         log.debug "Selected existing entry '$song', which is no longer in the last 20 list"
131                 }
132                 else {
133                         log.warn "Selected song '$song' not found"
134                 }
135         }
136         catch (Throwable t) {
137                 log.error t
138         }
139 }
140
141 def installed() {
142         log.debug "Installed with settings: ${settings}"
143
144         initialize()
145 }
146
147 def updated() {
148         log.debug "Updated with settings: ${settings}"
149
150         unsubscribe()
151         initialize()
152 }
153
154 def initialize() {
155
156         log.trace "Entering initialize()"
157     
158         state.lastSteps = 0
159     state.steps = jawbone.currentValue("steps").toInteger()
160     state.goal = jawbone.currentValue("goal").toInteger()
161     
162         subscribe (jawbone,"goal",goalHandler)
163     subscribe (jawbone,"steps",stepHandler)
164     
165     if (song) {
166                 saveSelectedSong()
167         }
168     
169         log.trace "Exiting initialize()"    
170 }
171
172 def goalHandler(evt) {
173
174         log.trace "Entering goalHandler()"
175
176         def goal = evt.value.toInteger()
177     
178     state.goal = goal
179     
180     log.trace "Exiting goalHandler()"
181 }
182
183 def stepHandler(evt) {
184
185         log.trace "Entering stepHandler()"
186     
187     log.debug "Event Value ${evt.value}"
188     log.debug "state.steps = ${state.steps}"
189     log.debug "state.goal = ${state.goal}"
190
191         def steps = evt.value.toInteger()
192     
193     state.lastSteps = state.steps
194     state.steps = steps
195     
196     def stepGoal
197     if (settings.thresholdType == "Goal")
198         stepGoal = state.goal
199     else
200         stepGoal = settings.threshold
201     
202     if ((state.lastSteps < stepGoal) && (state.steps >= stepGoal)) { // only trigger when crossing through the goal threshold
203     
204     // goal achieved for the day! Yay! Lets tell someone!
205     
206         if (settings.notificationType != "None") { // Push or SMS Notification requested
207
208             if (location.contactBookEnabled) {
209                 sendNotificationToContacts(stepMessage, recipients)
210             }
211             else {
212
213                 def options = [
214                     method: settings.notificationType.toLowerCase(),
215                     phone: settings.phone
216                 ]
217
218                 sendNotification(stepMessage, options)
219             }
220         }
221         
222         if (settings.sonos) { // play a song on the Sonos as requested
223         
224                 // runIn(1, sonosNotification, [overwrite: false])
225             sonosNotification()
226             
227         }  
228         
229         if (settings.hues) { // change the color of hue bulbs ras equested
230         
231                 // runIn(1, hueNotification, [overwrite: false])
232             hueNotification()
233         
234         }        
235         
236         if (settings.lights) { // flash the lights as requested
237         
238                         // runIn(1, lightsNotification, [overwrite: false])
239                 lightsNotification()
240         
241         }
242     
243     }
244     
245         log.trace "Exiting stepHandler()"    
246
247 }
248
249
250 def lightsNotification() {
251
252         // save the current state of the lights 
253     
254     log.trace "Save current state of lights"
255     
256         state.previousLights = [:]
257
258         lights.each {
259                 state.previousLights[it.id] = it.currentValue("switch")
260         }
261     
262     // Flash the light on and off 5 times for now - this could be configurable 
263             
264     log.trace "Now flash the lights"
265     
266     for (i in 1..flashCount) {
267            
268         lights.on()
269         pause(500)
270         lights.off()
271                
272     }
273     
274     // restore the original state
275     
276     log.trace "Now restore the original state of lights"    
277     
278         lights.each {
279                 it."${state.previousLights[it.id]}"()
280         }   
281
282
283 }
284
285 def hueNotification() {
286
287         log.trace "Entering hueNotification()"
288
289         def hueColor = 0
290         if(color == "Blue")
291                 hueColor = 70//60
292         else if(color == "Green")
293                 hueColor = 39//30
294         else if(color == "Yellow")
295                 hueColor = 25//16
296         else if(color == "Orange")
297                 hueColor = 10
298         else if(color == "Purple")
299                 hueColor = 75
300         else if(color == "Pink")
301                 hueColor = 83
302
303
304         state.previousHue = [:]
305
306         hues.each {
307                 state.previousHue[it.id] = [
308                         "switch": it.currentValue("switch"),
309                         "level" : it.currentValue("level"),
310                         "hue": it.currentValue("hue"),
311                         "saturation": it.currentValue("saturation")
312                 ]
313         }
314
315         log.debug "current values = ${state.previousHue}"
316
317         def newValue = [hue: hueColor, saturation: 100, level: (lightLevel as Integer) ?: 100]
318         log.debug "new value = $newValue"
319
320         hues*.setColor(newValue)
321         setTimer()
322     
323         log.trace "Exiting hueNotification()"
324     
325 }
326
327 def setTimer()
328 {
329         log.debug "runIn ${duration}, resetHue"
330         runIn(duration, resetHue, [overwrite: false])
331 }
332
333
334 def resetHue()
335 {
336     log.trace "Entering resetHue()"
337         settings.hues.each {
338                 it.setColor(state.previousHue[it.id])
339         }
340     log.trace "Exiting resetHue()"    
341 }
342
343 def sonosNotification() {
344
345         log.trace "sonosNotification()"
346     
347     if (settings.song) {
348     
349                 if (settings.resumePlaying) {
350                 if (settings.volume)
351                                 sonos.playTrackAndResume(state.selectedSong, settings.songDuration, settings.volume)
352             else
353                 sonos.playTrackAndResume(state.selectedSong, settings.songDuration)
354         } else {
355                 if (settings.volume)
356                                 sonos.playTrackAtVolume(state.selectedSong, settings.volume)
357             else
358                 sonos.playTrack(state.selectedSong)
359         }
360         
361                 sonos.on() // make sure it is playing
362         
363         }
364
365         log.trace "Exiting sonosNotification()"
366 }