/*
* Lightify4Button
*
* Description:
* This Hubitat driver is designed for use with the following devices:
* Lightify:
* Switch 4x-LIGHTIFY - WORKING
*
* Features List:
* Ability to handle multiple buttons for the Key Fob
* Ability to determine type based on model
* Ability to return battery state
* Ability to check a website (mine) if there is a newer version of the driver available
*
* Licensing:
* Copyright 2022 David Snell
* 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.
*
* Version Control:
* 0.7.0 - Semver versioning as well as overall updates
* 0.62 - Handling ZDO Device Announcements
* 0.61 - Major cleanup and design changes
* 0.6 - Update to driver version checking section
* 0.5 - Corrections to the ZigBee reporting
* 0.4 - Added sendEvent for ButtonPresses
* 0.3 - Correction to fingerprint and ClusterId detection
* 0.2 - Addition of the Lightify Switch
* 0.1 - Initial version split from Securifi Sensors driver
*
* Thank you(s):
* I would like to thank @Cobra his contributions to the community. Parts based on Cobra's driver update code
* have been included at the bottom of the driver and are noted as such.
*/
import hubitat.zigbee.zcl.DataType
metadata{
definition( name: "Lightify4Button", namespace: "Snell", author: "David Snell", importUrl: "https://www.drdsnell.com/projects/hubitat/drivers/Lightify4Button.groovy" ){
capability "Configuration"
capability "Sensor"
capability "Battery"
capability "Pushable Button"
capability "HoldableButton"
capability "DoubleTapableButton"
// Commands that are able to be activated
command "PushButton", [ [ name: "Push Button*", type: "ENUM", description: "Pick which button to push", constraints: [ 1 : "Top Left", 2 : "Top Right", 3 : "Bottom Left", 4 : "Bottom Right" ] ] ]
command "HoldButton", [ [ name: "Hold Button*", type: "ENUM", description: "Pick which button to hold", constraints: [ 1 : "Top Left", 2 : "Top Right", 3 : "Bottom Left", 4 : "Bottom Right" ] ] ]
command "ReleaseButton", [ [ name: "Release Button*", type: "ENUM", description: "Pick which button to release", constraints: [ 1 : "Top Left", 2 : "Top Right", 3 : "Bottom Left", 4 : "Bottom Right" ] ] ]
// Attributes being built into the device
// Driver identifies the driver being used for update purposes
attribute "Driver", "string"
// Version number, meant more for when I add in self-updating and related notifications
attribute "Version", "string"
// ButtonPressed is used to give the number of the button pressed
attribute "ButtonPressed", "int"
// ButtonPosition is used to show which button was pressed
attribute "ButtonPosition", "string"
// PressType is used to show if a press was long or short
attribute "PressType", "string"
// ButtonPresses is used to show the number of button presses
attribute "ButtonPresses", "int"
// Message is used to provide a message to the user of the device
attribute "Message", "string"
fingerprint profileId: "0104", inClusters: "0000, 1000, 0020, 1000, FD00", outClusters: "0003, 0004, 0005, 0006, 0008, 0019, 0300, 1000", manufacturer: "OSRAM", model: "Switch 4x LIGHTIFY", deviceJoinName: "Lightify 4 Button Switch"
}
preferences{
section{
input( type: "int", name: "MaxPresses", title: "Max button presses before rolling over?", required: false, defaultValue: 4 )
input( type: "enum", name: "LogType", title: "Enable Logging?", required: false, defaultValue: "2", multiple: false, options: [ [ "1" : "None" ], [ "2" : "Info" ], [ "3" : "Debug" ], [ "4" : "Trace" ] ] )
}
}
}
// Called when preferences are saved
// This clears state variables then sets some basic information as well as does a reconfig of the device
def updated(){
Logging( "Updated", 3 )
state.clear()
state.Driver = "Lightify4Button"
state.Version = "0.7.0"
Logging( "model ${ device.data.model } = Lightify 4 Button", 3 )
sendEvent( name:"numberOfButtons", value: 4 )
state.Pushed = 0
state.Held = 0
state.doubleTapped = 0
state.PressType = null
state.ButtonPresses = 0
if( MaxPresses == null ){
MaxPresses = 4
}
sendEvent( name: "pushed", value: 0 )
sendEvent( name: "held", value: 0 )
sendEvent( name: "doubleTapped", value: 0 )
unschedule()
schedule( new Date(), CheckForUpdate )
configure()
ReadDevice()
}
// Parse incoming device messages to generate events
def parse( String description ){
if( description?.startsWith( "zone status" ) ){
parseZoneMessage( description )
} else {
parseReport( description )
}
}
/*
* This parses ZigBee reports that do not have immediate events and tries to make sense
* of them by cluster. I am including as many clusters as possible in here, even ones this
* driver will never handle, so that it can be a useful reference for future work or others.
*/
private parseReport( String description ){
def descMap = zigbee.parseDescriptionAsMap( description )
def cluster = descMap.cluster ?: descMap.clusterId
def attrId = descMap.attrId
def ClusterIdentified = "Unknown" as String // Meant for logging with class data came in for
def Handled = false as boolean // Meant to help identify if a report handler existed for this data
switch( cluster ){
// General Clusters
case "0000": // Basic
ClusterIdentified = "Basic"
BasicReport( descMap )
Handled = true
break
case "0001": // Power configuration
ClusterIdentified = "Power config"
BatteryReport( descMap )
Handled = true
break
case "0002": // Device temperature config
ClusterIdentified = "Device temperature config"
break
case "0003": // Identify
ClusterIdentified = "Identify"
break
case "0004": // Groups
ClusterIdentified = "Groups"
break
case "0005": // Scenes
ClusterIdentified = "Scenes"
break
case "0006": // On/Off
ClusterIdentified = "On/Off"
Lightify4Button( descMap.sourceEndpoint, descMap.command )
Handled = true
break
case "0007": // On/Off switch configuration
ClusterIdentified = "On/Off switch configuration"
break
case "0008": // Level control
ClusterIdentified = "Level control"
Lightify4Button( descMap.sourceEndpoint, descMap.command )
Handled = true
break
case "0009": // Alarms
ClusterIdentified = "Alarms"
break
case "000A": // Time
ClusterIdentified = "Time"
break
case "000B": // RSSI Location
ClusterIdentified = "RSSI Location"
break
case "000C": // Analog Input
ClusterIdentified = "Analog Input"
break
case "000D": // Analog Output
ClusterIdentified = "Analog Output"
break
case "000E": // Analog Value
ClusterIdentified = "Analog Value"
break
case "000F": // Binary Input
ClusterIdentified = "Binary Input"
break
case "0010": // Binary Output
ClusterIdentified = "Binary Output"
break
case "0011": // Binary Value
ClusterIdentified = "Binary Value"
break
case "0012": // Multistate Input
ClusterIdentified = "Multistate Input"
break
case "0013": // Multistate Output & ZDO Device Announce
ClusterIdentified = "Multistate Output & ZDO Device Announce"
AnnounceReport( descMap )
Handled = true
break
case "0014": // Multistate Value
ClusterIdentified = "Multistate Value"
break
case "0015": // Commissioning
ClusterIdentified = "Commissioning"
break
case "0016": // Partition Cluster
ClusterIdentified = "Partition Cluster"
break
case "0017": // Node_Desc_Store_req
ClusterIdentified = "Node_Desc_Store_req"
break
case "0018": // Power_Desc_Store_req
ClusterIdentified = "Power_Desc_Store_req"
break
case "0019": // OTA Upgrades
ClusterIdentified = "OTA Upgrades"
break
case "001A": // Power Profile
ClusterIdentified = "Power Profile"
break
case "001B": // Appliance Control
ClusterIdentified = "Appliance Control"
break
case "001C": // Find_Node_Cache_req
ClusterIdentified = "Find_Node_Cache_req"
break
case "0020": // Poll Control
ClusterIdentified = "Poll Control"
break
case "0021": // Bind_req
ClusterIdentified = "Bind_req"
break
case "0022": // Unbind_req
ClusterIdentified = "Unbind_req"
break
case "0023": // Bind_Register_req
ClusterIdentified = "Bind_Register_req"
break
case "0024": // Replace_Device_req
ClusterIdentified = "Replace_Device_req"
break
case "0025": // Store_Bkup_Bind_Entry_req
ClusterIdentified = "Store_Bkup_Bind_Entry_req"
break
case "0026": // Rm_Bkup_Bind_Entry_req
ClusterIdentified = "Rm_Bkup_Bind_Entry_req"
break
case "0027": // Backup_Bind_Table_req
ClusterIdentified = "Backup_Bind_Table_req"
break
case "0028": // Recover_Bind_Table_req
ClusterIdentified = "Recover_Bind_Table_req"
break
case "0029": // Backup_Source_Bind_req
ClusterIdentified = "Backup_Source_Bind_req"
break
case "002A": // Recover_Source_Bind_req
ClusterIdentified = "Recover_Source_Bind_req"
break
// Network Management
case "0030": // Mgmt_NWK_Disc_req
ClusterIdentified = "Mgmt_NWK_Disc_req"
break
case "0031": // Mgmt_LQI_req
ClusterIdentified = "Mgmt_LQI_req"
break
case "0032": // Mgmt_Rtg_req
ClusterIdentified = "Mgmt_Rtg_req"
break
case "0033": // Mgmt_Bind_req
ClusterIdentified = "Mgmt_Bind_req"
break
case "0034": // Mgmt_Leave_req
ClusterIdentified = "Mgmt_Leave_req"
break
case "0035": // Mgmt_Direct_Join_req
ClusterIdentified = "Mgmt_Direct_Join_req"
break
case "0036": // Mgmt_Permit_Join_req
ClusterIdentified = "Mgmt_Permit_Join_req"
break
case "0037": // Mgmt_Cache_req
ClusterIdentified = "Mgmt_Cache_req"
break
case "0038": // Management Network Update Request
ClusterIdentified = "Management Network Update Request"
break
// Closures Clusters
case "0100": // Shade configuration
ClusterIdentified = "Shade configuration"
break
case "0101": // Door Lock
ClusterIdentified = "Door Lock"
break
case "0102": // Window Covering
ClusterIdentified = "Window Covering"
break
case "0103": // Barrier Control
ClusterIdentified = "Barrier Control"
break
// HVAC Clusters
case "0200": // Pump config and control
ClusterIdentified = "Pump config and control"
break
case "0201": // Thermostat
ClusterIdentified = "Thermostat"
break
case "0202": // Fan Control
ClusterIdentified = "Fan control"
break
case "0203": // Dehumidifier control
ClusterIdentified = "Dehumidifier control"
break
case "0204": // Thermostat UI config
ClusterIdentified = "Thermostat UI config"
break
// Lighting Clusters
case "0300": // Color control
ClusterIdentified = "Color control"
break
case "0301": // Ballast configuration
ClusterIdentified = "Ballast configuration"
break
// Measurement and Sensing Clusters
case "0400": // Luminance measurement
ClusterIdentified = "Luminance measurement"
break
case "0401": // Luminance level sensing
ClusterIdentified = "Luminance level sensing"
break
case "0402": // Temperature measurement
ClusterIdentified = "Temperature measurement"
break
case "0403": // Pressure measurement
ClusterIdentified = "Pressure measurement"
break
case "0404": // Flow measurement
ClusterIdentified = "Flow measurement"
break
case "0405": // Relative humidity measurement
ClusterIdentified = "Relative humidity measurement"
break
case "0406": // Occupancy sensing
ClusterIdentified = "Occupancy sensing"
break
case "040C": // CARBON_MONOXIDE_CONCENTRATION_MEASUREMENT
ClusterIdentified = "CARBON_MONOXIDE_CONCENTRATION_MEASUREMENT"
break
case "040D": // CARBON_DIOXIDE_CONCENTRATION_MEASUREMENT
ClusterIdentified = "CARBON_DIOXIDE_CONCENTRATION_MEASUREMENT"
break
case "040E": // ETHYLENE_CONCENTRATION_MEASUREMENT
ClusterIdentified = "ETHYLENE_CONCENTRATION_MEASUREMENT"
break
case "040F": // ETHYLENE_OXIDE_CONCENTRATION_MEASUREMENT
ClusterIdentified = "ETHYLENE_OXIDE_CONCENTRATION_MEASUREMENT"
break
case "0410": // HYDROGEN_CONCENTRATION_MEASUREMENT
ClusterIdentified = "HYDROGEN_CONCENTRATION_MEASUREMENT"
break
case "0411": // HYDROGEN_SULPHIDE_CONCENTRATION_MEASUREMENT
ClusterIdentified = "HYDROGEN_SULPHIDE_CONCENTRATION_MEASUREMENT"
break
case "0412": // NITRIC_OXIDE_CONCENTRATION_MEASUREMENT
ClusterIdentified = "NITRIC_OXIDE_CONCENTRATION_MEASUREMENT"
break
case "0413": // NITROGEN_DIOXIDE_CONCENTRATION_MEASUREMENT
ClusterIdentified = "NITROGEN_DIOXIDE_CONCENTRATION_MEASUREMENT"
break
case "0414": // OXYGEN_CONCENTRATION_MEASUREMENT
ClusterIdentified = "OXYGEN_CONCENTRATION_MEASUREMENT"
break
case "0415": // OZONE_CONCENTRATION_MEASUREMENT
ClusterIdentified = "OZONE_CONCENTRATION_MEASUREMENT"
break
case "0416": // SULFUR_DIOXIDE_CONCENTRATION_MEASUREMENT
ClusterIdentified = "SULFUR_DIOXIDE_CONCENTRATION_MEASUREMENT"
break
case "0417": // DISSOLVED_OXYGEN_CONCENTRATION_MEASUREMENT
ClusterIdentified = "DISSOLVED_OXYGEN_CONCENTRATION_MEASUREMENT"
break
case "0418": // BROMATE_CONCENTRATION_MEASUREMENT
ClusterIdentified = "BROMATE_CONCENTRATION_MEASUREMENT"
break
case "0419": // CHLORAMINES_CONCENTRATION_MEASUREMENT
ClusterIdentified = "CHLORAMINES_CONCENTRATION_MEASUREMENT"
break
case "041A": // CHLORINE_CONCENTRATION_MEASUREMENT
ClusterIdentified = "CHLORINE_CONCENTRATION_MEASUREMENT"
break
case "041B": // FECAL_COLIFORM_AND_E_COLI_CONCENTRATION_MEASUREMENT
ClusterIdentified = "FECAL_COLIFORM_AND_E_COLI_CONCENTRATION_MEASUREMENT"
break
case "041C": // FLUORIDE_CONCENTRATION_MEASUREMENT
ClusterIdentified = "FLUORIDE_CONCENTRATION_MEASUREMENT"
break
case "041D": // HALOACETIC_ACIDS_CONCENTRATION_MEASUREMENT
ClusterIdentified = "HALOACETIC_ACIDS_CONCENTRATION_MEASUREMENT"
break
case "041E": // TOTAL_TRIHALOMETHANES_CONCENTRATION_MEASUREMENT
ClusterIdentified = "TOTAL_TRIHALOMETHANES_CONCENTRATION_MEASUREMENT"
break
case "041F": // TOTAL_COLIFORM_BACTERIA_CONCENTRATION_MEASUREMENT
ClusterIdentified = "TOTAL_COLIFORM_BACTERIA_CONCENTRATION_MEASUREMENT"
break
case "0420": // TURBIDITY_CONCENTRATION_MEASUREMENT
ClusterIdentified = "TURBIDITY_CONCENTRATION_MEASUREMENT"
break
case "0421": // COPPER_CONCENTRATION_MEASUREMENT
ClusterIdentified = "COPPER_CONCENTRATION_MEASUREMENT"
break
case "0422": // LEAD_CONCENTRATION_MEASUREMENT
ClusterIdentified = "LEAD_CONCENTRATION_MEASUREMENT"
break
case "0423": // MANGANESE_CONCENTRATION_MEASUREMENT
ClusterIdentified = "MANGANESE_CONCENTRATION_MEASUREMENT"
break
case "0424": // SULFATE_CONCENTRATION_MEASUREMENT
ClusterIdentified = "SULFATE_CONCENTRATION_MEASUREMENT"
break
case "0425": // BROMODICHLOROMETHANE_CONCENTRATION_MEASUREMENT
ClusterIdentified = "BROMODICHLOROMETHANE_CONCENTRATION_MEASUREMENT"
break
case "0426": // BROMOFORM_CONCENTRATION_MEASUREMENT
ClusterIdentified = "BROMOFORM_CONCENTRATION_MEASUREMENT"
break
case "0427": // CHLORODIBROMOMETHANE_CONCENTRATION_MEASUREMENT
ClusterIdentified = "CHLORODIBROMOMETHANE_CONCENTRATION_MEASUREMENT"
break
case "0428": // CHLOROFORM_CONCENTRATION_MEASUREMENT
ClusterIdentified = "CHLOROFORM_CONCENTRATION_MEASUREMENT"
break
case "0429": // SODIUM_CONCENTRATION_MEASUREMENT
ClusterIdentified = "SODIUM_CONCENTRATION_MEASUREMENT"
break
// Security & Safety Clusters
case "0500": // IAS Zone
ClusterIdentified = "IAS Zone"
break
case "0501": // IAS ACE "Ancillary Control"
ClusterIdentified = "IAS ACE Ancillary Control"
break
case "0502": // IAS WD "Warning Devices"
ClusterIdentified = "IAS WD Warning Devices"
break
// Protocol Interface Clusters
case "0600": // Generic Tunnel
ClusterIdentified = "Interface Clusters"
break
case "0601": // BACnet Protocol Tunnel
ClusterIdentified = "BACnet Protocol Tunnel"
break
case "0602": // Analog Input (BACnet Regular)
ClusterIdentified = "Analog Input (BACnet Regular)"
break
case "0603": // Analog Input (BACnet Extended)
ClusterIdentified = "Analog Input (BACnet Extended)"
break
case "0604": // Analog Output (BACnet Regular)
ClusterIdentified = "Analog Output (BACnet Regular)"
break
case "0605": // Analog Output (BACnet Extended)
ClusterIdentified = "Analog Output (BACnet Extended)"
break
case "0606": // Analog Value (BACnet Regular)
ClusterIdentified = "Analog Value (BACnet Regular)"
break
case "0607": // Analog Value (BACnet Extended)
ClusterIdentified = "Analog Value (BACnet Extended)"
break
case "0608": // Binary Input (BACnet Regular)
ClusterIdentified = "Binary Input (BACnet Regular)"
break
case "0609": // Binary Input (BACnet Extended)
ClusterIdentified = "Binary Input (BACnet Extended)"
break
case "060A": // Binary Output (BACnet Regular)
ClusterIdentified = "Binary Output (BACnet Regular)"
break
case "060B": // Binary Output (BACnet Extended)
ClusterIdentified = "Binary Output (BACnet Extended)"
break
case "060C": // Binary Value (BACnet Regular)
ClusterIdentified = "Binary Value (BACnet Regular)"
break
case "060D": // Binary Value (BACnet Extended)
ClusterIdentified = "Binary Value (BACnet Extended)"
break
case "060E": // Multistate Input (BACnet Regular)
ClusterIdentified = "Multistate Input (BACnet Regular)"
break
case "060F": // Multistate Input (BACnet Extended)
ClusterIdentified = "Multistate Input (BACnet Extended)"
break
case "0610": // Multistate Output (BACnet Regular)
ClusterIdentified = "Multistate Output (BACnet Regular)"
break
case "0611": // Multistate Output (BACnet Extended)
ClusterIdentified = "Multistate Output (BACnet Extended)"
break
case "0612": // Multistate Value (BACnet Regular)
ClusterIdentified = "Multistate Value (BACnet Regular)"
break
case "0613": // Multistate Value (BACnet Extended)
ClusterIdentified = "Multistate Value (BACnet Extended)"
break
case "0614": // Protocol Tunnel Cluster
ClusterIdentified = "Protocol Tunnel Cluster"
break
case "0615": // Protocol Tunnel
ClusterIdentified = "Protocol Tunnel"
break
case "0700": // Price
ClusterIdentified = "Price"
break
case "0701": // Demand Response and Load Control
ClusterIdentified = "Demand Response and Load Control"
break
case "0702": // Metering
ClusterIdentified = "Metering"
break
case "0703": // Messaging
ClusterIdentified = "Messaging"
break
case "0704": // Tunneling Cluster
ClusterIdentified = "Tunneling Cluster"
break
case "0705": // PREPAYMENT
ClusterIdentified = "PREPAYMENT"
break
case "0706": // ENERGY_MANAGEMENT
ClusterIdentified = "ENERGY_MANAGEMENT"
break
case "0707": // CALENDAR
ClusterIdentified = "CALENDAR"
break
case "0708": // DEVICE_MANAGEMENT
ClusterIdentified = "DEVICE_MANAGEMENT"
break
case "0709": // EVENTS
ClusterIdentified = "EVENTS"
break
case "070A": // MDU_PAIRING
ClusterIdentified = "MDU_PAIRING"
break
case "070B": // SUB_GHZ
ClusterIdentified = "SUB_GHZ"
break
case "0800": // Key Establishment
ClusterIdentified = "Key Establishment"
break
case "0900": // INFORMATION
ClusterIdentified = "INFORMATION"
break
case "0901": // DATA_SHARING
ClusterIdentified = "DATA_SHARING"
break
case "0902": // GAMING
ClusterIdentified = "GAMING"
break
case "0903": // DATA_RATE_CONTROL
ClusterIdentified = "DATA_RATE_CONTROL"
break
case "0904": // VOICE_OVER_ZIGBEE
ClusterIdentified = "VOICE_OVER_ZIGBEE"
break
case "0905": // CHATTING
ClusterIdentified = "CHATTING"
break
case "0A00": // PAYMENT
ClusterIdentified = "PAYMENT"
break
case "0A01": // BILLING
ClusterIdentified = "BILLING"
break
case "0B00": // APPLIANCE_IDENTIFICATION
ClusterIdentified = "APPLIANCE_IDENTIFICATION"
break
case "0B01": // Meter Identification
ClusterIdentified = "Meter Identification"
break
case "0B02": // Appliance Events and Alerts
ClusterIdentified = "Appliance Events and Alerts"
break
case "0B03": // Appliance Statistics
ClusterIdentified = "Appliance Statistics"
break
case "0B04": // Electrical Measurement
ClusterIdentified = "Electrical Measurement"
break
case "0B05": // Diagnostic
ClusterIdentified = "Diagnostic"
break
case "1000": // ZLL_COMMISSIONING
ClusterIdentified = "ZLL_COMMISSIONING"
break
case "8000": // NWK_ADDR_RSP
ClusterIdentified = "NWK_ADDR_RSP"
break
case "8001": // IEEE_ADDR_RSP
ClusterIdentified = "IEEE_ADDR_RSP"
break
case "8002": // NODE_DESC_RSP
ClusterIdentified = "NODE_DESC_RSP"
break
case "8003": // POWER_DESC_RSP
ClusterIdentified = "POWER_DESC_RSP"
break
case "8004": // SIMPLE_DESC_RSP
ClusterIdentified = "SIMPLE_DESC_RSP"
break
case "8005": // ACTIVE_EP_RSP
ClusterIdentified = "ACTIVE_EP_RSP"
break
case "8006": // MATCH_DESC_RSP
ClusterIdentified = "MATCH_DESC_RSP"
break
case "8010": // Complex_Desc_rsp
ClusterIdentified = "Complex_Desc_rsp"
break
case "8011": // User_Desc_rsp
ClusterIdentified = "User_Desc_rsp"
break
case "8012": // Discovery_Cache_rsp
ClusterIdentified = "Discovery_Cache_rsp"
break
case "8014": // User_Desc_Conf
ClusterIdentified = "User_Desc_Conf"
break
case "8015": // System_Server_Discover_rsp
ClusterIdentified = "System_Server_Discover_rsp"
break
case "8016": // Discovery_Store_rsp
ClusterIdentified = "Discovery_Store_rsp"
break
case "8017": // Node_Desc_Store_rso
ClusterIdentified = "Node_Desc_Store_rso"
break
case "8018": // Power_Desc_Store_rsp
ClusterIdentified = "Power_Desc_Store_rsp"
break
case "8019": // Active_EP_Store_rsp
ClusterIdentified = "Active_EP_Store_rsp"
break
case "801A": // Simple_Desc_Store_rsp
ClusterIdentified = "Simple_Desc_Store_rsp"
break
case "801B": // Remove_Node_Cache_rsp
ClusterIdentified = "Remove_Node_Cache_rsp"
break
case "801C": // Find_Node_Cache_rsp
ClusterIdentified = "Find_Node_Cache_rsp"
break
// End Device Bind, Unbind and Bind Management
case "8020": // End_Dev_Bind_rsp
ClusterIdentified = "End_Dev_Bind_rsp"
break
case "8021": // Bind_rsp
ClusterIdentified = "Bind_rsp"
break
case "8022": // Unbind_rsp
ClusterIdentified = "Unbind_rsp"
break
case "8023": // Bind_Register_rsp
ClusterIdentified = "Bind_Register_rsp"
break
case "8024": // Replace_Device_rsp
ClusterIdentified = "Replace_Device_rsp"
break
case "8025": // Store_Bkup_Bind_Entry_rsp
ClusterIdentified = "Store_Bkup_Bind_Entry_rsp"
break
case "8026": // Rm_Bkup_Bind_Entry_rsp
ClusterIdentified = "Rm_Bkup_Bind_Entry_rsp"
break
case "8027": // Backup_Bind_Table_rsp
ClusterIdentified = "Backup_Bind_Table_rsp"
break
case "8028": // Recover_Bind_Table_rsp
ClusterIdentified = "Recover_Bind_Table_rsp"
break
case "8029": // Backup_Source_Bind_rsp
ClusterIdentified = "Backup_Source_Bind_rsp"
break
case "802A": // Recover_Source_Bind_rsp
ClusterIdentified = "Recover_Source_Bind_rsp"
break
// Network Management
case "8030": // Mgmt_NWK_Disc_rsp
ClusterIdentified = "Mgmt_NWK_Disc_rsp"
break
case "8031": // Mgmt_LQI_rsp
ClusterIdentified = "Mgmt_LQI_rsp"
break
case "8032": // Mgmt_Rtg_rsp
ClusterIdentified = "Mgmt_Rtg_rsp"
break
case "8033": // Mgmt_Bind_rsp
ClusterIdentified = "Mgmt_Bind_rsp"
break
case "8034": // Mgmt_Leave_rsp
ClusterIdentified = "Mgmt_Leave_rsp"
break
case "8035": // Mgmt_Direct_Join_rsp
ClusterIdentified = "Mgmt_Direct_Join_rsp"
break
case "8036": // Mgmt_Permit_Join_rsp
ClusterIdentified = "Mgmt_Permit_Join_rsp"
break
case "8037": // Mgmt_Cache_rsp
ClusterIdentified = "Mgmt_Cache_rsp"
break
case "8038": // Management Network Update Notify
ClusterIdentified = "Management Network Update Notify"
break
case "FC00": // SAMPLE_MFG_SPECIFIC
ClusterIdentified = "SAMPLE_MFG_SPECIFIC"
break
case "FC01": // OTA_CONFIGURATION
ClusterIdentified = "OTA_CONFIGURATION"
break
case "FC02": // MFGLIB
ClusterIdentified = "MFGLIB"
break
case "FC57": // SL_WWAH
ClusterIdentified = "SL_WWAH"
break
case "FD00": // Halo Smoke Detector Only ???
ClusterIdentified = "Halo Smoke Detector Only ???"
break
case "FD01": // Halo Smoke Detector Only ???
ClusterIdentified = "Halo Smoke Detector Only ???"
break
case "FD02": // Halo Smoke Detector Only ???
ClusterIdentified = "Halo Smoke Detector Only ???"
break
default:
ClusterIdentified = "Unknown"
break
}
if( ClusterIdentified == "Unknown" ){
Logging( "Unknown cluster. descMap = ${ descMap }", 3 )
} else {
if( !Handled ){
Logging( "Unhandled cluster. ${ cluster } is ${ ClusterIdentified }, descMap = ${ descMap }", 3 )
}
}
}
// Handles ZDO Announce reports
def AnnounceReport( descMap ){
switch( descMap.command ){
case "00":
Logging( "Device alive/awake", 3 )
break
default:
Logging( "Announce Report ${ descMap }", 3 )
break
}
}
// Processes temperature related reports
private BatteryReport( descMap ){
if( descMap.command == "07" ){
def BatteryPercent = descMap.data[ 0 ] as int
Logging( "Battery at ${ BatteryPercent }%", 2 )
if( state.Battery != BatteryPercent ){
state.Battery = BatteryPercent
sendEvent( name: "battery", value: BatteryPercent, unit: "%", isStateChanged: true )
}
}
}
// Used for when the Hold Button command is triggered.
private HoldButton( Value ){
switch( Value ){
case "Top Left":
Lightify4Button( "01", "05" )
break
case "Top Right":
Lightify4Button( "02", "05" )
break
case "Bottom Left":
Lightify4Button( "03", "05" )
break
case "Bottom Right":
Lightify4Button( "04", "05" )
break
}
}
// Used for when the Press Button command is triggered.
private PushButton( Value ){
switch( Value ){
case "Top Left":
Lightify4Button( "01", "01" )
break
case "Top Right":
Lightify4Button( "02", "01" )
break
case "Bottom Left":
Lightify4Button( "03", "01" )
break
case "Bottom Right":
Lightify4Button( "04", "01" )
break
}
}
// Used for when the Release Button command is triggered.
private ReleaseButton( Value ){
switch( Value ){
case "Top Left":
Lightify4Button( "01", "03" )
break
case "Top Right":
Lightify4Button( "02", "03" )
break
case "Bottom Left":
Lightify4Button( "03", "03" )
break
case "Bottom Right":
Lightify4Button( "04", "03" )
break
}
}
// Handles when a press is reported using a Lightify 4 Button Switch
private Lightify4Button( Source, Command ){
// Identifies if a value has changed for sendEvent purposes
def Changed = false
Logging( "Source = ${ Source }, Command = ${ Command }", 4 )
switch( Source ){
case "01":
if( state.ButtonPressed != 1 ){
state.ButtonPressed = 1
Changed = true
}
break
case "02":
if( state.ButtonPressed != 2 ){
state.ButtonPressed = 2
Changed = true
}
break
case "03":
if( state.ButtonPressed != 3 ){
state.ButtonPressed = 3
Changed = true
}
break
case "04":
if( state.ButtonPressed != 4 ){
state.ButtonPressed = 4
Changed = true
}
break
default:
Logging( "Unknown button", 3 )
break
}
// Handle the number of presses that have occurred
if( Changed ){
sendEvent( name: "ButtonPressed", value: state.ButtonPressed , isStateChange: true )
state.ButtonPresses = 1
} else {
if( MaxPresses == null ){
MaxPresses = 4
}
def Maximum = MaxPresses as int
if( state.ButtonPresses < Maximum ){
Logging( "Same button pressed so incrementing presses", 4 )
state.ButtonPresses += 1
} else {
Logging( "More presses than max, starting over at 1", 4 )
state.ButtonPresses = 1
}
}
Logging( "Button Presses = ${ state.ButtonPresses } and Max Presses = ${ MaxPresses }", 4 )
sendEvent( name: "ButtonPresses", value: state.ButtonPresses , isStateChange: true )
// Handle the description of what button was pressed
switch( state.ButtonPressed ){
case 1:
if( state.ButtonPosition != "Top Left" ){
state.ButtonPosition = "Top Left"
Changed = true
}
break
case 2:
if( state.ButtonPosition != "Top Right" ){
state.ButtonPosition = "Top Right"
Changed = true
}
break
case 3:
if( state.ButtonPosition != "Bottom Left" ){
state.ButtonPosition = "Bottom Left"
Changed = true
}
break
case 4:
if( state.ButtonPosition != "Bottom Right" ){
state.ButtonPosition = "Bottom Right"
Changed = true
}
break
}
if( Changed ){
sendEvent( name: "ButtonPosition", value: state.ButtonPosition, isStateChanged: true )
}
// Handle which command was received that determines type of push
switch( Command ){
case "00":
if( state.PressType != "pushed" ){
state.PressType = "pushed"
Changed = true
}
break
case "01":
if( state.PressType != "pushed" ){
state.PressType = "pushed"
Changed = true
}
break
case "03":
if( state.PressType != "released" ){
state.PressType = "released"
Changed = true
}
break
case "05":
if( state.PressType != "held" ){
state.PressType = "held"
Changed = true
}
break
}
if( Changed ){
sendEvent( name: "PressType", value: state.PressType, isStateChanged: true )
// Send the relevent events based on the PressType
switch( state.PressType ){
case "pushed":
sendEvent( name: "pushed", value: state.ButtonPressed, isStateChanged: true )
sendEvent( name: "held", value: 0, isStateChanged: true )
sendEvent( name: "doubleTapped", value: 0, isStateChanged: true )
sendEvent( name: "released", value: 0, isStateChanged: true )
break
case "held":
sendEvent( name: "pushed", value: state.ButtonPressed, isStateChanged: true )
sendEvent( name: "held", value: state.ButtonPressed, isStateChanged: true )
sendEvent( name: "doubleTapped", value: 0, isStateChanged: true )
sendEvent( name: "released", value: 0, isStateChanged: true )
break
case "double":
sendEvent( name: "pushed", value: state.ButtonPressed, isStateChanged: true )
sendEvent( name: "held", value: 0, isStateChanged: true )
sendEvent( name: "doubleTapped", value: state.ButtonPressed, isStateChanged: true )
sendEvent( name: "released", value: 0, isStateChanged: true )
break
case "released":
sendEvent( name: "pushed", value: 0, isStateChanged: true )
sendEvent( name: "held", value: 0, isStateChanged: true )
sendEvent( name: "doubleTapped", value: 0, isStateChanged: true )
sendEvent( name: "released", value: state.ButtonPressed, isStateChanged: true )
break
}
}
Logging( "${ state.ButtonPosition } Button ${ state.PressType }", 4 )
}
// Handles reports from the Basic cluster
def BasicReport( descMap ){
switch( descMap.attrId ){
case "0000":
state.ZCL_Version = descMap.value
Logging( "ZCL Version = ${ descMap.value }", 3 )
break
case "0001":
state.Application_Version = descMap.value
Logging( "Application Version = ${ descMap.value }", 3 )
break
case "0002":
state.Stack_Version = descMap.value
Logging( "Stack Version = ${ descMap.value }", 3 )
break
case "0003":
state.Hardware_Version = descMap.value
Logging( "Hardware Version = ${ descMap.value }", 3 )
break
case "0004":
state.Manufacturer_Name = descMap.value
Logging( "Manufacturer Name = ${ descMap.value }", 3 )
break
case "0005":
state.Model_Identifier = descMap.value
Logging( "Model Identifier = ${ descMap.value }", 3 )
break
case "0006":
state.Date_Code = descMap.value
Logging( "Date Code = ${ descMap.value }", 3 )
break
case "0007":
switch( descMap.value ){
case "00":
state.Power_Source = "Unknown 00"
Logging( "Power Source = Unknown 00", 3 )
break
case "01":
state.Power_Source = "Mains (single phase)"
Logging( "Power Source = Mains (single phase)", 3 )
break
case "02":
state.Power_Source = "Mains (3 phase)"
Logging( "Power Source = Mains (3 phase)", 3 )
break
case "03":
state.Power_Source = "Battery"
Logging( "Power Source = Battery", 3 )
break
case "04":
state.Power_Source = "DC source"
Logging( "Power Source = DC source", 3 )
break
case "05":
state.Power_Source = "Emergency mains constantly powered"
Logging( "Power Source = Emergency mains constantly powered", 3 )
break
case "06":
state.Power_Source = "Emergency mains and transfer switch"
Logging( "Power Source = Emergency mains and transfer switch", 3 )
break
case "81":
state.Power_Source = "Unknown (${ descMap.value })"
Logging( "Power Source is unknown (${ descMap.value })", 3 )
break
default:
state.Power_Source = "Unlisted (${ descMap.value })"
Logging( "Power Source is value (${ descMap.value }) is unlisted", 3 )
break
}
break
case "0010":
state.Location_Description = descMap.value
Logging( "Location Description = ${ descMap.value }", 3 )
break
case "0011":
state.Physical_Environment = descMap.value
Logging( "Physical Environment = ${ descMap.value }", 3 )
break
case "0012":
state.Device_Enabled = descMap.value
Logging( "Device Enabled = ${ descMap.value }", 3 )
break
case "0013":
state.Alarm_Mask = descMap.value
Logging( "Alarm Mask = ${ descMap.value }", 3 )
break
case "0014":
state.Disable_Local_Config = descMap.value
Logging( "Disable Local Config = ${ descMap.value }", 3 )
break
case "4000":
state.SW_Build_ID = descMap.value
Logging( "SW Build ID = ${ descMap.value }", 3 )
break
default:
Logging( "Basic descMap = ${ descMap }", 3 )
break
}
}
// ReadDevice is meant to check all the device basic information
def ReadDevice(){
Logging( "Reading device basic cluster...", 3 )
def cmds = zigbee.readAttribute( 0x0000, 0x0000 ) // ZCL Version
cmds += zigbee.readAttribute( 0x0000, 0x0002 ) // Stack Version
cmds += zigbee.readAttribute( 0x0000, 0x0003 ) // Hardware Version
cmds += zigbee.readAttribute( 0x0000, 0x0004 ) // Manufacturer Identifier
cmds += zigbee.readAttribute( 0x0000, 0x0005 ) // Model Identifier
cmds += zigbee.readAttribute( 0x0000, 0x0007 ) // Power Source
//cmds += zigbee.readAttribute( 0x0019, 0x0000 ) // UPGRADE_SERVER_ID
//cmds += zigbee.readAttribute( 0x0019, 0x0001 ) // FILE_OFFSET
//cmds += zigbee.readAttribute( 0x0019, 0x0002 ) // CURRENT_FILE_VERSION
//cmds += zigbee.readAttribute( 0x0019, 0x0003 ) // CURRENT_ZIGBEE_STACK_VERSION
//cmds += zigbee.readAttribute( 0x0019, 0x0004 ) // DOWNLOADED_FILE_VERSION
//cmds += zigbee.readAttribute( 0x0019, 0x0005 ) // DOWNLOADED_ZIGBEE_STACK_VERSION
//cmds += zigbee.readAttribute( 0x0019, 0x0006 ) // IMAGE_UPGRADE_STATUS
//cmds += zigbee.readAttribute( 0x0019, 0x0007 ) // MANUFACTURER_ID
//cmds += zigbee.readAttribute( 0x0019, 0x0008 ) // IMAGE_TYPE_ID
//cmds += zigbee.readAttribute( 0x0019, 0x0009 ) // MINIMUM_BLOCK_REQUEST_PERIOD
return cmds
}
// Sets the list of read commands
def ZigBeeReadCommands(){
def cmds = zigbee.readAttribute( 0x0001, 0x0020 ) // Battery voltage
cmds += zigbee.readAttribute( 0x0001, 0x0021 ) // Battery % remaining
//cmds += zigbee.enrollResponse()
Logging( "Read device attributes", 3 )
return cmds
}
// refresh command
def refresh(){
Logging( "Refreshing device...", 2 )
return ZigBeeReadCommands()
}
// Gets the list of commands
def ZigBeeReportingCommands(){
def cmds = zigbee.configureReporting( 0x0001, 0x0021, DataType.UINT8, 1, 86400, 1 ) // Battery % remaining
Logging( "Set ZigBee reporting", 3 )
return cmds
}
// Configures the device, typically at install or when preferences are saved
def configure(){
Logging( "Configuring device...", 2 )
return ZigBeeReportingCommands()
refresh()
}
// installed is called when the device is installed, all it really does is run updated
def installed(){
Logging( "Installed", 2 )
updated()
}
// initialize is called when the device is initialized, all it really does is run updated
def initialize(){
Logging( "Initialized", 2 )
updated()
}
// Handles whether logging is enabled and thus what to put there.
def Logging( LogMessage, LogLevel ){
// Add all messages as info logging
if( LogType >= "2" && LogLevel == 2 ){
log.info( "${ device.displayName } - ${ LogMessage }" )
} else if( LogType >= "3" && LogLevel == 3 ){
log.debug( "${ device.displayName } - ${ LogMessage }" )
} else if( LogType >= "4" && LogLevel == 4 ){
log.trace( "${ device.displayName } - ${ LogMessage }" )
} else if( LogLevel == 5 ){
log.error( "${ device.displayName } - ${ LogMessage }" )
}
}
// Checks drdsnell.com for the latest version of the driver
// Based on @Cobra's update checking method, although I prefer xml vs json
def CheckForUpdate(){
state.Driver = "Lightify4Button"
state.Version = "0.7.0"
httpGet( uri: "https://www.drdsnell.com/projects/hubitat/drivers/versions.json", contentType: "application/json" ){ resp ->
switch( resp.status ){
case 200:
if( resp.data."${ state.Driver }" ){
CurrentVersion = state.Version.split( /\./ )
SiteVersion = resp.data."${ state.Driver }".version.split( /\./ )
if( CurrentVersion == SiteVersion ){
Logging( "Driver version up to date", 3 )
sendEvent( name: "Version", value: "Up to date" )
} else if( CurrentVersion[ 0 ] > SiteVersion [ 0 ] ){
Logging( "Major development ${ CurrentVersion[ 0 ] }.${ CurrentVersion[ 1 ] }.${ CurrentVersion[ 2 ] } version", 3 )
sendEvent( name: "Version", value: "Major development ${ CurrentVersion[ 0 ] }.${ CurrentVersion[ 1 ] }.${ CurrentVersion[ 2 ] } version" )
} else if( CurrentVersion[ 1 ] > SiteVersion [ 1 ] ){
Logging( "Minor development ${ CurrentVersion[ 0 ] }.${ CurrentVersion[ 1 ] }.${ CurrentVersion[ 2 ] } version", 3 )
sendEvent( name: "Version", value: "Minor development ${ CurrentVersion[ 0 ] }.${ CurrentVersion[ 1 ] }.${ CurrentVersion[ 2 ] } version" )
} else if( CurrentVersion[ 2 ] > SiteVersion [ 2 ] ){
Logging( "Patch development ${ CurrentVersion[ 0 ] }.${ CurrentVersion[ 1 ] }.${ CurrentVersion[ 2 ] } version", 3 )
sendEvent( name: "Version", value: "Patch development ${ CurrentVersion[ 0 ] }.${ CurrentVersion[ 1 ] }.${ CurrentVersion[ 2 ] } version" )
} else if( SiteVersion[ 0 ] > CurrentVersion[ 0 ] ){
Logging( "New major release ${ SiteVersion[ 0 ] }.${ SiteVersion[ 1 ] }.${ SiteVersion[ 2 ] } available", 2 )
sendEvent( name: "Version", value: "New major release ${ SiteVersion[ 0 ] }.${ SiteVersion[ 1 ] }.${ SiteVersion[ 2 ] } available" )
} else if( SiteVersion[ 1 ] > CurrentVersion[ 1 ] ){
Logging( "New minor release ${ SiteVersion[ 0 ] }.${ SiteVersion[ 1 ] }.${ SiteVersion[ 2 ] } available", 2 )
sendEvent( name: "Version", value: "New minor release ${ SiteVersion[ 0 ] }.${ SiteVersion[ 1 ] }.${ SiteVersion[ 2 ] } available" )
} else if( SiteVersion[ 2 ] > CurrentVersion[ 2 ] ){
Logging( "New patch ${ SiteVersion[ 0 ] }.${ SiteVersion[ 1 ] }.${ SiteVersion[ 2 ] } available", 2 )
sendEvent( name: "Version", value: "New patch ${ SiteVersion[ 0 ] }.${ SiteVersion[ 1 ] }.${ SiteVersion[ 2 ] } available" )
}
} else {
Logging( "Unpublished driver", 3 )
sendEvent( name: "Version", value: "Unpublished driver" )
}
break
default:
Logging( "Unable to check drdsnell.com for driver updates.", 3 )
break
}
}
}