Update nfc-tag-toggle.groovy
[smartapps.git] / official / thermostat-mode-director.groovy
1 /**
2  *  Thermostat Mode Director
3  *  Source: https://github.com/tslagle13/SmartThings/blob/master/Director-Series-Apps/Thermostat-Mode-Director/Thermostat%20Mode%20Director.groovy
4  *
5  *  Version 3.0
6  *
7  *  Changelog:
8  *      2015-05-25
9  *              --Updated UI to make it look pretty.
10  *      2015-06-01
11  *      --Added option for modes to trigger thermostat boost.
12  *
13  *  Source code can be found here: https://github.com/tslagle13/SmartThings/blob/master/smartapps/tslagle13/vacation-lighting-director.groovy
14  *
15  *  Copyright 2015 Tim Slagle
16  *
17  *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
18  *  in compliance with the License. You may obtain a copy of the License at:
19  *
20  *      http://www.apache.org/licenses/LICENSE-2.0
21  *
22  *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
23  *  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
24  *  for the specific language governing permissions and limitations under the License.
25  *
26  */
27
28 // Automatically generated. Make future change here.
29 definition(
30         name: "Thermostat Mode Director",
31         namespace: "tslagle13",
32         author: "Tim Slagle",
33         description: "Changes mode of your thermostat based on the temperature range of a specified temperature sensor and shuts off the thermostat if any windows/doors are open.",
34         category: "Green Living",
35         iconUrl: "http://icons.iconarchive.com/icons/icons8/windows-8/512/Science-Temperature-icon.png",
36         iconX2Url: "http://icons.iconarchive.com/icons/icons8/windows-8/512/Science-Temperature-icon.png"
37 )
38
39 preferences {
40     page name:"pageSetup"
41     page name:"directorSettings"
42     page name:"ThermostatandDoors"
43     page name:"ThermostatBoost"
44     page name:"Settings"
45
46 }
47
48 // Show setup page
49 def pageSetup() {
50
51     def pageProperties = [
52         name:       "pageSetup",
53         title:      "Status",
54         nextPage:   null,
55         install:    true,
56         uninstall:  true
57     ]
58
59         return dynamicPage(pageProperties) {
60         section("About 'Thermostat Mode Director'"){
61                 paragraph "Changes mode of your thermostat based on the temperature range of a specified temperature sensor and shuts off the thermostat if any windows/doors are open."
62         }
63         section("Setup Menu") {
64             href "directorSettings", title: "Director Settings", description: "", state:greyedOut()
65             href "ThermostatandDoors", title: "Thermostat and Doors", description: "", state: greyedOutTherm()
66             href "ThermostatBoost", title: "Thermostat Boost", description: "", state: greyedOutTherm1()
67             href "Settings", title: "Settings", description: "", state: greyedOutSettings()
68             }
69         section([title:"Options", mobileOnly:true]) {
70             label title:"Assign a name", required:false
71         }
72     }
73 }
74
75 // Show "Setup" page
76 def directorSettings() {
77
78     def sensor = [
79         name:       "sensor",
80         type:       "capability.temperatureMeasurement",
81         title:      "Which?",
82         multiple:   false,
83         required:   true
84     ]
85     def setLow = [
86         name:       "setLow",
87         type:       "decimal",
88         title:      "Low temp?",
89         required:   true
90     ]
91     
92     def cold = [
93         name:       "cold",
94         type:       "enum",
95         title:          "Mode?",
96         metadata:   [values:["auto", "heat", "cool", "off"]]
97     ]
98     
99     def setHigh = [
100         name:       "setHigh",
101         type:       "decimal",
102         title:      "High temp?",
103         required:   true
104     ]
105     
106     def hot = [
107         name:       "hot",
108         type:       "enum",
109         title:          "Mode?",
110         metadata:   [values:["auto", "heat", "cool", "off"]]
111     ]
112     
113     def neutral = [
114         name:       "neutral",
115         type:       "enum",
116         title:          "Mode?",
117         metadata:   [values:["auto", "heat", "cool", "off"]]
118     ]
119     
120     def pageName = "Setup"
121     
122     def pageProperties = [
123         name:       "directorSettings",
124         title:      "Setup",
125         nextPage:   "pageSetup"
126     ]
127
128     return dynamicPage(pageProperties) {
129
130                 section("Which temperature sensor will control your thermostat?"){
131                         input sensor
132                 }
133         section(""){
134                 paragraph "Here you will setup the upper and lower thresholds for the temperature sensor that will send commands to your thermostat."
135         }
136                 section("When the temperature falls below this tempurature set mode to..."){
137                         input setLow
138                         input cold
139                 }
140         section("When the temperature goes above this tempurature set mode to..."){
141                         input setHigh
142                         input hot
143                 }
144         section("When temperature is between the previous temperatures, change mode to..."){
145                         input neutral
146                 }
147     }
148     
149 }
150
151 def ThermostatandDoors() {
152
153     def thermostat = [
154         name:       "thermostat",
155         type:       "capability.thermostat",
156         title:      "Which?",
157         multiple:   true,
158         required:   true
159     ]
160     def doors = [
161         name:       "doors",
162         type:       "capability.contactSensor",
163         title:      "Low temp?",
164         multiple:       true,
165         required:   true
166     ]
167     
168     def turnOffDelay = [
169         name:       "turnOffDelay",
170         type:       "decimal",
171         title:          "Number of minutes",
172         required:       false
173     ]
174     
175     def pageName = "Thermostat and Doors"
176     
177     def pageProperties = [
178         name:       "ThermostatandDoors",
179         title:      "Thermostat and Doors",
180         nextPage:   "pageSetup"
181     ]
182
183     return dynamicPage(pageProperties) {
184
185                 section(""){
186                 paragraph "If any of the doors selected here are open the thermostat will automatically be turned off and this app will be 'disabled' until all the doors are closed. (This is optional)"
187         }
188         section("Choose thermostat...") {
189                         input thermostat
190                 }
191         section("If these doors/windows are open turn off thermostat regardless of outdoor temperature") {
192                         input doors
193                 }
194                 section("Wait this long before turning the thermostat off (defaults to 1 minute)") {
195                         input turnOffDelay
196                 }
197     }
198     
199 }
200
201 def ThermostatBoost() {
202
203     def thermostat1 = [
204         name:       "thermostat1",
205         type:       "capability.thermostat",
206         title:      "Which?",
207         multiple:   true,
208         required:   true
209     ]
210     def turnOnTherm = [
211         name:           "turnOnTherm", 
212         type:           "enum", 
213         metadata:       [values: ["cool", "heat"]], 
214         required:       false
215     ]
216     
217     def modes1 = [
218         name:           "modes1", 
219         type:           "mode", 
220         title:          "Put thermostat into boost mode when mode is...", 
221         multiple:       true, 
222         required:       false
223     ]
224     
225     def coolingTemp = [
226         name:       "coolingTemp",
227         type:       "decimal",
228         title:          "Cooling Temp?",
229         required:       false
230     ]
231     
232     def heatingTemp = [
233         name:       "heatingTemp",
234         type:       "decimal",
235         title:          "Heating Temp?",
236         required:       false
237     ]
238     
239     def turnOffDelay2 = [
240         name:       "turnOffDelay2",
241         type:       "decimal",
242         title:          "Number of minutes",
243         required:       false,
244         defaultValue:30
245     ]
246     
247     def pageName = "Thermostat Boost"
248     
249     def pageProperties = [
250         name:       "ThermostatBoost",
251         title:      "Thermostat Boost",
252         nextPage:   "pageSetup"
253     ]
254
255     return dynamicPage(pageProperties) {
256
257                 section(""){
258                 paragraph "Here you can setup the ability to 'boost' your thermostat.  In the event that your thermostat is 'off'" +
259             " and you need to heat or cool your your home for a little bit you can 'touch' the app in the 'My Apps' section to boost your thermostat."
260         }
261                 section("Choose a thermostats to boost") {
262                         input thermostat1
263         }
264         section("If thermostat is off switch to which mode?") {
265                 input turnOnTherm
266                 }
267         section("Set the thermostat to the following temps") {
268                 input coolingTemp
269                 input heatingTemp
270                 }
271                 section("For how long?") {
272                 input turnOffDelay2
273                 }
274         section("In addtion to 'app touch' the following modes will also boost the thermostat") {
275                         input modes1
276         }
277     }
278     
279 }
280
281 // Show "Setup" page
282 def Settings() {
283
284     def sendPushMessage = [
285         name:           "sendPushMessage",
286         type:           "enum", 
287         title:          "Send a push notification?", 
288         metadata:       [values:["Yes","No"]], 
289         required:       true, 
290         defaultValue: "Yes"
291     ]
292     
293     def phoneNumber = [
294         name:           "phoneNumber", 
295         type:           "phone", 
296         title:          "Send SMS notifications to?", 
297         required:       false
298     ]
299     
300     def days = [
301         name:       "days",
302         type:       "enum",
303         title:      "Only on certain days of the week",
304         multiple:   true,
305         required:   false,
306         options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
307     ]
308     
309     def modes = [
310         name:           "modes", 
311         type:           "mode", 
312         title:          "Only when mode is", 
313         multiple:       true, 
314         required:       false
315     ]
316     
317     def pageName = "Settings"
318     
319     def pageProperties = [
320         name:       "Settings",
321         title:      "Settings",
322         nextPage:   "pageSetup"
323     ]
324
325     return dynamicPage(pageProperties) {
326
327
328                 section( "Notifications" ) {
329                         input sendPushMessage
330                         input phoneNumber
331                 }
332                 section(title: "More options", hideable: true) {
333                         href "timeIntervalInput", title: "Only during a certain time", description: getTimeLabel(starting, ending), state: greyedOutTime(starting, ending), refreshAfterSelection:true
334                         input days
335                         input modes
336                 }    
337     }
338     
339 }
340
341 def installed(){
342         init()
343 }
344
345 def updated(){
346         unsubscribe()
347         init()
348 }
349
350 def init(){
351         state.lastStatus = null
352         subscribe(app, appTouch)
353     runIn(60, "temperatureHandler")
354     subscribe(sensor, "temperature", temperatureHandler)
355     if(modes1){
356         subscribe(location, modeBoostChange)
357     }
358         if(doors){
359                 subscribe(doors, "contact.open", temperatureHandler)
360         subscribe(doors, "contact.closed", doorCheck)
361         }
362 }
363
364 def temperatureHandler(evt) {
365         if(modeOk && daysOk && timeOk) {
366                 if(setLow > setHigh){
367                         def temp = setLow
368                         setLow = setHigh
369                         setHigh = temp
370                 }
371                 if (doorsOk) {
372                         def currentTemp = sensor.latestValue("temperature")
373                         if (currentTemp < setLow) {
374                 if (state.lastStatus == "two" || state.lastStatus == "three" || state.lastStatus == null){
375                                         //log.info "Setting thermostat mode to ${cold}"
376                                         def msg = "I changed your thermostat mode to ${cold} because temperature is below ${setLow}"
377                                         thermostat?."${cold}"()
378                     sendMessage(msg)
379                     }
380                                 state.lastStatus = "one"
381                         }
382                         if (currentTemp > setHigh) {
383                 if (state.lastStatus == "one" || state.lastStatus == "three" || state.lastStatus == null){
384                                         //log.info "Setting thermostat mode to ${hot}"
385                                         def msg = "I changed your thermostat mode to ${hot} because temperature is above ${setHigh}"
386                                         thermostat?."${hot}"()
387                                         sendMessage(msg)
388                                 }
389                                 state.lastStatus = "two"
390                         }
391                         if (currentTemp > setLow && currentTemp < setHigh) {
392                 if (state.lastStatus == "two" || state.lastStatus == "one" || state.lastStatus == null){
393                                         //log.info "Setting thermostat mode to ${neutral}"
394                                         def msg = "I changed your thermostat mode to ${neutral} because temperature is neutral"
395                                         thermostat?."${neutral}"()
396                                         sendMessage(msg)
397                                 }
398                                 state.lastStatus = "three"
399                         }
400                 }
401         else{
402                         def delay = (turnOffDelay != null && turnOffDelay != "") ? turnOffDelay * 60 : 60
403                         log.debug("Detected open doors.  Checking door states again")
404                         runIn(delay, "doorCheck")
405                 }
406         }
407 }
408
409 def appTouch(evt) {
410 if(thermostat1){
411         state.lastStatus = "disabled"
412         def currentCoolSetpoint = thermostat1.latestValue("coolingSetpoint") as String
413     def currentHeatSetpoint = thermostat1.latestValue("heatingSetpoint") as String
414     def currentMode = thermostat1.latestValue("thermostatMode") as String
415         def mode = turnOnTherm
416     state.currentCoolSetpoint1 = currentCoolSetpoint
417     state.currentHeatSetpoint1 = currentHeatSetpoint
418     state.currentMode1 = currentMode
419     
420         thermostat1."${mode}"()
421         thermostat1.setCoolingSetpoint(coolingTemp)
422         thermostat1.setHeatingSetpoint(heatingTemp)
423         
424     thermoShutOffTrigger()
425     //log.debug("current coolingsetpoint is ${state.currentCoolSetpoint1}")
426     //log.debug("current heatingsetpoint is ${state.currentHeatSetpoint1}")
427     //log.debug("current mode is ${state.currentMode1}")
428 }    
429 }
430
431 def modeBoostChange(evt) {
432         if(thermostat1 && modes1.contains(location.mode)){
433                 state.lastStatus = "disabled"
434                 def currentCoolSetpoint = thermostat1.latestValue("coolingSetpoint") as String
435         def currentHeatSetpoint = thermostat1.latestValue("heatingSetpoint") as String
436         def currentMode = thermostat1.latestValue("thermostatMode") as String
437                 def mode = turnOnTherm
438         state.currentCoolSetpoint1 = currentCoolSetpoint
439         state.currentHeatSetpoint1 = currentHeatSetpoint
440         state.currentMode1 = currentMode
441     
442                 thermostat1."${mode}"()
443                 thermostat1.setCoolingSetpoint(coolingTemp)
444                 thermostat1.setHeatingSetpoint(heatingTemp)
445         
446         log.debug("current coolingsetpoint is ${state.currentCoolSetpoint1}")
447         log.debug("current heatingsetpoint is ${state.currentHeatSetpoint1}")
448         log.debug("current mode is ${state.currentMode1}")
449         }
450         else{
451                 thermoShutOff()
452     }    
453 }
454
455 def thermoShutOffTrigger() {
456     //log.info("Starting timer to turn off thermostat")
457     def delay = (turnOffDelay2 != null && turnOffDelay2 != "") ? turnOffDelay2 * 60 : 60 
458     state.turnOffTime = now()
459         log.debug ("Turn off delay is ${delay}")
460     runIn(delay, "thermoShutOff")
461   }
462
463 def thermoShutOff(){
464         if(state.lastStatus == "disabled"){
465                 def coolSetpoint = state.currentCoolSetpoint1
466         def heatSetpoint = state.currentHeatSetpoint1
467                 def mode = state.currentMode1
468         def coolSetpoint1 = coolSetpoint.replaceAll("\\]", "").replaceAll("\\[", "")
469         def heatSetpoint1 = heatSetpoint.replaceAll("\\]", "").replaceAll("\\[", "")
470         def mode1 = mode.replaceAll("\\]", "").replaceAll("\\[", "")
471     
472                 state.lastStatus = null
473                 //log.info("Returning thermostat back to normal")
474                 thermostat1.setCoolingSetpoint("${coolSetpoint1}")
475         thermostat1.setHeatingSetpoint("${heatSetpoint1}")
476         thermostat1."${mode1}"()
477         temperatureHandler()
478     }
479 }
480
481 def doorCheck(evt){
482         if (!doorsOk){
483                 log.debug("doors still open turning off ${thermostat}")
484                 def msg = "I changed your thermostat mode to off because some doors are open"
485                 
486         if (state.lastStatus != "off"){
487                 thermostat?.off()
488                         sendMessage(msg)
489                 }
490                 state.lastStatus = "off"
491         }
492
493         else{
494         if (state.lastStatus == "off"){
495                         state.lastStatus = null
496         }
497         temperatureHandler()
498         }
499 }
500
501 private sendMessage(msg){
502         if (sendPushMessage == "Yes") {
503                 sendPush(msg)
504         }
505         if (phoneNumber != null) {
506                 sendSms(phoneNumber, msg)
507         }
508 }
509
510 private getAllOk() {
511         modeOk && daysOk && timeOk && doorsOk
512 }
513
514 private getModeOk() {
515         def result = !modes || modes.contains(location.mode)
516         log.trace "modeOk = $result"
517         result
518 }
519
520 private getDoorsOk() {
521         def result = !doors || !doors.latestValue("contact").contains("open")
522         log.trace "doorsOk = $result"
523         result
524 }
525
526 private getDaysOk() {
527         def result = true
528         if (days) {
529                 def df = new java.text.SimpleDateFormat("EEEE")
530                 if (location.timeZone) {
531                         df.setTimeZone(location.timeZone)
532                 }
533                 else {
534                         df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
535                 }
536                 def day = df.format(new Date())
537                 result = days.contains(day)
538         }
539         log.trace "daysOk = $result"
540         result
541 }
542
543 private getTimeOk() {
544         def result = true
545         if (starting && ending) {
546                 def currTime = now()
547                 def start = timeToday(starting).time
548                 def stop = timeToday(ending).time
549                 result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
550         }
551     
552     else if (starting){
553         result = currTime >= start
554     }
555     else if (ending){
556         result = currTime <= stop
557     }
558     
559         log.trace "timeOk = $result"
560         result
561 }
562
563 def getTimeLabel(starting, ending){
564
565         def timeLabel = "Tap to set"
566         
567     if(starting && ending){
568         timeLabel = "Between" + " " + hhmm(starting) + " "  + "and" + " " +  hhmm(ending)
569     }
570     else if (starting) {
571                 timeLabel = "Start at" + " " + hhmm(starting)
572     }
573     else if(ending){
574     timeLabel = "End at" + hhmm(ending)
575     }
576         timeLabel
577 }
578
579 private hhmm(time, fmt = "h:mm a")
580 {
581         def t = timeToday(time, location.timeZone)
582         def f = new java.text.SimpleDateFormat(fmt)
583         f.setTimeZone(location.timeZone ?: timeZone(time))
584         f.format(t)
585 }
586 def greyedOut(){
587         def result = ""
588     if (sensor) {
589         result = "complete"     
590     }
591     result
592 }
593
594 def greyedOutTherm(){
595         def result = ""
596     if (thermostat) {
597         result = "complete"     
598     }
599     result
600 }
601
602 def greyedOutTherm1(){
603         def result = ""
604     if (thermostat1) {
605         result = "complete"     
606     }
607     result
608 }
609
610 def greyedOutSettings(){
611         def result = ""
612     if (starting || ending || days || modes || sendPushMessage) {
613         result = "complete"     
614     }
615     result
616 }
617
618 def greyedOutTime(starting, ending){
619         def result = ""
620     if (starting || ending) {
621         result = "complete"     
622     }
623     result
624 }
625         
626 private anyoneIsHome() {
627   def result = false
628
629   if(people.findAll { it?.currentPresence == "present" }) {
630     result = true
631   }
632
633   log.debug("anyoneIsHome: ${result}")
634
635   return result
636 }
637         
638 page(name: "timeIntervalInput", title: "Only during a certain time", refreshAfterSelection:true) {
639                 section {
640                         input "starting", "time", title: "Starting (both are required)", required: false 
641                         input "ending", "time", title: "Ending (both are required)", required: false 
642                 }
643         }