/** * Shelly Wave Pro Dimmer 2PM QPDM-0A2P01EU * Device Handler * Version 0.1 * Date: 15.3.2025 * Author: Rene Boer * Copyright , none free to use * * See https://kb.shelly.cloud/knowledge-base/shelly-wave-pro-dimmer-2pm-eu for full details on device. * * CHANGELOG: * 0.1: First release */ import groovy.transform.Field @Field String VERSION = "0.1" // When commented out, there is no specific handler routine in this driver for the device @Field static Map CMD_CLASS_VERS = [ 0x72: 2, // COMMAND_CLASS_MANUFACTURER_SPECIFIC_V2 0x70: 4, // COMMAND_CLASS_CONFIGURATION_V4 0x86: 3, // COMMAND_CLASS_VERSION_V3 0x6C: 1, // COMMAND_CLASS_SUPERVISION_V1 // 0x73: 1, // COMMAND_CLASS_POWER_LEVEL_V1 0x60: 4, // COMMAND_CLASS_MULTI_CHANNEL_V4 // 0x87: 3, // COMMAND_CLASS_INDICATOR_V3, 0x7A: 5, // COMMAND_CLASS_FIRMWARE_UPDATE_MD_V5 // 0x5A: 1, // COMMAND_CLASS_DEVICE_RESET_LOCALLY_V1 0x5B: 3, // COMMAND_CLASS_CENTRAL_SCENE_V3 // 0x8E: 3, // COMMAND_CLASS_MULTI_CHANNEL_ASSOCIATION_V3 // 0x59: 3, // COMMAND_CLASS_ASSOCIATION_GRP_INFO_V3 // 0x85: 2, // COMMAND_CLASS_ASSOCIATION_V2 0x32: 5, // COMMAND_CLASS_METER_V5 0x71: 8, // COMMAND_CLASS_NOTIFICATION_V8 0x26: 3, // COMMAND_CLASS_SWITCH_MULTILEVEL_V3 (no V4 on Hubitat) // 0x55: 2, // COMMAND_CLASS_TRANSPORT_SERVICE_V2 0x9F: 1, // COMMAND_CLASS_SECURITY_2_V1 0x98: 1, // COMMAND_CLASS_SECURITY_V1 0x5E: 2, // COMMAND_CLASS_ZWAVEPLUS_INFO_V2 0x20: 2, // COMMAND_CLASS_BASIC_V2 0x25: 2 // COMMAND_CLASS_SWITCH_BINARY_V2 ] // Device parameters @Field static Map parameterMap = [ 1: [ key: "configParam1", title: "SW1 Switch type", desc: "Defines how the to treat the switch connected to SW1.", type: "enum", defaultValue: 0, options:[0:"Push Button", 1:"Toggle state (closed - ON/opened - OFF)", 2:"Toggle change (changes status when switch changes status)"], required: true, size: 1], 2: [ key: "configParam2", title: "SW2 Switch type", desc: "Defines how the to treat the switch connected to SW2.", type: "enum", defaultValue: 0, options:[0:"Push Button", 1:"Toggle state (closed - ON/opened - OFF)", 2:"Toggle change (changes status when switch changes status)"], required: true, size: 1], 3: [ key: "configParam3", title: "SW3 Switch type", desc: "Defines how the to treat the switch connected to SW3.", type: "enum", defaultValue: 0, options:[0:"Push Button", 1:"Toggle state (closed - ON/opened - OFF)", 2:"Toggle change (changes status when switch changes status)"], required: true, size: 1], 4: [ key: "configParam4", title: "SW4 Switch type", desc: "Defines how the to treat the switch connected to SW4.", type: "enum", defaultValue: 0, options:[0:"Push Button", 1:"Toggle state (closed - ON/opened - OFF)", 2:"Toggle change (changes status when switch changes status)"], required: true, size: 1], 7: [ key: "configParam7", title: "SW1 detach mode", desc: "If enabled the switch SW1 will not change output1.", type: "enum", defaultValue: 0, options:[0:"Normal mode", 1:"Detached mode"], required: true, size: 1], 8: [ key: "configParam8", title: "SW2 detach mode", desc: "If enabled the switch SW2 will not change output 1. If normal mode Par 131 must be Active", type: "enum", defaultValue: 0, options:[0:"Normal mode", 1:"Detached mode"], required: true, size: 1], 9: [ key: "configParam9", title: "SW3 detach mode", desc: "If enabled the switch SW3 will not change output 2.", type: "enum", defaultValue: 0, options:[0:"Normal mode", 1:"Detached mode"], required: true, size: 1], 10: [ key: "configParam10", title: "SW4 detach mode", desc: "If enabled the switch SW4 will not change output 2. If normal mode Par 132 must be Active", type: "enum", defaultValue: 0, options:[0:"Normal mode", 1:"Detached mode"], required: true, size: 1], 17: [ key: "configParam17", title: "O1 action in case of power out", desc: "Determines if on/off status is saved and restored after power failure.", type: "enum", defaultValue: 0, options:[0:"Last status", 1:"Switch remains off"], required: true, size: 1], 18: [ key: "configParam18", title: "O2 action in case of power out", desc: "Determines if on/off status is saved and restored after power failure.", type: "enum", defaultValue: 0, options:[0:"Last status", 1:"Switch remains off"], required: true, size: 1], 19: [ key: "configParam19", title: "O1 Auto OFF with timer", desc: "If the load O1 is ON, you can schedule it to turn OFF automatically after the period of time.
0 Disabled, 1-32535s", type: "number", defaultValue: 0, min: 0, max: 32535, required: false, size: 2], 20: [ key: "configParam20", title: "O1 Auto ON with timer", desc: "If the load O1 is OFF, you can schedule it to turn ON automatically after the period of time.
0 Disabled, 1-32535s", type: "number", defaultValue: 0, min: 0, max: 32535, required: false, size: 2], 21: [ key: "configParam21", title: "O2 Auto OFF with timer", desc: "If the load O2 is ON, you can schedule it to turn OFF automatically after the period of time.
0 Disabled, 1-32535s", type: "number", defaultValue: 0, min: 0, max: 32535, required: false, size: 2], 22: [ key: "configParam22", title: "O2 Auto ON with timer", desc: "If the load O2 is OFF, you can schedule it to turn ON automatically after the period of time.
0 Disabled, 1-32535s", type: "number", defaultValue: 0, min: 0, max: 32535, required: false, size: 2], 25: [ key: "configParam25", title: "Set timer units to s or ms for O1", desc: "Set the timer in seconds or milliseconds in Parameters No. 19, 20.", type: "enum", defaultValue: 0, options:[0:"Timer set in seconds", 1:"Timer set in milliseconds"], required: false, size: 1], 26: [ key: "configParam26", title: "Set timer units to s or ms for O2", desc: "Set the timer in seconds or milliseconds in Parameters No. 21, 22.", type: "enum", defaultValue: 0, options:[0:"Timer set in seconds", 1:"Timer set in milliseconds"], required: false, size: 1], 36: [ key: "configParam36", title: "Power report on change for O1", desc: "The minimum change in consumed power that will result in sending a new report.
0 Disabled, 1-100%", type: "number", defaultValue: 10, min: 0, max: 100, required: true, size:1 ], 37: [ key: "configParam37", title: "Power report on change for O2", desc: "The minimum change in consumed power that will result in sending a new report.
0 Disabled, 1-100%", type: "number", defaultValue: 10, min: 0, max: 100, required: true, size:1 ], 39: [ key: "configParam39", title: "Minimal time between reports for O1", desc: "The minimum time that must elapse before a new power report.
0 Disabled, 1-120s", type: "enum", def: 0, options:[0:"Reports disabled", 30:"30 seconds", 60:"1 minute", 120:"2 minutes"], required: true, size: 1], 40: [ key: "configParam40", title: "Minimal time between reports for O2", desc: "The minimum time that must elapse before a new power report.
0 Disabled, 1-120s", type: "enum", def: 0, options:[0:"Reports disabled", 30:"30 seconds", 60:"1 minute", 120:"2 minutes"], required: true, size: 1], 78: [ key: "configParam78", title: "Forced Dimmer calibration O1", desc: "Read only, use command button.", type: "enum", def: 3, options:[2:"Device is calibrated", 3:"Device is not calibrated", 4:"Calibration error"], required: false, size: 1, ro: true], 89: [ key: "configParam89", title: "Forced Dimmer calibration O2", desc: "Read only, use command button.", type: "enum", def: 3, options:[2:"Device is calibrated", 3:"Device is not calibrated", 4:"Calibration error"], required: false, size: 1, ro: true], 121:[ key: "configParam121", title: "Maximum Dimming value O1", desc: "Determines maximum dimming value.
1-99%", type: "number", defaultValue: 99, min: 1, max: 99, required: true, size: 1], 122:[ key: "configParam122", title: "Maximum Dimming value O2", desc: "Determines maximum dimming value.
1-99%", type: "number", defaultValue: 99, min: 1, max: 99, required: true, size: 1], 123:[ key: "configParam123", title: "Minimum Dimming value O1", desc: "Determines minimum dimming value.
0-98%", type: "number", defaultValue: 15, min: 0, max: 98, required: true, size: 1], 124:[ key: "configParam124", title: "Minimum Dimming value O2", desc: "Determines minimum dimming value.
0-98%", type: "number", defaultValue: 15, min: 0, max: 98, required: true, size: 1], 125:[ key: "configParam125", title: "Dimming time (soft on/off) O1", desc: "Time during which the device will move between the min. and max. dimming values by a short press.
1-10s", type: "number", defaultValue: 3, min: 1, max: 10, required: true, size: 1], 126:[ key: "configParam126", title: "Dimming time (soft on/off) O2", desc: "Time during which the device will move between the min. and max. dimming values by a short press.
1-10s", type: "number", defaultValue: 3, min: 1, max: 10, required: true, size: 1], 127:[ key: "configParam127", title: "Fade rate O1", desc: "Time during which the Dimmer will move between the min. and max. dimming values during a button hold.", type: "enum", defaultValue: 3, options:[1:"5% per sec", 2:"7% per sec", 3:"10% per sec", 4:"15% per sec", 5:"20% per sec"], required: true, size: 1], 128:[ key: "configParam128", title: "Fade rate O2", desc: "Time during which the Dimmer will move between the min. and max. dimming values during a button hold.", type: "enum", defaultValue: 3, options:[1:"5% per sec", 2:"7% per sec", 3:"10% per sec", 4:"15% per sec", 5:"20% per sec"], required: true, size: 1], 129:[ key: "configParam129", title: "Minimum brightens on toggle O1", desc: "Minimum brightness on toggle.
1-99%", type: "number", defaultValue: 15, min: 1, max: 99, required: true, size: 1], 130:[ key: "configParam130", title: "Minimum brightens on toggle O2", desc: "Minimum brightness on toggle.
1-99%", type: "number", defaultValue: 15, min: 1, max: 99, required: true, size: 1], 131:[ key: "configParam131", title: "SW1 & SW2 Dual Button Mode", desc: "If enabled SW1 will be on button, SW2 off button. Switch types 1,2 must be Push button", type: "enum", defaultValue: 0, options:[0:"Inactive", 1:"Active"], required: true, size: 1], 132:[ key: "configParam132", title: "SW3 & SW4 Dual Button Mode", desc: "If enabled SW3 will be on button, SW4 off button. Switch types 3,4 must be Push button", type: "enum", defaultValue: 0, options:[0:"Inactive", 1:"Active"], required: true, size: 1] ] // report type definitions by MeterReport scale supported by device. @Field static meterReportTypes = [ 0: [type: "energy", unit: "kWh", special: false, description: "consumed"], 2: [type: "power", unit: "W", special: false, description: "consumes", max: 4000, highlow: true] ] // report type definitions by NotificationReport types supported by device. @Field static notificationReportTypes = [ 0x04: [type: "Heat Alarm", 0x02: [name: "Overheat detected", desc: "Device turned off. Short press S Button or Power cycle to restore."]], 0x08: [type: "Power Management", 0x06: [name: "Over-current detected", desc: "Device turned off. Short press S Button or Power cycle to restore."], 0x07: [name: "Over-voltage detected", desc: "Device turned off. Short press S Button or Power cycle to restore."]] ] // List of end points, we use one switch child device for all four buttons, for four switches change #endpoints to 6 and #buttons to 1 @Field static NUMBER_OF_ENDPOINTS = 3 @Field static NUMBER_OF_BUTTONS = 4 @Field static endPointTypes = [ 1: [type: 'O', comp: "Generic Component Metering Dimmer"], 2: [type: 'O', comp: "Generic Component Metering Dimmer"], 3: [type: 'SW', comp: "Generic Component Button Controller"], 4: [type: 'SW', comp: "Generic Component Button Controller"], 5: [type: 'SW', comp: "Generic Component Button Controller"], 6: [type: 'SW', comp: "Generic Component Button Controller"], ] metadata { definition(name: 'Shelly Wave Pro Dimmer 2PM', namespace: "reneboer", author: "Rene Boer", importUrl: "https://raw.githubusercontent.com/reneboer/Hubitat/main/Shelly/Shelly%20Wave%20PRO%20Dimmer%202PM%20Driver.groovy") { capability "Configuration" capability "Refresh" command "forceDimmerCalibration", [[name: "Force Dimmer calibration*", type: "ENUM", description: " Device will start executing force calibration procedure for the selected channel", default: "O1", constraints: ["O1", "O2"]]] command "resetPower" //command to issue Meter Reset commands to reset accumulated power measurements command "remoteReboot", [[name: "Reboot device*", type: "ENUM", description: "Reboot the device. Use for troubleshooting only.", default: "Please select", constraints: ["Please select", "Do nothing", "Perform reboot"]]] // Send remote reboot command to device fingerprint mfr: "0460", prod: "0001", deviceId: "0082", deviceJoinName: "Shelly Wave Pro Dimmer 2PM" fingerprint mfr: "0460", prod: "0001", deviceId: "0082", inClusters: "0x5E,0x98,0x9F,0x55,0x6C", secureInClusters: "0x26,0x71,0x32,0x85,0x59,0x8E,0x5B,0x5A,0x7A,0x87,0x60,0x73,0x86,0x70,0x72", deviceJoinName: "Shelly Wave Pro Dimmer 2PM" fingerprint mfr: "0460", prod: "0001", deviceId: "0082", inClusters: "0x5E,0x98,0x9F,0x55,0x6C,0x26,0x71,0x32,0x85,0x59,0x8E,0x5B,0x5A,0x7A,0x87,0x60,0x73,0x86,0x70,0x72", deviceJoinName: "SShelly Wave Pro Dimmer 2PM" } preferences { parameterMap.eachWithIndex {pnum, param, i -> input ( name: param.key, title: "${pnum}. ${param.title}", type: param.type, options: param.options, range: (param.min != null && param.max != null) ? "${param.min}..${param.max}" : null, defaultValue: param.def, description: param.desc, required: (param.required) ? true : false ) } input name: "continousHeldEnable", type: "bool", title: "Continous Button Held reporting", defaultValue: true, description:"When true five held events are processed per second. When false only first held event is processed." input name: "logEnable", type: "bool", title: "Enable debug logging", defaultValue: true input name: "txtEnable", type: "bool", title: "Enable descriptionText logging", defaultValue: true } } void installed() { logInfo "installed(${VERSION})" runIn (5, 'getConfig') // Get current device config after installed. runIn (15, 'refresh') // Get the measurements from the device. } void uninstalled() { logInfo "${device.label} uninstalled()" if (childDevices) { logDebug "removing child devices" removeChildDevices(getChildDevices()) } } private removeChildDevices(delete) { delete.each { deleteChildDevice(it.deviceNetworkId) } } void configure() { logDebug "Entering configure()" uninstalled() installed() } void updated() { logDebug "Entering updated()" logWarn "debug logging is: ${logEnable == true}" logWarn "description logging is: ${txtEnable == true}" unschedule() createChildDevices() List commands = [] parameterMap.eachWithIndex {pnum, param, i -> if (!param.ro && this["$param.key"] != null && (state."$param.key".toString() != this["$param.key"].toString() )) { commands << zwave.configurationV4.configurationSet(scaledConfigurationValue: this["$param.key"].toInteger(), parameterNumber: pnum, size: param.size) } } if (commands.size() > 0) { runCommandsWithInterstitialDelay(commands, 300) runIn(10, 'getConfig') } // if (logEnable) runIn(3600, 'logsOff') } void refresh(ep = 0) { logDebug "refresh(ep : ${ep})" List commands=[ zwave.switchBinaryV2.switchBinaryGet() ] meterReportTypes.each { scale, data -> commands << zwave.meterV5.meterGet(scale: scale) } if (ep > 0) { // Refresh give end point. runCommandsWithInterstitialDelay(commands, 300, ep) } else if (ep == 0) { // Refresh all dimmer end points endPointTypes.eachWithIndex { e, data, i -> if (data.type == "O") runCommandsWithInterstitialDelay(commands, 300, e) } } } // We only create the two dimmer child devices, not the four switches for now. private void createChildDevices() { logDebug "${device.label} creating child devices" def childDev String devLabel try { (1..NUMBER_OF_ENDPOINTS).each() { Map epd = endPointTypes[it] if (epd) { devLabel = "Shelly Wave Pro Dimmer ${epd.type}${epd.type == "O" ? it : NUMBER_OF_ENDPOINTS == 3 ? "1-4" : it - 2}" def cd = getChildDevice(${device.deviceNetworkId}-${it}") if (!cd) { childDev = addChildDevice("hubitat", epd.comp, "${device.deviceNetworkId}-${it}", [isComponent: true, label: devLabel, name: devLabel]) childDev.updateDataValue("endPoint","${it}") if (epd.type == "O") { childDev.sendEvent(name: "switch", value: "off") childDev.sendEvent(name: "level", value: 0) childDev.sendEvent(name: "power", value: 0) childDev.sendEvent(name: "energy", value: 0) } if (epd.type == "SW") childDev.sendEvent(name: "numberOfButtons", value: NUMBER_OF_BUTTONS) } else { } } } } catch (e) { logWarn "${e}" sendEvent(descriptionText: "Child device creation failed. Please make sure that the \"Shelly Wave Pro Dimmer 2PM Child Device\" is installed and published.", eventType: "ALERT", name: "childDeviceCreation", value: "failed", displayed: true) } } // Commands for child devices void componentOn(com.hubitat.app.DeviceWrapper cd) { logDebug "componentOn from ${cd.displayName} (${cd.deviceNetworkId})" Integer endPoint = getChildEP(cd) if (endPoint) runCommand(zwave.basicV2.basicSet(value: 0xFF), endPoint) } void componentOff(com.hubitat.app.DeviceWrapper cd) { logDebug "componentOff from ${cd.displayName} (${cd.deviceNetworkId})" Integer endPoint = getChildEP(cd) if (endPoint) runCommand(zwave.basicV2.basicSet(value: 0x00), endPoint) } void componentSetLevel(com.hubitat.app.DeviceWrapper cd, level, ramp = 0) { logDebug "componentSetLevel from ${cd.displayName} (${cd.deviceNetworkId})" Integer endPoint = getChildEP(cd) if (endPoint) { if (level > 99) level = 99 if (ramp > 100) ramp = 100 if (ramp < 1) ramp = 1 logDebug "setLevel(value: ${ramp}, dimmingDuration: ${duration})" runCommand(zwave.switchMultilevelV4.switchMultilevelSet(value: level, dimmingDuration: ramp), endPoint) } } void componentStartLevelChange(com.hubitat.app.DeviceWrapper cd, direction) { logDebug "componentStartLevelChange from ${cd.displayName} (${cd.deviceNetworkId})" Integer endPoint = getChildEP(cd) if (endPoint) { Boolean upDownVal = direction == "down" ? true : false String param = "configParam${(endPoint == 1 ? 125 : 126)}" Short dimmingDuration = settings."${param}" != null ? settings."${param}" : 3 Short startLevel = cd.currentValue("level") logDebug "startLevelChange(upDown: ${direction}, startLevel: ${startLevel}, dimmingDuration: ${dimmingDuration})" runCommand(zwave.switchMultilevelV4.switchMultilevelStartLevelChange(ignoreStartLevel: true, startLevel: startLevel, upDown: upDownVal, dimmingDuration: dimmingDuration), endPoint) } } void componentStopLevelChange(com.hubitat.app.DeviceWrapper cd) { logDebug "componentStopLevelChange from ${cd.displayName} (${cd.deviceNetworkId})" Integer endPoint = getChildEP(cd) if (endPoint) runCommand(zwave.switchMultilevelV4.switchMultilevelStopLevelChange(), endPoint) } void componentRefresh(com.hubitat.app.DeviceWrapper cd) { logDebug "componentRefresh from ${cd.displayName} (${cd.deviceNetworkId})" Integer endPoint = getChildEP(cd) if (endPoint) refresh(endPoint) } void componentPush(com.hubitat.app.DeviceWrapper cd, button) { logDebug "componentPush from ${cd.displayName} (${cd.deviceNetworkId})" Integer endPoint = getChildEP(cd) if (endPoint) sendButtonEvent("pushed", button, "digital", endPoint) } void componentDoubleTap(com.hubitat.app.DeviceWrapper cd, button) { logDebug "componentDoubleTap from ${cd.displayName} (${cd.deviceNetworkId})" Integer endPoint = getChildEP(cd) if (endPoint) sendButtonEvent("doubleTapped", button, "digital", endPoint) } void componentHold(com.hubitat.app.DeviceWrapper cd, button) { logDebug "componentHold from ${cd.displayName} (${cd.deviceNetworkId})" Integer endPoint = getChildEP(cd) if (endPoint) sendButtonEvent("held", button, "digital", endPoint) } void componentRelease(com.hubitat.app.DeviceWrapper cd, button) { logDebug "componentRelease from ${cd.displayName} (${cd.deviceNetworkId})" Integer endPoint = getChildEP(cd) if (endPoint) sendButtonEvent("released", button, "digital", endPoint) } void resetPower(Integer ep=0) { logDebug "resetPower() ep ${ep}" runIn (10, refresh) if (ep > 0) { // Reset give end point. runCommand(zwave.meterV5.meterReset(), ep) } else if (ep == 0) { // Reset all dimmer end points endPointTypes.eachWithIndex { e, data, i -> if (data.type == "O") runCommand(zwave.meterV5.meterReset(), e) } } } void remoteReboot(flag) { logDebug "remoteReboot(${flag})" if (flag == "Perform reboot") { logWarn "Rebooting device." runCommand zwave.configurationV1.configurationSet(parameterNumber: 117, size: 1, scaledConfigurationValue: 1) } } void forceDimmerCalibration(output) { logDebug "forceDimmerCalibration(${output})" pn = 78 if (output == "O2") { pn = 89 } runCommand zwave.configurationV1.configurationSet(parameterNumber: pn, size: 1, scaledConfigurationValue: 1) } // Hande button event private void sendButtonEvent(action, button, type, ep = 0){ if (button >= 1 && button <= NUMBER_OF_BUTTONS) { sendEventWrapper(name:action, value:button, descriptionText:"button ${button} was ${action} [${type}]", isStateChange:true, type:type, ep) } else { logWarn "button${action} button number ${button} invalid." } } // Request parameters from device. private void getConfig() { logDebug 'getConfig()' List commands = [] parameterMap.eachWithIndex {pnum, param, i -> if ( this["$param.key"] != null && state."$param.key".toString() != this["$param.key"].toString() ) { commands << zwave.configurationV4.configurationGet(parameterNumber: pnum) } } commands << zwave.versionV3.versionGet() if (!device.getDataValue("MSR")) commands << zwave.manufacturerSpecificV2.manufacturerSpecificGet() runCommandsWithInterstitialDelay commands } private void logsOff(){ logWarn "debug logging disabled..." device.updateSetting("logEnable",[value:"false",type:"bool"]) } /** * Incomming zwave event handlers. */ void parse(String description) { // logger("debug", "parse() - description: ${description.inspect()}") hubitat.zwave.Command cmd = zwave.parse(description, CMD_CLASS_VERS) if (cmd) { logDebug "parse() - parsed to cmd: ${cmd?.inspect()} with result: ${result?.inspect()}" zwaveEvent(cmd) } else { logErr "parse() - Non-parsed - description: ${description?.inspect()}" } } // Handle zwave events not expected void zwaveEvent(hubitat.zwave.Command cmd, ep=0) { logDebug "Unhandled zwaveEvent: $cmd (ep ${ep}) [${getObjectClassName(cmd)}]" } // COMMAND_CLASS_CENTRAL_SCENE_V3 void zwaveEvent(hubitat.zwave.commands.centralscenev3.CentralSceneNotification cmd, ep = 0) { logDebug "zwaveEvent(hubitat.zwave.commands.centralscenev3.CentralSceneNotification cmd, int endpoint = $ep)" Integer button = cmd.sceneNumber Integer key = cmd.keyAttributes String action switch (key){ case 0: //pushed action = "pushed" break case 1: //released, only after 2 state."buttonHold${button}" = 0 action = "released" break case 2: //holding. We get 5 reports per second for as long button is held. if (continousHeldEnable) { action = "held" } else { if (state."buttonHold${button}" == 0){ state."buttonHold${button}" = 1 action = "held" } } break case 3: //double tap, 4 is tripple tap action = "doubleTapped" break default: logWarn "zwaveEvent(CentralSceneNotification) - skipped. Unknown button action." } if (action) { if (NUMBER_OF_ENDPOINTS == 3) { sendButtonEvent(action, button, "physical", 3) } else { sendButtonEvent(action, 1, "physical", button + 2) } } } //COMMAND_CLASS_METER V5 (V6 not yet supported in Hubitat) void zwaveEvent(hubitat.zwave.commands.meterv5.MeterReport cmd, ep = 0) { logDebug "zwaveEvent(hubitat.zwave.commands.meterv5.MeterReport cmd, int endpoint = $ep, scale = $cmd.scale)" Map measurement = meterReportTypes[cmd.scale as Integer] if (measurement?.hasScale2) { measurement = meterReportTypes[cmd.scale as Integer][cmd.scale2 as Integer] } if (measurement) { // Update parent with totals value if (ep > 0) { // Update child with value def cd = getChildDevice("${device.deviceNetworkId}-${ep}") if (cd) { logDebug "Updating child device ${device.deviceNetworkId}-${ep}, scale ${cmd.scale}" cd.sendEvent([name: measurement.type, value: cmd.scaledMeterValue, unit: measurement.unit]) } } } else { logWarn "Scale not implemented. ${cmd.scale}, ${cmd.scale2}: ${cmd.scaledMeterValue}" } } void zwaveEvent(hubitat.zwave.commands.configurationv4.ConfigurationReport cmd) { logDebug "zwaveEvent(ConfigurationReport) - cmd: ${cmd.inspect()}" def newVal = cmd.scaledConfigurationValue.toInteger() Map param = parameterMap[cmd.parameterNumber.toInteger()] if (param) { def curVal = device.getSetting(param.key) if (param.type == "bool") { curVal = curVal == false ? 0 : 1} try { curVal = curVal.toInteger() }catch(Exception ex) { logWarn "Undefined parameter ${curVal}." curVal = null } Long sizeFactor = Math.pow(256,cmd.size).round() if (newVal < 0) { newVal += sizeFactor } if (curVal != newVal) { if (param.type == "enum") { newVal = newVal.toString()} if (param.type == "bool") { newVal = newVal == 0 ? false: true} device.updateSetting(param.key, [value: newVal, type: param.type]) logDebug "Updating device parameter setting ${cmd.parameterNumber} from ${curVal} to ${newVal}." } } else { logWarn "Unsupported parameter ${cmd.parameterNumber}." } } void zwaveEvent(hubitat.zwave.commands.basicv2.BasicReport cmd, ep = 0){ logDebug "zwaveEvent(BasicReport) - cmd: ${cmd.inspect()}, ep: ${ep}." sendEventWrapper(name: "switch", value: cmd.value ? "on" : "off", type: "physical", ep) } // Is send when button is detached, so do nothing. void zwaveEvent(hubitat.zwave.commands.switchbinaryv2.SwitchBinaryReport cmd, ep = 0){ logDebug "zwaveEvent(SwitchBinaryReport) - cmd: ${cmd.inspect()}, ep: ${ep}." } void zwaveEvent(hubitat.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) { logDebug "zwaveEvent(ManufacturerSpecificReport) - cmd: ${cmd.inspect()}" if (cmd.manufacturerName) { device.updateDataValue("manufacturer", cmd.manufacturerName) } if (cmd.productTypeId) { device.updateDataValue("productTypeId", cmd.productTypeId.toString()) } if (cmd.productId) { device.updateDataValue("deviceId", cmd.productId.toString()) } device.updateDataValue("MSR", String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId)) } void zwaveEvent(hubitat.zwave.commands.versionv3.VersionReport cmd) { logDebug "zwaveEvent(VersionReport) - cmd: ${cmd.inspect()}" device.updateDataValue("firmwareVersion", "${cmd.firmware0Version}.${cmd.firmware0SubVersion}") device.updateDataValue("protocolVersion", "${cmd.zWaveProtocolVersion}.${cmd.zWaveProtocolSubVersion}") device.updateDataValue("hardwareVersion", "${cmd.hardwareVersion}") if (cmd.firmwareTargets > 0) { cmd.targetVersions.each { target -> device.updateDataValue("firmware${target.target}Version", "${target.version}.${target.subVersion}") } } } //Decodes Multichannel Encapsulated Commands void zwaveEvent(hubitat.zwave.commands.multichannelv4.MultiChannelCmdEncap cmd) { hubitat.zwave.Command encapsulatedCmd = cmd.encapsulatedCommand(CMD_CLASS_VERS) logDebug "${cmd} --ENCAP-- ${encapsulatedCmd}" if (encapsulatedCmd) { zwaveEvent(encapsulatedCmd, cmd.sourceEndPoint as Integer) } else { logWarn "Unable to extract encapsulated cmd from $cmd" } } void zwaveEvent(hubitat.zwave.commands.switchmultilevelv3.SwitchMultilevelReport cmd, ep = 0) { logDebug "zwaveEvent(SwitchMultilevelReport) - cmd: ${cmd.inspect()}" sendEventWrapper(name:"switch", value: cmd.value ? "on" : "off", ep) sendEventWrapper(name:"level", value: cmd.value, unit:"%", descriptionText:"dimmed to ${cmd.value==255 ? 100 : cmd.value}%", ep) } // Devices that support the Security command class can send messages in an encrypted form; they arrive wrapped in a SecurityMessageEncapsulation command and must be unencapsulated void zwaveEvent(hubitat.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) { logDebug "zwaveEvent(SecurityMessageEncapsulation) - cmd: ${cmd.inspect()}" hubitat.zwave.Command encapsulatedCommand = cmd.encapsulatedCommand(CMD_CLASS_VERS) if (encapsulatedCommand) { logDebug "zwaveEvent(SecurityMessageEncapsulation) - encapsulatedCommand: ${encapsulatedCommand}" zwaveEvent(encapsulatedCommand) } else { logWarn "zwaveEvent(SecurityMessageEncapsulation) - Unable to extract Secure command from: ${cmd.inspect()}" } } //COMMAND_CLASS_SUPERVISION V1 void zwaveEvent(hubitat.zwave.commands.supervisionv1.SupervisionGet cmd, ep=0) { logDebug "zwaveEvent(SupervisionGet) - cmd: ${cmd.inspect()}" hubitat.zwave.Command encapsulatedCommand = cmd.encapsulatedCommand(commandClasses) if (encapsulatedCommand) { logDebug "zwaveEvent(SupervisionGet) - encapsulatedCommand: ${encapsulatedCommand}" zwaveEvent(encapsulatedCommand, ep) } else { logErr "SupervisionGet - Non-parsed - description: ${description?.inspect()}" } runCommand(zwave.supervisionV1.supervisionReport(sessionID: cmd.sessionID, reserved: 0, moreStatusUpdates: false, status: 0xFF, duration: 0), ep) } //COMMAND_CLASS_FIRMWARE_UPDATE_MD V5 void zwaveEvent(hubitat.zwave.commands.firmwareupdatemdv5.FirmwareMdReport cmd) { logDebug "zwaveEvent(FirmwareMdReport) - cmd: ${cmd.inspect()}" logInfo "Starting firmware update process..." } void zwaveEvent(hubitat.zwave.commands.firmwareupdatemdv5.FirmwareUpdateMdRequestReport cmd) { logDebug "zwaveEvent(FirmwareUpdateMdRequestReport) - cmd: ${cmd.inspect()}" if (cmd.status == 255) { logInfo "Valid firmware for device. Firmware update continuing..." } else { logErr "Invalid firmware for device, error code ${cms.status}" } } void zwaveEvent(hubitat.zwave.commands.firmwareupdatemdv5.FirmwareUpdateMdStatusReport cmd) { logDebug "zwaveEvent(FirmwareUpdateMdStatusReport) - cmd: ${cmd.inspect()}" if (cmd.status == 255) { logInfo "Firmware update succesfully completed." } else { logErr "Error updating firmware for device, error code ${cmd.status}" } } void zwaveEvent(hubitat.zwave.commands.firmwareupdatemdv5.FirmwareUpdateMdGet cmd) { logDebug "zwaveEvent(FirmwareUpdateMdGet ) - cmd: ${cmd.inspect()}" } /// Child Common Functions private getChildByEP(endPoint) { endPoint = endPoint.toString() //Searching using endPoint data value def childDev = childDevices?.find { it.getDataValue("endPoint") == endPoint } if (childDev) logDebug "Found Child for endPoint ${endPoint} using data.endPoint: ${childDev.displayName} (${childDev.deviceNetworkId})" //If not found try deeper search using the child DNIs else { String dni = getChildDNI(endPoint) childDev = childDevices?.find { it.deviceNetworkId == dni } if (childDev) { logDebug "Found Child for endPoint ${endPoint} parsing DNI: ${childDev.displayName} (${childDev.deviceNetworkId})" //Save the EP on the device so we can find it easily next time childDev.updateDataValue("endPoint","$endPoint") } } return childDev } private getChildEP(childDev) { Integer endPoint = safeToInt(childDev.getDataValue("endPoint"), null) if (!endPoint) { logWarn "Cannot determine endPoint number for ($childDev)" // executeProbeCmds() // runIn(2, createChildDevices) } return (endPoint ?: 0) } private String getChildDNI(epName) { return "${device.deviceId}-${epName}".toUpperCase() } // Wrapper for sendEvent to handle child events and support logging private void sendEventWrapper(Map evt, ep = 0) { //Set description if not passed in evt evt.descriptionText = evt.descriptionText ?: "${evt.name} set to ${evt.value} ${evt.unit ?: ''}".trim() //Endpoint Events if (ep) { def childDev = getChildByEP(ep) if (childDev) { if (childDev.currentValue(evt.name).toString() != evt.value.toString() || evt.isStateChange) { evt.descriptionText = "${childDev}: ${evt.descriptionText}" childDev.parse([evt]) } else { childDev.sendEvent(evt) } } else { if (state.deviceSync) { logDebug "No device for endpoint (${ep}) has been created yet..." } else { logErr "No device for endpoint (${ep}). Press Save Preferences (or Configure) to create child devices." } } return } //Main Device Events if (device.currentValue(evt.name).toString() != evt.value.toString() || evt.isStateChange) { logInfo "${evt.descriptionText}" } else { logDebug "${evt.descriptionText} [NOT CHANGED]" } //Always send event to update last activity sendEvent(evt) } private Integer safeToInt(val, defaultVal=0) { if ("${val}"?.isInteger()) { return "${val}".toInteger() } else if ("${val}"?.isNumber()) { return "${val}".toDouble()?.round() } else { return defaultVal } } // Logger wrapers private void logErr(message) { log.error message } private void logWarn(message) { log.warn message } private void logInfo(message) { log.info message } private void logDebug(message) { if (logEnable) log.debug message } // zwave command handlers multi channel private void runCommandsWithInterstitialDelay(List commands, int delay = 300, int ep = 0) { logDebug "Entering runCommandsWithInterstitialDelay() with ${commands.size()} commands for ep ${ep}" if (ep > 0) { sendHubCommand(new hubitat.device.HubMultiAction( delayBetween(commands.collect { command -> zwaveSecureEncap(zwave.multiChannelV4.multiChannelCmdEncap(sourceEndPoint: 0, bitAddress: 0, res01: 0, destinationEndPoint: ep).encapsulate(command)) }, delay), hubitat.device.Protocol.ZWAVE)) } else { sendHubCommand(new hubitat.device.HubMultiAction(delayBetween(commands.collect { command -> zwaveSecureEncap command }, delay), hubitat.device.Protocol.ZWAVE)) } } private void runCommand(hubitat.zwave.Command command, ep = 0) { logDebug "Entering runCommand() for ep ${ep}" if (ep) { sendHubCommand(new hubitat.device.HubAction(zwaveSecureEncap(zwave.multiChannelV4.multiChannelCmdEncap(sourceEndPoint: 0, bitAddress: 0, res01: 0, destinationEndPoint: ep).encapsulate(command)), hubitat.device.Protocol.ZWAVE)) } else { sendHubCommand(new hubitat.device.HubAction(zwaveSecureEncap(command), hubitat.device.Protocol.ZWAVE)) } }