From d4be2ab6c090dcb9cdc8ecec0764fe225f9b2fb6 Mon Sep 17 00:00:00 2001 From: limitup <17516880+limitup@users.noreply.github.com> Date: Sun, 17 Aug 2025 23:32:56 -0500 Subject: [PATCH 1/3] tuya plantation shutter --- zhaquirks/tuya/tuya_shutter.py | 93 ++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 zhaquirks/tuya/tuya_shutter.py diff --git a/zhaquirks/tuya/tuya_shutter.py b/zhaquirks/tuya/tuya_shutter.py new file mode 100644 index 0000000000..1d0a2aaf76 --- /dev/null +++ b/zhaquirks/tuya/tuya_shutter.py @@ -0,0 +1,93 @@ +from zigpy.quirks.v2 import BinarySensorDeviceClass, EntityPlatform, EntityType +from zhaquirks.tuya.builder import TuyaQuirkBuilder +from zhaquirks.const import BatterySize +import zigpy.types as t +from zigpy.quirks.v2.homeassistant import ( + PERCENTAGE, + UnitOfElectricPotential, + UnitOfTime, + UnitOfVolume, +) + +class OpeningStateEnum(t.enum8): + """Enum for opening state.""" + + Open = 0x00 + Stop = 0x01 + Close = 0x02 + Continue = 0x03 + +#class SituationSetEnum(t.enum8): +# """Enum for Situation Set.""" +# +# fully_open = 0 +# fully_close = 1 + +#class MotorDirectionEnum(t.enum8): +# """Enum for Motor Direction.""" +# +# Forward = 0 +# Backward = 1 + +( + TuyaQuirkBuilder("_TZE284_myikb7qz", "TS0601") + .tuya_enum( + dp_id=1, + attribute_name="control_state", + enum_class=OpeningStateEnum, + entity_type=EntityType.STANDARD, + entity_platform=EntityPlatform.SENSOR, + translation_key="control", + fallback_name="Control", + ) + .tuya_number( + dp_id=2, + attribute_name="curtain_target_setting", + type=t.uint32_t, + min_value=0, + max_value=100, + step=1, + unit=PERCENTAGE, + translation_key="curtain_target_setting", + fallback_name="Curtain Target Setting", + ) + .tuya_number( + dp_id=3, + attribute_name="curtain_postion", + type=t.uint32_t, + min_value=0, + max_value=100, + step=1, + unit=PERCENTAGE, + translation_key="curtain_position", + fallback_name="Curtain Position", + ) + #.tuya_enum( + # dp_id=5, + # attribute_name="motor_direction", + # enum_class=MotorDirectionEnum, + # entity_type=EntityType.STANDARD, + # entity_platform=EntityPlatform.SENSOR, + # translation_key="motor_direction", + # fallback_name="Motor Direction", + #) + #.tuya_enum( + # dp_id=11, + # attribute_name="situation_set", + # enum_class=SituationSetEnum, + # entity_type=EntityType.STANDARD, + # entity_platform=EntityPlatform.SENSOR, + # translation_key="situation_set", + # fallback_name="Situation Set", + #) + .tuya_battery(dp_id=13, battery_qty=1) + #.tuya_switch( + # dp_id=101, + # attribute_name="child_lock", + # translation_key="child_lock", + # fallback_name="Child lock", + #) + .skip_configuration() + .add_to_registry() +) + From f37b5dc63df49a6266c0722e824897014823c573 Mon Sep 17 00:00:00 2001 From: limitup <17516880+limitup@users.noreply.github.com> Date: Tue, 19 Aug 2025 15:02:10 -0500 Subject: [PATCH 2/3] pytest --- zhaquirks/tuya/tuya_shutter.py | 129 +++++++++++++++++++++------------ 1 file changed, 84 insertions(+), 45 deletions(-) diff --git a/zhaquirks/tuya/tuya_shutter.py b/zhaquirks/tuya/tuya_shutter.py index 1d0a2aaf76..f923bdce1d 100644 --- a/zhaquirks/tuya/tuya_shutter.py +++ b/zhaquirks/tuya/tuya_shutter.py @@ -1,13 +1,11 @@ -from zigpy.quirks.v2 import BinarySensorDeviceClass, EntityPlatform, EntityType -from zhaquirks.tuya.builder import TuyaQuirkBuilder -from zhaquirks.const import BatterySize +"""Tuya Plantation Shutter.""" + +from zigpy.quirks.v2 import EntityPlatform, EntityType +from zigpy.quirks.v2.homeassistant import PERCENTAGE import zigpy.types as t -from zigpy.quirks.v2.homeassistant import ( - PERCENTAGE, - UnitOfElectricPotential, - UnitOfTime, - UnitOfVolume, -) + +from zhaquirks.tuya.builder import TuyaQuirkBuilder + class OpeningStateEnum(t.enum8): """Enum for opening state.""" @@ -17,17 +15,30 @@ class OpeningStateEnum(t.enum8): Close = 0x02 Continue = 0x03 -#class SituationSetEnum(t.enum8): -# """Enum for Situation Set.""" -# -# fully_open = 0 -# fully_close = 1 -#class MotorDirectionEnum(t.enum8): -# """Enum for Motor Direction.""" -# -# Forward = 0 -# Backward = 1 +class SituationSetEnum(t.enum8): + """Enum for Situation Set.""" + + fully_open = 0x00 + fully_close = 0x01 + + +class MotorDirectionEnum(t.enum8): + """Enum for Motor Direction.""" + + Forward = 0x00 + Backward = 0x01 + + +class BorderLimitEnum(t.enum8): + """Enum for Border Limit Setting.""" + + up = 0x00 + down = 0x01 + up_delete = 0x02 + down_delete = 0x03 + remove_top_bottom = 0x04 + ( TuyaQuirkBuilder("_TZE284_myikb7qz", "TS0601") @@ -40,6 +51,7 @@ class OpeningStateEnum(t.enum8): translation_key="control", fallback_name="Control", ) + # Working .tuya_number( dp_id=2, attribute_name="curtain_target_setting", @@ -51,6 +63,7 @@ class OpeningStateEnum(t.enum8): translation_key="curtain_target_setting", fallback_name="Curtain Target Setting", ) + # Working .tuya_number( dp_id=3, attribute_name="curtain_postion", @@ -62,32 +75,58 @@ class OpeningStateEnum(t.enum8): translation_key="curtain_position", fallback_name="Curtain Position", ) - #.tuya_enum( - # dp_id=5, - # attribute_name="motor_direction", - # enum_class=MotorDirectionEnum, - # entity_type=EntityType.STANDARD, - # entity_platform=EntityPlatform.SENSOR, - # translation_key="motor_direction", - # fallback_name="Motor Direction", - #) - #.tuya_enum( - # dp_id=11, - # attribute_name="situation_set", - # enum_class=SituationSetEnum, - # entity_type=EntityType.STANDARD, - # entity_platform=EntityPlatform.SENSOR, - # translation_key="situation_set", - # fallback_name="Situation Set", - #) - .tuya_battery(dp_id=13, battery_qty=1) - #.tuya_switch( - # dp_id=101, - # attribute_name="child_lock", - # translation_key="child_lock", - # fallback_name="Child lock", - #) + # Working Needs to be manually triggered to pick up sensor + .tuya_enum( + dp_id=5, + attribute_name="motor_direction", + enum_class=MotorDirectionEnum, + entity_type=EntityType.STANDARD, + entity_platform=EntityPlatform.SENSOR, + translation_key="motor_direction", + fallback_name="Motor Direction", + ) + # Not Working + .tuya_enum( + dp_id=11, + attribute_name="situation_set", + enum_class=SituationSetEnum, + entity_type=EntityType.STANDARD, + entity_platform=EntityPlatform.SENSOR, + translation_key="situation_set", + fallback_name="Situation Set", + ) + # power_cfg=PowerConfiguration does not work + .tuya_battery( + dp_id=13, + ) + # Not working + .tuya_enum( + dp_id=16, + attribute_name="border_limit", + enum_class=BorderLimitEnum, + entity_type=EntityType.STANDARD, + entity_platform=EntityPlatform.SENSOR, + translation_key="border_limit", + fallback_name="Border Limit Setting", + ) + # Not working + .tuya_number( + dp_id=19, + attribute_name="position_best", + type=t.uint32_t, + min_value=0, + max_value=100, + step=1, + unit=PERCENTAGE, + translation_key="position_best", + fallback_name="Best Position", + ) + .tuya_switch( + dp_id=101, + attribute_name="child_lock", + translation_key="child_lock", + fallback_name="Child lock", + ) .skip_configuration() .add_to_registry() ) - From e3e38b37c13f60578e282b98c664413e846e4208 Mon Sep 17 00:00:00 2001 From: limitup <17516880+limitup@users.noreply.github.com> Date: Tue, 19 Aug 2025 22:02:36 -0500 Subject: [PATCH 3/3] tests --- tests/test_tuya_shutter.py | 34 ++++++++++++++++++++++++++++++++++ zhaquirks/tuya/tuya_shutter.py | 12 ++++++------ 2 files changed, 40 insertions(+), 6 deletions(-) create mode 100644 tests/test_tuya_shutter.py diff --git a/tests/test_tuya_shutter.py b/tests/test_tuya_shutter.py new file mode 100644 index 0000000000..3c411c970d --- /dev/null +++ b/tests/test_tuya_shutter.py @@ -0,0 +1,34 @@ +"""Tests for Tuya Shutter quirks.""" + +from zigpy.zcl.clusters.general import PowerConfiguration + +import zhaquirks +from zhaquirks.tuya import TuyaLocalCluster +from zhaquirks.tuya.mcu import TuyaMCUCluster + +zhaquirks.setup() + + +def test_valid_attributes(zigpy_device_from_v2_quirk): + """Test that valid attributes on virtual clusters are populated by Tuya datapoints mappings.""" + quirked = zigpy_device_from_v2_quirk("_TZE284_myikb7qz", "TS0601") + ep = quirked.endpoints[1] + + power_attr_id = PowerConfiguration.AttributeDefs.battery_percentage_remaining.id + + power_config_cluster = ep.power + + assert isinstance(power_config_cluster, TuyaLocalCluster) + + # check that the virtual clusters have expected valid attributes + assert {power_attr_id} == power_config_cluster._VALID_ATTRIBUTES + + +async def test_tuya(zigpy_device_from_v2_quirk): + """Example Tuya Test.""" + + quirked = zigpy_device_from_v2_quirk("_TZE284_myikb7qz", "TS0601") + ep = quirked.endpoints[1] + + assert ep.tuya_manufacturer is not None + assert isinstance(ep.tuya_manufacturer, TuyaMCUCluster) diff --git a/zhaquirks/tuya/tuya_shutter.py b/zhaquirks/tuya/tuya_shutter.py index f923bdce1d..61c74cfbc2 100644 --- a/zhaquirks/tuya/tuya_shutter.py +++ b/zhaquirks/tuya/tuya_shutter.py @@ -54,26 +54,26 @@ class BorderLimitEnum(t.enum8): # Working .tuya_number( dp_id=2, - attribute_name="curtain_target_setting", + attribute_name="shutter_target_setting", type=t.uint32_t, min_value=0, max_value=100, step=1, unit=PERCENTAGE, - translation_key="curtain_target_setting", - fallback_name="Curtain Target Setting", + translation_key="shutter_target_setting", + fallback_name="Shutter Target Setting", ) # Working .tuya_number( dp_id=3, - attribute_name="curtain_postion", + attribute_name="shutter_postion", type=t.uint32_t, min_value=0, max_value=100, step=1, unit=PERCENTAGE, - translation_key="curtain_position", - fallback_name="Curtain Position", + translation_key="shutter_position", + fallback_name="Shutter Position", ) # Working Needs to be manually triggered to pick up sensor .tuya_enum(