Repository: zigpy/zha-device-handlers

Issue:
[Device Support Request] Adeo LXEK-5 remote (Lexman Enki)
**Is your feature request related to a problem? Please describe.**
I have this [Adeo LXEK-5 Zigbee remote](https://zigbee.blakadder.com/ADEO_HR-C99C-Z-C045.html) for color lights control (device type : Color Control 0x800).
<img src="https://zigbee.blakadder.com/assets/images/devices/ADEO_HR-C99C-Z-C045.jpg" width="400"/>
- Model: HR-C99C-Z-C045
- Zigbee ID: "LXEK-5"
- Zigbee Device Type: "Color Control (0x800)"
- Brand: Lexman
- Manufacturer: Adeo (Leroy Merlin - french home improvement store)

The available buttons on the remote are:
Buttons | Cluster
------------ | -------------
On/Off | 0x0006 "OnOff"
Brightness Up / Down / Stop | 0x0008 "LevelControl"
Saturation/Hue/Color Temperature : Up / Down | 0x0300 "Color"
Scene 1 to 4 | 0xfe00 Manufacturer specific

Device get discovered in ZHA and added successfully but :
- There is no [Device automation triggers](https://github.com/zigpy/zha-device-handlers#how-device_automation_triggers-work) defined for any buttons, so they can be only detected through raw zha_events
- "Scene" buttons that use a manufacturer-specific cluster do not work even with raw zha_event (logs added below)

**Describe the solution you'd like**

- Port the functionality that is implemented in [Zigbee2mqtt](https://www.zigbee2mqtt.io/devices/HR-C99C-Z-C045.html) and in [Deconz](https://github.com/dresden-elektronik/deconz-rest-plugin/pull/4587) to get ZHA events for "Scene" buttons 1 to 4 on manufacturer-specific Cluster 0xfe00. Value from the cluster is in the range 0x0a (scene button 1) to 0x0d (scene button 4).
- Additionally, add Device automation triggers for all buttons.

**Device signature**
```javascript
{
  "node_descriptor": "NodeDescriptor(byte1=2, byte2=64, mac_capability_flags=128, manufacturer_code=4727, maximum_buffer_size=82, maximum_incoming_transfer_size=82, server_mask=11264, maximum_outgoing_transfer_size=82, descriptor_capability_field=0, *allocate_address=True, *complex_descriptor_available=False, *is_alternate_pan_coordinator=False, *is_coordinator=False, *is_end_device=True, *is_full_function_device=False, *is_mains_powered=False, *is_receiver_on_when_idle=False, *is_router=False, *is_security_capable=False, *is_valid=True, *logical_type=<LogicalType.EndDevice: 2>, *user_descriptor_available=False)",
  "endpoints": {
    "1": {
      "profile_id": 260,
      "device_type": "0x0800",
      "in_clusters": [
        "0x0000",
        "0x0001",
        "0x0003",
        "0x0b05",
        "0x1000",
        "0xfd01"
      ],
      "out_clusters": [
        "0x0003",
        "0x0004",
        "0x0006",
        "0x0008",
        "0x0019",
        "0x0300",
        "0x1000"
      ]
    }
  },
  "manufacturer": "ADEO",
  "model": "LXEK-5",
  "class": "zigpy.device.Device"
}
```

**Additional context**
HA debug logs when pressing "OFF" button on remote:
```
2021-05-19 19:45:36 DEBUG (MainThread) [bellows.ezsp.protocol] Application frame 69 (incomingMessageHandler) received: b'000401060001014000000085ffd85413ffff03010f00'
2021-05-19 19:45:36 DEBUG (MainThread) [bellows.zigbee.application] Received incomingMessageHandler frame with [<EmberIncomingMessageType.INCOMING_UNICAST: 0>, EmberApsFrame(profileId=260, clusterId=6, sourceEndpoint=1, destinationEndpoint=1, options=<EmberApsOption.APS_OPTION_RETRY: 64>, groupId=0, sequence=133), 255, -40, 0x1354, 255, 255, b'\x01\x0f\x00']
2021-05-19 19:45:36 DEBUG (MainThread) [zigpy.zcl] [0x1354:1:0x0006] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=CLUSTER_COMMAND manufacturer_specific=False is_reply=False disable_default_response=False> manufacturer=None tsn=15 command_id=0>
2021-05-19 19:45:36 DEBUG (MainThread) [zigpy.zcl] [0x1354:1:0x0006] ZCL request 0x0000: []
2021-05-19 19:45:36 DEBUG (MainThread) [zigpy.zcl] [0x1354:1:0x0006] No handler for cluster command 0
```
A zha_event is emitted:
```
{
    "event_type": "zha_event",
    "data": {
        "device_ieee": "60:a4:23:ff:fe:##:##:##",
        "unique_id": "60:a4:23:ff:fe:##:##:##:1:0x0006",
        "device_id": "0501c9e8666648ccd073ffc9773f2962",
        "endpoint_id": 1,
        "cluster_id": 6,
        "command": "off",
        "args": []
    },
    "origin": "LOCAL",
    "time_fired": "2021-05-19T17:45:37.568612+00:00",
    "context": {
        "id": "0351af6941ff8c7ad937b6386aa87405",
        "parent_id": null,
        "user_id": null
    }
}
```
HA debug logs when pressing the "Scene 3" (0x0c) button on remote:
```
2021-05-19 20:02:06 DEBUG (MainThread) [bellows.ezsp.protocol] Application frame 69 (incomingMessageHandler) received: b'00040100fe0101400000008affdf5413ffff070d771214000c01'
2021-05-19 20:02:06 DEBUG (MainThread) [bellows.zigbee.application] Received incomingMessageHandler frame with [<EmberIncomingMessageType.INCOMING_UNICAST: 0>, EmberApsFrame(profileId=260, clusterId=65024, sourceEndpoint=1, destinationEndpoint=1, options=<EmberApsOption.APS_OPTION_RETRY: 64>, groupId=0, sequence=138), 255, -33, 0x1354, 255, 255, b'\rw\x12\x14\x00\x0c\x01']
2021-05-19 20:02:06 DEBUG (MainThread) [zigpy.device] Ignoring message (b'0d771214000c01') on cluster 65024: unknown endpoint or cluster id: 'No cluster ID 0xfe00 on (60:a4:23:ff:fe:70:39:a0, 1)'
```
NO zha_event is emitted.


Discussion:
If i understood correctly, a quirk needs to be made for this device. It seems a bit complicated at first sight for me, so i would appreciate any help ! I can also provide additional logs and test on my HomeAssistant platform with ZHA.
There hasn't been any activity on this issue recently. Due to the high number of incoming GitHub notifications, we have to clean some of the old issues, as many of them have already been resolved with the latest updates. Please make sure to update to the latest version and check if that solves the issue. Let us know if that works for you by adding a comment 👍 This issue has now been marked as stale and will be closed if no further activity occurs. Thank you for your contributions.

Resolving patch:
diff --git a/zhaquirks/adeo/__init__.py b/zhaquirks/adeo/__init__.py
new file mode 100644
--- /dev/null
+++ b/zhaquirks/adeo/__init__.py
@@ -0,0 +1 @@
+"""Module for ADEO devices."""
diff --git a/zhaquirks/adeo/color_controller.py b/zhaquirks/adeo/color_controller.py
new file mode 100644
--- /dev/null
+++ b/zhaquirks/adeo/color_controller.py
@@ -0,0 +1,252 @@
+"""Device handler for ADEO Lexman LXEK-5 (HR-C99C-Z-C045) & ZBEK-26 (HR-C99C-Z-C045-B) color controllers."""
+from typing import Any, List, Optional, Union
+
+from zigpy.profiles import zha
+from zigpy.quirks import CustomDevice
+import zigpy.types as t
+from zigpy.zcl import foundation
+from zigpy.zcl.clusters.general import (
+    Basic,
+    Groups,
+    Identify,
+    LevelControl,
+    OnOff,
+    Ota,
+    PowerConfiguration,
+    Scenes,
+)
+from zigpy.zcl.clusters.homeautomation import Diagnostic
+from zigpy.zcl.clusters.lighting import Color
+from zigpy.zcl.clusters.lightlink import LightLink
+
+from zhaquirks import Bus, EventableCluster
+from zhaquirks.const import (
+    ARGS,
+    BUTTON_1,
+    BUTTON_2,
+    BUTTON_3,
+    BUTTON_4,
+    CLUSTER_ID,
+    COMMAND,
+    COMMAND_OFF,
+    COMMAND_ON,
+    COMMAND_STEP,
+    COMMAND_STEP_COLOR_TEMP,
+    COMMAND_STEP_HUE,
+    COMMAND_STEP_SATURATION,
+    DEVICE_TYPE,
+    DIM_DOWN,
+    DIM_UP,
+    ENDPOINT_ID,
+    ENDPOINTS,
+    INPUT_CLUSTERS,
+    MODELS_INFO,
+    OUTPUT_CLUSTERS,
+    PROFILE_ID,
+    SHORT_PRESS,
+    TURN_OFF,
+    TURN_ON,
+    ZHA_SEND_EVENT,
+)
+
+COLOR_UP = "color_up"
+COLOR_DOWN = "color_down"
+SATURATION_UP = "saturation_up"
+SATURATION_DOWN = "saturation_down"
+HUE_LEFT = "hue_left"
+HUE_RIGHT = "hue_right"
+
+MANUFACTURER_SPECIFIC_CLUSTER_ID = 0xFE00  # decimal = 65024
+SCENE_NO_GROUP = 0x0000
+
+
+class AdeoManufacturerCluster(EventableCluster):
+    """Custom manufacturer cluster (used for preset buttons 1-4)."""
+
+    cluster_id = MANUFACTURER_SPECIFIC_CLUSTER_ID
+    name = "AdeoManufacturerCluster"
+    ep_attribute = "adeo_manufacturer_cluster"
+    manufacturer_client_commands = {0x0000: ("preset", (t.uint8_t, t.uint8_t), False)}
+
+    def handle_cluster_request(
+        self,
+        hdr: foundation.ZCLHeader,
+        args: List[Any],
+        *,
+        dst_addressing: Optional[
+            Union[t.Addressing.Group, t.Addressing.IEEE, t.Addressing.NWK]
+        ] = None,
+    ):
+        """Handle the cluster command."""
+        if hdr.command_id == 0x0000:
+            self.endpoint.device.scenes_bus.listener_event(
+                "listener_event", ZHA_SEND_EVENT, "view", [SCENE_NO_GROUP, args[0]]
+            )
+        else:
+            super().handle_cluster_request(hdr, args, dst_addressing=dst_addressing)
+
+
+class AdeoScenesCluster(Scenes, EventableCluster):
+    """Scenes cluster to map preset buttons to the "view" command."""
+
+    cluster_id = Scenes.cluster_id
+
+    def __init__(self, *args, **kwargs):
+        """Init."""
+        super().__init__(*args, **kwargs)
+        self.endpoint.device.scenes_bus.add_listener(self)
+
+
+class AdeoColorController(CustomDevice):
+    """Custom device representing ADEO color controller."""
+
+    def __init__(self, *args, **kwargs):
+        """Init."""
+        self.scenes_bus = Bus()
+        super().__init__(*args, **kwargs)
+
+    signature = {
+        #  <SimpleDescriptor endpoint=1 profile=260 device_type=2048
+        #  device_version=1
+        #  input_clusters=[0, 1, 3, 2821, 4096, 64769]
+        #  output_clusters=[3, 4, 6, 8, 25, 768, 4096]>
+        MODELS_INFO: [("ADEO", "LXEK-5"), ("ADEO", "ZBEK-26")],
+        ENDPOINTS: {
+            1: {
+                PROFILE_ID: zha.PROFILE_ID,  # 260
+                DEVICE_TYPE: zha.DeviceType.COLOR_CONTROLLER,  # 2048
+                INPUT_CLUSTERS: [
+                    Basic.cluster_id,  # 0
+                    PowerConfiguration.cluster_id,  # 1
+                    Identify.cluster_id,  # 3
+                    Diagnostic.cluster_id,  # 2821
+                    LightLink.cluster_id,  # 4096
+                    0xFD01,  # 64769
+                ],
+                OUTPUT_CLUSTERS: [
+                    Identify.cluster_id,  # 3
+                    Groups.cluster_id,  # 4
+                    OnOff.cluster_id,  # 6
+                    LevelControl.cluster_id,  # 8
+                    Ota.cluster_id,  # 25
+                    Color.cluster_id,  # 768
+                    LightLink.cluster_id,  # 4096
+                ],
+            }
+        },
+    }
+
+    replacement = {
+        ENDPOINTS: {
+            1: {
+                PROFILE_ID: zha.PROFILE_ID,
+                DEVICE_TYPE: zha.DeviceType.COLOR_CONTROLLER,
+                INPUT_CLUSTERS: [
+                    Basic.cluster_id,  # 0
+                    PowerConfiguration.cluster_id,  # 1
+                    Identify.cluster_id,  # 3
+                    Diagnostic.cluster_id,  # 2821
+                    LightLink.cluster_id,  # 4096
+                    0xFD01,  # 64769
+                ],
+                OUTPUT_CLUSTERS: [
+                    Identify.cluster_id,  # 3
+                    Groups.cluster_id,  # 4
+                    AdeoScenesCluster,  # 5
+                    OnOff.cluster_id,  # 6
+                    LevelControl.cluster_id,  # 8
+                    Ota.cluster_id,  # 25
+                    Color.cluster_id,  # 768
+                    LightLink.cluster_id,  # 4096
+                    AdeoManufacturerCluster,  # 65024
+                ],
+            }
+        },
+    }
+
+    device_automation_triggers = {
+        (SHORT_PRESS, TURN_ON): {
+            COMMAND: COMMAND_ON,
+            CLUSTER_ID: 6,  # OnOff.cluster_id
+            ENDPOINT_ID: 1,
+            ARGS: [],
+        },
+        (SHORT_PRESS, TURN_OFF): {
+            COMMAND: COMMAND_OFF,
+            CLUSTER_ID: 6,  # OnOff.cluster_id
+            ENDPOINT_ID: 1,
+            ARGS: [],
+        },
+        (SHORT_PRESS, DIM_UP): {
+            COMMAND: COMMAND_STEP,
+            CLUSTER_ID: 8,  # LevelControl.cluster_id
+            ENDPOINT_ID: 1,
+            ARGS: [0, 26, 5],
+        },
+        (SHORT_PRESS, DIM_DOWN): {
+            COMMAND: COMMAND_STEP,
+            CLUSTER_ID: 8,  # LevelControl.cluster_id
+            ENDPOINT_ID: 1,
+            ARGS: [1, 26, 5],
+        },
+        (SHORT_PRESS, COLOR_UP): {
+            COMMAND: COMMAND_STEP_COLOR_TEMP,
+            CLUSTER_ID: 768,  # Color.cluster_id
+            ENDPOINT_ID: 1,
+            ARGS: [3, 22, 5, 153, 370],
+        },
+        (SHORT_PRESS, COLOR_DOWN): {
+            COMMAND: COMMAND_STEP_COLOR_TEMP,
+            CLUSTER_ID: 768,  # Color.cluster_id
+            ENDPOINT_ID: 1,
+            ARGS: [1, 22, 5, 153, 370],
+        },
+        (SHORT_PRESS, SATURATION_UP): {
+            COMMAND: COMMAND_STEP_SATURATION,
+            CLUSTER_ID: 768,  # Color.cluster_id
+            ENDPOINT_ID: 1,
+            ARGS: [1, 26, 5],
+        },
+        (SHORT_PRESS, SATURATION_DOWN): {
+            COMMAND: COMMAND_STEP_SATURATION,
+            CLUSTER_ID: 768,  # Color.cluster_id
+            ENDPOINT_ID: 1,
+            ARGS: [3, 26, 5],
+        },
+        (SHORT_PRESS, HUE_LEFT): {
+            COMMAND: COMMAND_STEP_HUE,
+            CLUSTER_ID: 768,  # Color.cluster_id
+            ENDPOINT_ID: 1,
+            ARGS: [3, 22, 5],
+        },
+        (SHORT_PRESS, HUE_RIGHT): {
+            COMMAND: COMMAND_STEP_HUE,
+            CLUSTER_ID: 768,  # Color.cluster_id
+            ENDPOINT_ID: 1,
+            ARGS: [1, 22, 5],
+        },
+        (SHORT_PRESS, BUTTON_1): {
+            COMMAND: "view",
+            CLUSTER_ID: 5,  # Scenes.cluster_id
+            ENDPOINT_ID: 1,
+            ARGS: [0, 0xA],
+        },
+        (SHORT_PRESS, BUTTON_2): {
+            COMMAND: "view",
+            CLUSTER_ID: 5,  # Scenes.cluster_id
+            ENDPOINT_ID: 1,
+            ARGS: [0, 0xB],
+        },
+        (SHORT_PRESS, BUTTON_3): {
+            COMMAND: "view",
+            CLUSTER_ID: 5,  # Scenes.cluster_id
+            ENDPOINT_ID: 1,
+            ARGS: [0, 0xC],
+        },
+        (SHORT_PRESS, BUTTON_4): {
+            COMMAND: "view",
+            CLUSTER_ID: 5,  # Scenes.cluster_id
+            ENDPOINT_ID: 1,
+            ARGS: [0, 0xD],
+        },
+    }
diff --git a/zhaquirks/const.py b/zhaquirks/const.py
--- a/zhaquirks/const.py
+++ b/zhaquirks/const.py
@@ -52,7 +52,9 @@
 COMMAND_SINGLE = "single"
 COMMAND_STEP = "step"
 COMMAND_STEP_COLOR_TEMP = "step_color_temp"
+COMMAND_STEP_HUE = "step_hue"
 COMMAND_STEP_ON_OFF = "step_with_on_off"
+COMMAND_STEP_SATURATION = "step_saturation"
 COMMAND_STOP = "stop"
 COMMAND_STOP_MOVE_STEP = "stop_move_step"
 COMMAND_TILT = "Tilt"


Test patch: