From 4b32dcf65aba421258af50e0927b42dc73b9430b Mon Sep 17 00:00:00 2001 From: prairiesnpr Date: Mon, 30 Dec 2024 11:08:33 +0000 Subject: [PATCH 1/6] Init rain sensor --- zhaquirks/tuya/tuya_rain.py | 80 +++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 zhaquirks/tuya/tuya_rain.py diff --git a/zhaquirks/tuya/tuya_rain.py b/zhaquirks/tuya/tuya_rain.py new file mode 100644 index 0000000000..3f7d3989eb --- /dev/null +++ b/zhaquirks/tuya/tuya_rain.py @@ -0,0 +1,80 @@ +"""Quirk for TS0207 rain sensors.""" + +import math + +from zigpy.quirks.v2.homeassistant import LIGHT_LUX, EntityType +from zigpy.quirks.v2.homeassistant.sensor import SensorDeviceClass, SensorStateClass +import zigpy.types as t +from zigpy.zcl.clusters.measurement import IlluminanceMeasurement +from zigpy.zcl.clusters.security import IasZone + +from zhaquirks.tuya import TuyaLocalCluster +from zhaquirks.tuya.builder import TuyaQuirkBuilder + + +class TuyaIasZone(IasZone, TuyaLocalCluster): + """IAS Zone for rain sensors.""" + + _CONSTANT_ATTRIBUTES = { + IasZone.AttributeDefs.zone_type.id: IasZone.ZoneType.Water_Sensor + } + + +class TuyaIlluminanceCluster(IlluminanceMeasurement, TuyaLocalCluster): + """Tuya Illuminance cluster.""" + + +( + TuyaQuirkBuilder("_TZ3210_tgvtvdoc", "TS0207") + .tuya_dp( + dp_id=101, + ep_attribute=TuyaIlluminanceCluster.ep_attribute, + attribute_name=TuyaIlluminanceCluster.AttributeDefs.measured_value.name, + converter=lambda x: (10000.0 * math.log10(x) + 1.0 if x != 0 else 0), + ) + .adds(TuyaIlluminanceCluster) + .tuya_sensor( + dp_id=102, + attribute_name="average_light_intensity_20mins", + type=t.uint32_t, + state_class=SensorStateClass.MEASUREMENT, + device_class=SensorDeviceClass.DURATION, + unit=LIGHT_LUX, + entity_type=EntityType.DIAGNOSTIC, + translation_key="average_light_intensity_20mins", + fallback_name="Average light intensity last 20 min", + ) + .tuya_sensor( + dp_id=103, + attribute_name="todays_max_light_intensity", + type=t.uint32_t, + state_class=SensorStateClass.MEASUREMENT, + device_class=SensorDeviceClass.DURATION, + unit=LIGHT_LUX, + entity_type=EntityType.DIAGNOSTIC, + translation_key="todays_max_light_intensity", + fallback_name="Todays max light intensity", + ) + .tuya_binary_sensor( + dp_id=104, + attribute_name="cleaning_reminder", + translation_key="cleaning_reminder", + fallback_name="Cleaning reminder", + ) + .tuya_dp( + dp_id=105, + ep_attribute=TuyaIasZone.ep_attribute, + attribute_name=TuyaIasZone.AttributeDefs.zone_status.name, + converter=lambda x: IasZone.ZoneStatus.Alarm_1 if x > 100 else 0, + ) + # .sensor( + # type=t.uint32_t, + # attribute_name=TuyaIasZone.AttributeDefs.zone_status.name, + # device_class=SensorDeviceClass.VOLTAGE, + # unit=UnitOfElectricPotential.MILLIVOLT, + # fallback_name="Rain Intensity", + # ) + .adds(TuyaIasZone) + .skip_configuration() + .add_to_registry() +) From fc837359b475dbefa992aeabf0505dc9577f9c40 Mon Sep 17 00:00:00 2001 From: prairiesnpr Date: Mon, 30 Dec 2024 11:12:36 +0000 Subject: [PATCH 2/6] Comment out lux for testing --- zhaquirks/tuya/tuya_rain.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/zhaquirks/tuya/tuya_rain.py b/zhaquirks/tuya/tuya_rain.py index 3f7d3989eb..8d8ecd09dd 100644 --- a/zhaquirks/tuya/tuya_rain.py +++ b/zhaquirks/tuya/tuya_rain.py @@ -2,7 +2,7 @@ import math -from zigpy.quirks.v2.homeassistant import LIGHT_LUX, EntityType +from zigpy.quirks.v2.homeassistant import EntityType from zigpy.quirks.v2.homeassistant.sensor import SensorDeviceClass, SensorStateClass import zigpy.types as t from zigpy.zcl.clusters.measurement import IlluminanceMeasurement @@ -39,7 +39,7 @@ class TuyaIlluminanceCluster(IlluminanceMeasurement, TuyaLocalCluster): type=t.uint32_t, state_class=SensorStateClass.MEASUREMENT, device_class=SensorDeviceClass.DURATION, - unit=LIGHT_LUX, + # unit=LIGHT_LUX, entity_type=EntityType.DIAGNOSTIC, translation_key="average_light_intensity_20mins", fallback_name="Average light intensity last 20 min", @@ -50,7 +50,7 @@ class TuyaIlluminanceCluster(IlluminanceMeasurement, TuyaLocalCluster): type=t.uint32_t, state_class=SensorStateClass.MEASUREMENT, device_class=SensorDeviceClass.DURATION, - unit=LIGHT_LUX, + # unit=LIGHT_LUX, entity_type=EntityType.DIAGNOSTIC, translation_key="todays_max_light_intensity", fallback_name="Todays max light intensity", From 9a8d178d6b37b821f6b34857e471d518b6f96004 Mon Sep 17 00:00:00 2001 From: prairiesnpr Date: Sat, 25 Jan 2025 12:44:56 +0000 Subject: [PATCH 3/6] Update methods add battery --- zhaquirks/tuya/tuya_rain.py | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/zhaquirks/tuya/tuya_rain.py b/zhaquirks/tuya/tuya_rain.py index 8d8ecd09dd..7c1d14c917 100644 --- a/zhaquirks/tuya/tuya_rain.py +++ b/zhaquirks/tuya/tuya_rain.py @@ -1,14 +1,11 @@ """Quirk for TS0207 rain sensors.""" -import math - -from zigpy.quirks.v2.homeassistant import EntityType +from zigpy.quirks.v2.homeassistant import LIGHT_LUX, EntityType from zigpy.quirks.v2.homeassistant.sensor import SensorDeviceClass, SensorStateClass import zigpy.types as t -from zigpy.zcl.clusters.measurement import IlluminanceMeasurement from zigpy.zcl.clusters.security import IasZone -from zhaquirks.tuya import TuyaLocalCluster +from zhaquirks.tuya import BatterySize, TuyaLocalCluster from zhaquirks.tuya.builder import TuyaQuirkBuilder @@ -20,26 +17,19 @@ class TuyaIasZone(IasZone, TuyaLocalCluster): } -class TuyaIlluminanceCluster(IlluminanceMeasurement, TuyaLocalCluster): - """Tuya Illuminance cluster.""" - - ( TuyaQuirkBuilder("_TZ3210_tgvtvdoc", "TS0207") - .tuya_dp( - dp_id=101, - ep_attribute=TuyaIlluminanceCluster.ep_attribute, - attribute_name=TuyaIlluminanceCluster.AttributeDefs.measured_value.name, - converter=lambda x: (10000.0 * math.log10(x) + 1.0 if x != 0 else 0), + .tuya_battery( + dp_id=4, battery_type=BatterySize.Other, battery_qty=1, battery_voltage=30 ) - .adds(TuyaIlluminanceCluster) + .tuya_illuminance(dp_id=101) .tuya_sensor( dp_id=102, attribute_name="average_light_intensity_20mins", type=t.uint32_t, state_class=SensorStateClass.MEASUREMENT, device_class=SensorDeviceClass.DURATION, - # unit=LIGHT_LUX, + unit=LIGHT_LUX, entity_type=EntityType.DIAGNOSTIC, translation_key="average_light_intensity_20mins", fallback_name="Average light intensity last 20 min", @@ -50,10 +40,10 @@ class TuyaIlluminanceCluster(IlluminanceMeasurement, TuyaLocalCluster): type=t.uint32_t, state_class=SensorStateClass.MEASUREMENT, device_class=SensorDeviceClass.DURATION, - # unit=LIGHT_LUX, + unit=LIGHT_LUX, entity_type=EntityType.DIAGNOSTIC, translation_key="todays_max_light_intensity", - fallback_name="Todays max light intensity", + fallback_name="Today's max light intensity", ) .tuya_binary_sensor( dp_id=104, From e9ae0dc78a2e08a4b82e698e5791697397593974 Mon Sep 17 00:00:00 2001 From: PrairieSnpr Date: Sat, 22 Feb 2025 11:15:29 -0500 Subject: [PATCH 4/6] Add second DP and tests --- tests/test_tuya_rain.py | 44 ++++++++++++++++++++++++++++++++++ zhaquirks/tuya/tuya_rain.py | 48 +++++++++++++++++++++++++++---------- 2 files changed, 79 insertions(+), 13 deletions(-) create mode 100644 tests/test_tuya_rain.py diff --git a/tests/test_tuya_rain.py b/tests/test_tuya_rain.py new file mode 100644 index 0000000000..85e5bc3eb2 --- /dev/null +++ b/tests/test_tuya_rain.py @@ -0,0 +1,44 @@ +"""Test for Tuya Siren.""" + +import pytest +from zigpy.zcl.clusters.security import IasZone + +from tests.common import ClusterListener +import zhaquirks + +zhaquirks.setup() + + +ZCL_TUYA_RAIN_MV_01 = b"\tp\x02\x00\x02i\x02\x00\x04\x00\x00\x00\x20" # 32mv +ZCL_TUYA_RAIN_MV_02 = b"\tp\x02\x00\x02i\x02\x00\x04\x00\x00\x01\xf4" # 500mv + + +@pytest.mark.parametrize( + "frame,value,rain_detected", + [(ZCL_TUYA_RAIN_MV_01, 32, False), (ZCL_TUYA_RAIN_MV_02, 500, True)], +) +async def test_siren_state_report( + zigpy_device_from_v2_quirk, frame, value, rain_detected +): + """Test tuya rain sensor standard state reporting.""" + + rain_dev = zigpy_device_from_v2_quirk("_TZ3210_tgvtvdoc", "TS0207") + tuya_cluster = rain_dev.endpoints[1].tuya_manufacturer + + ias_listener = ClusterListener(rain_dev.endpoints[1].ias_zone) + rain_listener = ClusterListener(tuya_cluster) + + hdr, args = tuya_cluster.deserialize(frame) + tuya_cluster.handle_message(hdr, args) + + assert len(ias_listener.cluster_commands) == 0 + assert len(ias_listener.attribute_updates) == 1 + assert ias_listener.attribute_updates[0][0] == IasZone.AttributeDefs.zone_status.id + assert ias_listener.attribute_updates[0][1] == ( + IasZone.ZoneStatus.Alarm_1 if rain_detected else 0 + ) + + assert len(rain_listener.cluster_commands) == 1 + assert len(rain_listener.attribute_updates) == 1 + assert rain_listener.attribute_updates[0][0] == 0xEF69 + assert rain_listener.attribute_updates[0][1] == value diff --git a/zhaquirks/tuya/tuya_rain.py b/zhaquirks/tuya/tuya_rain.py index 7c1d14c917..3ef7f84216 100644 --- a/zhaquirks/tuya/tuya_rain.py +++ b/zhaquirks/tuya/tuya_rain.py @@ -1,12 +1,18 @@ """Quirk for TS0207 rain sensors.""" -from zigpy.quirks.v2.homeassistant import LIGHT_LUX, EntityType +from zigpy.quirks.v2.homeassistant import LIGHT_LUX, EntityType, UnitOfElectricPotential from zigpy.quirks.v2.homeassistant.sensor import SensorDeviceClass, SensorStateClass import zigpy.types as t from zigpy.zcl.clusters.security import IasZone -from zhaquirks.tuya import BatterySize, TuyaLocalCluster +from zhaquirks.tuya import ( + TUYA_CLUSTER_ID, + BatterySize, + DPToAttributeMapping, + TuyaLocalCluster, +) from zhaquirks.tuya.builder import TuyaQuirkBuilder +from zhaquirks.tuya.mcu import TuyaMCUCluster class TuyaIasZone(IasZone, TuyaLocalCluster): @@ -51,19 +57,35 @@ class TuyaIasZone(IasZone, TuyaLocalCluster): translation_key="cleaning_reminder", fallback_name="Cleaning reminder", ) - .tuya_dp( + .tuya_dp_multi( + dp_id=105, + attribute_mapping=[ + DPToAttributeMapping( + ep_attribute=TuyaIasZone.ep_attribute, + attribute_name=TuyaIasZone.AttributeDefs.zone_status.name, + converter=lambda x: IasZone.ZoneStatus.Alarm_1 if x > 100 else 0, + ), + DPToAttributeMapping( + ep_attribute=TuyaMCUCluster.ep_attribute, + attribute_name="rain_intensity", + ), + ], + ) + .tuya_attribute( dp_id=105, - ep_attribute=TuyaIasZone.ep_attribute, - attribute_name=TuyaIasZone.AttributeDefs.zone_status.name, - converter=lambda x: IasZone.ZoneStatus.Alarm_1 if x > 100 else 0, + attribute_name="rain_intensity", + type=t.uint32_t, + is_manufacturer_specific=True, + ) + .sensor( + "rain_intensity", + TUYA_CLUSTER_ID, + device_class=SensorDeviceClass.VOLTAGE, + state_class=SensorStateClass.MEASUREMENT, + unit=UnitOfElectricPotential.MILLIVOLT, + entity_type=EntityType.STANDARD, + fallback_name="Rain Intensity", ) - # .sensor( - # type=t.uint32_t, - # attribute_name=TuyaIasZone.AttributeDefs.zone_status.name, - # device_class=SensorDeviceClass.VOLTAGE, - # unit=UnitOfElectricPotential.MILLIVOLT, - # fallback_name="Rain Intensity", - # ) .adds(TuyaIasZone) .skip_configuration() .add_to_registry() From 40b2aeda7be6c0df6f16f949808c5adfcc381819 Mon Sep 17 00:00:00 2001 From: PrairieSnpr Date: Mon, 24 Feb 2025 06:08:28 -0500 Subject: [PATCH 5/6] address review --- zhaquirks/tuya/tuya_rain.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/zhaquirks/tuya/tuya_rain.py b/zhaquirks/tuya/tuya_rain.py index 3ef7f84216..6221d0bd2f 100644 --- a/zhaquirks/tuya/tuya_rain.py +++ b/zhaquirks/tuya/tuya_rain.py @@ -1,6 +1,7 @@ """Quirk for TS0207 rain sensors.""" from zigpy.quirks.v2.homeassistant import LIGHT_LUX, EntityType, UnitOfElectricPotential +from zigpy.quirks.v2.homeassistant.binary_sensor import BinarySensorDeviceClass from zigpy.quirks.v2.homeassistant.sensor import SensorDeviceClass, SensorStateClass import zigpy.types as t from zigpy.zcl.clusters.security import IasZone @@ -54,7 +55,7 @@ class TuyaIasZone(IasZone, TuyaLocalCluster): .tuya_binary_sensor( dp_id=104, attribute_name="cleaning_reminder", - translation_key="cleaning_reminder", + device_class=BinarySensorDeviceClass.PROBLEM, fallback_name="Cleaning reminder", ) .tuya_dp_multi( @@ -84,7 +85,7 @@ class TuyaIasZone(IasZone, TuyaLocalCluster): state_class=SensorStateClass.MEASUREMENT, unit=UnitOfElectricPotential.MILLIVOLT, entity_type=EntityType.STANDARD, - fallback_name="Rain Intensity", + fallback_name="Rain intensity", ) .adds(TuyaIasZone) .skip_configuration() From d88743829c56553c7bccdc00fed84e6b7b7b4595 Mon Sep 17 00:00:00 2001 From: TheJulianJES Date: Wed, 26 Feb 2025 03:38:47 +0100 Subject: [PATCH 6/6] Fix siren references --- tests/test_tuya_rain.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_tuya_rain.py b/tests/test_tuya_rain.py index 85e5bc3eb2..c31e13efb3 100644 --- a/tests/test_tuya_rain.py +++ b/tests/test_tuya_rain.py @@ -1,4 +1,4 @@ -"""Test for Tuya Siren.""" +"""Test for Tuya rain sensor.""" import pytest from zigpy.zcl.clusters.security import IasZone @@ -17,7 +17,7 @@ "frame,value,rain_detected", [(ZCL_TUYA_RAIN_MV_01, 32, False), (ZCL_TUYA_RAIN_MV_02, 500, True)], ) -async def test_siren_state_report( +async def test_rain_sensor_state_report( zigpy_device_from_v2_quirk, frame, value, rain_detected ): """Test tuya rain sensor standard state reporting."""