Repository: zigpy/zha-device-handlers

Issue:
Request for support for Lutron connected bulb remote
Issue was originally opened here https://github.com/zigpy/bellows/issues/141 but was requested to be moved to the device handlers I believe the relevent information required is here. 

```
2019-02-07 18:37:44 INFO (MainThread) [zigpy.device] [0x6e04] Discovered endpoints: [1]
2019-02-07 18:37:45 INFO (MainThread) [zigpy.endpoint] [0x6e04:1] Discovered endpoint information: <SimpleDescriptor endpoint=1 profile=49246 device_type=2080 device_version=2 input_clusters=[0, 4096, 65280, 64580] output_clusters=[4096, 3, 6, 8, 4, 5, 0, 65280]>
2019-02-07 18:37:45 DEBUG (MainThread) [zigpy.endpoint] [0x6e04:1] Manufacturer: Lutron
2019-02-07 18:37:45 DEBUG (MainThread) [zigpy.endpoint] [0x6e04:1] Model: LZL4BWHL01 Remote
```


Discussion:
Events should be emitted for this naturally in ZHA now, and binding support is on the way!
@w1ll1am23 if you have time I can show you how to do this on discord and you can PR it if you'd like?

Resolving patch:
diff --git a/zhaquirks/__init__.py b/zhaquirks/__init__.py
--- a/zhaquirks/__init__.py
+++ b/zhaquirks/__init__.py
@@ -5,6 +5,7 @@
 from zigpy.quirks import CustomCluster
 import zigpy.types as types
 from zigpy.util import ListenableMixin
+from zigpy.zdo import types as zdotypes
 
 UNKNOWN = 'Unknown'
 
@@ -57,6 +58,35 @@ def _update_attribute(self, attrid, value):
         )
 
 
+class GroupBoundCluster(CustomCluster):
+    """
+    Cluster that can only bind to a group instead of direct to hub.
+
+    Binding this cluster results in binding to a group that the coordinator
+    is a member of.
+    """
+
+    COORDINATOR_GROUP_ID = 0x30  # Group id with only coordinator as a member
+
+    async def bind(self):
+        """Bind cluster to a group."""
+        # Ensure coordinator is a member of the group
+        application = self._endpoint.device.application
+        coordinator = application.get_device(application.ieee)
+        await coordinator.add_to_group(self.COORDINATOR_GROUP_ID)
+
+        # Bind cluster to group
+        dstaddr = zdotypes.MultiAddress()
+        dstaddr.addrmode = 1
+        dstaddr.nwk = self.COORDINATOR_GROUP_ID
+        dstaddr.endpoint = self._endpoint.endpoint_id
+        return await self._endpoint.device.zdo.Bind_req(
+            self._endpoint.device.ieee,
+            self._endpoint.endpoint_id,
+            self.cluster_id,
+            dstaddr)
+
+
 NAME = __name__
 PATH = __path__
 for importer, modname, ispkg in pkgutil.walk_packages(
diff --git a/zhaquirks/lutron/__init__.py b/zhaquirks/lutron/__init__.py
new file mode 100644
--- /dev/null
+++ b/zhaquirks/lutron/__init__.py
@@ -0,0 +1 @@
+"""Module for Lutron devices."""
diff --git a/zhaquirks/lutron/lzl4bwhl01remote.py b/zhaquirks/lutron/lzl4bwhl01remote.py
new file mode 100644
--- /dev/null
+++ b/zhaquirks/lutron/lzl4bwhl01remote.py
@@ -0,0 +1,94 @@
+"""Device handler for Lutron LZL4BWHL01 Remote."""
+from zigpy.profiles import zll
+from zigpy.zcl.clusters.general import Basic, Identify, Groups, Scenes, OnOff,\
+    LevelControl
+from zigpy.zcl.clusters.lightlink import LightLink
+from zigpy.quirks import CustomDevice
+from zhaquirks import GroupBoundCluster
+
+
+MANUFACTURER_SPECIFIC_CLUSTER_ID_1 = 0xFF00  # decimal = 65280
+MANUFACTURER_SPECIFIC_CLUSTER_ID_2 = 0xFC44  # decimal = 64580
+
+
+class OnOffGroupCluster(GroupBoundCluster, OnOff):
+    """On/Off Cluster which only binds to a group."""
+
+    pass
+
+
+class LevelControlGroupCluster(GroupBoundCluster, LevelControl):
+    """Level Control Cluster which only binds to a group."""
+
+    pass
+
+
+class LutronLZL4BWHL01Remote(CustomDevice):
+    """Custom device representing Lutron LZL4BWHL01 Remote."""
+
+    signature = {
+        #  <SimpleDescriptor endpoint=1 profile=49246 device_type=2080
+        #  device_version=2
+        #  input_clusters=[0, 4096, 65280, 64580]
+        #  output_clusters=[4096, 3, 6, 8, 4, 5, 0, 65280]>
+        1: {
+            'model': 'LZL4BWHL01 Remote',
+            'manufacturer': 'Lutron',
+            'profile_id': zll.PROFILE_ID,
+            'device_type': zll.DeviceType.CONTROLLER,
+            'input_clusters': [
+                Basic.cluster_id,
+                LightLink.cluster_id,
+                MANUFACTURER_SPECIFIC_CLUSTER_ID_1,
+                MANUFACTURER_SPECIFIC_CLUSTER_ID_2
+            ],
+            'output_clusters': [
+                LightLink.cluster_id,
+                Identify.cluster_id,
+                OnOff.cluster_id,
+                LevelControl.cluster_id,
+                Groups.cluster_id,
+                Scenes.cluster_id,
+                Basic.cluster_id,
+                MANUFACTURER_SPECIFIC_CLUSTER_ID_1
+            ],
+        },
+    }
+
+    replacement = {
+        'endpoints': {
+            1: {
+                'manufacturer': 'Lutron',
+                'model': 'LZL4BWHL01 Remote',
+                'profile_id': zll.PROFILE_ID,
+                'device_type': zll.DeviceType.CONTROLLER,
+                'input_clusters': [
+                    Basic.cluster_id,
+                    LightLink.cluster_id,
+                    MANUFACTURER_SPECIFIC_CLUSTER_ID_1,
+                    MANUFACTURER_SPECIFIC_CLUSTER_ID_2
+                ],
+                'output_clusters': [
+                    LightLink.cluster_id,
+                    Identify.cluster_id,
+                    OnOffGroupCluster,
+                    LevelControlGroupCluster,
+                    Groups.cluster_id,
+                    Scenes.cluster_id,
+                    Basic.cluster_id,
+                    MANUFACTURER_SPECIFIC_CLUSTER_ID_1
+                ],
+            }
+        },
+    }
+
+
+class LutronLZL4BWHL01Remote2(LutronLZL4BWHL01Remote):
+    """Custom device representing Lutron LZL4BWHL01 Remote."""
+
+    signature = {
+        1: {
+            **LutronLZL4BWHL01Remote.signature[1],
+            'manufacturer': ' Lutron',  # Some remotes report a leading space
+        }
+    }


Test patch: