From 0de5dfa1455a09e40b3d92873f6374b4da0df4e1 Mon Sep 17 00:00:00 2001 From: Mi Casa Date: Mon, 26 Jun 2017 23:27:24 -0700 Subject: [PATCH] Modifying 'My Lock' --- .../zwave-schlage-touchscreen-lock.groovy | 2832 ++++++++--------- 1 file changed, 1416 insertions(+), 1416 deletions(-) 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 index de0d80d..d296d16 100644 --- 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 @@ -1,1417 +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) +/** + * + * 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