2 * Thermostat Mode Director
3 * Source: https://github.com/tslagle13/SmartThings/blob/master/Director-Series-Apps/Thermostat-Mode-Director/Thermostat%20Mode%20Director.groovy
9 * --Updated UI to make it look pretty.
11 * --Added option for modes to trigger thermostat boost.
13 * Source code can be found here: https://github.com/tslagle13/SmartThings/blob/master/smartapps/tslagle13/vacation-lighting-director.groovy
15 * Copyright 2015 Tim Slagle
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:
20 * http://www.apache.org/licenses/LICENSE-2.0
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.
28 // Automatically generated. Make future change here.
30 name: "Thermostat Mode Director",
31 namespace: "tslagle13",
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"
40 page(name: "timeIntervalInput", title: "Only during a certain time") {
42 input "starting", "time", title: "Starting", required: false
43 input "ending", "time", title: "Ending", required: false
47 page name:"directorSettings"
48 page name:"ThermostatandDoors"
49 page name:"ThermostatBoost"
57 def pageProperties = [
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."
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()
75 section([title:"Options", mobileOnly:true]) {
76 label title:"Assign a name", required:false
82 def directorSettings() {
86 type: "capability.temperatureMeasurement",
102 metadata: [values:["auto", "heat", "cool", "off"]]
116 metadata: [values:["auto", "heat", "cool", "off"]]
123 metadata: [values:["auto", "heat", "cool", "off"]]
126 def pageName = "Setup"
128 def pageProperties = [
129 name: "directorSettings",
131 nextPage: "pageSetup"
134 return dynamicPage(pageProperties) {
136 section("Which temperature sensor will control your thermostat?"){
140 paragraph "Here you will setup the upper and lower thresholds for the temperature sensor that will send commands to your thermostat."
142 section("When the temperature falls below this tempurature set mode to..."){
146 section("When the temperature goes above this tempurature set mode to..."){
150 section("When temperature is between the previous temperatures, change mode to..."){
157 def ThermostatandDoors() {
161 type: "capability.thermostat",
168 type: "capability.contactSensor",
175 name: "turnOffDelay",
177 title: "Number of minutes",
181 def pageName = "Thermostat and Doors"
183 def pageProperties = [
184 name: "ThermostatandDoors",
185 title: "Thermostat and Doors",
186 nextPage: "pageSetup"
189 return dynamicPage(pageProperties) {
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)"
194 section("Choose thermostat...") {
197 section("If these doors/windows are open turn off thermostat regardless of outdoor temperature") {
200 section("Wait this long before turning the thermostat off (defaults to 1 minute)") {
207 def ThermostatBoost() {
211 type: "capability.thermostat",
219 metadata: [values: ["cool", "heat"]],
226 title: "Put thermostat into boost mode when mode is...",
234 title: "Cooling Temp?",
241 title: "Heating Temp?",
245 def turnOffDelay2 = [
246 name: "turnOffDelay2",
248 title: "Number of minutes",
253 def pageName = "Thermostat Boost"
255 def pageProperties = [
256 name: "ThermostatBoost",
257 title: "Thermostat Boost",
258 nextPage: "pageSetup"
261 return dynamicPage(pageProperties) {
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."
267 section("Choose a thermostats to boost") {
270 section("If thermostat is off switch to which mode?") {
273 section("Set the thermostat to the following temps") {
277 section("For how long?") {
280 section("In addtion to 'app touch' the following modes will also boost the thermostat") {
290 def sendPushMessage = [
291 name: "sendPushMessage",
293 title: "Send a push notification?",
294 metadata: [values:["Yes","No"]],
302 title: "Send SMS notifications to?",
309 title: "Only on certain days of the week",
312 options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
318 title: "Only when mode is",
323 def pageName = "Settings"
325 def pageProperties = [
328 nextPage: "pageSetup"
331 return dynamicPage(pageProperties) {
334 section( "Notifications" ) {
335 input sendPushMessage
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
357 state.lastStatus = null
358 subscribe(app, appTouch)
359 runIn(60, "temperatureHandler")
360 subscribe(sensor, "temperature", temperatureHandler)
362 subscribe(location, modeBoostChange)
365 subscribe(doors, "contact.open", temperatureHandler)
366 subscribe(doors, "contact.closed", doorCheck)
370 def temperatureHandler(evt) {
371 if(modeOk && daysOk && timeOk) {
372 if(setLow > setHigh){
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}"()
386 state.lastStatus = "one"
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}"()
395 state.lastStatus = "two"
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}"()
404 state.lastStatus = "three"
408 def delay = (turnOffDelay != null && turnOffDelay != "") ? turnOffDelay * 60 : 60
409 log.debug("Detected open doors. Checking door states again")
410 runIn(delay, "doorCheck")
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
426 thermostat1."${mode}"()
427 thermostat1.setCoolingSetpoint(coolingTemp)
428 thermostat1.setHeatingSetpoint(heatingTemp)
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}")
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
448 thermostat1."${mode}"()
449 thermostat1.setCoolingSetpoint(coolingTemp)
450 thermostat1.setHeatingSetpoint(heatingTemp)
452 log.debug("current coolingsetpoint is ${state.currentCoolSetpoint1}")
453 log.debug("current heatingsetpoint is ${state.currentHeatSetpoint1}")
454 log.debug("current mode is ${state.currentMode1}")
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")
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("\\[", "")
478 state.lastStatus = null
479 //log.info("Returning thermostat back to normal")
480 thermostat1.setCoolingSetpoint("${coolSetpoint1}")
481 thermostat1.setHeatingSetpoint("${heatSetpoint1}")
482 thermostat1."${mode1}"()
489 log.debug("doors still open turning off ${thermostat}")
490 def msg = "I changed your thermostat mode to off because some doors are open"
492 if (state.lastStatus != "off"){
496 state.lastStatus = "off"
500 if (state.lastStatus == "off"){
501 state.lastStatus = null
507 private sendMessage(msg){
508 if (sendPushMessage == "Yes") {
511 if (phoneNumber != null) {
512 sendSms(phoneNumber, msg)
517 modeOk && daysOk && timeOk && doorsOk
520 private getModeOk() {
521 def result = !modes || modes.contains(location.mode)
522 log.trace "modeOk = $result"
526 private getDoorsOk() {
527 def result = !doors || !doors.latestValue("contact").contains("open")
528 log.trace "doorsOk = $result"
532 private getDaysOk() {
535 def df = new java.text.SimpleDateFormat("EEEE")
536 if (location.timeZone) {
537 df.setTimeZone(location.timeZone)
540 df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
542 def day = df.format(new Date())
543 result = days.contains(day)
545 log.trace "daysOk = $result"
549 private getTimeOk() {
551 if (starting && ending) {
553 def start = timeToday(starting).time
554 def stop = timeToday(ending).time
555 result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
559 result = currTime >= start
562 result = currTime <= stop
565 log.trace "timeOk = $result"
569 def getTimeLabel(starting, ending){
571 def timeLabel = "Tap to set"
573 if(starting && ending){
574 timeLabel = "Between" + " " + hhmm(starting) + " " + "and" + " " + hhmm(ending)
577 timeLabel = "Start at" + " " + hhmm(starting)
580 timeLabel = "End at" + hhmm(ending)
585 private hhmm(time, fmt = "h:mm a")
587 def t = timeToday(time, location.timeZone)
588 def f = new java.text.SimpleDateFormat(fmt)
589 f.setTimeZone(location.timeZone ?: timeZone(time))
600 def greyedOutTherm(){
608 def greyedOutTherm1(){
616 def greyedOutSettings(){
618 if (starting || ending || days || modes || sendPushMessage) {
624 def greyedOutTime(starting, ending){
626 if (starting || ending) {
632 private anyoneIsHome() {
635 if(people.findAll { it?.currentPresence == "present" }) {
639 log.debug("anyoneIsHome: ${result}")