diff --git a/tests/test_develco.py b/tests/test_develco.py index dd95d12130..eb6a96798d 100644 --- a/tests/test_develco.py +++ b/tests/test_develco.py @@ -2,12 +2,17 @@ from unittest import mock +from zigpy.quirks.v2 import EntityType +from zigpy.quirks.v2.homeassistant import UnitOfTime import zigpy.types as t from zigpy.zcl import ClusterType, foundation +from zigpy.zcl.clusters.general import BinaryInput, PowerConfiguration +from zigpy.zcl.clusters.security import IasWd, IasZone, ZoneStatus from zigpy.zcl.clusters.smartenergy import Metering from tests.common import ClusterListener import zhaquirks +from zhaquirks.develco.smoke_heat_water_alarm import FrientSmokeHeatWaterIasZone zhaquirks.setup() @@ -187,3 +192,56 @@ async def test_mfg_cluster_events(zigpy_device_from_v2_quirk): assert ( metering_cluster.get(Metering.AttributeDefs.current_summ_delivered.id) == 1234 ) + + +async def test_frient_smoke_heat_water_alarm_entities(zigpy_device_from_v2_quirk): + """Test Frient Smoke/Heat/Water alarm exposes test bit and max duration number.""" + + device = zigpy_device_from_v2_quirk( + "frient A/S", + "SMSZB-120", + endpoint_ids=[1, 35], + cluster_ids={ + 35: { + IasZone.cluster_id: ClusterType.Server, + IasWd.cluster_id: ClusterType.Server, + PowerConfiguration.cluster_id: ClusterType.Server, + BinaryInput.cluster_id: ClusterType.Server, + } + }, + ) + + zone_cluster = device.endpoints[35].ias_zone + assert isinstance(zone_cluster, FrientSmokeHeatWaterIasZone) + + test_attr_id = zone_cluster.AttributeDefs.test.id + zone_status_attr_id = zone_cluster.AttributeDefs.zone_status.id + + zone_cluster._update_attribute(zone_status_attr_id, ZoneStatus(0x0000)) + assert zone_cluster.get(test_attr_id) is False + + zone_cluster._update_attribute(zone_status_attr_id, ZoneStatus(0x0100)) + assert zone_cluster.get(test_attr_id) is True + + binary_sensor_key = (35, IasZone.cluster_id, ClusterType.Server) + binary_sensor_metadata = next( + meta + for meta in device.exposes_metadata[binary_sensor_key] + if getattr(meta, "attribute_name", None) == "test" + ) + assert binary_sensor_metadata.entity_type == EntityType.DIAGNOSTIC + assert binary_sensor_metadata.translation_key == "test" + assert binary_sensor_metadata.fallback_name == "IAS test" + + number_key = (35, IasWd.cluster_id, ClusterType.Server) + number_metadata = next( + meta + for meta in device.exposes_metadata[number_key] + if getattr(meta, "attribute_name", None) == "max_duration" + ) + assert number_metadata.min == 0 + assert number_metadata.max == 65535 + assert number_metadata.step == 1 + assert number_metadata.unit == UnitOfTime.SECONDS + assert number_metadata.unique_id_suffix == "max_duration" + assert number_metadata.translation_key == "max_duration" diff --git a/zhaquirks/develco/heat_alarm.py b/zhaquirks/develco/heat_alarm.py deleted file mode 100644 index 0c2addc1f4..0000000000 --- a/zhaquirks/develco/heat_alarm.py +++ /dev/null @@ -1,13 +0,0 @@ -"""Frient Heat Detector.""" - -from zigpy.quirks.v2 import QuirkBuilder - -from . import DevelcoIasZone, DevelcoPowerConfiguration - -( - QuirkBuilder("frient A/S", "HESZB-120") - .applies_to("Develco Products A/S", "HESZB-120") - .replaces(DevelcoIasZone, endpoint_id=35) - .replaces(DevelcoPowerConfiguration, endpoint_id=35) - .add_to_registry() -) diff --git a/zhaquirks/develco/smoke_alarm.py b/zhaquirks/develco/smoke_alarm.py deleted file mode 100644 index e39b905eac..0000000000 --- a/zhaquirks/develco/smoke_alarm.py +++ /dev/null @@ -1,34 +0,0 @@ -"""Frient Smoke Alarm.""" - -from zigpy.quirks.v2 import QuirkBuilder -from zigpy.quirks.v2.homeassistant import EntityType -from zigpy.zcl.clusters.general import BinaryInput -from zigpy.zcl.clusters.security import IasWd, IasZone - -from . import DevelcoIasZone, DevelcoPowerConfiguration - -( - QuirkBuilder("frient A/S", "SMSZB-120") - .applies_to("Develco Products A/S", "SMSZB-120") - .replaces(DevelcoIasZone, endpoint_id=35) - .replaces(DevelcoPowerConfiguration, endpoint_id=35) - # Hide the default binary input sensor - .prevent_default_entity_creation( - endpoint_id=35, - cluster_id=BinaryInput.cluster_id, - ) - # The IAS Zone sensor should be primary - .change_entity_metadata( - endpoint_id=35, - cluster_id=IasZone.cluster_id, - new_primary=True, - ) - # Not the siren - .change_entity_metadata( - endpoint_id=35, - cluster_id=IasWd.cluster_id, - new_primary=False, - new_entity_category=EntityType.DIAGNOSTIC, - ) - .add_to_registry() -) diff --git a/zhaquirks/develco/smoke_heat_water_alarm.py b/zhaquirks/develco/smoke_heat_water_alarm.py new file mode 100644 index 0000000000..6625449fa8 --- /dev/null +++ b/zhaquirks/develco/smoke_heat_water_alarm.py @@ -0,0 +1,77 @@ +"""Frient Smoke & Heat & Water Alarm.""" + +from typing import Final + +from zigpy.quirks.v2 import EntityType, QuirkBuilder +from zigpy.quirks.v2.homeassistant import UnitOfTime +import zigpy.types as t +from zigpy.zcl.clusters.general import BinaryInput +from zigpy.zcl.clusters.security import IasWd, IasZone +from zigpy.zcl.foundation import ZCLAttributeDef + +from zhaquirks.develco import DevelcoIasZone, DevelcoPowerConfiguration + + +class FrientSmokeHeatWaterIasZone(DevelcoIasZone): + """Custom IAS Zone cluster for Smoke Alarm, Heat Alarm and Water Alarm with test support bit exposed.""" + + def _update_attribute(self, attrid, value): + super()._update_attribute(attrid, value) + if attrid == self.AttributeDefs.zone_status.id: + # Update test state from zone_status bit 8 + status_value = int(getattr(value, "value", value)) + test_state = bool(status_value & 0b0000000100000000) + super()._update_attribute(self.AttributeDefs.test.id, test_state) + + class AttributeDefs(IasZone.AttributeDefs): + """Attribute definitions.""" + + test: Final = ZCLAttributeDef( + id=0xFFF2, # Custom attribute ID + type=t.Bool, + ) + + +( + QuirkBuilder("frient A/S", "SMSZB-120") + .applies_to("Develco Products A/S", "SMSZB-120") + .applies_to("frient A/S", "HESZB-120") + .applies_to("Develco Products A/S", "HESZB-120") + .applies_to("frient A/S", "FLSZB-110") + .applies_to("Develco Products A/S", "FLSZB-110") + .replaces(DevelcoPowerConfiguration, endpoint_id=35) + .replaces(FrientSmokeHeatWaterIasZone, endpoint_id=35) + .binary_sensor( + attribute_name="test", + cluster_id=IasZone.cluster_id, + endpoint_id=35, + entity_type=EntityType.DIAGNOSTIC, + translation_key="test", + fallback_name="IAS test", + ) + .number( + attribute_name="max_duration", + cluster_id=IasWd.cluster_id, + endpoint_id=35, + min_value=0, + max_value=65535, + step=1, + unit=UnitOfTime.SECONDS, + translation_key="max_duration", + fallback_name="Max duration", + unique_id_suffix="max_duration", + ) + .prevent_default_entity_creation( + endpoint_id=35, + cluster_id=IasWd.cluster_id, + function=lambda entity: entity.translation_key + in ( + "default_siren_tone", + "default_siren_level", + "default_strobe_level", + "default_strobe", + ), + ) + .prevent_default_entity_creation(endpoint_id=35, cluster_id=BinaryInput.cluster_id) + .add_to_registry() +) diff --git a/zhaquirks/develco/water_leak.py b/zhaquirks/develco/water_leak.py deleted file mode 100644 index 105aced18b..0000000000 --- a/zhaquirks/develco/water_leak.py +++ /dev/null @@ -1,33 +0,0 @@ -"""Frient Water Leak.""" - -from zigpy.quirks.v2 import QuirkBuilder -from zigpy.quirks.v2.homeassistant import EntityType -from zigpy.zcl.clusters.general import BinaryInput -from zigpy.zcl.clusters.security import IasWd, IasZone - -from . import DevelcoIasZone, DevelcoPowerConfiguration - -( - QuirkBuilder("frient A/S", "FLSZB-110") - .replaces(DevelcoIasZone, endpoint_id=35) - .replaces(DevelcoPowerConfiguration, endpoint_id=35) - # Hide the default binary input sensor - .prevent_default_entity_creation( - endpoint_id=35, - cluster_id=BinaryInput.cluster_id, - ) - # The IAS Zone sensor should be primary - .change_entity_metadata( - endpoint_id=35, - cluster_id=IasZone.cluster_id, - new_primary=True, - ) - # Not the siren - .change_entity_metadata( - endpoint_id=35, - cluster_id=IasWd.cluster_id, - new_primary=False, - new_entity_category=EntityType.DIAGNOSTIC, - ) - .add_to_registry() -)