[Deprecated] Ring Alarm Keypad G2 - New Dev/Driver/Thread Links in 1st post!

Yes. It is being worked on but not getting anywhere with it.

Thanks

Not seeing the (Ring Keypad Sync) with your bundle manager. Am I missing something?

Yup!

Doh! Found it. Thanks! I was looking in another place.

1 Like

@bptworld do you know if the announcement volume and siren volumes are correctly implemented? It seems no matter what volume I choose for announcements & siren, they are the same. Keypad tones work fine, but at volume "4" the keypad tones are significantly louder than announcements or sirens.

Any tips? Just getting around to setting up HSM and love that this driver works with HSM natively. Thanks again for all the amazing work on this driver.

Scratch that!! I had to disable the proximity sensor now it works fine. I guess pulling the air gaps couldn't hurt but as soon as I turned the proximity sensor back on poof.

NM the problem was on my end. I fixed it by disconnecting power to all repeating devices. Started working right away. Man ZWAVE Hurts! Air gaps are awesome BTW

I just bought 3 of these a few days ago. They get one way zwave communication. I can get them working temporarily if I mess around a lot. Maybe it's because they shipped with a new firmware not sure. Same problem if you use this driver or built in driver. They work perfect with Home Assistant so yeah I don't know. I've seen other closed posts on the forum with the same issue with no solution. No it's not a ghost thing or non s2. Might want to take it off the compatibility list. Video of the issues, I get it working around 4:00 set it to 4k so you can see

Few questions.

Are those the hubs right next to it. May want to seperate them by a little distance? Could you try seperating them by about 1m. It is my understanding to close can cause a issue.

Have you upgraded the Zwave radio to the latest firmware?

By chance do you have any zwave devices that do power reporting?.

The fact you can get it working on and off to me seems like either the device itself is locking for sone reason or there is a radio problem with communication. My keypad does lock occasionally, but they are few and far between.

1 Like

They normally sit in the server rack just threw them on the desk for the video. The've never been a meter apart lol everything else works really well. One runs my Zwave one runs zigbee.

I have plenty of wave devices that can power report but the reporting is turned off.

I agree but my zwave actually works really well. I could make a video of that but that be even more boring then this video. Trust me things happen instantly, well um instantly as zwave can be. The keypad is direct not using any repeaters and as you can see in the zwave log there's no weird traffic going on.

The zwave log reports some stuff. But may not show all wonkyness.

The Zooz zen25 is a great example of a device that is know for causing strange Zwave issues and not showing anything strange in the Zwave logs. Folks can turn off power reporting, pair it without security and it will cause issues.

1 Like

I Fixed the problem by disabling the proximity sensor. I guess it ended up being a driver problem after all with the latest firmware Didn't hurt anything to go over my zwave network though.
You were right. You got me thinking it more than likely has to be my network. I just forgot the advice I use to give out myself and go around the house and either pull the air gaps or unplug any repeating device. Seems to have fixed the issues . Thank you

That actually kind of makes sense. The proximity sensor has had a few changes to it made over time. Early on it was not controllable at all, and did not send any data back to HE. Then at some point it started reporting and that is when @bptworld enabled it to be used as a motion sensor. Perhaps there is some compatibility issue with it with the latest firmware version.

So you just flipped the switch in the preferences to "Disable the Proximity Sensor"?

Yep Just flipped the switch to disable the Proximity sensor. Keypads been working right since, you just have to make sure to turn the proximity sensor off when motion is inactive or else there problems. Kinda sucks because now you have to actual push a button to see system status. I'll keep one of them on HE and put the other two on HA since there's no problems using them with HA You can actually see it break in the first few seconds of my video. Drove me crazy figuring this out.

Just sent you a pull request to fix night mode on the device page.

Merged, thank you!

1 Like

I see the problem now.

Arm away (etc) events from the keypad are received by parseEntryControl -> armAway -> armAwayEnd

