Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
665 lines (598 sloc) 22.7 KB
/**
* Zooz Zen26 Switch v2
*
* Revision History:
* 2019-03-24 - Initial release
* 2019-07-11 - Added all functions for firmware 2.01
* 2019-07-13 - Fix logic for null preferences
*
* Supported Command Classes
* Association v2
* Association Group Information
* Central Scene
* 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 Paddle Control 0 (Default)-Upper paddle turns light on, 1-Lower paddle turns light on, 2-either paddle toggles on/off
* 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 1 Auto Turn-Off 0 (Default)-Timer disabled, 1-Timer enabled; Set time in parameter 4
* 4 4 Turn-off Timer 60 (Default)-Time in minutes after turning on to automatically turn off (1-65535 minutes)
* 5 1 Auto Turn-On 0 (Default)-Timer disabled, 1-Timer enabled; Set time in parameter 6
* 6 4 Turn-on Timer 60 (Default)-Time in minutes after turning off to automatically turn on (1-65535 minutes)
* 8 1 Power Restore 2 (Default)-Remember state from pre-power failure, 0-Off after power restored, 1-On after power restore
* 10 1 Scene Control 0 (Default)-Scene control disabled, 1-Scene control enabled
* 11 1 Disable Paddle 1 (Default)-Paddle is used for local control, 0-Paddle single tap disabled
*/
metadata {
definition (name: "Zooz Zen26 Switch v2", 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"
capability "Button"
command "tapDown1"
command "tapDown2"
command "tapDown3"
command "tapDown4"
command "tapDown5"
command "tapUp1"
command "tapUp2"
command "tapUp3"
command "tapUp4"
command "tapUp5"
//zw:L type:1001 mfr:027A prod:A000 model:A001 ver:2.00 zwv:5.03 lib:03 cc:5E,25,85,8E,59,55,86,72,5A,73,70,5B,6C,9F,7A role:05 ff:8700 ui:8704
fingerprint mfr:"027A", prod:"A000", model:"A001", ver:"2.00", deviceJoinName: "Zooz Zen26 Switch v2"
fingerprint deviceId:"0x1001", inClusters: "0x5E,0x25,0x85,0x8E,0x59,0x55,0x86,0x72,0x5A,0x5B,0x73,0x70,0x6C,0x9F,0x7A"
fingerprint cc: "0x5E,0x25,0x85,0x8E,0x59,0x55,0x86,0x72,0x5A,0x5B,0x73,0x70,0x6C,0x9F,0x7A", mfr:"027A", prod:"A000", model:"A001", deviceJoinName: "Zooz Zen26 Switch v2"
}
// 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 "paddleControl", "enum", title: "Paddle Control", description: "Standard, Inverted, or Toggle", options:["std": "Standard", "invert": "Invert", "toggle": "Toggle"], defaultValue: "std"
input "powerRestore", "enum", title: "After Power Restore", description: "State after power restore", options:["prremember": "Remember", "proff": "Off", "pron": "On"],defaultValue: "prremember",displayDuringSetup: false
input "sceneCtrl", "bool", title: "Scene Control", description: "Enable scene control", required: false, defaultValue: false
input "autoTurnoff", "bool", title: "Auto Off", description: "Light will automatically turn off after set time", required: false, defaultValue: false
input "autoTurnon", "bool", title: "Auto On", description: "Light will automatically turn on after set time", required: false, defaultValue: false
input "offTimer", "number", title: "Off Timer", description: "Time in minutes to automatically turn off", required: false, defaultValue: 60, range: "1..65535"
input "onTimer", "number", title: "On Timer", description: "Time in minutes to automatically turn on", required: false, defaultValue: 60, range: "1..65535"
input "localControl", "bool", title: "Local Control", description: "Local paddle control enabled", required: false, defaultValue: true
input (
type: "paragraph",
element: "paragraph",
title: "Configure Association Groups:",
description: "Devices in association group 2 will receive Basic Set commands directly from the switch when it is turned on or off. Use this to control another device as if it was connected to this switch.\n\n" +"Devices are entered as a comma delimited list of IDs in hexadecimal format."
)
input (
name: "requestedGroup2",
title: "Association Group 2 Members (Max of 5):",
type: "text",
required: false
)
}
// 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
0x5B: 1, // Central Scene
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 << parmGet(5)
cmds << parmGet(6)
cmds << parmGet(8)
cmds << parmGet(10)
cmds << parmGet(11)
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(){
// These are needed as SmartThings is not honoring defaultValue in preferences. They are set to the device defaults
def setLocalControl = 1
if (localControl!=null) {setLocalControl = localControl == true ? 1 : 0}
def setOffTimer = 60
if (offTimer) {setOffTimer = offTimer}
def setOnTimer = 60
if (onTimer) {setOnTimer = onTimer}
def nodes = []
def commands = []
if (getDataValue("MSR") == null) {
def level = 99
commands << mfrGet()
commands << zwave.versionV1.versionGet().format()
commands << zwave.basicV1.basicSet(value: level).format()
commands << zwave.switchMultilevelV1.switchMultilevelGet().format()
}
def setScene = sceneCtrl == true ? 1 : 0
def setPowerRestore = 2
if (powerRestore != null) {setPowerRestore = powerRestore == "prremember" ? 2 : powerRestore == "proff" ? 0 : 1}
def setAutoTurnon = autoTurnon == true ? 1 : 0
def setAutoTurnoff = autoTurnoff == true ? 1 : 0
def setPaddleControl = 0
switch (paddleControl) {
case "std":
setPaddleControl = 0
break
case "invert":
setPaddleControl = 1
break
case "toggle":
setPaddleControl = 2
break
default:
setPaddleControl = 0
break
}
def setLedIndicator = 0
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
}
if (setScene) {
sendEvent(name: "numberOfButtons", value: 10, displayed: false)
} else {
sendEvent(name: "numberOfButtons", value: 0, displayed: false)
}
if (settings.requestedGroup2 != state.currentGroup2) {
nodes = parseAssocGroupList(settings.requestedGroup2, 2)
commands << zwave.associationV2.associationRemove(groupingIdentifier: 2, nodeId: []).format()
commands << zwave.associationV2.associationSet(groupingIdentifier: 2, nodeId: nodes).format()
commands << zwave.associationV2.associationGet(groupingIdentifier: 2).format()
state.currentGroup2 = settings.requestedGroup2
}
//parmset takes the parameter number, it's size, and the value - in that order
commands << parmSet(11, 1, setLocalControl)
commands << parmSet(10, 1, setScene)
commands << parmSet(8, 1, setPowerRestore)
commands << parmSet(6, 4, setOnTimer)
commands << parmSet(5, 1, setAutoTurnon)
commands << parmSet(4, 4, setOffTimer)
commands << parmSet(3, 1, setAutoTurnon)
commands << parmSet(2, 1, setLedIndicator)
commands << parmSet(1, 1, setPaddleControl)
commands << parmGet(11)
commands << parmGet(10)
commands << parmGet(8)
commands << parmGet(6)
commands << parmGet(5)
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"]
}
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd) {
[name: "switch", value: cmd.value ? "on" : "off"]
}
def zwaveEvent(physicalgraph.zwave.commands.associationv2.AssociationReport cmd) {
log.debug "---ASSOCIATION REPORT V2--- ${device.displayName} sent groupingIdentifier: ${cmd.groupingIdentifier} maxNodesSupported: ${cmd.maxNodesSupported} nodeId: ${cmd.nodeId} reportsToFollow: ${cmd.reportsToFollow}"
state.group3 = "1,2"
if (cmd.groupingIdentifier == 3) {
if (cmd.nodeId.contains(zwaveHubNodeId)) {
createEvent(name: "numberOfButtons", value: 10, displayed: false)
}
else {
sendEvent(name: "numberOfButtons", value: 0, displayed: false)
sendHubCommand(new physicalgraph.device.HubAction(zwave.associationV2.associationSet(groupingIdentifier: 3, nodeId: zwaveHubNodeId).format()))
sendHubCommand(new physicalgraph.device.HubAction(zwave.associationV2.associationGet(groupingIdentifier: 3).format()))
}
}
}
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 = "autooff"
value = reportValue == 1 ? "true" : "false"
break
case 4:
name = "autoofftimer"
value = cmd.configurationValue[3] + (cmd.configurationValue[2] * 0x100) + (cmd.configurationValue[1] * 0x10000) + (cmd.configurationValue[0] * 0x1000000)
break
case 5:
name = "autoon"
value = reportValue == 1 ? "true" : "false"
break
case 6:
name = "autoontimer"
value = cmd.configurationValue[3] + (cmd.configurationValue[2] * 0x100) + (cmd.configurationValue[1] * 0x10000) + (cmd.configurationValue[0] * 0x1000000)
break
case 8:
name = "afterfailure"
switch (reportValue) {
case 0:
value = "off"
break
case 1:
value = "on"
break
case 2:
value = "remember"
break
default:
break
}
case 10:
name = "scene_control"
value = reportValue == 1 ? "true" : "false"
break
case 11:
name = "local_control"
value = reportValue == 1 ? "true" : "false"
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 zwaveEvent(physicalgraph.zwave.commands.centralscenev1.CentralSceneNotification cmd) {
def result = []
switch (cmd.sceneNumber) {
case 1:
// Down
switch (cmd.keyAttributes) {
case 0:
// Press Once
result += createEvent(tapDown1Response("physical"))
result += createEvent([name: "switch", value: "off", type: "physical"])
break
case 3:
// 2 Times
result +=createEvent(tapDown2Response("physical"))
break
case 4:
// 3 times
result=createEvent(tapDown3Response("physical"))
break
case 5:
// 4 times
result=createEvent(tapDown4Response("physical"))
break
case 6:
// 5 times
result=createEvent(tapDown5Response("physical"))
break
default:
log.debug ("unexpected down press keyAttribute: $cmd.keyAttributes")
}
break
case 2:
// Up
switch (cmd.keyAttributes) {
case 0:
// Press Once
result += createEvent(tapUp1Response("physical"))
result += createEvent([name: "switch", value: "on", type: "physical"])
break
case 3:
// 2 Times
result+=createEvent(tapUp2Response("physical"))
break
case 4:
// 3 Times
result=createEvent(tapUp3Response("physical"))
break
case 5:
// 4 Times
result=createEvent(tapUp4Response("physical"))
break
case 6:
// 5 Times
result=createEvent(tapUp5Response("physical"))
break
default:
log.debug ("unexpected up press keyAttribute: $cmd.keyAttributes")
}
break
default:
// unexpected case
log.debug ("unexpected scene: $cmd.sceneNumber")
log.debug ("unexpected scene: $cmd")
}
}
def buttonEvent(button, value) {
createEvent(name: "button", value: value, data: [buttonNumber: button], descriptionText: "$device.displayName button $button was $value", isStateChange: true)
}
def tapDown1Response(String buttonType) {
[name: "button", value: "pushed", data: [buttonNumber: "2"], descriptionText: "$device.displayName Tap-Down-1 (button 2) pressed", isStateChange: true, type: "$buttonType"]
}
def tapDown2Response(String buttonType) {
[name: "button", value: "pushed", data: [buttonNumber: "4"], descriptionText: "$device.displayName Tap-Down-2 (button 4) pressed", isStateChange: true, type: "$buttonType"]
}
def tapDown3Response(String buttonType) {
[name: "button", value: "pushed", data: [buttonNumber: "6"], descriptionText: "$device.displayName Tap-Down-3 (button 6) pressed", isStateChange: true, type: "$buttonType"]
}
def tapDown4Response(String buttonType) {
[name: "button", value: "pushed", data: [buttonNumber: "8"], descriptionText: "$device.displayName Tap-Down-4 (button 8) pressed", isStateChange: true, type: "$buttonType"]
}
def tapDown5Response(String buttonType) {
[name: "button", value: "pushed", data: [buttonNumber: "10"], descriptionText: "$device.displayName Tap-Down-5 (button 10) pressed", isStateChange: true, type: "$buttonType"]
}
def tapUp1Response(String buttonType) {
[name: "button", value: "pushed", data: [buttonNumber: "1"], descriptionText: "$device.displayName Tap-Up-1 (button 1) pressed", isStateChange: true, type: "$buttonType"]
}
def tapUp2Response(String buttonType) {
[name: "button", value: "pushed", data: [buttonNumber: "3"], descriptionText: "$device.displayName Tap-Up-2 (button 3) pressed", isStateChange: true, type: "$buttonType"]
}
def tapUp3Response(String buttonType) {
[name: "button", value: "pushed", data: [buttonNumber: "5"], descriptionText: "$device.displayName Tap-Up-3 (button 5) pressed", isStateChange: true, type: "$buttonType"]
}
def tapUp4Response(String buttonType) {
[name: "button", value: "pushed", data: [buttonNumber: "7"], descriptionText: "$device.displayName Tap-Up-4 (button 7) pressed", isStateChange: true, type: "$buttonType"]
}
def tapUp5Response(String buttonType) {
[name: "button", value: "pushed", data: [buttonNumber: "9"], descriptionText: "$device.displayName Tap-Up-5 (button 9) pressed", isStateChange: true, type: "$buttonType"]
}
def tapDown1() {
sendEvent(tapDown1Response("digital"))
}
def tapDown2() {
sendEvent(tapDown2Response("digital"))
}
def tapDown3() {
sendEvent(tapDown3Response("digital"))
}
def tapDown4() {
sendEvent(tapDown4Response("digital"))
}
def tapDown5() {
sendEvent(tapDown5Response("digital"))
}
def tapUp1() {
sendEvent(tapUp1Response("digital"))
}
def tapUp2() {
sendEvent(tapUp2Response("digital"))
}
def tapUp3() {
sendEvent(tapUp3Response("digital"))
}
def tapUp4() {
sendEvent(tapUp4Response("digital"))
}
def tapUp5() {
sendEvent(tapUp5Response("digital"))
}
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}"}
}
private parseAssocGroupList(list, group) {
def nodes = group == 2 ? [] : [zwaveHubNodeId]
if (list) {
def nodeList = list.split(',')
def max = group == 2 ? 5 : 4
def count = 0
log.debug "parsing asssociation"
nodeList.each { node ->
node = node.trim()
if ( count >= max) {
log.warn "Association Group ${group}: Number of members is greater than ${max}! The following member was discarded: ${node}"
}
else if (node.matches("\\p{XDigit}+")) {
def nodeId = Integer.parseInt(node,16)
if (nodeId == zwaveHubNodeId) {
log.warn "Association Group ${group}: Adding the hub as an association is not allowed (it would break double-tap)."
}
else if ( (nodeId > 0) & (nodeId < 256) ) {
nodes << nodeId
count++
}
else {
log.warn "Association Group ${group}: Invalid member: ${node}"
}
}
else {
log.warn "Association Group ${group}: Invalid member: ${node}"
}
}
}
return nodes
}
You can’t perform that action at this time.