diff --git a/tests/test_xiaomi.py b/tests/test_xiaomi.py index 37c566799c..ca0e99cac5 100644 --- a/tests/test_xiaomi.py +++ b/tests/test_xiaomi.py @@ -80,6 +80,14 @@ AqaraFeederAcn001, OppleCluster, ) +from zhaquirks.xiaomi.aqara.led_strip_t1 import ( + AqaraLedStripT1, + LedStripT1Audio, + LedStripT1AudioEffect, + LedStripT1AudioSensitivity, + LedStripT1PowerOnStateMode, + LedStripT1Preset, +) from zhaquirks.xiaomi.aqara.light_acn import AqaraLightT1M, LumiPowerOnStateMode import zhaquirks.xiaomi.aqara.magnet_ac01 import zhaquirks.xiaomi.aqara.magnet_acn001 @@ -2282,3 +2290,116 @@ async def test_lumi_magnet_sensor_aq2_bad_direction(zigpy_device_from_quirk, cap # Our matching logic should be forgiving assert listener.attribute_updates == [(0, t.Bool.true)] + + +def test_t1_led_strip(zigpy_device_from_v2_quirk): + """Test Aqara T1 led strip adds missing endpoints.""" + + # create the device + device = zigpy_device_from_v2_quirk(AQARA, "lumi.light.acn132") + assert AqaraLedStripT1.cluster_id in device.endpoints[1].in_clusters + + aqara_cluster = device.endpoints[1].opple_cluster + cluster_listener = ClusterListener(aqara_cluster) + + # Checking min_brightness attribute update + aqara_cluster.update_attribute(AqaraLedStripT1.AttributeDefs.min_brightness.id, 12) + assert len(cluster_listener.attribute_updates) == 1 + assert ( + cluster_listener.attribute_updates[0][0] + == AqaraLedStripT1.AttributeDefs.min_brightness.id + ) + + assert cluster_listener.attribute_updates[0][1] == 12 + + # Checking max_brightness attribute update + aqara_cluster.update_attribute(AqaraLedStripT1.AttributeDefs.max_brightness.id, 88) + assert len(cluster_listener.attribute_updates) == 2 + assert ( + cluster_listener.attribute_updates[1][0] + == AqaraLedStripT1.AttributeDefs.max_brightness.id + ) + + assert cluster_listener.attribute_updates[1][1] == 88 + + # Checking power on state attribute update + aqara_cluster.update_attribute( + AqaraLedStripT1.AttributeDefs.power_on_state.id, + LedStripT1PowerOnStateMode.Toggle, + ) + assert len(cluster_listener.attribute_updates) == 3 + assert ( + cluster_listener.attribute_updates[2][0] + == AqaraLedStripT1.AttributeDefs.power_on_state.id + ) + + assert cluster_listener.attribute_updates[2][1] == LedStripT1PowerOnStateMode.Toggle + + # Checking length attribute update + aqara_cluster.update_attribute(AqaraLedStripT1.AttributeDefs.length.id, 3.2) + assert len(cluster_listener.attribute_updates) == 4 + assert ( + cluster_listener.attribute_updates[3][0] + == AqaraLedStripT1.AttributeDefs.length.id + ) + + assert cluster_listener.attribute_updates[3][1] == 3.2 + + # Checking audio attribute update + aqara_cluster.update_attribute( + AqaraLedStripT1.AttributeDefs.audio.id, LedStripT1Audio.On + ) + assert len(cluster_listener.attribute_updates) == 5 + assert ( + cluster_listener.attribute_updates[4][0] + == AqaraLedStripT1.AttributeDefs.audio.id + ) + + assert cluster_listener.attribute_updates[4][1] == LedStripT1Audio.On + + # Checking audio_sensitivity attribute update + aqara_cluster.update_attribute( + AqaraLedStripT1.AttributeDefs.audio_sensitivity.id, + LedStripT1AudioSensitivity.Medium, + ) + assert len(cluster_listener.attribute_updates) == 6 + assert ( + cluster_listener.attribute_updates[5][0] + == AqaraLedStripT1.AttributeDefs.audio_sensitivity.id + ) + + assert cluster_listener.attribute_updates[5][1] == LedStripT1AudioSensitivity.Medium + + # Checking audio_effect attribute update + aqara_cluster.update_attribute( + AqaraLedStripT1.AttributeDefs.audio_effect.id, LedStripT1AudioEffect.Rainbow + ) + assert len(cluster_listener.attribute_updates) == 7 + assert ( + cluster_listener.attribute_updates[6][0] + == AqaraLedStripT1.AttributeDefs.audio_effect.id + ) + + assert cluster_listener.attribute_updates[6][1] == LedStripT1AudioEffect.Rainbow + + # Checking preset attribute update + aqara_cluster.update_attribute( + AqaraLedStripT1.AttributeDefs.preset.id, LedStripT1Preset.Sweep + ) + assert len(cluster_listener.attribute_updates) == 8 + assert ( + cluster_listener.attribute_updates[7][0] + == AqaraLedStripT1.AttributeDefs.preset.id + ) + + assert cluster_listener.attribute_updates[7][1] == LedStripT1Preset.Sweep + + # Checking speed attribute update + aqara_cluster.update_attribute(AqaraLedStripT1.AttributeDefs.speed.id, 77) + assert len(cluster_listener.attribute_updates) == 9 + assert ( + cluster_listener.attribute_updates[8][0] + == AqaraLedStripT1.AttributeDefs.speed.id + ) + + assert cluster_listener.attribute_updates[8][1] == 77 diff --git a/zhaquirks/xiaomi/aqara/led_strip_t1.py b/zhaquirks/xiaomi/aqara/led_strip_t1.py new file mode 100644 index 0000000000..0da17c970b --- /dev/null +++ b/zhaquirks/xiaomi/aqara/led_strip_t1.py @@ -0,0 +1,208 @@ +"""Aqara led strip T1.""" + +from typing import Final + +from zigpy import types as t +from zigpy.quirks.v2 import QuirkBuilder +from zigpy.quirks.v2.homeassistant import PERCENTAGE, UnitOfLength +from zigpy.zcl.foundation import DataTypeId, ZCLAttributeDef + +from zhaquirks.xiaomi import XiaomiAqaraE1Cluster + + +class LedStripT1PowerOnStateMode(t.enum8): + """Aqara led strip power on state.""" + + On = 0x00 + Previous = 0x01 + Off = 0x02 + Toggle = 0x03 + + +class LedStripT1Audio(t.enum8): + """Aqara led strip audio enabled.""" + + Off = 0x00 + On = 0x01 + + +class LedStripT1AudioSensitivity(t.enum8): + "Aqara led strip audio sensitivity." + + Low = 0x00 + Medium = 0x01 + High = 0x02 + + +class LedStripT1AudioEffect(t.enum32): + "Aqara led strip audio effect." + + Random = 0x00 + Blink = 0x01 + Rainbow = 0x02 + Wave = 0x03 + + +class LedStripT1Preset(t.enum32): + "Aqara led strip preset." + + Breathe = 0x00 + Rainbow = 0x01 + Sweep = 0x02 + Flashing = 0x03 + Strobe = 0x04 + ReversedRainbow = 0x05 + Colorful = 0x06 + Scan = 0x07 + + +MIN_BRIGHTNESS_ID = 0x0515 +MAX_BRIGHTNESS_ID = 0x0516 +POWER_ON_STATE_ID = 0x0517 +STRIP_LENGTH_ID = 0x051B +ENABLE_AUDIO_ID = 0x051C +AUDIO_EFECT_ID = 0x051D +AUDIO_SENSITIVITY_ID = 0x051E +PRESET_ID = 0x051F +SPEED_ID = 0x0520 + + +class AqaraLedStripT1(XiaomiAqaraE1Cluster): + """Opple cluster.""" + + class AttributeDefs(XiaomiAqaraE1Cluster.AttributeDefs): + """Attribute definitions.""" + + min_brightness: Final = ZCLAttributeDef( + id=MIN_BRIGHTNESS_ID, type=t.uint8_t, is_manufacturer_specific=True + ) + + max_brightness: Final = ZCLAttributeDef( + id=MAX_BRIGHTNESS_ID, type=t.uint8_t, is_manufacturer_specific=True + ) + + power_on_state: Final = ZCLAttributeDef( + id=POWER_ON_STATE_ID, + type=LedStripT1PowerOnStateMode, + zcl_type=DataTypeId.uint8, + is_manufacturer_specific=True, + ) + + length: Final = ZCLAttributeDef( + id=STRIP_LENGTH_ID, type=t.uint8_t, is_manufacturer_specific=True + ) + + audio: Final = ZCLAttributeDef( + id=ENABLE_AUDIO_ID, + type=LedStripT1Audio, + zcl_type=DataTypeId.uint8, + is_manufacturer_specific=True, + ) + + audio_sensitivity: Final = ZCLAttributeDef( + id=AUDIO_SENSITIVITY_ID, + type=LedStripT1AudioSensitivity, + zcl_type=DataTypeId.uint8, + is_manufacturer_specific=True, + ) + + audio_effect: Final = ZCLAttributeDef( + id=AUDIO_EFECT_ID, + type=LedStripT1AudioEffect, + zcl_type=DataTypeId.uint32, + is_manufacturer_specific=True, + ) + + preset: Final = ZCLAttributeDef( + id=PRESET_ID, + type=LedStripT1Preset, + zcl_type=DataTypeId.uint32, + is_manufacturer_specific=True, + ) + + speed: Final = ZCLAttributeDef( + id=SPEED_ID, type=t.uint8_t, is_manufacturer_specific=True + ) + + +( + QuirkBuilder("Aqara", "lumi.light.acn132") + .friendly_name(manufacturer="Aqara", model="Led strip T1") + .replaces(AqaraLedStripT1) + .number( + AqaraLedStripT1.AttributeDefs.min_brightness.name, + AqaraLedStripT1.cluster_id, + min_value=0, + max_value=99, + step=1.0, + unit=PERCENTAGE, + translation_key="min_brightness", + fallback_name="Minimum brightness", + ) + .number( + AqaraLedStripT1.AttributeDefs.max_brightness.name, + AqaraLedStripT1.cluster_id, + min_value=0, + max_value=99, + step=1.0, + unit=PERCENTAGE, + translation_key="max_brightness", + fallback_name="Maximum brightness", + ) + .enum( + AqaraLedStripT1.AttributeDefs.power_on_state.name, + LedStripT1PowerOnStateMode, + AqaraLedStripT1.cluster_id, + translation_key="power_on_state", + fallback_name="Power on state", + ) + .number( + AqaraLedStripT1.AttributeDefs.length.name, + AqaraLedStripT1.cluster_id, + min_value=1, + max_value=10, + step=0.2, + multiplier=0.2, + unit=UnitOfLength.METERS, + translation_key="length", + fallback_name="Length", + ) + .enum( + AqaraLedStripT1.AttributeDefs.audio.name, + LedStripT1Audio, + AqaraLedStripT1.cluster_id, + translation_key="audio", + fallback_name="Audio", + ) + .enum( + AqaraLedStripT1.AttributeDefs.audio_sensitivity.name, + LedStripT1AudioSensitivity, + AqaraLedStripT1.cluster_id, + translation_key="audio_sensitivity", + fallback_name="Audio sensitivity", + ) + .enum( + AqaraLedStripT1.AttributeDefs.audio_effect.name, + LedStripT1AudioEffect, + AqaraLedStripT1.cluster_id, + translation_key="audio_effect", + fallback_name="Audio effect", + ) + .enum( + AqaraLedStripT1.AttributeDefs.preset.name, + LedStripT1Preset, + AqaraLedStripT1.cluster_id, + translation_key="preset", + fallback_name="Preset", + ) + .number( + AqaraLedStripT1.AttributeDefs.speed.name, + AqaraLedStripT1.cluster_id, + min_value=1, + max_value=100, + step=1, + translation_key="speed", + fallback_name="Speed", + ) + .add_to_registry() +)