Auto-Lock Doors

Full disclosure: I’m fairly new to development, so I expect that my chops are pretty poor. Bear with me.

I’m trying to write a SmartApp that automatically locks my door after a use-specified number of minutes. Here’s my code:

/**
 *  Auto-Lock Doors
 *
 *  Author: Chris Sader
 */
preferences {
	section("When a door unlocks...") {
		input "lock1", "capability.lock"
	}
	section("Lock it how many minutes later?") {
		input "minutesLater", "number", title: "When?"
	}
}

def installed() {
	log.debug "Installed with settings: ${settings}"
	subscribe(lock1, "lock", doorUnlockedHandler, [filterEvents: false])
}

def updated() {
	log.debug "Updated with settings: ${settings}"

	unsubscribe()
	subscribe(lock1, "lock", doorUnlockedHandler, [filterEvents: false])
}

def doorUnlockedHandler(evt) {
		log.debug "Lock ${lock1} was: ${evt.value}"
		def delay = minutesLater * 60 * 1000
		log.debug "Locking in ${minutesLater} minutes (${delay}ms)"
		if (evt.value == "unlocked") {
        	lock1.lock(delay: delay)
        }
}

When I installed this SmartApp, it seemed to work the first and second time (although the door locked quite a big AFTER the specified time limit was up).

Any help is appreciated.

To clarify, in case my intention wasn’t clear, the expectation is that this SmartApp would be triggered when the door’s status changes to “unlocked”, and after the specified amount of time has elapsed, a “lock” command would be sent.

I know there are issues with Z-Wave locks reporting their status correctly, especially after a manual state change. That could be what’s going on with your app. The app on my phone doesn’t even report the state change when a ST app changes it most the time.

@csader

Cory is absolutely right. Z-wave door locks do not report instantly when there is a manual status change. Future firmware updates from SmartThings may alleviate this problem, but right now a manual state change (ie, physically unlocking the door with a key or using the keypad to enter a code and unlocking the door) will not register right away. It might be up to 5 minutes before the lock reports that it has been unlocked.

If you’re using a kwikset (as I am) I know there is a way to force an auto lock after 30 seconds. This is within the lock itself, not via SmartThings.

Alternately, if you have an open/close sensor on this door, you may want to use that to trigger the app to run instead of the door lock.

Thanks @coryds and @chrisb. That’s really unfortunate.

I’m aware of the built-in 30 second auto-lock, but I wanted something more configurable. I would rather not have to use a open/close sensor (besides the fact that the Eversprings I own don’t work with ST, either) to do something I’ve been able to do for years with my Vera setup.

I know this is still the early stage. I just wish there was a more direct way to get these kinds of (IMHO) basic, critical issues fixed without having to wait.

First, nice start! Welcome to scripting, it’ll be a bumpy road ahead. :wink:

Secondly, you have a race condition. What happens if someone locks it, and then unlocks it again? Your timer does not reset.

In your case, the following will happen.
0 seconds, door unlocks. Timer set for locking in 300 seconds (at 300 seconds).
298 seconds, door locks.
299 seconds, door unlocks. Additional timer set for locking in 300 seconds (at 599 seconds).
300 seconds (0+300 seconds), door locks. (The human is expecting it to be “unlocked for 5 minutes”)
599 seconds (299+300 seconds), door locks.

You should account for resetting of the state change to prolong the timer.

0 seconds, door unlocks. Timer set for locking in 300 seconds (at 300 seconds)
298 seconds, door locks. <<Timer may clear to avoid double-locking, unless you want to be extra safe>>
299 seconds, door unlocks. <<Unschedule(), and reschedule locking for +300s)
599 seconds, door locks.

Use of the state variable can help remember the lock’s state by the program between runs.

https://smartthings.zendesk.com/entries/21601924-SmartApp-Application-State

(The human is expecting it to be “unlocked for 5 minutes”)

Stupid humans. This world would run some much nicer without them, wouldn’t it? :slight_smile:

Another way to handle it is to use the “runIn” command to schedule a second process instead of the delay. The benefit of doing that is we can unschedule the process if lock occurs:

def lockDoor() {                                                // This process locks the door.
               lock1.lock()                                     // Don't need delay because the process is scheduled to run later
}

def doorUnlockedHandler(evt) {
		log.debug "Lock ${lock1} was: ${evt.value}"

                if (evt.value == "lock") {                      // If the human locks the door then...
                        unschedule( lockDoor )                	// ...we don't need to lock it later. 	
                }                
		if (evt.value == "unlocked") {                  // If the human (or computer) unlocks the door then...
        	        log.debug "Locking in ${minutesLater} minutes (${delay}ms)"
		        def delay = minutesLater * 60           // runIn uses seconds so we don't need to multiple by 1000
                        runIn( delay, lockDoor)                 // ...schedule the door lock procedure to run x minutes later.
                }
}

If you run it like this then every time the hub sees the door locked it will unschedule the ‘lockDoor’ procedure. When the app see the door unlock it will reschedule the lock procedure in the specified minutes.

Now, having said this there is still the problem that the door lock is NOT updating manual state changes to the hub at this time. The Hub will poll the lock on a 5 minute rotation, but that’s a lot of extra time. Future firmware updates promise a much, much quicker update rotation (seconds vs. minutes), but that’s probably a couple of months away.

that is very sexy. can I throw it on the SmartThings-Users github repo (MIT license)?

@jnovak if you’re asking me, feel free. @chrisb did a good bit of work getting it to be awesomer, so can’t speak to his version.

Feel free to post it to the repo. Honestly, I’ve still very much a novice on the license front.

From what I can tell the MIT is basically public domain except that if someone reuses the software they have to say where it came from what the license is on it. Am I right on that?

In my opinion what I wrote above isn’t really all that novel and frankly is based in principle on what I’ve seen in other code in general, so I’m more of the opinion that it should just be public domain. @csader, why don’t you post the app with my (Public Domain) revisions in it and then @jnovack can post that in the repo until MIT with your name on it.

How’s this?

@chrisb, I removed the extra spaces in unschedule( lockDoor ) and runIn( delay, lockDoor). I’m assuming that’s not going to mess anything up?

/**
 *  Auto-Lock Doors v2
 *
 *  Author: Chris Sader (@csader)
 *  In collaboration with @chrisb
 */
 
preferences {
	section("When a door unlocks...") {
		input "lock1", "capability.lock"
	}
	section("Lock it how many minutes later?") {
		input "minutesLater", "number", title: "When?"
	}
}

def installed() {
	log.debug "Installed with settings: ${settings}"
	subscribe(lock1, "lock", doorUnlockedHandler, [filterEvents: false])
}

def updated() {
	log.debug "Updated with settings: ${settings}"

	unsubscribe()
	subscribe(lock1, "lock", doorUnlockedHandler, [filterEvents: false])
}

def lockDoor() {                                                // This process locks the door.
               lock1.lock()                                     // Don't need delay because the process is scheduled to run later
}

def doorUnlockedHandler(evt) {
		log.debug "Lock ${lock1} was: ${evt.value}"

                if (evt.value == "lock") {                      // If the human locks the door then...
                        unschedule(lockDoor)                	// ...we don't need to lock it later. 	
                }                
		if (evt.value == "unlocked") {                  		// If the human (or computer) unlocks the door then...
        	        log.debug "Locking in ${minutesLater} minutes (${delay}ms)"
		        def delay = minutesLater * 60          		   // runIn uses seconds so we don't need to multiple by 1000
                        runIn(delay, lockDoor)                 // ...schedule the door lock procedure to run x minutes later.
                }
}

Looks good to me.

Thank you, gentlemen (or ladies). I took the liberty of correcting, clarifying and rearranging for readability to novice coders.

@jnovack, seems you may have forgotten to change “Off Without Motion” to “Auto-Lock Door” when you copy/pasted the new header.

I gave the app a try (from the github source) and It seemed to work…but then I noticed it was sending spam locks to my door…basically sending a lock command every 5 minutes, even though it was locked.

Thanks for the feedback @coryds. I’m stumped. @chrisb or @jnovack, any thoughts why that would be happening?

I wonder if that’s due to the FilterEvents: False line. I’m never used that before so I’m not sure what that is.

According to https://smartthings.zendesk.com/entries/21603485-Event-Subscription-Methods:

Can also set "filterEvents:false", which will pass along all events for the device, including non state change events.

Then that’s the problem.

EVERY time that doorHandler gets called, it makes another runIn(5mins, lockDoor) call.

doorHandler should ONLY be called when there is action.

Try removing the filterEvents and let us know.

That’s my though too @jnovack but really the runIn should only be called if the evt == unlock. It really shouldn’t be scheduling to run if the door is locked.