Getting an event from a lock for ONLY when the lock is unlocked by a code

I think this may help. This originally was posted by @rvrolyk and I used it and updated it for my Schlage Lock and this combo works perfect.

https://community.hubitat.com/t/description-text-logging-actionable/17967/4?u=chipworkz

My updated code for my Schlage lock is below. I added the (evt.type == 'digital' && (evt.descriptionText.endsWith('unlocked [digital]') comments to track the difference between a manual and digital unlock event. I then use this Virtual Switch in my If logic in my rule. You will need to see how your Yale lock reports the "Digital" event to see if it is the same as the Schlage or not. I am not a developer, I was just able to tweak this to make it work for me.

/*
Installation Notes:
You must create a Virtual Switch for both the Virtual Lock and Virtual Unlock switches
even if you are not going to use one or the other. The app will not function correctly
if one of the switches are not created.

Schlage Lock Events
Lock name = Front Door Lock. (Varies per installation)

Locked using keypad Schlage button or code.
Lock event: lock : Front Door Lock was locked by keypad [physical]

Locked using inside dial.
Lock event: lock : Front Door Lock was locked by thumb turn [physical]

Locked using automation.
Lock event: lock : Front Door Lock was locked [digital]

Unlocked using code.
Unlock event: lock : Front Door Lock was unlocked by John

Unlocked using inside dial.
Unlock event: lock : Front Door Lock was unlocked by thumb turn [physical]

Unlocked using automation.
Unlock event: lock : Front Door Lock was unlocked [digital]
*/

definition(
name: "Manual Lock Events Monitor",
namespace: "chipworkz",
author: "chipworkz",
original author: "Russ Vrolyk",
description: "Tracks when a lock is manually controlled and updates the virtual switches",
category: "Convenience",
iconUrl: "",
iconX2Url: "",
iconX3Url: "")

def lock = [
name: "lock",
type: "capability.lock",
title: "Lock",
description: "Select the lock to monitor.",
required: true,
multiple: false
]

def virtualLockedSwitch = [
name: "virtualLockedSwitch",
type: "capability.switch",
title: "Virtual Lock Switch",
description: "Virtual switch that should be turned on when lock is manually locked if needed.",
required: true,
multiple: false
]

def virtualUnlockedSwitch = [
name: "virtualUnlockedSwitch",
type: "capability.switch",
title: "Virtual Unlock Switch",
description: "Virtual switch that should be turned on when lock is manually unlocked.",
required: true,
multiple: false
]

def enableLogging = [
name: "enableLogging",
type: "bool",
title: "Enable debug Logging?",
defaultValue: false,
required: true
]

preferences {
page(name: "mainPage", title: "Schlage Lock Events Monitor:", install: true, uninstall: true) {
section("") {
input lock
input virtualLockedSwitch
input virtualUnlockedSwitch
label title: "Assign an app name", required: false
}
section ("Advanced Settings") {
input enableLogging
}
}
}

def installed() {
log.info "Installed with settings: ${settings}"
initialize()
}

def updated() {
log.info "Updated with settings: ${settings}"
unsubscribe()
initialize()
}

def initialize() {
log "Lock status: ${lock.displayName} " + lockStatus()
subscribe(lock, "lock.locked", lockHandler)
subscribe(lock, "lock.unlocked", unlockHandler)
}

def lockStatus() {
return lock.currentValue("lock")
}

def lockHandler(evt) {
log("Lock event: ${evt.name} : ${evt.descriptionText}")
if (evt.type == 'physical' && (evt.descriptionText.endsWith('thumb turn [physical]') || evt.descriptionText.endsWith('keypad [physical]')) || (evt.type == 'digital' && (evt.descriptionText.endsWith('locked [digital]')))) {
log "${lock.displayName} was locked manually"
virtualLockedSwitch.on()
if (virtualUnlockedSwitch) {
virtualUnlockedSwitch.off()
}
} else {
log "${lock.displayName} was locked"
}
}

def unlockHandler(evt) {
log("Unlock event: ${evt.name} : ${evt.descriptionText}")
if (evt.type == 'physical' && evt.descriptionText.endsWith('thumb turn [physical]') || (evt.type == 'digital' && (evt.descriptionText.endsWith('unlocked [digital]')))) {
log "${lock.displayName} was unlocked manually"
virtualLockedSwitch.off()
if (virtualUnlockedSwitch) {
virtualUnlockedSwitch.on()
}
} else {
log "${lock.displayName} was unlocked"
}
}

def log(msg) {
if (enableLogging) {
log.debug msg
}
}

Ahhhh, you've got me much closer. Testing with this I'm seeing a "physical" unlock with a PIN and a "digital" unlock when it's manual. Does that jive with how yours is?

It seems that I can make this work. Is this different for different locks?

This seems to work for me:
image

That debug statement only get triggered if it's unlocked WITH a code. Seems weird that's called "physical" but....

I added my Schlage events to my code for reference. This is how mine reports the events. This is the only lock I have so I cannot say what Yale or other locks do.

1 Like

Thanks, this helped me get there!

HOWEVER... I will make note... even in your code:

subscribe(lock, "lock.unlocked", unlockHandler)

My handler is getting ALL events regardless of what I'm putting for the subscription. So in the example above.. I'm still seeing the lock events also. Something is amiss.

Yes my code is for both lock and unlock events. The virtual switch gets triggered and I am only using the unlock switch in my rule. I don't do anything with the locked virtual switch.

Lock Monitor2

update..spent some time tonight figuring this out...and finally got it.

I got the proper "unlocked" events now from the subscription. Not sure what I missed the first time but I AM only getting unlocked events now with this:

subscribe(lock, "lock.unlocked", lockHandler)

Second, I do NOT get an evt.type I'm guessing this is a driver thing...so that part needed to be removed. My if statement looks like this:

if (evt.descriptionText.endsWith('thumbturn [physical]') || evt.descriptionText.endsWith('command [digital]')) {
    infolog "$lock.displayName was unlocked manually"
} else {
    infolog "$lock.displayName was unlocked by CODE: $lastName"
    sendPushMessage.deviceNotification("$lock.displayName was unlocked by: $lastName")
}

Notice "thumbturn" in my case did NOT have a space like yours did. But updating this here for others in the future. Is there not a more standard way to handle these events for ALL locks? At least the digital/physical/thumbturn/code entered parts? Understand some locks will have different capabilities... either way...got it finally worked out now and FINALLY have a way to notify when a door gets unlocked ONLY by a code.

1 Like

More info for those that might come across this. @bptworld helped me and ultimately came up with code that hopefully covers all lock drivers...or at least most of them? Really think there needs to be more standardization there @mike.maxwell but...

here is the check that Bryan came up with that seems to be working for multiple lock types:

if (evt.descriptionText.contains("unlocked by")) { //lock was unlocked by code}

How the other built in apps are handling this I don't know...but I do know some of them don't work with HubConnect and this method will.

His event log(kwikset):

Mine(yale):

as you can see mine puts [physical] with a valid code, his does not. His doesn't use "thumbturn" it uses "manual" for someone turning the knob. I'm sure there's some other driver out there that uses "unlocked by manual" or "unlocked by thumbturn" or "unlocked by ..." FWIW you are going to have to test. No wonder why I've been having problems with Rule Machine and Notifications...

1 Like

the whole point of the lastCodeName event was so rules didn't have to parse through the description text.
description text is an optional attribute and its content is not defined.
physical/digital is also an optional attribute, and whilst we strive to make it accurate, it isn't always the case, devices in general do not make the distinction in the events they send to us, so we have to sort this out, usually with state flags ect...

the lastCodeName event should only be generated when a lock is unlocked by a pin code, is that not the case?

If not, let me know which lock drivers are in arrears...

That's the case. "Yale Zigbee Lock"

Subscribe to just the lastCodeName events but my simple routine with a debug log shows this:

This could REALLY explain why I've been having issues with RM and Notifications/etc.

ok, not sure I'm following the issue here.
This log shows in general display text if text logging is enabled in the driver, the actual events committed to the database are shown by clicking the events button in the driver...

Are you saying that unlocked by JR and Kyleigh are not generating lastCodeName events in the device events log?

Sorry let me better explain, first here are the actual events from the lock:

What I showed above was that I was writing an app. I wanted to only act on when an actual code was punched into the lock. No other events.

What am I missing about how to determine when ONLY the code has been entered? As you can see above in this thread I don't look alone in having to parse the description to get the desired result.

Again, I am NOT a developer but I think I figured this out. You need to subscribe to the event lastCodeName and not an event type of physical or digital.

def initialize() {
    log "Lock status: ${lock.displayName} " + lockStatus()
	subscribe(lock, "lastCodeName.Staci", unlockHandler2)
def unlockHandler2(evt) {
	log("Unlock event: ${evt.name} : ${evt.descriptionText}")
	if (evt.name == 'lastCodeName') {
		log "${lock.displayName} was unlocked by a Door Code"
		virtualLockedSwitch.off()
		if (virtualUnlockedSwitch) {
			virtualUnlockedSwitch.on()
		}
	} else {
		log "${lock.displayName} was unlocked"
	}
}

I am...but it's looking like it might be due to the HubConnect Lock driver not supporting that event.

Well this was a fun little learning experience. Sorry it isn't working for you but I will go ahead and post this here for anyone following this thread and wants to use this. I simplified my process and created the app specifically for the purpose of triggering for only when a code is used. I do not see a way with only Rule Machine to do this so it takes this extra app and virtual switch to be used within a rule. I know you could do everything within the app but I like to have the actions within a rule so I can do more with it if I want to.

Schalge Lock Reporter3

/*
Installation Notes:
You must create a Virtual Switch to be used in a rule so when it is on, you will perform an action.
The virtual switch should have the "Enable auto off" set to 5 seconds.

Schlage Lock Events for reference
Lock Name = "Front Door Lock" (Varies per installation)
Virtual Switch Name = "Front Door Code Used"  (Varies per installation)

Locked using keypad Schlage button or code.
Lock event: lock : Front Door Lock was locked by keypad [physical]

Locked using inside dial.
Lock event: lock : Front Door Lock was locked by thumb turn [physical]

Locked using automation.
Lock event: lock : Front Door Lock was locked [digital]

Unlocked using code.
Unlock event: lock : Front Door Lock was unlocked by John

Unlocked using inside dial.
Unlock event: lock : Front Door Lock was unlocked by thumb turn [physical]

Unlocked using automation.
Unlock event: lock : Front Door Lock was unlocked [digital]

Credit to @rvrolyk "Russ Vrolyk" for the original code this is based on.
*/


definition(
    name: "Schlage Lock User Code Reporter",
    namespace: "chipworkz",
    author: "chipworkz",
    description: "Triggers when a user code is used to unlock the door",
    category: "Convenience",
	iconUrl: "",
    iconX2Url: "",
    iconX3Url: "")


def lock = [
    name: "lock",
    type: "capability.lock",
    title: "Lock",
    description: "Select the lock to monitor.",
    required: true,
    multiple: false
]

def virtuaCodeUsedSwitch = [
    name: "virtuaCodeUsedSwitch",
    type: "capability.switch",
    title: "Virtual Code Used Switch",
    description: "Virtual switch that should be turned on when a code is used.",
    required: true,
    multiple: false
]

def enableLogging = [
    name:				"enableLogging",
    type:				"bool",
    title:				"Enable debug Logging?",
    defaultValue:		false,
    required:			true
]

preferences {
	page(name: "mainPage", title: "<b>Schlage Lock User Code Reporter:</b>", install: true, uninstall: true) {
		section("") {
			input lock
			input virtuaCodeUsedSwitch
		}
		section ("<b>Advanced Settings</b>") {
			input enableLogging
		}
	}
}

def installed() {
	log.info "Installed with settings: ${settings}"
	initialize()
}

def updated() {
	log.info "Updated with settings: ${settings}"
	unsubscribe()
	initialize()
}

def initialize() {
    log "Lock status: ${lock.displayName} " + lockStatus()
	subscribe(lock, "lastCodeName", unlockHandler)
}

def lockStatus() {
	return lock.currentValue("lock")
}

def unlockHandler(evt) {
	log("Unlock event: ${evt.descriptionText}")
	if (evt.name == 'lastCodeName') {
		virtuaCodeUsedSwitch.on()
		}
}

def log(msg) {
    if (enableLogging) {
        log.debug msg
    }
}

As a follow up...this does NOT work in RM either.

That rule NEVER gets triggered when entering any valid code on the lock.

working for me.. i have kwikset locks with zigbee..

Hi @mike.maxwell ,
I can confirm that with latest firmware Jan 2020, the driver for Schlage locks is not emitting the lastCodeName event. When locking/unlocking via the keypad, only lock events are emitted. The only way to disambiguate keypad lock/unlock is to parse the description text, which I agree is undesirable. How can I help provide more detailed debug information. Because I see the full event details in the device Event page on the hub, I thought this could be an issue with the application so I tried multiple: Rule Machine 4.0 and WebCore. It's unclear whether this Hub Portal page consumes events using the same interface that applications use.

Some notes:

  1. The events available to Rule Machine and WebCore are the same: battery, lock, lock code, lock codes. lastCodeName is not an option.

  2. I'll use the WebCore application and the simplest of piston to illustrate that from the lock event, I can determine what device sent the event ($currentEventDevice), whether it was a lock or unlock action ($currentEventValue), and the description text ($currentEventDescription). I simply listen for
    all lock events and log these variables.

    execute
    on events from
    Laundry Room Door's lock
    do
    with
    Location
    do
    Log info "Device: {$currentEventDevice} Value: {$currentEventValue} Description: {$currentEventDescription}";

         end with;
     end on;
    

    end execute;

  3. Interestingly, using the same logging of event details, when I listen for lock code or the plural version (lock codes) events, and log the event details, no lock/unlock actions performed on the device causes log output. These events either a) have no relation to lock/unlock activity, b) no events are emitted or c) there is a bug in WebCore for bubbling these specific events to pistons. I could try the same thing in Rule Machine, but I'm betting the result will be the same. In any case, from what I'm reading on this community thread, it seems clear these "lock code" and "lock codes" events are out of scope for our goal.

I'd be delighted to provide any further debug info. Thanks a ton for all you do to support the community! As a brand new Hubitat user coming off SmartThings, I'm looking forward to being a more active community participant.

I should have been more clear: there is no new regression in the latest firmware. I observed this same behavior on (n-1) firmware also.

Another possibility is I misunderstand your earlier question to this discussion related to lastCodeName event. I assumed you meant there is a unique event named lastCodeName, and this is a distinct event from battery, lock, etc. emitted by the device driver. If this isn't the case, and lastCodeName is a property of a different event that I can't find in my sleuthing.

You can create a virtual lock and test out if there are differences. My understanding is if a known code (named code) is used, you should get the event as you describe.

If a code that is not known is used, you will not receive the lastCodeName event update/change.

LastCodeName is a real event.
Are you seeing events for it in your device logs?