/*
* Z-Wave Universal Scanner
*
* For Support, Information, and Updates:
* https://community.hubitat.com/t/zwave-universal-scanner/97912
* 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)
- Refactor code to get ready for library merges
- Merge library and universal switch code improvements
- Settings stored data changed to JSON string
- Added friendly names to CC Report
- Added Basic On/Off commands to test most devices
- Parameter query will now sync settings from device automatically
- Added full associations detection and support
- Configure will run any scans that are missing
## [0.3.0] - 2024-06-28 (@jtp10181)
- Pushing some older changes up in prep for more updates
- Added ability to set multichannel lifeline
- Added Set Wake Interval command
- Added Set Parameter command (to manually set parameters)
- Added Command Class Report
- Added command to remove states and data entries from device
- Fixed range expansion issue with Hub Mesh (can cause java.lang.OutOfMemoryError)
## [0.2.0] - 2021-08-06 (@jtp10181)
### Added
- Get Info command to fetch device info and restore to data fields
- Get Info command also prints fingerprint to logs same as the 'Device' driver
- Set Lifeline Association command, useful after firmware updates if it gets cleared
- Made scanning for Name and Info optional (some devices hang on these)
### Fixed
- Was unable to update settings if created as different type, fixed by removing before setting
- Will handle signed or unsigned parameter values based on format specified in report
- Other minor fixes merged from my other drivers
## [0.1.0] - 2021-07-21 (@jtp10181)
- Initial Release
* Copyright 2022-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 = "Scanner"
@Field static final String COMM_LINK = "https://community.hubitat.com/t/z-wave-universal-device-scanner/97912"
metadata {
definition (
name: "Z-Wave Universal Scanner",
namespace: "jtp10181",
author: "Jeff Page (@jtp10181)",
singleThreaded: true,
importUrl: "https://raw.githubusercontent.com/jtp10181/Hubitat/main/Drivers/universal/zwave-universal-scanner.groovy"
) {
capability "Actuator"
capability "Configuration"
command "basicOn"
command "basicOff"
command "getInfo"
command "commandClassReport"
command "setLifelineAssociation", [[name:"Select Option*", type: "ENUM", constraints: ["Single Channel", "Multi-Channel"]] ]
command "setWakeInterval", [[name:"Wake Up Interval", description:"Wake Up Interval (in hours)", type: "NUMBER"]]
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"]]]
command "deleteChild", [[name:"Child DNI*", description:"DNI from Child or ALL to remove all", type: "STRING"]]
command "removeData",[[name:"dataType*", type: "ENUM", description: "Type of Data to Remove", constraints: ["State", "StateVariable", "DeviceData"]],
[name:"dataName*",type:"STRING", description:"Enter exact name of field to delete"]]
//DEBUGGING
// command "debugShowVars"
// command "testCommands"
attribute "syncStatus", "string"
}
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}"
}
void testCommands() {
List cmds = []
//Request NIF
// cmds << zwave.zwaveCmdClassV1.requestNodeInfo()
// cmds << (new hubitat.zwave.commands.zwavecmdclassv1.RequestNodeInfo())
// cmds << "0102"
// sendCommands(cmds)
}
//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..."
}
List configure() {
logWarn "configure..."
state.remove("deviceSync")
state.remove("queryParams")
state.remove("queryAssoc")
state.remove("pendingWakeUpInt")
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.endPoints == null) state.endPoints = 0
cmds << secureCmd(zwave.multiChannelV3.multiChannelEndPointGet())
cmds << "delay ${totalDelay}"
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) : []
}
/*******************************************************************
***** Driver Commands
********************************************************************/
/*** Capabilities ***/
def basicOn() {
logDebug "on..."
return secureCmd(zwave.basicV1.basicSet(value: 0xFF))
}
def basicOff() {
logDebug "off..."
return secureCmd(zwave.basicV1.basicSet(value: 0x00))
}
/*** 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)
}
void getInfo() {
List cmds = []
cmds << versionGetCmd()
cmds << mfgSpecificGetCmd()
cmds << deviceSpecificGetCmd()
sendCommands(cmds)
}
void setLifelineAssociation(chan) {
List cmds = []
logDebug "Setting lifeline association for ${chan}"
//Remove all group 1 associations
cmds << secureCmd(zwave.multiChannelAssociationV3.multiChannelAssociationRemove(groupingIdentifier: 1, nodeId:[], multiChannelNodeIds:[]))
if (chan == "Multi-Channel") {
cmds << secureCmd(zwave.multiChannelAssociationV3.multiChannelAssociationSet(groupingIdentifier: 1, multiChannelNodeIds: [[nodeId: zwaveHubNodeId, bitAddress:0, endPointId: 0]]))
cmds << secureCmd(zwave.multiChannelAssociationV3.multiChannelAssociationGet(groupingIdentifier: group))
}
else {
cmds << associationSetCmd(1, [zwaveHubNodeId])
cmds << associationGetCmd(1)
}
sendCommands(cmds)
}
void commandClassReport() {
List cmds = []
List ic = getDataValue("inClusters").split(",").collect{ hexStrToUnsignedInt(it) }
ic += getDataValue("secureInClusters")?.split(",").collect{ hexStrToUnsignedInt(it) }
ic.each {
if (it) cmds << secureCmd(zwave.versionV1.versionCommandClassGet(requestedCommandClass:it))
}
state.remove("queryParams")
sendCommands(cmds)
}
void deleteChild(String dni) {
logDebug "deleteChild: ${dni}"
if (dni == "ALL") {
childDevices.each { child ->
deleteChildDevice(child.deviceNetworkId)
}
}
else {
deleteChildDevice(dni)
}
}
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)
}
void setWakeInterval(wakeInt) {
wakeInt = safeToInt(wakeInt)
logDebug "setWakeInterval ( $wakeInt )"
state.pendingWakeUpInt = wakeInt
}
void removeData(String dataType, String dataName) {
log.debug "removeData(${dataType}, ${dataName})"
switch (dataType) {
case "State":
device.deleteCurrentState("${dataName}".toString())
break
case "StateVariable":
state.remove("${dataName}".toString())
break
case "DeviceData":
removeDataValue("${dataName}".toString())
break
default:
log.warn "removeSaveData invalid dataType: ${dataType}"
}
}
/*******************************************************************
***** 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) {
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) {
logDebug "Lifeline Association: ${cmd.nodeId} | MC: ${mcNodes}"
// state.group1Assoc = (mcNodes == ["${zwaveHubNodeId}:0"] ? true : false)
}
else {
logDebug "Unhandled Group: $cmd"
}
}
//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} [${ccLookup[ccNum]}]"
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)
}
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
}
}
void zwaveEvent(hubitat.zwave.commands.wakeupv2.WakeUpIntervalReport cmd) {
logTrace "${cmd}"
BigDecimal wakeHrs = safeToDec(cmd.seconds/3600,0,2)
logDebug "WakeUp Interval is $cmd.seconds seconds ($wakeHrs hours)"
device.updateDataValue("zwWakeupInterval", "${cmd.seconds}")
}
void zwaveEvent(hubitat.zwave.commands.wakeupv2.WakeUpNotification cmd, ep=0) {
logTrace "${cmd} (ep ${ep})"
logDebug "WakeUp Notification Received"
List cmds = ["delay 0"]
cmds << batteryGetCmd()
Integer newWakeUpInt = (state.pendingWakeUpInt as Integer)
if (newWakeUpInt != null) {
Integer wakeSeconds = newWakeUpInt ? newWakeUpInt*3600 : 43200
if (state.resyncAll || wakeSeconds != (device.getDataValue("zwWakeupInterval") as Integer)) {
logDebug "Settting WakeUp Interval to $wakeSeconds seconds"
cmds << wakeUpIntervalSetCmd(wakeSeconds)
cmds << wakeUpIntervalGetCmd()
}
state.remove("pendingWakeUpInt")
}
//Any configuration needed
cmds += getConfigureCmds()
//This needs a longer delay
cmds << "delay 1400" << wakeUpNoMoreInfoCmd()
//Clear pending status
state.resyncAll = false
state.remove("INFO")
sendCommands(cmds, 400)
}
/*******************************************************************
***** Event Senders
********************************************************************/
/*******************************************************************
***** Execute / Build Commands
********************************************************************/
List getConfigureCmds() {
logDebug "getConfigureCmds..."
List cmds = []
if (state.resyncAll || !firmwareVersion) {
cmds << mfgSpecificGetCmd()
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 getConfigureAssocsCmds(Boolean logging=false) {
List cmds = []
// if (!state.group1Assoc || state.resyncAll) {
// if (logging) logDebug "Setting lifeline association..."
// 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
}
/*******************************************************************
***** Other Functions
********************************************************************/
/*** Static Lists and Settings ***/
@Field static final Map ccLookup = [
0x20: "Basic",
0x21: "Controller Replication",
0x22: "Application Status",
0x23: "Z/IP",
0x25: "Binary Switch",
0x26: "Multilevel Switch",
0x2B: "Scene Activation",
0x2C: "Scene Actuator Configuration",
0x2D: "Scene Controller Configuration",
0x31: "Multilevel Sensor",
0x32: "Meter",
0x33: "Color Switch",
0x34: "Network Management Inclusion",
0x36: "Basic Tariff Information",
0x37: "HRV Status",
0x39: "HRV Control",
0x3A: "Demand Control Plan Configuration",
0x3B: "Demand Control Plan Monitor ",
0x3C: "Meter Table Configuration",
0x3D: "Meter Table Monitor",
0x3E: "Meter Table Push Configuration",
0x3F: "Prepayment",
0x40: "Thermostat Mode",
0x41: "Prepayment Encapsulation",
0x42: "Thermostat Operating State",
0x43: "Thermostat Setpoint",
0x44: "Thermostat Fan Mode",
0x45: "Thermostat Fan State",
0x47: "Thermostat Setback",
0x48: "Rate Table Configuration",
0x49: "Rate Table Monitor",
0x4A: "Tariff Table Configuration",
0x4B: "Tariff Table Monitor",
0x4C: "Door Lock Logging",
0x4D: "Network Management Basic Node",
0x4F: "Z/IP 6LoWPAN",
0x52: "Network Management Proxy",
0x53: "Schedule",
0x55: "Transport Service",
0x58: "Z/IP ND",
0x59: "Association Group Information (AGI)",
0x5A: "Device Reset Locally",
0x5B: "Central Scene",
0x5C: "IP Association",
0x5D: "Anti-theft",
0x5E: "Z-Wave Plus Info",
0x5F: "Z/IP Gateway",
0x60: "Multi Channel",
0x61: "Z/IP Portal",
0x62: "Door Lock",
0x63: "User Code",
0x64: "Humidity Control Setpoint",
0x66: "Barrier Operator",
0x67: "Network Management Installation and Maintenance",
0x68: "Z/IP Naming and Location",
0x69: "Mailbox",
0x6A: "Window Covering",
0x6B: "Irrigation",
0x6C: "Supervision",
0x6D: "Humidity Control Mode",
0x6E: "Humidity Control Operating State",
0x6F: "Entry Control",
0x70: "Configuration",
0x71: "Notification",
0x72: "Manufacturer Specific",
0x73: "Powerlevel",
0x74: "Inclusion Controller",
0x75: "Protection",
0x77: "Node Naming and Location",
0x78: "Node Provisioning",
0x79: "Sound Switch",
0x7A: "Firmware Update Meta Data",
0x7E: "Anti-theft Unlock",
0x80: "Battery",
0x81: "Clock",
0x84: "Wake Up",
0x85: "Association",
0x86: "Version",
0x87: "Indicator",
0x89: "Language",
0x8A: "Time",
0x8B: "Time Parameters",
0x8C: "Geographic Location",
0x8E: "Multi Channel Association",
0x8F: "Multi Command",
0x90: "Energy Production",
0x91: "Manufacturer proprietary",
0x92: "Screen Meta Data",
0x93: "Screen Attributes",
0x94: "Simple AV Control",
0x98: "Security 0",
0x9B: "Association Command Configuration",
0x9D: "Alarm Silence",
0x9F: "Security 2",
0xA0: "IR Repeater",
0xA1: "Authentication",
0xA2: "Authentication Media Write",
0xA3: "Generic Schedule",
0x29: "Multilevel Toggle Switch (Deprecated)",
0x30: "Binary Sensor (Deprecated)",
0x35: "Pulse Meter (Deprecated)",
0x46: "Climate Control Schedule (Deprecated)",
0x4E: "Schedule Entry Lock (Deprecated)",
0x56: "CRC-16 Encapsulation (Deprecated)",
0x76: "Lock (Deprecated)",
0x7B: "Grouping Name (Deprecated)",
0x88: "Proprietary (Deprecated)",
0x9C: "Alarm Sensor (Deprecated)",
0x27: "All Switch (Obsoleted)",
0x28: "Binary Toggle Switch (Obsoleted)",
0x50: "Basic Window Covering (Obsoleted)",
0x51: "Move To Position Window Covering (Obsoleted)",
0x54: "Network Management Primary (Obsoleted)",
0x57: "Application Capability (Obsoleted)",
0x7C: "Remote Association Activation (Obsoleted)",
0x7D: "Remote Association Configuration (Obsoleted)",
0x82: "Hail (Obsoleted)",
0x9A: "IP Configuration (Obsoleted)",
0x9E: "Sensor Configuration (Obsoleted)"
]
//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