From e0e68cebed934e739dccd44e2144929a6a92bbd3 Mon Sep 17 00:00:00 2001 From: Mi Casa Date: Mon, 26 Jun 2017 21:56:30 -0700 Subject: [PATCH] MSA-2063: My Device --- .../zwave-schlage-touchscreen-lock.groovy | 1417 +++++++++++++++++ 1 file changed, 1417 insertions(+) create mode 100644 devicetypes/garyd9/zwave-schlage-touchscreen-lock.src/zwave-schlage-touchscreen-lock.groovy diff --git a/devicetypes/garyd9/zwave-schlage-touchscreen-lock.src/zwave-schlage-touchscreen-lock.groovy b/devicetypes/garyd9/zwave-schlage-touchscreen-lock.src/zwave-schlage-touchscreen-lock.groovy new file mode 100644 index 0000000..d296d16 --- /dev/null +++ b/devicetypes/garyd9/zwave-schlage-touchscreen-lock.src/zwave-schlage-touchscreen-lock.groovy @@ -0,0 +1,1417 @@ +/** + * + * INSTRUCTIONS: If you scroll down a couple pages, you should find a line that looks like: + * main "toggle" + * and that followed by a line that STARTS with: + * details(["toggle", + * If you want to change the items that are available on the details page of the device (from 'things'), + * you should edit the "details" line to include whatever items you want to see (along with the order + * you want to see them in.) There's a sample "details" line commented out (starts with //) below the + * first one. That sample enables all (or mostly all) the possible items. + * + * After the first time you have the device type installed (or after you've changed it), you might have + * to forcibly terminate the mobile app before the new/changed stuff will show up. (Most mobile apps + * don't actually terminate when you exit them. How to terminate an app depends on your mobile OS.) + * + * If a toggle is showing up as "loading..." on the UI (and you haven't recently changed it), tap the + * tile and it should reload the status within 10 seconds. + * 2015-08-21 : refactor everything to bring in most of the updates from ST's base z-wave lock type. Ensure + * its compatible with Erik Thayer's "lock code manager" smart app. (https://community.smartthings.com/t/lock-code-manager/12280) + * 2015-03-07 : When the lock is locked/unlocked automatically, from the keypad, or manually, include that + * information in the map.data, usedCode. (0 for keypad, "manual" for manually, and "auto" for automatic) + * 2015-02-02 : changed state values to prevent UI confusion. (Previously, when setting one item to 'unknown', + * the UI might show ALL the items as 'unknown'.) Also added beeper toggle. + * + * This is a modification of work originally copyrighted by "SmartThings." All modifications to their work + * is released under the following terms: + * + * The original licensing applies, with the following exceptions: + * 1. These modifications may NOT be used without freely distributing all these modifications freely + * and without limitation, in source form. The distribution may be met with a link to source code + * with these modifications. + * 2. These modifications may NOT be used, directly or indirectly, for the purpose of any type of + * monetary gain. These modifications may not be used in a larger entity which is being sold, + * leased, or anything other than freely given. + * 3. To clarify 1 and 2 above, if you use these modifications, it must be a free project, and + * available to anyone with "no strings attached." (You may require a free registration on + * a free website or portal in order to distribute the modifications.) + * 4. The above listed exceptions to the original licensing do not apply to the holder of the + * copyright of the original work. The original copyright holder can use the modifications + * to hopefully improve their original work. In that event, this author transfers all claim + * and ownership of the modifications to "SmartThings." + * + * Original Copyright information: + * + * Copyright 2014 SmartThings + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License + * for the specific language governing permissions and limitations under the License. + * + */ + +metadata +{ + // Automatically generated. Make future change here. + definition (name: "Z-Wave Schlage Touchscreen Lock", namespace: "garyd9", author: "Gary D") + { + capability "Actuator" + capability "Lock" + capability "Polling" + capability "Refresh" + capability "Sensor" + capability "Lock Codes" + capability "Battery" + + attribute "beeperMode", "string" + attribute "vacationMode", "string" // "on", "off", "unknown" + attribute "lockLeave", "string" // "on", "off", "unknown" + attribute "alarmMode", "string" // "unknown", "Off", "Alert", "Tamper", "Kick" + attribute "alarmSensitivity", "number" // 0 is unknown, otherwise 1-5 scaled to 1-99 + attribute "localControl", "string" // "on", "off", "unknown" + attribute "autoLock", "string" // "on", "off", "unknown" + attribute "pinLength", "number" + + command "unlockwtimeout" + + command "setBeeperMode" + command "setVacationMode" + command "setLockLeave" + command "setAlarmMode" + command "setAlarmSensitivity" + command "setLocalControl" + command "setAutoLock" + command "setPinLength" + + fingerprint deviceId: "0x4003", inClusters: "0x98" + fingerprint deviceId: "0x4004", inClusters: "0x98" + } + + simulator { + status "locked": "command: 9881, payload: 00 62 03 FF 00 00 FE FE" + status "unlocked": "command: 9881, payload: 00 62 03 00 00 00 FE FE" + + reply "9881006201FF,delay 4200,9881006202": "command: 9881, payload: 00 62 03 FF 00 00 FE FE" + reply "988100620100,delay 4200,9881006202": "command: 9881, payload: 00 62 03 00 00 00 FE FE" + } + + tiles(scale: 2) { + multiAttributeTile(name:"toggle", type: "generic", width: 6, height: 4){ + tileAttribute ("device.lock", key: "PRIMARY_CONTROL") { + + attributeState "locked", label:'locked', action:"lock.unlock", icon:"st.locks.lock.locked", backgroundColor:"#79b821", nextState:"unlocking" + attributeState "unlocked", label:'unlocked', action:"lock.lock", icon:"st.locks.lock.unlocked", backgroundColor:"#ff0000", nextState:"locking" + attributeState "unknown", label:"unknown", action:"lock.lock", icon:"st.locks.lock.unknown", backgroundColor:"#ffffff", nextState:"locking" + attributeState "locking", label:'locking', icon:"st.locks.lock.locked", backgroundColor:"#79b821" + attributeState "unlocking", label:'unlocking', icon:"st.locks.lock.unlocked", backgroundColor:"#ffffff" + } + } + //standardTile("lock", "device.lock", inactiveLabel: false, decoration: "flat") + //{ + // state "default", label:'lock', action:"lock.lock", icon:"st.locks.lock.locked", nextState:"locking" + //} + //standardTile("unlock", "device.lock", inactiveLabel: false, decoration: "flat") + //{ + // state "default", label:'unlock', action:"lock.unlock", icon:"st.locks.lock.unlocked", nextState:"unlocking" + // } + valueTile("battery", "device.battery", inactiveLabel: false, canChangeBackground: true, width: 2, height: 2) { + state "battery", label:'${currentValue}% Battery', unit:"", + backgroundColors:[ + [value: 19, color: "#BC2323"], + [value: 20, color: "#D04E00"], + [value: 30, color: "#D04E00"], + [value: 40, color: "#DAC400"], + [value: 41, color: "#79b821"] + ] + } + standardTile("refresh", "device.lock", inactiveLabel: false, decoration: "flat", width: 2, height: 2) + { + state "default", label:' ', action:"refresh.refresh", icon:"st.secondary.refresh" + } + standardTile("alarmMode", "device.alarmMode", inactiveLabel: true, canChangeIcon: false, decoration: "flat", width: 2, height: 2) + { + state "unknown_alarmMode", label: 'Alarm Mode\nLoading...', icon:"st.unknown.unknown.unknown", action:"setAlarmMode", nextState:"unknown_alarmMode" + state "Off_alarmMode", label: 'Alarm: Off', icon:"st.alarm.beep.beep", action:"setAlarmMode", nextState:"unknown_alarmMode" + state "Alert_alarmMode", label: 'Alert', icon:"st.alarm.beep.beep", action:"setAlarmMode", backgroundColor:"#79b821", nextState:"unknown_alarmMode" + state "Tamper_alarmMode", label: 'Tamper', icon:"st.alarm.beep.beep", action:"setAlarmMode", backgroundColor:"#79b821", nextState:"unknown_alarmMode" + state "Kick_alarmMode", label: 'Kick', icon:"st.alarm.beep.beep", action:"setAlarmMode", backgroundColor:"#79b821", nextState:"unknown_alarmMode" + } + controlTile("alarmSensitivity", "device.alarmSensitivity", "slider", height: 2, width: 2, inactiveLabel: false, range:"(1..5)") + { + state "alarmSensitivity", label:'Sensitivity', action:"setAlarmSensitivity", backgroundColor:"#ff0000" + } + standardTile("autoLock", "device.autoLock", inactiveLabel: true, canChangeIcon: false, decoration: "flat", width: 2, height: 2) + { + state "unknown_autoLock", label: 'Auto Lock\nLoading...', icon:"st.unknown.unknown.unknown", action:"setAutoLock", nextState:"unknown_autoLock" + state "off_autoLock", label: 'Auto Lock', icon:"st.presence.house.unlocked", action:"setAutoLock", nextState:"unknown_autoLock" + state "on_autoLock", label: 'Auto Lock ENABLED', icon:"st.presence.house.secured", action:"setAutoLock", nextState:"unknown_autoLock" + } + + // not included in details + + standardTile("vacationMode", "device.vacationMode", inactiveLabel: true, canChangeIcon: false, decoration: "flat", width: 2, height: 2) + { + state "unknown_vacationMode", label: 'Vacation\nLoading...', icon:"st.unknown.unknown.unknown", action:"setVacationMode", nextState:"unknown_vacationMode" + state "off_vacationMode", label: 'Vacation', icon:"st.Health & Wellness.health2", action:"setVacationMode", nextState:"unknown_vacationMode" + state "on_vacationMode", label: 'Vacation', icon:"st.Health & Wellness.health2", action:"setVacationMode", nextState:"unknown_vacationMode" + } + + // not included in details + + standardTile("lockLeave", "device.lockLeave", inactiveLabel: true, canChangeIcon: false, decoration: "flat", width: 2, height: 2) + { + state "unknown_lockLeave", label: 'Lock & Leave\nLoading...', icon:"st.unknown.unknown.unknown", action:"setLockLeave", nextState:"unknown_lockLeave" + state "off_lockLeave", label: 'Lock & Leave', icon:"st.Health & Wellness.health12", action:"setLockLeave", nextState:"unknown_lockLeave" + state "on_lockLeave", label: 'Lock & Leave ENABLED', icon:"st.Health & Wellness.health12", action:"setLockLeave", nextState:"unknown_lockLeave" + } + + // not included in details + + standardTile("localControl", "device.localControl", inactiveLabel: true, canChangeIcon: false) + { + state "unknown_localControl", label: 'Local Ctrl\nLoading...', icon:"st.unknown.unknown.unknown", action:"setLocalControl", nextState:"unknown_localControl" + state "off_localControl", label: 'Local Ctrl', icon:"st.Home.home3", action:"setLocalControl", nextState:"unknown_localControl" + state "on_localControl", label: 'Local Ctrl', icon:"st.Home.home3", action:"setLocalControl", backgroundColor:"#79b821", nextState:"unknown_localControl" + } + + + // not included in details + + standardTile("beeperMode", "device.beeperMode", inactiveLabel: true, canChangeIcon: false) + { + state "unknown_beeperMode", label: 'Beeper\nLoading...', icon:"st.unknown.unknown.unknown", action:"setBeeperMode", nextState:"unknown_beeperMode" + state "off_beeperMode", label: 'Beeper', icon:"st.unknown.unknown.unknown", action:"setBeeperMode", nextState:"unknown_beeperMode" + state "on_beeperMode", label: 'Beeper', icon:"st.unknown.unknown.unknown", action:"setBeeperMode", backgroundColor:"#79b821", nextState:"unknown_beeperMode" + } + + + main "toggle" + details(["toggle", "lock", "unlock", "alarmMode", "alarmSensitivity", "battery", "autoLock", "lockLeave", "refresh"]) +// details(["toggle", "lock", "unlock", "alarmMode", "alarmSensitivity", "battery", "autoLock", "lockLeave", "vacationMode", "beeperMode", "refresh"]) + } +} + +import physicalgraph.zwave.commands.doorlockv1.* +import physicalgraph.zwave.commands.usercodev1.* + +def parse(String description) +{ + def result = null + if (description.startsWith("Err")) + { + if (state.sec) + { + result = createEvent(descriptionText:description, displayed:false) + } + else + { + result = createEvent( + descriptionText: "This lock failed to complete the network security key exchange. If you are unable to control it via SmartThings, you must remove it from your network and add it again.", + eventType: "ALERT", + name: "secureInclusion", + value: "failed", + displayed: true, + ) + } + } + else + { + def cmd = zwave.parse(description, [ 0x98: 1, 0x72: 2, 0x85: 2, 0x86: 1 ]) + if (cmd) + { + result = zwaveEvent(cmd) + } + } + log.debug "\"$description\" parsed to ${result.inspect()}" + result +} + +def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) +{ + def encapsulatedCommand = cmd.encapsulatedCommand([0x62: 1, 0x71: 2, 0x80: 1, 0x85: 2, 0x63: 1, 0x98: 1, 0x86: 1]) + // log.debug "encapsulated: $encapsulatedCommand" + if (encapsulatedCommand) + { + zwaveEvent(encapsulatedCommand) + } +} + +def zwaveEvent(physicalgraph.zwave.commands.securityv1.NetworkKeyVerify cmd) { + createEvent(name:"secureInclusion", value:"success", descriptionText:"Secure inclusion was successful") +} + +def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityCommandsSupportedReport cmd) +{ + state.sec = cmd.commandClassSupport.collect { String.format("%02X ", it) }.join() + if (cmd.commandClassControl) + { + state.secCon = cmd.commandClassControl.collect { String.format("%02X ", it) }.join() + } + log.debug "Security command classes: $state.sec" + createEvent(name:"secureInclusion", value:"success", descriptionText:"Lock is securely included") +} + +def zwaveEvent(DoorLockOperationReport cmd) +{ + def result = [] + def map = [ name: "lock" ] + if (cmd.doorLockMode == 0xFF) + { + map.value = "locked" + } + else if (cmd.doorLockMode >= 0x40) + { + map.value = "unknown" + } + else if (cmd.doorLockMode & 1) + { + map.value = "unlocked with timeout" + } + else + { + map.value = "unlocked" + if (state.assoc != zwaveHubNodeId) + { + log.debug "setting association" + result << response(secure(zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:zwaveHubNodeId))) + result << response(zwave.associationV1.associationSet(groupingIdentifier:2, nodeId:zwaveHubNodeId)) + result << response(secure(zwave.associationV1.associationGet(groupingIdentifier:1))) + } + } + result ? [createEvent(map), *result] : createEvent(map) +} + +def zwaveEvent(physicalgraph.zwave.commands.alarmv2.AlarmReport cmd) +{ + def result = [] + def map = null + if (cmd.zwaveAlarmType == 6) // ZWAVE_ALARM_TYPE_ACCESS_CONTROL + { + if (1 <= cmd.zwaveAlarmEvent && cmd.zwaveAlarmEvent < 10) + { + map = [ name: "lock", value: (cmd.zwaveAlarmEvent & 1) ? "locked" : "unlocked" ] + } + switch(cmd.zwaveAlarmEvent) + { + case 1: + map.descriptionText = "$device.displayName was manually locked" + map.data = [ usedCode: "manual" ] + break + case 2: + map.descriptionText = "$device.displayName was manually unlocked" + map.data = [ usedCode: "manual" ] + break + case 5: + if (cmd.eventParameter) + { + map.descriptionText = "$device.displayName was locked with code ${cmd.eventParameter.first()}" + map.data = [ usedCode: cmd.eventParameter[0] ] + } + else + { + map.descriptionText = "$device.displayName was locked with keypad" + map.data = [ usedCode: -1 ] + } + break + case 6: + if (cmd.eventParameter) + { + map.descriptionText = "$device.displayName was unlocked with code ${cmd.eventParameter.first()}" + map.data = [ usedCode: cmd.eventParameter[0] ] + } + else + { + map.descriptionText = "$device.displayName was unlocked with keypad" + map.data = [ usedCode: -1 ] + } + break + case 9: + map.descriptionText = "$device.displayName was autolocked" + map.data = [ usedCode: "auto" ] + break + case 7: + case 8: + case 0xA: + map = [ name: "lock", value: "unknown", descriptionText: "$device.displayName was not locked fully" ] + break + case 0xB: + map = [ name: "lock", value: "unknown", descriptionText: "$device.displayName is jammed", eventType: "ALERT", displayed: true ] + break + case 0xC: + map = [ name: "codeChanged", value: "all", descriptionText: "$device.displayName: all user codes deleted", displayed: true, isStateChange: true ] + allCodesDeleted() + break + case 0xD: + if (cmd.eventParameter) + { + map = [ name: "codeReport", value: cmd.eventParameter[0], data: [ code: "" ], isStateChange: true ] + map.descriptionText = "$device.displayName code ${map.value} was deleted" + map.isStateChange = (state["code$map.value"] != "") + state["code$map.value"] = "" + } + else + { + map = [ name: "codeChanged", descriptionText: "$device.displayName: user code deleted", isStateChange: true ] + } + break + case 0xE: + map = [ name: "codeChanged", value: cmd.alarmLevel, descriptionText: "$device.displayName: user code added", isStateChange: true ] + if (cmd.eventParameter) + { + map.value = cmd.eventParameter[0] + result << response(requestCode(cmd.eventParameter[0])) + } + break + case 0xF: + map = [ name: "tamper", value: "detected", descriptionText: "$device.displayName: Too many user code failures.", eventType: "ALERT", displayed: true, isStateChange: true ] + break + // map = [ name: "codeChanged", descriptionText: "$device.displayName: user code not added, duplicate", isStateChange: true ] + // break + case 0x10: + map = [ name: "tamper", value: "detected", descriptionText: "$device.displayName: keypad temporarily disabled", displayed: true ] + break + case 0x11: + map = [ descriptionText: "$device.displayName: keypad is busy" ] + break + case 0x12: + map = [ name: "codeChanged", descriptionText: "$device.displayName: program code changed", isStateChange: true ] + break + case 0x13: + map = [ name: "tamper", value: "detected", descriptionText: "$device.displayName: code entry attempt limit exceeded", displayed: true ] + break + default: + map = map ?: [ descriptionText: "$device.displayName: alarm event $cmd.zwaveAlarmEvent", displayed: false ] + break + } + } + else if (cmd.zwaveAlarmType == 7) // ZWAVE_ALARM_TYPE_BURGLAR + { + map = [ name: "tamper", value: "detected", displayed: true, isStateChange: true ] + switch (cmd.zwaveAlarmEvent) + { + case 0: + map.value = "clear" + map.descriptionText = "$device.displayName: tamper alert cleared" + break + case 1: + case 2: + map.descriptionText = "$device.displayName: intrusion attempt detected" + break + case 3: + map.descriptionText = "$device.displayName: covering removed" + break + case 4: + map.descriptionText = "$device.displayName: invalid code" + break + default: + map.descriptionText = "$device.displayName: tamper alarm $cmd.zwaveAlarmEvent" + break + } + } + else switch(cmd.alarmType) + { + // Schlage locks should be using the alarmv2 variables above or lock/unlock events +/* + case 21: // Manually locked + case 18: // Locked with keypad + case 24: // Locked by command (Kwikset 914) + case 27: // Autolocked + map = [ name: "lock", value: "locked" ] + break + case 16: // Note: for levers this means it's unlocked, for non-motorized deadbolt, it's just unsecured and might not get unlocked + case 19: + map = [ name: "lock", value: "unlocked" ] + if (cmd.alarmLevel) { + map.descriptionText = "$device.displayName was unlocked with code $cmd.alarmLevel" + map.data = [ usedCode: cmd.alarmLevel ] + } + break + case 22: + case 25: // Kwikset 914 unlocked by command + map = [ name: "lock", value: "unlocked" ] + break +*/ + case 9: + case 17: + case 23: + case 26: + map = [ name: "lock", value: "unknown", descriptionText: "$device.displayName bolt is jammed" ] + break + case 13: + map = [ name: "codeChanged", value: cmd.alarmLevel, descriptionText: "$device.displayName code $cmd.alarmLevel was added", isStateChange: true ] + result << response(requestCode(cmd.alarmLevel)) + break + case 32: + map = [ name: "codeChanged", value: "all", descriptionText: "$device.displayName: all user codes deleted", isStateChange: true ] + allCodesDeleted() + case 33: + map = [ name: "codeReport", value: cmd.alarmLevel, data: [ code: "" ], isStateChange: true ] + map.descriptionText = "$device.displayName code $cmd.alarmLevel was deleted" + map.isStateChange = (state["code$cmd.alarmLevel"] != "") + state["code$cmd.alarmLevel"] = "" + break + case 112: + map = [ name: "codeChanged", value: cmd.alarmLevel, descriptionText: "$device.displayName code $cmd.alarmLevel changed", isStateChange: true ] + result << response(requestCode(cmd.alarmLevel)) + break + case 130: // Yale YRD batteries replaced + map = [ descriptionText: "$device.displayName batteries replaced", isStateChange: true ] + break + case 131: + map = [ /*name: "codeChanged", value: cmd.alarmLevel,*/ descriptionText: "$device.displayName code $cmd.alarmLevel is duplicate", isStateChange: false ] + break + case 161: + if (cmd.alarmLevel == 2) + { + map = [ descriptionText: "$device.displayName front escutcheon removed", isStateChange: true ] + } + else + { + map = [ descriptionText: "$device.displayName detected failed user code attempt", isStateChange: true ] + } + break + case 167: + if (!state.lastbatt || (new Date().time) - state.lastbatt > 12*60*60*1000) + { + map = [ descriptionText: "$device.displayName: battery low", isStateChange: true ] + result << response(secure(zwave.batteryV1.batteryGet())) + } + else + { + map = [ name: "battery", value: device.currentValue("battery"), descriptionText: "$device.displayName: battery low", displayed: true ] + } + break + case 168: + map = [ name: "battery", value: 1, descriptionText: "$device.displayName: battery level critical", displayed: true ] + break + case 169: + map = [ name: "battery", value: 0, descriptionText: "$device.displayName: battery too low to operate lock", isStateChange: true ] + break + default: + map = [ displayed: false, descriptionText: "$device.displayName: alarm event $cmd.alarmType level $cmd.alarmLevel" ] + break + } + result ? [createEvent(map), *result] : createEvent(map) +} + +def zwaveEvent(UserCodeReport cmd) +{ + def result = [] + def name = "code$cmd.userIdentifier" + def code = cmd.code + def map = [:] + if (cmd.userIdStatus == UserCodeReport.USER_ID_STATUS_OCCUPIED || + (cmd.userIdStatus == UserCodeReport.USER_ID_STATUS_STATUS_NOT_AVAILABLE && cmd.user && code != "**********")) + { + if (code == "**********") + { // Schlage locks send us this instead of the real code + state.blankcodes = true + code = state["set$name"] ?: decrypt(state[name]) ?: code + state.remove("set$name".toString()) + } + if (!code && cmd.userIdStatus == 1) + { // Schlage touchscreen sends blank code to notify of a changed code + map = [ name: "codeChanged", value: cmd.userIdentifier, displayed: true, isStateChange: true ] + map.descriptionText = "$device.displayName code $cmd.userIdentifier " + (state[name] ? "changed" : "was added") + code = state["set$name"] ?: decrypt(state[name]) ?: "****" + state.remove("set$name".toString()) + } + else + { + map = [ name: "codeReport", value: cmd.userIdentifier, data: [ code: code ] ] + map.descriptionText = "$device.displayName code $cmd.userIdentifier is set" + map.displayed = (cmd.userIdentifier != state.requestCode && cmd.userIdentifier != state.pollCode) + map.isStateChange = true + } + result << createEvent(map) + } + else + { + map = [ name: "codeReport", value: cmd.userIdentifier, data: [ code: "" ] ] + if (state.blankcodes && state["reset$name"]) + { // we deleted this code so we can tell that our new code gets set + map.descriptionText = "$device.displayName code $cmd.userIdentifier was reset" + map.displayed = map.isStateChange = false + result << createEvent(map) + state["set$name"] = state["reset$name"] + result << response(setCode(cmd.userIdentifier, state["reset$name"])) + state.remove("reset$name".toString()) + } + else + { + if (state[name]) + { + map.descriptionText = "$device.displayName code $cmd.userIdentifier was deleted" + } + else + { + map.descriptionText = "$device.displayName code $cmd.userIdentifier is not set" + } + map.displayed = (cmd.userIdentifier != state.requestCode && cmd.userIdentifier != state.pollCode) + map.isStateChange = true + result << createEvent(map) + } + code = "" + } + state[name] = code ? encrypt(code) : code + + if (cmd.userIdentifier == state.requestCode) + { // reloadCodes() was called, keep requesting the codes in order + if (state.requestCode + 1 > state.codes || state.requestCode >= 30) + { + state.remove("requestCode") // done + } + else + { + state.requestCode = state.requestCode + 1 // get next + result << response(requestCode(state.requestCode)) + } + } + if (cmd.userIdentifier == state.pollCode) + { + if (state.pollCode + 1 > state.codes || state.pollCode >= 30) + { + state.remove("pollCode") // done + } + else + { + state.pollCode = state.pollCode + 1 + } + } + log.debug "code report parsed to ${result.inspect()}" + result +} + +def zwaveEvent(UsersNumberReport cmd) +{ + def result = [] + state.codes = cmd.supportedUsers + if (state.requestCode && state.requestCode <= cmd.supportedUsers) + { + result << response(requestCode(state.requestCode)) + } + result +} + +def zwaveEvent(physicalgraph.zwave.commands.associationv2.AssociationReport cmd) { + def result = [] + if (cmd.nodeId.any { it == zwaveHubNodeId }) + { + state.remove("associationQuery") + log.debug "$device.displayName is associated to $zwaveHubNodeId" + result << createEvent(descriptionText: "$device.displayName is associated") + state.assoc = zwaveHubNodeId + if (cmd.groupingIdentifier == 2) + { + result << response(zwave.associationV1.associationRemove(groupingIdentifier:1, nodeId:zwaveHubNodeId)) + } + } + else if (cmd.groupingIdentifier == 1) + { + result << response(secure(zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:zwaveHubNodeId))) + } + else if (cmd.groupingIdentifier == 2) + { + result << response(zwave.associationV1.associationSet(groupingIdentifier:2, nodeId:zwaveHubNodeId)) + } + result +} + +def zwaveEvent(physicalgraph.zwave.commands.timev1.TimeGet cmd) +{ + def result = [] + def now = new Date().toCalendar() + if(location.timeZone) now.timeZone = location.timeZone + result << createEvent(descriptionText: "$device.displayName requested time update", displayed: false) + result << response(secure(zwave.timeV1.timeReport( + hourLocalTime: now.get(Calendar.HOUR_OF_DAY), + minuteLocalTime: now.get(Calendar.MINUTE), + secondLocalTime: now.get(Calendar.SECOND))) + ) + result +} + +def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd) +{ + // The old Schlage locks use group 1 for basic control - we don't want that, so unsubscribe from group 1 + def result = [ createEvent(name: "lock", value: cmd.value ? "unlocked" : "locked") ] + result << response(zwave.associationV1.associationRemove(groupingIdentifier:1, nodeId:zwaveHubNodeId)) + if (state.assoc != zwaveHubNodeId) + { + result << response(zwave.associationV1.associationGet(groupingIdentifier:2)) + } + result +} + +def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) +{ + def map = [ name: "battery", unit: "%" ] + if (cmd.batteryLevel == 0xFF) + { + map.value = 1 + map.descriptionText = "$device.displayName has a low battery" + } + else + { + map.value = cmd.batteryLevel + } + state.lastbatt = new Date().time + createEvent(map) +} + +def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) +{ + def result = [] + + def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId) + log.debug "msr: $msr" + updateDataValue("MSR", msr) + + result << createEvent(descriptionText: "$device.displayName MSR: $msr", isStateChange: false) + result +} + +def zwaveEvent(physicalgraph.zwave.commands.versionv1.VersionReport cmd) +{ + def fw = "${cmd.applicationVersion}.${cmd.applicationSubVersion}" + updateDataValue("fw", fw) + if (state.MSR == "003B-6341-5044") { + updateDataValue("ver", "${cmd.applicationVersion >> 4}.${cmd.applicationVersion & 0xF}") + } + def text = "$device.displayName: firmware version: $fw, Z-Wave version: ${cmd.zWaveProtocolVersion}.${cmd.zWaveProtocolSubVersion}" + createEvent(descriptionText: text, isStateChange: false) +} + +def zwaveEvent(physicalgraph.zwave.commands.applicationstatusv1.ApplicationBusy cmd) +{ + def msg = cmd.status == 0 ? "try again later" : + cmd.status == 1 ? "try again in $cmd.waitTime seconds" : + cmd.status == 2 ? "request queued" : "sorry" + createEvent(displayed: true, descriptionText: "$device.displayName is busy, $msg") +} + +def zwaveEvent(physicalgraph.zwave.commands.applicationstatusv1.ApplicationRejectedRequest cmd) +{ + createEvent(displayed: true, descriptionText: "$device.displayName rejected the last request") +} + +def zwaveEvent(physicalgraph.zwave.commands.configurationv2.ConfigurationReport cmd) +{ + def result = [] + def map = null // use this for config reports that are handled + + // use desc/val for generic handling of config reports (it will just send a descriptionText for the acitivty stream) + def desc = null + def val = "" + + switch (cmd.parameterNumber) + { + case 0x3: + map = parseBinaryConfigRpt('beeperMode', cmd.configurationValue[0], 'Beeper Mode') + break + + // done: vacation mode toggle + case 0x4: + map = parseBinaryConfigRpt('vacationMode', cmd.configurationValue[0], 'Vacation Mode') + break + + // done: lock and leave mode + case 0x5: + map = parseBinaryConfigRpt('lockLeave', cmd.configurationValue[0], 'Lock & Leave') + break + + // these don't seem to be useful. It's just a bitmap of the code slots used. + case 0x6: + desc = "User Slot Bit Fields" + val = "${cmd.configurationValue[3]} ${cmd.configurationValue[2]} ${cmd.configurationValue[1]} ${cmd.configurationValue[0]}" + break + + // done: the alarm mode of the lock. + case 0x7: + map = [ name:"alarmMode", displayed: true ] + // when getting the alarm mode, also query the sensitivity for that current alarm mode + switch (cmd.configurationValue[0]) + { + case 0x00: + map.value = "Off_alarmMode" + break + case 0x01: + map.value = "Alert_alarmMode" + result << response(secure(zwave.configurationV2.configurationGet(parameterNumber: 0x08))) + break + case 0x02: + map.value = "Tamper_alarmMode" + result << response(secure(zwave.configurationV2.configurationGet(parameterNumber: 0x09))) + break + case 0x03: + map.value = "Kick_alarmMode" + result << response(secure(zwave.configurationV2.configurationGet(parameterNumber: 0x0A))) + break + default: + map.value = "unknown_alarmMode" + } + map.descriptionText = "$device.displayName Alarm Mode set to \"$map.value\"" + break + + // done: alarm sensitivities - one for each mode + case 0x8: + case 0x9: + case 0xA: + def whichMode = null + switch (cmd.parameterNumber) + { + case 0x8: + whichMode = "Alert" + break; + case 0x9: + whichMode = "Tamper" + break; + case 0xA: + whichMode = "Kick" + break; + } + def curAlarmMode = device.currentValue("alarmMode") + val = "${cmd.configurationValue[0]}" + + // the lock has sensitivity values between 1 and 5. We set the slider's range ("1".."5") in the Tile's Definition + def modifiedValue = cmd.configurationValue[0] + + map = [ descriptionText: "$device.displayName Alarm $whichMode Sensitivity set to $val", displayed: true ] + + if (curAlarmMode == "${whichMode}_alarmMode") + { + map.name = "alarmSensitivity" + map.value = modifiedValue + } + else + { + log.debug "got sensitivity for $whichMode while in $curAlarmMode" + map.isStateChange = true + } + + break + + case 0xB: + map = parseBinaryConfigRpt('localControl', cmd.configurationValue[0], 'Local Alarm Control') + break + + // how many times has the electric motor locked or unlock the device? + case 0xC: + desc = "Electronic Transition Count" + def ttl = cmd.configurationValue[3] + (cmd.configurationValue[2] * 0x100) + (cmd.configurationValue[1] * 0x10000) + (cmd.configurationValue[0] * 0x1000000) + val = "$ttl" + break + + // how many times has the device been locked or unlocked manually? + case 0xD: + desc = "Mechanical Transition Count" + def ttl = cmd.configurationValue[3] + (cmd.configurationValue[2] * 0x100) + (cmd.configurationValue[1] * 0x10000) + (cmd.configurationValue[0] * 0x1000000) + val = "$ttl" + break + + // how many times has there been a failure by the electric motor? (due to jamming??) + case 0xE: + desc = "Electronic Failed Count" + def ttl = cmd.configurationValue[3] + (cmd.configurationValue[2] * 0x100) + (cmd.configurationValue[1] * 0x10000) + (cmd.configurationValue[0] * 0x1000000) + val = "$ttl" + break + + // done: auto lock mode + case 0xF: + map = parseBinaryConfigRpt('autoLock', cmd.configurationValue[0], 'Auto Lock') + break + + // this will be useful as an attribute/command usable by a smartapp + case 0x10: + map = [ name: 'pinLength', value: cmd.configurationValue[0], displayed: true, descriptionText: "$device.displayName PIN length configured to ${cmd.configurationValue[0]} digits"] + break + + // not sure what this one stores + case 0x11: + desc = "Electronic High Preload Transition Count" + def ttl = cmd.configurationValue[3] + (cmd.configurationValue[2] * 0x100) + (cmd.configurationValue[1] * 0x10000) + (cmd.configurationValue[0] * 0x1000000) + val = "$ttl" + break + + // ??? + case 0x12: + desc = "Bootloader Version" + val = "${cmd.configurationValue[0]}" + break + default: + desc = "Unknown parameter ${cmd.parameterNumber}" + val = "${cmd.configurationValue[0]}" + break + } + if (map) + { + result << createEvent(map) + } + else if (desc != null) + { + // generic description text + result << createEvent([ descriptionText: "$device.displayName reports \"$desc\" configured as \"$val\"", displayed: true, isStateChange: true ]) + } + result +} + +def parseBinaryConfigRpt(paramName, paramValue, paramDesc) +{ + def map = [ name: paramName, displayed: true ] + + def newVal = "on" + if (paramValue == 0) + { + newVal = "off" + } + map.value = "${newVal}_${paramName}" + map.descriptionText = "$device.displayName $paramDesc has been turned $newVal" + return map +} + + + +def zwaveEvent(physicalgraph.zwave.Command cmd) +{ + createEvent(displayed: false, descriptionText: "$device.displayName: $cmd") +} + +def lockAndCheck(doorLockMode) +{ + secureSequence([ + zwave.doorLockV1.doorLockOperationSet(doorLockMode: doorLockMode), + zwave.doorLockV1.doorLockOperationGet() + ], 4200) +} + +def lock() +{ + lockAndCheck(DoorLockOperationSet.DOOR_LOCK_MODE_DOOR_SECURED) +} + +def unlock() +{ + lockAndCheck(DoorLockOperationSet.DOOR_LOCK_MODE_DOOR_UNSECURED) +} + +def unlockwtimeout() +{ + lockAndCheck(DoorLockOperationSet.DOOR_LOCK_MODE_DOOR_UNSECURED_WITH_TIMEOUT) +} + +def refresh() { +// def cmds = [secure(zwave.doorLockV1.doorLockOperationGet())] + def cmds = secureSequence([ + zwave.doorLockV1.doorLockOperationGet(), +// zwave.configurationV2.configurationBulkGet(numberOfParameters: 3, parameterOffset: 0x8), +// zwave.configurationV2.configurationBulkGet(numberOfParameters: 4, parameterOffset: 0x3), +// zwave.configurationV2.configurationGet(parameterNumber: 0x3), // beeper (done) +// zwave.configurationV2.configurationGet(parameterNumber: 0x4), // vacation mode (done) +// zwave.configurationV2.configurationGet(parameterNumber: 0x5), // lock and leave (done) +// zwave.configurationV2.configurationGet(parameterNumber: 0x6), // user slot bit field (not needed) +// zwave.configurationV2.configurationGet(parameterNumber: 0x7), // alarm mode (done) +// zwave.configurationV2.configurationGet(parameterNumber: 0x8), // alert alarm sensitivity (done: retrieved after alarm mode) +// zwave.configurationV2.configurationGet(parameterNumber: 0x9), // tamper alarm sensitivity (done: retrieved after alarm mode) +// zwave.configurationV2.configurationGet(parameterNumber: 0xA), // kick alarm sensititivy (done: retrieved after alarm mode) +// zwave.configurationV2.configurationGet(parameterNumber: 0xB), // local alarm control disable (done) +// zwave.configurationV2.configurationGet(parameterNumber: 0xC), // electronic transition count +// zwave.configurationV2.configurationGet(parameterNumber: 0xD), // mechanical transition count +// zwave.configurationV2.configurationGet(parameterNumber: 0xE), // electronic failure count +// zwave.configurationV2.configurationGet(parameterNumber: 0xF), // autolock (done) +// zwave.configurationV2.configurationGet(parameterNumber: 0x10), // user code pin length (done) +// zwave.configurationV2.configurationGet(parameterNumber: 0x11,) // electronic high preload transition count +// zwave.configurationV2.configurationGet(parameterNumber: 0x12,) // bootloader version + + ], 6000) + + // go ahead and fill in any missing values + if (null == device.latestValue("pinLength")) + { + log.debug "getting pin length" + cmds << "delay 6000" + cmds << secure(zwave.configurationV2.configurationGet(parameterNumber: 0x10)) + } + + + if (state.assoc == zwaveHubNodeId) { + log.debug "$device.displayName is associated to ${state.assoc}" + } else if (!state.associationQuery) { + log.debug "checking association" + cmds << "delay 4200" + cmds << zwave.associationV1.associationGet(groupingIdentifier:2).format() // old Schlage locks use group 2 and don't secure the Association CC + cmds << secure(zwave.associationV1.associationGet(groupingIdentifier:1)) + state.associationQuery = new Date().time + } else if (new Date().time - state.associationQuery.toLong() > 9000) { + log.debug "setting association" + cmds << "delay 6000" + cmds << zwave.associationV1.associationSet(groupingIdentifier:2, nodeId:zwaveHubNodeId).format() + cmds << secure(zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:zwaveHubNodeId)) + cmds << zwave.associationV1.associationGet(groupingIdentifier:2).format() + cmds << secure(zwave.associationV1.associationGet(groupingIdentifier:1)) + state.associationQuery = new Date().time + } + log.debug "refresh is sending ${cmds.inspect()}, state: ${state.inspect()}" + cmds +} + +def poll() { + def cmds = [] + state.pinLength = null; + if (state.assoc != zwaveHubNodeId && secondsPast(state.associationQuery, 19 * 60)) + { + log.debug "setting association" + cmds << zwave.associationV1.associationSet(groupingIdentifier:2, nodeId:zwaveHubNodeId).format() + cmds << secure(zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:zwaveHubNodeId)) + cmds << zwave.associationV1.associationGet(groupingIdentifier:2).format() + cmds << "delay 6000" + cmds << secure(zwave.associationV1.associationGet(groupingIdentifier:1)) + cmds << "delay 6000" + state.associationQuery = new Date().time + } + else + { + // go ahead and fill in any missing values + if (null == device.latestValue("pinLength")) + { + cmds << secure(zwave.configurationV2.configurationGet(parameterNumber: 0x10)) + cmds << "delay 6000" + } + + // Only check lock state if it changed recently or we haven't had an update in an hour + def latest = device.currentState("lock")?.date?.time + if (!latest || !secondsPast(latest, 6 * 60) || secondsPast(state.lastPoll, 55 * 60)) + { + cmds << secure(zwave.doorLockV1.doorLockOperationGet()) + state.lastPoll = (new Date()).time + } + else if (!state.MSR) + { + cmds << zwave.manufacturerSpecificV1.manufacturerSpecificGet().format() + } + else if (!state.fw) + { + cmds << zwave.versionV1.versionGet().format() + } + else if (!state.codes) + { + state.pollCode = 1 + cmds << secure(zwave.userCodeV1.usersNumberGet()) + } + else if (state.pollCode && state.pollCode <= state.codes) + { + cmds << requestCode(state.pollCode) + } + else if (!state.lastbatt || (new Date().time) - state.lastbatt > 53*60*60*1000) + { + cmds << secure(zwave.batteryV1.batteryGet()) + } + else if (!state.enc) + { + encryptCodes() + state.enc = 1 + } + } + reportAllCodes(state) + + log.debug "poll is sending ${cmds.inspect()}" + device.activity() + cmds ?: null +} + +private def encryptCodes() +{ + def keys = new ArrayList(state.keySet().findAll { it.startsWith("code") }) + keys.each { key -> + def match = (key =~ /^code(\d+)$/) + if (match) try + { + def keynum = match[0][1].toInteger() + if (keynum > 30 && !state[key]) + { + state.remove(key) + } + else if (state[key] && !state[key].startsWith("~")) + { + log.debug "encrypting $key: ${state[key].inspect()}" + state[key] = encrypt(state[key]) + } + } + catch (java.lang.NumberFormatException e) { } + } +} + +def requestCode(codeNumber) +{ + log.debug "getting user code $codeNumber" + secure(zwave.userCodeV1.userCodeGet(userIdentifier: codeNumber)) +} + +def reloadAllCodes() +{ + def cmds = [] + if (!state.codes) + { + state.requestCode = 1 + cmds << secure(zwave.userCodeV1.usersNumberGet()) + } + else + { + if(!state.requestCode) state.requestCode = 1 + cmds << requestCode(codeNumber) + } + cmds +} + +def setCode(codeNumber, code) +{ + def strcode = code + log.debug "setting code $codeNumber to $code" + if (code instanceof String) + { + code = code.toList().findResults { if(it > ' ' && it != ',' && it != '-') it.toCharacter() as Short } + } + else + { + strcode = code.collect{ it as Character }.join() + } + if (state.blankcodes) + { + // Can't just set, we won't be able to tell if it was successful + if (state["code$codeNumber"] != "") + { + if (state["setcode$codeNumber"] != strcode) + { + state["resetcode$codeNumber"] = strcode + return deleteCode(codeNumber) + } + } + else + { + state["setcode$codeNumber"] = strcode + } + } + secureSequence([ + zwave.userCodeV1.userCodeSet(userIdentifier:codeNumber, userIdStatus:1, user:code), + zwave.userCodeV1.userCodeGet(userIdentifier:codeNumber) + ], 7000) +} + +def deleteCode(codeNumber) +{ + log.debug "deleting code $codeNumber" + secureSequence([ + zwave.userCodeV1.userCodeSet(userIdentifier:codeNumber, userIdStatus:0), + zwave.userCodeV1.userCodeGet(userIdentifier:codeNumber) + ], 7000) +} + +def updateCodes(codeSettings) +{ +log.debug "updateCodes called with: ${codeSettings.inspect()}" + if(codeSettings instanceof String) codeSettings = util.parseJson(codeSettings) + def set_cmds = [] + def get_cmds = [] + codeSettings.each { name, updated -> + def current = decrypt(state[name]) + if (name.startsWith("code")) + { + def n = name[4..-1].toInteger() + if (updated?.size() >= 4 && updated != current) + { + log.debug "$name was $current, set to $updated" + def cmds = setCode(n, updated) + set_cmds << cmds.first() + get_cmds << cmds.last() + } + else if ((current && updated == "") || updated == "0") + { + log.debug "$name was $current, set to deleted" + def cmds = deleteCode(n) + set_cmds << cmds.first() + get_cmds << cmds.last() + } + else if (updated && updated.size() < 4) + { + log.debug "Attempt to set $name to a value that's too short" + // Entered code was too short + codeSettings["code$n"] = current + } + else + { + log.debug "$name remains unchanged." + } + } + else + log.warn("unexpected entry $name: $updated") + } + if (set_cmds) + { + return response(delayBetween(set_cmds, 2200) + ["delay 2200"] + delayBetween(get_cmds, 4200)) + } +} + +def getCode(codeNumber) +{ + decrypt(state["code$codeNumber"]) +} + +def getAllCodes() +{ + state.findAll { it.key.startsWith 'code' }.collectEntries { + [it.key, (it.value instanceof String && it.value.startsWith("~")) ? decrypt(it.value) : it.value] + } +} + +private secure(physicalgraph.zwave.Command cmd) +{ + zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format() +} + +private secureSequence(commands, delay=4200) +{ + delayBetween(commands.collect{ secure(it) }, delay) +} + +private Boolean secondsPast(timestamp, seconds) +{ + if (!(timestamp instanceof Number)) + { + if (timestamp instanceof Date) + { + timestamp = timestamp.time + } + else if ((timestamp instanceof String) && timestamp.isNumber()) + { + timestamp = timestamp.toLong() + } + else + { + return true + } + } + return (new Date().time - timestamp) > (seconds * 1000) +} + +private allCodesDeleted() { + if (state.codes instanceof Integer) + { + (1..state.codes).each { n -> + if (state["code$n"]) + { + result << createEvent(name: "codeReport", value: n, data: [ code: "" ], descriptionText: "code $n was deleted", + displayed: false, isStateChange: true) + } + state["code$n"] = "" + } + } +} + +// all the on/off parameters work the same way, so make a common method +// to deal with them +// +def setOnOffParameter(paramName, paramNumber) +{ + def cmds = null + def cs = device.currentValue(paramName) + + // change parameter to the 'unknown' value - it will get refreshed after it is done changing + sendEvent(name: paramName, value: "unknown_${paramName}", displayed: false ) + + if (cs == "on_${paramName}") + { + // turn it off + cmds = secureSequence([zwave.configurationV2.configurationSet(parameterNumber: paramNumber, size: 1, configurationValue: [0])],5000) + } + else if (cs == "off_${paramName}") + { + // turn it on + cmds = secureSequence([zwave.configurationV2.configurationSet(parameterNumber: paramNumber, size: 1, configurationValue: [0xFF])],5000) + } + else + { + // it's in an unknown state, so just query it + cmds = secureSequence([zwave.configurationV2.configurationGet(parameterNumber: paramNumber)], 5000) + } + + log.debug "set $paramName sending ${cmds.inspect()}" + + cmds +} + +def setBeeperMode() +{ + setOnOffParameter("beeperMode", 0x3) +} + +def setVacationMode() +{ + setOnOffParameter("vacationMode", 0x4) +} + +def setLockLeave() +{ + setOnOffParameter("lockLeave", 0x5) +} + +def setLocalControl() +{ + setOnOffParameter("localControl", 0xB) +} + +def setAutoLock() +{ + setOnOffParameter("autoLock", 0xF) +} + +def setAlarmMode(def newValue = null) +{ + + def cs = device.currentValue("alarmMode") + def newMode = 0x0 + + def cmds = null + + if (newValue == null) + { + switch (cs) + { + case "Off_alarmMode": + newMode = 0x1 + break + case "Alert_alarmMode": + newMode = 0x2 + break + case "Tamper_alarmMode": + newMode = 0x3 + break + case "Kick_alarmMode": + newMode = 0x0 + break + case "unknown_alarmMode": + default: + // don't send a mode - instead request the current state + cmds = secureSequence([zwave.configurationV2.configurationGet(parameterNumber: 0x7)], 5000) + + } + } + else + { + switch (newValue) + { + case "Off": + newMode = 0x0 + break + case "Alert": + newMode = 0x1 + break + case "Tamper": + newMode = 0x2 + break + case "Kick": + newMode = 0x3 + break + default: + // don't send a mode - instead request the current state + cmds = secureSequence([zwave.configurationV2.configurationGet(parameterNumber: 0x7)], 5000) + + } + } + if (cmds == null) + { + // change the alarmSensitivity to the 'unknown' value - it will get refreshed after the alarm mode is done changing + sendEvent(name: 'alarmSensitivity', value: 0, displayed: false ) + cmds = secureSequence([zwave.configurationV2.configurationSet(parameterNumber: 7, size: 1, configurationValue: [newMode])],5000) + } + + log.debug "setAlarmMode sending ${cmds.inspect()}" + cmds +} + +def setPinLength(newValue) +{ + def cmds = null + if ((newValue == null) || (newValue == 0)) + { + // just send a request to refresh the value + cmds = secureSequence([zwave.configurationV2.configurationGet(parameterNumber: 0x10)],5000) + } + else if (newValue <= 8) + { + sendEvent(descriptionText: "$device.displayName attempting to change PIN length to $newValue", displayed: true, isStateChange: true) + cmds = secureSequence([zwave.configurationV2.configurationSet(parameterNumber: 10, size: 1, configurationValue: [newValue])],5000) + } + else + { + sendEvent(descriptionText: "$device.displayName UNABLE to set PIN length of $newValue", displayed: true, isStateChange: true) + } + log.debug "setPinLength sending ${cmds.inspect()}" + cmds +} + +def setAlarmSensitivity(newValue) +{ + def cmds = null + if (newValue != null) + { + // newvalue will be between 1 and 5 inclusive as controlled by the slider's range definition + newValue = newValue.toInteger(); + + // there are three possible values to set. which one depends on the current alarmMode + def cs = device.currentValue("alarmMode") + + def paramToSet = 0 + + switch(cs) + { + case "Off_alarmMode": + // do nothing. the slider should be disabled anyway + break + case "Alert_alarmMode": + // set param 8 + paramToSet = 0x8 + break + case "Tamper_alarmMode": + paramToSet = 0x9 + break + case "Kick_alarmMode": + paramToSet = 0xA + break + case "unknown_alarmMode": + default: + sendEvent(descriptionText: "$device.displayName unable to set alarm sensitivity while alarm mode in unknown state", displayed: true, isStateChange: true) + break + } + if (paramToSet != 0) + { + // first set the attribute to 0 for UI purposes + sendEvent(name: 'alarmSensitivity', value: 0, displayed: false ) + // then add the actual attribute set call + cmds = secureSequence([zwave.configurationV2.configurationSet(parameterNumber: paramToSet, size: 1, configurationValue: [newValue])],5000) + log.debug "setAlarmSensitivity sending ${cmds.inspect()}" + } + } + cmds +} + +// provides compatibility with Erik Thayer's "Lock Code Manager" +def reportAllCodes(state) +{ + def map = [ name: "reportAllCodes", data: [:], displayed: false, isStateChange: false, type: "physical" ] + state.each { entry -> + //iterate through all the state entries and add them to the event data to be handled by application event handlers + if ( entry.key ==~ /^code\d{1,}/ && entry.value.startsWith("~") ) + { + map.data.put(entry.key, decrypt(entry.value)) + } + else + { + map.data.put(entry.key, entry.value) + } + } + sendEvent(map) +} \ No newline at end of file