[OBSOLETE] Groovy Code for: Smart Light Timer, X minutes unless already on

UPDATE 7/9/2019: I put all my code on Github, as formatting here is messed up:


I regularly get requests for the latest version of this app: “Smart Light Timer, X minutes unless already on”, so I thought I’d post it to make it easier to find.

This app will listen for a variety of of triggers. When triggered, it will turn on a set of switches (or other actions), for a configurable amount of time. If more actions happens WHILE the lights are on, the timer will be extended. If switches are toggled off/on while the lights are in timer mode, the timer will STOP and the lights remain on. If the lights are already ON when the trigger happens, NO timer is started, and the lights simply stays on.

I use this extensively throughout my home, and it solves a bunch of problems that Smartthings could not be bothered solving in their standard app.

Hope this makes it easier to find. This version has been pending publication in Smartthings for 3 years, and is the newest version.

Cheers
Anders

/**

  • Smart Timer
  • Loosely based on “Light Follows Me”
  • This prevent them from turning off when the timer expires, if they were already turned on
  • If the switch is already on, if won’t be affected by the timer (Must be turned of manually)
  • If the switch is toggled while in timeout-mode, it will remain on and ignore the timer (Must be turned of manually)
  • The timeout perid begins when the contact is closed, or motion stops, so leaving a door open won’t start the timer until it’s closed.
  • Author: andersheie@gmail.com
  • Date: 2015-10-30
    */

definition(
name: “Smart Light Timer, X minutes unless already on”,
namespace: “Pope”,
author: “listpope@cox.net”,
description: “Turns on a switch for X minutes, then turns it off. Unless, the switch is already on, in which case it stays on. If the switch is toggled while the timer is running, the timer is canceled.”,
category: “Convenience”,
iconUrl: “http://upload.wikimedia.org/wikipedia/commons/6/6a/Light_bulb_icon_tips.svg”,
iconX2Url: “http://upload.wikimedia.org/wikipedia/commons/6/6a/Light_bulb_icon_tips.svg”)

preferences {
section(“Turn on when there is movement…”){
input “motions”, “capability.motionSensor”, multiple: true, title: “Select motion detectors”, required: false
}
section(“Or, turn on when one of these contacts opened”){
input “contacts”, “capability.contactSensor”, multiple: true, title: “Select Contacts”, required: false
}
section(“Or, turn on when any of these people come home”) {
input “peoplearrive”, “capability.presenceSensor”, multiple: true, required: false
}
section(“Or, turn on when any of these people leave”) {
input “peopleleave”, “capability.presenceSensor”, multiple: true, required: false
}
section(“And off after no more triggers after…”){
input “minutes1”, “number”, title: “Minutes?”, defaultValue: “5”
}
section(“Turn on/off light(s)…”){
input “switches”, “capability.switch”, multiple: true, title: “Select Lights”
}
}

def installed()
{
log.debug “installed()”
initialize()
}

def updated()
{
log.debug “updated()”
initialize()
}

def initialize()
{
log.debug “initialize()”
// Reset to new set of Switches, just in case.
unsubscribe()

state.switches = [:]

switches.each { 
  // log.debug "ID: $it.id" 
    // Set ready state for each switch
    state.switches["$it.id"] = "ready"
}
logStates()

subscribe(motions, “motion”, motionHandler)
subscribe(switches, “switch”, switchChange)
subscribe(contacts, “contact”, contactHandler)
subscribe(peoplearrive, “presence.present”, presencePresentHandler)
subscribe(peopleleave, “presence.not present”, presenceNotPresentHandler)
state.lastAction = null
schedule(“0 * * * * ?”, “scheduleCheck”)

}

def logStates() {
if(state.switches == null) {
state.switches = [:]
}
state.switches.each {
log.debug “State: $it.key = $it.value”
}
}

def turnSwitchOn(id) {
for(S in switches) {
if(S.id == id) {
S.on()
}
}
}

def turnSwitchOff(id) {
for(S in switches) {
if(S.id == id) {
S.off()
}
}
}

def initState(deviceID) {
// Have to add this so existing apps that are neither updated nor initiated can slowly get ‘reset’ with states.
// Smartthings doesn’t call any specific method when new version is published.

if(state.switches == null) {
state.switches = [:]
}
if(state.switches[deviceID] == null) {
state.switches[deviceID] = “ready”
}

}

def switchChange(evt) {
def deviceID = evt.device.id
log.debug "SwitchChange: $evt.name: $evt.value $evt.device.id current state = " + state.switches[deviceID]
initState(deviceID)

if(evt.value == “on”) {
if(state.switches[deviceID] == “activating”) {
// OK, probably an event from Activating something, and not the switch itself. Go to Active mode.
log.debug “$deviceID = " + state.switches[deviceID] + " → active”
state.switches[deviceID] = “active”
} else if(state.switches[deviceID] != “active”) {
log.debug “$deviceID = " + state.switches[deviceID] + " → already on”
state.switches[deviceID] = “already on”
}
} else {
// If active and switch is turned of manually, then stop the schedule and go to ready state
state.switches[deviceID] = “ready”
}
logStates()

}

def contactHandler(evt) {
log.debug “contactHandler: $evt.name: $evt.value”

state.switches.each { thisswitch ->
	 initState(thisswitch.key)
     //log.debug "Looking for $thisswitch.key"
     if (evt.value == "open") {
        if(state.switches[thisswitch.key] == "ready") {
            log.debug "Turning on lights by contact opening: $thisswitch"
            log.debug "$thisswitch.key = " + state.switches[thisswitch.key] + " -> activating"
            state.switches[thisswitch.key] = "activating"
            turnSwitchOn(thisswitch.key)
            setActiveAndSchedule()
            
        }
    } else if (evt.value == "closed") {
        if (!state.lastAction && (state.switches[thisswitch.key] == "active" 
        					   || state.switches[thisswitch.key] == "activating")) {
            // When contact closes, we reset the timer if not already set
            log.debug "$thisswitch.key = " + state.switches[thisswitch.key] + " -> active"
            state.switches[thisswitch.key] = "active"
            setActiveAndSchedule()
        }
    }
}
logStates()

}

def scheduleCheck() {
log.debug “schedule check, ts = ${state.lastAction}”
boolean resetInactive = false
state.switches.each { thisswitch →
initState(thisswitch.key)
if(state.switches[thisswitch.key] != “already on”) {
if(state.lastAction != null) {
def elapsed = now() - state.lastAction
log.debug “${elapsed / 1000} sec since events stopped”
def threshold = 1000 * 60 * minutes1
if (elapsed >= threshold) {
if (state.switches[thisswitch.key] == “active”
|| state.switches[thisswitch.key] == “activating”) {
state.switches[thisswitch.key] = “ready”
log.debug “Turning off lights by switch closing: $thisswitch”
turnSwitchOff(thisswitch.key)
}
resetInactive = true
}
}
}
}
if(resetInactive) {
state.lastAction = null
unschedule()
}
logStates()
}

/**********************************************************************/

def motionHandler(evt) {
log.debug "motionHandler: $evt.name: $evt.value (current state: " + state.myState + “)”

state.switches.each { thisswitch →
initState(thisswitch.key)
if (evt.value == “active”) {
if(state.switches[thisswitch.key] == “ready”
|| state.switches[thisswitch.key] == “active”
|| state.switches[thisswitch.key] == “activating” ) {
log.debug “$thisswitch.key = " + state.switches[thisswitch.key] + " → activating”
state.switches[thisswitch.key] = “activating”
turnSwitchOn(thisswitch.key)
setActiveAndSchedule()
}
} else if (evt.value == “inactive”) {
if (state.switches[thisswitch.key] == “active” || state.switches[thisswitch.key] == “activating”) {
// When Motion ends, we reset the timer if not already set
log.debug “$thisswitch.key = " + state.switches[thisswitch.key] + " → active”
state.switches[thisswitch.key] = “active”
setActiveAndSchedule()
}
}
}
logStates()
}

