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
46 page name:"directorSettings"
47 page name:"ThermostatandDoors"
48 page name:"ThermostatBoost"
49 page name:"SettingsMethod"
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 "SettingsMethod", title: "SettingsMethod", 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") {
288 def SettingsMethod() {
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 = "SettingsMethod"
325 def pageProperties = [
326 name: "SettingsMethod",
327 title: "SettingsMethod",
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
356 // input "sensor", "capability.temperatureMeasurement"
357 // input "doors", "capability.contactSensor"
360 state.lastStatus = null
361 subscribe(app, appTouch)
362 runIn(60, "temperatureHandler")
363 subscribe(sensor, "temperature", temperatureHandler)
365 subscribe(location, modeBoostChange)
368 subscribe(doors, "contact.open", temperatureHandler)
369 subscribe(doors, "contact.closed", doorCheck)
373 def temperatureHandler(evt) {
374 if(modeOk && daysOk && timeOk) {
375 if(setLow > setHigh){
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}"()
389 state.lastStatus = "one"
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}"()
398 state.lastStatus = "two"
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}"()
407 state.lastStatus = "three"
411 def delay = (turnOffDelay != null && turnOffDelay != "") ? turnOffDelay * 60 : 60
412 log.debug("Detected open doors. Checking door states again")
413 runIn(delay, "doorCheck")
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
429 thermostat1."${mode}"()
430 thermostat1.setCoolingSetpoint(coolingTemp)
431 thermostat1.setHeatingSetpoint(heatingTemp)
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}")
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
451 thermostat1."${mode}"()
452 thermostat1.setCoolingSetpoint(coolingTemp)
453 thermostat1.setHeatingSetpoint(heatingTemp)
455 log.debug("current coolingsetpoint is ${state.currentCoolSetpoint1}")
456 log.debug("current heatingsetpoint is ${state.currentHeatSetpoint1}")
457 log.debug("current mode is ${state.currentMode1}")
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")
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("\\[", "")
481 state.lastStatus = null
482 //log.info("Returning thermostat back to normal")
483 thermostat1.setCoolingSetpoint("${coolSetpoint1}")
484 thermostat1.setHeatingSetpoint("${heatSetpoint1}")
485 thermostat1."${mode1}"()
492 log.debug("doors still open turning off ${thermostat}")
493 def msg = "I changed your thermostat mode to off because some doors are open"
495 if (state.lastStatus != "off"){
499 state.lastStatus = "off"
503 if (state.lastStatus == "off"){
504 state.lastStatus = null
510 private sendMessage(msg){
511 if (sendPushMessage == "Yes") {
514 if (phoneNumber != null) {
515 sendSms(phoneNumber, msg)
520 modeOk && daysOk && timeOk && doorsOk
523 private getModeOk() {
524 def result = !modes || modes.contains(location.mode)
525 log.trace "modeOk = $result"
529 private getDoorsOk() {
530 def result = !doors || !doors.latestValue("contact").contains("open")
531 log.trace "doorsOk = $result"
535 private getDaysOk() {
538 def df = new java.text.SimpleDateFormat("EEEE")
539 if (location.timeZone) {
540 df.setTimeZone(location.timeZone)
543 df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
545 def day = df.format(new Date())
546 result = days.contains(day)
548 log.trace "daysOk = $result"
552 private getTimeOk() {
554 if (starting && ending) {
556 def start = timeToday(starting).time
557 def stop = timeToday(ending).time
558 result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
562 result = currTime >= start
565 result = currTime <= stop
568 log.trace "timeOk = $result"
572 def getTimeLabel(starting, ending){
574 def timeLabel = "Tap to set"
576 if(starting && ending){
577 timeLabel = "Between" + " " + hhmm(starting) + " " + "and" + " " + hhmm(ending)
580 timeLabel = "Start at" + " " + hhmm(starting)
583 timeLabel = "End at" + hhmm(ending)
588 private hhmm(time, fmt = "h:mm a")
590 def t = timeToday(time, location.timeZone)
591 def f = new java.text.SimpleDateFormat(fmt)
592 f.setTimeZone(location.timeZone ?: timeZone(time))
603 def greyedOutTherm(){
611 def greyedOutTherm1(){
619 def greyedOutSettings(){
621 if (starting || ending || days || modes || sendPushMessage) {
627 def greyedOutTime(starting, ending){
629 if (starting || ending) {
635 private anyoneIsHome() {
638 if(people.findAll { it?.currentPresence == "present" }) {
642 log.debug("anyoneIsHome: ${result}")