Zwave - Yale YRD220 Lock

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