/* * * AlertMe Fob Driver * */ @Field String driverVersion = "v1.32 (25th August 2023)" #include BirdsLikeWires.alertme #include BirdsLikeWires.library import groovy.transform.Field @Field boolean debugMode = false @Field int reportIntervalMinutes = 2 @Field int checkEveryMinutes = 1 @Field int rangeEveryHours = 6 metadata { definition (name: "AlertMe Fob", namespace: "BirdsLikeWires", author: "Andrew Davison", importUrl: "https://raw.githubusercontent.com/birdslikewires/hubitat/master/alertme/drivers/alertme_fob.groovy") { capability "Battery" capability "Configuration" //capability "PresenceSensor" // to be re-enabled as this is a real thing this device would be used for capability "PushableButton" capability "Refresh" capability "ReleasableButton" capability "SignalStrength" capability "VoltageMeasurement" command "normalMode" command "rangingMode" //command "quietMode" attribute "batteryState", "string" attribute "healthStatus", "enum", ["offline", "online"] if (debugMode) { command "testCommand" } fingerprint profileId: "C216", inClusters: "00F0,00F3,00F4,00F1", outClusters: "", manufacturer: "AlertMe.com", model: "Keyfob Device", deviceJoinName: "AlertMe Fob" } } preferences { input name: "infoLogging", type: "bool", title: "Enable logging", defaultValue: true input name: "debugLogging", type: "bool", title: "Enable debug logging", defaultValue: false input name: "traceLogging", type: "bool", title: "Enable trace logging", defaultValue: false } void testCommand() { logging("${device} : Test Command", "info") sendZigbeeCommands(["he raw ${device.deviceNetworkId} 0 ${device.endpointId} 0x00F6 {11 00 FC 01} {0xC216}"]) // version information request } void configureSpecifics() { // Called by main configure() method in BirdsLikeWires.alertme String modelCheck = "${getDeviceDataByName('model')}" if ("${modelCheck}" == "Care Pendant Device") { device.name = "AlertMe Pendant" sendEvent(name: "numberOfButtons", value: 1, isStateChange: false) } else if ("${modelCheck}" == "Keyfob Device") { device.name = "AlertMe Fob" sendEvent(name: "numberOfButtons", value: 2, isStateChange: false) state.lastAwayPress = 0 state.lastAwayRelease = 0 state.lastHomePress = 0 state.lastHomeRelease = 0 } state.operatingMode = "normal" // Schedule ranging report. randomSixty = Math.abs(new Random().nextInt() % 60) randomTwentyFour = Math.abs(new Random().nextInt() % 24) schedule("${randomSixty} ${randomSixty} ${randomTwentyFour}/${rangeEveryHours} * * ? *", rangingMode) } void updateSpecifics() { // Called by library updated() method in BirdsLikeWires.library rangingMode() } void processMap(Map map) { if (map.clusterId == "00C0") { // Pendant trigger message. if (map.command == "0A") { logging("${device} : Trigger : Pendant Button Pressed", "info") sendEvent(name: "pushed", value: 1, isStateChange: true) alertmeCare(0) // Plonks the pendant straight back into idle mode. } else { reportToDev(map) } } else if (map.clusterId == "00F3") { // Fob trigger message. // On the Keyfob both press and release are always sent, regardless of how long the button is held. // This can be beneficial in case of a lost transmission. Both press and release could be bound to the same non-toggling action. // For some reason, key fobs are very 'bouncy' and often send more than one press or release per actuation, sometimes with a long delay. // This could again be for reasons of resiliance, as these were originally designed for arming or disarming the security system. long buttonDebounceTimeoutMillis = 8000 int buttonNumber String buttonName if (map.data[0] == "00") { buttonNumber = 1 buttonName = "Home" } else { buttonNumber = 2 buttonName = "Away" } long millisNow = new Date().time long millisElapsedAwayPress = millisNow - state.lastAwayPress long millisElapsedAwayRelease = millisNow - state.lastAwayRelease long millisElapsedHomePress = millisNow - state.lastHomePress long millisElapsedHomeRelease = millisNow - state.lastHomeRelease if (map.command == "00") { // Release if (buttonNumber == 1) { // Home Button if (millisElapsedHomeRelease > buttonDebounceTimeoutMillis) { state.lastHomeRelease = millisNow logging("${device} : Trigger : Button ${buttonNumber} (${buttonName}) Released", "info") sendEvent(name: "released", value: buttonNumber, isStateChange: true) } else { logging("${device} : Debounced : Button ${buttonNumber} (${buttonName}) Release", "debug") } } else { // Away Button if (millisElapsedAwayRelease > buttonDebounceTimeoutMillis) { state.lastAwayRelease = millisNow logging("${device} : Trigger : Button ${buttonNumber} (${buttonName}) Released", "info") sendEvent(name: "released", value: buttonNumber, isStateChange: true) } else { logging("${device} : Debounced : Button ${buttonNumber} (${buttonName}) Release", "debug") } } } else if (map.command == "01") { // Press if (buttonNumber == 1) { // Home Button if (millisElapsedHomePress > buttonDebounceTimeoutMillis) { state.lastHomePress = millisNow logging("${device} : Trigger : Button ${buttonNumber} (${buttonName}) Pressed", "info") sendEvent(name: "pushed", value: buttonNumber, isStateChange: true) } else { logging("${device} : Debounced : Button ${buttonNumber} (${buttonName}) Press", "debug") } } else { // Away Button if (millisElapsedAwayPress > buttonDebounceTimeoutMillis) { state.lastAwayPress = millisNow logging("${device} : Trigger : Button ${buttonNumber} (${buttonName}) Pressed", "info") sendEvent(name: "pushed", value: buttonNumber, isStateChange: true) } else { logging("${device} : Debounced : Button ${buttonNumber} (${buttonName}) Press", "debug") } } } else { reportToDev(map) } } else { reportToDev(map) } }