However, the Arm Away/Arm Home/Disarm buttons on the device page (provided by capability SecurityKeypad) call armAway/disarm directly.

The sendEvent armingIn that HSM subscribes to is only inside parseEntryControl.

So, the device page goes direct (never touches parseEntryControl) -- which never get the opportunity to send the armingIn event.

I moved the armingIn event inside armHomeEnd/armAwayEnd/armNightEnd/disarmEnd and all seems to work.

  • rule machine can arm/disarm HSM and keypads reflect correctly
  • device page arms/disarms HSM and keypads reflect correctly
  • keypads arm/disarm HSM and other keypads stay in sync

w00t.

Can you test this before merging ?

Seems to work.

Nope, sorry. I've moved all my keypads over to a Ring hub. Feel free to branch off this driver and continue to offer it up to the community.

doh! I'd appreciate if someones reading this can give it a test.

code is temporarily here

EDIT: code was merged, fingers crossed.

I’ll give it a shot. I have just one keypad but I’ll order another if this works. Thanks for working on it.

During Black Friday I almost bought more into the ring ecosystem, but I held off for another year.

I’ll reply back with an update soon.

I'm not an expert on GitHub stuff (yet, anyway) and I would hate to bust something too badly...

But, I was looking at the driver and made some changes (on my hub):

  1. I added a preference for the "Home Arm" button:
  • After "validateCheck", I inserted this:
       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"
  • Then, in the "setPartialFunction" function, below the "if (logEnable)" line, I added this (and I fixed the spelling of "setPartialFuntion" in that logging line also) :
    if (!mode) {
        mode = partialFunctionValue
        if (logEnable) log.debug "In setPartialFunction (${version()}) - set mode from preferences: ${mode}"
    }
  1. I added code to set "validCode" to FALSE in 4 places in the "parseEntryControl" function:
  • In "case 2" (check mark), in the else clause for the "if (validateCheck)" test
  • In "case 17" (police), before the first existing "SendEvent"
  • In "case 16" (fire), before the first existing "SendEvent"
  • In "case 19" (medical), before the first existing "SendEvent"

using this line of code:

sendEvent(name:"validCode", value: "false", isStateChange: true)
  1. Updated the "armNightEnd" function to set the keypad status:
  • Uncommented the "keypadUpdateStatus" call
  • Updated the "keypadUpdateStatus" function to send the "0x0A" status to the keypad (like Armed Home) while still setting the status to "0x00" in the driver for HSM, inserting a few lines then updating the existing "sendToDevice" call as follows
    int kpstatus = status
    if (kpstatus == 0x00) kpstatus = 0x0A

    sendToDevice(zwave.indicatorV3.indicatorSet(indicatorCount:1, value: 0, indicatorValues:[[indicatorId:kpstatus, propertyId:2, value:0xFF]]).format())
Full Driver Code
/*
    Ring Keypad Gen 2 - Community Driver

    Copyright 2020 -> 2021 Hubitat Inc.  All Rights Reserved
    Special Thanks to Bryan Copeland (@bcopeland) for writing and releasing this code to the community!

    1.2.9 - 12/04/22 - Force "validCode" false for Police, Fire, Medical buttons and when "Check Mark" codes aren't validated, add preference to allow Partial Function to be set, set the keypad status in armNightEnd
    1.2.8 - 12/01/22 - Fix device page buttons by sending armingIn event in the arm/disarm subs
    1.2.7 - 12/01/22 - Fix Arm Night on device page
    1.2.6 - 08/05/22 - Adjusted for use with HSM. To sync keypads without using HPM, a separate app will be available in Bundle Manager (Ring Keypad Sync).
    1.2.4 - 07/01/22 - Rollback to working version
    1.2.2 - 06/09/22 - @dkilgore90 add "validCode" attribute and "validateCheck" preference
    1.2.1 - 04/14/22 - Bug hunting
    1.2.0 - 04/04/22 - Fixed Tones
    ---
    1.0.0 - 11/11/21 - Initial Community Release
*/