def presencePresentHandler(evt) {
log.debug “presence: $evt.linkText is now $evt.value”

state.switches.each { thisswitch ->
	initState(thisswitch.key)
    if (evt.value == "present") {
        if(state.switches[thisswitch.key] == "ready" 
        	|| state.switches[thisswitch.key] == "active" 
            || state.switches[thisswitch.key] == "activating" ) {
            log.debug "Presence turning on switch $thisswitch"
            state.switches[thisswitch.key] = "active"
            turnSwitchOn(thisswitch.key)
            // We don't wait until the person leave, but instead start timer immediately.
            setActiveAndSchedule()
            
        }
    } 
}
logStates()

}

def presenceNotPresentHandler(evt) {
log.debug “presence: $evt.linkText is now $evt.value”

state.switches.each { thisswitch ->
	initState(thisswitch.key)
    if (evt.value == "not present") {
        if(state.switches[thisswitch.key] == "ready" 
        	|| state.switches[thisswitch.key] == "active" 
            || state.switches[thisswitch.key] == "activating" ) {
            log.debug "No Presence turning on lights $thisswitch"
            state.switches[thisswitch.key] = "active"
            turnSwitchOn(thisswitch.key)
            // We don't wait until the person arrive back, but instead start timer immediately.
            setActiveAndSchedule()
        }
    } 
}
logStates()

}

def setActiveAndSchedule() {
unschedule()
state.lastAction = now()
schedule(“0 * * * * ?”, “scheduleCheck”)
log.debug “Scheduled new timer”
}

4 Likes

Just curious, how is this diff from the default Smart Lighting app by SmartThings?

By its features listed above.

Available via GitHub?

1 Like

How do you prevent lights going on when its not dark??

I use another app to switch modes based on a light sensor. If it’s daytime, this app won’t run.

Excellent! I have been bothered by this a lot! Thanks for doing this! Can’t wait to try it!

James

Thanks for updating. I deleted the old app and when creating the new one from code, I get this error:

startup failed: script_app_metadata_7f8bf99a_e283_4260_aa10_306197310a2b: 13: unexpected token: Light @ line 13, column 14. name: “Smart Light Timer, X minutes unless already on”, ^ 1 error

Fix this error message by changing all the quote marks to simple quotation marks
– change all “ to "
– change all ” to "
App installs but not working for me yet

If you just copy and pasted off this thread, you may have picked up some additional annotations the forum throws in.

I found that all my evt.name calls had included the click count, rendering it an unrecognized evt.name1. Reverting them all to evt.name got the code working for me again.

Thank you sprint113. That was the error that was stopping mine from working.

And thanks Anders

This app is brilliant! Thanks for your hard work.
Can you add an option to only trigger at a certain time/sunset etc. please?

I haven’t figured out how to change all the quotation marks to get rid of the error yet. Hopefully there will be a finished version coming out

Copy it all into notepad or textedit first.

Do a find and replace for
find
"
replace
"

Click replace all. (yes you are just " in both boxes but it’ll change the formatted ones to normal ones.)

Use the same method to change evt.name1 to evt.name

Then copy and paste into your piston.

I tried that on my MacBook using Word last night but was not successful. I couldn’t find regular quotes or turn off “smart quotes” as they called it. I will see if I can use windows today on notepad or textedit. Thanks

I just tried it on notepad and in Word. On word it said successful 220 times or something like that and same error. It didn’t actually replace the smart quotes with regular quotes. When I get some times I might go through and do everything manually.

I used standard MS Word.
Just did find and replace with " and " and it worked.

I ended up using Webcore in the end to do this which involves installing webcore and various other things, but is pretty good once you have it setup.

Sorry, the community won’t let me paste the code without messing it up.

UPDATE 7/9/2019: I put all my code on Github, as formatting here is messed up:

Hopefully that solves the formatting problem for good.