diff --git a/tests/test_xiaomi.py b/tests/test_xiaomi.py index 3836c79c47..f534e0932a 100644 --- a/tests/test_xiaomi.py +++ b/tests/test_xiaomi.py @@ -1135,12 +1135,13 @@ async def test_xiaomi_e1_thermostat_schedule_settings_deserialization( ( (zhaquirks.xiaomi.aqara.motion_ac02.LumiMotionAC02, 0), (zhaquirks.xiaomi.aqara.motion_agl02.MotionT1, -1), + (zhaquirks.xiaomi.aqara.motion_acn001.MotionE1, -1), ), ) async def test_xiaomi_p1_t1_motion_sensor( zigpy_device_from_quirk, quirk, invalid_iilluminance_report ): - """Test Aqara P1 and T1 motion sensors.""" + """Test Aqara P1, T1, and E1 motion sensors.""" device = zigpy_device_from_quirk(quirk) @@ -1192,12 +1193,12 @@ async def test_xiaomi_p1_t1_motion_sensor( opple_cluster.update_attribute(274, 0xFFFF) # confirm invalid illuminance report is interpreted as 0 for P1 sensor, - # and -1 for the T1 sensor, as it doesn't seem like the T1 sensor sends invalid illuminance reports + # and -1 for the T1/E1 sensors, as they don't seem to send invalid illuminance reports assert len(illuminance_listener.attribute_updates) == 2 assert illuminance_listener.attribute_updates[1][0] == zcl_iilluminance_id assert illuminance_listener.attribute_updates[1][1] == invalid_iilluminance_report - # send illuminance report only + # send illuminance report only, parsed via Xiaomi cluster implementation opple_cluster.update_attribute( XIAOMI_AQARA_ATTRIBUTE_E1, create_aqara_attr_report({101: 20}) ) diff --git a/zhaquirks/xiaomi/__init__.py b/zhaquirks/xiaomi/__init__.py index 91607cfa7b..251d13742b 100644 --- a/zhaquirks/xiaomi/__init__.py +++ b/zhaquirks/xiaomi/__init__.py @@ -24,6 +24,7 @@ from zigpy.zcl.clusters.homeautomation import ElectricalMeasurement from zigpy.zcl.clusters.measurement import ( IlluminanceMeasurement, + OccupancySensing, PressureMeasurement, RelativeHumidity, TemperatureMeasurement, @@ -389,7 +390,7 @@ def _parse_aqara_attributes(self, value): attribute_names.update({11: ILLUMINANCE_MEASUREMENT}) elif self.endpoint.device.model == "lumi.curtain.acn002": attribute_names.update({101: BATTERY_PERCENTAGE_REMAINING_ATTRIBUTE}) - elif self.endpoint.device.model in ["lumi.motion.agl02", "lumi.motion.ac02"]: + elif self.endpoint.device.model in ["lumi.motion.agl02", "lumi.motion.ac02", "lumi.motion.acn001"]: attribute_names.update({101: ILLUMINANCE_MEASUREMENT}) if self.endpoint.device.model == "lumi.motion.ac02": attribute_names.update({105: DETECTION_INTERVAL}) @@ -464,6 +465,22 @@ class XiaomiAqaraE1Cluster(XiaomiCluster): ep_attribute = "opple_cluster" +class XiaomiMotionManufacturerCluster(XiaomiAqaraE1Cluster): + """Xiaomi manufacturer cluster to parse motion and illuminance reports.""" + + def _update_attribute(self, attrid, value): + super()._update_attribute(attrid, value) + if attrid == 274: + value = value - 65536 + self.endpoint.illuminance.update_attribute( + IlluminanceMeasurement.AttributeDefs.measured_value.id, value + ) + self.endpoint.occupancy.update_attribute( + OccupancySensing.AttributeDefs.occupancy.id, + OccupancySensing.Occupancy.Occupied, + ) + + class BinaryOutputInterlock(CustomCluster, BinaryOutput): """Xiaomi binaryoutput cluster with added interlock attribute.""" diff --git a/zhaquirks/xiaomi/aqara/motion_ac02.py b/zhaquirks/xiaomi/aqara/motion_ac02.py index efb52175b6..c372691828 100644 --- a/zhaquirks/xiaomi/aqara/motion_ac02.py +++ b/zhaquirks/xiaomi/aqara/motion_ac02.py @@ -8,7 +8,6 @@ from zigpy.profiles import zha from zigpy.quirks import CustomDevice from zigpy.zcl.clusters.general import Basic, Identify, Ota, PowerConfiguration -from zigpy.zcl.clusters.measurement import IlluminanceMeasurement, OccupancySensing from zhaquirks import Bus, LocalDataCluster from zhaquirks.const import ( @@ -23,7 +22,7 @@ LocalIlluminanceMeasurementCluster, MotionCluster, OccupancyCluster, - XiaomiAqaraE1Cluster, + XiaomiMotionManufacturerCluster, XiaomiPowerConfiguration, ) @@ -34,8 +33,12 @@ _LOGGER = logging.getLogger(__name__) -class OppleCluster(XiaomiAqaraE1Cluster): - """Opple cluster.""" +class OppleCluster(XiaomiMotionManufacturerCluster): + """Xiaomi manufacturer cluster. + + This uses the shared XiaomiMotionManufacturerCluster implementation + which parses motion and illuminance reports from Xiaomi devices. + """ attributes = { DETECTION_INTERVAL: ("detection_interval", types.uint8_t, True), @@ -43,18 +46,6 @@ class OppleCluster(XiaomiAqaraE1Cluster): TRIGGER_INDICATOR: ("trigger_indicator", types.uint8_t, True), } - def _update_attribute(self, attrid: int, value: Any) -> None: - super()._update_attribute(attrid, value) - if attrid == MOTION_ATTRIBUTE: - value = value - 65536 - self.endpoint.illuminance.update_attribute( - IlluminanceMeasurement.AttributeDefs.measured_value.id, value - ) - self.endpoint.occupancy.update_attribute( - OccupancySensing.AttributeDefs.occupancy.id, - OccupancySensing.Occupancy.Occupied, - ) - async def write_attributes( self, attributes: dict[str | int, Any], manufacturer: int | None = None ) -> list: diff --git a/zhaquirks/xiaomi/aqara/motion_acn001.py b/zhaquirks/xiaomi/aqara/motion_acn001.py new file mode 100644 index 0000000000..168372aa01 --- /dev/null +++ b/zhaquirks/xiaomi/aqara/motion_acn001.py @@ -0,0 +1,68 @@ +"""Xiaomi Aqara E1 motion sensor device.""" +from zigpy.profiles import zha +from zigpy.zcl.clusters.general import Identify, Ota + +from zhaquirks import Bus +from zhaquirks.const import ( + DEVICE_TYPE, + ENDPOINTS, + INPUT_CLUSTERS, + MODELS_INFO, + OUTPUT_CLUSTERS, + PROFILE_ID, +) +from zhaquirks.xiaomi import ( + LUMI, + BasicCluster, + IlluminanceMeasurementCluster, + LocalOccupancyCluster, + MotionCluster, + XiaomiAqaraE1Cluster, + XiaomiCustomDevice, + XiaomiMotionManufacturerCluster, + XiaomiPowerConfiguration, +) + + +class MotionE1(XiaomiCustomDevice): + """Xiaomi motion sensor device lumi.motion.acn001.""" + + def __init__(self, *args, **kwargs): + """Init.""" + self.battery_size = 11 + self.motion_bus = Bus() + super().__init__(*args, **kwargs) + + signature = { + MODELS_INFO: [(LUMI, "lumi.motion.acn001")], + ENDPOINTS: { + 1: { + PROFILE_ID: zha.PROFILE_ID, + DEVICE_TYPE: zha.DeviceType.IAS_ZONE, + INPUT_CLUSTERS: [ + BasicCluster.cluster_id, + XiaomiPowerConfiguration.cluster_id, + Identify.cluster_id, + XiaomiAqaraE1Cluster.cluster_id, + ], + OUTPUT_CLUSTERS: [Identify.cluster_id, Ota.cluster_id], + } + }, + } + + replacement = { + ENDPOINTS: { + 1: { + INPUT_CLUSTERS: [ + BasicCluster, + XiaomiPowerConfiguration, + Identify.cluster_id, + LocalOccupancyCluster, + MotionCluster, + IlluminanceMeasurementCluster, + XiaomiMotionManufacturerCluster, + ], + OUTPUT_CLUSTERS: [Identify.cluster_id, Ota.cluster_id], + } + }, + } diff --git a/zhaquirks/xiaomi/aqara/motion_agl02.py b/zhaquirks/xiaomi/aqara/motion_agl02.py index e8c0fd1ac0..30223320e5 100644 --- a/zhaquirks/xiaomi/aqara/motion_agl02.py +++ b/zhaquirks/xiaomi/aqara/motion_agl02.py @@ -3,7 +3,7 @@ from zigpy.profiles import zha from zigpy.zcl.clusters.general import Identify, Ota -from zigpy.zcl.clusters.measurement import IlluminanceMeasurement, OccupancySensing +from zigpy.zcl.clusters.measurement import OccupancySensing from zhaquirks import Bus from zhaquirks.const import ( @@ -20,29 +20,11 @@ IlluminanceMeasurementCluster, LocalOccupancyCluster, MotionCluster, - XiaomiAqaraE1Cluster, XiaomiCustomDevice, + XiaomiMotionManufacturerCluster, XiaomiPowerConfiguration, ) -XIAOMI_CLUSTER_ID = 0xFCC0 - - -class XiaomiManufacturerCluster(XiaomiAqaraE1Cluster): - """Xiaomi manufacturer cluster.""" - - def _update_attribute(self, attrid, value): - super()._update_attribute(attrid, value) - if attrid == 274: - value = value - 65536 - self.endpoint.illuminance.update_attribute( - IlluminanceMeasurement.AttributeDefs.measured_value.id, value - ) - self.endpoint.occupancy.update_attribute( - OccupancySensing.AttributeDefs.occupancy.id, - OccupancySensing.Occupancy.Occupied, - ) - class MotionT1(XiaomiCustomDevice): """Xiaomi motion sensor device.""" @@ -73,6 +55,7 @@ def __init__(self, *args, **kwargs): } }, } + replacement = { ENDPOINTS: { 1: { @@ -83,7 +66,7 @@ def __init__(self, *args, **kwargs): LocalOccupancyCluster, MotionCluster, IlluminanceMeasurementCluster, - XiaomiManufacturerCluster, + XiaomiMotionManufacturerCluster, ], OUTPUT_CLUSTERS: [Identify.cluster_id, Ota.cluster_id], }