/** * Zooz Zen26 Switch * * Revision History: * 2018-10-27 - Initial release * 2018-11-18 - Typo in comments, fix parameter for timer * 2018-11-19 - Fix LED parameter for all values * 2019-07-11 - Fix for SmartThings not honoring defaultValue in preferences and leaving preference value as null unless explicitly set * * Supported Command Classes * * Association v2 * Association Group Information * Configuration * Device Reset Local * Firmware Update MD * Manufacturer Specific * Multichannel Association * Powerlevel * Security 2 * Supervision * Switch_binary * Transport Service * Version v2 * ZWavePlus Info v2 * * Parm Size Description Value * 1 1 Invert Switch 0 (Default)-Upper paddle turns light on, 1-Lower paddle turns light on * 2 1 LED Indicator 0 (Default)-LED is on when light is OFF, 1-LED is on when light is ON, 2-LED is always off, 3-LED is always on * 3 2 Auto Turn-Off Timer 0 (Disabled)-Timer for switch to automatically shut off (0-32768) * 4 1 Power Restore 2 (Default)-Remember state from pre-power failure, 0-Off after power restored, 1-On after power restore */ metadata { definition (name: "Zooz Zen26 Switch", namespace: "doncaruana", author: "Don Caruana", ocfDeviceType: "oic.d.switch", mnmn: "SmartThings", vid: "generic-switch") { capability "Actuator" capability "Indicator" capability "Switch" capability "Polling" capability "Refresh" capability "Sensor" capability "Health Check" capability "Light" //zw:L type:1001 mfr:027A prod:A000 model:A001 ver:1.00 zwv:5.03 lib:03 cc:5E,25,85,8E,59,55,86,72,5A,73,70,6C,9F,7A role:05 ff:8700 ui:8704 fingerprint mfr:"027A", prod:"A000", model:"A001", deviceJoinName: "Zooz Zen26 Switch" fingerprint deviceId:"0x1001", inClusters: "0x5E,0x25,0x85,0x8E,0x59,0x55,0x86,0x72,0x5A,0x73,0x70,0x6C,0x9F,0x7A" fingerprint cc: "0x5E,0x25,0x85,0x8E,0x59,0x55,0x86,0x72,0x5A,0x73,0x70,0x6C,0x9F,0x7A", mfr:"027A", prod:"A000", model:"A001", deviceJoinName: "Zooz Zen26 Switch" } // simulator metadata simulator { status "on": "command: 2003, payload: FF" status "off": "command: 2003, payload: 00" // reply messages reply "2001FF,delay 100,2502": "command: 2503, payload: FF" reply "200100,delay 100,2502": "command: 2503, payload: 00" } preferences { input "ledIndicator", "enum", title: "LED Indicator", description: "When Off... ", options:["on": "When On", "off": "When Off", "never": "Never", "always": "Always"], defaultValue: "off" input "invertSwitch", "bool", title: "Invert Switch", description: "Flip switch upside down", required: false, defaultValue: false input "powerRestore", "enum", title: "After Power Restore", description: "State after power restore", options:["prremember": "Remember", "proff": "Off", "pron": "On"],defaultValue: "prremember",displayDuringSetup: false input "offTimer", "number", title: "Off Timer", description: "Time in seconds to automatically turn off 0(disabled)-32768", required: false, defaultValue: 0, range: "0..32768" } // tile definitions tiles(scale: 2) { multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){ tileAttribute ("device.switch", key: "PRIMARY_CONTROL") { attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#00a0dc", nextState:"turningOff" attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn" attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#00a0dc", nextState:"on" attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"off" } } standardTile("refresh", "device.switch", width: 2, height: 2, inactiveLabel: false, decoration: "flat") { state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh" } main "switch" details(["switch","refresh"]) } } private getCommandClassVersions() { [ 0x59: 1, // AssociationGrpInfo 0x85: 2, // Association 0x5A: 1, // DeviceResetLocally 0x72: 2, // ManufacturerSpecific 0x73: 1, // Powerlevel 0x86: 1, // Version 0x5E: 2, // ZwaveplusInfo 0x25: 1, // Binary Switch 0x70: 1, // Configuration 0x20: 1, // Basic 0x55: 1, // Transport Service 0x6C: 1, // Supervision 0x7A: 1, // Firmware Update Metadata 0x8E: 1, // Multi Channel Association ] } def installed() { log.debug "installed()" def cmds = [] cmds << mfrGet() cmds << zwave.versionV1.versionGet().format() cmds << parmGet(1) cmds << parmGet(2) cmds << parmGet(3) cmds << parmGet(4) cmds << zwave.basicV1.basicSet(value: 0xFF).format() // Device-Watch simply pings if no device events received for 32min(checkInterval) sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID]) return response(delayBetween(cmds,200)) } def updated(){ log.debug "updated()" // This setOffTimer is because the parameter "default" doesn't actually give a workable number def setOffTimer = 0 if (offTimer) {setOffTimer = offTimer} def setPowerRestore = 2 if (powerRestore != null) {setPowerRestore = powerRestore == "prremember" ? 2 : powerRestore == "proff" ? 0 : 1} def setInvertSwitch = invertSwitch == true ? 1 : 0 def setLedIndicator = 0 def commands = [] switch (ledIndicator) { case "off": setLedIndicator = 0 break case "on": setLedIndicator = 1 break case "never": setLedIndicator = 2 break case "always": setLedIndicator = 3 break default: setLedIndicator = 0 break } //parmset takes the parameter number, it's size, and the value - in that order commands << parmSet(4, 1, setPowerRestore) commands << parmSet(3, 2, setOffTimer) commands << parmSet(2, 1, setLedIndicator) commands << parmSet(1, 1, setInvertSwitch) commands << parmGet(4) commands << parmGet(3) commands << parmGet(2) commands << parmGet(1) // Device-Watch simply pings if no device events received for 32min(checkInterval) sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID]) return response(delayBetween(commands, 500)) } def parse(String description) { def result = null def cmd = zwave.parse(description, commandClassVersions) if (cmd) { result = createEvent(zwaveEvent(cmd)) } if (result?.name == 'hail' && hubFirmwareLessThan("000.011.00602")) { result = [result, response(zwave.basicV1.basicGet())] log.debug "Was hailed: requesting state update" } else { log.debug "Parse returned ${result?.descriptionText}" } return result } // Removed because basic report gets automatically returned with every action as well as multilevel, // so there is no physical/digital distinction def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) { // [name: "switch", value: cmd.value ? "on" : "off", type: "physical"] } def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd) { [name: "switch", value: cmd.value ? "on" : "off", type: "physical"] } def zwaveEvent(physicalgraph.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd) { [name: "switch", value: cmd.value ? "on" : "off", type: "digital"] } def zwaveEvent(physicalgraph.zwave.commands.configurationv1.ConfigurationReport cmd) { def name = "" def value = "" def reportValue = cmd.configurationValue[0] log.debug "---CONFIGURATION REPORT V1--- ${device.displayName} parameter ${cmd.parameterNumber} with a byte size of ${cmd.size} is set to ${cmd.configurationValue}" switch (cmd.parameterNumber) { case 1: name = "topoff" value = reportValue == 1 ? "true" : "false" break case 2: switch (reportValue) { case 0: value = "off" break case 1: value = "on" break case 2: value = "never" break case 3: value = "always" break default: value = "off" break } name = "ledfollow" break case 3: name = "autoofftimer" value = cmd.configurationValue[1] + (cmd.configurationValue[0] * 0x100) break case 4: name = "afterfailure" switch (reportValue) { case 0: value = "off" break case 1: value = "on" break case 2: value = "remember" break default: break } } createEvent([name: name, value: value]) } def zwaveEvent(physicalgraph.zwave.commands.hailv1.Hail cmd) { [name: "hail", value: "hail", descriptionText: "Switch button was pressed", displayed: false] } def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) { def manufacturerCode = String.format("%04X", cmd.manufacturerId) def productTypeCode = String.format("%04X", cmd.productTypeId) def productCode = String.format("%04X", cmd.productId) def msr = manufacturerCode + "-" + productTypeCode + "-" + productCode updateDataValue("MSR", msr) updateDataValue("Manufacturer", "Zooz") updateDataValue("Manufacturer ID", manufacturerCode) updateDataValue("Product Type", productTypeCode) updateDataValue("Product Code", productCode) createEvent([descriptionText: "$device.displayName MSR: $msr", isStateChange: false]) } def zwaveEvent(physicalgraph.zwave.commands.crc16encapv1.Crc16Encap cmd) { def versions = commandClassVersions def version = versions[cmd.commandClass as Integer] def ccObj = version ? zwave.commandClass(cmd.commandClass, version) : zwave.commandClass(cmd.commandClass) def encapsulatedCommand = ccObj?.command(cmd.command)?.parse(cmd.data) if (encapsulatedCommand) { zwaveEvent(encapsulatedCommand) } } def zwaveEvent(physicalgraph.zwave.Command cmd) { // Handles all Z-Wave commands we aren't interested in [:] } def on() { delayBetween([ zwave.basicV1.basicSet(value: 0xFF).format(), zwave.switchBinaryV1.switchBinaryGet().format() ]) } def off() { delayBetween([ zwave.basicV1.basicSet(value: 0x00).format(), zwave.switchBinaryV1.switchBinaryGet().format() ]) } def poll() { log.debug "poll" delayBetween([ zwave.switchBinaryV1.switchBinaryGet().format(), zwave.manufacturerSpecificV1.manufacturerSpecificGet().format() ]) } /** * PING is used by Device-Watch in attempt to reach the Device **/ def ping() { log.debug "ping" refresh() } def refresh() { log.debug "refresh" delayBetween([ zwave.switchBinaryV1.switchBinaryGet().format(), zwave.manufacturerSpecificV1.manufacturerSpecificGet().format() ]) } def parmSet(parmnum, parmsize, parmval) { return zwave.configurationV1.configurationSet(scaledConfigurationValue: parmval, parameterNumber: parmnum, size: parmsize).format() } def parmGet(parmnum) { return zwave.configurationV1.configurationGet(parameterNumber: parmnum).format() } def mfrGet() { return zwave.manufacturerSpecificV2.manufacturerSpecificGet().format() } def zwaveEvent(physicalgraph.zwave.commands.versionv1.VersionReport cmd) { updateDataValue("applicationVersion", "${cmd.applicationVersion}") updateDataValue("applicationSubVersion", "${cmd.applicationSubVersion}") updateDataValue("zWaveLibraryType", "${cmd.zWaveLibraryType}") updateDataValue("zWaveProtocolVersion", "${cmd.zWaveProtocolVersion}") updateDataValue("zWaveProtocolSubVersion", "${cmd.zWaveProtocolSubVersion}") } def zwaveEvent(physicalgraph.zwave.commands.versionv1.VersionCommandClassReport cmd) { log.debug "vccr" def rcc = "" log.debug "version: ${cmd.commandClassVersion}" log.debug "class: ${cmd.requestedCommandClass}" rcc = Integer.toHexString(cmd.requestedCommandClass.toInteger()).toString() log.debug "${rcc}" if (cmd.commandClassVersion > 0) {log.debug "0x${rcc}_V${cmd.commandClassVersion}"} }