import groovy.transform.Field
import groovy.json.JsonOutput

def version() {
    return "1.2.8.1"
}

metadata {
    definition (name: "Ring Alarm Keypad G2 Community", namespace: "hubitat", author: "Community") {
        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 "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()}<br>Note:<br>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: "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: "proximitySensor", 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 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<String> 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) {
    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 {
            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[0x0B].securityKeypadState, armCmd: armingStates[0x0B].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 {
            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 {
            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") {
        if (delay > 0 ) {
            exitDelay(delay)
            runIn(delay, disarmEnd)
        } else {
            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)
    }
}

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<String> 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<Short> 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 (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"
                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"
                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"
                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
                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
                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
                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
                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 1:     // Button pressed or held, idle timeout reached without explicit submission
                state.type="physical"
                handleButtons(code)
                break
        }
    }
}

void handleButtons(String code) {
    List<String> buttons = code.split('')
    for (String btn : buttons) {
        try {
            int val = Integer.parseInt(btn)
            sendEvent(name: "pushed", value: val, isStateChange: true)
        } catch (NumberFormatException e) {
            // Handle button holds here
            char ch = btn
            char a = 'A'
            int pos = ch - a + 1
            sendEvent(name: "held", value: pos, isStateChange: true)
        }
    }
}

void push(btn) {
    state.type = "digital"
    sendEvent(name: "pushed", value: btn, isStateChange: true)
}

void hold(btn) {
    state.type = "digital"
    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)
                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) {
        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) {
    // Don't need to handle reports
}

// standard config

List<String> runConfigs() {
    List<String> cmds = []
    configParams.each { param, data ->
        if (settings[data.input.name]) {
            cmds.addAll(configCmd(param, data.parameterSize, settings[data.input.name]))
        }
    }
    return cmds
}

List<String> pollConfigs() {
    List<String> cmds = []
    configParams.each { param, data ->
        if (settings[data.input.name]) {
            cmds.add(zwave.configurationV1.configurationGet(parameterNumber: param.toInteger()).format())
        }
    }
    return cmds
}

List<String> configCmd(parameterNumber, size, scaledConfigurationValue) {
    List<String> 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(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) {
    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 "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) {
    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<String> setDefaultAssociation() {
    List<String> cmds = []
    cmds.add(zwave.associationV2.associationSet(groupingIdentifier: 1, nodeId: zwaveHubNodeId).format())
    cmds.add(zwave.associationV2.associationGet(groupingIdentifier: 1).format())
    return cmds
}

List<String> processAssociations(){
    List<String> cmds = []
    cmds.addAll(setDefaultAssociation())
    return cmds
}

void zwaveEvent(hubitat.zwave.commands.associationv2.AssociationReport cmd) {
    List<String> 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 "skip:${cmd}"
}

void zwaveEvent(hubitat.zwave.commands.securityv1.SecurityMessageEncapsulation 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) {
        parseEntryControl(cmd.commandIdentifier, cmd.commandByte)
    } else {
        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 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<String> 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<String> commands(List<String> cmds, Long delay=300) {
    return delayBetween(cmds.collect{ zwaveSecureEncap(it) }, delay)
}

void proximitySensorHandler() {
    if(proximitySensor) {
        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())
    }
}

I have a few questions about that driver...

  1. I can't tell what the "Keypad Update Status" function does. It accepts a string from the device page and appears to call the "keypadUpdateStatus" function in the driver--but that function expects 3 parameters (an integer and 2 strings). Not sure it could possibly work as-is??

  2. I don't understand why the "armNightEnd" function isn't calling the "keypadUpdateStatus" function (like all the other arm/disarm functions). In my code above, I tried something, guessing that the "0x00" code that HSM likes wasn't valid for the Ring Keypad--so I sent the "0x0A" code to the keypad and retained the "0x00" code for the "event" that HSM should handle.

Thoughts?