4 * Copyright 2014 Jeff's Account
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:
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
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"
27 page(name: "setupNotifications")
28 page(name: "timeIntervalInput", title: "Only during a certain time") {
30 input "starting", "time", title: "Starting", required: false
31 input "ending", "time", title: "Ending", required: false
36 def setupNotifications() {
38 dynamicPage(name: "setupNotifications", title: "Configure Your Goal Notifications.", install: true, uninstall: true) {
40 section("Select your Jawbone UP") {
41 input "jawbone", "capability.stepSensor", title: "Jawbone UP", required: true, multiple: false
44 section("Notify Me When"){
45 input "thresholdType", "enum", title: "Select When to Notify", required: false, defaultValue: "Goal Reached", options: ["Goal","Threshold"], submitOnChange:true
46 if (settings.thresholdType) {
47 if (settings.thresholdType == "Threshold") {
48 input "threshold", "number", title: "Enter Step Threshold", description: "Number", required: true
53 section("Via a push notification and/or an SMS message"){
54 input("recipients", "contact", title: "Send notifications to") {
55 input "phone", "phone", title: "Phone Number (for SMS, optional)", required: false
56 input "notificationType", "enum", title: "Select Notification", required: false, defaultValue: "None", options: ["None", "Push", "SMS", "Both"]
60 section("Flash the Lights") {
61 input "lights", "capability.switch", title: "Which Lights?", required: false, multiple: true
62 input "flashCount", "number", title: "How Many Times?", defaultValue: 5, required: false
65 section("Change the Color of the Lights") {
66 input "hues", "capability.colorControl", title: "Which Hue Bulbs?", required:false, multiple:true
67 input "color", "enum", title: "Hue Color?", required: false, multiple:false, options: ["Red","Green","Blue","Yellow","Orange","Purple","Pink"]
68 input "lightLevel", "enum", title: "Light Level?", required: false, options: [10,20,30,40,50,60,70,80,90,100]
69 input "duration", "number", title: "Duration in Seconds?", defaultValue: 30, required: false
72 section("Play a song on the Sonos") {
73 input "sonos", "capability.musicPlayer", title: "On this Sonos player", required: false, submitOnChange:true
75 input "song","enum",title:"Play this track or radio station", required:true, multiple: false, options: songOptions()
76 input "resumePlaying", "bool", title: "Resume currently playing music after notification", required: false, defaultValue: true
77 input "volume", "number", title: "Temporarily change volume", description: "0-100%", required: false
78 input "songDuration", "number", title: "Play for this many seconds", defaultValue: 60, description: "0-100%", required: true
85 private songOptions() {
87 // Make sure current selection is in the set
89 def options = new LinkedHashSet()
90 if (state.selectedSong?.station) {
91 options << state.selectedSong.station
93 else if (state.selectedSong?.description) {
94 // TODO - Remove eventually? 'description' for backward compatibility
95 options << state.selectedSong.description
98 // Query for recent tracks
99 def states = sonos.statesSince("trackData", new Date(0), [max:30])
100 def dataMaps = states.collect{it.jsonValue}
101 options.addAll(dataMaps.collect{it.station})
103 log.trace "${options.size()} songs in list"
104 options.take(20) as List*/
105 state.selectedSong = "SomeTrack"
108 private saveSelectedSong() {
112 log.info "Looking for $thisSong"
113 def songs = sonos.statesSince("trackData", new Date(0), [max:30]).collect{it.jsonValue}
114 log.info "Searching ${songs.size()} records"
116 def data = songs.find {s -> s.station == thisSong}
117 log.info "Found ${data?.station}"
119 state.selectedSong = data
120 log.debug "Selected song = $state.selectedSong"
122 else if (song == state.selectedSong?.station) {
123 log.debug "Selected existing entry '$song', which is no longer in the last 20 list"
126 log.warn "Selected song '$song' not found"
129 catch (Throwable t) {
132 state.selectedSong = "SomeTrack"
136 log.debug "Installed with settings: ${settings}"
137 // Initialize input value
143 log.debug "Updated with settings: ${settings}"
151 log.trace "Entering initialize()"
154 state.steps = jawbone.currentValue("steps").toInteger()
155 state.goal = jawbone.currentValue("goal").toInteger()
157 subscribe (jawbone,"goal",goalHandler)
158 subscribe (jawbone,"steps",stepHandler)
164 log.trace "Exiting initialize()"
167 def goalHandler(evt) {
169 log.trace "Entering goalHandler()"
171 def goal = evt.value.toInteger()
175 log.trace "Exiting goalHandler()"
178 def stepHandler(evt) {
180 log.trace "Entering stepHandler()"
182 log.debug "Event Value ${evt.value}"
183 log.debug "state.steps = ${state.steps}"
184 log.debug "state.goal = ${state.goal}"
186 def steps = evt.value.toInteger()
188 state.lastSteps = state.steps
192 if (settings.thresholdType == "Goal")
193 stepGoal = state.goal
195 stepGoal = settings.threshold
197 if ((state.lastSteps < stepGoal) && (state.steps >= stepGoal)) { // only trigger when crossing through the goal threshold
199 // goal achieved for the day! Yay! Lets tell someone!
201 if (settings.notificationType != "None") { // Push or SMS Notification requested
203 if (location.contactBookEnabled) {
204 sendNotificationToContacts(stepMessage, recipients)
209 method: settings.notificationType.toLowerCase(),
210 phone: settings.phone
213 sendNotification(stepMessage, options)
217 if (settings.sonos) { // play a song on the Sonos as requested
219 // runIn(1, sonosNotification, [overwrite: false])
224 if (settings.hues) { // change the color of hue bulbs ras equested
226 // runIn(1, hueNotification, [overwrite: false])
231 if (settings.lights) { // flash the lights as requested
233 // runIn(1, lightsNotification, [overwrite: false])
240 log.trace "Exiting stepHandler()"
245 def lightsNotification() {
247 // save the current state of the lights
249 log.trace "Save current state of lights"
251 state.previousLights = [:]
254 state.previousLights[it.id] = it.currentValue("switch")
257 // Flash the light on and off 5 times for now - this could be configurable
259 log.trace "Now flash the lights"
261 for (i in 1..flashCount) {
269 // restore the original state
271 log.trace "Now restore the original state of lights"
274 it."${state.previousLights[it.id]}"()
280 def hueNotification() {
282 log.trace "Entering hueNotification()"
287 else if(color == "Green")
289 else if(color == "Yellow")
291 else if(color == "Orange")
293 else if(color == "Purple")
295 else if(color == "Pink")
299 state.previousHue = [:]
302 state.previousHue[it.id] = [
303 "switch": it.currentValue("switch"),
304 "level" : it.currentValue("level"),
305 "hue": it.currentValue("hue"),
306 "saturation": it.currentValue("saturation")
310 log.debug "current values = ${state.previousHue}"
312 def newValue = [hue: hueColor, saturation: 100, level: (lightLevel as Integer) ?: 100]
313 log.debug "new value = $newValue"
315 hues*.setColor(newValue)
318 log.trace "Exiting hueNotification()"
324 log.debug "runIn ${duration}, resetHue"
325 runIn(duration, resetHue, [overwrite: false])
331 log.trace "Entering resetHue()"
333 it.setColor(state.previousHue[it.id])
335 log.trace "Exiting resetHue()"
338 def sonosNotification() {
340 log.trace "sonosNotification()"
344 if (settings.resumePlaying) {
346 sonos.playTrackAndResume(state.selectedSong, settings.songDuration, settings.volume)
348 sonos.playTrackAndResume(state.selectedSong, settings.songDuration)
351 sonos.playTrackAtVolume(state.selectedSong, settings.volume)
353 sonos.playTrack(state.selectedSong)
356 sonos.on() // make sure it is playing
360 log.trace "Exiting sonosNotification()"