Update speaker-weather-forecast.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:"directorSettings"
47     page name:"ThermostatandDoors"
48     page name:"ThermostatBoost"
49     page name:"SettingsMethod"
50     page name:"pageSetup"
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 "SettingsMethod", title: "SettingsMethod", 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 SettingsMethod() {
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 = "SettingsMethod"
324     
325     def pageProperties = [
326         name:       "SettingsMethod",
327         title:      "SettingsMethod",
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 // input "sensor", "capability.temperatureMeasurement"
357 // input "doors", "capability.contactSensor"
358
359 def init(){
360         state.lastStatus = null
361         subscribe(app, appTouch)
362     runIn(60, "temperatureHandler")
363     subscribe(sensor, "temperature", temperatureHandler)
364     if(modes1){
365         subscribe(location, modeBoostChange)
366     }
367         if(doors){
368                 subscribe(doors, "contact.open", temperatureHandler)
369         subscribe(doors, "contact.closed", doorCheck)
370         }
371 }
372
373 def temperatureHandler(evt) {
374         if(modeOk && daysOk && timeOk) {
375                 if(setLow > setHigh){
376                         def temp = setLow
377                         setLow = setHigh
378                         setHigh = temp
379                 }
380                 if (doorsOk) {
381                         def currentTemp = sensor.latestValue("temperature")
382                         if (currentTemp < setLow) {
383                 if (state.lastStatus == "two" || state.lastStatus == "three" || state.lastStatus == null){
384                                         //log.info "Setting thermostat mode to ${cold}"
385                                         def msg = "I changed your thermostat mode to ${cold} because temperature is below ${setLow}"
386                                         thermostat?."${cold}"()
387                     sendMessage(msg)
388                     }
389                                 state.lastStatus = "one"
390                         }
391                         if (currentTemp > setHigh) {
392                 if (state.lastStatus == "one" || state.lastStatus == "three" || state.lastStatus == null){
393                                         //log.info "Setting thermostat mode to ${hot}"
394                                         def msg = "I changed your thermostat mode to ${hot} because temperature is above ${setHigh}"
395                                         thermostat?."${hot}"()
396                                         sendMessage(msg)
397                                 }
398                                 state.lastStatus = "two"
399                         }
400                         if (currentTemp > setLow && currentTemp < setHigh) {
401                 if (state.lastStatus == "two" || state.lastStatus == "one" || state.lastStatus == null){
402                                         //log.info "Setting thermostat mode to ${neutral}"
403                                         def msg = "I changed your thermostat mode to ${neutral} because temperature is neutral"
404                                         thermostat?."${neutral}"()
405                                         sendMessage(msg)
406                                 }
407                                 state.lastStatus = "three"
408                         }
409                 }
410         else{
411                         def delay = (turnOffDelay != null && turnOffDelay != "") ? turnOffDelay * 60 : 60
412                         log.debug("Detected open doors.  Checking door states again")
413                         runIn(delay, "doorCheck")
414                 }
415         }
416 }
417
418 def appTouch(evt) {
419 if(thermostat1){
420         state.lastStatus = "disabled"
421         def currentCoolSetpoint = thermostat1.latestValue("coolingSetpoint") as String
422     def currentHeatSetpoint = thermostat1.latestValue("heatingSetpoint") as String
423     def currentMode = thermostat1.latestValue("thermostatMode") as String
424         def mode = turnOnTherm
425     state.currentCoolSetpoint1 = currentCoolSetpoint
426     state.currentHeatSetpoint1 = currentHeatSetpoint
427     state.currentMode1 = currentMode
428     
429         thermostat1."${mode}"()
430         thermostat1.setCoolingSetpoint(coolingTemp)
431         thermostat1.setHeatingSetpoint(heatingTemp)
432         
433     thermoShutOffTrigger()
434     //log.debug("current coolingsetpoint is ${state.currentCoolSetpoint1}")
435     //log.debug("current heatingsetpoint is ${state.currentHeatSetpoint1}")
436     //log.debug("current mode is ${state.currentMode1}")
437 }    
438 }
439
440 def modeBoostChange(evt) {
441         if(thermostat1 && modes1.contains(location.mode)){
442                 state.lastStatus = "disabled"
443                 def currentCoolSetpoint = thermostat1.latestValue("coolingSetpoint") as String
444         def currentHeatSetpoint = thermostat1.latestValue("heatingSetpoint") as String
445         def currentMode = thermostat1.latestValue("thermostatMode") as String
446                 def mode = turnOnTherm
447         state.currentCoolSetpoint1 = currentCoolSetpoint
448         state.currentHeatSetpoint1 = currentHeatSetpoint
449         state.currentMode1 = currentMode
450     
451                 thermostat1."${mode}"()
452                 thermostat1.setCoolingSetpoint(coolingTemp)
453                 thermostat1.setHeatingSetpoint(heatingTemp)
454         
455         log.debug("current coolingsetpoint is ${state.currentCoolSetpoint1}")
456         log.debug("current heatingsetpoint is ${state.currentHeatSetpoint1}")
457         log.debug("current mode is ${state.currentMode1}")
458         }
459         else{
460                 thermoShutOff()
461     }    
462 }
463
464 def thermoShutOffTrigger() {
465     //log.info("Starting timer to turn off thermostat")
466     def delay = (turnOffDelay2 != null && turnOffDelay2 != "") ? turnOffDelay2 * 60 : 60 
467     state.turnOffTime = now()
468         log.debug ("Turn off delay is ${delay}")
469     runIn(delay, "thermoShutOff")
470   }
471
472 def thermoShutOff(){
473         if(state.lastStatus == "disabled"){
474                 def coolSetpoint = state.currentCoolSetpoint1
475         def heatSetpoint = state.currentHeatSetpoint1
476                 def mode = state.currentMode1
477         def coolSetpoint1 = coolSetpoint.replaceAll("\\]", "").replaceAll("\\[", "")
478         def heatSetpoint1 = heatSetpoint.replaceAll("\\]", "").replaceAll("\\[", "")
479         def mode1 = mode.replaceAll("\\]", "").replaceAll("\\[", "")
480     
481                 state.lastStatus = null
482                 //log.info("Returning thermostat back to normal")
483                 thermostat1.setCoolingSetpoint("${coolSetpoint1}")
484         thermostat1.setHeatingSetpoint("${heatSetpoint1}")
485         thermostat1."${mode1}"()
486         temperatureHandler()
487     }
488 }
489
490 def doorCheck(evt){
491         if (!doorsOk){
492                 log.debug("doors still open turning off ${thermostat}")
493                 def msg = "I changed your thermostat mode to off because some doors are open"
494                 
495         if (state.lastStatus != "off"){
496                 thermostat?.off()
497                         sendMessage(msg)
498                 }
499                 state.lastStatus = "off"
500         }
501
502         else{
503         if (state.lastStatus == "off"){
504                         state.lastStatus = null
505         }
506         temperatureHandler()
507         }
508 }
509
510 private sendMessage(msg){
511         if (sendPushMessage == "Yes") {
512                 sendPush(msg)
513         }
514         if (phoneNumber != null) {
515                 sendSms(phoneNumber, msg)
516         }
517 }
518
519 private getAllOk() {
520         modeOk && daysOk && timeOk && doorsOk
521 }
522
523 private getModeOk() {
524         def result = !modes || modes.contains(location.mode)
525         log.trace "modeOk = $result"
526         result
527 }
528
529 private getDoorsOk() {
530         def result = !doors || !doors.latestValue("contact").contains("open")
531         log.trace "doorsOk = $result"
532         result
533 }
534
535 private getDaysOk() {
536         def result = true
537         if (days) {
538                 def df = new java.text.SimpleDateFormat("EEEE")
539                 if (location.timeZone) {
540                         df.setTimeZone(location.timeZone)
541                 }
542                 else {
543                         df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
544                 }
545                 def day = df.format(new Date())
546                 result = days.contains(day)
547         }
548         log.trace "daysOk = $result"
549         result
550 }
551
552 private getTimeOk() {
553         def result = true
554         if (starting && ending) {
555                 def currTime = now()
556                 def start = timeToday(starting).time
557                 def stop = timeToday(ending).time
558                 result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
559         }
560     
561     else if (starting){
562         result = currTime >= start
563     }
564     else if (ending){
565         result = currTime <= stop
566     }
567     
568         log.trace "timeOk = $result"
569         result
570 }
571
572 def getTimeLabel(starting, ending){
573
574         def timeLabel = "Tap to set"
575         
576     if(starting && ending){
577         timeLabel = "Between" + " " + hhmm(starting) + " "  + "and" + " " +  hhmm(ending)
578     }
579     else if (starting) {
580                 timeLabel = "Start at" + " " + hhmm(starting)
581     }
582     else if(ending){
583     timeLabel = "End at" + hhmm(ending)
584     }
585         timeLabel
586 }
587
588 private hhmm(time, fmt = "h:mm a")
589 {
590         def t = timeToday(time, location.timeZone)
591         def f = new java.text.SimpleDateFormat(fmt)
592         f.setTimeZone(location.timeZone ?: timeZone(time))
593         f.format(t)
594 }
595 def greyedOut(){
596         def result = ""
597     if (sensor) {
598         result = "complete"     
599     }
600     result
601 }
602
603 def greyedOutTherm(){
604         def result = ""
605     if (thermostat) {
606         result = "complete"     
607     }
608     result
609 }
610
611 def greyedOutTherm1(){
612         def result = ""
613     if (thermostat1) {
614         result = "complete"     
615     }
616     result
617 }
618
619 def greyedOutSettings(){
620         def result = ""
621     if (starting || ending || days || modes || sendPushMessage) {
622         result = "complete"     
623     }
624     result
625 }
626
627 def greyedOutTime(starting, ending){
628         def result = ""
629     if (starting || ending) {
630         result = "complete"     
631     }
632     result
633 }
634         
635 private anyoneIsHome() {
636   def result = false
637
638   if(people.findAll { it?.currentPresence == "present" }) {
639     result = true
640   }
641
642   log.debug("anyoneIsHome: ${result}")
643
644   return result
645 }