/*
* Zooz ZEN53 DC Motor Controller
* - Model: ZEN53
*
* For Support, Information, and Updates:
* https://community.hubitat.com/t/zooz-zen53/117790
* https://github.com/jtp10181/Hubitat/tree/main/Drivers/zooz
*
Changelog:
## [1.2.0] - 2024-09-14 (@jtp10181)
- Update library and common code
- Added new paramters 19/20 for FW 1.40
- Fix for range expansion and sharing with Hub Mesh
## [1.0.4] - 2024-04-05 (@jtp10181)
- Added singleThreaded flag
- Added option to set position to 0 when closed
- Updated Library and common code
## [1.0.2] - 2023-07-15 (@jtp10181)
- Added option to change open command behavior
- Fixed potential variable type issue with startPositionChange
## [1.0.0] - 2023-05-28 (@jtp10181)
- Updated to current library code
## [0.2.0] - 2023-05-08 (@jtp10181)
- Update to use library code
## [0.1.0] - 2023-04-24 (@jtp10181)
- Initial Release
* Copyright 2023-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.transform.Field
@Field static final String VERSION = "1.2.0"
@Field static final String DRIVER = "Zooz-ZEN53"
@Field static final String COMM_LINK = "https://community.hubitat.com/t/zooz-zen53/117790"
@Field static final Map deviceModelNames = ["0904:0219":"ZEN53"]
metadata {
definition (
name: "Zooz ZEN53 DC Motor Controller",
namespace: "jtp10181",
author: "Jeff Page (@jtp10181)",
singleThreaded: true,
importUrl: "https://raw.githubusercontent.com/jtp10181/Hubitat/main/Drivers/zooz/zooz-zen53-dc-motor.groovy"
) {
capability "Actuator"
capability "WindowShade"
capability "Configuration"
capability "Refresh"
command "startCalibration", [ [name:"Calibration Mode*", type: "ENUM", constraints: CALIBRATION_MODES] ]
command "refreshParams"
command "setParameter",[[name:"parameterNumber*",type:"NUMBER", description:"Parameter Number"],
[name:"value*",type:"NUMBER", description:"Parameter Value"],
[name:"size",type:"NUMBER", description:"Parameter Size"]]
//DEBUGGING
//command "debugShowVars"
attribute "syncStatus", "string"
fingerprint mfr:"027A", prod:"0904", deviceId:"0219", inClusters:"0x00,0x00", controllerType: "ZWV" //Zooz ZEN53 DC Motor Controller
}
preferences {
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 (!isLongRange()) {
input "assocEnabled", "bool", defaultValue: false,
title: fmtTitle("Show Association Settings"),
description: fmtDesc("Turn on and Save to show the Association Settings")
if (assocEnabled) {
for(int i in 2..maxAssocGroups) {
input "assocDNI$i", "string", required: false,
title: fmtTitle("Device Associations - Group $i"),
description: fmtDesc("Supports up to ${maxAssocNodes} Hex Device IDs separated by commas. Check device documentation for more info. Save as blank or 0 to clear.")
}
}
} else {
input "assocEnabled", "hidden", title: fmtTitle("Associations Not Available"),
description: fmtDesc("Associations are not available when device is paired in Long Range mode")
}
input "openPosition", "enum", title: fmtTitle("Open Command Behavior"), defaultValue: 0xFF,
options: [0xFF:"Use Last Position", 99:"Always Open Fully"]
input "closedPosition", "bool", title: fmtTitle("Set Position when Closed"), defaultValue: false,
description: fmtDesc("Disabled the position will stay at the prior value when closed, Enabled the position is set to 0 when closed.")
}
}
void debugShowVars() {
log.warn "settings ${settings.hashCode()} ${settings}"
log.warn "paramsList ${paramsList.hashCode()} ${paramsList}"
log.warn "paramsMap ${paramsMap.hashCode()} ${paramsMap}"
}
//Association Settings
@Field static final int maxAssocGroups = 4
@Field static final int maxAssocNodes = 5
/*** Static Lists and Settings ***/
@Field static final Map CALIBRATION_MODES = ["0":"Exit Calibration", "5:1":"Shutter Mode", "5:2":"Ventian Mode", "17:1":"Repositioning Mode"]
//Main Parameters Listing
@Field static Map paramsMap =
[
powerFailure: [ num: 1,
title: "Behavior After Power Failure",
size: 1, defaultVal: 100,
options: [100:"Restores Last Status", 0:"Forced to Closed", 99:"Forced to Full Open"],
],
ledMode: [ num: 2,
title: "LED Indicator",
size: 1, defaultVal: 1,
options: [1:"LED On When Load On", 0:"LED Always Off"]
],
motorMode: [ num: 6,
title: "Motor Mode",
size: 1, defaultVal: 1,
options: [0:"Shutter without positioning", 1:"Shutter with positioning", 2:"Venetian"]
],
motorReverse: [ num: 9,
title: "Reverse Motor Operation",
size: 1, defaultVal: 0,
options: [0:"Normal", 1:"Reversed", 2:"Venetian"]
],
cycleShutter: [ num: 3,
title: "Motor Cycle Duration for Shutter Mode",
description: "[0 = Use Limit Switches, 100 = 1 second]",
size: 2, defaultVal: 6000,
range: "0..65535"
],
cycleVenetian: [ num: 4,
title: "Motor Cycle Duration for Venetian Mode",
description: "[0 = Use Limit Switches, 100 = 1 second]",
size: 2, defaultVal: 1000,
range: "0..65535"
],
cycleOpen: [ num: 19,
title: "Motor Cycle Duration for Up/Open",
description: "[0 = Disabled, 100 = 1 second] If this setting is used it will override parameters 3/4",
size: 2, defaultVal: 0,
range: "0..65535",
firmVer: 1.40
],
cycleClose: [ num: 20,
title: "Motor Cycle Duration for Down/Closed",
description: "[0 = Disabled, 100 = 1 second] If this setting is used it will override parameters 3/4",
size: 2, defaultVal: 0,
range: "0..65535",
firmVer: 1.40
],
paddleProgramming: [ num: 8,
title: "External Switch Pairing",
description: "Allow activating Z-Wave inclusion from the wall switch",
size: 1, defaultVal: 1,
options: [1:"Enabled", 0:"Disabled"],
],
switchType: [ num: 7,
title: "External Switch Type",
size: 1, defaultVal: 0,
options: [0:"Single 2-Button Switch (SPDT)", 1:"Two 1-Button Switches (SPST)"],
],
switchTypeS1: [ num: 14,
title: "Switch Type (S1)",
size: 1, defaultVal: 0,
options: [0:"Toggle Switch", 1:"Momentary Switch"],
],
switchTypeS2: [ num: 15,
title: "Switch Type (S2)",
size: 1, defaultVal: 0,
options: [0:"Toggle Switch", 1:"Momentary Switch"],
],
offTimer: [ num: 11,
title: "Auto Close Timer",
size: 4, defaultVal: 0,
description: "0 - Disabled",
range: "0..86400",
],
onTimer: [ num: 10,
title: "Auto Open Timer",
size: 4, defaultVal: 0,
description: "0 - Disabled",
range: "0..86400",
],
timerUnits: [ num: 12,
title: "Auto Timer Units",
size: 1, defaultVal: 0,
options: [0:"seconds", 1:"minutes"],
],
loadControl: [ num: 13,
title: "Load Control Mode",
size: 1, defaultVal: 1,
options: [1:"Enable Switch and Z-Wave", 0:"Disable Switch Control", 2:"Disable Switch and Z-Wave Control"],
],
// Hidden Parameters to Set Defaults
assocReports: [ num: 16,
title: "Association Reports",
size: 1, defaultVal: 0,
options: [0:"MultiLevel for Z-Wave, Basic for Physical", 1:"Always MultiLevel Reports"],
hidden: true
],
calibrationMode: [ num: 5,
title: "Auto Calibration Mode",
size: 1, defaultVal: 0,
options: [0:"Exit Calibration", 1:"Shutter Mode", 2:"Ventian Mode"],
hidden: true
],
calibrationManual: [ num: 17,
title: "Manual Calibration",
size: 1, defaultVal: 0,
options: [0:"Exit Calibration", 1:"Start Repositioning"],
hidden: true
],
]
/* ZEN53
CommandClassReport - class:0x22, version:1 (Application Status)
CommandClassReport - class:0x26, version:4 (Multilevel Switch)
CommandClassReport - class:0x55, version:2 (Transport Service)
CommandClassReport - class:0x59, version:3 (Association Group Information (AGI))
CommandClassReport - class:0x5A, version:1 (Device Reset Locally)
CommandClassReport - class:0x5E, version:2 (Z-Wave Plus Info)
CommandClassReport - class:0x6A, version:1 (Window Covering)
CommandClassReport - class:0x6C, version:1 (Supervision)
CommandClassReport - class:0x70, version:4 (Configuration)
CommandClassReport - class:0x72, version:2 (Manufacturer Specific)
CommandClassReport - class:0x73, version:1 (Powerlevel)
CommandClassReport - class:0x7A, version:5 (Firmware Update Meta Data)
CommandClassReport - class:0x85, version:2 (Association)
CommandClassReport - class:0x86, version:3 (Version)
CommandClassReport - class:0x87, version:3 (Indicator)
CommandClassReport - class:0x8E, version:3 (Multi Channel Association)
CommandClassReport - class:0x9F, version:1 (Security 2)
*/
//Set Command Class Versions
@Field static final Map commandClassVersions = [
0x26: 4, // switchmultilevelv4
0x6A: 1, // (Window Covering)
0x6C: 1, // supervisionv1
0x70: 1, // configurationv1
0x72: 2, // manufacturerspecificv2
0x85: 2, // associationv2
0x86: 2, // versionv2
0x8E: 3, // multichannelassociationv3
]
/*******************************************************************
***** Core Functions
********************************************************************/
void installed() {
logWarn "installed..."
initialize()
}
void initialize() {
logWarn "initialize..."
refresh()
}
void configure() {
logWarn "configure..."
if (!pendingChanges || state.resyncAll == null) {
logDebug "Enabling Full Re-Sync"
clearVariables()
state.resyncAll = true
}
updateSyncingStatus(6)
runIn(1, executeRefreshCmds)
runIn(4, executeConfigureCmds)
}
void updated() {
logDebug "updated..."
runIn(1, executeConfigureCmds)
}
void refresh() {
logDebug "refresh..."
executeRefreshCmds()
}
/*******************************************************************
***** Driver Commands
********************************************************************/
/*** Capabilities ***/
def open() {
logDebug "open..."
return getOnOffCmds(openPosition ?: 0xFF)
}
def close() {
logDebug "close..."
return getOnOffCmds(0x00)
}
def setPosition(level) {
logDebug "setLevel($level)..."
return getSetLevelCmds(level)
}
def startPositionChange(direction) {
Boolean upDown = (direction == "close") ? true : false
Integer durationVal = validateRange(duration, getParamValue("holdRampRate") as Integer, 0, 127)
logDebug "startPositionChange($direction)"
List cmds = [
switchMultilevelStartLvChCmd(upDown, 255),
switchMultilevelGetCmd()
]
return delayBetween(cmds, 200)
}
def stopPositionChange() {
logDebug "stopPositionChange()"
return switchMultilevelStopLvChCmd()
}
/*** Custom Commands ***/
void startCalibration(String efName) {
String keyVal = CALIBRATION_MODES.find{ efName.equalsIgnoreCase(it.value) }.key
List param = keyVal.split(":")
logDebug "Start Calibration [${keyVal} : ${efName}] | ${param}"
List cmds = []
if (param[0] == "0") {
cmds += configSetGetCmd([num:5, size:1], 0)
cmds += configSetGetCmd([num:17, size:1], 0)
cmds += configGetCmd(getParam("cycleShutter"))
cmds += configGetCmd(getParam("cycleVenetian"))
}
else {
cmds += configSetGetCmd([num:(param[0] as Integer), size:1], (param[1] as Integer))
}
sendCommands(cmds, 500)
}
void refreshParams() {
List cmds = []
for (int i = 1; i <= maxAssocGroups; i++) {
cmds << associationGetCmd(i)
}
configParams.each { param ->
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: 6A([0-9A-F]{2})/) {
logDebug "Unsupported (Windowshade): $description"
} else {
zwaveParse(description)
}
}
void zwaveEvent(hubitat.zwave.commands.supervisionv1.SupervisionGet cmd, ep=0) {
if (cmd.commandClassIdentifier == 0x6A) { //Unsupported Windowshade
logDebug "Unsupported (Windowshade): $cmd (ep ${ep})"
} else {
zwaveSupervision(cmd,ep)
}
}
void zwaveEvent(hubitat.zwave.commands.configurationv1.ConfigurationReport cmd) {
logTrace "${cmd}"
updateSyncingStatus()
Map param = getParam(cmd.parameterNumber)
Integer val = cmd.scaledConfigurationValue
if (param) {
//Convert scaled signed integer to unsigned
Long sizeFactor = Math.pow(256,param.size).round()
if (val < 0) { val += sizeFactor }
logDebug "${param.name} - ${param.title} (#${param.num}) = ${val.toString()}"
setParamStoredValue(param.num, val)
}
else {
logDebug "Parameter #${cmd.parameterNumber} = ${val.toString()}"
}
if (param.name == "cycleShutter" || param.name == "cycleVenetian") {
logDebug "${param.name} - ${param.title} (#${param.num}) Updating Settings"
device.updateSetting(param.name, [type: "number", value: param.num])
}
}
void zwaveEvent(hubitat.zwave.commands.associationv2.AssociationReport cmd) {
logTrace "${cmd}"
updateSyncingStatus()
Integer grp = cmd.groupingIdentifier
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"
}
}
void zwaveEvent(hubitat.zwave.commands.basicv1.BasicReport cmd, ep=0) {
logTrace "${cmd} (ep ${ep})"
sendSwitchEvents([value:cmd.value, duration:0], "physical", ep)
}
void zwaveEvent(hubitat.zwave.commands.switchmultilevelv4.SwitchMultilevelReport cmd, ep=0) {
logTrace "${cmd} (ep ${ep})"
sendSwitchEvents([value:cmd.value, targetValue:cmd.targetValue, duration:cmd.duration], "digital", ep)
}
/*******************************************************************
***** 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 ?: ''}"
//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(Map cmd, String type, Integer ep=0) {
Integer pos = cmd.value
String wShade = "unknown"
if (cmd.duration == null || cmd.duration == 0) {
//Done Moving
switch (cmd.value as Integer) {
case 0:
wShade = "closed"
break
case 1..98:
wShade = "partially open"
break
case 99:
wShade = "open"
break
}
}
else {
pos = null //Ignore position
if (cmd.targetValue > cmd.value) {
wShade = "opening"
} else {
wShade = "closing"
}
}
String desc = "windowShade is ${wShade}" + (type ? " (${type})" : "")
sendEventLog(name:"windowShade", value:wShade, type:type, desc:desc, ep)
if (pos || (pos == 0 && closedPosition)) {
Integer level = (pos == 99 ? 100 : pos)
desc = "position is set to ${level}%"
if (type) desc += " (${type})"
sendEventLog(name:"position", value:level, type:type, unit:"%", desc:desc, ep)
}
}
/*******************************************************************
***** Execute / Build Commands
********************************************************************/
void executeConfigureCmds() {
logDebug "executeConfigureCmds..."
//Checks and sets scheduled turn off
checkLogLevel()
List cmds = []
if (!firmwareVersion || !state.deviceModel) {
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.name} - ${param.title} (#${param.num}) from ${storedVal} to ${paramVal}"
cmds += configSetGetCmd(param, paramVal)
}
}
state.resyncAll = false
if (cmds) sendCommands(cmds)
}
void executeRefreshCmds() {
List cmds = []
if (state.resyncAll || !firmwareVersion || !state.deviceModel) {
cmds << mfgSpecificGetCmd()
cmds << versionGetCmd()
}
cmds << switchMultilevelGetCmd()
sendCommands(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
}
List getOnOffCmds(val, Integer endPoint=0) {
return getSetLevelCmds(val, null, endPoint)
}
List getSetLevelCmds(level, duration=null, Integer endPoint=0) {
Short levelVal = safeToInt(level, 99)
// level 0xFF tells device to use last level, 0x00 is off
if (levelVal != 0xFF && levelVal != 0x00) {
levelVal = validateRange(levelVal, 99, 1, 99)
}
// Duration Encoding:
// 0x01..0x7F 1 second (0x01) to 127 seconds (0x7F) in 1 second resolution.
// 0x80..0xFE 1 minute (0x80) to 127 minutes (0xFE) in 1 minute resolution.
// 0xFF Factory default duration.
//Convert seconds to minutes above 120s
if (duration > 120) {
logDebug "getSetLevelCmds converting ${duration}s to ${Math.round(duration/60)}min"
duration = (duration / 60) + 127
}
Short durationVal = validateRange(duration, -1, -1, 254)
if (duration == null || durationVal == -1) {
durationVal = 0xFF
}
logDebug "getSetLevelCmds output [level:${levelVal}, duration:${durationVal}, endPoint:${endPoint}]"
return delayBetween( [
switchMultilevelSetCmd(levelVal, durationVal, endPoint),
switchMultilevelGetCmd()], 200)
}
/*******************************************************************
***** Required for Library
********************************************************************/
//These have to be added in after the fact or groovy complains
void fixParamsMap() {
paramsMap['settings'] = [fixed: true]
}
Integer getParamValueAdj(Map param) {
return getParamValue(param)
}
//#include jtp10181.zwaveDriverLibrary
/*******************************************************************
*******************************************************************
***** Z-Wave Driver Library by Jeff Page (@jtp10181)
*******************************************************************
********************************************************************
Changelog:
2023-05-10 - First version used in drivers
2023-05-12 - Adjustments to community links
2023-05-14 - Updates for power metering
2023-05-18 - Adding requirement for getParamValueAdj in driver
2023-05-24 - Fix for possible RuntimeException error due to bad cron string
2023-10-25 - Less saving to the configVals data, and some new functions
2023-10-26 - Added some battery shortcut functions
2023-11-08 - Added ability to adjust settings on firmware range
2024-01-28 - Adjusted logging settings for new / upgrade installs; added mfgSpecificReport
2024-06-15 - Added isLongRange function; convert range to string to prevent expansion
2024-07-16 - Support for multi-target version reports; adjust checkIn logic
********************************************************************/
library (
author: "Jeff Page (@jtp10181)",
category: "zwave",
description: "Z-Wave Driver Library",
name: "zwaveDriverLibrary",
namespace: "jtp10181",
documentationLink: ""
)
/*******************************************************************
***** Z-Wave Reports (COMMON)
********************************************************************/
//Include these in Driver
//void parse(String description) {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 zwaveParse(String description) {
hubitat.zwave.Command cmd = zwave.parse(description, commandClassVersions)
if (cmd) {
logTrace "parse: ${description} --PARSED-- ${cmd}"
zwaveEvent(cmd)
} else {
logWarn "Unable to parse: ${description}"
}
//Update Last Activity
updateLastCheckIn()
}
//Decodes Multichannel Encapsulated Commands
void zwaveMultiChannel(hubitat.zwave.commands.multichannelv3.MultiChannelCmdEncap cmd) {
hubitat.zwave.Command encapsulatedCmd = cmd.encapsulatedCommand(commandClassVersions)
logTrace "${cmd} --ENCAP-- ${encapsulatedCmd}"
if (encapsulatedCmd) {
zwaveEvent(encapsulatedCmd, cmd.sourceEndPoint as Integer)
} else {
logWarn "Unable to extract encapsulated cmd from $cmd"
}
}
//Decodes Supervision Encapsulated Commands (and replies to device)
void zwaveSupervision(hubitat.zwave.commands.supervisionv1.SupervisionGet cmd, ep=0) {
hubitat.zwave.Command encapsulatedCmd = cmd.encapsulatedCommand(commandClassVersions)
logTrace "${cmd} --ENCAP-- ${encapsulatedCmd}"
if (encapsulatedCmd) {
zwaveEvent(encapsulatedCmd, ep)
} else {
logWarn "Unable to extract encapsulated cmd from $cmd"
}
sendCommands(secureCmd(zwave.supervisionV1.supervisionReport(sessionID: cmd.sessionID, reserved: 0, moreStatusUpdates: false, status: 0xFF, duration: 0), ep))
}
void zwaveEvent(hubitat.zwave.commands.versionv2.VersionReport cmd) {
logTrace "${cmd}"
String fullVersion = String.format("%d.%02d",cmd.firmware0Version,cmd.firmware0SubVersion)
String zwaveVersion = String.format("%d.%02d",cmd.zWaveProtocolVersion,cmd.zWaveProtocolSubVersion)
device.updateDataValue("firmwareVersion", fullVersion)
device.updateDataValue("protocolVersion", zwaveVersion)
device.updateDataValue("hardwareVersion", "${cmd.hardwareVersion}")
if (cmd.targetVersions) {
Map tVersions = [:]
cmd.targetVersions.each {
tVersions[it.target] = String.format("%d.%02d",it.version,it.subVersion)
device.updateDataValue("firmware${it.target}Version", tVersions[it.target])
}
logDebug "Received Version Report - Main Firmware: ${fullVersion} | Targets: ${tVersions}"
}
else {
logDebug "Received Version Report - Firmware: ${fullVersion}"
}
setDevModel(new BigDecimal(fullVersion))
}
void zwaveEvent(hubitat.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
logTrace "${cmd}"
device.updateDataValue("manufacturer",cmd.manufacturerId.toString())
device.updateDataValue("deviceType",cmd.productTypeId.toString())
device.updateDataValue("deviceId",cmd.productId.toString())
logDebug "fingerprint mfr:\"${hubitat.helper.HexUtils.integerToHexString(cmd.manufacturerId, 2)}\", "+
"prod:\"${hubitat.helper.HexUtils.integerToHexString(cmd.productTypeId, 2)}\", "+
"deviceId:\"${hubitat.helper.HexUtils.integerToHexString(cmd.productId, 2)}\", "+
"inClusters:\"${device.getDataValue("inClusters")}\""+
(device.getDataValue("secureInClusters") ? ", secureInClusters:\"${device.getDataValue("secureInClusters")}\"" : "")
}
void zwaveEvent(hubitat.zwave.Command cmd, ep=0) {
logDebug "Unhandled zwaveEvent: $cmd (ep ${ep}) [${getObjectClassName(cmd)}]"
}
/*******************************************************************
***** Z-Wave Command Shortcuts
********************************************************************/
//These send commands to the device either a list or a single command
void sendCommands(List cmds, Long delay=200) {
sendHubCommand(new hubitat.device.HubMultiAction(delayBetween(cmds, delay), hubitat.device.Protocol.ZWAVE))
}
//Single Command
void sendCommands(String cmd) {
sendHubCommand(new hubitat.device.HubAction(cmd, hubitat.device.Protocol.ZWAVE))
}
//Consolidated zwave command functions so other code is easier to read
String associationSetCmd(Integer group, List nodes) {
return secureCmd(zwave.associationV2.associationSet(groupingIdentifier: group, nodeId: nodes))
}
String associationRemoveCmd(Integer group, List nodes) {
return secureCmd(zwave.associationV2.associationRemove(groupingIdentifier: group, nodeId: nodes))
}
String associationGetCmd(Integer group) {
return secureCmd(zwave.associationV2.associationGet(groupingIdentifier: group))
}
String mcAssociationGetCmd(Integer group) {
return secureCmd(zwave.multiChannelAssociationV3.multiChannelAssociationGet(groupingIdentifier: group))
}
String versionGetCmd() {
return secureCmd(zwave.versionV2.versionGet())
}
String mfgSpecificGetCmd() {
return secureCmd(zwave.manufacturerSpecificV2.manufacturerSpecificGet())
}
String switchBinarySetCmd(Integer value, Integer ep=0) {
return secureCmd(zwave.switchBinaryV1.switchBinarySet(switchValue: value), ep)
}
String switchBinaryGetCmd(Integer ep=0) {
return secureCmd(zwave.switchBinaryV1.switchBinaryGet(), ep)
}
String switchMultilevelSetCmd(Integer value, Integer duration, Integer ep=0) {
return secureCmd(zwave.switchMultilevelV4.switchMultilevelSet(dimmingDuration: duration, value: value), ep)
}
String switchMultilevelGetCmd(Integer ep=0) {
return secureCmd(zwave.switchMultilevelV4.switchMultilevelGet(), ep)
}
String switchMultilevelStartLvChCmd(Boolean upDown, Integer duration, Integer ep=0) {
//upDown: false=up, true=down
return secureCmd(zwave.switchMultilevelV4.switchMultilevelStartLevelChange(upDown: upDown, ignoreStartLevel:1, dimmingDuration: duration), ep)
}
String switchMultilevelStopLvChCmd(Integer ep=0) {
return secureCmd(zwave.switchMultilevelV4.switchMultilevelStopLevelChange(), ep)
}
String meterGetCmd(meter, Integer ep=0) {
return secureCmd(zwave.meterV3.meterGet(scale: meter.scale), ep)
}
String meterResetCmd(Integer ep=0) {
return secureCmd(zwave.meterV3.meterReset(), ep)
}
String wakeUpIntervalGetCmd() {
return secureCmd(zwave.wakeUpV2.wakeUpIntervalGet())
}
String wakeUpIntervalSetCmd(val) {
return secureCmd(zwave.wakeUpV2.wakeUpIntervalSet(seconds:val, nodeid:zwaveHubNodeId))
}
String wakeUpNoMoreInfoCmd() {
return secureCmd(zwave.wakeUpV2.wakeUpNoMoreInformation())
}
String batteryGetCmd() {
return secureCmd(zwave.batteryV1.batteryGet())
}
String sensorMultilevelGetCmd(sensorType) {
Integer scale = (temperatureScale == "F" ? 1 : 0)
return secureCmd(zwave.sensorMultilevelV11.sensorMultilevelGet(scale: scale, sensorType: sensorType))
}
String notificationGetCmd(notificationType, eventType, Integer ep=0) {
return secureCmd(zwave.notificationV3.notificationGet(notificationType: notificationType, v1AlarmType:0, event: eventType), ep)
}
String configSetCmd(Map param, Integer value) {
//Convert from unsigned to signed for scaledConfigurationValue
Long sizeFactor = Math.pow(256,param.size).round()
if (value >= sizeFactor/2) { value -= sizeFactor }
return secureCmd(zwave.configurationV1.configurationSet(parameterNumber: param.num, size: param.size, scaledConfigurationValue: value))
}
String configGetCmd(Map param) {
return secureCmd(zwave.configurationV1.configurationGet(parameterNumber: param.num))
}
List configSetGetCmd(Map param, Integer value) {
List cmds = []
cmds << configSetCmd(param, value)
cmds << configGetCmd(param)
return cmds
}
/*******************************************************************
***** Z-Wave Encapsulation
********************************************************************/
//Secure and MultiChannel Encapsulate
String secureCmd(String cmd) {
return zwaveSecureEncap(cmd)
}
String secureCmd(hubitat.zwave.Command cmd, ep=0) {
return zwaveSecureEncap(multiChannelCmd(cmd, ep))
}
//MultiChannel Encapsulate if needed
//This is called from secureCmd or superviseCmd, do not call directly
String multiChannelCmd(hubitat.zwave.Command cmd, ep) {
//logTrace "multiChannelCmd: ${cmd} (ep ${ep})"
if (ep > 0) {
cmd = zwave.multiChannelV3.multiChannelCmdEncap(destinationEndPoint:ep).encapsulate(cmd)
}
return cmd.format()
}
/*******************************************************************
***** Common Functions
********************************************************************/
/*** Parameter Store Map Functions ***/
@Field static Map configsList = new java.util.concurrent.ConcurrentHashMap()
Integer getParamStoredValue(Integer paramNum) {
//Using Data (Map) instead of State Variables
Map configsMap = getParamStoredMap()
return safeToInt(configsMap[paramNum], null)
}
void setParamStoredValue(Integer paramNum, Number value) {
//Using Data (Map) instead of State Variables
TreeMap configsMap = getParamStoredMap()
configsMap[paramNum] = value
configsList[device.id][paramNum] = value
//device.updateDataValue("configVals", configsMap.inspect())
}
Map getParamStoredMap() {
TreeMap configsMap = configsList[device.id]
if (configsMap == null) {
configsMap = [:]
if (device.getDataValue("configVals")) {
try {
configsMap = evaluate(device.getDataValue("configVals"))
}
catch(Exception e) {
logWarn("Clearing Invalid configVals: ${e}")
device.removeDataValue("configVals")
}
}
configsList[device.id] = configsMap
}
return configsMap
}
/*** Parameter List Functions ***/
//This will rebuild the list for the current model and firmware only as needed
//paramsList Structure: MODEL:[FIRMWARE:PARAM_MAPS]
//PARAM_MAPS [num, name, title, description, size, defaultVal, options, firmVer]
@Field static Map> paramsList = new java.util.concurrent.ConcurrentHashMap()
void updateParamsList() {
logDebug "Update Params List"
String devModel = state.deviceModel
Short modelNum = deviceModelShort
Short modelSeries = Math.floor(modelNum/10)
BigDecimal firmware = firmwareVersion
List