Repository: zigpy/zha-device-handlers

Issue:
[Device Support Request] Aqara LLKZMK11LM 2 ways relay (relay_c2acn01) Interlock support
**Is your feature request related to a problem? Please describe.**
Prohibit the possibility to power on the 2 ways at the same time

**Describe the solution you'd like**
Add the interlock option to HA

- [x] Interlock

**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.**
```
Device 0x0000 (00:15:8d:00:06:32:74:40) left the network
Device 0x010e (00:15:8d:00:06:32:74:40) joined the network
Device 00:15:8d:00:06:32:74:40 changed id (0x6ac2 => 0x010e)
Canceling old initialize call
[0x010e] Requesting 'Node Descriptor'
Tries remaining: 2
[0x010e] Extending timeout for 0xa5 request
[0x010e:zdo] ZDO request ZDOCmd.Device_annce: [0x010E, 00:15:8d:00:06:32:74:40, 142]
[0x010e] Node Descriptor: NodeDescriptor(byte1=1, byte2=64, mac_capability_flags=142, manufacturer_code=4151, maximum_buffer_size=127, maximum_incoming_transfer_size=100, server_mask=0, maximum_outgoing_transfer_size=100, descriptor_capability_field=0)
[0x010e] Discovering endpoints
Tries remaining: 3
[0x010e] Discovered endpoints: [1, 2]
[0x010e:1] Discovering endpoint information
Tries remaining: 3
[0x010e:1] Discovered endpoint information: SizePrefixedSimpleDescriptor(endpoint=1, profile=260, device_type=257, device_version=2, input_clusters=[0, 3, 4, 5, 1, 2, 10, 6, 16, 2820, 12], output_clusters=[25, 10])
[0x010e:1:0x0000] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=False is_reply=True disable_default_response=True> manufacturer=None tsn=171 command_id=Command.Read_Attributes_rsp>
[0x010e:1] Manufacturer: LUMI
[0x010e:1] Model: lumi.relay.c2acn01
[0x010e:2] Discovering endpoint information
Tries remaining: 3
[0x010e:1:0x0000] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=False is_reply=True disable_default_response=True> manufacturer=None tsn=171 command_id=Command.Read_Attributes_rsp>
[0x010e:1:0x0000] ZCL request 0x0001: [[ReadAttributeRecord(attrid=4, status=<Status.SUCCESS: 0>, value=<TypeValue type=CharacterString, value=LUMI>), ReadAttributeRecord(attrid=5, status=<Status.SUCCESS: 0>, value=<TypeValue type=CharacterString, value=lumi.relay.c2acn01>)]]
[0x010e:1:0x0000] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=False is_reply=True disable_default_response=True> manufacturer=None tsn=171 command_id=Command.Read_Attributes_rsp>
[0x010e:1:0x0000] ZCL request 0x0001: [[ReadAttributeRecord(attrid=4, status=<Status.SUCCESS: 0>, value=<TypeValue type=CharacterString, value=LUMI>), ReadAttributeRecord(attrid=5, status=<Status.SUCCESS: 0>, value=<TypeValue type=CharacterString, value=lumi.relay.c2acn01>)]]
[0x010e:2] Discovered endpoint information: SizePrefixedSimpleDescriptor(endpoint=2, profile=260, device_type=257, device_version=2, input_clusters=[6, 16, 4, 5], output_clusters=[])
Checking quirks for LUMI lumi.relay.c2acn01 (00:15:8d:00:06:32:74:40)
Considering <class 'zhaquirks.xiaomi.aqara.relay_c2acn01.Relay'>
Found custom device replacement for 00:15:8d:00:06:32:74:40: <class 'zhaquirks.xiaomi.aqara.relay_c2acn01.Relay'>
device - 0x010E:00:15:8d:00:06:32:74:40 entering async_device_initialized - is_new_join: True
device - 0x010E:00:15:8d:00:06:32:74:40 has joined the ZHA zigbee network
[0x010E](lumi.relay.c2acn01): started configuration
[0x010E:ZDO](lumi.relay.c2acn01): 'async_configure' stage succeeded
[0x010e:1:0x0000] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=False is_reply=True disable_default_response=True> manufacturer=None tsn=3 command_id=Command.Report_Attributes>
[0x010e:1:0x0000] ZCL request 0x000a: [[Attribute(attrid=5, value=<TypeValue type=CharacterString, value=lumi.relay.c2acn01>), Attribute(attrid=1, value=<TypeValue type=uint8_t, value=1>)]]
[0x010e:1:0x0000] Attribute report received: model=lumi.relay.c2acn01, app_version=1
[0x010e:1:0x0000] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=False is_reply=True disable_default_response=True> manufacturer=None tsn=3 command_id=Command.Report_Attributes>
[0x010e:1:0x0000] ZCL request 0x000a: [[Attribute(attrid=5, value=<TypeValue type=CharacterString, value=lumi.relay.c2acn01>), Attribute(attrid=1, value=<TypeValue type=uint8_t, value=1>)]]
[0x010e:1:0x0000] Attribute report received: model=lumi.relay.c2acn01, app_version=1
[0x010e:1:0x0000] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=False is_reply=True disable_default_response=True> manufacturer=None tsn=3 command_id=Command.Report_Attributes>
[0x010e:1:0x0000] ZCL request 0x000a: [[Attribute(attrid=5, value=<TypeValue type=CharacterString, value=lumi.relay.c2acn01>), Attribute(attrid=1, value=<TypeValue type=uint8_t, value=1>)]]
[0x010e:1:0x0000] Attribute report received: model=lumi.relay.c2acn01, app_version=1
[0x010E:1:0x0006]: skipping channel configuration
[0x010E:1:0x0000]: skipping channel configuration
[0x010E:1:0x0b04]: skipping channel configuration
[0x010E:1:0x0019]: skipping channel configuration
[0x010E:2:0x0006]: skipping channel configuration
[0x010E:1:0x0006]: 'async_configure' stage succeeded
[0x010E:1:0x0000]: 'async_configure' stage succeeded
[0x010E:1:0x0b04]: 'async_configure' stage succeeded
[0x010E:1:0x0019]: 'async_configure' stage succeeded
[0x010E:2:0x0006]: 'async_configure' stage succeeded
[0x010E](lumi.relay.c2acn01): completed configuration
[0x010E](lumi.relay.c2acn01): stored in registry: ZhaDeviceEntry(name='LUMI lumi.relay.c2acn01', ieee='00:15:8d:00:06:32:74:40', last_seen=1607953705.7980297)
[0x010E](lumi.relay.c2acn01): started initialization
[0x010E:ZDO](lumi.relay.c2acn01): 'async_initialize' stage succeeded
[0x010e:1:0x0006] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=False is_reply=True disable_default_response=True> manufacturer=None tsn=175 command_id=Command.Read_Attributes_rsp>
[0x010e:1:0x0006] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=False is_reply=True disable_default_response=True> manufacturer=None tsn=175 command_id=Command.Read_Attributes_rsp>
[0x010e] Invalid state on future for 0xaf seq -- probably duplicate response
[0x010e:2:0x0006] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=False is_reply=True disable_default_response=True> manufacturer=None tsn=177 command_id=Command.Read_Attributes_rsp>
[0x010e:2:0x0006] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=False is_reply=True disable_default_response=True> manufacturer=None tsn=177 command_id=Command.Read_Attributes_rsp>
[0x010e:2:0x0006] ZCL request 0x0001: [[ReadAttributeRecord(attrid=0, status=<Status.SUCCESS: 0>, value=<TypeValue type=Bool, value=Bool.false>)]]
[0x010E:2:0x0006]: 'async_initialize' stage succeeded
[0x010e:1:0x0000] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=True is_reply=True disable_default_response=True> manufacturer=4447 tsn=4 command_id=Command.Report_Attributes>
[0x010e:1:0x0000] ZCL request 0x000a: [[Attribute(attrid=65281, value=<TypeValue type=CharacterString, value=(>)]]
[0x010e:1:0x0000] Attribute report received: 65281=(
[0x010e:1:0x0000] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=True is_reply=True disable_default_response=True> manufacturer=4447 tsn=4 command_id=Command.Report_Attributes>
[0x010e:1:0x0000] ZCL request 0x000a: [[Attribute(attrid=65281, value=<TypeValue type=CharacterString, value=(>)]]
[0x010e:1:0x0000] Attribute report received: 65281=(
[0x010e] Delivery error for seq # 0xaf, on endpoint id 1 cluster 0x0006: ZiGate doesn't answer to command
[0x010E:1:0x0006]: 'async_initialize' stage succeeded
[0x010E:1:0x0000]: 'async_initialize' stage succeeded
[0x010E:1:0x0b04]: 'async_initialize' stage succeeded
[0x010E:1:0x0019]: 'async_initialize' stage succeeded
[0x010E](lumi.relay.c2acn01): power source: Mains
[0x010E](lumi.relay.c2acn01): completed initialization
[0x010E:1:0x0b04]: async_update
[0x010E:1:0x0006]: attempting to update onoff state - from cache: False
[0x010E:2:0x0006]: attempting to update onoff state - from cache: False
[0x010e:1:0x0006] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=False is_reply=True disable_default_response=True> manufacturer=None tsn=179 command_id=Command.Read_Attributes_rsp>
[0x010e:1:0x0006] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=False is_reply=True disable_default_response=True> manufacturer=None tsn=179 command_id=Command.Read_Attributes_rsp>
[0x010e] Invalid state on future for 0xb3 seq -- probably duplicate response
[0x010e:2:0x0006] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=False is_reply=True disable_default_response=True> manufacturer=None tsn=181 command_id=Command.Read_Attributes_rsp>
[0x010e:2:0x0006] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=False is_reply=True disable_default_response=True> manufacturer=None tsn=181 command_id=Command.Read_Attributes_rsp>
[0x010e:2:0x0006] ZCL request 0x0001: [[ReadAttributeRecord(attrid=0, status=<Status.SUCCESS: 0>, value=<TypeValue type=Bool, value=Bool.false>)]]
[0xD4E6:1:0x0b04]: async_update
[0x6E52:1:0x0b04]: async_update
[0x010E:1:0x0b04]: async_update
[0xd4e6:1:0x0b04] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=False is_reply=True disable_default_response=True> manufacturer=None tsn=183 command_id=Command.Read_Attributes_rsp>
[0x6e52:1:0x0b04] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=False is_reply=True disable_default_response=True> manufacturer=None tsn=185 command_id=Command.Read_Attributes_rsp>
[0x010e] Delivery error for seq # 0xb3, on endpoint id 1 cluster 0x0006: ZiGate doesn't answer to command
[0xd4e6] Delivery error for seq # 0xb7, on endpoint id 1 cluster 0x0b04: ZiGate doesn't answer to command
[0xD4E6:1:0x0b04]: async_update
[0x6E52:1:0x0b04]: async_update
[0x010E:1:0x0b04]: async_update
[0xd4e6:1:0x0b04] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=False is_reply=True disable_default_response=True> manufacturer=None tsn=187 command_id=Command.Read_Attributes_rsp>
[0x6e52:1:0x0b04] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=False is_reply=True disable_default_response=True> manufacturer=None tsn=189 command_id=Command.Read_Attributes_rsp>
[0x6e52:1:0x0b04] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=False is_reply=True disable_default_response=True> manufacturer=None tsn=191 command_id=Command.Read_Attributes_rsp>
Read attribute for: cluster_id: [2820] cluster_type: [in] endpoint_id: [1] attribute: [1291] manufacturer: [None] response --> response: [({1291: 0}, {})]
[0xd4e6:1:0x0b04] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=False is_reply=True disable_default_response=True> manufacturer=None tsn=193 command_id=Command.Read_Attributes_rsp>
Read attribute for: cluster_id: [2820] cluster_type: [in] endpoint_id: [1] attribute: [1291] manufacturer: [None] response --> response: [({1291: 0}, {})]
[0xd4e6] Delivery error for seq # 0xbb, on endpoint id 1 cluster 0x0b04: ZiGate doesn't answer to command
[0xD4E6:1:0x0b04]: async_update
[0x6E52:1:0x0b04]: async_update
[0x010E:1:0x0b04]: async_update
[0xd4e6:1:0x0b04] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=False is_reply=True disable_default_response=True> manufacturer=None tsn=195 command_id=Command.Read_Attributes_rsp>
[0x6e52:1:0x0b04] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=False is_reply=True disable_default_response=True> manufacturer=None tsn=197 command_id=Command.Read_Attributes_rsp>
[0xd4e6] Delivery error for seq # 0xc3, on endpoint id 1 cluster 0x0b04: ZiGate doesn't answer to command
[0xD4E6:1:0x0b04]: async_update
[0x6E52:1:0x0b04]: async_update
[0x010E:1:0x0b04]: async_update
[0xd4e6:1:0x0b04] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=False is_reply=True disable_default_response=True> manufacturer=None tsn=199 command_id=Command.Read_Attributes_rsp>
[0x6e52:1:0x0b04] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=False is_reply=True disable_default_response=True> manufacturer=None tsn=201 command_id=Command.Read_Attributes_rsp>
[0x6e52:1:0x0b04] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=False is_reply=True disable_default_response=True> manufacturer=None tsn=203 command_id=Command.Read_Attributes_rsp>
Read attribute for: cluster_id: [2820] cluster_type: [in] endpoint_id: [1] attribute: [1291] manufacturer: [None] response --> response: [({1291: 0}, {})]
[0xd4e6:1:0x0b04] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=False is_reply=True disable_default_response=True> manufacturer=None tsn=205 command_id=Command.Read_Attributes_rsp>
Read attribute for: cluster_id: [2820] cluster_type: [in] endpoint_id: [1] attribute: [1291] manufacturer: [None] response --> response: [({1291: 0}, {})]
[0xd4e6] Delivery error for seq # 0xc7, on endpoint id 1 cluster 0x0b04: ZiGate doesn't answer to command
```

```
{
  "node_descriptor": "NodeDescriptor(byte1=1, byte2=64, mac_capability_flags=142, manufacturer_code=4151, maximum_buffer_size=127, maximum_incoming_transfer_size=100, server_mask=0, maximum_outgoing_transfer_size=100, descriptor_capability_field=0)",
  "endpoints": {
    "1": {
      "profile_id": 260,
      "device_type": "0x0101",
      "in_clusters": [
        "0x0000",
        "0x0001",
        "0x0002",
        "0x0003",
        "0x0004",
        "0x0005",
        "0x0006",
        "0x000a",
        "0x000c",
        "0x0010",
        "0x0b04"
      ],
      "out_clusters": [
        "0x000a",
        "0x0019"
      ]
    },
    "2": {
      "profile_id": 260,
      "device_type": "0x0101",
      "in_clusters": [
        "0x0004",
        "0x0005",
        "0x0006",
        "0x0010"
      ],
      "out_clusters": []
    }
  },
  "manufacturer": "LUMI",
  "model": "lumi.relay.c2acn01",
  "class": "zhaquirks.xiaomi.aqara.relay_c2acn01.Relay"
}
```

**Additional context**
https://github.com/Koenkk/zigbee-herdsman-converters/issues/544
https://youtu.be/omGVI5nFK04?t=40 (see at 40seconds)

It works on zigbee2mqtt:
https://www.zigbee2mqtt.io/devices/LLKZMK11LM.html
Thanks !


Discussion:
Orig message:

Enable:
```
No.     Time           Source                Destination           Protocol Length Info
    108 134.067464     0x0000                0xed08                ZigBee HA 54     ZCL: Write Attributes, Seq: 75

Frame 108: 54 bytes on wire (432 bits), 54 bytes captured (432 bits) on interface 0
IEEE 802.15.4 Data, Dst: 0xed08, Src: 0x0000
ZigBee Network Layer Data, Dst: 0xed08, Src: 0x0000
ZigBee Application Support Layer Data, Dst Endpt: 1, Src Endpt: 1
ZigBee Cluster Library Frame, Mfr: Unknown (0x115f), Command: Write Attributes, Seq: 75
    Frame Control Field: Profile-wide (0x14)
        .... ..00 = Frame Type: Profile-wide (0x0)
        .... .1.. = Manufacturer Specific: True
        .... 0... = Direction: Client to Server
        ...1 .... = Disable Default Response: True
    Manufacturer Code: Unknown (0x115f)
    Sequence Number: 75
    Command: Write Attributes (0x02)
    Attribute Field, Boolean: 0x01
        Attribute: 0xff06
        Data Type: Boolean (0x10)
        0000 0001 = Boolean: True
```

