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