/* * 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 } } }