diff --git a/tests/test_tuya_valve.py b/tests/test_tuya_valve.py index 0a1d2e63af..f77b9b00fa 100644 --- a/tests/test_tuya_valve.py +++ b/tests/test_tuya_valve.py @@ -8,6 +8,8 @@ from tests.common import ClusterListener, wait_for_zigpy_tasks import zhaquirks +import zhaquirks.tuya +import zhaquirks.tuya.ts0601_valve zhaquirks.setup() @@ -127,3 +129,40 @@ async def test_report_values_psbzs(zigpy_device_from_quirk, quirk): assert tuya_listener.attribute_updates[2][1] == 0 # frost lock state is inverted assert tuya_listener.attribute_updates[3][0] == 0xEF13 assert tuya_listener.attribute_updates[3][1] == 1 # frost lock state is inverted + + +@pytest.mark.parametrize( + "model,manuf", + [ + ("_TZE284_8zizsafo", "TS0601"), + ], +) +async def test_giex_03_quirk(zigpy_device_from_v2_quirk, model, manuf): + """Test Giex GX03 Valve Quirk.""" + + quirked_device = zigpy_device_from_v2_quirk(model, manuf) + tuya_cluster = quirked_device.endpoints[1].tuya_manufacturer + + with mock.patch.object( + tuya_cluster.endpoint, "request", return_value=foundation.Status.SUCCESS + ) as m1: + (status,) = await tuya_cluster.write_attributes( + { + "valve_duration_1": 10, + } + ) + await wait_for_zigpy_tasks() + m1.assert_called_with( + cluster=61184, + sequence=1, + data=b"\x01\x01\x00\x00\x01\x19\x02\x00\x04\x00\x00\x00\x0a", + command_id=0, + timeout=5, + expect_reply=False, + use_ieee=False, + ask_for_ack=None, + priority=t.PacketPriority.NORMAL, + ) + assert status == [ + foundation.WriteAttributesStatusRecord(foundation.Status.SUCCESS) + ] diff --git a/zhaquirks/tuya/__init__.py b/zhaquirks/tuya/__init__.py index 84caf4e383..c86c3013e0 100644 --- a/zhaquirks/tuya/__init__.py +++ b/zhaquirks/tuya/__init__.py @@ -929,6 +929,16 @@ class TuyaPowerConfigurationCluster3AA(TuyaPowerConfigurationCluster): } +class TuyaPowerConfigurationCluster4AA(PowerConfiguration, TuyaLocalCluster): + """PowerConfiguration cluster for devices with 4 AA.""" + + _CONSTANT_ATTRIBUTES = { + PowerConfiguration.AttributeDefs.battery_size.id: 3, + PowerConfiguration.AttributeDefs.battery_rated_voltage.id: 15, + PowerConfiguration.AttributeDefs.battery_quantity.id: 4, + } + + class TuyaThermostat(CustomDevice): """Generic Tuya thermostat device.""" diff --git a/zhaquirks/tuya/ts0601_valve.py b/zhaquirks/tuya/ts0601_valve.py index 8c79830a0e..b4a2885d5b 100644 --- a/zhaquirks/tuya/ts0601_valve.py +++ b/zhaquirks/tuya/ts0601_valve.py @@ -2,6 +2,9 @@ from zigpy.profiles import zha from zigpy.quirks import CustomDevice +from zigpy.quirks.v2 import EntityPlatform, EntityType +from zigpy.quirks.v2.homeassistant import UnitOfTime +from zigpy.quirks.v2.homeassistant.sensor import SensorDeviceClass, SensorStateClass import zigpy.types as t from zigpy.zcl import foundation from zigpy.zcl.clusters.general import Basic, Groups, Identify, OnOff, Ota, Scenes, Time @@ -16,7 +19,13 @@ OUTPUT_CLUSTERS, PROFILE_ID, ) -from zhaquirks.tuya import TUYA_CLUSTER_ED00_ID, EnchantedDevice, TuyaLocalCluster +from zhaquirks.tuya import ( + TUYA_CLUSTER_ED00_ID, + EnchantedDevice, + TuyaLocalCluster, + TuyaPowerConfigurationCluster4AA, +) +from zhaquirks.tuya.builder import TuyaQuirkBuilder from zhaquirks.tuya.mcu import ( DPToAttributeMapping, TuyaMCUCluster, @@ -493,3 +502,97 @@ class GiexValveVar02(CustomDevice): } } } + + +class GiexIrrigationStatus(t.enum8): + """Giex Irrigation Status Enum.""" + + Manual = 0x00 + Auto = 0x01 + Idle = 0x02 + + +( + TuyaQuirkBuilder("_TZE284_8zizsafo", "TS0601") # Giex GX04 + .tuya_battery(dp_id=59, power_cfg=TuyaPowerConfigurationCluster4AA) + .tuya_switch( + dp_id=1, + attribute_name="valve_on_off_1", + entity_type=EntityType.STANDARD, + translation_key="valve_on_off_1", + fallback_name="Valve 1", + ) + .tuya_switch( + dp_id=2, + attribute_name="valve_on_off_2", + entity_type=EntityType.STANDARD, + translation_key="valve_on_off_2", + fallback_name="Valve 2", + ) + .tuya_number( + dp_id=13, + attribute_name="valve_countdown_1", + type=t.uint16_t, + device_class=SensorDeviceClass.DURATION, + unit=UnitOfTime.MINUTES, + min_value=0, + max_value=1440, + step=1, + translation_key="valve_countdown_1", + fallback_name="Irrigation time 1", + ) + .tuya_number( + dp_id=14, + attribute_name="valve_countdown_2", + type=t.uint16_t, + device_class=SensorDeviceClass.DURATION, + unit=UnitOfTime.MINUTES, + min_value=0, + max_value=1440, + step=1, + translation_key="valve_countdown_2", + fallback_name="Irrigation time 2", + ) + .tuya_sensor( + dp_id=25, + attribute_name="valve_duration_1", + type=t.uint32_t, + state_class=SensorStateClass.MEASUREMENT, + device_class=SensorDeviceClass.DURATION, + unit=UnitOfTime.SECONDS, + entity_type=EntityType.STANDARD, + translation_key="irrigation_duration_1", + fallback_name="Irrigation duration 1", + ) + .tuya_sensor( + dp_id=26, + attribute_name="valve_duration_2", + type=t.uint32_t, + state_class=SensorStateClass.MEASUREMENT, + device_class=SensorDeviceClass.DURATION, + unit=UnitOfTime.SECONDS, + entity_type=EntityType.STANDARD, + translation_key="irriation_duration_2", + fallback_name="Irrigation duration 2", + ) + .tuya_enum( + dp_id=104, + attribute_name="valve_status_1", + enum_class=GiexIrrigationStatus, + entity_platform=EntityPlatform.SENSOR, + entity_type=EntityType.STANDARD, + translation_key="valve_status_1", + fallback_name="Status 1", + ) + .tuya_enum( + dp_id=105, + attribute_name="valve_status_2", + enum_class=GiexIrrigationStatus, + entity_platform=EntityPlatform.SENSOR, + entity_type=EntityType.STANDARD, + translation_key="valve_status_2", + fallback_name="Status 2", + ) + .skip_configuration() + .add_to_registry() +)