/*
* Universal Z-Wave Switch
*
* For Support, Information, and Updates:
* https://community.hubitat.com/t/universal-z-wave-drivers/140325
* https://github.com/jtp10181/Hubitat/tree/main/Drivers/
*
Changelog:
## Known Issues
- Do not try to scan multiple devices at once
## [0.4.0] - 2024-07-16 (@jtp10181)
- Added full associations detection and support
- Added scene/button detection and support
- Configure will run any scans that are missing
## [0.1.0] - 2024-07-07 (@jtp10181)
- Initial Release based from Universal Scanner
- Added supervised commands support
* Copyright 2024 Jeff Page
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*
*/
import groovy.json.JsonOutput
import groovy.json.JsonSlurper
import groovy.transform.Field
@Field static final String VERSION = "0.4.0"
@Field static final String PACKAGE = "Uni-ZW"
@Field static final String DRIVER = "Switch"
@Field static final String COMM_LINK = "https://community.hubitat.com/t/universal-z-wave-drivers/140325"
metadata {
definition (
name: "Universal Z-Wave Switch",
namespace: "jtp10181",
author: "Jeff Page (@jtp10181)",
singleThreaded: true,
importUrl: "https://raw.githubusercontent.com/jtp10181/Hubitat/main/Drivers/universal/universal-zwave-switch.groovy"
) {
capability "Actuator"
capability "Switch"
capability "Configuration"
capability "Refresh"
capability "PushableButton"
capability "HoldableButton"
capability "ReleasableButton"
capability "DoubleTapableButton"
command "setParameter",[[name:"parameterNumber*",type:"NUMBER", description:"Parameter Number"],
[name:"value*",type:"NUMBER", description:"Parameter Value"],
[name:"size",type:"NUMBER", description:"Parameter Size"]]
command "queryDevice", [[name: "option*", type: "ENUM", constraints: ["Parameters", "Associations", "Sync Only"]]]
//DEBUGGING
// command "debugShowVars"
attribute "syncStatus", "string"
fingerprint mfr:"001D", prod:"0042", deviceId:"0002", inClusters:"0x00,0x00", controllerType: "ZWV" //Leviton ZW15S Switch
}
preferences {
//Saved Parameters
configParams.each { param ->
if (!param.hidden) {
if (param.options) {
Integer paramVal = getParamValue(param)
input "configParam${param.num}", "enum",
title: fmtTitle("${param.title}"),
description: fmtDesc("• Parameter #${param.num}, Selected: ${paramVal}" + (param?.description ? "
• ${param?.description}" : '')),
defaultValue: param.defaultVal,
options: param.options,
required: false
}
else if (param.range) {
input "configParam${param.num}", "number",
title: fmtTitle("${param.title}"),
description: fmtDesc("• Parameter #${param.num}, Range: ${(param.range).toString()}, DEFAULT: ${param.defaultVal}" + (param?.description ? "
• ${param?.description}" : '')),
defaultValue: param.defaultVal,
range: param.range,
required: false
}
}
}
if (!configParams) {
input "instructions", "hidden",
title: fmtTitle("Instructions / Help"),
description: fmtDesc("To discover settings for your device, run Query Device (Parameters) and watch the Current States for updates")
}
assocSettings.findAll{ it.num > 1 }.each { assoc ->
// logDebug "parameters assocSettings ${assoc}"
input "assocDNI${assoc.num}", "string", required: false,
title: fmtTitle("Associations - Group ${assoc.num} (${assoc.name})"),
description: fmtDesc("${assoc.name} - Supports up to ${assoc.maxNodes} Hex Device IDs separated by commas. Save as blank or 0 to clear.")
}
input "supervisedCmds", "bool",
title: fmtTitle("Supervised Commands") + " (Experimental)",
description: fmtDesc("This can increase reliability when the device is paired with security, but may not work correctly on all devices."),
defaultValue: false
input "scanType", "enum", defaultValue: 3,
title: fmtTitle("Parameter Scan Type"),
description: fmtDesc("Try basic scans if the full scan won't complete"),
options: [3: "Full Name/Details", 2:"Basic/Name Only", 1:"Basic Info Only"]
}
}
void debugShowVars() {
log.warn "settings ${settings.hashCode()} ${settings}"
// log.warn "paramsList ${paramsList.hashCode()} ${paramsList}"
// log.warn "paramsMap ${paramsMap.hashCode()} ${paramsMap}"
log.warn "paramScan ${paramScan.hashCode()} ${paramScan}"
log.warn "assocScan ${assocScan.hashCode()} ${assocScan}"
log.warn "supervisedPackets ${supervisedPackets.hashCode()} ${supervisedPackets}"
}
//Association Settings
// @Field static final int maxAssocGroups = 1
// @Field static final int maxAssocNodes = 1
/*** Static Lists and Settings ***/
//Set Command Class Versions
@Field static final Map commandClassVersions = [
0x5B: 3, // Central Scene
0x25: 2, // Switch Binary (switchBinary)
0x26: 4, // Switch Multilevel (switchMultilevel)
0x60: 3, // Multi Channel
0x6C: 1, // Supervision (supervision)
0x70: 3, // Configuration (configuration)
0x72: 2, // Manufacturer Specific (manufacturerSpecific)
0x85: 2, // Association (association)
0x8E: 3, // Multi Channel Association (multiChannelAssociation)
0x59: 3, // Association Group Information (associationGrpInfo)
0x86: 2, // Version (version)
]
/*******************************************************************
***** Core Functions
********************************************************************/
void installed() {
logWarn "installed..."
initialize()
}
void initialize() {
logWarn "initialize..."
refresh()
}
List configure() {
logWarn "configure..."
state.remove("deviceSync")
state.remove("queryParams")
state.remove("queryAssoc")
if (!pendingChanges || state.resyncAll == null) {
logDebug "Enabling Full Re-Sync"
state.resyncAll = true
}
List cmds = []
Integer totalDelay = 100
if (!device.getDataValue("zwAssociations")) {
logWarn "Queuing up full Associations Query"
runInMillis(totalDelay, scanAssociations)
totalDelay += 2000
}
if ((state.configCCVer == null || state.configCCVer >= 3) && !device.getDataValue("parameters")) {
logWarn "Queuing up full Parameters Query"
runInMillis(totalDelay, scanParamsCC)
totalDelay += 5000
}
if (state.numberOfButtons == null) sendEvent(name:"numberOfButtons", value: 0)
if (state.endPoints == null) state.endPoints = 0
cmds << secureCmd(zwave.multiChannelV3.multiChannelEndPointGet())
cmds << secureCmd(zwave.centralSceneV3.centralSceneSupportedGet())
cmds << "delay ${totalDelay}"
cmds += getRefreshCmds()
cmds << "delay 2000"
cmds += getConfigureCmds()
if (state.resyncAll) clearVariables()
updateSyncingStatus(6)
return cmds ? delayBetween(cmds, 300) : []
}
List updated() {
logDebug "updated..."
checkLogLevel()
state.remove("deviceSync")
refreshSyncStatus()
List cmds = getConfigureCmds()
return cmds ? delayBetween(cmds, 300) : []
}
List refresh() {
logDebug "refresh..."
List cmds = getRefreshCmds()
return cmds ? delayBetween(cmds, 200) : []
}
/*******************************************************************
***** Driver Commands
********************************************************************/
/*** Capabilities ***/
def on() {
logDebug "on..."
return getOnOffCmds(0xFF)
}
def off() {
logDebug "off..."
return getOnOffCmds(0x00)
}
//Button commands required with capabilities
void push(buttonId) { sendBasicButtonEvent(buttonId, "pushed") }
void hold(buttonId) { sendBasicButtonEvent(buttonId, "held") }
void release(buttonId) { sendBasicButtonEvent(buttonId, "released") }
void doubleTap(buttonId) { sendBasicButtonEvent(buttonId, "doubleTapped") }
/*** Custom Commands ***/
void queryDevice(option) {
if (option == "Parameters") scanParamsCC()
else if (option == "Associations") scanAssociations()
else if (option == "Sync Only") syncFromDevice()
else logWarn "queryDevice unrecognized option: ${option}"
}
void scanParamsCC() {
state.queryParams = true
String cmd = secureCmd(zwave.versionV2.versionCommandClassGet(requestedCommandClass:0x70))
sendEvent(name:"queryStatus", value:"Probing for Config Support...")
sendCommands(cmd)
}
void scanParameters(param=1) {
logDebug "scanParameters: Starting with #${param}"
paramScan = [:]
Map args = [parameterNumber: param]
String cmd = secureCmd(new hubitat.zwave.commands.configurationv3.ConfigurationPropertiesGet(args))
sendEvent(name:"queryStatus", value:"Scanning ($param)")
sendCommands(cmd)
}
void scanAssociations() {
logDebug "scanAssociations probing for number of association groups"
state.queryAssoc = true
assocScan = [:]
List cmds = []
cmds << secureCmd(zwave.associationV2.associationGroupingsGet())
cmds << secureCmd(zwave.multiChannelAssociationV2.multiChannelAssociationGroupingsGet())
sendEvent(name:"queryStatus", value:"Probing for Association Groups...")
sendCommands(cmds)
}
void syncFromDevice() {
sendEvent(name:"queryStatus", value:"Syncing Settings from Device...")
state.deviceSync = true
device.removeDataValue("configVals")
configsList[device.id] = [:]
List cmds = []
for (int i = 1; i <= maxAssocGroups; i++) {
cmds << associationGetCmd(i)
}
configParams.each { param ->
device.removeSetting("configParam${param.num}")
logDebug "Getting ${param.title} (#${param.num}) from device"
cmds += configGetCmd(param)
}
if (cmds) sendCommands(cmds)
}
def setParameter(paramNum, value, size = null) {
paramNum = safeToInt(paramNum)
Map param = getParam(paramNum)
if (param && !size) { size = param.size }
if (paramNum == null || value == null || size == null) {
logWarn "Incomplete parameter list supplied..."
logWarn "Syntax: setParameter(paramNum, value, size)"
return
}
logDebug "setParameter ( number: $paramNum, value: $value, size: $size )" + (param ? " [${param.name} - ${param.title}]" : "")
return configSetGetCmd([num: paramNum, size: size], value as Integer)
}
/*******************************************************************
***** Z-Wave Reports
********************************************************************/
void parse(String description) {
if (description =~ /command: 700F/) {
description = description.replace("FF FF FF FF", "7F FF FF FE")
}
zwaveParse(description)
}
void zwaveEvent(hubitat.zwave.commands.multichannelv3.MultiChannelCmdEncap cmd) {
zwaveMultiChannel(cmd)
}
void zwaveEvent(hubitat.zwave.commands.supervisionv1.SupervisionGet cmd, ep=0) {
zwaveSupervision(cmd,ep)
}
void zwaveEvent(hubitat.zwave.commands.configurationv3.ConfigurationReport cmd) {
logTrace "${cmd}"
updateSyncingStatus()
Map param = getParam(cmd.parameterNumber)
Long val = cmd.scaledConfigurationValue
if (param) {
//Convert scaled signed integer to unsigned
if (param.format >= 1 || param.format == null) {
Long sizeFactor = Math.pow(256,param.size).round()
if (val < 0) { val += sizeFactor }
}
logDebug "${param.title} (#${param.num}) = ${val.toString()}"
setParamStoredValue(param.num, val)
}
else {
logDebug "Parameter #${cmd.parameterNumber} = ${val.toString()}"
}
if (state.deviceSync) {
device.updateSetting("configParam${cmd.parameterNumber}", val as Long)
}
}
//Association Scanning
void zwaveEvent(hubitat.zwave.commands.associationv2.AssociationGroupingsReport cmd) {
logTrace "${cmd}"
logDebug "Association Groups Report found ${cmd.supportedGroupings} groups"
//assocScan[0] = [groups: cmd.supportedGroupings]
state.assocGroups = cmd.supportedGroupings
if (cmd.supportedGroupings) {
List cmds = []
for (int i = 1; i <= cmd.supportedGroupings; i++) {
cmds << associationGetCmd(i)
}
sendEvent(name:"queryStatus", value:"Checking (${cmd.supportedGroupings}) Groups...")
sendCommands(cmds, 500)
}
}
//Association Scanning
void zwaveEvent(hubitat.zwave.commands.multichannelassociationv3.MultiChannelAssociationGroupingsReport cmd) {
logTrace "${cmd}"
logDebug "Multi-Channel Association Groups Report found ${cmd.supportedGroupings} groups"
}
//Association Scanning (Name)
void zwaveEvent(hubitat.zwave.commands.associationgrpinfov3.AssociationGroupNameReport cmd) {
logTrace "${cmd}"
String name = new String(cmd.name as byte[])
if (assocScan[cmd.groupingIdentifier] != null){
assocScan[cmd.groupingIdentifier].name = name
}
}
//Associations
void zwaveEvent(hubitat.zwave.commands.associationv2.AssociationReport cmd) {
logTrace "${cmd}"
updateSyncingStatus()
Integer grp = cmd.groupingIdentifier
//Handle Query / Scan responses
if (state.queryAssoc && grp > 0) {
assocScan[cmd.groupingIdentifier] = [
num: cmd.groupingIdentifier,
maxNodes: cmd.maxNodesSupported
]
//Got the last one, schedule processing
if (grp >= maxAssocGroups) {
runIn (2, processAssocScan)
}
List cmds = []
cmds << secureCmd(zwave.associationGrpInfoV1.associationGroupNameGet(groupingIdentifier: grp))
// cmds << secureCmd(zwave.associationGrpInfoV1.associationGroupCommandListGet(groupingIdentifier: grp))
sendCommands(cmds)
}
if (grp == 1) {
if (!state.endPoints) {
logDebug "Lifeline Association: ${cmd.nodeId}"
state.group1Assoc = (cmd.nodeId == [zwaveHubNodeId]) ? true : false
}
}
else if (grp > 1 && grp <= maxAssocGroups) {
String dnis = convertIntListToHexList(cmd.nodeId)?.join(", ")
logDebug "Confirmed Group $grp Association: " + (cmd.nodeId.size()>0 ? "${dnis} // ${cmd.nodeId}" : "None")
if (cmd.nodeId.size() > 0) {
if (!state.assocNodes) state.assocNodes = [:]
state.assocNodes["$grp"] = cmd.nodeId
} else {
state.assocNodes?.remove("$grp" as String)
}
device.updateSetting("assocDNI$grp", [value:"${dnis}", type:"string"])
}
else {
logDebug "Unhandled Group: $cmd"
}
}
//Associations (MultiChannel)
void zwaveEvent(hubitat.zwave.commands.multichannelassociationv3.MultiChannelAssociationReport cmd) {
logTrace "${cmd}"
updateSyncingStatus()
List mcNodes = []
cmd.multiChannelNodeIds.each {mcNodes += "${it.nodeId}:${it.endPointId}"}
if (cmd.groupingIdentifier == 1) {
if (state.endPoints) {
logDebug "Lifeline Association: ${cmd.nodeId} | MC: ${mcNodes}"
state.group1Assoc = (mcNodes == ["${zwaveHubNodeId}:0"] ? true : false)
}
}
else {
logDebug "Unhandled Group: $cmd"
}
}
void zwaveEvent(hubitat.zwave.commands.basicv1.BasicReport cmd, ep=0) {
logTrace "${cmd} (ep ${ep})"
sendSwitchEvents(cmd.value, digitalCheck(), ep)
}
void zwaveEvent(hubitat.zwave.commands.switchbinaryv2.SwitchBinaryReport cmd, ep=0) {
logTrace "${cmd} (ep ${ep})"
if (cmd.duration == 0) {
sendSwitchEvents(cmd.value, digitalCheck(), ep)
}
}
void zwaveEvent(hubitat.zwave.commands.switchmultilevelv4.SwitchMultilevelReport cmd, ep=0) {
logTrace "${cmd} (ep ${ep})"
if (cmd.duration == 0) {
sendSwitchEvents(cmd.value, digitalCheck(), ep)
}
}
String digitalCheck() {
String type = (state.isDigital ? "digital" : "physical")
state.remove("isDigital")
return type
}
//Handle device that sends Hail instead of status updates
void zwaveEvent(hubitat.zwave.commands.hailv1.Hail cmd, ep=0) {
logTrace "${cmd} (ep ${ep})"
sendCommands(getRefreshCmds())
}
//Command Class Reports
void zwaveEvent(hubitat.zwave.commands.versionv2.VersionCommandClassReport cmd) {
logTrace "${cmd}"
Integer ccNum = (cmd.requestedCommandClass as Integer)
Integer ccVer = (cmd.commandClassVersion as Integer)
logInfo "--- CommandClassReport - class:0x${intToHexStr(ccNum)}, version:${ccVer}"
if (ccNum == 0x70 && state.queryParams) {
state.configCCVer = ccVer
if (ccVer >= 3) {
logDebug "Device reports Configuration CC v${ccVer}, supports fetching properties"
runInMillis(500, scanParameters)
}
else {
logWarn "Device reports Configuration CC v${ccVer}, DOES NOT support fetching properties"
sendEvent(name:"queryStatus", value:"Parameter Query not supported by device")
state.remove("queryParams")
}
}
}
//Parameter Scanning
void zwaveEvent(hubitat.zwave.commands.configurationv3.ConfigurationPropertiesReport cmd) {
logTrace "${cmd}"
List newCmds = []
String status = "invalid"
//Skip if size=0 (invalid param)
if (cmd.size) {
//Save The Properties
paramScan[cmd.parameterNumber] = [
num: cmd.parameterNumber, format: cmd.format,
size: cmd.size, defaultVal: cmd.defaultValue,
range: ("${cmd.minValue}..${cmd.maxValue}").toString(),
title: "Parameter #${cmd.parameterNumber}",
description: ""
]
status = "saved"
//Request Name and Info
Map args = [parameterNumber: cmd.parameterNumber]
Integer type = (scanType as Integer) ?: 3
if (type >= 2) newCmds << secureCmd(new hubitat.zwave.commands.configurationv3.ConfigurationNameGet(args))
if (type >= 3) newCmds << secureCmd(new hubitat.zwave.commands.configurationv3.ConfigurationInfoGet(args))
}
//Request Next Paramater if there is one
if (cmd.nextParameterNumber && cmd.nextParameterNumber != cmd.parameterNumber) {
logDebug "Received Param $cmd.parameterNumber (${status}), next is #$cmd.nextParameterNumber"
Map args = [parameterNumber: cmd.nextParameterNumber]
sendEvent(name:"queryStatus", value:"Scanning ($cmd.nextParameterNumber)")
newCmds << "delay 500" << secureCmd(new hubitat.zwave.commands.configurationv3.ConfigurationPropertiesGet(args))
}
else {
logDebug "Received Param $cmd.parameterNumber (${status}), that was the last one"
sendEvent(name:"queryStatus", value:"Query Complete... wait for processing")
state.remove("queryParams")
runIn(4, processParamScan)
}
//logDebug "Sending: ${newCmds}"
if (newCmds) sendCommands(newCmds, 500)
}
//Parameter Scanning
void zwaveEvent(hubitat.zwave.commands.configurationv3.ConfigurationNameReport cmd) {
logTrace "${cmd}"
if (paramScan[cmd.parameterNumber]) {
if (paramScan[cmd.parameterNumber].titleTmp == null) paramScan[cmd.parameterNumber].titleTmp = []
paramScan[cmd.parameterNumber].titleTmp[cmd.reportsToFollow] = "$cmd.name"
} else {
logWarn "ConfigurationNameReport: Skipping, Unknown Paramater: ${cmd.parameterNumber}"
}
}
//Parameter Scanning
void zwaveEvent(hubitat.zwave.commands.configurationv3.ConfigurationInfoReport cmd) {
logTrace "${cmd}"
if (paramScan[cmd.parameterNumber]) {
if (paramScan[cmd.parameterNumber].descTmp == null) paramScan[cmd.parameterNumber].descTmp = []
paramScan[cmd.parameterNumber].descTmp[cmd.reportsToFollow] = "$cmd.info"
} else {
logWarn "ConfigurationInfoReport: Skipping, Unknown Paramater: ${cmd.parameterNumber}"
}
}
//Multi-Channel Detection
void zwaveEvent(hubitat.zwave.commands.multichannelv3.MultiChannelEndPointReport cmd, ep=0) {
logTrace "${cmd} (ep ${ep})"
if (cmd.endPoints > 0) {
logDebug "Endpoints (${cmd.endPoints}) Detected and Enabled"
state.endPoints = cmd.endPoints
//runIn(1,createChildDevices)
}
}
//Central Scene (buttons) Detection
void zwaveEvent(hubitat.zwave.commands.centralscenev3.CentralSceneSupportedReport cmd) {
logTrace "${cmd}"
//Figure out the max key presses per button
Integer maxTaps = 1
cmd.supportedKeyAttributes.each {
if (it.keyPress5x) maxTaps=5
else if (it.keyPress4x && maxTaps < 4) maxTaps=4
else if (it.keyPress3x && maxTaps < 3) maxTaps=3
else if (it.keyPress2x && maxTaps < 2) maxTaps=2
else if (it.keyPress1x && maxTaps < 1) maxTaps=1
}
Integer nob = cmd.supportedScenes * maxTaps
state.numberOfButtons = [cmd.supportedScenes, nob]
sendEvent(name:"numberOfButtons", value: nob)
logDebug "CentralSceneSupportedReport: Actual Buttons: ${cmd.supportedScenes}, maxTaps ${maxTaps}, numberofButtons: ${nob}"
//Save to device data
Map csMap = [
supportedScenes: cmd.supportedScenes,
identical: cmd.identical,
supportedKeyAttributes: cmd.supportedKeyAttributes,
numberOfButtons: nob,
maxTaps: maxTaps
]
String csJson = JsonOutput.toJson(csMap) as String
device.updateDataValue("zwCentralScene", csJson)
}
//Central Scene (buttons)
void zwaveEvent(hubitat.zwave.commands.centralscenev3.CentralSceneNotification cmd, ep=0){
logTrace "${cmd} (ep ${ep})"
Integer physicalButtons = state.numberOfButtons?.getAt(0)
Integer btnBaseNum = cmd.sceneNumber ?: 0
Map sceneEvt = [name: "", value: btnBaseNum, desc: "", type:"physical", isStateChange:true]
Integer keyAtt = cmd.keyAttributes as Integer
String btnDesc = ""
if (!physicalButtons || !btnBaseNum) return
//DoubleTapped
if (keyAtt == 3) {
sceneEvt.name = "doubleTapped"
sceneEvt.desc = "button ${sceneEvt.value} ${sceneEvt.name}"
sendEventLog(sceneEvt)
}
if (keyAtt == 2) sceneEvt.name = "held"
else if (keyAtt == 1) sceneEvt.name = "released"
else sceneEvt.name = "pushed"
if (keyAtt >= 3) {
//Adjust button number
btnDesc = " [button ${btnBaseNum} pushed ${keyAtt - 1}x]"
sceneEvt.value = btnBaseNum + (physicalButtons * (keyAtt - 2))
}
sceneEvt.desc = "button ${sceneEvt.value} ${sceneEvt.name}${btnDesc}"
sendEventLog(sceneEvt)
}
void zwaveEvent(hubitat.zwave.commands.manufacturerspecificv2.DeviceSpecificReport cmd) {
logTrace "${cmd}"
switch (cmd.deviceIdType) {
case 1: //Serial Number
String serialNumber = ""
if (cmd.deviceIdDataFormat == 1) {
serialNumber = convertIntListToHexList(cmd.deviceIdData).join()
} else {
cmd.deviceIdData.each { serialNumber += (char)it }
}
logDebug "Device Serial Number: $serialNumber"
device.updateDataValue("serialNumber", serialNumber)
break
}
}
/*******************************************************************
***** Event Senders
********************************************************************/
//evt = [name, value, type, unit, desc, isStateChange]
void sendEventLog(Map evt, Integer ep=0) {
//Set description if not passed in
evt.descriptionText = evt.desc ?: "${evt.name} set to ${evt.value} ${evt.unit ?: ''}".trim()
//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)
}
void sendSwitchEvents(rawVal, String type, Integer ep=0) {
String value = (rawVal ? "on" : "off")
String desc = "switch is turned ${value}" + (type ? " (${type})" : "")
sendEventLog(name:"switch", value:value, type:type, desc:desc, ep)
}
void sendBasicButtonEvent(buttonId, String name) {
String desc = "button ${buttonId} ${name} (digital)"
sendEventLog(name:name, value:buttonId, type:"digital", desc:desc, isStateChange:true)
}
/*******************************************************************
***** Execute / Build Commands
********************************************************************/
List getConfigureCmds() {
logDebug "getConfigureCmds..."
List cmds = []
if (!firmwareVersion) {
cmds << versionGetCmd()
}
cmds += getConfigureAssocsCmds(true)
configParams.each { param ->
Integer paramVal = getParamValueAdj(param)
Integer storedVal = getParamStoredValue(param.num)
if ((paramVal != null) && (state.resyncAll || (storedVal != paramVal))) {
logDebug "Changing ${param.title} (#${param.num}) from ${storedVal} to ${paramVal}"
cmds += configSetGetCmd(param, paramVal)
}
}
state.resyncAll = false
return cmds ?: []
}
List getRefreshCmds() {
List cmds = []
if (state.resyncAll || !firmwareVersion) {
cmds << mfgSpecificGetCmd()
cmds << versionGetCmd()
}
cmds << switchBinaryGetCmd()
return cmds ?: []
}
List getConfigureAssocsCmds(Boolean logging=false) {
List cmds = []
if (!state.group1Assoc || state.resyncAll) {
if (state.group1Assoc == false) {
if (logging) logDebug "Clearing incorrect lifeline association..."
cmds << associationRemoveCmd(1,[])
cmds << secureCmd(zwave.multiChannelAssociationV3.multiChannelAssociationRemove(groupingIdentifier: 1, nodeId:[], multiChannelNodeIds:[]))
}
if (logging) logDebug "Setting ${state.endPoints ? 'multi-channel' : 'standard'} lifeline association..."
if (state.endPoints > 0) {
cmds << secureCmd(zwave.multiChannelAssociationV3.multiChannelAssociationSet(groupingIdentifier: 1, multiChannelNodeIds: [[nodeId: zwaveHubNodeId, bitAddress:0, endPointId: 0]]))
cmds << mcAssociationGetCmd(1)
}
else {
cmds << associationSetCmd(1, [zwaveHubNodeId])
cmds << associationGetCmd(1)
}
}
for (int i = 2; i <= maxAssocGroups; i++) {
List cmdsEach = []
List settingNodeIds = getAssocDNIsSettingNodeIds(i)
//Need to remove first then add in case we are at limit
List oldNodeIds = state.assocNodes?."$i"?.findAll { !(it in settingNodeIds) }
if (oldNodeIds) {
if (logging) logDebug "Removing Group $i Association: ${convertIntListToHexList(oldNodeIds)} // $oldNodeIds"
cmdsEach << associationRemoveCmd(i, oldNodeIds)
}
List newNodeIds = settingNodeIds.findAll { !(it in state.assocNodes?."$i") }
if (newNodeIds) {
if (logging) logDebug "Adding Group $i Association: ${convertIntListToHexList(newNodeIds)} // $newNodeIds"
cmdsEach << associationSetCmd(i, newNodeIds)
}
if (cmdsEach || state.resyncAll) {
cmdsEach << associationGetCmd(i)
cmds += cmdsEach
}
}
return cmds
}
String getOnOffCmds(val, Integer endPoint=0) {
state.isDigital = true
return switchBinarySetCmd(val ? 0xFF : 0x00, endPoint)
}
/*******************************************************************
***** Other Functions
********************************************************************/
//paramScan Structure: PARAM_NUM:[PARAM_MAPS]
//PARAM_MAPS [num, name, title, description, size, defaultVal, options, firmVer]
@Field static Map paramScan = new java.util.concurrent.ConcurrentHashMap()
//Process the scanned parameters and save to data
void processParamScan() {
List