when disabling it I get this:

```
No.     Time           Source                Destination           Protocol Length Info
    154 179.949980     0x0000                0xed08                ZigBee HA 54     ZCL: Write Attributes, Seq: 82

Frame 154: 54 bytes on wire (432 bits), 54 bytes captured (432 bits) on interface 0
IEEE 802.15.4 Data, Dst: 0xed08, Src: 0x0000
ZigBee Network Layer Data, Dst: 0xed08, Src: 0x0000
ZigBee Application Support Layer Data, Dst Endpt: 1, Src Endpt: 1
ZigBee Cluster Library Frame, Mfr: Unknown (0x115f), Command: Write Attributes, Seq: 82
    Frame Control Field: Profile-wide (0x14)
        .... ..00 = Frame Type: Profile-wide (0x0)
        .... .1.. = Manufacturer Specific: True
        .... 0... = Direction: Client to Server
        ...1 .... = Disable Default Response: True
    Manufacturer Code: Unknown (0x115f)
    Sequence Number: 82
    Command: Write Attributes (0x02)
    Attribute Field, Boolean: 0x00
        Attribute: 0xff06
        Data Type: Boolean (0x10)
        0000 0000 = Boolean: False
```

Binary output cluster on ep id == 1


Resolving patch:
diff --git a/zhaquirks/xiaomi/__init__.py b/zhaquirks/xiaomi/__init__.py
--- a/zhaquirks/xiaomi/__init__.py
+++ b/zhaquirks/xiaomi/__init__.py
@@ -8,7 +8,13 @@
 import zigpy.device
 from zigpy.profiles import zha
 from zigpy.quirks import CustomCluster, CustomDevice
-from zigpy.zcl.clusters.general import AnalogInput, Basic, OnOff, PowerConfiguration
+from zigpy.zcl.clusters.general import (
+    AnalogInput,
+    Basic,
+    BinaryOutput,
+    OnOff,
+    PowerConfiguration,
+)
 from zigpy.zcl.clusters.homeautomation import ElectricalMeasurement
 from zigpy.zcl.clusters.measurement import (
     IlluminanceMeasurement,
@@ -294,6 +300,12 @@ def _calculate_remaining_battery_percentage(voltage):
         return min(200, percent)
 
 
+class BinaryOutputInterlock(CustomCluster, BinaryOutput):
+    """Xiaomi binaryoutput cluster with added interlock attribute."""
+
+    manufacturer_attributes = {0xFF06: ("interlock", t.Bool)}
+
+
 class PowerConfigurationCluster(LocalDataCluster, PowerConfiguration):
     """Xiaomi power configuration cluster implementation."""
 
diff --git a/zhaquirks/xiaomi/aqara/relay_c2acn01.py b/zhaquirks/xiaomi/aqara/relay_c2acn01.py
--- a/zhaquirks/xiaomi/aqara/relay_c2acn01.py
+++ b/zhaquirks/xiaomi/aqara/relay_c2acn01.py
@@ -21,6 +21,7 @@
     LUMI,
     AnalogInputCluster,
     BasicCluster,
+    BinaryOutputInterlock,
     ElectricalMeasurementCluster,
     XiaomiCustomDevice,
 )
@@ -104,7 +105,7 @@ def __init__(self, *args, **kwargs):
                     Identify.cluster_id,
                     OnOff.cluster_id,
                     Scenes.cluster_id,
-                    BinaryOutput.cluster_id,
+                    BinaryOutputInterlock,
                     Time.cluster_id,
                     ElectricalMeasurementCluster,
                     AnalogInputCluster,
@@ -116,7 +117,7 @@ def __init__(self, *args, **kwargs):
                 DEVICE_TYPE: zha.DeviceType.DIMMABLE_LIGHT,
                 INPUT_CLUSTERS: [
                     OnOff.cluster_id,
-                    BinaryOutput.cluster_id,
+                    BinaryOutputInterlock,
                     Groups.cluster_id,
                     Scenes.cluster_id,
                 ],


Test patch: