/* Ring Keypad Gen 2 - Community Driver Copyright 2020 -> 2021 Hubitat Inc. All Rights Reserved Portions Copyright (c) 2022 Jeremy Kister https://jeremy.kister.net./ Special Thanks to Bryan Copeland (@bcopeland) for writing and releasing this code to the community! And Bryan Turcotte (@bptworld) for years of code and support. See the community thread for more information: https://community.hubitat.com/t/107035 For the commit history, see: https://github.com/jkister/hubitat/commits/master/drivers/rakg2 */ import groovy.transform.Field import groovy.json.JsonOutput def version() { return "1.2.11.1" } metadata { definition (name: "Ring Alarm Keypad G2 Community", namespace: "jkister", author: "Community", importUrl: "https://raw.githubusercontent.com/jkister/hubitat/master/drivers/rakg2/rakg2-driver.groovy") { capability "Actuator" capability "Sensor" capability "Configuration" capability "SecurityKeypad" capability "Battery" capability "Alarm" capability "PowerSource" capability "LockCodes" capability "Motion Sensor" capability "PushableButton" capability "HoldableButton" command "entry" command "keypadUpdateStatus", ["string"] command "setArmNightDelay", ["number"] command "setArmAwayDelay", ["number"] command "setArmHomeDelay", ["number"] command "setArmNightDelay", ["number"] command "setPartialFunction" command "resetKeypad" command "playTone", [[name: "Play Tone", type: "STRING", description: "Tone_1, Tone_2, etc."]] command "volAnnouncement", [[name:"Announcement Volume", type:"NUMBER", description: "Volume level (1-10)"]] command "volKeytone", [[name:"Keytone Volume", type:"NUMBER", description: "Volume level (1-10)"]] command "volSiren", [[name:"Chime Tone Volume", type:"NUMBER", description: "Volume level (1-10)"]] attribute "alarmStatusChangeTime", "STRING" attribute "alarmStatusChangeEpochms", "NUMBER" attribute "armingIn", "NUMBER" attribute "armAwayDelay", "NUMBER" attribute "armHomeDelay", "NUMBER" attribute "armNightDelay", "NUMBER" attribute "lastCode", "STRING" attribute "lastCodeName", "STRING" attribute "lastCodeTime", "STRING" attribute "lastCodeEpochms", "NUMBER" attribute "motion", "STRING" attribute "validCode", "ENUM", ["true", "false"] attribute "volAnnouncement", "NUMBER" attribute "volKeytone", "NUMBER" attribute "volSiren", "NUMBER" fingerprint mfr:"0346", prod:"0101", deviceId:"0301", inClusters:"0x5E,0x98,0x9F,0x6C,0x55", deviceJoinName: "Ring Alarm Keypad G2" } preferences { input name: "about", type: "paragraph", element: "paragraph", title: "Ring Alarm Keypad G2 Community Driver", description: "${version()}
Note:
The first 3 Tones are alarm sounds that also flash the Red Indicator Bar on the keypads. The rest are more pleasant sounds that could be used for a variety of things." configParams.each { input it.value.input } input name: "theTone", type: "enum", title: "Chime tone", options: [ ["Tone_1":"(Tone_1) Siren (default)"], ["Tone_2":"(Tone_2) 3 Beeps"], ["Tone_3":"(Tone_3) 4 Beeps"], ["Tone_4":"(Tone_4) Navi"], ["Tone_5":"(Tone_5) Guitar"], ["Tone_6":"(Tone_6) Windchimes"], ["Tone_7":"(Tone_7) DoorBell 1"], ["Tone_8":"(Tone_8) DoorBell 2"], ["Tone_9":"(Tone_9) Invalid Code Sound"], ], defaultValue: "Tone_1", description: "" input name: "instantArming", type: "bool", title: "Enable set alarm without code", defaultValue: false, description: "" input name: "validateCheck", type: "bool", title: "Validate codes submitted with checkmark", defaultValue: false, description: "" input name: "setLastCode", type: "bool", title: "Set lastCode variable to last code entered (if false, only show valid/invalid)", defaultValue: false, description: "" input name: "partialFunctionValue", type: "enum", title: "Home Button Action", options: [ ["armHome":"Arm Home (default)"], ["armNight":"Arm Night"], ], defaultValue: "armHome", description: "After setting this, press \"Save Preferences\" below then press the \"Set Partial Function\" button above" input name: "disableProximitySensor", type: "bool", title: "Disable the Proximity Sensor", defaultValue: false, description: "" input name: "optEncrypt", type: "bool", title: "Enable lockCode encryption", defaultValue: false, description: "" input name: "logEnable", type: "bool", title: "Enable debug logging", defaultValue: true input name: "txtEnable", type: "bool", title: "Enable descriptionText logging", defaultValue: true } } @Field static Map configParams = [ //4: [input: [name: "configParam4", type: "enum", title: "Announcement Volume", description:"", defaultValue:7, options:[0:"0",1:"1",2:"2",3:"3",4:"4",5:"5",6:"6",7:"7",8:"8",9:"9",10:"10"]],parameterSize:1], //5: [input: [name: "configParam5", type: "enum", title: "Keytone Volume", description:"", defaultValue:6, options:[0:"0",1:"1",2:"2",3:"3",4:"4",5:"5",6:"6",7:"7",8:"8",9:"9",10:"10"]],parameterSize:1], //6: [input: [name: "configParam6", type: "enum", title: "Siren Volume", description:"", defaultValue:10, options:[0:"0",1:"1",2:"2",3:"3",4:"4",5:"5",6:"6",7:"7",8:"8",9:"9",10:"10"]],parameterSize:1], 7: [input: [name: "configParam7", type: "number", title: "Long press Emergency Duration", description:"", defaultValue: 3, range:"2..5"],parameterSize:1], 8: [input: [name: "configParam8", type: "number", title: "Long press Number pad Duration", description:"", defaultValue: 3, range:"2..5"],parameterSize:1], 12: [input: [name: "configParam12", type: "number", title: "Security Mode Brightness", description:"", defaultValue: 100, range:"0..100"],parameterSize:1], 13: [input: [name: "configParam13", type: "number", title: "Key Backlight Brightness", description:"", defaultValue: 100, range:"0..100"],parameterSize:1], ] @Field static Map armingStates = [ 0x00: [securityKeypadState: "armed night", hsmCmd: "armNight"], 0x02: [securityKeypadState: "disarmed", hsmCmd: "disarm"], 0x0A: [securityKeypadState: "armed home", hsmCmd: "armHome"], 0x0B: [securityKeypadState: "armed away", hsmCmd: "armAway"] ] @Field static Map CMD_CLASS_VERS=[0x86:2, 0x70:1, 0x20:1, 0x86:3] void debug(string){ if (logEnable) log.debug "${string}" } void logsOff(){ log.warn "debug logging disabled..." device.updateSetting("logEnable", [value:"false", type:"bool"]) } void updated() { log.info "updated..." log.warn "debug logging is: ${logEnable == true}" log.warn "description logging is: ${txtEnable == true}" log.warn "encryption is: ${optEncrypt == true}" unschedule() if (logEnable) runIn(1800,logsOff) sendToDevice(runConfigs()) updateEncryption() proximitySensorHandler() volAnnouncement() volKeytone() volSiren() } void installed() { initializeVars() } void uninstalled() { } void initializeVars() { // first run only sendEvent(name:"codeLength", value: 4) sendEvent(name:"maxCodes", value: 100) sendEvent(name:"lockCodes", value: "") sendEvent(name:"armHomeDelay", value: 5) sendEvent(name:"armAwayDelay", value: 5) sendEvent(name:"armNightDelay", value: 5) sendEvent(name:"volAnnouncement", value: 7) sendEvent(name:"volKeytone", value: 5) sendEvent(name:"volSiren", value: 75) sendEvent(name:"securityKeypad", value:"disarmed") state.keypadConfig=[entryDelay:5, exitDelay: 5, armNightDelay:5, armAwayDelay:5, armHomeDelay: 5, codeLength: 4, partialFunction: "armHome"] state.keypadStatus=2 state.initialized=true } void resetKeypad() { state.initialized=false configure() getCodes() } void configure() { if (logEnable) log.debug "configure()" if (!state.initialized) initializeVars() if (!state.keypadConfig) initializeVars() keypadUpdateStatus(state.keypadStatus, state.type, state.code) runIn(5,pollDeviceData) } void pollDeviceData() { List cmds = [] cmds.add(zwave.versionV3.versionGet().format()) cmds.add(zwave.manufacturerSpecificV2.deviceSpecificGet(deviceIdType: 1).format()) cmds.add(zwave.batteryV1.batteryGet().format()) cmds.add(zwave.notificationV8.notificationGet(notificationType: 8, event: 0).format()) cmds.add(zwave.notificationV8.notificationGet(notificationType: 7, event: 0).format()) cmds.addAll(processAssociations()) sendToDevice(cmds) } void keypadUpdateStatus(Integer status,String type="digital", String code) { // put keypad into Home mode when system is in Night mode int kpstatus = status if (kpstatus == 0x00) kpstatus = 0x0A sendToDevice(zwave.indicatorV3.indicatorSet(indicatorCount:1, value: 0, indicatorValues:[[indicatorId:kpstatus, propertyId:2, value:0xFF]]).format()) state.keypadStatus = status if (state.code != "") { type = "physical" } eventProcess(name: "securityKeypad", value: armingStates[status].securityKeypadState, type: type, data: state.code) state.code = "" state.type = "digital" } void armNight(delay) { if (logEnable) log.debug "In armNight (${version()}) - delay: ${delay}" def sk = device.currentValue("securityKeypad") if(sk != "armed night") { if (delay > 0 ) { exitDelay(delay) runIn(delay, armNightEnd) } else if ( delay == null ){ // delay is null - event received from device page? let HSM/whatever pick it up debug "armNight: sending emulated event" keypadUpdateStatus(0x0A, "digital", "") sendEvent(name:"armingIn", value: state.keypadConfig.armNightDelay, data:[armMode: armingStates[0x00].securityKeypadState, armCmd: armingStates[0x00].hsmCmd], isStateChange:true) } else { armNightEnd() } } else { if (logEnable) log.debug "In armNight - securityKeypad already set to 'armed night', so skipping." } } void armNightEnd() { if (!state.code) { state.code = "" } if (!state.type) { state.type = "physical" } def sk = device.currentValue("securityKeypad") if(sk != "armed night") { keypadUpdateStatus(0x00, state.type, state.code) Date now = new Date() long ems = now.getTime() sendEvent(name:"armingIn", value: state.keypadConfig.armNightDelay, data:[armMode: armingStates[0x00].securityKeypadState, armCmd: armingStates[0x00].hsmCmd], isStateChange:true) sendEvent(name:"alarmStatusChangeTime", value: "${now}", isStateChange:true) sendEvent(name:"alarmStatusChangeEpochms", value: "${ems}", isStateChange:true) } } void armAway(delay) { if (logEnable) log.debug "In armAway (${version()}) - delay: ${delay}" def sk = device.currentValue("securityKeypad") if(sk != "armed away") { if (delay > 0 ) { exitDelay(delay) runIn(delay, armAwayEnd) } else if( delay == null ){ // delay is null - event received from device page? let HSM/whatever pick it up debug "armAway: sending emulated event" keypadUpdateStatus(0x0B, "digital", "") sendEvent(name:"armingIn", value: state.keypadConfig.armAwayDelay, data:[armMode: armingStates[0x0B].securityKeypadState, armCmd: armingStates[0x0B].hsmCmd], isStateChange:true) }else{ armAwayEnd() } } else { if (logEnable) log.debug "In armAway - securityKeypad already set to 'armed away', so skipping." } } void armAwayEnd() { if (!state.code) { state.code = "" } if (!state.type) { state.type = "physical" } def sk = device.currentValue("securityKeypad") if(sk != "armed away") { keypadUpdateStatus(0x0B, state.type, state.code) Date now = new Date() long ems = now.getTime() sendEvent(name:"armingIn", value: state.keypadConfig.armAwayDelay, data:[armMode: armingStates[0x0B].securityKeypadState, armCmd: armingStates[0x0B].hsmCmd], isStateChange:true) sendEvent(name:"alarmStatusChangeTime", value: "${now}", isStateChange:true) sendEvent(name:"alarmStatusChangeEpochms", value: "${ems}", isStateChange:true) changeStatus("set") } } void armHome(delay) { if (logEnable) log.debug "In armHome (${version()}) - delay: ${delay}" def sk = device.currentValue("securityKeypad") if(sk != "armed home") { if (delay > 0) { exitDelay(delay) runIn(delay, armHomeEnd) } else if( delay == null ){ // delay is null - event received from device page? let HSM/whatever pick it up debug "armHome: sending emulated event" keypadUpdateStatus(0x0A, "digital", "") sendEvent(name:"armingIn", value: state.keypadConfig.armHomeDelay, data:[armMode: armingStates[0x0A].securityKeypadState, armCmd: armingStates[0x0A].hsmCmd], isStateChange:true) }else{ armHomeEnd() } } else { if (logEnable) log.debug "In armHome - securityKeypad already set to 'armed home', so skipping." } } void armHomeEnd() { if (!state.code) { state.code = "" } if (!state.type) { state.type = "physical" } def sk = device.currentValue("securityKeypad") if(sk != "armed home") { keypadUpdateStatus(0x0A, state.type, state.code) Date now = new Date() long ems = now.getTime() sendEvent(name:"armingIn", value: state.keypadConfig.armHomeDelay, data:[armMode: armingStates[0x0A].securityKeypadState, armCmd: armingStates[0x0A].hsmCmd], isStateChange:true) sendEvent(name:"alarmStatusChangeTime", value: "${now}", isStateChange:true) sendEvent(name:"alarmStatusChangeEpochms", value: "${ems}", isStateChange:true) changeStatus("set") } } void disarm(delay) { if (logEnable) log.debug "In disarm (${version()}) - delay: ${delay}" def sk = device.currentValue("securityKeypad") if(sk != "disarmed") { disarmEnd() } else { if (logEnable) log.debug "In disarm - securityKeypad already set to 'disarmed', so skipping." } } void disarmEnd() { if (!state.code) { state.code = "" } if (!state.type) { state.type = "physical" } def sk = device.currentValue("securityKeypad") if(sk != "disarmed") { keypadUpdateStatus(0x02, state.type, state.code) Date now = new Date() long ems = now.getTime() sendEvent(name:"armingIn", value: 0, data:[armMode: armingStates[0x02].securityKeypadState, armCmd: armingStates[0x02].hsmCmd], isStateChange:true) sendEvent(name:"alarmStatusChangeTime", value: "${now}", isStateChange:true) sendEvent(name:"alarmStatusChangeEpochms", value: "${ems}", isStateChange:true) changeStatus("off") unschedule(armHomeEnd) unschedule(armAwayEnd) unschedule(armNightEnd) unschedule(changeStatus) } else { if (logEnable) log.debug "In disarm - securityKeypad already set to 'disarmed', so skipping." } } void exitDelay(delay){ if (logEnable) log.debug "In exitDelay (${version()}) - delay: ${delay}" if (delay) { sendToDevice(zwave.indicatorV3.indicatorSet(indicatorCount:1, value: 0, indicatorValues:[[indicatorId:0x12, propertyId:7, value:delay.toInteger()]]).format()) // update state so that a disarm command during the exit delay resets the indicator lights state.keypadStatus = "18" type = state.code != "" ? "physical" : "digital" eventProcess(name: "securityKeypad", value: "exit delay", type: type, data: state.code) } } def changeStatus(data) { if (logEnable) log.debug "In changeStatus (${version()}) - data: ${data}" sendEvent(name: "alarm", value: data, isStateChange: true) } void setEntryDelay(delay){ if (logEnable) log.debug "In setEntryDelay (${version()}) - delay: ${delay}" state.keypadConfig.entryDelay = delay != null ? delay.toInteger() : 0 } void setExitDelay(Map delays){ if (logEnable) log.debug "In setExitDelay (${version()}) - delay: ${delays}" state.keypadConfig.exitDelay = (delays?.awayDelay ?: 0).toInteger() state.keypadConfig.armNightDelay = (delays?.nightDelay ?: 0).toInteger() state.keypadConfig.armHomeDelay = (delays?.homeDelay ?: 0).toInteger() state.keypadConfig.armAwayDelay = (delays?.awayDelay ?: 0).toInteger() } void setExitDelay(delay){ if (logEnable) log.debug "In setExitDelay (${version()}) - delay: ${delay}" state.keypadConfig.exitDelay = delay != null ? delay.toInteger() : 0 } void setArmNightDelay(delay){ if (logEnable) log.debug "In setArmNightDelay (${version()}) - delay: ${delay}" sendEvent(name:"armNightDelay", value: delay) state.keypadConfig.armNightDelay = delay != null ? delay.toInteger() : 0 } void setArmAwayDelay(delay){ if (logEnable) log.debug "In setArmAwayDelay (${version()}) - delay: ${delay}" sendEvent(name:"armAwayDelay", value: delay) state.keypadConfig.armAwayDelay = delay != null ? delay.toInteger() : 0 } void setArmHomeDelay(delay){ if (logEnable) log.debug "In setArmHomeDelay (${version()}) - delay: ${delay}" sendEvent(name:"armHomeDelay", value: delay) state.keypadConfig.armHomeDelay = delay != null ? delay.toInteger() : 0 } void setCodeLength(pincodelength) { if (logEnable) log.debug "In setCodeLength (${version()}) - pincodelength: ${pincodelength}" eventProcess(name:"codeLength", value: pincodelength, descriptionText: "${device.displayName} codeLength set to ${pincodelength}") state.keypadConfig.codeLength = pincodelength // set zwave entry code key buffer // 6F06XX10 sendToDevice("6F06" + hubitat.helper.HexUtils.integerToHexString(pincodelength.toInteger()+1,1).padLeft(2,'0') + "0F") } void setPartialFunction(mode = null) { if (logEnable) log.debug "In setPartialFunction (${version()}) - mode: ${mode}" if (!mode) { mode = partialFunctionValue if (logEnable) log.debug "In setPartialFunction (${version()}) - set mode from preferences: ${mode}" } if ( !(mode in ["armHome","armNight"]) ) { if (txtEnable) log.warn "custom command used by HSM" } else if (mode in ["armHome","armNight"]) { state.keypadConfig.partialFunction = mode == "armHome" ? "armHome" : "armNight" } } // alarm capability commands void off() { if (logEnable) log.debug "In off (${version()})" eventProcess(name:"alarm", value:"off") changeStatus("off") sendToDevice(zwave.indicatorV3.indicatorSet(indicatorCount:1, value: 0, indicatorValues:[[indicatorId:state.keypadStatus, propertyId:2, value:0xFF]]).format()) } void both() { if (logEnable) log.debug "In both (${version()})" siren() } void siren() { if (logEnable) log.debug "In Siren (${version()})" eventProcess(name:"alarm", value:"siren") changeStatus("siren") sendToDevice(zwave.indicatorV3.indicatorSet(indicatorCount:1, value: 0, indicatorValues:[[indicatorId:0x0C, propertyId:2, value:0xFF]]).format()) } void strobe() { if (logEnable) log.debug "In strobe (${version()})" eventProcess(name:"alarm", value:"strobe") changeStatus("strobe") List cmds=[] cmds.add(zwave.indicatorV3.indicatorSet(indicatorCount:1, value: 0, indicatorValues:[[indicatorId:0x0C, propertyId:2, value:0xFF]]).format()) cmds.add(zwave.indicatorV3.indicatorSet(indicatorCount:1, value: 0, indicatorValues:[[indicatorId:0x0C, propertyId:2, value:0x00]]).format()) sendToDevice(cmds) } void zwaveEvent(hubitat.zwave.commands.basicv1.BasicReport cmd) { // this is redundant/ambiguous and I don't care what happens here } void parseEntryControl(Short command, List commandBytes) { if (logEnable) log.debug "In parseEntryControl (${version()})" //log.debug "parse: ${command}, ${commandBytes}" if (command == 0x01) { Map ecn = [:] ecn.sequenceNumber = commandBytes[0] ecn.dataType = commandBytes[1] ecn.eventType = commandBytes[2] ecn.eventDataLength = commandBytes[3] def currentStatus = device.currentValue('securityKeypad') def alarmStatus = device.currentValue('alarm') String code="" if (ecn.eventDataLength>0) { for (int i in 4..(ecn.eventDataLength+3)) { if (logEnable) log.debug "character ${i}, value ${commandBytes[i]}" code += (char) commandBytes[i] } if (setLastCode) { sendEvent(name: "lastCode", value: code, isStateChange: true) } else { sendEvent(name: "lastCode", value: "", isStateChange: true) } } if (logEnable) log.debug "Entry control: ${ecn} keycache: ${code}" switch (ecn.eventType) { case 5: // Away Mode Button if (logEnable) log.debug "In case 5 - Away Mode Button" sendEvent(name: "held", value: -1, isStateChange: true) if (validatePin(code) || instantArming) { if(currentStatus == "disarmed") { if (logEnable) log.debug "In case 5 - Passed - currentStatus: ${currentStatus}" state.type="physical" if (!state.keypadConfig.armAwayDelay) { state.keypadConfig.armAwayDelay = 0 } armAway(state.keypadConfig.armAwayDelay) } else { if (logEnable) log.debug "In case 5 - Failed - Please Disarm Alarm before changing alarm type - currentStatus: ${currentStatus}" } } else { if (logEnable) log.debug "In case 5 - Failed - Invalid PIN - currentStatus: ${currentStatus}" sendToDevice(zwave.indicatorV3.indicatorSet(indicatorCount:1, value: 0, indicatorValues:[[indicatorId:0x09, propertyId:2, value:0xFF]]).format()) } break case 6: // Home Mode Button if (logEnable) log.debug "In case 6 - Home Mode Button" sendEvent(name: "held", value: -1, isStateChange: true) if (validatePin(code) || instantArming) { if(currentStatus == "disarmed") { if (logEnable) log.debug "In case 6 - Passed" state.type="physical" if(!state.keypadConfig.partialFunction) state.keypadConfig.partialFunction="armHome" if (state.keypadConfig.partialFunction == "armHome") { if (logEnable) log.debug "In case 6 - Partial Passed" if (!state.keypadConfig.armHomeDelay) { state.keypadConfig.armHomeDelay = 0 } armHome(state.keypadConfig.armHomeDelay) } } else { if(alarmStatus == "active") { if (logEnable) log.debug "In case 6 - Silenced - Alarm will sound again in 10 seconds - currentStatus: ${currentStatus}" changeStatus("silent") runIn(10, changeStatus, [data:"active"]) } else { if (logEnable) log.debug "In case 6 - Failed - Please Disarm Alarm before changing alarm type - currentStatus: ${currentStatus}" } } } else { if (logEnable) log.debug "In case 6 - Home Mode Failed - Invalid PIN - currentStatus: ${currentStatus}" sendToDevice(zwave.indicatorV3.indicatorSet(indicatorCount:1, value: 0, indicatorValues:[[indicatorId:0x09, propertyId:2, value:0xFF]]).format()) } break case 3: // Disarm Mode Button if (logEnable) log.debug "In case 3 - Disarm Mode Button" sendEvent(name: "held", value: -1, isStateChange: true) if (validatePin(code)) { if (logEnable) log.debug "In case 3 - Code Passed" state.type="physical" disarm() } else { if (logEnable) log.debug "In case 3 - Disarm Failed - Invalid PIN - currentStatus: ${currentStatus}" sendToDevice(zwave.indicatorV3.indicatorSet(indicatorCount:1, value: 0, indicatorValues:[[indicatorId:0x09, propertyId:2, value:0xFF]]).format()) } break // Added all buttons case 2: // Code sent after hitting the Check Mark if (logEnable) log.debug "Check Mark Button Pressed" sendEvent(name: "held", value: -1, isStateChange: true) state.type="physical" Date now = new Date() long ems = now.getTime() if(!code) code = "check mark" if (validateCheck) { if (validatePin(code)) { if (logEnable) log.debug "In case 2 (check mark) - Code Passed" } else { if (logEnable) log.debug "In case 2 (check mark) - Code Failed - Invalid PIN - currentStatus: ${currentStatus}" sendToDevice(zwave.indicatorV3.indicatorSet(indicatorCount:1, value: 0, indicatorValues:[[indicatorId:0x09, propertyId:2, value:0xFF]]).format()) } } else { sendEvent(name:"validCode", value: "false", isStateChange: true) sendEvent(name:"lastCodeName", value: "${code}", isStateChange:true) sendEvent(name:"lastCodeTime", value: "${now}", isStateChange:true) sendEvent(name:"lastCodeEpochms", value: "${ems}", isStateChange:true) } break case 17: // Police Button if (logEnable) log.debug "Police Button Pressed" state.type="physical" Date now = new Date() long ems = now.getTime() sendEvent(name:"validCode", value: "false", isStateChange: true) sendEvent(name:"lastCodeName", value: "police", isStateChange:true) sendEvent(name:"lastCodeTime", value: "${now}", isStateChange:true) sendEvent(name:"lastCodeEpochms", value: "${ems}", isStateChange:true) sendEvent(name: "held", value: 11, isStateChange: true) break case 16: // Fire Button if (logEnable) log.debug "Fire Button Pressed" state.type="physical" Date now = new Date() long ems = now.getTime() sendEvent(name:"validCode", value: "false", isStateChange: true) sendEvent(name:"lastCodeName", value: "fire", isStateChange:true) sendEvent(name:"lastCodeTime", value: "${now}", isStateChange:true) sendEvent(name:"lastCodeEpochms", value: "${ems}", isStateChange:true) sendEvent(name: "held", value: 12, isStateChange: true) break case 19: // Medical Button if (logEnable) log.debug "Medical Button Pressed" state.type="physical" Date now = new Date() long ems = now.getTime() sendEvent(name:"validCode", value: "false", isStateChange: true) sendEvent(name:"lastCodeName", value: "medical", isStateChange:true) sendEvent(name:"lastCodeTime", value: "${now}", isStateChange:true) sendEvent(name:"lastCodeEpochms", value: "${ems}", isStateChange:true) sendEvent(name: "held", value: 13, isStateChange: true) break case 25: // X Button if (logEnable) log.debug "X Button Pressed" state.type="physical" sendEvent(name:"validCode", value: "false", isStateChange: true) sendEvent(name:"lastCodeName", value: "", isStateChange:true) sendEvent(name: "held", value: -1, isStateChange: true) break case 1: // Button pressed or held, idle timeout reached without explicit submission if (logEnable) log.debug "In case 1 - Code Entry Ended" state.type="physical" handleButtons(code) break case 0: // Code entry started if (logEnable) log.debug "In case 0 Code Entry Started" sendEvent(name:"validCode", value: "false", isStateChange: true) sendEvent(name:"lastCode", value: "", isStateChange: true) sendEvent(name:"lastCodeName", value: "", isStateChange:true) sendEvent(name: "held", value: -1, isStateChange: true) break } } } void handleButtons(String code) { List buttons = code.split('') for (String btn : buttons) { try { int val = Integer.parseInt(btn) sendEvent(name: "pushed", value: val, isStateChange: true) sendEvent(name: "held", value: -1, isStateChange: true) } catch (NumberFormatException e) { // Handle button holds here char ch = btn char a = 'A' int pos = ch - a + 1 sendEvent(name: "pushed", value: -1, isStateChange: true) sendEvent(name: "held", value: pos, isStateChange: true) } } } void push(btn) { state.type = "digital" sendEvent(name: "pushed", value: btn, isStateChange: true) sendEvent(name: "held", value: -1, isStateChange: true) } void hold(btn) { state.type = "digital" sendEvent(name: "pushed", value: -1, isStateChange: true) sendEvent(name: "held", value: btn, isStateChange:true) switch (btn) { case 11: Date now = new Date() long ems = now.getTime() sendEvent(name:"lastCodeName", value: "police", isStateChange:true) sendEvent(name:"lastCodeTime", value: "${now}", isStateChange:true) sendEvent(name:"lastCodeEpochms", value: "${ems}", isStateChange:true) break case 12: Date now = new Date() long ems = now.getTime() sendEvent(name:"lastCodeName", value: "fire", isStateChange:true) sendEvent(name:"lastCodeTime", value: "${now}", isStateChange:true) sendEvent(name:"lastCodeEpochms", value: "${ems}", isStateChange:true) break case 13: Date now = new Date() long ems = now.getTime() sendEvent(name:"lastCodeName", value: "medical", isStateChange:true) sendEvent(name:"lastCodeTime", value: "${now}", isStateChange:true) sendEvent(name:"lastCodeEpochms", value: "${ems}", isStateChange:true) break } } void zwaveEvent(hubitat.zwave.commands.notificationv8.NotificationReport cmd) { Map evt = [:] if (cmd.notificationType == 8) { // power management switch (cmd.event) { case 1: // Power has been applied if (txtEnable) log.info "${device.displayName} Power has been applied" break case 2: // AC mains disconnected evt.name = "powerSource" evt.value = "battery" evt.descriptionText = "${device.displayName} AC mains disconnected" eventProcess(evt) break case 3: // AC mains re-connected evt.name = "powerSource" evt.value = "mains" evt.descriptionText = "${device.displayName} AC mains re-connected" eventProcess(evt) break case 12: // battery is charging if (txtEnable) log.info "${device.displayName} Battery is charging" break } } } void entry(){ int intDelay = state.keypadConfig.entryDelay ? state.keypadConfig.entryDelay.toInteger() : 0 if (intDelay) entry(intDelay) } void entry(entranceDelay){ if (logEnable) log.debug "In entry (${version()}) - delay: ${entranceDelay}" if (entranceDelay) { sendToDevice(zwave.indicatorV3.indicatorSet(indicatorCount:1, value: 0, indicatorValues:[[indicatorId:0x11, propertyId:7, value:entranceDelay.toInteger()]]).format()) } } void getCodes(){ if (logEnable) log.debug "getCodes()" updateEncryption() } private updateEncryption(){ String lockCodes = device.currentValue("lockCodes") //encrypted or decrypted if (lockCodes){ if (optEncrypt && lockCodes[0] == "{") { //resend encrypted sendEvent(name:"lockCodes",value: encrypt(lockCodes), isStateChange:true) } else if (!optEncrypt && lockCodes[0] != "{") { //resend decrypted sendEvent(name:"lockCodes", value: decrypt(lockCodes), isStateChange:true) } else { sendEvent(name:"lockCodes", value: lockCodes, isStateChange:true) } } } private Boolean validatePin(String pincode) { boolean retVal = false Map lockcodes = [:] if (optEncrypt) { try { lockcodes = parseJson(decrypt(device.currentValue("lockCodes"))) } catch(e) { log.warn "Ring Alarm Keypad G2 Community - No lock codes found." } } else { try { lockcodes = parseJson(device.currentValue("lockCodes")) } catch(e) { log.warn "Ring Alarm Keypad G2 Community - No lock codes found." } } //log.debug "Lock codes: ${lockcodes}" if(lockcodes) { lockcodes.each { if(it.value["code"] == pincode) { Date now = new Date() long ems = now.getTime() //log.debug "found code: ${pincode} user: ${it.value['name']}" sendEvent(name:"validCode", value: "true", isStateChange: true) if (!setLastCode) sendEvent(name: "lastCode", value: "valid", isStateChange: true) sendEvent(name:"lastCodeName", value: "${it.value['name']}", isStateChange:true) sendEvent(name:"lastCodeTime", value: "${now}", isStateChange:true) sendEvent(name:"lastCodeEpochms", value: "${ems}", isStateChange:true) retVal=true String code = JsonOutput.toJson(["${it.key}":["name": "${it.value.name}", "code": "${it.value.code}", "isInitiator": true]]) if (optEncrypt) { state.code=encrypt(code) } else { state.code=code } } } } if (!retVal) { if (!setLastCode) sendEvent(name: "lastCode", value: "invalid", isStateChange: true) sendEvent(name:"validCode", value: "false", isStateChange: true) } return retVal } void setCode(codeposition, pincode, name) { if (logEnable) log.debug "setCode(${codeposition}, ${pincode}, ${name})" boolean newCode = true Map lockcodes = [:] if (device.currentValue("lockCodes") != null) { if (optEncrypt) { lockcodes = parseJson(decrypt(device.currentValue("lockCodes"))) } else { lockcodes = parseJson(device.currentValue("lockCodes")) } } if (lockcodes["${codeposition}"]) { newCode = false } lockcodes["${codeposition}"] = ["code": "${pincode}", "name": "${name}"] if (optEncrypt) { sendEvent(name: "lockCodes", value: encrypt(JsonOutput.toJson(lockcodes))) } else { sendEvent(name: "lockCodes", value: JsonOutput.toJson(lockcodes), isStateChange: true) } if (newCode) { sendEvent(name: "codeChanged", value:"added") } else { sendEvent(name: "codeChanged", value: "changed") } //log.debug "Lock codes: ${lockcodes}" } void deleteCode(codeposition) { if (logEnable) log.debug "deleteCode(${codeposition})" Map lockcodes=[:] if (device.currentValue("lockCodes") != null) { if (optEncrypt) { lockcodes = parseJson(decrypt(device.currentValue("lockCodes"))) } else { lockcodes = parseJson(device.currentValue("lockCodes")) } } lockcodes["${codeposition}"] = [:] lockcodes.remove("${codeposition}") if (optEncrypt) { sendEvent(name: "lockCodes", value: encrypt(JsonOutput.toJson(lockcodes))) } else { sendEvent(name: "lockCodes", value: JsonOutput.toJson(lockcodes), isStateChange: true) } sendEvent(name: "codeChanged", value: "deleted") //log.debug "remove ${codeposition} Lock codes: ${lockcodes}" } void zwaveEvent(hubitat.zwave.commands.indicatorv3.IndicatorReport cmd) { if (logEnable) log.debug "Ignoring zwaveEvent Indicator Report" // Don't need to handle reports } // standard config List runConfigs() { List cmds = [] configParams.each { param, data -> if (settings[data.input.name]) { cmds.addAll(configCmd(param, data.parameterSize, settings[data.input.name])) } } return cmds } List pollConfigs() { List cmds = [] configParams.each { param, data -> if (settings[data.input.name]) { cmds.add(zwave.configurationV1.configurationGet(parameterNumber: param.toInteger()).format()) } } return cmds } List configCmd(parameterNumber, size, scaledConfigurationValue) { List cmds = [] cmds.add(zwave.configurationV1.configurationSet(parameterNumber: parameterNumber.toInteger(), size: size.toInteger(), scaledConfigurationValue: scaledConfigurationValue.toInteger()).format()) cmds.add(zwave.configurationV1.configurationGet(parameterNumber: parameterNumber.toInteger()).format()) return cmds } void zwaveEvent(hubitat.zwave.commands.configurationv1.ConfigurationReport cmd) { if (logEnable) log.debug "Handling zwaveEvent Configuration Report" if(configParams[cmd.parameterNumber.toInteger()]) { Map configParam = configParams[cmd.parameterNumber.toInteger()] int scaledValue cmd.configurationValue.reverse().eachWithIndex { v, index -> scaledValue = scaledValue | v << (8 * index) } device.updateSetting(configParam.input.name, [value: "${scaledValue}", type: configParam.input.type]) } } // Battery v1 void zwaveEvent(hubitat.zwave.commands.batteryv1.BatteryReport cmd) { if (logEnable) log.debug "Handling zwaveEvent Battery Report" Map evt = [name: "battery", unit: "%"] if (cmd.batteryLevel == 0xFF) { evt.descriptionText = "${device.displayName} has a low battery" evt.value = 1 } else { evt.value = cmd.batteryLevel evt.descriptionText = "${device.displayName} battery is ${evt.value}${evt.unit}" } evt.isStateChange = true if (txtEnable && evt.descriptionText) log.info evt.descriptionText sendEvent(evt) } // MSP V2 void zwaveEvent(hubitat.zwave.commands.manufacturerspecificv2.DeviceSpecificReport cmd) { if (logEnable) log.debug "Handling zwaveEvent Device Specific Report" if (logEnable) log.debug "Device Specific Report - DeviceIdType: ${cmd.deviceIdType}, DeviceIdFormat: ${cmd.deviceIdDataFormat}, Data: ${cmd.deviceIdData}" if (cmd.deviceIdType == 1) { String serialNumber = "" if (cmd.deviceIdDataFormat == 1) { cmd.deviceIdData.each { serialNumber += hubitat.helper.HexUtils.integerToHexString(it & 0xff,1).padLeft(2, '0')} } else { cmd.deviceIdData.each { serialNumber += (char) it } } device.updateDataValue("serialNumber", serialNumber) } } // Version V2 void zwaveEvent(hubitat.zwave.commands.versionv3.VersionReport cmd) { if (logEnable) log.debug "Handling zwaveEvent Version Report" Double firmware0Version = cmd.firmware0Version + (cmd.firmware0SubVersion / 100) Double protocolVersion = cmd.zWaveProtocolVersion + (cmd.zWaveProtocolSubVersion / 100) if (logEnable) log.debug "Version Report - FirmwareVersion: ${firmware0Version}, ProtocolVersion: ${protocolVersion}, HardwareVersion: ${cmd.hardwareVersion}" device.updateDataValue("firmwareVersion", "${firmware0Version}") device.updateDataValue("protocolVersion", "${protocolVersion}") device.updateDataValue("hardwareVersion", "${cmd.hardwareVersion}") if (cmd.firmwareTargets > 0) { cmd.targetVersions.each { target -> Double targetVersion = target.version + (target.subVersion / 100) device.updateDataValue("firmware${target.target}Version", "${targetVersion}") } } } // Association V2 List setDefaultAssociation() { List cmds = [] cmds.add(zwave.associationV2.associationSet(groupingIdentifier: 1, nodeId: zwaveHubNodeId).format()) cmds.add(zwave.associationV2.associationGet(groupingIdentifier: 1).format()) return cmds } List processAssociations(){ List cmds = [] cmds.addAll(setDefaultAssociation()) return cmds } void zwaveEvent(hubitat.zwave.commands.associationv2.AssociationReport cmd) { if (logEnable) log.debug "Handling zwaveEvent Association Report" List temp = [] if (cmd.nodeId != []) { cmd.nodeId.each { temp.add(it.toString().format( '%02x', it.toInteger() ).toUpperCase()) } } if (logEnable) log.debug "Association Report - Group: ${cmd.groupingIdentifier}, Nodes: $temp" } // event filter void eventProcess(Map evt) { if (txtEnable && evt.descriptionText) log.info evt.descriptionText if (device.currentValue(evt.name).toString() != evt.value.toString()) { sendEvent(evt) } } // universal void zwaveEvent(hubitat.zwave.Command cmd) { if (logEnable) log.debug "Ignoring zwaveEvent Command:${cmd}" } void zwaveEvent(hubitat.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) { if (logEnable) log.debug "Handling zwaveEvent Security Message Encapsulation:${cmd}" hubitat.zwave.Command encapsulatedCommand = cmd.encapsulatedCommand(CMD_CLASS_VERS) if (encapsulatedCommand) { zwaveEvent(encapsulatedCommand) } } void zwaveEvent(hubitat.zwave.commands.supervisionv1.SupervisionGet cmd) { if (logEnable) log.debug "Supervision Get - SessionID: ${cmd.sessionID}, CC: ${cmd.commandClassIdentifier}, Command: ${cmd.commandIdentifier}" if (cmd.commandClassIdentifier == 0x6F) { if (logEnable) log.debug "Processing command" parseEntryControl(cmd.commandIdentifier, cmd.commandByte) } else { if (logEnable) log.debug "Re-driving zwaveEvent for this command" hubitat.zwave.Command encapsulatedCommand = cmd.encapsulatedCommand(CMD_CLASS_VERS) if (encapsulatedCommand) { zwaveEvent(encapsulatedCommand) } } // device quirk requires this to be unsecure reply sendToDevice(zwave.supervisionV1.supervisionReport(sessionID: cmd.sessionID, reserved: 0, moreStatusUpdates: false, status: 0xFF, duration: 0).format()) } //void zwaveEvent(hubitat.zwave.commands.supervisionv1.SupervisionGet cmd, ep = 0) { // if (logEnable) log.debug "Supervision Get - SessionID: ${cmd.sessionID}, CC: ${cmd.commandClassIdentifier}, Command: ${cmd.commandIdentifier}, ep: ${ep}" // hubitat.zwave.Command encapsulatedCommand = cmd.encapsulatedCommand(CMD_CLASS_VERS) // if (encapsulatedCommand) { // if (logEnable) log.debug "Re-driving zwaveEvent for this command" // zwaveEvent(encapsulatedCommand, ep) // } // if (ep > 0) { // sendHubCommand(new hubitat.device.HubAction(zwaveSecureEncap(zwave.multiChannelV4.multiChannelCmdEncap(sourceEndPoint: 0, bitAddress: 0, res01: 0, destinationEndPoint: ep).encapsulate(zwave.supervisionV1.supervisionReport(sessionID: cmd.sessionID, reserved: 0, moreStatusUpdates: false, status: 0xFF, duration: 0)).format()), hubitat.device.Protocol.ZWAVE)) // } else { // sendHubCommand(new hubitat.device.HubAction(zwaveSecureEncap(zwave.supervisionV1.supervisionReport(sessionID: cmd.sessionID, reserved: 0, moreStatusUpdates: false, status: 0xFF, duration: 0).format()), hubitat.device.Protocol.ZWAVE)) // } // if (cmd.commandClassIdentifier == 0x6F) { // if (logEnable) log.debug "Processing command" // parseEntryControl(cmd.commandIdentifier, cmd.commandByte) // } else { // if (logEnable) log.debug "Ignoring command" // } //} void parse(String description) { if (logEnable) log.debug "parse - ${description}" ver = getDataValue("firmwareVersion") if(ver >= "1.18") { if(description.contains("6C01") && description.contains("FF 07 08 00")) { sendEvent(name:"motion", value: "active", isStateChange:true) } else if(description.contains("6C01") && description.contains("FF 07 00 01 08")) { sendEvent(name:"motion", value: "inactive", isStateChange:true) } } hubitat.zwave.Command cmd = zwave.parse(description, CMD_CLASS_VERS) if (cmd) { zwaveEvent(cmd) } } void sendToDevice(List cmds, Long delay=300) { sendHubCommand(new hubitat.device.HubMultiAction(commands(cmds, delay), hubitat.device.Protocol.ZWAVE)) } void sendToDevice(String cmd, Long delay=300) { sendHubCommand(new hubitat.device.HubAction(zwaveSecureEncap(cmd), hubitat.device.Protocol.ZWAVE)) } List commands(List cmds, Long delay=300) { return delayBetween(cmds.collect{ zwaveSecureEncap(it) }, delay) } void proximitySensorHandler() { if(disableProximitySensor) { if (logEnable) log.debug "Turning the Proximity Sensor OFF" sendToDevice(new hubitat.zwave.commands.configurationv1.ConfigurationSet(parameterNumber: 15, size: 1, scaledConfigurationValue: 0).format()) } else { if (logEnable) log.debug "Turning the Proximity Sensor ON" sendToDevice(new hubitat.zwave.commands.configurationv1.ConfigurationSet(parameterNumber: 15, size: 1, scaledConfigurationValue: 1).format()) } } def volAnnouncement(newVol=null) { if (logEnable) log.debug "In volAnnouncement (${version()}) - newVol: ${newVol}" if(newVol) { def currentVol = device.currentValue('volAnnouncement') if(newVol.toString() == currentVol.toString()) { if (logEnable) log.debug "Announcement Volume hasn't changed, so skipping" } else { if (logEnable) log.debug "Setting the Announcement Volume to $newVol" nVol = newVol.toInteger() sendToDevice(new hubitat.zwave.commands.configurationv1.ConfigurationSet(parameterNumber: 4, size: 1, scaledConfigurationValue: nVol).format()) sendEvent(name:"volAnnouncement", value: newVol, isStateChange:true) } } else { if (logEnable) log.debug "Announcement value not specified, so skipping" } } def volKeytone(newVol=null) { if (logEnable) log.debug "In volKeytone (${version()}) - newVol: ${newVol}" if(newVol) { def currentVol = device.currentValue('volKeytone') if(newVol.toString() == currentVol.toString()) { if (logEnable) log.debug "Keytone Volume hasn't changed, so skipping" } else { if (logEnable) log.debug "Setting the Keytone Volume to $newVol" nVol = newVol.toInteger() sendToDevice(new hubitat.zwave.commands.configurationv1.ConfigurationSet(parameterNumber: 5, size: 1, scaledConfigurationValue: nVol).format()) sendEvent(name:"volKeytone", value: newVol, isStateChange:true) } } else { if (logEnable) log.debug "Keytone value not specified, so skipping" } } def volSiren(newVol=null) { if (logEnable) log.debug "In volSiren (${version()}) - newVol: ${newVol}" if(newVol) { def currentVol = device.currentValue('volSiren') if(newVol.toString() == currentVol.toString()) { if (logEnable) log.debug "Siren Volume hasn't changed, so skipping" def sVol = currentVol.toInteger() * 10 } else { if (logEnable) log.debug "Setting the Siren Volume to $newVol" sVol = newVol.toInteger() * 10 sendToDevice(new hubitat.zwave.commands.configurationv1.ConfigurationSet(parameterNumber: 6, size: 1, scaledConfigurationValue: sVol).format()) sendEvent(name:"volSiren", value: newVol, isStateChange:true) } } else { def currentVol = device.currentValue('volSiren') if(currentVol) { sVol = currentVol.toInteger() * 10 } else { sVol = 90 } } return sVol } def playTone(tone=null) { volSiren() if (logEnable) log.debug "In playTone (${version()}) - tone: ${tone} at Volume: ${sVol}" if(!tone) { tone = theTone if (logEnable) log.debug "In playTone - Tone is NULL, so setting tone to theTone: ${tone}" } if(tone == "Tone_1") { if (logEnable) log.debug "In playTone - Tone 1" // Siren changeStatus("active") sendToDevice(zwave.indicatorV3.indicatorSet(indicatorCount:1, value: 0, indicatorValues:[[indicatorId:0x0C, propertyId:2, value:sVol]]).format()) } else if(tone == "Tone_2") { if (logEnable) log.debug "In playTone - Tone 2" // 3 chirps changeStatus("active") sendToDevice(zwave.indicatorV3.indicatorSet(indicatorCount:1, value: 0, indicatorValues:[[indicatorId:0x0E, propertyId:2, value:sVol]]).format()) } else if(tone == "Tone_3") { if (logEnable) log.debug "In playTone - Tone 3" // 4 chirps changeStatus("active") sendToDevice(zwave.indicatorV3.indicatorSet(indicatorCount:1, value: 0, indicatorValues:[[indicatorId:0x0F, propertyId:2, value:sVol]]).format()) } else if(tone == "Tone_4") { if (logEnable) log.debug "In playTone - Tone 4" // Navi changeStatus("active") sendToDevice(zwave.indicatorV3.indicatorSet(indicatorCount:1, value: 0, indicatorValues:[[indicatorId:0x60, propertyId:0x09, value:sVol]]).format()) } else if(tone == "Tone_5") { if (logEnable) log.debug "In playTone - Tone 5" // Guitar changeStatus("active") sendToDevice(zwave.indicatorV3.indicatorSet(indicatorCount:1, value: 0, indicatorValues:[[indicatorId:0x61, propertyId:0x09, value:sVol]]).format()) } else if(tone == "Tone_6") { if (logEnable) log.debug "In playTone - Tone 6" // Windchimes changeStatus("active") sendToDevice(zwave.indicatorV3.indicatorSet(indicatorCount:1, value: 0, indicatorValues:[[indicatorId:0x62, propertyId:0x09, value:sVol]]).format()) } else if(tone == "Tone_7") { if (logEnable) log.debug "In playTone - Tone 7" // Doorbell 1 changeStatus("active") sendToDevice(zwave.indicatorV3.indicatorSet(indicatorCount:1, value: 0, indicatorValues:[[indicatorId:0x63, propertyId:0x09, value:sVol]]).format()) } else if(tone == "Tone_8") { if (logEnable) log.debug "In playTone - Tone 8" // Doorbell 2 changeStatus("active") sendToDevice(zwave.indicatorV3.indicatorSet(indicatorCount:1, value: 0, indicatorValues:[[indicatorId:0x64, propertyId:0x09, value:sVol]]).format()) } else if(tone == "Tone_9") { if (logEnable) log.debug "In playTone - Tone 9" // Invalid Code Sound changeStatus("active") sendToDevice(zwave.indicatorV3.indicatorSet(indicatorCount:1, value: 0, indicatorValues:[[indicatorId:0x09, propertyId:0x01, value:sVol]]).format()) } else if(tone == "test") { if (logEnable) log.debug "In playTone - test" // test changeStatus("active") //sendToDevice(zwave.indicatorV3.indicatorSet(indicatorCount:1, value: 0, indicatorValues:[[indicatorId:0x61, propertyId:0x09, value:0xFF]]).format()) //pauseExecution(5000) sendToDevice(zwave.indicatorV3.indicatorSet(indicatorCount:1, value: 0, indicatorValues:[[indicatorId:0x61, propertyId:0x09, value:sVol]]).format()) } }