Skip to content

Instantly share code, notes, and snippets.

@jymbob
Created April 19, 2018 09:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jymbob/645987710c2914133fb5953d3d2be468 to your computer and use it in GitHub Desktop.
Save jymbob/645987710c2914133fb5953d3d2be468 to your computer and use it in GitHub Desktop.
Add sw2 to Fibaro Dimmer Smartthings DTH
/*****************************************************************************************************************
* Copyright: David Lomas (codersaur) - Additional code by Jymbob
*
* Name: Fibaro Dimmer 2
*
* Author: David Lomas (codersaur)
*
* Date: 2017-02-27
*
* Version: 2.02
*
* Source: https://github.com/codersaur/SmartThings/tree/master/devices/fibaro-dimmer-2
*
* Author: David Lomas (codersaur)
*
* Description: An advanced SmartThings device handler for the Fibaro Dimmer 2 (FGD-212) Z-Wave Dimmer.
*
* For full information, including installation instructions, exmples, and version history, see:
* https://github.com/codersaur/SmartThings/tree/master/devices/fibaro-dimmer-2
*
* License:
* 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.
*
*****************************************************************************************************************/
metadata {
definition (name: "Fibaro Dimmer 2", namespace: "codersaur", author: "David Lomas") {
capability "Actuator"
capability "Switch"
capability "Button"
capability "Holdable Button"
capability "Switch Level"
capability "Light"
capability "Sensor"
capability "Power Meter"
capability "Energy Meter"
capability "Polling"
capability "Refresh"
capability "Configuration"
// Custom (Virtual) Capabilities:
//capability "Fault"
//capability "Logging"
//capability "Scene Controller"
// Standard (Capability) Attributes:
attribute "switch", "string"
attribute "switch2", "string"
attribute "level", "number"
attribute "power", "number"
attribute "energy", "number"
// Custom Attributes:
attribute "fault", "string" // Indicates if the device has any faults. 'clear' if no active faults.
attribute "logMessage", "string" // Important log messages.
attribute "energyLastReset", "string" // Last time that Accumulated Engergy was reset.
attribute "syncPending", "number" // Number of config items that need to be synced with the physical device.
attribute "nightmode", "string" // 'Enabled' or 'Disabled'.
attribute "scene", "number" // ID of last-activated scene.
attribute "numberOfButtons", "number"
// Display Attributes:
// These are only required because the UI lacks number formatting and strips leading zeros.
attribute "dispPower", "string"
attribute "dispEnergy", "string"
// Custom Commands:
command "reset"
command "resetEnergy"
command "enableNightmode"
command "disableNightmode"
command "toggleNightmode"
command "clearFault"
command "sync"
command "test"
command "configure"
command "sw2on"
command "sw2off"
// Fingerprints (new format):
fingerprint mfr: "010F", prod: "0102", model: "1000"
fingerprint type: "1101", mfr: "010F", cc: "5E,86,72,59,73,22,31,32,71,56,98,7A"
fingerprint type: "1101", mfr: "010F", cc: "5E,86,72,59,73,22,31,32,71,56,98,7A", sec: "20,5A,85,26,8E,60,70,75,27", secOut: "2B"
}
tiles(scale: 2) {
// Multi Tile:
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
}
tileAttribute ("device.level", key: "SLIDER_CONTROL", range:"(0..100)") {
attributeState "level", action:"setLevel"
}
}
// Instantaneous Power:
valueTile("instMode", "device.dispPower", decoration: "flat", width: 2, height: 1) {
state "default", label:'Now:', action:"refresh", icon: "https://raw.githubusercontent.com/codersaur/SmartThings/master/icons/tile_2x1_refresh.png"
}
valueTile("power", "device.dispPower", decoration: "flat", width: 2, height: 1) {
state "default", label:'${currentValue}', icon: "https://raw.githubusercontent.com/codersaur/SmartThings/master/icons/tile_2x1_top_bottom_2.png"
}
// Accumulated Energy:
valueTile("energyLastReset", "device.energyLastReset", decoration: "flat", width: 2, height: 1) {
state "default", label:'Since: ${currentValue}', action:"resetEnergy", icon: "https://raw.githubusercontent.com/codersaur/SmartThings/master/icons/tile_2x1_stopwatch_reset.png"
}
valueTile("energy", "device.dispEnergy", width: 2, height: 1) {
state "default", label:'${currentValue}', icon: "https://raw.githubusercontent.com/codersaur/SmartThings/master/icons/tile_2x1_top_bottom_2.png"
}
// Other Tiles:
standardTile("sw2", "device.switch2", width: 2, height: 2, canChangeIcon: true) {
state "on", action: "sw2off", label: 'On', icon: "st.switches.switch.on", backgroundColor: "#79b821"
state 'off', action: "sw2on", label: 'Off', icon: "st.switches.switch.off", backgroundColor: "#ffffff"
}
standardTile("nightmode", "device.nightmode", decoration: "flat", width: 2, height: 2) {
state "default", label:'${currentValue}', action:"toggleNightmode", icon:"st.Weather.weather4"
}
valueTile("scene", "device.scene", decoration: "flat", width: 2, height: 2) {
state "default", label:'Scene: ${currentValue}'
}
standardTile("refresh", "device.power", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", label:'', action:"refresh", icon:"st.secondary.refresh"
}
standardTile("syncPending", "device.syncPending", decoration: "flat", width: 2, height: 2) {
state "default", label:'Sync Pending', action:"sync", backgroundColor:"#FF6600"
state "0", label:'Synced', action:"", backgroundColor:"#79b821"
}
standardTile("fault", "device.fault", decoration: "flat", width: 2, height: 2) {
state "default", label:'${currentValue} Fault', action:"clearFault", backgroundColor:"#FF6600", icon:"st.secondary.tools"
state "clear", label:'${currentValue}', action:"", backgroundColor:"#79b821", icon:""
}
standardTile("test", "device.power", decoration: "flat", width: 2, height: 2) {
state "default", label:'Test', action:"test"
}
standardTile("configure", "device.configure", inactiveLabel: false, width: 2, height: 2, decoration: "flat") {
state "configure", label:'', action:"configuration.configure", icon:"st.secondary.configure"
}
// Tile Layouts:
main(["switch"])
details([
"switch",
"instMode","power",
"nightmode",
"energyLastReset","energy",
"sw2",
"scene",
//"refresh",
//"test",
"syncPending",
"fault",
"configure"
])
}
preferences {
section { // GENERAL:
input (
type: "paragraph",
element: "paragraph",
title: "GENERAL:",
description: "General device handler settings."
)
input (
name: "configLoggingLevelIDE",
title: "IDE Live Logging Level: Messages 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", // iPhone users can uncomment these lines!
required: true
)
input (
name: "configLoggingLevelDevice",
title: "Device Logging Level: Messages with this level and higher will be logged to the logMessage attribute.",
type: "enum",
options: [
"0" : "None",
"1" : "Error",
"2" : "Warning"
],
// defaultValue: "2", // iPhone users can uncomment these lines!
required: true
)
input (
name: "configSyncAll",
title: "Force Full Sync: All device parameters, association groups, and protection settings will " +
"be re-sent to the device. This will take several minutes and you may need to press the 'sync' " +
"tile a few times.",
type: "boolean",
// defaultValue: false, // iPhone users can uncomment these lines!
required: true
)
input (
name: "configProactiveReports",
title: "Proactively Request Reports: Additonal requests for status reports will be made. " +
"Use only if status reporting is unreliable.",
type: "boolean",
// defaultValue: false, // iPhone users can uncomment these lines!
required: true
)
}
section { // PROTECTION:
input type: "paragraph",
element: "paragraph",
title: "PROTECTION:",
description: "Prevent unintentional control (e.g. by a child) by disabling the physical switches and/or RF control."
input (
name: "configProtectLocal",
title: "Local Protection: Applies to physical switches:",
type: "enum",
options: [
"0" : "Unprotected",
//"1" : "Protection by sequence", // Not supported by Fibaro Dimmer 2.
"2" : "No operation possible"
],
// defaultValue: "0", // iPhone users can uncomment these lines!
required: true
)
input (
name: "configProtectRF",
title: "RF Protection: Applies to Z-Wave commands sent from hub or other devices:",
type: "enum",
options: [
"0" : "Unprotected",
"1" : "No RF control"//,
//"2" : "No RF response" // Not supported by Fibaro Dimmer 2.
],
// defaultValue: "0", // iPhone users can uncomment these lines!
required: true
)
}
section { // NIGHTMODE:
input type: "paragraph",
element: "paragraph",
title: "NIGHTMODE:",
description: "Nightmode forces the dimmer to switch on at a specific level (e.g. low-level during the night).\n" +
"Nightmode can be enabled/disabled manually using the new Nightmode tile, or scheduled below."
input type: "number",
name: "configNightmodeLevel",
title: "Nightmode Level: The dimmer will always switch on at this level when nightmode is enabled.",
range: "1..100",
// defaultValue: "10", // iPhone users can uncomment these lines!
required: true
input type: "boolean",
name: "configNightmodeForce",
title: "Force Nightmode: If the dimmer is on when nightmode is enabled, the Nightmode Level is applied immediately " +
"(otherwise it's only applied next time the dimmer is switched on).",
// defaultValue: true, // iPhone users can uncomment these lines!
required: true
input type: "time",
name: "configNightmodeStartTime",
title: "Nightmode Start Time: Nightmode will be enabled every day at this time.",
required: false
input type: "time",
name: "configNightmodeStopTime",
title: "Nightmode Stop Time: Nightmode will be disabled every day at this time.",
required: false
}
generatePrefsParams()
generatePrefsAssocGroups()
}
}
/**
* parse()
*
* Called when messages from the device are received by the hub. The parse method is responsible for interpreting
* those messages and returning event definitions (and command responses).
*
* As this is a Z-wave device, zwave.parse() is used to convert the message into a command. The command is then
* passed to zwaveEvent(), which is overloaded for each type of command below.
*
* Parameters:
* String description The raw message from the device.
**/
def parse(description) {
logger("parse(): Parsing raw message: ${description}","trace")
def result = null
if (description.startsWith("Err")) {
logger("parse(): Unknown Error. Raw message: ${description}","error")
}
else if (description != "updated") {
// The purpose of the replace statement here is to fix a bug, see:
// https://community.smartthings.com/t/wireless-wall-switch-zme-wallc-s-to-control-smartthings-devices-and-routines/24810/28
def cmd = zwave.parse(description.replace("98C1", "9881"), getCommandClassVersions())
if (cmd) {
result = zwaveEvent(cmd)
} else {
logger("parse(): Could not parse raw message: ${description}","error")
}
}
if(state.isConfigured != "true") configure()
return result
}
/*****************************************************************************************************************
* Z-wave Event Handlers.
*****************************************************************************************************************/
/**
* zwaveEvent( COMMAND_CLASS_BASIC V1 (0x20) : BASIC_REPORT )
*
* The Basic Report command is used to advertise the status of the primary functionality of the device.
*
* Action: Pass command to dimmerEvent().
*
* cmd attributes:
* Short value
* 0x00 = Off
* 0x01..0x63 = 0..100%
* 0xFE = Unknown
* 0xFF = On
**/
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) {
logger("zwaveEvent(): Basic Report received: ${cmd}","trace")
return dimmerEvent(cmd)
}
/**
* zwaveEvent( COMMAND_CLASS_BASIC V1 (0x20) : BASIC_SET )
*
* The Basic Set command is used to set a value in a supporting device.
* If this command is received by the hub, the hub must be a member of one or more association groups.
*
* Action: No action required as state change will be triggered via BASIC_REPORT handler.
*
* cmd attributes:
* Short value
* 0x00 = Off
* 0x01..0x63 = 0..100%
* 0xFE = Unknown
* 0xFF = On
*
* Example: BasicSet(value: 0)
**/
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd) {
logger("zwaveEvent(): Basic Set received: ${cmd}","trace")
}
/**
* zwaveEvent( COMMAND_CLASS_SWITCH_MULTILEVEL V3 (0x26) : SWITCH_MULTILEVEL_REPORT )
*
* The Switch Multilevel Report is used to advertise the status of a multilevel device.
*
* Action: Pass command to dimmerEvent().
*
* cmd attributes:
* Short value
* 0x00 = Off
* 0x01..0x63 = 0..100%
* 0xFE = Unknown
* 0xFF = On [Deprecated]
*
* Example: SwitchMultilevelReport(value: 1)
**/
def zwaveEvent(physicalgraph.zwave.commands.switchmultilevelv3.SwitchMultilevelReport cmd) {
logger("zwaveEvent(): Switch Multilevel Report received: ${cmd}","trace")
return dimmerEvent(cmd)
}
/**
* zwaveEvent( COMMAND_CLASS_SWITCH_MULTILEVEL V3 (0x26) : SWITCH_MULTILEVEL_SET )
*
* The Switch Multilevel Set command is used to set a value in a supporting device.
* If this command is received by the hub, the hub must be a member of one or more association groups.
*
* Action: No action required as state change will be triggered via SWITCH_MULTILEVEL_REPORT handler.
*
* cmd attributes:
* Short value
* 0x00 = Off
* 0x01..0x63 = 0..100%
* 0xFE = Unknown
* 0xFF = On [Deprecated]
* Short dimmingDuration
*
* Example: SwitchMultilevelSet(dimmingDuration: 1, value: 0)
**/
def zwaveEvent(physicalgraph.zwave.commands.switchmultilevelv3.SwitchMultilevelSet cmd) {
logger("zwaveEvent(): Switch Multilevel Set received: ${cmd}","trace")
}
/**
* zwaveEvent( COMMAND_CLASS_SWITCH_MULTILEVEL V3 (0x26) : SWITCH_MULTILEVEL_START_LEVEL_CHANGE )
*
* The Multilevel Switch Start Level Change command is used to initiate a transition to a new level.
* If this command is received by the hub, the hub must be a member of one or more association groups.
*
* Action: No action required as state change will be triggered via a SWITCH_MULTILEVEL_REPORT on completion
* of the transition.
*
* cmd attributes:
* Short dimmingDuration
* Boolean ignoreStartLevel
* Short incDec
* Short startLevel
* Short stepSize
* Short upDown
*
* Example: SwitchMultilevelStartLevelChange(dimmingDuration: 3, ignoreStartLevel: false, incDec: 0,
* reserved00: 0, startLevel: 4, stepSize: 1, upDown: 0)
**/
def zwaveEvent(physicalgraph.zwave.commands.switchmultilevelv3.SwitchMultilevelStartLevelChange cmd) {
logger("zwaveEvent(): Switch Multilevel Start Level Change received: ${cmd}","trace")
}
/**
* zwaveEvent( COMMAND_CLASS_SWITCH_MULTILEVEL V3 (0x26) : SWITCH_MULTILEVEL_STOP_LEVEL_CHANGE )
*
* The Multilevel Switch Stop Level Change command is used to stop an ongoing transition.
* If this command is received by the hub, the hub must be a member of one or more association groups.
*
* Action: No action required as state change will be triggered via a SWITCH_MULTILEVEL_REPORT on completion
* of the transition.
*
* cmd attributes: None
*
* Example: SwitchMultilevelStopLevelChange()
**/
def zwaveEvent(physicalgraph.zwave.commands.switchmultilevelv3.SwitchMultilevelStopLevelChange cmd) {
logger("zwaveEvent(): Switch Multilevel Stop Level Change received: ${cmd}","trace")
}
/**
* dimmerEvent()
*
* Common handler for BasicReport, SwitchBinaryReport, SwitchMultilevelReport.
*
* Action: Raise 'switch' and 'level' events.
* Restore pending level if dimmer has been switched on after nightmode has been disabled.
* If Proactive Reporting is enabled, and the level has changed, request a meter report.
**/
def dimmerEvent(physicalgraph.zwave.Command cmd) {
def result = []
// switch event:
def switchValue = (cmd.value ? "on" : "off")
def switchEvent = createEvent(name: "switch", value: switchValue)
if (switchEvent.isStateChange) logger("Dimmer turned ${switchValue}.","info")
result << switchEvent
// level event:
def levelValue = Math.round (cmd.value * 100 / 99)
def levelEvent = createEvent(name: "level", value: levelValue, unit: "%")
if (levelEvent.isStateChange) logger("Dimmer level is ${levelValue}%","info")
result << levelEvent
// Store last active level, which is needed for nightmode functionality:
if (levelValue > 0) state.lastActiveLevel = levelValue
// Restore pending level if dimmer has been switched on after nightmode has been disabled:
if (!state.nightmodeActive & (state.nightmodePendingLevel > 0) & switchEvent.isStateChange & switchValue == "on") {
logger("dimmerEvent(): Applying Pending Level: ${state.nightmodePendingLevel}","debug")
result << response(secure(zwave.basicV1.basicSet(value: Math.round(state.nightmodePendingLevel.toInteger() * 99 / 100 ))))
state.nightmodePendingLevel = 0
}
// Else if Proactive Reporting is enabled, and the level has changed, request a meter report:
else if (state.proactiveReports & levelEvent.isStateChange) {
result << response(["delay 5000", secure(zwave.meterV3.meterGet(scale: 2)),"delay 10000", secure(zwave.meterV3.meterGet(scale: 2))])
// Meter request is delayed for 5s, although sometimes this isn't long enough, so make a second request after another 10 seconds.
}
return result
}
/**
* zwaveEvent( COMMAND_CLASS_SWITCH_ALL V1 (0x27) : SWITCH_ALL_REPORT )
*
* The All Switch Report Command is used to report if the device is included or excluded from the all on/all off
* functionality.
*
* Note: The Fibaro Dimmer 2 supports control of this functionality via Parameter #11, in addition to
* SWITCH_ALL_SET commands.
*
* Action: Log an info message.
*
* cmd attributes:
* Short mode
* 0 = MODE_EXCLUDED_FROM_THE_ALL_ON_ALL_OFF_FUNCTIONALITY
* 1 = MODE_EXCLUDED_FROM_THE_ALL_ON_FUNCTIONALITY_BUT_NOT_ALL_OFF
* 2 = MODE_EXCLUDED_FROM_THE_ALL_OFF_FUNCTIONALITY_BUT_NOT_ALL_ON
* 255 = MODE_INCLUDED_IN_THE_ALL_ON_ALL_OFF_FUNCTIONALITY
**/
def zwaveEvent(physicalgraph.zwave.commands.switchallv1.SwitchAllReport cmd) {
logger("zwaveEvent(): Switch All Report received: ${cmd}","trace")
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")
return msg
}
/**
* zwaveEvent( COMMAND_CLASS_SCENE_ACTIVATION (0x2B) : SCENE_ACTIVATION_SET )
*
* The Scene Activation Set Command is used to activate the setting associated to the scene ID.
*
* Action: Raise scene event and log an info message.
*
* cmd attributes:
* Short dimmingDuration
* 0x00 = Instantly
* 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 = Dimming duration configured by the Scene Actuator Configuration Set and Scene
* Controller Configuration Set Command depending on device used.
* Short sceneId
* 0x00..0xFF = Scene0..Scene255
**/
def zwaveEvent(physicalgraph.zwave.commands.sceneactivationv1.SceneActivationSet cmd) {
logger("zwaveEvent(): Scene Activation Set received: ${cmd}","trace")
def result = []
switch (cmd.sceneId) {
case 20:
def liveEvent = createEvent(name: "button", value: "pushed", data: [buttonNumber: 1], isStateChange: true)
def switchEvent = createEvent(name: "switch2", value: "on")
logger("S2 on","info")
result << liveEvent
result << switchEvent
break
case 21:
def liveEvent = createEvent(name: "button", value: "pushed", data: [buttonNumber: 2], isStateChange: true)
def switchEvent = createEvent(name: "switch2", value: "off")
logger("S2 off","info")
result << liveEvent
result << switchEvent
break
default:
logger("Scene #${cmd.sceneId} was activated.","info")
break
}
result << createEvent(name: "scene", value: "$cmd.sceneId", data: [switchType: "$settings.param20"], descriptionText: "Scene id ${cmd.sceneId} was activated", isStateChange: true)
return result
}
def sw2on() {
logger("sw2on called")
sendEvent(name: "button", value: "pushed", data: [buttonNumber: 1], isStateChange: true)
sendEvent(name: "switch2", value: "on")
}
def sw2off() {
logger("sw2off called")
sendEvent(name: "button", value: "pushed", data: [buttonNumber: 2], isStateChange: true)
sendEvent(name: "switch2", value: "off")
}
/**
* zwaveEvent( COMMAND_CLASS_SENSOR_MULTILEVEL V4 (0x31) : SENSOR_MULTILEVEL_REPORT )
*
* The Multilevel Sensor Report Command is used by a multilevel sensor to advertise a sensor reading.
*
* Action: Raise appropriate type of event (and disp event) and log an info message.
*
* Note: SmartThings does not yet have capabilities corresponding to all possible sensor types, therefore
* some of the event types raised below are non-standard.
*
* Note: Fibaro Dimmer 2 appears to report power (sensorType 4) only.
*
* cmd attributes:
* Short precision Indicates the number of decimals.
* E.g. The decimal value 1025 with precision 2 is therefore equal to 10.25.
* Short scale Indicates what unit the sensor uses.
* BigDecimal scaledSensorValue Sensor value as a double.
* Short sensorType Sensor Type (8 bits).
* List<Short> sensorValue Sensor value as an array of bytes.
* Short size Indicates the number of bytes used for the sensor value.
**/
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv4.SensorMultilevelReport cmd) {
logger("zwaveEvent(): SensorMultilevelReport received: ${cmd}","trace")
def result = []
def map = [ displayed: true, value: cmd.scaledSensorValue.toString() ]
def dispMap = [ displayed: false ]
// Sensor Types up to V4 only, there are further sensor types up to V10 defined.
switch (cmd.sensorType) {
case 1: // Air Temperature (V1)
map.name = "temperature"
map.unit = (cmd.scale == 1) ? "F" : "C"
break
case 2: // General Purpose (V1)
map.name = "value"
map.unit = (cmd.scale == 1) ? "" : "%"
break
case 3: // Luninance (V1)
map.name = "illuminance"
map.unit = (cmd.scale == 1) ? "lux" : "%"
break
case 4: // Power (V2)
map.name = "power"
map.unit = (cmd.scale == 1) ? "Btu/h" : "W"
dispMap.name = "dispPower"
dispMap.value = String.format("%.1f",cmd.scaledSensorValue as BigDecimal) + " ${map.unit}"
break
case 5: // Humidity (V2)
map.name = "humidity"
map.unit = (cmd.scale == 1) ? "g/m^3" : "%"
break
case 6: // Velocity (V2)
map.name = "velocity"
map.unit = (cmd.scale == 1) ? "mph" : "m/s"
break
case 7: // Direction (V2)
map.name = "direction"
map.unit = ""
break
case 8: // Atmospheric Pressure (V2)
case 9: // Barometric Pressure (V2)
map.name = "pressure"
map.unit = (cmd.scale == 1) ? "inHg" : "kPa"
break
case 0xA: // Solar Radiation (V2)
map.name = "radiation"
map.unit = "W/m^3"
break
case 0xB: // Dew Point (V2)
map.name = "dewPoint"
map.unit = (cmd.scale == 1) ? "F" : "C"
break
case 0xC: // Rain Rate (V2)
map.name = "rainRate"
map.unit = (cmd.scale == 1) ? "in/h" : "mm/h"
break
case 0xD: // Tide Level (V2)
map.name = "tideLevel"
map.unit = (cmd.scale == 1) ? "ft" : "m"
break
case 0xE: // Weight (V3)
map.name = "weight"
map.unit = (cmd.scale == 1) ? "lbs" : "kg"
break
case 0xF: // Voltage (V3)
map.name = "voltage"
map.unit = (cmd.scale == 1) ? "mV" : "V"
dispMap.name = "dispVoltage"
dispMap.value = String.format("%.1f",cmd.scaledSensorValue as BigDecimal) + " ${map.unit}"
break
case 0x10: // Current (V3)
map.name = "current"
map.unit = (cmd.scale == 1) ? "mA" : "A"
dispMap.name = "dispCurrent"
dispMap.value = String.format("%.1f",cmd.scaledSensorValue as BigDecimal) + " ${map.unit}"
break
case 0x11: // Carbon Dioxide Level (V3)
map.name = "carbonDioxide"
map.unit = "ppm"
break
case 0x12: // Air Flow (V3)
map.name = "fluidFlow"
map.unit = (cmd.scale == 1) ? "cfm" : "m^3/h"
break
case 0x13: // Tank Capacity (V3)
map.name = "fluidVolume"
map.unit = (cmd.scale == 0) ? "ltr" : (cmd.scale == 1) ? "m^3" : "gal"
break
case 0x14: // Distance (V3)
map.name = "distance"
map.unit = (cmd.scale == 0) ? "m" : (cmd.scale == 1) ? "cm" : "ft"
break
default:
logger("zwaveEvent(): SensorMultilevelReport with unhandled sensorType: ${cmd}","warn")
map.name = "unknown"
map.unit = "unknown"
break
}
logger("New sensor reading: Name: ${map.name}, Value: ${map.value}, Unit: ${map.unit}","info")
result << createEvent(map)
if (dispMap.name) { result << createEvent(dispMap) }
return result
}
/**
* zwaveEvent( COMMAND_CLASS_METER V3 (0x32) : METER_REPORT )
*
* The Meter Report Command is used to advertise a meter reading.
*
* Action: Raise appropriate type of event (and disp... event) and log an info message.
*
* Note: Fibaro Dimmer 2 supports energy and power only. It will not report current, voltage, or power factor.
*
* cmd attributes:
* Integer deltaTime Time in seconds since last report.
* Short meterType Specifies the type of metering device.
* 0x00 = Unknown
* 0x01 = Electric meter
* 0x02 = Gas meter
* 0x03 = Water meter
* List<Short> meterValue Meter value as an array of bytes.
* Double scaledMeterValue Meter value as a double.
* List<Short> previousMeterValue Previous meter value as an array of bytes.
* Double scaledPreviousMeterValue Previous meter value as a double.
* Short size The size of the array for the meterValue and previousMeterValue.
* Short scale Indicates what unit the sensor uses (dependent on meterType).
* Short precision The decimal precision of the values.
* Short rateType Specifies if it is import or export values to be read.
* 0x01 = Import (consumed)
* 0x02 = Export (produced)
* Boolean scale2 ???
**/
def zwaveEvent(physicalgraph.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")
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
case 2: // Gas meter:
switch (cmd.scale) {
case 0: // Accumulated Gas Volume (m^3):
result << createEvent(name: "fluidVolume", value: cmd.scaledMeterValue, unit: "m^3", displayed: true)
result << createEvent(name: "dispFluidVolume", value: String.format("%.2f",cmd.scaledMeterValue as BigDecimal) + " m^3", displayed: false)
logger("New meter reading: Accumulated Gas Volume: ${cmd.scaledMeterValue} m^3","info")
break
case 1: // Accumulated Gas Volume (ft^3):
result << createEvent(name: "fluidVolume", value: cmd.scaledMeterValue, unit: "ft^3", displayed: true)
result << createEvent(name: "dispFluidVolume", value: String.format("%.2f",cmd.scaledMeterValue as BigDecimal) + " ft^3", displayed: false)
logger("New meter reading: Accumulated Gas Volume: ${cmd.scaledMeterValue} ft^3","info")
break
case 3: // Accumulated Pulse Count:
result << createEvent(name: "pulseCount", value: cmd.scaledMeterValue, unit: "", displayed: true)
logger("New meter reading: Accumulated Gas Pulse Count: ${cmd.scaledMeterValue}","info")
break
default:
logger("zwaveEvent(): Meter Report with unhandled scale: ${cmd}","warn")
break
}
break
case 3: // Water meter:
switch (cmd.scale) {
case 0: // Accumulated Water Volume (m^3):
result << createEvent(name: "fluidVolume", value: cmd.scaledMeterValue, unit: "m^3", displayed: true)
result << createEvent(name: "dispFluidVolume", value: String.format("%.2f",cmd.scaledMeterValue as BigDecimal) + " m^3", displayed: false)
logger("New meter reading: Accumulated Water Volume: ${cmd.scaledMeterValue} m^3","info")
break
case 1: // Accumulated Water Volume (ft^3):
result << createEvent(name: "fluidVolume", value: cmd.scaledMeterValue, unit: "ft^3", displayed: true)
result << createEvent(name: "dispFluidVolume", value: String.format("%.2f",cmd.scaledMeterValue as BigDecimal) + " ft^3", displayed: false)
logger("New meter reading: Accumulated Water Volume: ${cmd.scaledMeterValue} ft^3","info")
break
case 2: // Accumulated Water Volume (US gallons):
result << createEvent(name: "fluidVolume", value: cmd.scaledMeterValue, unit: "gal", displayed: true)
result << createEvent(name: "dispFluidVolume", value: String.format("%.2f",cmd.scaledMeterValue as BigDecimal) + " gal", displayed: false)
logger("New meter reading: Accumulated Water Volume: ${cmd.scaledMeterValue} gal","info")
break
case 3: // Accumulated Pulse Count:
result << createEvent(name: "pulseCount", value: cmd.scaledMeterValue, unit: "", displayed: true)
logger("New meter reading: Accumulated Water Pulse Count: ${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
}
/**
* zwaveEvent( COMMAND_CLASS_CRC16_ENCAP V1 (0x56) : CRC_16_ENCAP )
*
* The CRC-16 Encapsulation Command Class is used to encapsulate a command with an additional CRC-16 checksum
* to ensure integrity of the payload. The purpose for this command class is to ensure a higher integrity level
* of payloads carrying important data.
*
* Action: Extract the encapsulated command and pass to zwaveEvent().
*
* Note: Validation of the checksum is not necessary as this is performed by the hub.
*
* cmd attributes:
* Integer checksum Checksum.
* Short command Command identifier of the embedded command.
* Short commandClass Command Class identifier of the embedded command.
* List<Short> data Embedded command data.
*
* Example: Crc16Encap(checksum: 125, command: 2, commandClass: 50, data: [33, 68, 0, 0, 0, 194, 0, 0, 77])
**/
def zwaveEvent(physicalgraph.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)
}
}
/**
* zwaveEvent( COMMAND_CLASS_DEVICE_RESET_LOCALLY V1 (0x5A) : DEVICE_RESET_LOCALLY_NOTIFICATION )
*
* The Device Reset Locally Notification Command is used to advertise that the device will be reset.
*
* Action: Log a warn message.
**/
def zwaveEvent(physicalgraph.zwave.commands.deviceresetlocallyv1.DeviceResetLocallyNotification cmd) {
logger("zwaveEvent(): Device Reset Locally Notification: ${cmd}","trace")
logger("zwaveEvent(): Device was reset!","warn")
}
/**
* zwaveEvent( COMMAND_CLASS_MULTICHANNEL V4 (0x60) : MULTI_CHANNEL_CMD_ENCAP )
*
* The Multi Channel Command Encapsulation command is used to encapsulate commands. Any command supported by
* a Multi Channel End Point may be encapsulated using this command.
*
* Action: Extract the encapsulated command and pass to the appropriate zwaveEvent() handler.
*
* Note: We only receive these commands from a Dimmer 2 if the hub has been added to one or more association
* groups 2-5, which is not normally needed. The sourceEndPoint attribute will indicate if from S1 or S2, but we
* don't care here, because button presses are handled via SCENE_ACTIVATION_SET commands instead.
*
* cmd attributes:
* Boolean bitAddress Set to true if multicast addressing is used.
* Short command Command identifier of the embedded command.
* Short commandClass Command Class identifier of the embedded command.
* Short destinationEndPoint Destination End Point.
* List<Short> parameter Carries the parameter(s) of the embedded command.
* Short sourceEndPoint Source End Point.
*
* Example: MultiChannelCmdEncap(bitAddress: false, command: 1, commandClass: 32, destinationEndPoint: 0,
* parameter: [0], sourceEndPoint: 1)
**/
def zwaveEvent(physicalgraph.zwave.commands.multichannelv3.MultiChannelCmdEncap cmd) {
logger("zwaveEvent(): Multi Channel Command Encapsulation command received: ${cmd}","trace")
def encapsulatedCommand = cmd.encapsulatedCommand(getCommandClassVersions())
if (!encapsulatedCommand) {
logger("zwaveEvent(): Could not extract command from ${cmd}","error")
} else {
return zwaveEvent(encapsulatedCommand)
}
}
/**
* zwaveEvent( COMMAND_CLASS_CONFIGURATION V1 (0x70) : CONFIGURATION_REPORT )
*
* The Configuration Report Command is used to advertise the actual value of the advertised parameter.
*
* Action: Store the value in the parameter cache, update syncPending, and log an info message.
*
* 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.
*
* cmd attributes:
* List<Short> configurationValue Value of parameter (byte array).
* Short parameterNumber Parameter ID.
* Short size Size of parameter's value (bytes).
*
* Example: ConfigurationReport(configurationValue: [0], parameterNumber: 14, reserved11: 0,
* scaledConfigurationValue: 0, size: 1)
**/
def zwaveEvent(physicalgraph.zwave.commands.configurationv1.ConfigurationReport cmd) {
logger("zwaveEvent(): Configuration Report received: ${cmd}","trace")
state."paramCache${cmd.parameterNumber}" = cmd.scaledConfigurationValue.toInteger()
def paramName = getParamsMd().find( { it.id == cmd.parameterNumber }).name
logger("Parameter #${cmd.parameterNumber} [${paramName}] has value: ${cmd.scaledConfigurationValue}","info")
updateSyncPending()
}
/**
* zwaveEvent( COMMAND_CLASS_NOTIFICATION V3 (0x71) : NOTIFICATION_REPORT )
*
* The Notification Report Command is used to advertise notification information.
*
* Action: Raise appropriate type of event (e.g. fault, tamper, water) and log an info or warn message.
*
* Note: SmartThings does not yet have official capabilities definited for many types of notification. E.g. this
* handler raises 'fault' events, which is not part of any standard capability.
*
* cmd attributes:
* Short event Event Type (see code below).
* List<Short> eventParameter Event Parameter(s) (depends on Event type).
* Short eventParametersLength Length of eventParameter.
* Short notificationStatus The notification reporting status of the device (depends on push or pull model).
* Short notificationType Notification Type (see code below).
* Boolean sequence
* Short v1AlarmLevel Legacy Alarm Level from Alarm CC V1.
* Short v1AlarmType Legacy Alarm Type from Alarm CC V1.
* Short zensorNetSourceNodeId Source node ID
*
* Example: NotificationReport(event: 8, eventParameter: [], eventParametersLength: 0, notificationStatus: 255,
* notificationType: 8, reserved61: 0, sequence: false, v1AlarmLevel: 0, v1AlarmType: 0, zensorNetSourceNodeId: 0)
**/
def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cmd) {
logger("zwaveEvent(): Notification Report received: ${cmd}","trace")
def result = []
switch (cmd.notificationType) {
//case 1: // Smoke Alarm: // Not Implemented yet. Should raise smoke/carbonMonoxide/consumableStatus events etc...
//case 2: // CO Alarm:
//case 3: // CO2 Alarm:
case 4: // Heat Alarm:
switch (cmd.event) {
case 0: // Previous Events cleared:
// Do not send a fault clear event automatically.
logger("Heat Alarm Cleared","info")
break
case 1: // Overheat detected:
case 2: // Overheat detected, Unknown Location:
result << createEvent(name: "fault", value: "overheat", descriptionText: "Overheat detected!", displayed: true)
logger("Overheat detected!","warn")
break
case 3: // Rapid Temperature Rise:
case 4: // Rapid Temperature Rise, Unknown Location:
result << createEvent(name: "fault", value: "temperature", descriptionText: "Rapid temperature rise detected!", displayed: true)
logger("Rapid temperature rise detected!","warn")
break
case 5: // Underheat detected:
case 6: // Underheat detected, Unknown Location:
result << createEvent(name: "fault", value: "underheat", descriptionText: "Underheat detected!", displayed: true)
logger("Underheat detected!","warn")
break
default:
logger("zwaveEvent(): Notification Report recieved with unhandled event: ${cmd}","warn")
break
}
break
//case 5: // Water Alarm: // Not Implemented yet. Should raise water/consumableStatus events etc...
case 8: // Power Management:
switch (cmd.event) {
case 0: // Previous Events cleared:
// Do not send a fault clear event automatically.
logger("Previous Events cleared","info")
break
//case 1: // Mains Connected:
//case 2: // AC Mains Disconnected:
//case 3: // AC Mains Re-connected:
case 4: // Surge:
result << createEvent(name: "fault", value: "surge", descriptionText: "Power surge detected!", displayed: true)
logger("Power surge detected!","warn")
break
case 5: // Voltage Drop:
result << createEvent(name: "fault", value: "voltage", descriptionText: "Voltage drop detected!", displayed: true)
logger("Voltage drop detected!","warn")
break
case 6: // Over-current:
result << createEvent(name: "fault", value: "current", descriptionText: "Over-current detected!", displayed: true)
logger("Over-current detected!","warn")
break
case 7: // Over-Voltage:
result << createEvent(name: "fault", value: "voltage", descriptionText: "Over-voltage detected!", displayed: true)
logger("Over-voltage detected!","warn")
break
case 8: // Overload:
result << createEvent(name: "fault", value: "load", descriptionText: "Overload detected!", displayed: true)
logger("Overload detected!","warn")
break
case 9: // Load Error:
result << createEvent(name: "fault", value: "load", descriptionText: "Load Error detected!", displayed: true)
logger("Load Error detected!","warn")
break
default:
logger("zwaveEvent(): Notification Report recieved with unhandled event: ${cmd}","warn")
break
}
break
case 9: // system:
switch (cmd.event) {
case 0: // Previous Events cleared:
// Do not send a fault clear event automatically.
logger("Previous Events cleared","info")
break
case 1: // Harware Failure:
case 3: // Harware Failure (with manufacturer proprietary failure code):
result << createEvent(name: "fault", value: "hardware", descriptionText: "Hardware failure detected!", displayed: true)
logger("Hardware failure detected!","warn")
break
case 2: // Software Failure:
case 4: // Software Failure (with manufacturer proprietary failure code):
result << createEvent(name: "fault", value: "firmware", descriptionText: "Firmware failure detected!", displayed: true)
logger("Firmware failure detected!","warn")
break
case 6: // Tampering:
result << createEvent(name: "tamper", value: "detected", descriptionText: "Tampering: Product covering removed!", displayed: true)
logger("Tampering: Product covering removed!","warn")
break
default:
logger("zwaveEvent(): Notification Report recieved with unhandled event: ${cmd}","warn")
break
}
break
default:
logger("zwaveEvent(): Notification Report recieved with unhandled notificationType: ${cmd}","warn")
break
}
return result
}
/**
* zwaveEvent( COMMAND_CLASS_MANUFACTURER_SPECIFIC V2 (0x72) : MANUFACTURER_SPECIFIC_REPORT )
*
* Manufacturer-Specific Reports are used to advertise manufacturer-specific information, such as product number
* and serial number.
*
* Action: Publish values as device 'data'. Log a warn message if manufacturerId and/or productId do not
* correspond to Fibaro Dimmer 2.
*
* Example: ManufacturerSpecificReport(manufacturerId: 271, manufacturerName: Fibargroup, productId: 4096,
* productTypeId: 258)
**/
def zwaveEvent(physicalgraph.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 ( 271 != cmd.manufacturerId) logger("Device Manufacturer is not Fibaro. Using this device handler with a different device may damage your device!","warn")
if ( 4096 != cmd.productId) logger("Product ID does not match Fibaro Dimmer 2. 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_POWERLEVEL V1 (0x73) : POWERLEVEL_REPORT )
*
* The Powerlevel Report is used to advertise the current RF transmit power of the device.
*
* Action: Log an info message.
*
* cmd attributes:
* Short powerLevel The current power level indicator value in effect on the node
* Short timeout The time in seconds the node has at Power level before resetting to normal Power level.
*
* Example: PowerlevelReport(powerLevel: 0, timeout: 0)
**/
def zwaveEvent(physicalgraph.zwave.commands.powerlevelv1.PowerlevelReport cmd) {
logger("zwaveEvent(): Powerlevel Report received: ${cmd}","trace")
def power = (cmd.powerLevel > 0) ? "minus${cmd.powerLevel}dBm" : "NormalPower"
logger("Powerlevel Report: Power: ${power}, Timeout: ${cmd.timeout}","info")
}
/**
* zwaveEvent( COMMAND_CLASS_PROTECTION V2 (0x75) : PROTECTION_REPORT )
*
* 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(physicalgraph.zwave.commands.protectionv2.ProtectionReport cmd) {
logger("zwaveEvent(): Protection Report received: ${cmd}","trace")
state.protectLocalCache = cmd.localProtectionState
state.protectRFCache = cmd.rfProtectionState
def lp, rfp = ""
switch(cmd.localProtectionState) {
case 0:
lp = "Unprotected"
break
case 1:
lp = "Protection by sequence"
break
case 2:
lp = "No operation possible"
break
default:
lp = "Unknwon"
break
}
switch(cmd.rfProtectionState) {
case 0:
rfp = "Unprotected"
break
case 1:
rfp = "No RF Control"
break
case 2:
rfp = "No RF Response"
break
default:
rfp = "Unknwon"
break
}
logger("Protection Report: Local Protection: ${lp}, RF Protection: ${rfp}","info")
updateSyncPending()
}
/**
* zwaveEvent( COMMAND_CLASS_FIRMWARE_UPDATE_MD V2 (0x7A) : FirmwareMdReport )
*
* The Firmware Meta Data Report Command is used to advertise the status of the current firmware in the device.
*
* Action: Publish values as device 'data' and log an info message. No check is performed.
*
* cmd attributes:
* Integer checksum Checksum of the firmware image.
* Integer firmwareId Firware ID (this is not the firmware version).
* Integer manufacturerId Manufacturer ID.
*
* Example: FirmwareMdReport(checksum: 50874, firmwareId: 274, manufacturerId: 271)
**/
def zwaveEvent(physicalgraph.zwave.commands.firmwareupdatemdv2.FirmwareMdReport cmd) {
logger("zwaveEvent(): Firmware Metadata Report received: ${cmd}","trace")
// Display as hex strings:
def firmwareIdDisp = String.format("%04X",cmd.firmwareId)
def checksumDisp = String.format("%04X",cmd.checksum)
logger("Firmware Metadata Report: Firmware ID: ${firmwareIdDisp}, Checksum: ${checksumDisp}","info")
updateDataValue("firmwareId","${firmwareIdDisp}")
updateDataValue("firmwareChecksum","${checksumDisp}")
}
/**
* zwaveEvent( COMMAND_CLASS_ASSOCIATION V2 (0x85) : ASSOCIATION_REPORT )
*
* The Association Report command is used to advertise the current destination nodes of a given association group.
*
* Action: Log info message only. Do not cache values as the Fibaro Dimmer 2 uses COMMAND_CLASS_MULTI_CHANNEL_ASSOCIATION.
*
* 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: 4, maxNodesSupported: 5, nodeId: [1], reportsToFollow: 0)
**/
def zwaveEvent(physicalgraph.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.sort().each { hexArray.add(String.format("%02X", it)) };
logger("Association Group ${cmd.groupingIdentifier} contains nodes: ${hexArray} (hexadecimal format)","info")
//updateSyncPending()
}
/**
* zwaveEvent( COMMAND_CLASS_VERSION V1 (0x86) : VERSION_REPORT )
*
* 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. No check is performed.
*
* Note: Device actually supports V2, but SmartThings only supports V1.
*
* 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(physicalgraph.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)
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_MULTI_CHANNEL_ASSOCIATION V2 (0x8E) : ASSOCIATION_REPORT )
*
* The Multi-channel Association Report command is used to advertise the current destinations of a given
* association group (nodes and endpoints).
*
* Action: Store the destinations in the assocGroup cache, update syncPending, and log an info message.
*
* 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: MultiChannelAssociationReport(groupingIdentifier: 2, maxNodesSupported: 8, nodeId: [9,0,1,1,2,3],
* reportsToFollow: 0)
**/
def zwaveEvent(physicalgraph.zwave.commands.multichannelassociationv2.MultiChannelAssociationReport cmd) {
logger("zwaveEvent(): Multi-Channel Association Report received: ${cmd}","trace")
state."assocGroupCache${cmd.groupingIdentifier}" = cmd.nodeId // Must not sort as order is important.
def assocGroupName = getAssocGroupsMd().find( { it.id == cmd.groupingIdentifier} ).name
// Display to user in hex format (same as IDE):
def hexArray = []
cmd.nodeId.each { hexArray.add(String.format("%02X", it)) };
logger("Association Group #${cmd.groupingIdentifier} [${assocGroupName}] contains destinations: ${hexArray}","info")
updateSyncPending()
}
/**
* zwaveEvent( COMMAND_CLASS_SECURITY V1 (0x98) : SECURITY_COMMANDS_SUPPORTED_REPORT )
*
* The Security Commands Supported Report command advertises which command classes are supported using security
* encapsulation.
*
* Action: Store the list of supported command classes in state.secureCommandClasses. Log info message.
*
* Example: SecurityCommandsSupportedReport(commandClassControl: [43],
* commandClassSupport: [32, 90, 133, 38, 142, 96, 112, 117, 39], reportsToFollow: 0)
**/
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityCommandsSupportedReport cmd) {
logger("zwaveEvent(): Security Commands Supported Report received: ${cmd}","trace")
state.secureCommandClasses = cmd.commandClassSupport
// Display to user in hex format (same as IDE):
def hexArray = []
cmd.commandClassSupport.sort().each { hexArray.add(String.format("0x%02X", it)) };
logger("Security Commands Supported: ${hexArray}","info")
}
/**
* zwaveEvent( COMMAND_CLASS_SECURITY V1 (0x98) : SECURITY_MESSAGE_ENCAPSULATION )
*
* The Security Message Encapsulation command is used to encapsulate Z-Wave commands using AES-128.
*
* Action: Extract the encapsulated command and pass to the appropriate zwaveEvent() handler.
*
* cmd attributes:
* List<Short> commandByte Parameters of the encapsulated command.
* Short commandClassIdentifier Command Class ID of the encapsulated command.
* Short commandIdentifier Command ID of the encapsulated command.
* Boolean secondFrame Indicates if first or second frame.
* Short sequenceCounter
* Boolean sequenced True if the command is transmitted using multiple frames.
**/
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
logger("zwaveEvent(): Security Encapsulated Command received: ${cmd}","trace")
def encapsulatedCommand = cmd.encapsulatedCommand(getCommandClassVersions())
if (encapsulatedCommand) {
return zwaveEvent(encapsulatedCommand)
} else {
logger("zwaveEvent(): Unable to extract security encapsulated command from: ${cmd}","error")
}
}
/**
* zwaveEvent( DEFAULT CATCHALL )
*
* Called for all commands that aren't handled above.
**/
def zwaveEvent(physicalgraph.zwave.Command cmd) {
logger("zwaveEvent(): No handler for command: ${cmd}","error")
}
/*****************************************************************************************************************
* Capability-related Commands:
*****************************************************************************************************************/
/**
* on() [Capability: Switch]
*
* Turn the dimmer on.
**/
def on() {
logger("on(): Turning dimmer on.","info")
def cmds = []
cmds << zwave.basicV1.basicSet(value: 0xFF)
if (state.proactiveReports) cmds << zwave.switchMultilevelV1.switchMultilevelGet()
sendSecureSequence(cmds,5000)
}
/**
* off() [Capability: Switch]
*
* Turn the dimmer off.
**/
def off() {
logger("off(): Turning dimmer off.","info")
def cmds = []
cmds << zwave.basicV1.basicSet(value: 0x00)
if (state.proactiveReports) cmds << zwave.switchMultilevelV1.switchMultilevelGet()
sendSecureSequence(cmds,5000)
}
/**
* setLevel() [Capability: Switch Level]
*
* Set the dimmer level.
*
* Parameters:
* level Target level (0-100%).
**/
def setLevel(level) {
logger("setLevel(${level})","trace")
if (level < 0) level = 0
if (level > 100) level = 100
logger("Setting dimmer to ${level}%","info")
// Clear nightmodePendingLevel as it's been overridden.
state.nightmodePendingLevel = 0
def cmds = []
cmds << zwave.basicV1.basicSet(value: Math.round(level * 99 / 100 )) // Convert from 0-100 to 0-99.
if (state.proactiveReports) cmds << zwave.switchMultilevelV1.switchMultilevelGet()
sendSecureSequence(cmds,5000)
}
/**
* refresh() [Capability: Refresh]
*
* Request switchMultilevel, energy, and power reports.
* Also, force a configuration sync.
**/
def refresh() {
logger("refresh()","trace")
def cmds = []
cmds << zwave.switchMultilevelV1.switchMultilevelGet()
cmds << zwave.meterV3.meterGet(scale: 0)
cmds << zwave.meterV3.meterGet(scale: 2)
sendSecureSequence(cmds,200)
sync()
}
/**
* poll() [Capability: Polling]
*
* Calls refresh().
**/
def poll() {
logger("poll()","trace")
refresh()
}
/*****************************************************************************************************************
* Custom Commands:
*****************************************************************************************************************/
/**
* reset()
*
* Calls 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.
*
* Does not return a list of commands, it sends them immediately using sendSecureSequence(). This is required if
* triggered by schedule().
**/
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")
sendSecureSequence([
zwave.meterV3.meterReset(),
zwave.meterV3.meterGet(scale: 0)
],400)
}
/**
* enableNightmode(level)
*
* Force switch-on illuminance level.
*
* Does not return a list of commands, it sends them immediately using sendSecureSequence(). This is required if
* triggered by schedule().
**/
def enableNightmode(level=-1) {
logger("enableNightmode(${level})","info")
// Clean level value:
if (level == -1) level = settings.configNightmodeLevel.toInteger()
if (level > 100) level = 100
if (level < 1) level = 1
// If nightmode is not already active, save last active level and current value of param19, so they can be restored when nightmode is stopped:
if (!state.nightmodeActive) {
state.nightmodePriorLevel = state.lastActiveLevel
logger("enableNightmode(): Saved previous active level: ${state.nightmodePriorLevel}","info")
if (!state.paramCache19) state.paramCache19 = 0
state.nightmodePriorParam19 = state.paramCache19.toInteger()
logger("enableNightmode(): Saved previous param19: ${state.paramCache19}","info")
}
// If the dimmer is already on, and configNightmodeForce is enabled, then adjust the level immediately:
if (("on" == device.latestValue("switch")) & ("true" == configNightmodeForce)) sendSecureSequence([zwave.basicV1.basicSet(value: Math.round(level * 99 / 100 ))])
state.nightmodeActive = true
sendEvent(name: "nightmode", value: "Enabled", descriptionText: "Nightmode Enabled", isStateChange: true)
// Update parameter #19 for force next switch-on level:
state.paramTarget19 = level.toInteger()
sync()
}
/**
* disableNightmode()
*
* Stop nightmode and restore previous values.
*
* Does not return a list of commands, it sends them immediately using sendSecureSequence(). This is required if
* triggered by schedule().
**/
def disableNightmode() {
logger("disableNightmode()","info")
// If nightmode is active, restore param19:
if (state.nightmodeActive) {
logger("disableNightmode(): Restoring previous value of param19 to: ${state.nightmodePriorParam19}","debug")
state.paramTarget19 = state.nightmodePriorParam19
sync()
if (state.nightmodePriorLevel > 0) {
if (("on" == device.latestValue("switch")) & ("true" == configNightmodeForce)) {
// Dimmer is already on and configNightmodeForce is enabled, so adjust the level immediately:
logger("disableNightmode(): Restoring level to: ${state.nightmodePriorLevel}","debug")
sendSecureSequence([zwave.basicV1.basicSet(value: Math.round(state.nightmodePriorLevel.toInteger() * 99 / 100 ))])
} else if (0 == state.nightmodePriorParam19) {
// Dimmer is off (or configNightmodeForce is not enabled), so need to set a flag to restore the level after it's switched on again, but only if param19 is zero.
logger("disableNightmode(): Setting flag to restore level at next switch-on: ${state.nightmodePriorLevel}","debug")
state.nightmodePendingLevel = state.nightmodePriorLevel
}
}
}
state.nightmodeActive = false
sendEvent(name: "nightmode", value: "Disabled", descriptionText: "Nightmode Disabled", isStateChange: true)
}
/**
* toggleNightmode()
**/
def toggleNightmode() {
logger("toggleNightmode()","trace")
if (state.nightmodeActive) {
disableNightmode()
}
else {
enableNightmode(configNightmodeLevel)
}
}
/**
* clearFault()
*
* Clear all active faults.
**/
def clearFault() {
logger("clearFault(): Clearing active faults.","info")
sendEvent(name: "fault", value: "clear", descriptionText: "Fault cleared", displayed: true)
}
/*****************************************************************************************************************
* SmartThings System Commands:
*****************************************************************************************************************/
/**
* installed()
*
* Runs when the device is first installed.
*
* Action: Set initial values for internal state, and request a full configuration report from the device.
**/
def installed() {
log.trace "installed()"
configure()
state.installedAt = now()
state.energyLastReset = new Date().format("YYYY/MM/dd \n HH:mm:ss", location.timeZone)
state.loggingLevelIDE = 3
state.loggingLevelDevice = 2
state.protectLocalTarget = 0
state.protectRFTarget = 0
sendEvent(name: "fault", value: "clear", descriptionText: "Fault cleared", displayed: false)
refreshConfig()
}
/**
* updated()
*
* Runs when the user hits "Done" from Settings page.
*
* Action: Process new settings, sync parameters and association group members with the physical device. Request
* Firmware Metadata, Manufacturer-Specific, and Version reports.
*
* Note: Weirdly, update() seems to be called twice. So execution is aborted if there was a previous execution
* within two seconds. See: https://community.smartthings.com/t/updated-being-called-twice/62912
**/
def updated() {
logger("updated()","trace")
configure()
def cmds = []
if (!state.updatedLastRanAt || now() >= state.updatedLastRanAt + 2000) {
state.updatedLastRanAt = now()
// Update internal state:
state.loggingLevelIDE = settings.configLoggingLevelIDE.toInteger()
state.loggingLevelDevice = settings.configLoggingLevelDevice.toInteger()
state.syncAll = ("true" == settings.configSyncAll)
state.proactiveReports = ("true" == settings.configProactiveReports)
// Manage Schedules:
manageSchedules()
// Update Parameter target values:
getParamsMd().findAll( {!it.readonly} ).each { // Exclude readonly parameters.
state."paramTarget${it.id}" = settings."configParam${it.id}"?.toInteger()
}
// Check if auto-calibration is being forced. If so, must ignore target values for P1/2/30:
if (state.paramTarget13 > 0) {
state.paramCache13 = null // Remove cached value to force sync of P13:
logger("Auto-calibration is being forced.","info")
if (state.paramTarget1 != null) logger("Auto-calibration is being forced, but a value has been " +
"provided for parameter #1. This will be ignored! Check Live Logging for the auto-calibrated " +
"value shortly.","warn")
if (state.paramTarget2 != null) logger("Auto-calibration is being forced, but a value has been " +
"provided for parameter #2. This will be ignored! Check Live Logging for the auto-calibrated " +
"value shortly.","warn")
if (state.paramTarget30 != null) logger("Auto-calibration is being forced, but a value has been " +
"provided for parameter #30. This will be ignored! Check Live Logging for the auto-calibrated " +
"value shortly.","warn")
state.paramTarget1 = null
state.paramTarget2 = null
state.paramTarget30 = null
}
// Update Assoc Group target values:
state.assocGroupTarget1 = [ zwaveHubNodeId ] // Assoc Group #1 is Lifeline and will contain controller only.
getAssocGroupsMd().findAll( { it.id != 1} ).each {
state."assocGroupTarget${it.id}" = parseAssocGroupInput(settings."configAssocGroup${it.id}", it.maxNodes)
}
// Update Protection target values:
state.protectLocalTarget = settings.configProtectLocal.toInteger()
state.protectRFTarget = settings.configProtectRF.toInteger()
// Sync configuration with phyiscal device:
sync(state.syncAll)
// Set target for parameter #13 [Force Auto-calibration] back to 0 [Readout].
// Sync will now only complete when auto-calibration has completed:
state.paramTarget13 = 0
// Request device medadata (this just seems the best place to do it):
cmds << zwave.firmwareUpdateMdV2.firmwareMdGet()
cmds << zwave.manufacturerSpecificV2.manufacturerSpecificGet()
cmds << zwave.powerlevelV1.powerlevelGet()
cmds << zwave.versionV1.versionGet()
return response(secureSequence(cmds))
}
else {
logger("updated(): Ran within last 2 seconds so aborting.","debug")
}
}
/*****************************************************************************************************************
* Private Helper Functions:
*****************************************************************************************************************/
/**
* 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
}
}
/**
* parseAssocGroupInput(string, maxNodes)
*
* Converts a comma-delimited string of destinations (nodes and endpoints) into an array suitable for passing to
* multiChannelAssociationSet(). All numbers are interpreted as hexadecimal. Anything that's not a valid node or
* endpoint is discarded (warn). If the list has more than maxNodes, the extras are discarded (warn).
*
* Example input strings:
* "9,A1" = Nodes: 9 & 161 (no multi-channel endpoints) => Output: [9, 161]
* "7,8:1,8:2" = Nodes: 7, Endpoints: Node8:endpoint1 & node8:endpoint2 => Output: [7, 0, 8, 1, 8, 2]
*/
private parseAssocGroupInput(string, maxNodes) {
logger("parseAssocGroupInput(): Parsing Association Group Nodes: ${string}","trace")
// First split into nodes and endpoints. Count valid entries as we go.
if (string) {
def nodeList = string.split(',')
def nodes = []
def endpoints = []
def count = 0
nodeList = nodeList.each { node ->
node = node.trim()
if ( count >= maxNodes) {
logger("parseAssocGroupInput(): Number of nodes and endpoints is greater than ${maxNodes}! The following node was discarded: ${node}","warn")
}
else if (node.matches("\\p{XDigit}+")) { // There's only hexadecimal digits = nodeId
def nodeId = Integer.parseInt(node,16) // Parse as hex
if ( (nodeId > 0) & (nodeId < 256) ) { // It's a valid nodeId
nodes << nodeId
count++
}
else {
logger("parseAssocGroupInput(): Invalid nodeId: ${node}","warn")
}
}
else if (node.matches("\\p{XDigit}+:\\p{XDigit}+")) { // endpoint e.g. "0A:2"
def endpoint = node.split(":")
def nodeId = Integer.parseInt(endpoint[0],16) // Parse as hex
def endpointId = Integer.parseInt(endpoint[1],16) // Parse as hex
if ( (nodeId > 0) & (nodeId < 256) & (endpointId > 0) & (endpointId < 256) ) { // It's a valid endpoint
endpoints.addAll([nodeId,endpointId])
count++
}
else {
logger("parseAssocGroupInput(): Invalid endpoint: ${node}","warn")
}
}
else {
logger("parseAssocGroupInput(): Invalid nodeId: ${node}","warn")
}
}
return (endpoints) ? nodes + [0] + endpoints : nodes
}
else {
return []
}
}
/**
* sync()
*
* Manages synchronisation of parameters, association groups, and protection state 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 sendSecureSequence(). This is required if
* 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
}
getParamsMd().findAll( {!it.readonly} ).each { // Exclude readonly 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}: Destinations: ${targetNodesHex}","info")
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)
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++
}
sendEvent(name: "syncPending", value: syncPending, displayed: false)
sendSecureSequence(cmds,1000) // Need a delay of at least 1000ms.
}
/**
* updateSyncPending()
*
* Updates syncPending attribute, which advertises remaining number of sync operations.
**/
private updateSyncPending() {
def syncPending = 0
getParamsMd().findAll( {!it.readonly} ).each { // Exclude readonly 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++
}
logger("updateSyncPending(): syncPending: ${syncPending}", "debug")
if ((syncPending == 0) & (device.latestValue("syncPending") > 0)) logger("Sync Complete.", "info")
sendEvent(name: "syncPending", value: syncPending, displayed: false)
}
/**
* refreshConfig()
*
* Request configuration reports from the physical device: [ Configuration, Association, Protection,
* SecuritySupportedCommands, Powerlevel, Manufacturer-Specific, Firmware Metadata, Version, etc. ]
*
* Really only needed at installation or when debugging, as sync will request the necessary reports when the
* configuration is changed.
*/
private refreshConfig() {
logger("refreshConfig()","trace")
def cmds = []
getParamsMd().each { cmds << zwave.configurationV1.configurationGet(parameterNumber: it.id) }
getAssocGroupsMd().each { cmds << zwave.multiChannelAssociationV2.multiChannelAssociationGet(groupingIdentifier: it.id) }
cmds << zwave.protectionV2.protectionGet()
cmds << zwave.securityV1.securityCommandsSupportedGet()
cmds << zwave.manufacturerSpecificV2.manufacturerSpecificGet()
cmds << zwave.firmwareUpdateMdV2.firmwareMdGet()
cmds << zwave.versionV1.versionGet()
cmds << zwave.powerlevelV1.powerlevelGet()
sendSecureSequence(cmds, 1000) // Delay must be at least 1000 to reliabilty get all results processed.
}
/**
* secure(cmd)
*
* Secures and formats a command using securityMessageEncapsulation.
*
* Note: All commands are secured, there is little benefit to not securing commands that are not in
* state.secureCommandClasses.
**/
private secure(physicalgraph.zwave.Command cmd) {
//if ( state.secureCommandClasses.contains(cmd.commandClassId.toInteger()) ) {...
return zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
}
/**
* secureSequence()
*
* Secure an array of commands. Returns a list of formatted commands.
**/
private secureSequence(commands, delay = 200) {
return delayBetween(commands.collect{ secure(it) }, delay)
}
/**
* sendSecureSequence()
*
* Secure an array of commands and send them using sendHubCommand.
**/
private sendSecureSequence(commands, delay = 200) {
sendHubCommand(commands.collect{ response(secure(it)) }, delay)
}
/**
* 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.
**/
private generatePrefsAssocGroups() {
section {
input (
type: "paragraph",
element: "paragraph",
title: "ASSOCIATION GROUPS:",
description: "Association groups enable the dimmer 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.id != 1} ).each { // Don't show AssocGroup1 (Lifeline).
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
)
}
}
}
/**
* manageSchedules()
*
* Schedules/unschedules Nightmode.
**/
private manageSchedules() {
logger("manageSchedules()","trace")
if (configNightmodeStartTime) {
schedule(configNightmodeStartTime, enableNightmode)
logger("manageSchedules(): Nightmode scheduled to start at ${configNightmodeStartTime}","debug")
} else {
try {
unschedule("enableNightmode")
}
catch(e) {
// Unschedule failed
}
}
if (configNightmodeStopTime) {
schedule(configNightmodeStopTime, disableNightmode)
logger("manageSchedules(): Nightmode scheduled to stop at ${configNightmodeStopTime}","debug")
} else {
try {
unschedule("disableNightmode")
}
catch(e) {
// Unschedule failed
}
}
}
/**
* test()
*
* Temp testing method. Called from 'test' tile.
**/
private test() {
logger("test()","trace")
def cmds = []
if (cmds) return secureSequence(cmds,200)
}
/*****************************************************************************************************************
* Static Matadata Functions:
*
* These functions encapsulate metadata about the device. Mostly obtained from:
* Z-wave Alliance Reference for Fibaro Dimmer 2: http://products.z-wavealliance.org/products/1729
*****************************************************************************************************************/
/**
* 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/1729/classes
**/
private getCommandClassVersions() {
return [0x20: 1, // Basic V1
0x22: 1, // Application Status V1
0x26: 3, // Switch Multilevel V3
0x27: 1, // Switch All V1
0x2B: 1, // Scene Activation V1
0x31: 4, // Sensor Multilevel V4
0x32: 3, // Meter V3
0x56: 1, // CRC16 Encapsulation V1
0x59: 1, // Association Group Information V1 (Not handled, as no need)
0x5A: 1, // Device Reset Locally V1
//0x5E: 2, // Z-Wave Plus Info V2 (Not supported by SmartThings)
0x60: 3, // Multi Channel V4 (Device supports V4, but SmartThings only supports V3)
0x70: 1, // Configuration V1
0x71: 3, // Notification V5 ((Device supports V5, but SmartThings only supports V3)
0x72: 2, // Manufacturer Specific V2
0x73: 1, // Powerlevel V1
0x75: 2, // Protection V2
0x7A: 2, // Firmware Update MD V3 (Device supports V3, but SmartThings only supports V2)
0x85: 2, // Association V2
0x86: 1, // Version V2 (Device supports V2, but SmartThings only supports V1)
0x8E: 2, // Multi Channel Association V3 (Device supports V3, but SmartThings only supports V2)
0x98: 1 // Security V1
]
}
/**
* getParamsMd()
*
* Returns device parameters metadata. Used by sync(), updateSyncPending(), and generatePrefsParams().
*
* Reference: http://products.z-wavealliance.org/products/1729/configs
**/
private getParamsMd() {
return [
[id: 1, size: 1, type: "number", range: "1..98", defaultValue: 1, required: false, readonly: false,
name: "Minimum Brightness Level",
description: "Set automatically during the calibration process, but can be changed afterwards.\n" +
"Values: 1-98 = Brightness level (%)"],
[id: 2, size: 1, type: "number", range: "2..99", defaultValue: 99, required: false, readonly: false,
name: "Maximum Brightness Level",
description: "Set automatically during the calibration process, but can be changed afterwards.\n" +
"Values: 2-99 = Brightness level (%)"],
[id: 3, size: 1, type: "number", range: "1..99", defaultValue: 1, required: false, readonly: false,
name: "Incandescence Level of CFLs",
description : "The Dimmer 2 will set to this value after first switch on. It is required for warming up " +
"and switching dimmable compact fluorescent lamps and certain types of light sources.\n" +
"Values: 1-99 = Brightness level (%)"],
[id: 4, size: 2, type: "number", range: "0..255", defaultValue: 0, required: false, readonly: false,
name: "Incandescence Time of CFLs",
description : "The time required for switching compact fluorescent lamps and certain types of light sources.\n" +
"Values:\n0 = Function Disabled\n1-255 = 0.1-25.5s in 0.1s steps"],
[id: 5, size: 1, type: "number", range: "1..99", defaultValue : 1, required: false, readonly: false,
name: "Dimming Step Size (Auto)",
description : "The percentage value of a dimming step during automatic control.\n" +
"Values: 1-99 = Dimming step (%)"],
[id: 6, size: 2, type: "number", range: "0..255", defaultValue: 1, required: false, readonly: false,
name: "Dimming Step Time (Auto)",
description : "The time of a single dimming step during automatic control.\n" +
"Values: 0-255 = 0-2.55s, in 10ms steps"],
[id: 7, size: 1, type: "number", range: "1..99", defaultValue: 1, required: false, readonly: false,
name: "Dimming Step Size (Manual)",
description : "The percentage value of a dimming step during manual control.\n" +
"Values: 1-99 = Dimming step (%)"],
[id: 8, size: 2, type: "number", range: "0..255", defaultValue: 5, required: false, readonly: false,
name: "Dimming Step Time (Manual)",
description : "The time of a single dimming step during manual control.\n" +
"Values: 0-255 = 0-2.55s, in 10ms steps"],
[id: 9, size: 1, type: "enum", defaultValue: "1", required: false, readonly: false,
name: "State After Power Failure",
description : "Dimmer state to restore after a power failure.",
options: ["0" : "0: Off", "1" : "1: Restore Previous State"] ],
[id: 10, size: 2, type: "number", range: "0..32767", defaultValue: 0, required: false, readonly: false,
name: "Timer Functionality (Auto-off)",
description : "Automatically switch off the device after a specified time.\n" +
"Values:\n0 = Function Disabled\n1-32767 = time in seconds"],
[id: 11, size: 2, type: "enum", defaultValue: "255", required: false, readonly: false,
name: "ALL ON/ALL OFF Function",
description : "Response to SWITCH_ALL_SET commands.",
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"] ],
[id: 13, size: 1, type: "enum", defaultValue: "0", required: false, readonly: false,
name: "Force Auto-calibration",
description : "During calibration this parameter is set to 1 or 2 and switched to 0 upon completion.",
options: ["0" : "0: Readout",
"1" : "1: Force auto-calibration WITHOUT Fibaro Bypass 2",
"2" : "2: Force auto-calibration WITH Fibaro Bypass 2"] ],
[id: 14, size: 1, type: "readonly", readonly: true,
name: "Auto-calibration Status",
description : "Read-Only: Indicates if dimmer is using auto-calibration (1) or manual (0) settings."],
[id: 15, size: 1, type: "number", range: "0..99", defaultValue: 30, required: false, readonly: false,
name: "Burnt Out Bulb Detection",
description : "Power variation, compared to standard power consumption (measured during calibration), " +
"to be interpreted as load error/burnt out bulb.\n" +
"Values:\n0 = Function Disabled\n1-99 = Power variation (%)"],
[id: 16, size: 2, type: "number", range: "0..255", defaultValue: 5, required: false, readonly: false,
name: "Time Delay for Burnt Out Bulb/Overload Detection",
description : "Time delay (in seconds) for LOAD ERROR or OVERLOAD detection.\n" +
"Values:\n0 = Detection Disabled\n1-255 = Time delay (s)"],
[id: 19, size: 1, type: "number", range: "0..99", defaultValue: 0, required: false, readonly: false,
name: "Forced Switch-on Brightness Level",
description : "Switching on the dimmer will always set this brightness level.\n" +
"Note, the Nightmode feature can be used to change this parameter on a schedule.\n" +
"Values:\n0 = Function Disabled\n1-99 = Brightness level (%)"],
[id: 20, size: 1, type: "enum", defaultValue: "0", required: false, readonly: false,
name: "Switch Type",
description : "Physical switch type: momentary, toggle, or roller blind (S1 to brighten, S2 to dim).",
options: ["0" : "0: Momentary Switch",
"1" : "1: Toggle Switch",
"2" : "2: Roller Blind Switch"] ],
[id: 21, size: 1, type: "enum", defaultValue: "0", required: false, readonly: false,
name: "Value Sent to Associated Devices on Single Click",
description : "0xFF will set associated devices to their last-saved state. Current Level will " +
"synchronise the state of all devices with this dimmer.",
options: ["0" : "0: 0xFF",
"1" : "1: Current Level"] ],
[id: 22, size: 1, type: "enum", defaultValue: "0", required: false, readonly: false,
name: "Assign Toggle Switch Status to Device Status",
description : "By default, each change of toggle switch position results in an on/off action " +
"regardless the physical connection of contacts.",
options: ["0" : "0: Change on Every Switch State Change",
"1" : "1: Synchronise with Switch State"] ],
[id: 23, size: 1, type: "enum", defaultValue: "1", required: false, readonly: false,
name: "Double-click sets Max Brightness",
description : "Double-clicking will set brightness level to maximum.",
options: ["0" : "0: Double-click DISABLED",
"1" : "1: Double-click ENABLED"] ],
[id: 24, size: 1, type: "number", range: "0..31", defaultValue: 0, required: false, readonly: false,
name: "Command Frames Sent to 2nd and 3rd Association Groups (S1 Associations)",
description : "Determines which actions will not result in sending frames to association groups.\n" +
"Values (add together):\n" +
"0 = All actions sent to association groups\n" +
"1 = Do not send when switching ON (single click)\n" +
"2 = Do not send when switching OFF (single click)\n" +
"4 = Do not send when changing dimming level (holding and releasing)\n" +
"8 = Do not send on double click\n" +
"16 = Send 0xFF value on double click"],
[id: 25, size: 1, type: "number", range: "0..31", defaultValue: 0, required: false, readonly: false,
name: "Command Frames Sent to 4th and 5th Association Groups (S2 Associations)",
description : "Determines which actions will not result in sending frames to association groups.\n" +
"Values (add together):\n" +
"0 = All actions sent to association groups\n" +
"1 = Do not send when switching ON (single click)\n" +
"2 = Do not send when switching OFF (single click)\n" +
"4 = Do not send when changing dimming level (holding and releasing)\n" +
"8 = Do not send on double click\n" +
"16 = Send 0xFF value on double click"],
[id: 26, size: 1, type: "enum", defaultValue: "0", required: false, readonly: false,
name: "3-way Switch Function",
description : "Switch S2 also controls the dimmer when in 3-way switch mode. " +
"Function is disabled if parameter #20 is set to 2 (roller blind switch).",
options: ["0" : "0: 3-way switch function for S2 DISABLED",
"1" : "1: 3-way switch function for S2 ENABLED"] ],
[id: 27, size: 1, type: "number", range: "0..15", defaultValue: 15, required: false, readonly: false,
name: "Association Group Security Mode",
description : "Defines if commands sent to association groups are secure or non-secure.\n" +
"Values (add together):\n" +
"0 = all groups (2-5) sent as non-secure\n" +
"1 = 2nd group sent as secure\n" +
"2 = 3rd group sent as secure\n" +
"4 = 4th group sent as secure\n" +
"8 = 5th group sent as secure\n" +
"E.g. 15 = all groups (2-5) sent as secure."],
[id: 28, size: 1, type: "enum", defaultValue: "0", required: false, readonly: false,
name: "Scene Activation",
description : "Defines if SCENE_ACTIVATION_SET commands are sent.",
options: ["0" : "0: Function DISABLED",
"1" : "1: Function ENABLED"] ],
[id: 29, size: 1, type: "enum", defaultValue: "0", required: false, readonly: false,
name: "Swap S1 and S2",
description : "Swap the roles of S1 and S2 without changes to physical wiring.",
options: ["0" : "0: Standard Mode",
"1" : "1: S1 operates as S2, S2 operates as S1"] ],
[id: 30, size: 1, type: "enum", defaultValue: "2", required: false, readonly: false,
name: "Load Control Mode",
description : "Override the dimmer mode (i.e. leading or trailing edge).",
options: ["0" : "0: Force leading edge mode",
"1" : "1: Force trailing edge mode",
"2" : "2: Automatic (based on auto-calibration)"] ],
[id: 31, size: 1, type: "readonly", readonly: true,
name: "Load Control Mode Recognised During Auto-calibration",
description : "Read-Only: Indicates the load control mode recognised during auto-calibration. Leading Edge (0) / trailing Edge (1)."],
[id: 32, size: 1, type: "enum", defaultValue: "2", required: false, readonly: false,
name: "On/Off Mode",
description : "This mode is necessary when connecting non-dimmable light sources.",
options: ["0" : "0: On/Off mode DISABLED (dimming is possible)",
"1" : "1: On/Off mode ENABLED (dimming not possible)",
"2" : "2: Automatic (based on auto-calibration)"] ],
[id: 33, size: 1, type: "readonly", readonly: true,
name: "Dimmability of the Load",
description : "Read-Only: Indicates the dimmability of the load recognised during auto-calibration. Dimmable (0) / Non-dimmable (1)."],
[id: 34, size: 1, type: "enum", defaultValue: "1", required: false, readonly: false,
name: "Soft-Start",
description : "Time required to warm up the filament of halogen bulbs.",
options: ["0" : "0: No soft-start",
"1" : "1: Short soft-start (0.1s)",
"2" : "2: Long soft-start (0.5s)"] ],
[id: 35, size: 1, type: "enum", defaultValue: "1", required: false, readonly: false,
name: "Auto-calibration",
description : "Determines when auto-calibration is triggered.",
options: ["0" : "0: No auto-calibration",
"1" : "1: Auto-calibration after first power on only",
"2" : "2: Auto-calibration after each power on",
"3" : "3: Auto-calibration after first power on and after each LOAD ERROR",
"4" : "4: Auto-calibration after each power on and after each LOAD ERROR"] ],
[id: 37, size: 1, type: "enum", defaultValue: "1", required: false, readonly: false,
name: "Behaviour After OVERCURRENT or SURGE",
description : "The dimmer will turn off when a surge or overcurrent is detected. " +
"By default, the device performs three attempts to turn on the load.",
options: ["0" : "0: Device disabled until command or external switch",
"1" : "1: Three attempts to turn on the load"] ],
[id: 39, size: 2, type: "number", range: "0..350", defaultValue : 250, required: false, readonly: false,
name: "Power Limit - OVERLOAD",
description : "Reaching the defined value will result in turning off the load. " +
"Additional apparent power limit of 350VA is active by default.\n" +
"Values:\n0 = Function Disabled\n1-350 = Power limit (W)"],
[id: 40, size: 1, type: "enum", defaultValue: "3", required: false, readonly: false,
name: "Response to General Purpose Alarm",
description : "",
options: ["0" : "0: No reaction",
"1" : "1: Turn on the load",
"2" : "2: Turn off the load",
"3" : "3: Load blinking"] ],
[id: 41, size: 1, type: "enum", defaultValue: "2", required: false, readonly: false,
name: "Response to Water Flooding Alarm",
description : "",
options: ["0" : "0: No reaction",
"1" : "1: Turn on the load",
"2" : "2: Turn off the load",
"3" : "3: Load blinking"] ],
[id: 42, size: 1, type: "enum", defaultValue: "3", required: false, readonly: false,
name: "Response to Smoke, CO, or CO2 Alarm",
description : "",
options: ["0" : "0: No reaction",
"1" : "1: Turn on the load",
"2" : "2: Turn off the load",
"3" : "3: Load blinking"] ],
[id: 43, size: 1, type: "enum", defaultValue: "1", required: false, readonly: false,
name: " Response to Temperature Alarm",
description : "",
options: ["0" : "0: No reaction",
"1" : "1: Turn on the load",
"2" : "2: Turn off the load",
"3" : "3: Load blinking"] ],
[id: 44, size: 2, type: "number", range: "1..32767", defaultValue : 600, required: false, readonly: false,
name: "Time of Alarm State",
description : "Values: 1-32767 = Time (s)"],
[id: 45, size: 1, type: "enum", defaultValue: "1", required: false, readonly: false,
name: "OVERLOAD Alarm Report",
description : "Power consumption above Power Limit.",
options: ["0" : "0: No reaction",
"1" : "1: Send an alarm frame"] ],
[id: 46, size: 1, type: "enum", defaultValue: "1", required: false, readonly: false,
name: "LOAD ERROR Alarm Report",
description : "No load, load failure, or burnt out bulb.",
options: ["0" : "0: No reaction",
"1" : "1: Send an alarm frame"] ],
[id: 47, size: 1, type: "enum", defaultValue: "1", required: false, readonly: false,
name: "OVERCURRENT Alarm Report",
description : "Short circuit, or burnt out bulb causing overcurrent",
options: ["0" : "0: No reaction",
"1" : "1: Send an alarm frame"] ],
[id: 48, size: 1, type: "enum", defaultValue: "1", required: false, readonly: false,
name: "SURGE Alarm Report",
description : "",
options: ["0" : "0: No reaction",
"1" : "1: Send an alarm frame"] ],
[id: 49, size: 1, type: "enum", defaultValue: "1", required: false, readonly: false,
name: "OVERHEAT and VOLTAGE DROP Alarm Report",
description : "Critical temperature, or low voltage.",
options: ["0" : "0: No reaction",
"1" : "1: Send an alarm frame"] ],
[id: 50, size: 1, type: "number", range: "0..100", defaultValue : 10, required: false, readonly: false,
name: "Power Reports Threshold",
description : "Power level change that will result in a new power report being sent.\n" +
"Values:\n0 = Reports disabled\n1-100 = % change from previous report"],
[id: 52, size: 2, type: "number", range: "0..32767", defaultValue : 3600, required: false, readonly: false,
name: "Reporting Period",
description : "The time period between consecutive power and energy reports.\n" +
"Values:\n0 = Reports disabled\n1-32767 = Time period (s)"],
[id: 53, size: 2, type: "number", range: "0..255", defaultValue : 10, required: false, readonly: false,
name: "Energy Reports Threshold",
description : "Energy level change that will result in a new energy report being sent.\n" +
"Values:\n0 = Reports disabled,\n1-255 = 0.01-2.55 kWh"],
[id: 54, size: 1, type: "enum", defaultValue: "0", required: false, readonly: false,
name: "Self-measurement",
description : "Include power and energy consumed by the device itself in reports.",
options: ["0" : "0: Self-measurement DISABLED",
"1" : "1: Self-measurement ENABLED"] ],
[id: 58, size: 1, type: "enum", defaultValue: "0", required: false, readonly: false,
name: "Method of Calculating Active Power",
description : "Useful in 2-wire configurations with non-resistive loads.",
options: ["0" : "0: Standard algorithm",
"1" : "1: Based on calibration data",
"2" : "2: Based on control angle"] ],
[id: 59, size: 2, type: "number", range: "0..500", defaultValue : 0, required: false, readonly: false,
name: "Approximated Power at Max Brightness",
description : "Determines the approximate value of the power that will be reported by the device at " +
"it's maximum brightness level.\n" +
"Values: 0-500 = Power (W)"],
]
}
/**
* getAssocGroupsMd()
*
* Returns association groups metadata. Used by sync(), updateSyncPending(), and generatePrefsAssocGroups().
*
* Reference: http://products.z-wavealliance.org/products/1729/assoc
**/
private getAssocGroupsMd() {
return [
[id: 1, maxNodes: 1, name: "Lifeline",
description : "Reports device state. Main Z-Wave controller should be added to this group."],
[id: 2, maxNodes: 8, name: "On/Off (S1)",
description : "Sends on/off commands to associated devices when S1 is pressed (BASIC_SET)."],
[id: 3, maxNodes: 8, name: "Dimmer (S1)",
description : "Sends dim/brighten commands to associated devices when S1 is pressed (SWITCH_MULTILEVEL_SET)."],
[id: 4, maxNodes: 8, name: "On/Off (S2)",
description : "Sends on/off commands to associated devices when S2 is pressed (BASIC_SET)."],
[id: 5, maxNodes: 8, name: "Dimmer (S2)",
description : "Sends dim/brighten commands to associated devices when S2 is pressed (SWITCH_MULTILEVEL_SET)."]
]
}
def configure() {
logger("configure()")
sendEvent(name: "checkInterval", value: 2 * 60 * 12 * 60 + 5 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
sendEvent(name: "numberOfButtons", value: 4, displayed: true)
state.isConfigured = "true"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment