Repository: zigpy/zha-device-handlers

Issue:
[Device Support Request] Legrand Dimmer switch w/o neutral for sw_build_id=0x0039 with and without Ballast cluster
**Is your feature request related to a problem? Please describe.**
Legrand native Home+Control App with the recent firmware (at least with 0x39) allows to setup min and max light levels. This is not possible this the current implementation of Legrand dimmer quirk. 

I have updated firmware on some of my devices and connected them back to ZHA. Some devices have Ballast cluster with `min_level` and `max_level` attributes, but some devices with exactly the same firmware do not have such cluster. Please refer to device signatures below.

This issue is continuation of #497 

**Describe the solution you'd like**
Add support of Ballas cluster. 

Actually, if device doesn't report Ballast cluster, it still can be enabled. I just added it to `replacement`, and it worked.

**Device signature - this can be acquired by removing the device from ZHA and pairing it again from the add devices screen. Be sure to add the entire content of the log panel after pairing the device to a code block below this line.**

In order to get the accurate signature, I disabled all Legrand quirks.

Device with firmware 0x39 but **without** Ballast cluster
```
{
  "node_descriptor": "NodeDescriptor(logical_type=<LogicalType.Router: 1>, complex_descriptor_available=0, user_descriptor_available=1, reserved=0, aps_flags=0, frequency_band=<FrequencyBand.Freq2400MHz: 8>, mac_capability_flags=<MACCapabilityFlags.AllocateAddress|RxOnWhenIdle|MainsPowered|FullFunctionDevice: 142>, manufacturer_code=4129, maximum_buffer_size=89, maximum_incoming_transfer_size=63, server_mask=10752, maximum_outgoing_transfer_size=63, descriptor_capability_field=<DescriptorCapability.NONE: 0>, *allocate_address=True, *is_alternate_pan_coordinator=False, *is_coordinator=False, *is_end_device=False, *is_full_function_device=True, *is_mains_powered=True, *is_receiver_on_when_idle=True, *is_router=True, *is_security_capable=False)",
  "endpoints": {
    "1": {
      "profile_id": 260,
      "device_type": "0x0100",
      "in_clusters": [
        "0x0000",
        "0x0003",
        "0x0004",
        "0x0005",
        "0x0006",
        "0x0008",
        "0x000f",
        "0xfc01"
      ],
      "out_clusters": [
        "0x0000",
        "0x0005",
        "0x0006",
        "0x0019",
        "0xfc01"
      ]
    },
    "242": {
      "profile_id": 41440,
      "device_type": "0x0066",
      "in_clusters": [
        "0x0021"
      ],
      "out_clusters": [
        "0x0021"
      ]
    }
  },
  "manufacturer": " Legrand",
  "model": " Dimmer switch w/o neutral",
  "class": "zigpy.device.Device"
}
```

Device with firmware 0x39 but *with* Ballast cluster

```
{
  "node_descriptor": "NodeDescriptor(logical_type=<LogicalType.Router: 1>, complex_descriptor_available=0, user_descriptor_available=1, reserved=0, aps_flags=0, frequency_band=<FrequencyBand.Freq2400MHz: 8>, mac_capability_flags=<MACCapabilityFlags.AllocateAddress|RxOnWhenIdle|MainsPowered|FullFunctionDevice: 142>, manufacturer_code=4129, maximum_buffer_size=89, maximum_incoming_transfer_size=63, server_mask=10752, maximum_outgoing_transfer_size=63, descriptor_capability_field=<DescriptorCapability.NONE: 0>, *allocate_address=True, *is_alternate_pan_coordinator=False, *is_coordinator=False, *is_end_device=False, *is_full_function_device=True, *is_mains_powered=True, *is_receiver_on_when_idle=True, *is_router=True, *is_security_capable=False)",
  "endpoints": {
    "1": {
      "profile_id": 260,
      "device_type": "0x0100",
      "in_clusters": [
        "0x0000",
        "0x0003",
        "0x0004",
        "0x0005",
        "0x0006",
        "0x0008",
        "0x000f",
        "0x0301",
        "0xfc01"
      ],
      "out_clusters": [
        "0x0000",
        "0x0005",
        "0x0006",
        "0x0019",
        "0xfc01"
      ]
    },
    "242": {
      "profile_id": 41440,
      "device_type": "0x0066",
      "in_clusters": [
        "0x0021"
      ],
      "out_clusters": [
        "0x0021"
      ]
    }
  },
  "manufacturer": " Legrand",
  "model": " Dimmer switch w/o neutral",
  "class": "zigpy.device.Device"
}
```



Discussion:

Resolving patch:
diff --git a/zhaquirks/legrand/dimmer.py b/zhaquirks/legrand/dimmer.py
--- a/zhaquirks/legrand/dimmer.py
+++ b/zhaquirks/legrand/dimmer.py
@@ -5,6 +5,7 @@
 from zigpy.zcl.clusters.general import (
     Basic,
     BinaryInput,
+    GreenPowerProxy,
     Groups,
     Identify,
     LevelControl,
@@ -12,6 +13,7 @@
     Ota,
     Scenes,
 )
+from zigpy.zcl.clusters.lighting import Ballast
 from zigpy.zcl.clusters.manufacturer_specific import ManufacturerSpecificCluster
 
 from zhaquirks.const import (
@@ -132,8 +134,8 @@ class DimmerWithoutNeutral2(DimmerWithoutNeutral):
     }
 
 
-class DimmerWithoutNeutral3(DimmerWithoutNeutral):
-    """Dimmer switch w/o neutral (at least for firmware 0x2e3)."""
+class DimmerWithoutNeutral3(CustomDevice):
+    """Dimmer switch w/o neutral (at least for firmware 0x2e and above)."""
 
     signature = {
         #  <SimpleDescriptor endpoint=1 profile=260 device_type=256
@@ -171,6 +173,118 @@ class DimmerWithoutNeutral3(DimmerWithoutNeutral):
             },
         },
     }
+    replacement = {
+        ENDPOINTS: {
+            1: {
+                PROFILE_ID: zha.PROFILE_ID,
+                DEVICE_TYPE: zha.DeviceType.ON_OFF_LIGHT,
+                INPUT_CLUSTERS: [
+                    Basic.cluster_id,
+                    Identify.cluster_id,
+                    Groups.cluster_id,
+                    OnOff.cluster_id,
+                    LevelControl.cluster_id,
+                    Scenes.cluster_id,
+                    BinaryInput.cluster_id,
+                    # Some devices with firmware 0x39 have Ballast cluster,
+                    # but some of them don't. But in any case Ballast works,
+                    # if we add it here.
+                    Ballast.cluster_id,
+                    LegrandCluster,
+                ],
+                OUTPUT_CLUSTERS: [
+                    Basic.cluster_id,
+                    LegrandCluster,
+                    Ota.cluster_id,
+                    OnOff.cluster_id,
+                    Scenes.cluster_id,
+                ],
+            },
+            # Green Power End Point
+            242: {
+                PROFILE_ID: 0xA1E0,
+                DEVICE_TYPE: 0x0066,  # GP Combo Minimum
+                INPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
+                OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
+            },
+        },
+    }
+
+
+class DimmerWithoutNeutralAndBallast(CustomDevice):
+    """Dimmer switch w/o neutral (at least for firmware 0x39)."""
+
+    signature = {
+        #  <SimpleDescriptor endpoint=1 profile=260 device_type=256
+        # device_version=1
+        # input_clusters=[0, 3, 4, 5, 6, 8, 15, 769, 64513]
+        # output_clusters=[0, 5, 6, 25, 64513]>
+        MODELS_INFO: [(f" {LEGRAND}", " Dimmer switch w/o neutral")],
+        ENDPOINTS: {
+            1: {
+                PROFILE_ID: zha.PROFILE_ID,
+                DEVICE_TYPE: zha.DeviceType.ON_OFF_LIGHT,
+                INPUT_CLUSTERS: [
+                    Basic.cluster_id,
+                    Identify.cluster_id,
+                    Groups.cluster_id,
+                    OnOff.cluster_id,
+                    LevelControl.cluster_id,
+                    Scenes.cluster_id,
+                    BinaryInput.cluster_id,
+                    Ballast.cluster_id,
+                    MANUFACTURER_SPECIFIC_CLUSTER_ID,
+                ],
+                OUTPUT_CLUSTERS: [
+                    Basic.cluster_id,
+                    MANUFACTURER_SPECIFIC_CLUSTER_ID,
+                    Ota.cluster_id,
+                    OnOff.cluster_id,
+                    Scenes.cluster_id,
+                ],
+            },
+            242: {
+                PROFILE_ID: 41440,
+                DEVICE_TYPE: 0x0066,
+                INPUT_CLUSTERS: [0x0021],
+                OUTPUT_CLUSTERS: [0x0021],
+            },
+        },
+    }
+
+    replacement = {
+        ENDPOINTS: {
+            1: {
+                PROFILE_ID: zha.PROFILE_ID,
+                DEVICE_TYPE: zha.DeviceType.ON_OFF_LIGHT,
+                INPUT_CLUSTERS: [
+                    Basic.cluster_id,
+                    Identify.cluster_id,
+                    Groups.cluster_id,
+                    OnOff.cluster_id,
+                    LevelControl.cluster_id,
+                    Scenes.cluster_id,
+                    BinaryInput.cluster_id,
+                    Ballast.cluster_id,
+                    LegrandCluster,
+                ],
+                OUTPUT_CLUSTERS: [
+                    Basic.cluster_id,
+                    LegrandCluster,
+                    Ota.cluster_id,
+                    OnOff.cluster_id,
+                    Scenes.cluster_id,
+                ],
+            },
+            # Green Power End Point
+            242: {
+                PROFILE_ID: 0xA1E0,
+                DEVICE_TYPE: 0x0066,  # GP Combo Minimum
+                INPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
+                OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
+            },
+        },
+    }
 
 
 class DimmerWithNeutral(DimmerWithoutNeutral):


Test patch: