Zwave - Yale YRD220 Lock

@chris - I was trying to edit the Yale YRD220 lock settings via the paper UI and ran into a few issues and wasn’t sure they were reported yet.

  • Parameter #2 (Auto Re-lock) is defined twice. The UI is showing the first as -1 and the second as blank. Based on how my lock is working, it should be showing as “On”.

  • The save icon isn’t enabled after editing several fields, it’s only triggered when I fill in the blank drop-down listed above. I’m assuming this is because there is input validation and the field is required?

  • If I fill in the parameter #2, then try to change the auto-lock time, the UI says it saves, but the change never happens. I also see the following error in the trace log:

    2017-09-13 22:28:59.725 [DEBUG] [ding.zwave.handler.ZWaveThingHandler] - NODE 20: Configuration update received
    2017-09-13 22:28:59.732 [DEBUG] [ding.zwave.handler.ZWaveThingHandler] - NODE 20: Configuration update usercode_code_139 to null
    2017-09-13 22:28:59.736 [ERROR] [ome.io.rest.core.thing.ThingResource] - Exception during HTTP PUT request for update config at 'things/zwave:device:15a076229ab:node20/config' for thing 'zwave:device:15a076229ab:node20': null

I have a trace log after this point if you’d like, but I didn’t see any obvious errors.

Lastly, is it possible to add another channel to get the raw alarm code (which I believe includes the user code)? I wasn’t clear from threads if that’s possible. We have the base alarm code which is great - thanks for adding that.

I’ve removed one from the database.

I guess this is a paper UI issue - not the binding.

Maybe it’s related to the duplication of parameters. Let’s fix that (I’ll look at doing an update tonight with the updated database) then if it continues, can you open the debugger in your browser and see what data is sent from the UI to OH (in the network tab in the debugger).

I will change the current definition to use the alarm_raw channel. This is quite different but gives all the information you need. It’s a json string though so it will need a little processing in a rule to get the data out. Is this ok?

@chris, Thanks! I’m fine with the switch to JSON if it gets more data. But say that as someone who hasn’t had to process JSON in a rule before :slight_smile:

@5iver might be able to give you an example :wink: .

Here is my current rule for parsing alarm_raw. My locks (Schlage BE469) do not update the lock_door channel, so I use alarm_raw to update the items linked to lock_door, and base rules off of that item’s state. Maybe all locks behave this way? You’ll find several examples of using the JSONPATH transform to parse the data in the lambda. This can be tricky though, because JSONPATH will throw an exception if the element name you are searching for does not exist in the JSON. I haven’t had any issues with the binding not providing the data I was expecting, but some try/catch blocks would be a good idea. You can find some detailed info on JSONPATH here.

This rule requires at least a OH 2.2 release build to utilize triggeringItem.

REQUIRED

MAP transformation service
JSONPATH transformation service

MAP (lock.map)

OFF=Unlocked
ON=Locked
undefined=Unknown
-=Unknown
NULL=Unknown

ITEMS

Group:Switch:OR(OFF,ON)    gLock    "Locks [MAP(lock.map):%s]"    <lock>
Group    gLockRaw    "Locks (raw) [%s]"    <lock>
Switch    Lock_EntranceFront    "Lock (Front Entrance) [MAP(lock.map):%s]"    <lock>    (gLock)    {channel="zwave:device:55555:node5:lock_door"}
String 	  Lock_EntranceFront_Alarm_Raw    "Lock (Front Entrance): Alarm Raw [%s]"    <shield>    (gLockRaw)    {channel="zwave:device:55555:node5:alarm_raw"}
Switch 	  Lock_GarageAttached_Inner    "Lock (Front Entrance) [MAP(lock.map):%s]"    <lock>    (gLock)    {channel="zwave:device:55555:node5:lock_door"}
String 	  Lock_GarageAttached_Inner_Alarm_Raw    "Lock (Front Entrance): Alarm Raw [%s]"    <shield>    (gLockRaw)    {channel="zwave:device:55555:node5:alarm_raw"}
Switch 	  Lock_GarageAttached_Outer    "Lock (Front Entrance) [MAP(lock.map):%s]"    <lock>    (gLock)    {channel="zwave:device:55555:node5:lock_door"}
String 	  Lock_GarageAttached_Outer_Alarm_Raw    "Lock (Front Entrance): Alarm Raw [%s]"    <shield>    (gLockRaw)    {channel="zwave:device:55555:node5:alarm_raw"}

