Repository: zigpy/zha-device-handlers
Pull request #2678 (2023-10-23 21:48:09)

Add "power outage memory" support for Aqara LED Bulb T1 (ZNLDP13LM)
## Proposed change
This quirk adds support for power on behavior configuration to [Aqara LED Bulb T1 aka Xiaomi ZNLDP13LM](https://zigbee.blakadder.com/Aqara_ZNLDP13LM.html).

## Additional information
None


## Checklist
- [x] The changes are tested and work correctly
- [x] `pre-commit` checks pass / the code has been formatted using Black
- [x] Tests have been added to verify that the new code works


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/zigpy/zha-device-handlers/pull/2678?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=zigpy) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Comparison is base [(`307f926`)](https://app.codecov.io/gh/zigpy/zha-device-handlers/commit/307f92676845241e20cbd79401479139293f7e05?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=zigpy) 86.80% compared to head [(`21983aa`)](https://app.codecov.io/gh/zigpy/zha-device-handlers/pull/2678?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=zigpy) 86.81%.

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##              dev    #2678   +/-   ##
=======================================
  Coverage   86.80%   86.81%           
=======================================
  Files         281      281           
  Lines        8571     8576    +5     
=======================================
+ Hits         7440     7445    +5     
  Misses       1131     1131           
```


| [Files](https://app.codecov.io/gh/zigpy/zha-device-handlers/pull/2678?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=zigpy) | Coverage Δ | |
|---|---|---|
| [zhaquirks/xiaomi/aqara/light\_acn.py](https://app.codecov.io/gh/zigpy/zha-device-handlers/pull/2678?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=zigpy#diff-emhhcXVpcmtzL3hpYW9taS9hcWFyYS9saWdodF9hY24ucHk=) | `100.00% <100.00%> (ø)` | |


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/zigpy/zha-device-handlers/pull/2678?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=zigpy).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=zigpy).

TheJulianJES: Interesting that the bulb has `ElectricalMeasurement` clusters and so on. Can you send a screenshot of what entities ZHA generates?
Otherwise, PR LGTM.

Patch:
diff --git a/zhaquirks/quirk_ids.py b/zhaquirks/quirk_ids.py
new file mode 100644
index 0000000..8306c91
--- /dev/null
+++ b/zhaquirks/quirk_ids.py
@@ -0,0 +1,5 @@
+"""Quirk IDs used for matching quirked devices in ZHA."""
+
+# Tuya
+TUYA_PLUG_ONOFF = "tuya.plug_on_off_attributes"  # plugs with configurable attributes on the OnOff cluster
+TUYA_PLUG_MANUFACTURER = "tuya.plug_manufacturer_attributes"  # plugs with configurable attributes on a custom cluster
diff --git a/zhaquirks/tuya/ts000x.py b/zhaquirks/tuya/ts000x.py
index f807cad..7875a71 100644
--- a/zhaquirks/tuya/ts000x.py
+++ b/zhaquirks/tuya/ts000x.py
@@ -20,6 +20,7 @@ from zhaquirks.const import (
     OUTPUT_CLUSTERS,
     PROFILE_ID,
 )
+from zhaquirks.quirk_ids import TUYA_PLUG_ONOFF
 from zhaquirks.tuya import (
     TuyaZBE000Cluster,
     TuyaZBElectricalMeasurement,
@@ -33,6 +34,8 @@ from zhaquirks.tuya.mcu import EnchantedDevice
 class Switch_1G_GPP(EnchantedDevice):
     """Tuya 1 gang switch module with restore power state support."""
 
+    quirk_id = TUYA_PLUG_ONOFF
+
     signature = {
         MODEL: "TS0001",
         ENDPOINTS: {
@@ -89,6 +92,8 @@ class Switch_1G_GPP(EnchantedDevice):
 class Switch_1G_Metering(EnchantedDevice):
     """Tuya 1 gang switch with metering support."""
 
+    quirk_id = TUYA_PLUG_ONOFF
+
     signature = {
         MODEL: "TS0001",
         ENDPOINTS: {
@@ -148,6 +153,8 @@ class Switch_1G_Metering(EnchantedDevice):
 class Switch_2G_GPP(EnchantedDevice):
     """Tuya 2 gang switch module with restore power state support."""
 
+    quirk_id = TUYA_PLUG_ONOFF
+
     signature = {
         MODEL: "TS0002",
         ENDPOINTS: {
@@ -236,6 +243,8 @@ class Switch_2G_GPP(EnchantedDevice):
 class Switch_2G_Metering(EnchantedDevice):
     """Tuya 2 gang switch with metering support."""
 
+    quirk_id = TUYA_PLUG_ONOFF
+
     signature = {
         MODEL: "TS0002",
         ENDPOINTS: {
@@ -324,6 +333,8 @@ class Switch_2G_Metering(EnchantedDevice):
 class Switch_2G_Var03(EnchantedDevice):
     """Tuya 2 gang (variation 03)."""
 
+    quirk_id = TUYA_PLUG_ONOFF
+
     signature = {
         MODEL: "TS0002",
         ENDPOINTS: {
@@ -416,6 +427,8 @@ class Switch_2G_Var03(EnchantedDevice):
 class Switch_3G_GPP(EnchantedDevice):
     """Tuya 3 gang switch module with restore power state support."""
 
+    quirk_id = TUYA_PLUG_ONOFF
+
     signature = {
         MODEL: "TS0003",
         ENDPOINTS: {
@@ -530,6 +543,8 @@ class Switch_3G_GPP(EnchantedDevice):
 class Switch_3G_Metering(EnchantedDevice):
     """Tuya 3 gang switch with metering support."""
 
+    quirk_id = TUYA_PLUG_ONOFF
+
     signature = {
         MODEL: "TS0003",
         ENDPOINTS: {
@@ -641,6 +656,8 @@ class Switch_3G_Metering(EnchantedDevice):
 class Switch_3G_GPP_Var2(EnchantedDevice):
     """Tuya 3 gang switch module."""
 
+    quirk_id = TUYA_PLUG_ONOFF
+
     signature = {
         MODEL: "TS0003",
         ENDPOINTS: {
@@ -754,6 +771,8 @@ class Switch_3G_GPP_Var2(EnchantedDevice):
 class Switch_4G_GPP(EnchantedDevice):
     """Tuya 4 gang switch module with restore power state support."""
 
+    quirk_id = TUYA_PLUG_ONOFF
+
     signature = {
         MODEL: "TS0004",
         ENDPOINTS: {
@@ -894,6 +913,8 @@ class Switch_4G_GPP(EnchantedDevice):
 class Switch_4G_Metering(EnchantedDevice):
     """Tuya 4 gang switch with metering support."""
 
+    quirk_id = TUYA_PLUG_ONOFF
+
     signature = {
         MODEL: "TS0004",
         ENDPOINTS: {
@@ -1028,6 +1049,8 @@ class Switch_4G_Metering(EnchantedDevice):
 class Switch_4G_GPP_Var2(EnchantedDevice):
     """Tuya 4 gang switch module."""
 
+    quirk_id = TUYA_PLUG_ONOFF
+
     signature = {
         MODEL: "TS0004",
         ENDPOINTS: {
diff --git a/zhaquirks/tuya/ts011f_plug.py b/zhaquirks/tuya/ts011f_plug.py
index 1f540e1..4bb55cf 100644
--- a/zhaquirks/tuya/ts011f_plug.py
+++ b/zhaquirks/tuya/ts011f_plug.py
@@ -26,6 +26,7 @@ from zhaquirks.const import (
     OUTPUT_CLUSTERS,
     PROFILE_ID,
 )
+from zhaquirks.quirk_ids import TUYA_PLUG_ONOFF
 from zhaquirks.tuya import (
     TuyaNewManufCluster,
     TuyaZB1888Cluster,
@@ -42,6 +43,8 @@ from zhaquirks.tuya.mcu import EnchantedDevice
 class Plug(EnchantedDevice):
     """Tuya plug with restore power state support."""
 
+    quirk_id = TUYA_PLUG_ONOFF
+
     signature = {
         MODEL: "TS011F",
         ENDPOINTS: {
@@ -102,6 +105,8 @@ class Plug(EnchantedDevice):
 class Plug_1AC(CustomDevice):
     """Tuya plug without metering with restore power state support."""
 
+    quirk_id = TUYA_PLUG_ONOFF
+
     signature = {
         MODEL: "TS011F",
         ENDPOINTS: {
@@ -160,6 +165,8 @@ class Plug_1AC(CustomDevice):
 class Plug_2AC_2USB(EnchantedDevice):
     """Tuya 2 outlet + 2 USB with restore power state support."""
 
+    quirk_id = TUYA_PLUG_ONOFF
+
     signature = {
         MODELS_INFO: [("_TZ3000_3zofvcaa", "TS011F")],
         ENDPOINTS: {
@@ -297,6 +304,8 @@ class Plug_2AC_2USB(EnchantedDevice):
 class Plug_3AC_4USB(CustomDevice):
     """Tuya 3 outlet + 4 USB with restore power state support."""
 
+    quirk_id = TUYA_PLUG_ONOFF
+
     signature = {
         MODEL: "TS011F",
         ENDPOINTS: {
@@ -407,6 +416,8 @@ class Plug_3AC_4USB(CustomDevice):
 class Plug_4AC_2USB(CustomDevice):
     """Tuya 4 outlet + 2 USB surge protector with restore power state support."""
 
+    quirk_id = TUYA_PLUG_ONOFF
+
     signature = {
         MODEL: "TS011F",
         ENDPOINTS: {
@@ -589,6 +600,8 @@ class Plug_4AC_2USB(CustomDevice):
 class Plug_TZ3210_2AC(CustomDevice):
     """TS0011F 2 outlet plug."""
 
+    quirk_id = TUYA_PLUG_ONOFF
+
     signature = {
         MODEL: "TS011F",
         ENDPOINTS: {
@@ -650,7 +663,7 @@ class Plug_TZ3210_2AC(CustomDevice):
                     Identify.cluster_id,
                     Groups.cluster_id,
                     Scenes.cluster_id,
-                    OnOff.cluster_id,
+                    TuyaZBOnOffAttributeCluster,
                     TuyaZBMeteringCluster,
                     TuyaZBElectricalMeasurement,
                     TuyaNewManufCluster,
@@ -664,7 +677,7 @@ class Plug_TZ3210_2AC(CustomDevice):
                     Identify.cluster_id,
                     Groups.cluster_id,
                     Scenes.cluster_id,
-                    OnOff.cluster_id,
+                    TuyaZBOnOffAttributeCluster,
                     TuyaNewManufCluster,
                 ],
                 OUTPUT_CLUSTERS: [],
@@ -682,6 +695,8 @@ class Plug_TZ3210_2AC(CustomDevice):
 class Plug_TZ3210_1AC(CustomDevice):
     """TS0011F 1 outlet plug."""
 
+    quirk_id = TUYA_PLUG_ONOFF
+
     signature = {
         MODEL: "TS011F",
         ENDPOINTS: {
@@ -727,7 +742,7 @@ class Plug_TZ3210_1AC(CustomDevice):
                     Identify.cluster_id,
                     Groups.cluster_id,
                     Scenes.cluster_id,
-                    OnOff.cluster_id,
+                    TuyaZBOnOffAttributeCluster,
                     TuyaZBMeteringCluster,
                     TuyaZBElectricalMeasurement,
                     TuyaNewManufCluster,
@@ -747,6 +762,8 @@ class Plug_TZ3210_1AC(CustomDevice):
 class Plug_4AC_2USB_cfnprab5(EnchantedDevice):
     """Tuya 4 outlet + 2 USB surge protector with restore power state support."""
 
+    quirk_id = TUYA_PLUG_ONOFF
+
     signature = {
         MODEL: "TS011F",
         ENDPOINTS: {
@@ -913,6 +930,8 @@ class Plug_4AC_2USB_cfnprab5(EnchantedDevice):
 class Plug_4AC_2USB_Metering(EnchantedDevice):
     """Tuya Tuya 4 outlet + 2 USB with power metering."""
 
+    quirk_id = TUYA_PLUG_ONOFF
+
     signature = {
         MODEL: "TS011F",
         ENDPOINTS: {
@@ -1031,6 +1050,8 @@ class Plug_4AC_2USB_Metering(EnchantedDevice):
 class Plug_v2(EnchantedDevice):
     """Another TS011F Tuya plug. First one using this definition is _TZ3000_okaz9tjs."""
 
+    quirk_id = TUYA_PLUG_ONOFF
+
     signature = {
         MODEL: "TS011F",
         ENDPOINTS: {
@@ -1079,6 +1100,8 @@ class Plug_v2(EnchantedDevice):
 class Plug_v3(EnchantedDevice):
     """Tuya TS011F plug. One plug is _Tz3000_0Zfrhq4I."""
 
+    quirk_id = TUYA_PLUG_ONOFF
+
     signature = {
         MODEL: "TS011F",
         ENDPOINTS: {
@@ -1150,6 +1173,8 @@ class Plug_v3(EnchantedDevice):
 class Plug_2AC_var03(CustomDevice):
     """Tuya 2 socket wall outlet with child lock and power-restore state support."""
 
+    quirk_id = TUYA_PLUG_ONOFF
+
     signature = {
         MODEL: "TS011F",
         ENDPOINTS: {
@@ -1220,6 +1245,8 @@ class Plug_2AC_var03(CustomDevice):
 class Plug_CB_Metering(EnchantedDevice):
     """Circuit breaker with monitoring, e.g. Tongou TO-Q-SY1-JZT. First one using this definition was _TZ3000_qeuvnohg."""
 
+    quirk_id = TUYA_PLUG_ONOFF
+
     signature = {
         MODEL: "TS011F",
         MODELS_INFO: [("_TZ3000_qeuvnohg", "TS011F")],
@@ -1288,6 +1315,8 @@ class Plug_CB_Metering(EnchantedDevice):
 class Plug_2AC_var05(EnchantedDevice):
     """Immax TS0011F 2 outlet plug."""
 
+    quirk_id = TUYA_PLUG_ONOFF
+
     signature = {
         MODEL: "TS011F",
         ENDPOINTS: {
diff --git a/zhaquirks/tuya/ts0121_plug.py b/zhaquirks/tuya/ts0121_plug.py
index 286fe57..0df931a 100644
--- a/zhaquirks/tuya/ts0121_plug.py
+++ b/zhaquirks/tuya/ts0121_plug.py
@@ -22,6 +22,7 @@ from zhaquirks.const import (
     OUTPUT_CLUSTERS,
     PROFILE_ID,
 )
+from zhaquirks.quirk_ids import TUYA_PLUG_ONOFF
 from zhaquirks.tuya import (
     TuyaNewManufCluster,
     TuyaZBE000Cluster,
@@ -35,6 +36,8 @@ from zhaquirks.tuya import (
 class Plug(CustomDevice):
     """Tuya TS0121 plug with restore tuya power state support."""
 
+    quirk_id = TUYA_PLUG_ONOFF
+
     signature = {
         MODEL: "TS0121",
         ENDPOINTS: {
@@ -79,6 +82,8 @@ class Plug(CustomDevice):
 class TS0121B(CustomDevice):
     """Tuya TS0121 plug with restore tuya power state support and ZGP endpoint."""
 
+    quirk_id = TUYA_PLUG_ONOFF
+
     signature = {
         MODEL: "TS0121",
         ENDPOINTS: {
@@ -137,6 +142,8 @@ class TS0121B(CustomDevice):
 class TS0121_Var03(CustomDevice):
     """Tuya TS0121 plug DeviceType.MAIN_POWER_OUTLET."""
 
+    quirk_id = TUYA_PLUG_ONOFF
+
     signature = {
         MODEL: "TS0121",
         ENDPOINTS: {
diff --git a/zhaquirks/tuya/ts0601_sensor.py b/zhaquirks/tuya/ts0601_sensor.py
index 41114b8..aaed2ba 100644
--- a/zhaquirks/tuya/ts0601_sensor.py
+++ b/zhaquirks/tuya/ts0601_sensor.py
@@ -75,6 +75,26 @@ class TemperatureHumidityManufCluster(TuyaMCUCluster):
     }
 
 
+class TemperatureHumidityBatteryStatesManufCluster(TuyaMCUCluster):
+    """Tuya Manufacturer Cluster with Temperature and Humidity data points. Battery states 25, 50 and 100%."""
+
+    dp_to_attribute: Dict[int, DPToAttributeMapping] = {
+        1: TemperatureHumidityManufCluster.dp_to_attribute[1],
+        2: TemperatureHumidityManufCluster.dp_to_attribute[2],
+        3: DPToAttributeMapping(
+            TuyaPowerConfigurationCluster2AAA.ep_attribute,
+            "battery_percentage_remaining",
+            converter=lambda x: {0: 25, 1: 50, 2: 100}[x],  # double reported percentage
+        ),
+    }
+
+    data_point_handlers = {
+        1: "_dp_2_attr_update",
+        2: "_dp_2_attr_update",
+        3: "_dp_2_attr_update",
+    }
+
+
 class TuyaTempHumiditySensor(CustomDevice):
     """Custom device representing tuya temp and humidity sensor with e-ink screen."""
 
@@ -181,8 +201,6 @@ class TuyaTempHumiditySensorVar03(CustomDevice):
         # "in_clusters": ["0x0000","0x0004","0x0005","0xef00"],
         # "out_clusters": ["0x000a","0x0019"]
         MODELS_INFO: [
-            ("_TZE200_yjjdcqsq", "TS0601"),
-            ("_TZE200_9yapgbuv", "TS0601"),
             ("_TZE200_qyflbnbj", "TS0601"),
             ("_TZE200_utkemkbs", "TS0601"),
         ],
@@ -221,6 +239,53 @@ class TuyaTempHumiditySensorVar03(CustomDevice):
     }
 
 
+class TuyaTempHumiditySensorVar04(CustomDevice):
+    """Tuya temp and humidity sensor (variation 04)."""
+
+    signature = {
+        # "profile_id": 260,
+        # "device_type": "0x0051",
+        # "in_clusters": ["0x0000","0x0004","0x0005","0xef00"],
+        # "out_clusters": ["0x000a","0x0019"]
+        MODELS_INFO: [
+            ("_TZE200_yjjdcqsq", "TS0601"),
+            ("_TZE200_9yapgbuv", "TS0601"),
+        ],
+        ENDPOINTS: {
+            1: {
+                PROFILE_ID: zha.PROFILE_ID,
+                DEVICE_TYPE: zha.DeviceType.SMART_PLUG,
+                INPUT_CLUSTERS: [
+                    Basic.cluster_id,
+                    Groups.cluster_id,
+                    Scenes.cluster_id,
+                    TemperatureHumidityManufCluster.cluster_id,
+                ],
+                OUTPUT_CLUSTERS: [Ota.cluster_id, Time.cluster_id],
+            }
+        },
+    }
+
+    replacement = {
+        SKIP_CONFIGURATION: True,
+        ENDPOINTS: {
+            1: {
+                DEVICE_TYPE: zha.DeviceType.TEMPERATURE_SENSOR,
+                INPUT_CLUSTERS: [
+                    Basic.cluster_id,
+                    Groups.cluster_id,
+                    Scenes.cluster_id,
+                    TemperatureHumidityBatteryStatesManufCluster,
+                    TuyaTemperatureMeasurement,
+                    TuyaRelativeHumidity,
+                    TuyaPowerConfigurationCluster2AAA,
+                ],
+                OUTPUT_CLUSTERS: [Ota.cluster_id, Time.cluster_id],
+            }
+        },
+    }
+
+
 class SoilManufCluster(TuyaMCUCluster):
     """Tuya Manufacturer Cluster with Temperature and Humidity data points."""
 
diff --git a/zhaquirks/tuya/ts0601_switch.py b/zhaquirks/tuya/ts0601_switch.py
index da2c34f..c23e3fa 100644
--- a/zhaquirks/tuya/ts0601_switch.py
+++ b/zhaquirks/tuya/ts0601_switch.py
@@ -10,6 +10,7 @@ from zhaquirks.const import (
     OUTPUT_CLUSTERS,
     PROFILE_ID,
 )
+from zhaquirks.quirk_ids import TUYA_PLUG_MANUFACTURER
 from zhaquirks.tuya import TuyaSwitch
 from zhaquirks.tuya.mcu import (
     MoesSwitchManufCluster,
@@ -22,6 +23,8 @@ from zhaquirks.tuya.mcu import (
 class TuyaSingleSwitchTI(TuyaSwitch):
     """Tuya single channel switch time on in cluster device."""
 
+    quirk_id = TUYA_PLUG_MANUFACTURER
+
     signature = {
         # "node_descriptor": "<NodeDescriptor byte1=1 byte2=64 mac_capability_flags=142 manufacturer_code=4098
         #                       maximum_buffer_size=82 maximum_incoming_transfer_size=82 server_mask=11264
@@ -68,6 +71,8 @@ class TuyaSingleSwitchTI(TuyaSwitch):
 class TuyaSingleSwitchTO(TuyaSwitch):
     """Tuya single channel switch time on out cluster device."""
 
+    quirk_id = TUYA_PLUG_MANUFACTURER
+
     signature = {
         # "node_descriptor": "<NodeDescriptor byte1=1 byte2=64 mac_capability_flags=142 manufacturer_code=4098
         #                       maximum_buffer_size=82 maximum_incoming_transfer_size=82 server_mask=11264
@@ -176,6 +181,8 @@ class TuyaSingleSwitch_GP(TuyaSwitch):
 class TuyaDoubleSwitchTO(TuyaSwitch):
     """Tuya double channel switch time on out cluster device."""
 
+    quirk_id = TUYA_PLUG_MANUFACTURER
+
     signature = {
         # "node_descriptor": "<NodeDescriptor byte1=1 byte2=64 mac_capability_flags=142 manufacturer_code=4098
         #                       maximum_buffer_size=82 maximum_incoming_transfer_size=82 server_mask=11264
@@ -231,6 +238,8 @@ class TuyaDoubleSwitchTO(TuyaSwitch):
 class TuyaDoubleSwitch_GP(TuyaSwitch):
     """Tuya double channel switch with GreenPowerProxy cluster device."""
 
+    quirk_id = TUYA_PLUG_MANUFACTURER
+
     signature = {
         MODELS_INFO: [
             ("_TZE200_7deq70b8", "TS0601"),
@@ -297,6 +306,8 @@ class TuyaDoubleSwitch_GP(TuyaSwitch):
 class TuyaTripleSwitchTO(TuyaSwitch):
     """Tuya triple channel switch time on out cluster device."""
 
+    quirk_id = TUYA_PLUG_MANUFACTURER
+
     signature = {
         MODELS_INFO: [
             # ("_TZE200_kyfqmmyl", "TS0601"),  ## candidate reported in #716
@@ -353,6 +364,8 @@ class TuyaTripleSwitchTO(TuyaSwitch):
 class TuyaTripleSwitch_GP(TuyaSwitch):
     """Tuya triple channel switch with GreenPowerProxy cluster device."""
 
+    quirk_id = TUYA_PLUG_MANUFACTURER
+
     signature = {
         MODELS_INFO: [
             ("_TZE200_2hf7x9n3", "TS0601"),  # reported #1597
@@ -427,6 +440,8 @@ class TuyaTripleSwitch_GP(TuyaSwitch):
 class TuyaQuadrupleSwitchTO(TuyaSwitch):
     """Tuya quadruple channel switch time on out cluster device."""
 
+    quirk_id = TUYA_PLUG_MANUFACTURER
+
     signature = {
         MODELS_INFO: [
             ("_TZE200_aqnazj70", "TS0601"),
@@ -574,6 +589,8 @@ class TuyaQuadrupleSwitch_GP(TuyaSwitch):
 class TuyaSextupleSwitchTO(TuyaSwitch):
     """Tuya sextuple channel switch time on out cluster device."""
 
+    quirk_id = TUYA_PLUG_MANUFACTURER
+
     signature = {
         # <SimpleDescriptor endpoint=1 profile=260 device_type=81
         # input_clusters=[0x0000,0x0004,0x0005,0xEF00]
@@ -656,6 +673,8 @@ class TuyaSextupleSwitchTO(TuyaSwitch):
 class TuyaSextupleSwitchTO_GP(TuyaSwitch):
     """Tuya sextuple channel switch time on out cluster device with GreenPowerProxy cluster device."""
 
+    quirk_id = TUYA_PLUG_MANUFACTURER
+
     signature = {
         MODELS_INFO: [
             ("_TZE200_9mahtqtg", "TS0601"),
diff --git a/zhaquirks/xiaomi/aqara/light_acn.py b/zhaquirks/xiaomi/aqara/light_acn.py
index b9c50f1..9ef4527 100644
--- a/zhaquirks/xiaomi/aqara/light_acn.py
+++ b/zhaquirks/xiaomi/aqara/light_acn.py
@@ -1,15 +1,23 @@
 from zigpy import types as t
-from zigpy.profiles import zha
+from zigpy.profiles import zgp, zha
 from zigpy.zcl.clusters.general import (
+    Alarms,
+    AnalogInput,
+    BinaryInput,
+    DeviceTemperature,
+    GreenPowerProxy,
     Groups,
     Identify,
     LevelControl,
+    MultistateInput,
     OnOff,
     Ota,
     Scenes,
     Time,
 )
+from zigpy.zcl.clusters.homeautomation import ElectricalMeasurement
 from zigpy.zcl.clusters.lighting import Color
+from zigpy.zcl.clusters.smartenergy import Metering
 
 from zhaquirks.const import (
     DEVICE_TYPE,
@@ -19,7 +27,12 @@ from zhaquirks.const import (
     OUTPUT_CLUSTERS,
     PROFILE_ID,
 )
-from zhaquirks.xiaomi import BasicCluster, XiaomiAqaraE1Cluster, XiaomiCustomDevice
+from zhaquirks.xiaomi import (
+    LUMI,
+    BasicCluster,
+    XiaomiAqaraE1Cluster,
+    XiaomiCustomDevice,
+)
 
 
 class OppleClusterLight(XiaomiAqaraE1Cluster):
@@ -84,3 +97,104 @@ class LumiLightAcn003(XiaomiCustomDevice):
             }
         }
     }
+
+
+class LumiLightAcn014(XiaomiCustomDevice):
+    """Quirk for Aqara LED Bulb T1 also known as Xiaomi ZNLDP13LM.
+
+    It identifies itself as lumi.light.acn014, however someone reported there are devices with the same model number
+    but identification lumi.light.cwac02.
+    Provides dimmable light control with color temperature setting.
+    This quirk adds support for power on behavior by adding power_outage_memory attribute.
+    """
+
+    signature = {
+        MODELS_INFO: [
+            (LUMI, "lumi.light.acn014"),
+        ],
+        ENDPOINTS: {
+            1: {
+                PROFILE_ID: zha.PROFILE_ID,
+                DEVICE_TYPE: zha.DeviceType.COLOR_TEMPERATURE_LIGHT,
+                INPUT_CLUSTERS: [
+                    BasicCluster.cluster_id,  # 0x0000
+                    DeviceTemperature.cluster_id,  # 0x0002
+                    Identify.cluster_id,  # 0x0003
+                    Groups.cluster_id,  # 0x0004
+                    Scenes.cluster_id,  # 0x0005
+                    OnOff.cluster_id,  # 0x0006
+                    LevelControl.cluster_id,  # 0x0008
+                    Alarms.cluster_id,  # 0x0009
+                    AnalogInput.cluster_id,  # 0x000C
+                    BinaryInput.cluster_id,  # 0x000F
+                    MultistateInput.cluster_id,  # 0x0012
+                    Color.cluster_id,  # 0x0300
+                    Metering.cluster_id,  # 0x0702
+                    ElectricalMeasurement.cluster_id,  # 0x0B04
+                    OppleClusterLight.cluster_id,  # 0xFCC0 - manufacturer specific
+                ],
+                OUTPUT_CLUSTERS: [
+                    Time.cluster_id,  # 0x000A
+                    Ota.cluster_id,  # 0x0019
+                ],
+            },
+            21: {
+                PROFILE_ID: zha.PROFILE_ID,
+                DEVICE_TYPE: zha.DeviceType.COLOR_TEMPERATURE_LIGHT,
+                INPUT_CLUSTERS: [
+                    AnalogInput.cluster_id,  # 0x000C
+                ],
+                OUTPUT_CLUSTERS: [],
+            },
+            31: {
+                PROFILE_ID: zha.PROFILE_ID,
+                DEVICE_TYPE: zha.DeviceType.COLOR_TEMPERATURE_LIGHT,
+                INPUT_CLUSTERS: [
+                    AnalogInput.cluster_id,  # 0x000C
+                ],
+                OUTPUT_CLUSTERS: [],
+            },
+            242: {
+                PROFILE_ID: zgp.PROFILE_ID,
+                DEVICE_TYPE: zgp.DeviceType.PROXY_BASIC,
+                INPUT_CLUSTERS: [],
+                OUTPUT_CLUSTERS: [
+                    GreenPowerProxy.cluster_id,  # 0x0021
+                ],
+            },
+        },
+    }
+    replacement = {
+        ENDPOINTS: {
+            1: {
+                PROFILE_ID: zha.PROFILE_ID,
+                DEVICE_TYPE: zha.DeviceType.COLOR_TEMPERATURE_LIGHT,
+                INPUT_CLUSTERS: [
+                    BasicCluster,  # 0x0000
+                    DeviceTemperature.cluster_id,  # 0x0002
+                    Identify.cluster_id,  # 0x0003
+                    Groups.cluster_id,  # 0x0004
+                    Scenes.cluster_id,  # 0x0005
+                    OnOff.cluster_id,  # 0x0006
+                    LevelControl.cluster_id,  # 0x0008
+                    Alarms.cluster_id,  # 0x0009
+                    AnalogInput.cluster_id,  # 0x000C
+                    MultistateInput.cluster_id,  # 0x0012
+                    Color.cluster_id,  # 0x0300
+                    OppleClusterLight,  # 0xFCC0 - manufacturer specific
+                ],
+                OUTPUT_CLUSTERS: [
+                    Time.cluster_id,  # 0x000A
+                    Ota.cluster_id,  # 0x0019
+                ],
+            },
+            242: {
+                PROFILE_ID: zgp.PROFILE_ID,
+                DEVICE_TYPE: zgp.DeviceType.PROXY_BASIC,
+                INPUT_CLUSTERS: [],
+                OUTPUT_CLUSTERS: [
+                    GreenPowerProxy.cluster_id,  # 0x0021
+                ],
+            },
+        }
+    }


Test patch:
diff --git a/tests/test_xiaomi.py b/tests/test_xiaomi.py
index c8a4c97..6122b67 100644
--- a/tests/test_xiaomi.py
+++ b/tests/test_xiaomi.py
@@ -1539,3 +1539,58 @@ def test_aqara_acn003_signature_match(assert_signature_matches_quirk):
     assert_signature_matches_quirk(
         zhaquirks.xiaomi.aqara.light_acn.LumiLightAcn003, signature
     )
+
+
+def test_aqara_acn014_signature_match(assert_signature_matches_quirk):
+    signature = {
+        "node_descriptor": "NodeDescriptor(logical_type=<LogicalType.Router: 1>, complex_descriptor_available=0, user_descriptor_available=0, reserved=0, aps_flags=0, frequency_band=<FrequencyBand.Freq2400MHz: 8>, mac_capability_flags=<MACCapabilityFlags.FullFunctionDevice|MainsPowered|RxOnWhenIdle|AllocateAddress: 142>, manufacturer_code=4447, maximum_buffer_size=82, maximum_incoming_transfer_size=82, server_mask=11264, maximum_outgoing_transfer_size=82, 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": 0x0104,
+                "device_type": "0x010c",
+                "in_clusters": [
+                    "0x0000",
+                    "0x0002",
+                    "0x0003",
+                    "0x0004",
+                    "0x0005",
+                    "0x0006",
+                    "0x0008",
+                    "0x0009",
+                    "0x000c",
+                    "0x000f",
+                    "0x0012",
+                    "0x0300",
+                    "0x0702",
+                    "0x0b04",
+                    "0xfcc0",
+                ],
+                "out_clusters": ["0x000a", "0x0019"],
+            },
+            "21": {
+                "profile_id": 0x0104,
+                "device_type": "0x010c",
+                "in_clusters": ["0x000c"],
+                "out_clusters": [],
+            },
+            "31": {
+                "profile_id": 0x0104,
+                "device_type": "0x010c",
+                "in_clusters": ["0x000c"],
+                "out_clusters": [],
+            },
+            "242": {
+                "profile_id": 0xA1E0,
+                "device_type": "0x0061",
+                "in_clusters": [],
+                "out_clusters": ["0x0021"],
+            },
+        },
+        "manufacturer": "LUMI",
+        "model": "lumi.light.acn014",
+        "class": "zigpy.device.Device",
+    }
+
+    assert_signature_matches_quirk(
+        zhaquirks.xiaomi.aqara.light_acn.LumiLightAcn014, signature
+    )