diff --git a/zhaquirks/const.py b/zhaquirks/const.py index 5d0d81480f..df1727baaf 100644 --- a/zhaquirks/const.py +++ b/zhaquirks/const.py @@ -18,6 +18,7 @@ ATTR_ID = "attr_id" ATTRIBUTE_ID = "attribute_id" ATTRIBUTE_NAME = "attribute_name" +ATTRIBUTE_VALUE = "attribute_value" BUTTON = "button" BUTTON_1 = "button_1" BUTTON_2 = "button_2" diff --git a/zhaquirks/develco/__init__.py b/zhaquirks/develco/__init__.py index cbf7cce5cb..0889a41f72 100644 --- a/zhaquirks/develco/__init__.py +++ b/zhaquirks/develco/__init__.py @@ -1,9 +1,11 @@ """Quirks for Develco Products A/S.""" -from zigpy import types as t +from typing import Final + from zigpy.quirks import CustomCluster +import zigpy.types as t from zigpy.zcl import foundation -from zigpy.zcl.clusters.security import IasZone +from zigpy.zcl.clusters.security import IasZone, ZoneStatus from zhaquirks import PowerConfigurationCluster @@ -19,22 +21,19 @@ class DevelcoPowerConfiguration(PowerConfigurationCluster): class DevelcoIasZone(CustomCluster, IasZone): - """Custom IasZone for Develco.""" - - client_commands = { - 0x00: foundation.ZCLCommandDef( - "status_change_notification", - { - "zone_status": IasZone.ZoneStatus, - "extended_status?": t.bitmap8, + """IAS Zone, patched to fix a bug with the status change notification command.""" + + class ClientCommandDefs(IasZone.ClientCommandDefs): + """IAS Zone command definitions.""" + + status_change_notification: Final = foundation.ZCLCommandDef( + id=0x00, + schema={ + "zone_status": ZoneStatus, + "extended_status": t.bitmap8, + # These two should not be optional "zone_id?": t.uint8_t, "delay?": t.uint16_t, }, - False, - ), - 0x01: foundation.ZCLCommandDef( - "enroll", - {"zone_type": IasZone.ZoneType, "manufacturer_code": t.uint16_t}, - False, - ), - } + direction=foundation.Direction.Client_to_Server, + ) diff --git a/zhaquirks/develco/air_quality.py b/zhaquirks/develco/air_quality.py index c8819ec292..03358a4340 100644 --- a/zhaquirks/develco/air_quality.py +++ b/zhaquirks/develco/air_quality.py @@ -1,241 +1,80 @@ """Develco Air Quality Sensor.""" import logging +from typing import Final -from zigpy.profiles import zha -from zigpy.quirks import CustomCluster, CustomDevice +from zigpy.quirks import CustomCluster +from zigpy.quirks.v2 import QuirkBuilder, SensorDeviceClass, SensorStateClass +from zigpy.quirks.v2.homeassistant import CONCENTRATION_PARTS_PER_BILLION import zigpy.types as t -from zigpy.zcl.clusters.general import ( - Basic, - Identify, - OnOff, - Ota, - PollControl, - PowerConfiguration, - Scenes, - Time, +from zigpy.zcl.foundation import ( + ZCL_CLUSTER_REVISION_ATTR, + ZCL_REPORTING_STATUS_ATTR, + BaseAttributeDefs, + ZCLAttributeDef, ) -from zigpy.zcl.clusters.measurement import RelativeHumidity, TemperatureMeasurement -from zhaquirks import Bus, LocalDataCluster -from zhaquirks.const import ( - DEVICE_TYPE, - ENDPOINTS, - INPUT_CLUSTERS, - MODELS_INFO, - OUTPUT_CLUSTERS, - PROFILE_ID, -) -from zhaquirks.develco import DEVELCO, DevelcoPowerConfiguration - -MANUFACTURER = 0x1015 -VOC_MEASURED_VALUE = 0x0000 -VOC_MIN_MEASURED_VALUE = 0x0001 -VOC_MAX_MEASURED_VALUE = 0x0002 -VOC_RESOLUTION = 0x0003 - -VOC_REPORTED = "voc_reported" -MIN_VOC_REPORTED = "min_voc_reported" -MAX_VOC_REPORTED = "max_voc_reported" -VOC_RESOLUTION_REPORTED = "voc_resolution_reported" +from zhaquirks.develco import DevelcoPowerConfiguration _LOGGER = logging.getLogger(__name__) class DevelcoVOCMeasurement(CustomCluster): - """Input Cluster to route manufacturer specific VOC cluster to actual VOC cluster.""" + """Develco VOC cluster definition.""" cluster_id = 0xFC03 name = "VOC Level" - ep_attribute = "voc_level" - attributes = { - VOC_MEASURED_VALUE: ("measured_value", t.uint16_t, True), - VOC_MIN_MEASURED_VALUE: ("min_measured_value", t.uint16_t, True), - VOC_MAX_MEASURED_VALUE: ("max_measured_value", t.uint16_t, True), - VOC_RESOLUTION: ("resolution", t.uint16_t, True), - } - server_commands = {} - client_commands = {} + ep_attribute = "develco_voc_level" - def __init__(self, *args, **kwargs): - """Init.""" - self._current_state = {} - super().__init__(*args, **kwargs) - self.endpoint.device.app_cluster = self + class AttributeDefs(BaseAttributeDefs): + """Attribute definitions, same as all the other `Measurement` clusters.""" - def _update_attribute(self, attrid, value): - super()._update_attribute(attrid, value) - if attrid == VOC_MEASURED_VALUE and value is not None: - self.endpoint.device.voc_bus.listener_event(VOC_REPORTED, value) - if attrid == VOC_MIN_MEASURED_VALUE and value is not None: - self.endpoint.device.voc_bus.listener_event(MIN_VOC_REPORTED, value) - if attrid == VOC_MAX_MEASURED_VALUE and value is not None: - self.endpoint.device.voc_bus.listener_event(MAX_VOC_REPORTED, value) - if attrid == VOC_RESOLUTION and value is not None: - self.endpoint.device.voc_bus.listener_event(VOC_RESOLUTION_REPORTED, value) - _LOGGER.debug( - "%s Develco VOC : [%s]", - self.endpoint.device.ieee, - self._attr_cache, + measured_value: Final = ZCLAttributeDef( + id=0x0000, + type=t.uint16_t, # In parts per billion + access="rp", + mandatory=True, + is_manufacturer_specific=True, ) - - -class DevelcoRelativeHumidity(CustomCluster, RelativeHumidity): - """Handles invalid values for Humidity.""" - - def _update_attribute(self, attrid, value): - # Drop values out of specified range (0-100% RH) - if 0 <= value <= 10000: - super()._update_attribute(attrid, value) - _LOGGER.debug( - "%s Develco Humidity : [%s]", - self.endpoint.device.ieee, - self._attr_cache, + min_measured_value: Final = ZCLAttributeDef( + id=0x0001, + type=t.uint16_t, + access="r", + mandatory=True, + is_manufacturer_specific=True, ) - - -class DevelcoTemperatureMeasurement(CustomCluster, TemperatureMeasurement): - """Handles invalid values for Temperature.""" - - def _update_attribute(self, attrid, value): - # Drop values out of specified range (0-50°C) - if 0 <= value <= 5000: - super()._update_attribute(attrid, value) - _LOGGER.debug( - "%s Develco Temperature : [%s]", - self.endpoint.device.ieee, - self._attr_cache, + max_measured_value: Final = ZCLAttributeDef( + id=0x0002, + type=t.uint16_t, + access="r", + mandatory=True, + is_manufacturer_specific=True, + ) + tolerance: Final = ZCLAttributeDef( + id=0x0003, + type=t.uint16_t, + access="r", + is_manufacturer_specific=True, ) - -class EmulatedVOCMeasurement(LocalDataCluster): - """VOC measurement cluster to receive reports from the Develco VOC cluster.""" - - cluster_id = 0x042E - name = "VOC Level" - ep_attribute = "voc_level" - attributes = { - VOC_MEASURED_VALUE: ("measured_value", t.uint16_t, True), - VOC_MIN_MEASURED_VALUE: ("min_measured_value", t.uint16_t, True), - VOC_MAX_MEASURED_VALUE: ("max_measured_value", t.uint16_t, True), - VOC_RESOLUTION: ("resolution", t.uint16_t, True), - } - MEASURED_VALUE_ID = 0x0000 - MIN_MEASURED_VALUE_ID = 0x0001 - MAX_MEASURED_VALUE_ID = 0x0002 - RESOLUTION_ID = 0x0003 - - def __init__(self, *args, **kwargs): - """Init.""" - super().__init__(*args, **kwargs) - self.endpoint.device.voc_bus.add_listener(self) - - async def bind(self): - """Bind cluster.""" - result = await self.endpoint.device.app_cluster.bind() - return result - - async def write_attributes(self, attributes, manufacturer=None): - """Ignore write_attributes.""" - return (0,) - - def _update_attribute(self, attrid, value): - # Drop values out of specified range (0-60000 ppb) - if 0 <= value <= 60000: - # Convert ppb into mg/m³ approximation according to develco spec - value = value * 0.0000045 - super()._update_attribute(attrid, value) - - def voc_reported(self, value): - """VOC reported.""" - self._update_attribute(self.MEASURED_VALUE_ID, value) - - def min_voc_reported(self, value): - """Minimum Measured VOC reported.""" - self._update_attribute(self.MIN_MEASURED_VALUE_ID, value) - - def max_voc_reported(self, value): - """Maximum Measured VOC reported.""" - self._update_attribute(self.MAX_MEASURED_VALUE_ID, value) - - def voc_resolution_reported(self, value): - """VOC Resolution reported.""" - self._update_attribute(self.RESOLUTION_ID, value) - - -class AQSZB110(CustomDevice): - """Custom device Develco air quality sensor.""" - - manufacturer_id_override = MANUFACTURER - - def __init__(self, *args, **kwargs): - """Init.""" - self.voc_bus = Bus() - super().__init__(*args, **kwargs) - - signature = { - # - # - MODELS_INFO: [ - (DEVELCO, "AQSZB-110"), - ("frient A/S", "AQSZB-110"), - ], - ENDPOINTS: { - 1: { - PROFILE_ID: 0xC0C9, - DEVICE_TYPE: 1, - INPUT_CLUSTERS: [ - Identify.cluster_id, - Scenes.cluster_id, - OnOff.cluster_id, - ], - OUTPUT_CLUSTERS: [], - }, - 38: { - PROFILE_ID: zha.PROFILE_ID, - DEVICE_TYPE: zha.DeviceType.TEMPERATURE_SENSOR, - INPUT_CLUSTERS: [ - Basic.cluster_id, - PowerConfiguration.cluster_id, - Identify.cluster_id, - PollControl.cluster_id, - TemperatureMeasurement.cluster_id, - RelativeHumidity.cluster_id, - 0xFC03, - ], - OUTPUT_CLUSTERS: [Identify.cluster_id, Time.cluster_id, Ota.cluster_id], - }, - }, - } - - replacement = { - ENDPOINTS: { - 1: { - PROFILE_ID: 0xC0C9, - DEVICE_TYPE: 1, - INPUT_CLUSTERS: [ - Identify.cluster_id, - Scenes.cluster_id, - OnOff.cluster_id, - ], - OUTPUT_CLUSTERS: [], - }, - 38: { - PROFILE_ID: zha.PROFILE_ID, - DEVICE_TYPE: zha.DeviceType.TEMPERATURE_SENSOR, - INPUT_CLUSTERS: [ - Basic.cluster_id, - DevelcoPowerConfiguration, - Identify.cluster_id, - PollControl.cluster_id, - DevelcoTemperatureMeasurement, - DevelcoRelativeHumidity, - DevelcoVOCMeasurement, - EmulatedVOCMeasurement, - ], - OUTPUT_CLUSTERS: [Identify.cluster_id, Time.cluster_id, Ota.cluster_id], - }, - }, - } + cluster_revision: Final = ZCL_CLUSTER_REVISION_ATTR + reporting_status: Final = ZCL_REPORTING_STATUS_ATTR + + +( + QuirkBuilder("frient A/S", "AQSZB-110") + .applies_to("Develco Products A/S", "AQSZB-110") + .replaces(DevelcoVOCMeasurement, endpoint_id=38) + .replaces(DevelcoPowerConfiguration, endpoint_id=38) + .sensor( + attribute_name=DevelcoVOCMeasurement.AttributeDefs.measured_value.name, + cluster_id=DevelcoVOCMeasurement.cluster_id, + endpoint_id=38, + device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS, + state_class=SensorStateClass.MEASUREMENT, + unit=CONCENTRATION_PARTS_PER_BILLION, + fallback_name="VOC Level", + unique_id_suffix="voc_level", + ) + .add_to_registry() +) diff --git a/zhaquirks/develco/emi.py b/zhaquirks/develco/emi.py new file mode 100644 index 0000000000..2e2a5eb1c7 --- /dev/null +++ b/zhaquirks/develco/emi.py @@ -0,0 +1,15 @@ +"""Frient Electricity Meter Interface.""" + +from zigpy.quirks.v2 import QuirkBuilder + +( + QuirkBuilder("frient A/S", "EMIZB-151") + # These endpoints are duplicates and completely broken: each one is a "mirror" of + # endpoint 2 and will set up duplicate attribute reporting for every attribute, the + # attribute reports will instead be emitted from endpoint 2! + .prevent_default_entity_creation(endpoint_id=64) + .prevent_default_entity_creation(endpoint_id=65) + .prevent_default_entity_creation(endpoint_id=66) + .prevent_default_entity_creation(endpoint_id=67) + .add_to_registry() +) diff --git a/zhaquirks/develco/heat_alarm.py b/zhaquirks/develco/heat_alarm.py index d6334c31b2..0c2addc1f4 100644 --- a/zhaquirks/develco/heat_alarm.py +++ b/zhaquirks/develco/heat_alarm.py @@ -1,213 +1,13 @@ -"""Develco Heat Alarm.""" +"""Frient Heat Detector.""" -import zigpy.profiles.zha -from zigpy.quirks import CustomDevice -from zigpy.zcl.clusters.general import ( - Basic, - BinaryInput, - Identify, - OnOff, - Ota, - PollControl, - PowerConfiguration, - Scenes, - Time, -) -from zigpy.zcl.clusters.measurement import TemperatureMeasurement -from zigpy.zcl.clusters.security import IasWd, IasZone - -from zhaquirks.const import ( - DEVICE_TYPE, - ENDPOINTS, - INPUT_CLUSTERS, - MODELS_INFO, - OUTPUT_CLUSTERS, - PROFILE_ID, -) -from zhaquirks.develco import DEVELCO, FRIENT, DevelcoIasZone, DevelcoPowerConfiguration - -MANUFACTURER = 0x1015 - - -class HESZB120(CustomDevice): - """Custom device heat alarm.""" - - manufacturer_id_override = MANUFACTURER +from zigpy.quirks.v2 import QuirkBuilder - signature = { - # - # - # - MODELS_INFO: [(DEVELCO, "HESZB-120"), (FRIENT, "HESZB-120")], - ENDPOINTS: { - 1: { - PROFILE_ID: 49353, - DEVICE_TYPE: 1, - INPUT_CLUSTERS: [ - Identify.cluster_id, - Scenes.cluster_id, - OnOff.cluster_id, - ], - OUTPUT_CLUSTERS: [], - }, - 35: { - PROFILE_ID: zigpy.profiles.zha.PROFILE_ID, - DEVICE_TYPE: zigpy.profiles.zha.DeviceType.IAS_CONTROL, - INPUT_CLUSTERS: [ - Basic.cluster_id, - PowerConfiguration.cluster_id, - Identify.cluster_id, - BinaryInput.cluster_id, - PollControl.cluster_id, - IasZone.cluster_id, - IasWd.cluster_id, - ], - OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id], - }, - 38: { - PROFILE_ID: zigpy.profiles.zha.PROFILE_ID, - DEVICE_TYPE: zigpy.profiles.zha.DeviceType.TEMPERATURE_SENSOR, - INPUT_CLUSTERS: [ - Basic.cluster_id, - Identify.cluster_id, - TemperatureMeasurement.cluster_id, - ], - OUTPUT_CLUSTERS: [Identify.cluster_id], - }, - }, - } +from . import DevelcoIasZone, DevelcoPowerConfiguration - replacement = { - ENDPOINTS: { - 1: { - PROFILE_ID: 49353, - DEVICE_TYPE: 1, - INPUT_CLUSTERS: [ - Identify.cluster_id, - Scenes.cluster_id, - OnOff.cluster_id, - ], - OUTPUT_CLUSTERS: [], - }, - 35: { - PROFILE_ID: zigpy.profiles.zha.PROFILE_ID, - DEVICE_TYPE: zigpy.profiles.zha.DeviceType.IAS_CONTROL, - INPUT_CLUSTERS: [ - Basic.cluster_id, - DevelcoPowerConfiguration, - Identify.cluster_id, - BinaryInput.cluster_id, - PollControl.cluster_id, - DevelcoIasZone, - IasWd.cluster_id, - ], - OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id], - }, - 38: { - PROFILE_ID: zigpy.profiles.zha.PROFILE_ID, - DEVICE_TYPE: zigpy.profiles.zha.DeviceType.TEMPERATURE_SENSOR, - INPUT_CLUSTERS: [ - Basic.cluster_id, - Identify.cluster_id, - TemperatureMeasurement.cluster_id, - ], - OUTPUT_CLUSTERS: [Identify.cluster_id], - }, - }, - } - - -class HESZB120F(CustomDevice): - """Frient A/S Heat Alarm.""" - - manufacturer_id_override = MANUFACTURER - - signature = { - # - # - # - MODELS_INFO: [ - (FRIENT, "HESZB-120"), - ], - ENDPOINTS: { - 1: { - PROFILE_ID: 49353, - DEVICE_TYPE: 1, - INPUT_CLUSTERS: [ - Identify.cluster_id, - Scenes.cluster_id, - OnOff.cluster_id, - ], - OUTPUT_CLUSTERS: [], - }, - 35: { - PROFILE_ID: zigpy.profiles.zha.PROFILE_ID, - DEVICE_TYPE: zigpy.profiles.zha.DeviceType.IAS_ZONE, - INPUT_CLUSTERS: [ - Basic.cluster_id, - PowerConfiguration.cluster_id, - Identify.cluster_id, - BinaryInput.cluster_id, - PollControl.cluster_id, - IasZone.cluster_id, - IasWd.cluster_id, - ], - OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id], - }, - 38: { - PROFILE_ID: zigpy.profiles.zha.PROFILE_ID, - DEVICE_TYPE: zigpy.profiles.zha.DeviceType.TEMPERATURE_SENSOR, - INPUT_CLUSTERS: [ - Basic.cluster_id, - Identify.cluster_id, - TemperatureMeasurement.cluster_id, - ], - OUTPUT_CLUSTERS: [Identify.cluster_id], - }, - }, - } - - replacement = { - ENDPOINTS: { - 1: { - PROFILE_ID: 49353, - DEVICE_TYPE: 1, - INPUT_CLUSTERS: [ - Identify.cluster_id, - Scenes.cluster_id, - OnOff.cluster_id, - ], - OUTPUT_CLUSTERS: [], - }, - 35: { - PROFILE_ID: zigpy.profiles.zha.PROFILE_ID, - DEVICE_TYPE: zigpy.profiles.zha.DeviceType.IAS_ZONE, - INPUT_CLUSTERS: [ - Basic.cluster_id, - DevelcoPowerConfiguration, - Identify.cluster_id, - BinaryInput.cluster_id, - PollControl.cluster_id, - DevelcoIasZone, - IasWd.cluster_id, - ], - OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id], - }, - 38: { - PROFILE_ID: zigpy.profiles.zha.PROFILE_ID, - DEVICE_TYPE: zigpy.profiles.zha.DeviceType.TEMPERATURE_SENSOR, - INPUT_CLUSTERS: [ - Basic.cluster_id, - Identify.cluster_id, - TemperatureMeasurement.cluster_id, - ], - OUTPUT_CLUSTERS: [Identify.cluster_id], - }, - }, - } +( + 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/humidity.py b/zhaquirks/develco/humidity.py new file mode 100644 index 0000000000..8029f56796 --- /dev/null +++ b/zhaquirks/develco/humidity.py @@ -0,0 +1,11 @@ +"""Develco Smart Humidity Sensor.""" + +from zigpy.quirks.v2 import QuirkBuilder + +from zhaquirks.develco import DevelcoPowerConfiguration + +( + QuirkBuilder("frient A/S", "HMSZB-120") + .replaces(DevelcoPowerConfiguration, endpoint_id=38) + .add_to_registry() +) diff --git a/zhaquirks/develco/intelligent_keypad.py b/zhaquirks/develco/intelligent_keypad.py new file mode 100644 index 0000000000..92edea143f --- /dev/null +++ b/zhaquirks/develco/intelligent_keypad.py @@ -0,0 +1,39 @@ +"""Intelligent keypad.""" + +from zigpy.quirks.v2 import QuirkBuilder +from zigpy.quirks.v2.homeassistant.binary_sensor import BinarySensorDeviceClass +from zigpy.zcl.clusters.general import BinaryInput +from zigpy.zcl.clusters.security import IasWd, IasZone + +( + QuirkBuilder("frient A/S", "KEPZB-110") + .prevent_default_entity_creation(endpoint_id=44, cluster_id=BinaryInput.cluster_id) + # Hide the default `ias_zone` entity + .prevent_default_entity_creation( + endpoint_id=44, + cluster_id=IasZone.cluster_id, + function=lambda entity: entity.translation_key == "ias_zone", + ) + .prevent_default_entity_creation( + endpoint_id=44, + cluster_id=IasWd.cluster_id, + function=lambda entity: entity.translation_key + in ( + "default_siren_tone", + "default_siren_level", + "default_strobe_level", + "default_strobe", + ), + ) + .binary_sensor( + endpoint_id=44, + cluster_id=IasZone.cluster_id, + attribute_name=IasZone.AttributeDefs.zone_status.name, + device_class=BinarySensorDeviceClass.TAMPER, + attribute_converter=lambda value: bool(value & IasZone.ZoneStatus.Tamper), + unique_id_suffix="tamper", + translation_key="tamper", + fallback_name="Tamper", + ) + .add_to_registry() +) diff --git a/zhaquirks/develco/io_module.py b/zhaquirks/develco/io_module.py new file mode 100644 index 0000000000..9448b3090e --- /dev/null +++ b/zhaquirks/develco/io_module.py @@ -0,0 +1,16 @@ +"""Develco IO Module.""" + +from zigpy.quirks.v2 import QuirkBuilder + +( + QuirkBuilder("frient A/S", "IOMZB-110") + # Name the two outputs + # .add_metadata(unique_id="116-0x0006-on_off", name="COM 1") + # .add_metadata(unique_id="117-0x0006-on_off", name="COM 2") + # And the two inputs + # .add_metadata(unique_id="112-0x000f-binary_input", name="IN1") + # .add_metadata(unique_id="112-0x000f-binary_input", name="IN2") + # .add_metadata(unique_id="112-0x000f-binary_input", name="IN3") + # .add_metadata(unique_id="112-0x000f-binary_input", name="IN4") + .add_to_registry() +) diff --git a/zhaquirks/develco/motion.py b/zhaquirks/develco/motion.py index bb5f5e909d..758e37f8d1 100644 --- a/zhaquirks/develco/motion.py +++ b/zhaquirks/develco/motion.py @@ -1,398 +1,22 @@ """Develco Motion Sensor Pro.""" -from zigpy.profiles import zha -from zigpy.quirks import CustomDevice -from zigpy.zcl.clusters.general import ( - Basic, - BinaryInput, - Identify, - OnOff, - Ota, - PollControl, - PowerConfiguration, - Scenes, - Time, -) -from zigpy.zcl.clusters.measurement import ( - IlluminanceMeasurement, - OccupancySensing, - TemperatureMeasurement, -) -from zigpy.zcl.clusters.security import IasZone +from zigpy.quirks.v2 import QuirkBuilder +from zigpy.zcl.clusters.general import BinaryInput -from zhaquirks.const import ( - DEVICE_TYPE, - ENDPOINTS, - INPUT_CLUSTERS, - MODELS_INFO, - OUTPUT_CLUSTERS, - PROFILE_ID, -) from zhaquirks.develco import DEVELCO, FRIENT, DevelcoIasZone, DevelcoPowerConfiguration -MANUFACTURER = 0x1015 - - -class MOSZB140(CustomDevice): - """Custom device Develco Motion Sensor Pro.""" - - manufacturer_id_override = MANUFACTURER - - signature = { - # - # - # - # - # - # - # - MODELS_INFO: [(DEVELCO, "MOSZB-140"), (FRIENT, "MOSZB-140")], - ENDPOINTS: { - 1: { - PROFILE_ID: 0xC0C9, - DEVICE_TYPE: 1, - INPUT_CLUSTERS: [ - Identify.cluster_id, - Scenes.cluster_id, - OnOff.cluster_id, - ], - OUTPUT_CLUSTERS: [], - }, - 34: { - PROFILE_ID: zha.PROFILE_ID, - DEVICE_TYPE: zha.DeviceType.OCCUPANCY_SENSOR, - INPUT_CLUSTERS: [ - Basic.cluster_id, - Identify.cluster_id, - OccupancySensing.cluster_id, - ], - OUTPUT_CLUSTERS: [], - }, - 35: { - PROFILE_ID: zha.PROFILE_ID, - DEVICE_TYPE: zha.DeviceType.IAS_ZONE, - INPUT_CLUSTERS: [ - Basic.cluster_id, - PowerConfiguration.cluster_id, - Identify.cluster_id, - BinaryInput.cluster_id, - PollControl.cluster_id, - IasZone.cluster_id, - ], - OUTPUT_CLUSTERS: [ - Time.cluster_id, - Ota.cluster_id, - ], - }, - 38: { - PROFILE_ID: zha.PROFILE_ID, - DEVICE_TYPE: zha.DeviceType.TEMPERATURE_SENSOR, - INPUT_CLUSTERS: [ - Basic.cluster_id, - Identify.cluster_id, - TemperatureMeasurement.cluster_id, - ], - OUTPUT_CLUSTERS: [Identify.cluster_id], - }, - 39: { - PROFILE_ID: zha.PROFILE_ID, - DEVICE_TYPE: zha.DeviceType.LIGHT_SENSOR, - INPUT_CLUSTERS: [ - Basic.cluster_id, - Identify.cluster_id, - IlluminanceMeasurement.cluster_id, - ], - OUTPUT_CLUSTERS: [], - }, - 40: { - PROFILE_ID: zha.PROFILE_ID, - DEVICE_TYPE: zha.DeviceType.OCCUPANCY_SENSOR, - INPUT_CLUSTERS: [ - Basic.cluster_id, - Identify.cluster_id, - OccupancySensing.cluster_id, - ], - OUTPUT_CLUSTERS: [], - }, - 41: { - PROFILE_ID: zha.PROFILE_ID, - DEVICE_TYPE: zha.DeviceType.OCCUPANCY_SENSOR, - INPUT_CLUSTERS: [ - Basic.cluster_id, - Identify.cluster_id, - OccupancySensing.cluster_id, - ], - OUTPUT_CLUSTERS: [], - }, - }, - } - - replacement = { - ENDPOINTS: { - 1: { - PROFILE_ID: 0xC0C9, - DEVICE_TYPE: 1, - INPUT_CLUSTERS: [ - Identify.cluster_id, - Scenes.cluster_id, - OnOff.cluster_id, - ], - OUTPUT_CLUSTERS: [], - }, - 34: { - PROFILE_ID: zha.PROFILE_ID, - DEVICE_TYPE: zha.DeviceType.OCCUPANCY_SENSOR, - INPUT_CLUSTERS: [ - Basic.cluster_id, - Identify.cluster_id, - OccupancySensing.cluster_id, - ], - OUTPUT_CLUSTERS: [], - }, - 35: { - PROFILE_ID: zha.PROFILE_ID, - DEVICE_TYPE: zha.DeviceType.IAS_ZONE, - INPUT_CLUSTERS: [ - Basic.cluster_id, - DevelcoPowerConfiguration, - Identify.cluster_id, - BinaryInput.cluster_id, - PollControl.cluster_id, - DevelcoIasZone, - ], - OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id], - }, - 38: { - PROFILE_ID: zha.PROFILE_ID, - DEVICE_TYPE: zha.DeviceType.TEMPERATURE_SENSOR, - INPUT_CLUSTERS: [ - Basic.cluster_id, - Identify.cluster_id, - TemperatureMeasurement.cluster_id, - ], - OUTPUT_CLUSTERS: [Identify.cluster_id], - }, - 39: { - PROFILE_ID: zha.PROFILE_ID, - DEVICE_TYPE: zha.DeviceType.LIGHT_SENSOR, - INPUT_CLUSTERS: [ - Basic.cluster_id, - Identify.cluster_id, - IlluminanceMeasurement.cluster_id, - ], - OUTPUT_CLUSTERS: [], - }, - 40: { - PROFILE_ID: zha.PROFILE_ID, - DEVICE_TYPE: zha.DeviceType.OCCUPANCY_SENSOR, - INPUT_CLUSTERS: [ - Basic.cluster_id, - Identify.cluster_id, - OccupancySensing.cluster_id, - ], - OUTPUT_CLUSTERS: [], - }, - 41: { - PROFILE_ID: zha.PROFILE_ID, - DEVICE_TYPE: zha.DeviceType.OCCUPANCY_SENSOR, - INPUT_CLUSTERS: [ - Basic.cluster_id, - Identify.cluster_id, - OccupancySensing.cluster_id, - ], - OUTPUT_CLUSTERS: [], - }, - } - } - - -class MOSZB140_Var02(CustomDevice): - """Custom device Develco Motion Sensor Pro (variation 02).""" - - manufacturer_id_override = MANUFACTURER - - signature = { - # - # - # - # - # - # - # - MODELS_INFO: [(DEVELCO, "MOSZB-140"), (FRIENT, "MOSZB-140")], - ENDPOINTS: { - 1: { - PROFILE_ID: 0xC0C9, - DEVICE_TYPE: 1, - INPUT_CLUSTERS: [ - Identify.cluster_id, - Scenes.cluster_id, - OnOff.cluster_id, - ], - OUTPUT_CLUSTERS: [], - }, - 34: { - PROFILE_ID: zha.PROFILE_ID, - DEVICE_TYPE: zha.DeviceType.OCCUPANCY_SENSOR, - INPUT_CLUSTERS: [ - Basic.cluster_id, - Identify.cluster_id, - OccupancySensing.cluster_id, - ], - OUTPUT_CLUSTERS: [], - }, - 35: { - PROFILE_ID: zha.PROFILE_ID, - DEVICE_TYPE: zha.DeviceType.IAS_ZONE, - INPUT_CLUSTERS: [ - Basic.cluster_id, - PowerConfiguration.cluster_id, - Identify.cluster_id, - BinaryInput.cluster_id, - PollControl.cluster_id, - IasZone.cluster_id, - ], - OUTPUT_CLUSTERS: [ - Identify.cluster_id, - Time.cluster_id, - Ota.cluster_id, - ], - }, - 38: { - PROFILE_ID: zha.PROFILE_ID, - DEVICE_TYPE: zha.DeviceType.TEMPERATURE_SENSOR, - INPUT_CLUSTERS: [ - Basic.cluster_id, - Identify.cluster_id, - TemperatureMeasurement.cluster_id, - ], - OUTPUT_CLUSTERS: [], - }, - 39: { - PROFILE_ID: zha.PROFILE_ID, - DEVICE_TYPE: zha.DeviceType.LIGHT_SENSOR, - INPUT_CLUSTERS: [ - Basic.cluster_id, - Identify.cluster_id, - IlluminanceMeasurement.cluster_id, - ], - OUTPUT_CLUSTERS: [], - }, - 40: { - PROFILE_ID: zha.PROFILE_ID, - DEVICE_TYPE: zha.DeviceType.OCCUPANCY_SENSOR, - INPUT_CLUSTERS: [ - Basic.cluster_id, - Identify.cluster_id, - OccupancySensing.cluster_id, - ], - OUTPUT_CLUSTERS: [], - }, - 41: { - PROFILE_ID: zha.PROFILE_ID, - DEVICE_TYPE: zha.DeviceType.OCCUPANCY_SENSOR, - INPUT_CLUSTERS: [ - Basic.cluster_id, - Identify.cluster_id, - OccupancySensing.cluster_id, - ], - OUTPUT_CLUSTERS: [], - }, - }, - } - - replacement = { - ENDPOINTS: { - 1: { - PROFILE_ID: 0xC0C9, - DEVICE_TYPE: 1, - INPUT_CLUSTERS: [ - Identify.cluster_id, - Scenes.cluster_id, - OnOff.cluster_id, - ], - OUTPUT_CLUSTERS: [], - }, - 34: { - PROFILE_ID: zha.PROFILE_ID, - DEVICE_TYPE: zha.DeviceType.OCCUPANCY_SENSOR, - INPUT_CLUSTERS: [ - Basic.cluster_id, - Identify.cluster_id, - OccupancySensing.cluster_id, - ], - OUTPUT_CLUSTERS: [], - }, - 35: { - PROFILE_ID: zha.PROFILE_ID, - DEVICE_TYPE: zha.DeviceType.IAS_ZONE, - INPUT_CLUSTERS: [ - Basic.cluster_id, - DevelcoPowerConfiguration, - Identify.cluster_id, - BinaryInput.cluster_id, - PollControl.cluster_id, - DevelcoIasZone, - ], - OUTPUT_CLUSTERS: [ - Identify.cluster_id, - Time.cluster_id, - Ota.cluster_id, - ], - }, - 38: { - PROFILE_ID: zha.PROFILE_ID, - DEVICE_TYPE: zha.DeviceType.TEMPERATURE_SENSOR, - INPUT_CLUSTERS: [ - Basic.cluster_id, - Identify.cluster_id, - TemperatureMeasurement.cluster_id, - ], - OUTPUT_CLUSTERS: [], - }, - 39: { - PROFILE_ID: zha.PROFILE_ID, - DEVICE_TYPE: zha.DeviceType.LIGHT_SENSOR, - INPUT_CLUSTERS: [ - Basic.cluster_id, - Identify.cluster_id, - IlluminanceMeasurement.cluster_id, - ], - OUTPUT_CLUSTERS: [], - }, - 40: { - PROFILE_ID: zha.PROFILE_ID, - DEVICE_TYPE: zha.DeviceType.OCCUPANCY_SENSOR, - INPUT_CLUSTERS: [ - Basic.cluster_id, - Identify.cluster_id, - OccupancySensing.cluster_id, - ], - OUTPUT_CLUSTERS: [], - }, - 41: { - PROFILE_ID: zha.PROFILE_ID, - DEVICE_TYPE: zha.DeviceType.OCCUPANCY_SENSOR, - INPUT_CLUSTERS: [ - Basic.cluster_id, - Identify.cluster_id, - OccupancySensing.cluster_id, - ], - OUTPUT_CLUSTERS: [], - }, - } - } +( + QuirkBuilder("frient A/S", "MOSZB-153") + .applies_to(DEVELCO, "MOSZB-140") + .applies_to(FRIENT, "MOSZB-140") + .replaces(DevelcoPowerConfiguration, endpoint_id=35) + .replaces(DevelcoIasZone, endpoint_id=35) + # This entity does not do anything + .prevent_default_entity_creation(endpoint_id=35, cluster_id=BinaryInput.cluster_id) + # This endpoint holds only an occupancy cluster that updates unusably slowly + .prevent_default_entity_creation(endpoint_id=34) + # These endpoints are duplicates of 35 and do not create useful entities + .prevent_default_entity_creation(endpoint_id=40) + .prevent_default_entity_creation(endpoint_id=41) + .add_to_registry() +) diff --git a/zhaquirks/develco/open_close.py b/zhaquirks/develco/open_close.py index 22dbc9bd31..cc0f6dda9d 100644 --- a/zhaquirks/develco/open_close.py +++ b/zhaquirks/develco/open_close.py @@ -1,33 +1,11 @@ """Door/Windows sensors.""" -from zigpy.profiles import zha -from zigpy.quirks import CustomCluster, CustomDevice -import zigpy.types as t -from zigpy.zcl import foundation -from zigpy.zcl.clusters.general import ( - Basic, - BinaryInput, - Identify, - OnOff, - Ota, - PollControl, - PowerConfiguration, - Scenes, - Time, -) -from zigpy.zcl.clusters.measurement import TemperatureMeasurement -from zigpy.zcl.clusters.security import IasZone +from zigpy.quirks.v2 import QuirkBuilder +from zigpy.zcl.clusters.general import BinaryInput from zhaquirks import PowerConfigurationCluster -from zhaquirks.const import ( - DEVICE_TYPE, - ENDPOINTS, - INPUT_CLUSTERS, - MODELS_INFO, - OUTPUT_CLUSTERS, - PROFILE_ID, -) -from zhaquirks.develco import DEVELCO, FRIENT + +from . import DevelcoIasZone class DevelcoPowerConfiguration(PowerConfigurationCluster): @@ -37,163 +15,15 @@ class DevelcoPowerConfiguration(PowerConfigurationCluster): MAX_VOLTS = 3.0 -class DevelcoIASZone(CustomCluster, IasZone): - """IAS Zone.""" - - client_commands = IasZone.client_commands.copy() - client_commands[0x0000] = foundation.ZCLCommandDef( - "status_change_notification", - { - "zone_status": IasZone.ZoneStatus, - "extended_status": t.bitmap8, - # These two should not be optional - "zone_id?": t.uint8_t, - "delay?": t.uint16_t, - }, - False, - is_manufacturer_specific=True, - ) - - -class WISZB120(CustomDevice): - """Custom device representing door/windows sensors, with built-in temperature measuring.""" - - signature = { - # - # - # - MODELS_INFO: [(DEVELCO, "WISZB-120"), (FRIENT, "WISZB-120")], - ENDPOINTS: { - 1: { - PROFILE_ID: 0xC0C9, - DEVICE_TYPE: 1, - INPUT_CLUSTERS: [ - Identify.cluster_id, - Scenes.cluster_id, - OnOff.cluster_id, - ], - OUTPUT_CLUSTERS: [], - }, - 35: { - PROFILE_ID: zha.PROFILE_ID, - DEVICE_TYPE: zha.DeviceType.IAS_ZONE, - INPUT_CLUSTERS: [ - Basic.cluster_id, - PowerConfiguration.cluster_id, - Identify.cluster_id, - BinaryInput.cluster_id, - PollControl.cluster_id, - IasZone.cluster_id, - ], - OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id], - }, - 38: { - PROFILE_ID: zha.PROFILE_ID, - DEVICE_TYPE: zha.DeviceType.TEMPERATURE_SENSOR, - INPUT_CLUSTERS: [ - Basic.cluster_id, - Identify.cluster_id, - TemperatureMeasurement.cluster_id, - ], - OUTPUT_CLUSTERS: [Identify.cluster_id], - }, - }, - } - - replacement = { - ENDPOINTS: { - 1: { - INPUT_CLUSTERS: [ - Identify.cluster_id, - Scenes.cluster_id, - OnOff.cluster_id, - ], - OUTPUT_CLUSTERS: [], - }, - 35: { - INPUT_CLUSTERS: [ - Basic.cluster_id, - DevelcoPowerConfiguration, - Identify.cluster_id, - BinaryInput.cluster_id, - PollControl.cluster_id, - DevelcoIASZone, - ], - OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id], - }, - 38: { - PROFILE_ID: zha.PROFILE_ID, - DEVICE_TYPE: zha.DeviceType.TEMPERATURE_SENSOR, - INPUT_CLUSTERS: [ - Basic.cluster_id, - Identify.cluster_id, - TemperatureMeasurement.cluster_id, - ], - OUTPUT_CLUSTERS: [Identify.cluster_id], - }, - } - } - - -class WISZB121(CustomDevice): - """Custom device representing door/windows sensors, without built-in temperature measuring.""" - - signature = { - # - # - MODELS_INFO: [(DEVELCO, "WISZB-121"), (FRIENT, "WISZB-121")], - ENDPOINTS: { - 1: { - PROFILE_ID: 0xC0C9, - DEVICE_TYPE: 1, - INPUT_CLUSTERS: [ - Identify.cluster_id, - Scenes.cluster_id, - OnOff.cluster_id, - ], - OUTPUT_CLUSTERS: [], - }, - 35: { - PROFILE_ID: zha.PROFILE_ID, - DEVICE_TYPE: zha.DeviceType.IAS_ZONE, - INPUT_CLUSTERS: [ - Basic.cluster_id, - PowerConfiguration.cluster_id, - Identify.cluster_id, - BinaryInput.cluster_id, - PollControl.cluster_id, - IasZone.cluster_id, - ], - OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id], - }, - }, - } - - replacement = { - ENDPOINTS: { - 1: { - INPUT_CLUSTERS: [ - Identify.cluster_id, - Scenes.cluster_id, - OnOff.cluster_id, - ], - OUTPUT_CLUSTERS: [], - }, - 35: { - INPUT_CLUSTERS: [ - Basic.cluster_id, - DevelcoPowerConfiguration, - Identify.cluster_id, - BinaryInput.cluster_id, - PollControl.cluster_id, - DevelcoIASZone, - ], - OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id], - }, - } - } +( + QuirkBuilder("frient A/S", "WISZB-131") + .applies_to("Develco Products A/S", "WISZB-120") + .applies_to("frient A/S", "WISZB-120") + .applies_to("Develco Products A/S", "WISZB-121") + .applies_to("frient A/S", "WISZB-121") + .replaces(DevelcoIasZone, endpoint_id=35) + .replaces(DevelcoPowerConfiguration, endpoint_id=35) + # The binary input cluster is a duplicate + .prevent_default_entity_creation(endpoint_id=35, cluster_id=BinaryInput.cluster_id) + .add_to_registry() +) diff --git a/zhaquirks/develco/power_plug.py b/zhaquirks/develco/power_plug.py index d89521fb48..936331c1fe 100644 --- a/zhaquirks/develco/power_plug.py +++ b/zhaquirks/develco/power_plug.py @@ -1,107 +1,43 @@ """Develco smart plugs.""" -from zigpy.profiles import zha -from zigpy.quirks import CustomCluster, CustomDevice -from zigpy.zcl.clusters.general import ( - Alarms, - Basic, - DeviceTemperature, - Groups, - Identify, - OnOff, - Ota, - Scenes, - Time, +from zigpy.quirks.v2 import ( + EntityType, + QuirkBuilder, + SensorDeviceClass, + SensorStateClass, ) -from zigpy.zcl.clusters.homeautomation import ElectricalMeasurement -from zigpy.zcl.clusters.measurement import OccupancySensing +from zigpy.quirks.v2.homeassistant import UnitOfTemperature +from zigpy.zcl.clusters.general import DeviceTemperature from zigpy.zcl.clusters.smartenergy import Metering -from zhaquirks.const import ( - DEVICE_TYPE, - ENDPOINTS, - INPUT_CLUSTERS, - MODELS_INFO, - OUTPUT_CLUSTERS, - PROFILE_ID, +( + QuirkBuilder("frient A/S", "SPLZB-141") + .applies_to("Develco Products A/S", "SPLZB-131") + .prevent_default_entity_creation( + endpoint_id=2, + cluster_id=DeviceTemperature.cluster_id, + function=lambda entity: entity.__class__.__name__ == "DeviceTemperature", + ) + # This attribute does not actually work + .prevent_default_entity_creation( + endpoint_id=2, + cluster_id=Metering.cluster_id, + function=lambda entity: ( + entity.info_object.translation_key == "summation_delivered" + ), + ) + .sensor( + endpoint_id=2, + cluster_id=DeviceTemperature.cluster_id, + attribute_name=DeviceTemperature.AttributeDefs.current_temperature.name, + device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, + unit=UnitOfTemperature.CELSIUS, + divisor=1, # This should be 100 but the device does not follow the spec + translation_key="device_temperature", + fallback_name="Device temperature", + entity_type=EntityType.DIAGNOSTIC, + unique_id_suffix="2", # Replace the ZHA entity + ) + .add_to_registry() ) -from zhaquirks.develco import DEVELCO - -DEV_TEMP_ID = DeviceTemperature.attributes_by_name["current_temperature"].id - - -class DevelcoDeviceTemperature(CustomCluster, DeviceTemperature): - """Custom device temperature cluster to multiply the temperature by 100.""" - - def _update_attribute(self, attrid, value): - if attrid == DEV_TEMP_ID: - value = value * 100 - super()._update_attribute(attrid, value) - - -class SPLZB131(CustomDevice): - """Custom device Develco smart plug device.""" - - signature = { - MODELS_INFO: [(DEVELCO, "SPLZB-131")], - ENDPOINTS: { - 1: { - PROFILE_ID: 0xC0C9, - DEVICE_TYPE: 1, - INPUT_CLUSTERS: [ - Scenes.cluster_id, - OnOff.cluster_id, - ], - OUTPUT_CLUSTERS: [], - }, - 2: { - PROFILE_ID: zha.PROFILE_ID, - DEVICE_TYPE: zha.DeviceType.SMART_PLUG, - INPUT_CLUSTERS: [ - Basic.cluster_id, - DeviceTemperature.cluster_id, - Identify.cluster_id, - Groups.cluster_id, - Scenes.cluster_id, - OnOff.cluster_id, - Alarms.cluster_id, - Metering.cluster_id, - ElectricalMeasurement.cluster_id, - ], - OUTPUT_CLUSTERS: [ - Basic.cluster_id, - Identify.cluster_id, - Time.cluster_id, - Ota.cluster_id, - OccupancySensing.cluster_id, - ], - }, - }, - } - - replacement = { - ENDPOINTS: { - 2: { - PROFILE_ID: zha.PROFILE_ID, - DEVICE_TYPE: zha.DeviceType.SMART_PLUG, - INPUT_CLUSTERS: [ - Basic.cluster_id, - DevelcoDeviceTemperature, - Identify.cluster_id, - Groups.cluster_id, - Scenes.cluster_id, - OnOff.cluster_id, - Alarms.cluster_id, - Metering.cluster_id, - ElectricalMeasurement.cluster_id, - ], - OUTPUT_CLUSTERS: [ - Basic.cluster_id, - Identify.cluster_id, - Time.cluster_id, - Ota.cluster_id, - OccupancySensing.cluster_id, - ], - }, - } - } diff --git a/zhaquirks/develco/smart_button.py b/zhaquirks/develco/smart_button.py new file mode 100644 index 0000000000..a4c95f4aeb --- /dev/null +++ b/zhaquirks/develco/smart_button.py @@ -0,0 +1,31 @@ +"""Smart button.""" + +from zigpy.quirks.v2 import QuirkBuilder +from zigpy.zcl import ClusterType +from zigpy.zcl.clusters.general import BinaryInput, OnOff + +from zhaquirks.const import BUTTON, CLUSTER_ID, COMMAND, COMMAND_CLICK, ENDPOINT_ID + +( + QuirkBuilder("frient A/S", "SBTZB-110") + .prevent_default_entity_creation( + endpoint_id=32, + cluster_id=OnOff.cluster_id, + cluster_type=ClusterType.Client, + ) + # Don't create a binary entity for a button, instead create automation triggers + .prevent_default_entity_creation( + endpoint_id=32, + cluster_id=BinaryInput.cluster_id, + ) + .device_automation_triggers( + { + (COMMAND_CLICK, BUTTON): { + ENDPOINT_ID: 32, + CLUSTER_ID: int(OnOff.cluster_id), + COMMAND: OnOff.ServerCommandDefs.toggle.name, + }, + } + ) + .add_to_registry() +) diff --git a/zhaquirks/develco/smart_siren.py b/zhaquirks/develco/smart_siren.py new file mode 100644 index 0000000000..1e25a598d2 --- /dev/null +++ b/zhaquirks/develco/smart_siren.py @@ -0,0 +1,48 @@ +"""Smart siren.""" + +from zigpy.quirks.v2 import ( + EntityType, + QuirkBuilder, + SensorDeviceClass, + SensorStateClass, +) +from zigpy.quirks.v2.homeassistant import PERCENTAGE +from zigpy.quirks.v2.homeassistant.binary_sensor import BinarySensorDeviceClass +from zigpy.zcl.clusters.general import PowerConfiguration +from zigpy.zcl.clusters.security import IasZone + +( + QuirkBuilder("frient A/S", "SIRZB-111") + .applies_to("frient A/S", "SIRZB-110") + # Hide the default `ias_zone` entity + .prevent_default_entity_creation( + endpoint_id=43, + cluster_id=IasZone.cluster_id, + function=lambda entity: entity.translation_key == "ias_zone", + ) + # And instead create a tamper sensor + .binary_sensor( + endpoint_id=43, + cluster_id=IasZone.cluster_id, + attribute_name=IasZone.AttributeDefs.zone_status.name, + device_class=BinarySensorDeviceClass.TAMPER, + attribute_converter=lambda value: bool(value & IasZone.ZoneStatus.Tamper), + unique_id_suffix="tamper", + translation_key="tamper", + fallback_name="Tamper", + ) + # This is a mains-powered device that has a backup battery + .sensor( + attribute_name=PowerConfiguration.AttributeDefs.battery_percentage_remaining.name, + cluster_id=PowerConfiguration.cluster_id, + endpoint_id=43, + device_class=SensorDeviceClass.BATTERY, + state_class=SensorStateClass.MEASUREMENT, + unit=PERCENTAGE, + divisor=2, # ZCL reports battery in units of 0.5%, so 200 => 100% + fallback_name="Battery", + unique_id_suffix="battery", + entity_type=EntityType.DIAGNOSTIC, + ) + .add_to_registry() +) diff --git a/zhaquirks/develco/smoke_alarm.py b/zhaquirks/develco/smoke_alarm.py index 6caff9fc70..1ecd58d82f 100644 --- a/zhaquirks/develco/smoke_alarm.py +++ b/zhaquirks/develco/smoke_alarm.py @@ -1,121 +1,13 @@ -"""Develco Heat Alarm.""" +"""Frient Smoke Alarm.""" -import zigpy.profiles.zha -from zigpy.quirks import CustomDevice -from zigpy.zcl.clusters.general import ( - Basic, - BinaryInput, - Identify, - OnOff, - Ota, - PollControl, - PowerConfiguration, - Scenes, - Time, -) -from zigpy.zcl.clusters.measurement import TemperatureMeasurement -from zigpy.zcl.clusters.security import IasWd, IasZone - -from zhaquirks.const import ( - DEVICE_TYPE, - ENDPOINTS, - INPUT_CLUSTERS, - MODELS_INFO, - OUTPUT_CLUSTERS, - PROFILE_ID, -) - -from . import DEVELCO, FRIENT, DevelcoIasZone, DevelcoPowerConfiguration - -MANUFACTURER = 0x1015 +from zigpy.quirks.v2 import QuirkBuilder +from . import DevelcoIasZone, DevelcoPowerConfiguration -class SMSZB120(CustomDevice): - """Custom device heat alarm.""" - - manufacturer_id_override = MANUFACTURER - - signature = { - # - # - # - MODELS_INFO: [(DEVELCO, "SMSZB-120"), (FRIENT, "SMSZB-120")], - ENDPOINTS: { - 1: { - PROFILE_ID: 49353, - DEVICE_TYPE: 1, - INPUT_CLUSTERS: [ - Identify.cluster_id, - Scenes.cluster_id, - OnOff.cluster_id, - ], - OUTPUT_CLUSTERS: [], - }, - 35: { - PROFILE_ID: zigpy.profiles.zha.PROFILE_ID, - DEVICE_TYPE: zigpy.profiles.zha.DeviceType.IAS_ZONE, - INPUT_CLUSTERS: [ - Basic.cluster_id, - PowerConfiguration.cluster_id, - Identify.cluster_id, - BinaryInput.cluster_id, - PollControl.cluster_id, - IasZone.cluster_id, - IasWd.cluster_id, - ], - OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id], - }, - 38: { - PROFILE_ID: zigpy.profiles.zha.PROFILE_ID, - DEVICE_TYPE: zigpy.profiles.zha.DeviceType.TEMPERATURE_SENSOR, - INPUT_CLUSTERS: [ - Basic.cluster_id, - Identify.cluster_id, - TemperatureMeasurement.cluster_id, - ], - OUTPUT_CLUSTERS: [Identify.cluster_id], - }, - }, - } - - replacement = { - ENDPOINTS: { - 1: { - PROFILE_ID: 49353, - DEVICE_TYPE: 1, - INPUT_CLUSTERS: [ - Identify.cluster_id, - Scenes.cluster_id, - OnOff.cluster_id, - ], - OUTPUT_CLUSTERS: [], - }, - 35: { - PROFILE_ID: zigpy.profiles.zha.PROFILE_ID, - DEVICE_TYPE: zigpy.profiles.zha.DeviceType.IAS_ZONE, - INPUT_CLUSTERS: [ - Basic.cluster_id, - DevelcoPowerConfiguration, - Identify.cluster_id, - BinaryInput.cluster_id, - PollControl.cluster_id, - DevelcoIasZone, - IasWd.cluster_id, - ], - OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id], - }, - 38: { - PROFILE_ID: zigpy.profiles.zha.PROFILE_ID, - DEVICE_TYPE: zigpy.profiles.zha.DeviceType.TEMPERATURE_SENSOR, - INPUT_CLUSTERS: [ - Basic.cluster_id, - Identify.cluster_id, - TemperatureMeasurement.cluster_id, - ], - OUTPUT_CLUSTERS: [Identify.cluster_id], - }, - }, - } +( + QuirkBuilder("frient A/S", "SMSZB-120") + .applies_to("Develco Products A/S", "SMSZB-120") + .replaces(DevelcoIasZone, endpoint_id=35) + .replaces(DevelcoPowerConfiguration, endpoint_id=35) + .add_to_registry() +)