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"
41 page name:"directorSettings"
42 page name:"ThermostatandDoors"
43 page name:"ThermostatBoost"
51 def pageProperties = [
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."
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()
69 section([title:"Options", mobileOnly:true]) {
70 label title:"Assign a name", required:false
76 def directorSettings() {
80 type: "capability.temperatureMeasurement",
96 metadata: [values:["auto", "heat", "cool", "off"]]
110 metadata: [values:["auto", "heat", "cool", "off"]]
117 metadata: [values:["auto", "heat", "cool", "off"]]
120 def pageName = "Setup"
122 def pageProperties = [
123 name: "directorSettings",
125 nextPage: "pageSetup"
128 return dynamicPage(pageProperties) {
130 section("Which temperature sensor will control your thermostat?"){
134 paragraph "Here you will setup the upper and lower thresholds for the temperature sensor that will send commands to your thermostat."
136 section("When the temperature falls below this tempurature set mode to..."){
140 section("When the temperature goes above this tempurature set mode to..."){
144 section("When temperature is between the previous temperatures, change mode to..."){
151 def ThermostatandDoors() {
155 type: "capability.thermostat",
162 type: "capability.contactSensor",
169 name: "turnOffDelay",
171 title: "Number of minutes",
175 def pageName = "Thermostat and Doors"
177 def pageProperties = [
178 name: "ThermostatandDoors",
179 title: "Thermostat and Doors",
180 nextPage: "pageSetup"
183 return dynamicPage(pageProperties) {
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)"
188 section("Choose thermostat...") {
191 section("If these doors/windows are open turn off thermostat regardless of outdoor temperature") {
194 section("Wait this long before turning the thermostat off (defaults to 1 minute)") {
201 def ThermostatBoost() {
205 type: "capability.thermostat",
213 metadata: [values: ["cool", "heat"]],
220 title: "Put thermostat into boost mode when mode is...",
228 title: "Cooling Temp?",
235 title: "Heating Temp?",
239 def turnOffDelay2 = [
240 name: "turnOffDelay2",
242 title: "Number of minutes",
247 def pageName = "Thermostat Boost"
249 def pageProperties = [
250 name: "ThermostatBoost",
251 title: "Thermostat Boost",
252 nextPage: "pageSetup"
255 return dynamicPage(pageProperties) {
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."
261 section("Choose a thermostats to boost") {
264 section("If thermostat is off switch to which mode?") {
267 section("Set the thermostat to the following temps") {
271 section("For how long?") {
274 section("In addtion to 'app touch' the following modes will also boost the thermostat") {
284 def sendPushMessage = [
285 name: "sendPushMessage",
287 title: "Send a push notification?",
288 metadata: [values:["Yes","No"]],
296 title: "Send SMS notifications to?",
303 title: "Only on certain days of the week",
306 options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
312 title: "Only when mode is",
317 def pageName = "Settings"
319 def pageProperties = [
322 nextPage: "pageSetup"
325 return dynamicPage(pageProperties) {
328 section( "Notifications" ) {
329 input sendPushMessage
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
351 state.lastStatus = null
352 subscribe(app, appTouch)
353 runIn(60, "temperatureHandler")
354 subscribe(sensor, "temperature", temperatureHandler)
356 subscribe(location, modeBoostChange)
359 subscribe(doors, "contact.open", temperatureHandler)
360 subscribe(doors, "contact.closed", doorCheck)
364 def temperatureHandler(evt) {
365 if(modeOk && daysOk && timeOk) {
366 if(setLow > setHigh){
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}"()
380 state.lastStatus = "one"
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}"()
389 state.lastStatus = "two"
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}"()
398 state.lastStatus = "three"
402 def delay = (turnOffDelay != null && turnOffDelay != "") ? turnOffDelay * 60 : 60
403 log.debug("Detected open doors. Checking door states again")
404 runIn(delay, "doorCheck")
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
420 thermostat1."${mode}"()
421 thermostat1.setCoolingSetpoint(coolingTemp)
422 thermostat1.setHeatingSetpoint(heatingTemp)
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}")
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
442 thermostat1."${mode}"()
443 thermostat1.setCoolingSetpoint(coolingTemp)
444 thermostat1.setHeatingSetpoint(heatingTemp)
446 log.debug("current coolingsetpoint is ${state.currentCoolSetpoint1}")
447 log.debug("current heatingsetpoint is ${state.currentHeatSetpoint1}")
448 log.debug("current mode is ${state.currentMode1}")
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")
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("\\[", "")
472 state.lastStatus = null
473 //log.info("Returning thermostat back to normal")
474 thermostat1.setCoolingSetpoint("${coolSetpoint1}")
475 thermostat1.setHeatingSetpoint("${heatSetpoint1}")
476 thermostat1."${mode1}"()
483 log.debug("doors still open turning off ${thermostat}")
484 def msg = "I changed your thermostat mode to off because some doors are open"
486 if (state.lastStatus != "off"){
490 state.lastStatus = "off"
494 if (state.lastStatus == "off"){
495 state.lastStatus = null
501 private sendMessage(msg){
502 if (sendPushMessage == "Yes") {
505 if (phoneNumber != null) {
506 sendSms(phoneNumber, msg)
511 modeOk && daysOk && timeOk && doorsOk
514 private getModeOk() {
515 def result = !modes || modes.contains(location.mode)
516 log.trace "modeOk = $result"
520 private getDoorsOk() {
521 def result = !doors || !doors.latestValue("contact").contains("open")
522 log.trace "doorsOk = $result"
526 private getDaysOk() {
529 def df = new java.text.SimpleDateFormat("EEEE")
530 if (location.timeZone) {
531 df.setTimeZone(location.timeZone)
534 df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
536 def day = df.format(new Date())
537 result = days.contains(day)
539 log.trace "daysOk = $result"
543 private getTimeOk() {
545 if (starting && ending) {
547 def start = timeToday(starting).time
548 def stop = timeToday(ending).time
549 result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
553 result = currTime >= start
556 result = currTime <= stop
559 log.trace "timeOk = $result"
563 def getTimeLabel(starting, ending){
565 def timeLabel = "Tap to set"
567 if(starting && ending){
568 timeLabel = "Between" + " " + hhmm(starting) + " " + "and" + " " + hhmm(ending)
571 timeLabel = "Start at" + " " + hhmm(starting)
574 timeLabel = "End at" + hhmm(ending)
579 private hhmm(time, fmt = "h:mm a")
581 def t = timeToday(time, location.timeZone)
582 def f = new java.text.SimpleDateFormat(fmt)
583 f.setTimeZone(location.timeZone ?: timeZone(time))
594 def greyedOutTherm(){
602 def greyedOutTherm1(){
610 def greyedOutSettings(){
612 if (starting || ending || days || modes || sendPushMessage) {
618 def greyedOutTime(starting, ending){
620 if (starting || ending) {
626 private anyoneIsHome() {
629 if(people.findAll { it?.currentPresence == "present" }) {
633 log.debug("anyoneIsHome: ${result}")