Hi All,
I'm very new to Hubitat, and hoping to migrate all my devices from ST. I have some Greenwave smart plugs (10 of) which I'm trying to get working. I read up on the changes between the Groovy and altered the code accordingly, (generously created by codersaur for my Firmware on the plugs, which worked great on ST) I've managed to get the device code saved. The attributes are all there, yet executing any command fails with the error below.
The full code:
metadata {
definition (name: "GreenWave PowerNode (Single) Advanced", namespace: "codersaur", author: "David Lomas") {
capability "Actuator"
capability "Switch"
capability "Sensor"
capability "Energy Meter"
capability "Power Meter"
capability "Polling"
capability "Refresh"
// Standard Attributes:
attribute "switch", "enum", ["on", "off"]
attribute "power", "number"
attribute "energy", "number"
// Custom Attributes:
attribute "energyLastReset", "string" // Last time Accumulated Engergy was reset.
attribute "fault", "string" // Indicates if the device has any faults. 'clear' if no active faults.
attribute "localProtectionMode", "enum", ["unprotected","sequence","noControl"] // Physical protection mode.
attribute "rfProtectionMode", "enum", ["unprotected","noControl","noResponse"] // Wireless protection mode.
attribute "logMessage", "string" // Important log messages.
attribute "syncPending", "number" // Number of config items that need to be synced with the physical device.
attribute "wheelStatus", "enum", ["black","green","blue","red","yellow","violet","orange","aqua","pink","white"]
// Display Attributes:
// These are only required because the UI lacks number formatting and strips leading zeros.
attribute "dispEnergy", "string"
attribute "dispPower", "string"
// Custom Commands:
command "blink" // Causes the Circle LED to blink for ~20 seconds.
command "reset" // Alias for resetEnergy().
command "resetEnergy" // Reset accumulated energy figure to 0.
command "resetFault" // Reset fault alarm to 'clear'.
command "setLocalProtectionMode" // Set physical protection mode.
command "toggleLocalProtectionMode" // Toggle physical protection mode.
command "setRfProtectionMode" // Set wireless protection mode.
command "toggleRfProtectionMode" // Toggle wireless protection mode.
command "sync" // Sync configuration with physical device.
command "test" // Test function.
// Fingerprints:
fingerprint mfr: "0153", prod: "0002", model: "0002"
fingerprint type: "1001", mfr: "0153", cc: "25,27,32,56,70,71,72,75,85,86,87"
fingerprint inClusters: "0x20,0x25,0x27,0x32,0x56,0x70,0x71,0x72,0x75,0x85,0x86,0x87"
}
main(["switch"])
details([
"switch",
"instMode","power",
"wheelStatus",
"energyLastReset","energy",
"blink",
"localProtectionMode",
"rfProtectionMode",
"syncPending",
// "refresh",
"fault"//,
//"test"
])
}
preferences {
section { // GENERAL:
input (
type: "paragraph",
element: "paragraph",
title: "GENERAL:",
description: "General device handler settings."
)
input (
name: "configLoggingLevelIDE",
title: "IDE Live Logging Level:\nMessages with this level and higher will be logged to the IDE.",
type: "enum",
options: [
"0" : "None",
"1" : "Error",
"2" : "Warning",
"3" : "Info",
"4" : "Debug",
"5" : "Trace"
],
defaultValue: "3",
required: false
)
input (
name: "configLoggingLevelDevice",
title: "Device Logging Level:\nMessages with this level and higher will be logged to the logMessage attribute.",
type: "enum",
options: [
"0" : "None",
"1" : "Error",
"2" : "Warning"
],
defaultValue: "2",
required: false
)
input (
name: "configSyncAll",
title: "Force Full Sync:\nAll device settings will be re-sent to the device.",
type: "boolean",
defaultValue: false,
required: false
)
input (
name: "configAutoOffTime",
title: "Timer Function (Auto-off):\nAutomatically switch off the device after a specified time.\n" +
"Values:\n0 = Function Disabled\n1-86400 = time in seconds\nDefault Value: 0",
type: "number",
range: "0..86400",
defaultValue: 0,
required: false
)
input (
name: "configIgnoreCurrentLeakageAlarms",
title: "Ignore Current Leakage Alarms:\nDo not raise a fault on a current leakage alarm.",
type: "boolean",
defaultValue: false,
required: false
)
input (
name: "configSwitchAllMode",
title: "ALL ON/ALL OFF Function:\nResponse to SWITCH_ALL_SET commands.",
type: "enum",
options: [
"0" : "0: All ON not active, All OFF not active",
"1" : "1: All ON not active, All OFF active",
"2" : "2: All ON active, All OFF not active",
"255" : "255: All ON active, All OFF active"],
defaultValue: "255",
required: false
)
}
generatePrefsParams()
//generatePrefsAssocGroups() // All Assoc Groups are HubOnly for this device.
}
/*****************************************************************************************************************
* SmartThings System Commands:
*****************************************************************************************************************/
def installed() {
log.trace "installed()"
state.installedAt = now()
state.energyLastReset = new Date().format("YYYY/MM/dd \n HH:mm:ss", location.timeZone)
state.loggingLevelIDE = 3
state.loggingLevelDevice = 2
state.useSecurity = false
state.useCrc16 = true
state.fwVersion = 4.23 // Will be updated when versionReport is received.
state.protectLocalTarget = 0
state.protectRfTarget = 0
state.autoOffTime = 0
sendEvent(name: "fault", value: "clear", displayed: false)
def cmds = []
cmds << zwave.configurationV1.configurationSet(parameterNumber: 1, size: 1, scaledConfigurationValue: 255) // Set Keep-Alive to 255.
cmds << zwave.configurationV1.configurationGet(parameterNumber: 2) // Wheel Status
cmds << zwave.protectionV2.protectionGet()
cmds << zwave.manufacturerSpecificV2.manufacturerSpecificGet()
cmds << zwave.versionV1.versionGet()
sendCommands(cmds)
}
def updated() {
logger("updated()","trace")
def cmds = []
if (!state.updatedLastRanAt || now() >= state.updatedLastRanAt + 2000) {
state.updatedLastRanAt = now()
// Update internal state:
state.loggingLevelIDE = (settings.configLoggingLevelIDE) ? settings.configLoggingLevelIDE.toInteger() : 3
state.loggingLevelDevice = (settings.configLoggingLevelDevice) ? settings.configLoggingLevelDevice.toInteger(): 2
state.syncAll = ("true" == settings.configSyncAll)
state.autoOffTime = (settings.configAutoOffTime) ? settings.configAutoOffTime.toInteger() : 0
state.ignoreCurrentLeakageAlarms = ("true" == settings.configIgnoreCurrentLeakageAlarms)
state.switchAllModeTarget = (settings.configSwitchAllMode) ? settings.configSwitchAllMode.toInteger() : 255
// Update Parameter target values:
getParamsMd().findAll( { !it.readonly & (it.fwVersion <= state.fwVersion) } ).each { // Exclude readonly/newer parameters.
state."paramTarget${it.id}" = settings."configParam${it.id}"?.toInteger()
}
// Update Assoc Group target values:
getAssocGroupsMd().findAll( { !it.hubOnly } ).each {
state."assocGroupTarget${it.id}" = parseAssocGroupInput(settings."configAssocGroup${it.id}", it.maxNodes)
}
getAssocGroupsMd().findAll( { it.hubOnly } ).each {
state."assocGroupTarget${it.id}" = [ zwaveHubNodeId ]
}
// Sync configuration with phyiscal device:
sync(state.syncAll)
// Request device medadata (this just seems the best place to do it):
cmds << zwave.manufacturerSpecificV2.manufacturerSpecificGet()
cmds << zwave.versionV1.versionGet()
return sendCommands(cmds)
}
else {
logger("updated(): Ran within last 2 seconds so aborting.","debug")
}
}
def parse(description) {
logger("parse(): Parsing raw message: ${description}","trace")
def result = []
def cmd = zwave.parse(description, getCommandClassVersions())
if (cmd) {
result += zwaveEvent(cmd)
} else {
logger("parse(): Could not parse raw message: ${description}","error")
}
return result
}
/*****************************************************************************************************************
* Z-wave Event Handlers.
*****************************************************************************************************************/
def zwaveEvent(hubitat.zwave.commands.basicv1.BasicReport cmd) {
logger("zwaveEvent(): Basic Report received: ${cmd}","trace")
def result = []
def switchValue = (cmd.value ? "on" : "off")
def switchEvent = createEvent(name: "switch", value: switchValue)
if (switchEvent.isStateChange) logger("Switch turned ${switchValue}.","info")
result << switchEvent
if ( switchEvent.isStateChange & (switchValue == "on") & (state.autoOffTime > 0) ) {
logger("Scheduling Auto-off in ${state.autoOffTime} seconds.","info")
runIn(state.autoOffTime,autoOff)
}
return result
}
def zwaveEvent(hubitat.zwave.commands.applicationstatusv1.ApplicationBusy cmd) {
logger("zwaveEvent(): Application Busy received: ${cmd}","trace")
switch(cmd.status) {
case 0:
logger("Device is busy. Try again later.","warn")
break
case 1:
logger("Device is busy. Retry in ${cmd.waitTime} seconds.","warn")
break
case 2:
logger("Device is busy. Request is queued.","warn")
break
}
}
def zwaveEvent(hubitat.zwave.commands.applicationstatusv1.ApplicationRejectedRequest cmd) {
//logger("zwaveEvent(): Application Rejected Request received: ${cmd}","trace")
logger("A command was rejected. Most likely, RF Protection Mode is set to 'No Control'.","warn")
}
def zwaveEvent(hubitat.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd) {
logger("zwaveEvent(): Switch Binary Report received: ${cmd}","trace")
def result = []
def switchValue = (cmd.value ? "on" : "off")
def switchEvent = createEvent(name: "switch", value: switchValue)
if (switchEvent.isStateChange) logger("Switch turned ${switchValue}.","info")
result << switchEvent
if ( switchEvent.isStateChange & (switchValue == "on") & (state.autoOffTime > 0) ) {
logger("Scheduling Auto-off in ${state.autoOffTime} seconds.","info")
runIn(state.autoOffTime,autoOff)
}
return result
}
def zwaveEvent(hubitat.zwave.commands.switchallv1.SwitchAllReport cmd) {
logger("zwaveEvent(): Switch All Report received: ${cmd}","trace")
state.switchAllModeCache = cmd.mode
def msg = ""
switch (cmd.mode) {
case 0:
msg = "Device is excluded from the all on/all off functionality."
break
case 1:
msg = "Device is excluded from the all on functionality but not all off."
break
case 2:
msg = "Device is excluded from the all off functionality but not all on."
break
default:
msg = "Device is included in the all on/all off functionality."
break
}
logger("Switch All Mode: ${msg}","info")
updateSyncPending()
}
def zwaveEvent(hubitat.zwave.commands.meterv3.MeterReport cmd) {
logger("zwaveEvent(): Meter Report received: ${cmd}","trace")
def result = []
switch (cmd.meterType) {
case 1: // Electric meter:
switch (cmd.scale) {
case 0: // Accumulated Energy (kWh):
result << createEvent(name: "energy", value: cmd.scaledMeterValue, unit: "kWh", displayed: true)
result << createEvent(name: "dispEnergy", value: String.format("%.2f",cmd.scaledMeterValue as BigDecimal) + " kWh", displayed: false)
logger("New meter reading: Accumulated Energy: ${cmd.scaledMeterValue} kWh","info")
break
case 1: // Accumulated Energy (kVAh):
result << createEvent(name: "energy", value: cmd.scaledMeterValue, unit: "kVAh", displayed: true)
result << createEvent(name: "dispEnergy", value: String.format("%.2f",cmd.scaledMeterValue as BigDecimal) + " kVAh", displayed: false)
logger("New meter reading: Accumulated Energy: ${cmd.scaledMeterValue} kVAh","info")
break
case 2: // Instantaneous Power (Watts):
result << createEvent(name: "power", value: cmd.scaledMeterValue, unit: "W", displayed: true)
result << createEvent(name: "dispPower", value: String.format("%.1f",cmd.scaledMeterValue as BigDecimal) + " W", displayed: false)
logger("New meter reading: Instantaneous Power: ${cmd.scaledMeterValue} W","info")
// Request Switch Binary Report if power suggests switch state has changed:
def sw = (cmd.scaledMeterValue) ? "on" : "off"
if ( device.latestValue("switch") != sw) { result << prepCommands([zwave.switchBinaryV1.switchBinaryGet()]) }
break
case 3: // Accumulated Pulse Count:
result << createEvent(name: "pulseCount", value: cmd.scaledMeterValue, unit: "", displayed: true)
logger("New meter reading: Accumulated Electricity Pulse Count: ${cmd.scaledMeterValue}","info")
break
case 4: // Instantaneous Voltage (Volts):
result << createEvent(name: "voltage", value: cmd.scaledMeterValue, unit: "V", displayed: true)
result << createEvent(name: "dispVoltage", value: String.format("%.1f",cmd.scaledMeterValue as BigDecimal) + " V", displayed: false)
logger("New meter reading: Instantaneous Voltage: ${cmd.scaledMeterValue} V","info")
break
case 5: // Instantaneous Current (Amps):
result << createEvent(name: "current", value: cmd.scaledMeterValue, unit: "A", displayed: true)
result << createEvent(name: "dispCurrent", value: String.format("%.1f",cmd.scaledMeterValue as BigDecimal) + " V", displayed: false)
logger("New meter reading: Instantaneous Current: ${cmd.scaledMeterValue} A","info")
break
case 6: // Instantaneous Power Factor:
result << createEvent(name: "powerFactor", value: cmd.scaledMeterValue, unit: "", displayed: true)
result << createEvent(name: "dispPowerFactor", value: String.format("%.1f",cmd.scaledMeterValue as BigDecimal), displayed: false)
logger("New meter reading: Instantaneous Power Factor: ${cmd.scaledMeterValue}","info")
break
default:
logger("zwaveEvent(): Meter Report with unhandled scale: ${cmd}","warn")
break
}
break
default:
logger("zwaveEvent(): Meter Report with unhandled meterType: ${cmd}","warn")
break
}
return result
}
def zwaveEvent(hubitat.zwave.commands.crc16encapv1.Crc16Encap cmd) {
logger("zwaveEvent(): CRC-16 Encapsulation Command received: ${cmd}","trace")
def versions = getCommandClassVersions()
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)
// TO DO: It should be possible to replace the lines above with this line soon...
//def encapsulatedCommand = cmd.encapsulatedCommand(getCommandClassVersions())
if (!encapsulatedCommand) {
logger("zwaveEvent(): Could not extract command from ${cmd}","error")
} else {
return zwaveEvent(encapsulatedCommand)
}
}
def zwaveEvent(hubitat.zwave.commands.configurationv1.ConfigurationReport cmd) {
logger("zwaveEvent(): Configuration Report received: ${cmd}","trace")
def result = []
def paramMd = getParamsMd().find( { it.id == cmd.parameterNumber })
// Some values are treated as unsigned and some as signed, so we convert accordingly:
def paramValue = (paramMd?.isSigned) ? cmd.scaledConfigurationValue : byteArrayToUInt(cmd.configurationValue)
def signInfo = (paramMd?.isSigned) ? "SIGNED" : "UNSIGNED"
state."paramCache${cmd.parameterNumber}" = paramValue
logger("Parameter #${cmd.parameterNumber} [${paramMd?.name}] has value: ${paramValue} [${signInfo}]","info")
updateSyncPending()
// Update wheelStatus if parameter #2:
if (cmd.parameterNumber == 2) {
def wheelStatus = getWheelColours()[paramValue]
def wheelEvent = createEvent(name: "wheelStatus", value: wheelStatus)
if (wheelEvent.isStateChange) logger("Room Colour Wheel changed to ${wheelStatus}.","info")
result << wheelEvent
}
return result
}
def zwaveEvent(hubitat.zwave.commands.alarmv1.AlarmReport cmd) {
logger("zwaveEvent(): Alarm Report received: ${cmd}","trace")
def result = []
switch(cmd.alarmType) {
case 1: // Current Leakage:
if (!state.ignoreCurrentLeakageAlarms) { result << createEvent(name: "fault", value: "currentLeakage",
descriptionText: "Current Leakage detected!", displayed: true) }
logger("Current Leakage detected!","warn")
break
// TO DO: Check other alarm codes.
default: // Over-current:
result << createEvent(name: "fault", value: "current", descriptionText: "Over-current detected!", displayed: true)
logger("Over-current detected!","warn")
break
}
return result
}
def zwaveEvent(hubitat.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
logger("zwaveEvent(): Manufacturer-Specific Report received: ${cmd}","trace")
// Display as hex strings:
def manufacturerIdDisp = String.format("%04X",cmd.manufacturerId)
def productIdDisp = String.format("%04X",cmd.productId)
def productTypeIdDisp = String.format("%04X",cmd.productTypeId)
logger("Manufacturer-Specific Report: Manufacturer ID: ${manufacturerIdDisp}, Manufacturer Name: ${cmd.manufacturerName}" +
", Product Type ID: ${productTypeIdDisp}, Product ID: ${productIdDisp}","info")
if ( 153 != cmd.manufacturerId) logger("Device Manufacturer is not GreenWave Reality. " +
"Using this device handler with a different device may damage your device!","warn")
if ( 2 != cmd.productId) logger("Product ID does not match GreenWave PowerNode (Single). " +
"Using this device handler with a different device may damage you device!","warn")
updateDataValue("manufacturerName",cmd.manufacturerName)
updateDataValue("manufacturerId",manufacturerIdDisp)
updateDataValue("productId",productIdDisp)
updateDataValue("productTypeId",productTypeIdDisp)
}
/**
* zwaveEvent( COMMAND_CLASS_PROTECTION_V2 (0x75) : PROTECTION_REPORT_V2 (0x03) )
*
* The Protection Report is used to report the protection state of a device.
* I.e. measures to prevent unintentional control (e.g. by a child).
*
* Action: Cache values, update syncPending, and log an info message.
*
* cmd attributes:
* Short localProtectionState Local protection state (i.e. physical switches/buttons)
* Short rfProtectionState RF protection state.
*
* Example: ProtectionReport(localProtectionState: 0, reserved01: 0, reserved11: 0, rfProtectionState: 0)
**/
def zwaveEvent(hubitat.zwave.commands.protectionv2.ProtectionReport cmd) {
logger("zwaveEvent(): Protection Report received: ${cmd}","trace")
def result = []
state.protectLocalCache = cmd.localProtectionState
state.protectRfCache = cmd.rfProtectionState
def lpStates = ["unprotected","sequence","noControl"]
def lpValue = lpStates[cmd.localProtectionState]
def lpEvent = createEvent(name: "localProtectionMode", value: lpValue)
if (lpEvent.isStateChange) logger("Local Protection set to ${lpValue}.","info")
result << lpEvent
def rfpStates = ["unprotected","noControl","noResponse"]
def rfpValue = rfpStates[cmd.rfProtectionState]
def rfpEvent = createEvent(name: "rfProtectionMode", value: rfpValue)
if (rfpEvent.isStateChange) logger("RF Protection set to ${rfpValue}.","info")
result << rfpEvent
logger("Protection Report: Local Protection: ${lpValue}, RF Protection: ${rfpValue}","info")
updateSyncPending()
return result
}
/**
* zwaveEvent( COMMAND_CLASS_ASSOCIATION_V2 (0x85) : ASSOCIATION_REPORT_V2 (0x03) )
*
* The Association Report command is used to advertise the current destination nodes of a given association group.
*
* Action: Cache value and log info message only.
*
* Note: Ideally, we want to update the corresponding preference value shown on the Settings GUI, however this
* is not possible due to security restrictions in the SmartThings platform.
*
* Example: AssociationReport(groupingIdentifier: 1, maxNodesSupported: 1, nodeId: [1], reportsToFollow: 0)
**/
def zwaveEvent(hubitat.zwave.commands.associationv2.AssociationReport cmd) {
logger("zwaveEvent(): Association Report received: ${cmd}","trace")
state."assocGroupCache${cmd.groupingIdentifier}" = cmd.nodeId
// Display to user in hex format (same as IDE):
def hexArray = []
cmd.nodeId.each { hexArray.add(String.format("%02X", it)) };
def assocGroupMd = getAssocGroupsMd().find( { it.id == cmd.groupingIdentifier })
logger("Association Group ${cmd.groupingIdentifier} [${assocGroupMd?.name}] contains nodes: ${hexArray} (hexadecimal format)","info")
updateSyncPending()
}
/**
* zwaveEvent( COMMAND_CLASS_VERSION (0x86) : VERSION_REPORT (0x12) )
*
* The Version Report Command is used to advertise the library type, protocol version, and application version.
*
* Action: Publish values as device 'data' and log an info message.
* Store fwVersion as state.fwVersion.
*
* cmd attributes:
* Short applicationSubVersion
* Short applicationVersion
* Short zWaveLibraryType
* Short zWaveProtocolSubVersion
* Short zWaveProtocolVersion
*
* Example: VersionReport(applicationSubVersion: 4, applicationVersion: 3, zWaveLibraryType: 3,
* zWaveProtocolSubVersion: 5, zWaveProtocolVersion: 4)
**/
def zwaveEvent(hubitat.zwave.commands.versionv1.VersionReport cmd) {
logger("zwaveEvent(): Version Report received: ${cmd}","trace")
def zWaveLibraryTypeDisp = String.format("%02X",cmd.zWaveLibraryType)
def zWaveLibraryTypeDesc = ""
switch(cmd.zWaveLibraryType) {
case 1:
zWaveLibraryTypeDesc = "Static Controller"
break
case 2:
zWaveLibraryTypeDesc = "Controller"
break
case 3:
zWaveLibraryTypeDesc = "Enhanced Slave"
break
case 4:
zWaveLibraryTypeDesc = "Slave"
break
case 5:
zWaveLibraryTypeDesc = "Installer"
break
case 6:
zWaveLibraryTypeDesc = "Routing Slave"
break
case 7:
zWaveLibraryTypeDesc = "Bridge Controller"
break
case 8:
zWaveLibraryTypeDesc = "Device Under Test (DUT)"
break
case 0x0A:
zWaveLibraryTypeDesc = "AV Remote"
break
case 0x0B:
zWaveLibraryTypeDesc = "AV Device"
break
default:
zWaveLibraryTypeDesc = "N/A"
}
def applicationVersionDisp = String.format("%d.%02d",cmd.applicationVersion,cmd.applicationSubVersion)
def zWaveProtocolVersionDisp = String.format("%d.%02d",cmd.zWaveProtocolVersion,cmd.zWaveProtocolSubVersion)
state.fwVersion = new BigDecimal(applicationVersionDisp)
logger("Version Report: Application Version: ${applicationVersionDisp}, " +
"Z-Wave Protocol Version: ${zWaveProtocolVersionDisp}, " +
"Z-Wave Library Type: ${zWaveLibraryTypeDisp} (${zWaveLibraryTypeDesc})","info")
updateDataValue("applicationVersion","${cmd.applicationVersion}")
updateDataValue("applicationSubVersion","${cmd.applicationSubVersion}")
updateDataValue("zWaveLibraryType","${zWaveLibraryTypeDisp}")
updateDataValue("zWaveProtocolVersion","${cmd.zWaveProtocolVersion}")
updateDataValue("zWaveProtocolSubVersion","${cmd.zWaveProtocolSubVersion}")
}
/**
* zwaveEvent( COMMAND_CLASS_INDICATOR (0x87) : INDICATOR_REPORT (0x03) )
*
* The Indicator Report command is used to advertise the state of an indicator.
*
* Action: Do nothing. It doesn't tell us anything useful.
*
* cmd attributes:
* Short value Indicator status.
* 0x00 = Off/Disabled
* 0x01..0x63 = Indicator Range.
* 0xFF = On/Enabled.
*
* Example: IndicatorReport(value: 0)
**/
def zwaveEvent(hubitat.zwave.commands.indicatorv1.IndicatorReport cmd) {
logger("zwaveEvent(): Indicator Report received: ${cmd}","trace")
}
/**
* zwaveEvent( DEFAULT CATCHALL )
*
* Called for all commands that aren't handled above.
**/
def zwaveEvent(hubitat.zwave.Command cmd) {
logger("zwaveEvent(): No handler for command: ${cmd}","error")
}
/*****************************************************************************************************************
* Capability-related Commands:
*****************************************************************************************************************/
/**
* on() [Capability: Switch]
*
* Turn the switch on.
**/
def on() {
logger("on(): Turning switch on.","info")
sendCommands([
zwave.basicV1.basicSet(value: 0xFF).format(),
zwave.switchBinaryV1.switchBinaryGet().format(),
"delay 3000",
zwave.meterV2.meterGet(scale: 2).format()
])
}
/**
* off() [Capability: Switch]
*
* Turn the switch off.
**/
def off() {
logger("off(): Turning switch off.","info")
sendCommands([
zwave.basicV1.basicSet(value: 0x00).format(),
zwave.switchBinaryV1.switchBinaryGet().format(),
"delay 3000",
zwave.meterV2.meterGet(scale: 2).format()
])
}
/**
* poll() [Capability: Polling]
*
* Calls refresh().
**/
def poll() {
logger("poll()","trace")
refresh()
}
/**
* refresh() [Capability: Refresh]
*
* Action: Request switchBinary, energy, and power reports. Plus, get wheel status.
* Trigger a sync too.
**/
def refresh() {
logger("refresh()","trace")
sendCommands([
zwave.switchBinaryV1.switchBinaryGet().format(),
zwave.meterV2.meterGet(scale: 0).format(),
zwave.meterV2.meterGet(scale: 2).format(),
zwave.configurationV1.configurationGet(parameterNumber: 2) // Wheel Status
])
sync()
}
/*****************************************************************************************************************
* Custom Commands:
*****************************************************************************************************************/
/**
* blink()
*
* Causes the Circle LED to blink for ~20 seconds.
**/
def blink() {
logger("blink(): Blinking Circle LED","info")
sendCommands([zwave.indicatorV1.indicatorSet(value: 255)])
}
/**
* autoOff()
*
* Calls off(), but with additional log message.
**/
def autoOff() {
logger("autoOff(): Automatically turning off the device.","info")
off()
}
/**
* reset()
*
* Alias for resetEnergy().
*
* Note: this used to be part of the official 'Energy Meter' capability, but isn't anymore.
**/
def reset() {
logger("reset()","trace")
resetEnergy()
}
/**
* resetEnergy()
*
* Reset the Accumulated Energy figure held in the device.
**/
def resetEnergy() {
logger("resetEnergy(): Resetting Accumulated Energy","info")
state.energyLastReset = new Date().format("YYYY/MM/dd \n HH:mm:ss", location.timeZone)
sendEvent(name: "energyLastReset", value: state.energyLastReset, descriptionText: "Accumulated Energy Reset")
sendCommands([
zwave.meterV3.meterReset(),
zwave.meterV3.meterGet(scale: 0)
],400)
}
/**
* resetFault()
*
* Reset fault alarm to 'clear'.
**/
def resetFault() {
logger("resetFault(): Resetting fault alarm.","info")
sendEvent(name: "fault", value: "clear", descriptionText: "Fault alarm cleared", displayed: true)
}
/**
* setLocalProtectionMode(localProtectionMode)
*
* Set local (physical) protection mode.
*
* Note: GreenWave PowerNode supports "unprotected" and "noControl" modes only.
*
* localProtectionMode values:
* "unprotected" Physical switches are operational.
* "sequence" Special sequence required to operate.
* "noControl" Physical switches are disabled.
**/
def setLocalProtectionMode(localProtectionMode) {
logger("setLocalProtectionMode(${localProtectionMode})","trace")
switch(localProtectionMode.toLowerCase()) {
case "unprotected":
state.protectLocalTarget = 0
break
case "sequence":
logger("setLocalProtectionMode(): Protection by sequence is not supported by this device.","warn")
state.protectLocalTarget = 2
break
case "nocontrol":
state.protectLocalTarget = 2
break
default:
logger("setLocalProtectionMode(): Unknown protection mode: ${localProtectionMode}.","warn")
}
sync()
}
/**
* toggleLocalProtectionMode()
*
* Toggle local (physical) protection mode between "unprotected" and "noControl" modes.
**/
def toggleLocalProtectionMode() {
logger("toggleLocalProtectionMode()","trace")
if (device.latestValue("localProtectionMode") != "unprotected") {
setLocalProtectionMode("unprotected")
}
else {
setLocalProtectionMode("noControl")
}
}
/**
* setRfProtectionMode(rfProtectionMode)
*
* Set RF (wireless) protection mode.
*
* Note: GreenWave PowerNode supports "unprotected" and "noControl" modes only.
*
* rfProtectionMode values:
* "unprotected" Device responds to wireless commands.
* "noControl" Device ignores wireless commands (sends ApplicationRejectedRequest).
* "noResponse" Device ignores wireless commands.
**/
def setRfProtectionMode(rfProtectionMode) {
logger("setRfProtectionMode(${rfProtectionMode})","trace")
switch(rfProtectionMode.toLowerCase()) {
case "unprotected":
state.protectRfTarget = 0
break
case "nocontrol":
state.protectRfTarget = 1
break
case "noresponse":
logger("setRfProtectionMode(): NoResponse mode is not supported by this device.","warn")
state.protectRfTarget = 1
break
default:
logger("setRfProtectionMode(): Unknown protection mode: ${rfProtectionMode}.","warn")
}
sync()
}
/**
* toggleRfProtectionMode()
*
* Toggle RF (wireless) protection mode between "unprotected" and "noControl" modes.
**/
def toggleRfProtectionMode() {
logger("toggleRfProtectionMode()","trace")
if (device.latestValue("rfProtectionMode") != "unprotected") {
setRfProtectionMode("unprotected")
}
else {
setRfProtectionMode("noControl")
}
}
/*****************************************************************************************************************
* Private Helper Functions:
*****************************************************************************************************************/
/**
* encapCommand(cmd)
*
* Applies security or CRC16 encapsulation to a command as needed.
* Returns a hubitat.zwave.Command.
**/
private encapCommand(hubitat.zwave.Command cmd) {
if (state.useSecurity) {
return zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd)
}
else if (state.useCrc16) {
return zwave.crc16EncapV1.crc16Encap().encapsulate(cmd)
}
else {
return cmd
}
}
/**
* prepCommands(cmds, delay=200)
*
* Converts a list of commands (and delays) into a HubMultiAction object, suitable for returning via parse().
* Uses encapCommand() to apply security or CRC16 encapsulation as needed.
**/
private prepCommands(cmds, delay=200) {
return response(delayBetween(cmds.collect{ (it instanceof hubitat.zwave.Command ) ? encapCommand(it).format() : it },delay))
}
/**
* sendCommands(cmds, delay=200)
*
* Sends a list of commands directly to the device using sendHubCommand.
* Uses encapCommand() to apply security or CRC16 encapsulation as needed.
**/
private sendCommands(cmds, delay=200) {
sendHubCommand(hubitat.device.HubAction)( cmds.collect{ (it instanceof hubitat.zwave.Command ) ? response(encapCommand(it)) : response(it) }, delay)
}
/**
* logger()
*
* Wrapper function for all logging:
* Logs messages to the IDE (Live Logging), and also keeps a historical log of critical error and warning
* messages by sending events for the device's logMessage attribute.
* Configured using configLoggingLevelIDE and configLoggingLevelDevice preferences.
**/
private logger(msg, level = "debug") {
switch(level) {
case "error":
if (state.loggingLevelIDE >= 1) log.error msg
if (state.loggingLevelDevice >= 1) sendEvent(name: "logMessage", value: "ERROR: ${msg}", displayed: false, isStateChange: true)
break
case "warn":
if (state.loggingLevelIDE >= 2) log.warn msg
if (state.loggingLevelDevice >= 2) sendEvent(name: "logMessage", value: "WARNING: ${msg}", displayed: false, isStateChange: true)
break
case "info":
if (state.loggingLevelIDE >= 3) log.info msg
break
case "debug":
if (state.loggingLevelIDE >= 4) log.debug msg
break
case "trace":
if (state.loggingLevelIDE >= 5) log.trace msg
break
default:
log.debug msg
break
}
}
/**
* sync()
*
* Manages synchronisation of parameters, association groups, etc. with the physical device.
* The syncPending attribute advertises remaining number of sync operations.
*
* Does not return a list of commands, it sends them immediately using sendCommands(), which means sync() can be
* triggered by schedule().
*
* Parameters:
* forceAll Force all items to be synced, otherwise only changed items will be synced.
**/
private sync(forceAll = false) {
logger("sync(): Syncing configuration with the physical device.","info")
def cmds = []
def syncPending = 0
if (forceAll) { // Clear all cached values.
getParamsMd().findAll( {!it.readonly} ).each { state."paramCache${it.id}" = null }
getAssocGroupsMd().each { state."assocGroupCache${it.id}" = null }
state.protectLocalCache = null
state.protectRfCache = null
state.switchAllModeCache = null
}
getParamsMd().findAll( { !it.readonly & (it.fwVersion <= state.fwVersion) } ).each { // Exclude readonly/newer parameters.
if ( (state."paramTarget${it.id}" != null) & (state."paramCache${it.id}" != state."paramTarget${it.id}") ) {
cmds << zwave.configurationV1.configurationSet(parameterNumber: it.id, size: it.size, scaledConfigurationValue: state."paramTarget${it.id}".toInteger())
cmds << zwave.configurationV1.configurationGet(parameterNumber: it.id)
logger("sync(): Syncing parameter #${it.id} [${it.name}]: New Value: " + state."paramTarget${it.id}","info")
syncPending++
}
}
getAssocGroupsMd().each {
def cachedNodes = state."assocGroupCache${it.id}"
def targetNodes = state."assocGroupTarget${it.id}"
if ( cachedNodes != targetNodes ) {
// Display to user in hex format (same as IDE):
def targetNodesHex = []
targetNodes.each { targetNodesHex.add(String.format("%02X", it)) }
logger("sync(): Syncing Association Group #${it.id} [${it.name}]: Destinations: ${targetNodesHex}","info")
if (it.multiChannel) {
cmds << zwave.multiChannelAssociationV2.multiChannelAssociationRemove(groupingIdentifier: it.id, nodeId: []) // Remove All
cmds << zwave.multiChannelAssociationV2.multiChannelAssociationSet(groupingIdentifier: it.id, nodeId: targetNodes)
cmds << zwave.multiChannelAssociationV2.multiChannelAssociationGet(groupingIdentifier: it.id)
}
else {
cmds << zwave.associationV2.associationRemove(groupingIdentifier: it.id, nodeId: []) // Remove All
cmds << zwave.associationV2.associationSet(groupingIdentifier: it.id, nodeId:[zwaveHubNodeId])
cmds << zwave.associationV2.associationGet(groupingIdentifier: it.id)
}
syncPending++
}
}
if ( (state.protectLocalTarget != null) & (state.protectRfTarget != null)
& ( (state.protectLocalCache != state.protectLocalTarget) || (state.protectRfCache != state.protectRfTarget) ) ) {
logger("sync(): Syncing Protection State: Local Protection: ${state.protectLocalTarget}, RF Protection: ${state.protectRfTarget}","info")
cmds << zwave.protectionV2.protectionSet(localProtectionState : state.protectLocalTarget, rfProtectionState: state.protectRfTarget)
cmds << zwave.protectionV2.protectionGet()
syncPending++
}
if ( (state.switchAllModeTarget != null) & (state.switchAllModeCache != state.switchAllModeTarget) ) {
logger("sync(): Syncing SwitchAll Mode: ${state.switchAllModeTarget}","info")
cmds << zwave.switchAllV1.switchAllSet(mode: state.switchAllModeTarget)
cmds << zwave.switchAllV1.switchAllGet()
syncPending++
}
sendEvent(name: "syncPending", value: syncPending, displayed: false)
sendCommands(cmds,800)
}
/**
* updateSyncPending()
*
* Updates syncPending attribute, which advertises remaining number of sync operations.
**/
private updateSyncPending() {
def syncPending = 0
getParamsMd().findAll( { !it.readonly & (it.fwVersion <= state.fwVersion) } ).each { // Exclude readonly/newer parameters.
if ( (state."paramTarget${it.id}" != null) & (state."paramCache${it.id}" != state."paramTarget${it.id}") ) {
syncPending++
}
}
getAssocGroupsMd().each {
def cachedNodes = state."assocGroupCache${it.id}"
def targetNodes = state."assocGroupTarget${it.id}"
if ( cachedNodes != targetNodes ) {
syncPending++
}
}
if ( (state.protectLocalCache == null) || (state.protectRfCache == null) ||
(state.protectLocalCache != state.protectLocalTarget) || (state.protectRfCache != state.protectRfTarget) ) {
syncPending++
}
if ( (state.switchAllModeTarget != null) & (state.switchAllModeCache != state.switchAllModeTarget) ) {
syncPending++
}
logger("updateSyncPending(): syncPending: ${syncPending}", "debug")
if ((syncPending == 0) & (device.latestValue("syncPending") > 0)) logger("Sync Complete.", "info")
sendEvent(name: "syncPending", value: syncPending, displayed: false)
}
/**
* generatePrefsParams()
*
* Generates preferences (settings) for device parameters.
**/
private generatePrefsParams() {
section {
input (
type: "paragraph",
element: "paragraph",
title: "DEVICE PARAMETERS:",
description: "Device parameters are used to customise the physical device. " +
"Refer to the product documentation for a full description of each parameter."
)
getParamsMd().findAll( {!it.readonly} ).each { // Exclude readonly parameters.
def lb = (it.description.length() > 0) ? "\n" : ""
switch(it.type) {
case "number":
input (
name: "configParam${it.id}",
title: "#${it.id}: ${it.name}: \n" + it.description + lb +"Default Value: ${it.defaultValue}",
type: it.type,
range: it.range,
// defaultValue: it.defaultValue, // iPhone users can uncomment these lines!
required: it.required
)
break
case "enum":
input (
name: "configParam${it.id}",
title: "#${it.id}: ${it.name}: \n" + it.description + lb + "Default Value: ${it.defaultValue}",
type: it.type,
options: it.options,
// defaultValue: it.defaultValue, // iPhone users can uncomment these lines!
required: it.required
)
break
}
}
} // section
}
/**
* generatePrefsAssocGroups()
*
* Generates preferences (settings) for Association Groups.
* Excludes any groups that are hubOnly.
**/
private generatePrefsAssocGroups() {
section {
input (
type: "paragraph",
element: "paragraph",
title: "ASSOCIATION GROUPS:",
description: "Association groups enable the device to control other Z-Wave devices directly, " +
"without participation of the main controller.\n" +
"Enter a comma-delimited list of destinations (node IDs and/or endpoint IDs) for " +
"each association group. All IDs must be in hexadecimal format. E.g.:\n" +
"Node destinations: '11, 0F'\n" +
"Endpoint destinations: '1C:1, 1C:2'"
)
getAssocGroupsMd().findAll( { !it.hubOnly } ).each {
input (
name: "configAssocGroup${it.id}",
title: "Association Group #${it.id}: ${it.name}: \n" + it.description + " \n[MAX NODES: ${it.maxNodes}]",
type: "text",
// defaultValue: "", // iPhone users can uncomment these lines!
required: false
)
}
}
}
/**
* byteArrayToUInt(byteArray)
*
* Converts a byte array to an UNSIGNED int.
**/
private byteArrayToUInt(byteArray) {
// return java.nio.ByteBuffer.wrap(byteArray as byte[]).getInt()
def i = 0
byteArray.reverse().eachWithIndex { b, ix -> i += b * (0x100 ** ix) }
return i
}
/**
* test()
*
* Called from 'test' tile.
**/
private test() {
logger("test()","trace")
def cmds = []
sendCommands(cmds, 500)
}
/*****************************************************************************************************************
* Static Matadata Functions:
*
* These functions encapsulate metadata about the device. Mostly obtained from:
* Z-wave Alliance Reference: http://products.z-wavealliance.org/products/1036
*****************************************************************************************************************/
/**
* getCommandClassVersions()
*
* Returns a map of the command class versions supported by the device. Used by parse() and zwaveEvent() to
* extract encapsulated commands from MultiChannelCmdEncap, MultiInstanceCmdEncap, SecurityMessageEncapsulation,
* and Crc16Encap messages.
*
* Reference: http://products.z-wavealliance.org/products/629/classes
**/
private getCommandClassVersions() {
return [
0x20: 1, // Basic V1
0x22: 1, // Application Status V1 (Not advertised but still sent)
0x25: 1, // Switch Binary V1
0x27: 1, // Switch All V1
0x32: 3, // Meter V3
0x56: 1, // CRC16 Encapsulation V1
0x70: 1, // Configuration V1
0x71: 1, // Alarm (Notification) V1
0x72: 2, // Manufacturer Specific V2
0x75: 2, // Protection V2
0x85: 2, // Association V2
0x86: 1, // Version V1
0x87: 1 // Indicator V1
]
}
/**
* getParamsMd()
*
* Returns device parameters metadata. Used by sync(), updateSyncPending(), and generatePrefsParams().
*
* List attributes:
* id/size/type/range/defaultValue/required/name/description/options These directly correspond to input attributes.
* readonly If the parameter is readonly, then it will not be displayed by generatePrefsParams() or synced.
* isSigned Indicates if the raw byte value represents a signed or unsigned number.
* fwVersion The minimum firmware version that supports the parameter. Parameters with a higher fwVersion than the
* device instance will not be displayed by generatePrefsParams() or synced.
**/
private getParamsMd() {
return [
// Firmware v4.22 onwards:
[id: 0, size: 1, type: "number", range: "1..100", defaultValue: 10, required: false, readonly: false,
isSigned: true, fwVersion: 4.22,
name: "Power Report Threshold",
description : "Power level change that will result in a new power report being sent.\n" +
"Values: 1-100 = % change from previous report"],
[id: 1, size: 1, type: "number", range: "0..255", defaultValue: 255, required: false, readonly: false, // Real default is 2.
isSigned: false, fwVersion: 4.22,
name: "Keep-Alive Time",
description : "Time after which the LED indicator will flash if there has been no communication from the hub.\n" +
"Values: 1-255 = time in minutes"],
[id: 2, size: 1, type: "number", defaultValue: 0, required: false, readonly: true, // READ-ONLY!
isSigned: false, fwVersion: 4.22,
name: "Wheel Status",
description : "Indicates the position of the Room Colour Selector wheel."],
// Firmware v4.28 onwards:
[id: 3, size: 1, type: "enum", defaultValue: "2", required: false, readonly: false,
isSigned: true, fwVersion: 4.28,
name: "State After Power Failure",
description : "Switch state to restore after a power failure. [Firmware 4.28+ Only]",
options: ["0" : "0: Off",
"1" : "1: Restore Previous State",
"2" : "2: On"] ],
[id: 4, size: 1, type: "enum", defaultValue: "1", required: false, readonly: false,
isSigned: true, fwVersion: 4.28,
name: "LED for Network Error",
description : "LED indicates network error. [Firmware 4.28+ Only]",
options: ["0" : "0: DISABLED",
"1" : "1: ENABLED"] ]
]
}
/**
* getAssocGroupsMd()
*
* Returns association groups metadata. Used by sync(), updateSyncPending(), and generatePrefsAssocGroups().
*
* List attributes:
* id Association group ID (groupingIdentifier).
* maxNodes Maximum nodes supported.
* name Name, shown on device settings screen and logs.
* hubOnly Group should only contain the SmartThings hub (not shown on settings screen).
* multiChannel Group supports multiChannelAssociation.
* description Description, shown on device settings screen.
**/
private getAssocGroupsMd() {
return [
[id: 1, maxNodes: 1, name: "Wheel Status", hubOnly: true, multiChannel: false,
description : "Reports wheel status using CONFIGURATION_REPORT commands."],
[id: 2, maxNodes: 1, name: "Relay Health", hubOnly: true, multiChannel: false,
description : "Sends ALARM commands when current leakage is detected."],
[id: 3, maxNodes: 1, name: "Power Level", hubOnly: true, multiChannel: false,
description : "Reports instantaneous power using METER_REPORT commands (configured using parameter #0)."],
[id: 4, maxNodes: 1, name: "Overcurrent Protection", hubOnly: true, multiChannel: false,
description : "Sends ALARM commands when overcurrent is detected."]
]
}
/**
* getWheelColours()
*
* Returns a map of wheel colours.
**/
private getWheelColours() {
return [
0x80 : "black",
0x81 : "green",
0x82 : "blue",
0x83 : "red",
0x84 : "yellow",
0x85 : "violet",
0x86 : "orange",
0x87 : "aqua",
0x88 : "pink",
0x89 : "white"
]
}
This is the line the error is reporting on.
private sendCommands(cmds, delay=200) {
sendHubCommand(hubitat.device.HubAction)( cmds.collect{ (it instanceof hubitat.zwave.Command ) ? response(encapCommand(it)) : response(it) }, delay)
}
If anyone could help or provide some tips, that would be great. Original documentation here
Many thanks