Specifically, all of the Switch items need to be in gLock, and if the Switch item is named XXXXX then the String item should be named XXXXX_Alarm_Raw.

RULE

// Imports need to be at the very top of the rule file
import org.eclipse.smarthome.model.script.ScriptServiceUtil

rule "Lock: Update lock states after alarm_raw event"
when
    Member of gLockRaw received update
then
    val actionItem = ScriptServiceUtil.getItemRegistry.getItem(triggeringItem.name.toString.replace("_Alarm_Raw",""))
    logInfo("Rules", "Lock: Alarm events: {}=[{}]",actionItem.name,triggeringItem.state.toString)
    val StringBuilder message = new StringBuilder(actionItem.name)
    switch (transform("JSONPATH","$.type",triggeringItem.state.toString)) {
        case "ACCESS_CONTROL" : {
            switch (transform("JSONPATH", "$.event", triggeringItem.state.toString)) {
                case "1", case "3", case "5" : {
                    actionItem.postUpdate(ON)
                    message.append(" updated to ON (locked)")
                }
                case "2", case "4" : {
                    actionItem.postUpdate(OFF)
                    message.append(" updated to OFF (unlocked)")
                }
                case "6" : {
                    actionItem.postUpdate(OFF)
                    message.append(" was unlocked with")
                    switch (transform("JSONPATH", "$.code", triggeringItem.state.toString)) {
                        case "1" : {
                            message.append(" Scott's code")
                        }
                        case "2" : {
                            message.append(" Lisa's code")
                        }
                    }
                }
                case "11" : {
                    actionItem.postUpdate(OFF)// jammed could mean unlocked, so I do this for safety... you may want to send an alert here too
                    message.append(" is jammed, so setting lock to OFF (unlocked)")
                }
                case "16" : {
                    message.append(" keypad is disabled due to too many failed codes")
                }
                default : {
                    message.append(" unknown door lock Event, ").append(triggeringItem.state.toString)
                }
            }
            if (transform("JSONPATH", "$.event", triggeringItem.previousState(true).state.toString) == "11" && transform("JSONPATH", "$.event", triggeringItem.state.toString) != "11") {
                message.append(" and is no longer jammed")
            }
        }
        case "BURGLAR" : {
            //gSiren.sendCommand(ON)
            message.append(" has detected an intruder")
        }
        case "POWER_MANAGEMENT" : {
            message.append(" received a Power Management alarm, ").append(triggeringItem.state.toString)
        }
        default : {
            message.append(" received and unknown Type in alarmRawParser, ").append(triggeringItem.state.toString)
        }
    }
    logInfo("Rules", "Lock: Alarm events: {}", message.toString)
end

In case anyone is interested in a Jython version…

import json
from time import sleep

from core.rules import rule
from core.triggers import when
from core.actions import PersistenceExtensions, NotificationAction
#from personal.utils import Notification

@rule("Lock: Update lock state")
@when("Member of gLockRaw received update")
def updateLockState(event):
    #updateLockState.log.debug("Update lock states after alarm_raw event: {}: [{}]: start".format(event.itemName,event.itemState))
    actionItem = ir.getItem(event.itemName.replace("_Alarm_Raw",""))
    message = ""
    rawType = json.loads(str(event.itemState))['type']
    if rawType == "ACCESS_CONTROL":
        rawEvent = json.loads(str(event.itemState))['event']
        if rawEvent in ["1", "3", "5"]:# locked
            events.postUpdate(actionItem.name, "ON")
            updateLockState.log.debug("Update lock states after alarm_raw event: {} updated to ON (locked)".format(actionItem.label))
        elif rawEvent in ["2", "4"]:# unlocked
            events.postUpdate(actionItem.name, "OFF")
            updateLockState.log.debug("Update lock states after alarm_raw event: {} updated to OFF (unlocked)".format(actionItem.label))
        elif rawEvent == "6":# unlocked with code
            message = "{} was unlocked with".format(actionItem.label)
            rawCode = json.loads(str(event.itemState))['code']
            if rawCode == "1":
                message = "{} Scott's code".format(message)
                '''
                # this block of code is used as a fallback in case presence detection (OwnTracks) hasn't already updated to avoid alarms)
                if items["Scott_Region"] != StringType("Home"):
                    events.sendCommand("Scott_Region", "Home")
                    message = "{} and Presence state was updated".format(message)
                '''
            elif rawCode == "2":
                message = "{} Lisa's code".format(message)
                '''
                # this block of code is used as a fallback in case presence (OwnTracks) detection hasn't already updated to avoid alarms)
                if items["Lisa_Region"] != StringType("Home"):
                    events.sendCommand("Lisa_Region", "Home")
                    message = "{} and Presence state was updated".format(message)
                '''
            #sleep(0.1)# wait for Presence to catch up
            events.postUpdate(actionItem.name, "OFF")
        elif rawEvent == "11":
            events.postUpdate(actionItem.name, "OFF")# jammed could mean unlocked, so I do this for safety... you may want to send an alert here too
            message = "{} is jammed".format(actionItem.label)
        elif rawEvent == "16":
            message = "{} keypad is disabled due to too many failed attempts".format(message)
        else:
            message = "Unknown lock event: {}: {}".format(actionItem.label,event.itemState)
        if rawEvent != "11" and json.loads(str(PersistenceExtensions.previousState(ir.getItem(event.itemName),True).state))['event'] == StringType("11"):
            message = "{} and is no longer jammed".format(message)
    elif rawType == "BURGLAR":
        message = "Intruder at {}".format(actionItem.label)
    elif rawType == "POWER_MANAGEMENT":
        message = "Power Management alarm for {}: {}".format(actionItem.label,json.loads(str(event.itemState))['notification'].replace("POWER_MANAGEMENT__","").replace("_"," ").capitalize())
    else:
        message = "Unknown Type in alarmRawParser: {}: {}".format(event.itemName,event.itemState)
    updateLockState.log.debug("Update lock states after alarm_raw event: {}".format(message))    
    Notification("Update lock states",message)# this is a function I use for delivering audio, android and Kodi notifications based on presence

This is a list of alarm_raw events that I have seen from a BE469…

{"notification":"ACCESS__MANUAL_LOCK",                          "type":"ACCESS_CONTROL","event":"1","status":"255"}
{"notification":"ACCESS__MANUAL_UNLOCK",                        "type":"ACCESS_CONTROL","event":"2","status":"255"}
unlocked with zwave? event 3
locked with zwave? event 4
{"notification":"ACCESS__KEYPAD_LOCK",  "code":"1",             "type":"ACCESS_CONTROL","event":"5","status":"255"}
{"notification":"ACCESS__KEYPAD_UNLOCK","code":"1",             "type":"ACCESS_CONTROL","event":"6","status":"255"}
{"notification":"ACCESS__LOCK_JAMMED",                          "type":"ACCESS_CONTROL","event":"11","status":"255"}
{"notification":"ACCESS__KEYPAD_LOCK",                          "type":"ACCESS_CONTROL","event":"5","status":"255"}
{"notification":"ACCESS_CONTROL__KEYPAD_TEMPORARILY_DISABLED",  "type":"ACCESS_CONTROL","event":"16","status":"255"}
{"notification":"BURGLAR__TAMPER_UNKNOWN",                      "type":"BURGLAR","event":"2","status":"255"}
{"notification":"HOME_SECURITY__INTRUSION_UNKNOWN",             "type":"BURGLAR","event":"2","status":"255"}
{"notification":"POWER__REPLACE_BATTERY_SOON",                  "type":"POWER_MANAGEMENT","event":"10","status":"255"}
{"notification":"POWER_MANAGEMENT__REPLACE_BATTERY_SOON",       "type":"POWER_MANAGEMENT","event":"10","status":"255"}

[EDIT: updated lambda to include too many incorrect keypad entries]
[EDIT: refactor to use single item]
[EDIT: removed lambda]
{EDIT: added gLockRaw, ‘Member of’ trigger, ScriptServiceUtil method of finding Item, better logging, cleaned up coments, and added Jython version

1 Like

@5iver - Thanks for the detailed response – I’ll give that a shot

@chris, Thanks for building the new binding version. I installed it, and still get a 500 error when attempting to save. The error is on a put call to /rest/things/zwave:device:15a076229ab:node20/config

The payload is as follows: https://pastebin.com/PsWKRzqT (User codes redacted of course). I notice keys with apparently negative numbers in them, is that correct?

I should also note that the value for config parameter #2 still doesn’t load correctly - not sure if that’s related.

I thought I’d fixed the negative number issue.

Try to edit the XML. Look in the COMMAND_CLASS_USER_CODE command class and change numberOfUsersSupported to a small number (eg 10 or 20). See if that helps for starters…

Just edit the xml file in userdata/zwave? Do I have to clear cache or something? I tried changing the number of users to 20 and while the UI only shows 20, the HTTP request is trying to set the full amount. I also noticed the XML has negative user code numbers in it. I edited the file to remove but no luck.

I did set the value through the REST API manually it works (although it’s locking after about 180 seconds instead of the 255 so I’m not sure what that’s about).

Sorry - I should have been clearer. You can only edit the XML when the binding is not running, so you need to stop if, then change the above users supported variable, then restart the binding. This won’t solve all the current invalid codes - you could also delete them from the XML at the same time (maybe just delete ALL codes in the XML - they will be regenerated on startup, and it will only request 20 codes if you set the above to 20). I’ll make a quick fix to see if I can stop that as well.

I’m not sure if this is why you get the errors when saving of course…

This was fixed previously, but somehow crept back in. I’ve now added a test to catch it if it changes again!

I’ve been experimenting and trying to get the above Rule to work with my Kwikset lock. I’m not certain if its differences in the lock or perhaps me just not fully understanding the rule.

First my alarm raw only shows a Type and Value, and does not show an “ACCESS_CONTROL” Again, perhaps I’m making it more complicated then it should be. For my lock a Type 19, Value 1 would meant I entered Code 1 on the keypad, and I’m trying to send an notification to my phone when it unlocks.

Currently I’m getting this error in my logs when attempting

16:48:03.674 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Lock: Update lock states after alarm_raw event (Lock_EntranceFront)': Could not invoke method: org.eclipse.smarthome.core.items.GenericItem.getState() on instance: {"type":"21","value":"1"}
2017-11-09 16:48:29.199 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Lock: Update lock states after alarm_raw event (Lock_EntranceFront)': Could not invoke method: org.eclipse.smarthome.core.items.GenericItem.getState() on instance: {"type":"19","value":"1"}
2017-11-09 16:48:36.139 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Lock: Update lock states after alarm_raw event (Lock_EntranceFront)': Could not invoke method: org.eclipse.smarthome.core.items.GenericItem.getState() on instance: {"type":"21","value":"1"}

I did try to modify the rule eliminating ACCESS Control, and changing $.event to $.type where I thought was correct as following, but still get the same error message in the logs.

Which version of OH are you running and what model of Kwikset lock?

I’m on Snapshop/Build 1073 and have the KwikSet 916 Deadbolt. However the OH2 binding see’s it has the 914TRL, although I don’t think that makes a difference as I can control everything. here’s a screenshot of the lock info.

FYI, Type 21, Value 1 is locking the door from the inside on this lock.

I’ve further stripped the rule down to this, but still keep getting the same error. I wonder if its the rule, the import at the top or something else?

import org.eclipse.xtext.xbase.lib.Functions

val Functions$Function2<GenericItem, GenericItem, Boolean> alarmRawParser = [
    lockAlarmRaw,
    lockItem |
    
    logDebug("Rules", "Lock: Alarm events: {}=[{}]",lockItem.getName,lockAlarmRaw.state.toString)
            switch (transform("JSONPATH", "$.type", lockAlarmRaw.state.toString)) {
                case "21" : {
                    lockItem.postUpdate(ON)
                    logDebug("Rules", "Lock: Alarm events: {} updated to ON (locked)",lockItem.getName)
                    sendNotification("xxxxxx@gmail.com", "Front Door Unlocked.")
                }
                case "22" : {
                    lockItem.postUpdate(OFF)
                    logDebug("Rules", "Lock: Alarm events: {} updated to OFF (unlocked)",lockItem.getName)
                    sendNotification("xxxxx@gmail.com", "Front Door locked.")
                }
                case "19" : {
                    val StringBuilder presenceMessage = new StringBuilder
                    switch (transform("JSONPATH", "$.value", lockAlarmRaw.state.toString)) {
                        case "1" : {
                            sendNotification("xxxxx@gmail.com", "Front Door Unlocked.")
                        }
                        case "2" : {
                            sendNotification("xxxxxx@gmail.com", "Front Door Unlocked.")
                        }
                    }
                    lockItem.postUpdate(OFF)
                    logDebug("Rules", "Lock: {}",presenceMessage.toString)
                    //SMS_Notification.sendCommand(presenceMessage.toString)
                }
            }
    true
]

rule "Lock: Update lock states after alarm_raw event (Lock_EntranceFront)"
when
	Item Lock_FrontDoor_Alarm_Raw received update
then
    alarmRawParser.apply(Lock_FrontDoor_Alarm_Raw.state.toString,Lock_FrontDoor,"Lock (Front Entrance)")
end

The Schlage BE469 (which is what I have) and the Yale YRD220 do not automatically report the lock position, which is a big reason for these rules/lambda. In the big zwave thread, you mentioned that your lock reports the state of the lock all on it’s own, so you probably just need a rule for acting on user code entries. You could take out the lines with postUpdate in them, and you probably don’t need to act on type 21 or 22. The database entry on Chris’ site has some other codes you might want to act on though. Your lock also only supports Alarm V1, so your alarm messages are a little different (just type and value, based on this commit).

Did you install the JSONPath Transformation service? That import is required in order to use lambdas, and should be placed at the very top of the file. You can also strip out the three lines with presenceMessage in them.

Yes, the JSON Transformation is installed, and I have the import as the first line in the file. I’ve also stripped out the presenceMessage. You may notice I removed the “ACCESS_CONTROL” and Burglar Case stuff, not sure if that apply’s to my lock or not. And just have the JSONPATH looking at $.type and $.value.

And yea, I had thought perhaps my lock was always reporting back the state, but that does appear to be the case. I remember with WINK, their app somehow got the status update, probably also using the raw alarm info.

So currently, I can control the lock, but anything do manually, either with the keypad or manually turning the deadbolt does not update to OH2 yet.

I’ll get there :slight_smile: so much learning to do!

Latest, stripped down version of the rule and still getting same error

import org.eclipse.xtext.xbase.lib.Functions

val Functions$Function2<GenericItem, GenericItem, Boolean> alarmRawParser = [
    lockAlarmRaw,
    lockItem |
    
    logDebug("Rules", "Lock: Alarm events: {}=[{}]",lockItem.getName,lockAlarmRaw.state.toString)
            switch (transform("JSONPATH", "$.type", lockAlarmRaw.state.toString)) {
                case "21" : {
                    //lockItem.postUpdate(ON)
                    logDebug("Rules", "Lock: Alarm events: {} updated to ON (locked)",lockItem.getName)
                    sendNotification("xxxx@gmail.com", "Front Door Unlocked.")
                }
                case "22" : {
                    //lockItem.postUpdate(OFF)
                    logDebug("Rules", "Lock: Alarm events: {} updated to OFF (unlocked)",lockItem.getName)
                    sendNotification("xxxx@gmail.com", "Front Door locked.")
                }
                case "19" : {
                    switch (transform("JSONPATH", "$.value", lockAlarmRaw.state.toString)) {
                        case "1" : {
                            sendNotification("xxx@gmail.com", "Front Door Unlocked.")
                        }
                        case "2" : {
                            sendNotification("xxx@gmail.com", "Front Door Unlocked.")
                        }
                    }
                }
            }
    true
]

rule "Lock: Update lock states after alarm_raw event (Lock_EntranceFront)"
when
	Item Lock_FrontDoor_Alarm_Raw received update
then
    alarmRawParser.apply(Lock_FrontDoor_Alarm_Raw.state.toString,Lock_FrontDoor,"Lock (Front Entrance)")
end

Hi Paul. I don’t have a Z-Wave lock myself, but from an inspection of your rules, which appear to be a pretty straightforward copy of @5iver’s rules from post 5 of this thread, you are supplying a string object as the first argument to the alarmRawParser lambda, but the definition of that lambda declares the first argument as an object of type GenericItem. That, I think, is the cause of the error you reported.

In your rule “Lock: Update lock states after alarm_raw event (Lock_EntranceFront)”, you should change:

alarmRawParser.apply(Lock_FrontDoor_Alarm_Raw.state.toString,Lock_FrontDoor,"Lock (Front Entrance)")

to:

alarmRawParser.apply(Lock_FrontDoor_Alarm_Raw,Lock_FrontDoor,"Lock (Front Entrance)")

With that change I think the error will be gone.

1 Like

Great catch on that, and now its working. I’ve getting a Notifications now on my phone when the door is unlocked and by who, as well when it is locked.

I’ll probably add a separate rule now to send notice if the door is unlocked after a certain PM night time as well.

That’s embarrassing… when I edited the lambda, I forgot to update the rule! I’ve corrected it now. Thank you!

Sorry for the headache @ptmuldoon!