Skip to content

Commit 5ce06cf

Browse files
Add Aqara E1 motion sensor support (#3163)
* Add Aqara E1 motion sensor quirk * Extract `XiaomiMotionManufacturerCluster` out of motion quirks * Fix illuminance Aqara attribute reports not being parsed * Add test for E1 sensor --------- Co-authored-by: TheJulianJES <[email protected]>
1 parent 4383f00 commit 5ce06cf

File tree

5 files changed

+101
-41
lines changed

5 files changed

+101
-41
lines changed

tests/test_xiaomi.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1135,12 +1135,13 @@ async def test_xiaomi_e1_thermostat_schedule_settings_deserialization(
11351135
(
11361136
(zhaquirks.xiaomi.aqara.motion_ac02.LumiMotionAC02, 0),
11371137
(zhaquirks.xiaomi.aqara.motion_agl02.MotionT1, -1),
1138+
(zhaquirks.xiaomi.aqara.motion_acn001.MotionE1, -1),
11381139
),
11391140
)
11401141
async def test_xiaomi_p1_t1_motion_sensor(
11411142
zigpy_device_from_quirk, quirk, invalid_iilluminance_report
11421143
):
1143-
"""Test Aqara P1 and T1 motion sensors."""
1144+
"""Test Aqara P1, T1, and E1 motion sensors."""
11441145

11451146
device = zigpy_device_from_quirk(quirk)
11461147

@@ -1192,12 +1193,12 @@ async def test_xiaomi_p1_t1_motion_sensor(
11921193
opple_cluster.update_attribute(274, 0xFFFF)
11931194

11941195
# confirm invalid illuminance report is interpreted as 0 for P1 sensor,
1195-
# and -1 for the T1 sensor, as it doesn't seem like the T1 sensor sends invalid illuminance reports
1196+
# and -1 for the T1/E1 sensors, as they don't seem to send invalid illuminance reports
11961197
assert len(illuminance_listener.attribute_updates) == 2
11971198
assert illuminance_listener.attribute_updates[1][0] == zcl_iilluminance_id
11981199
assert illuminance_listener.attribute_updates[1][1] == invalid_iilluminance_report
11991200

1200-
# send illuminance report only
1201+
# send illuminance report only, parsed via Xiaomi cluster implementation
12011202
opple_cluster.update_attribute(
12021203
XIAOMI_AQARA_ATTRIBUTE_E1, create_aqara_attr_report({101: 20})
12031204
)

zhaquirks/xiaomi/__init__.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from zigpy.zcl.clusters.homeautomation import ElectricalMeasurement
2525
from zigpy.zcl.clusters.measurement import (
2626
IlluminanceMeasurement,
27+
OccupancySensing,
2728
PressureMeasurement,
2829
RelativeHumidity,
2930
TemperatureMeasurement,
@@ -389,7 +390,7 @@ def _parse_aqara_attributes(self, value):
389390
attribute_names.update({11: ILLUMINANCE_MEASUREMENT})
390391
elif self.endpoint.device.model == "lumi.curtain.acn002":
391392
attribute_names.update({101: BATTERY_PERCENTAGE_REMAINING_ATTRIBUTE})
392-
elif self.endpoint.device.model in ["lumi.motion.agl02", "lumi.motion.ac02"]:
393+
elif self.endpoint.device.model in ["lumi.motion.agl02", "lumi.motion.ac02", "lumi.motion.acn001"]:
393394
attribute_names.update({101: ILLUMINANCE_MEASUREMENT})
394395
if self.endpoint.device.model == "lumi.motion.ac02":
395396
attribute_names.update({105: DETECTION_INTERVAL})
@@ -464,6 +465,22 @@ class XiaomiAqaraE1Cluster(XiaomiCluster):
464465
ep_attribute = "opple_cluster"
465466

466467

468+
class XiaomiMotionManufacturerCluster(XiaomiAqaraE1Cluster):
469+
"""Xiaomi manufacturer cluster to parse motion and illuminance reports."""
470+
471+
def _update_attribute(self, attrid, value):
472+
super()._update_attribute(attrid, value)
473+
if attrid == 274:
474+
value = value - 65536
475+
self.endpoint.illuminance.update_attribute(
476+
IlluminanceMeasurement.AttributeDefs.measured_value.id, value
477+
)
478+
self.endpoint.occupancy.update_attribute(
479+
OccupancySensing.AttributeDefs.occupancy.id,
480+
OccupancySensing.Occupancy.Occupied,
481+
)
482+
483+
467484
class BinaryOutputInterlock(CustomCluster, BinaryOutput):
468485
"""Xiaomi binaryoutput cluster with added interlock attribute."""
469486

zhaquirks/xiaomi/aqara/motion_ac02.py

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
from zigpy.profiles import zha
99
from zigpy.quirks import CustomDevice
1010
from zigpy.zcl.clusters.general import Basic, Identify, Ota, PowerConfiguration
11-
from zigpy.zcl.clusters.measurement import IlluminanceMeasurement, OccupancySensing
1211

1312
from zhaquirks import Bus, LocalDataCluster
1413
from zhaquirks.const import (
@@ -23,7 +22,7 @@
2322
LocalIlluminanceMeasurementCluster,
2423
MotionCluster,
2524
OccupancyCluster,
26-
XiaomiAqaraE1Cluster,
25+
XiaomiMotionManufacturerCluster,
2726
XiaomiPowerConfiguration,
2827
)
2928

@@ -34,27 +33,19 @@
3433
_LOGGER = logging.getLogger(__name__)
3534

3635

37-
class OppleCluster(XiaomiAqaraE1Cluster):
38-
"""Opple cluster."""
36+
class OppleCluster(XiaomiMotionManufacturerCluster):
37+
"""Xiaomi manufacturer cluster.
38+
39+
This uses the shared XiaomiMotionManufacturerCluster implementation
40+
which parses motion and illuminance reports from Xiaomi devices.
41+
"""
3942

4043
attributes = {
4144
DETECTION_INTERVAL: ("detection_interval", types.uint8_t, True),
4245
MOTION_SENSITIVITY: ("motion_sensitivity", types.uint8_t, True),
4346
TRIGGER_INDICATOR: ("trigger_indicator", types.uint8_t, True),
4447
}
4548

46-
def _update_attribute(self, attrid: int, value: Any) -> None:
47-
super()._update_attribute(attrid, value)
48-
if attrid == MOTION_ATTRIBUTE:
49-
value = value - 65536
50-
self.endpoint.illuminance.update_attribute(
51-
IlluminanceMeasurement.AttributeDefs.measured_value.id, value
52-
)
53-
self.endpoint.occupancy.update_attribute(
54-
OccupancySensing.AttributeDefs.occupancy.id,
55-
OccupancySensing.Occupancy.Occupied,
56-
)
57-
5849
async def write_attributes(
5950
self, attributes: dict[str | int, Any], manufacturer: int | None = None
6051
) -> list:
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
"""Xiaomi Aqara E1 motion sensor device."""
2+
from zigpy.profiles import zha
3+
from zigpy.zcl.clusters.general import Identify, Ota
4+
5+
from zhaquirks import Bus
6+
from zhaquirks.const import (
7+
DEVICE_TYPE,
8+
ENDPOINTS,
9+
INPUT_CLUSTERS,
10+
MODELS_INFO,
11+
OUTPUT_CLUSTERS,
12+
PROFILE_ID,
13+
)
14+
from zhaquirks.xiaomi import (
15+
LUMI,
16+
BasicCluster,
17+
IlluminanceMeasurementCluster,
18+
LocalOccupancyCluster,
19+
MotionCluster,
20+
XiaomiAqaraE1Cluster,
21+
XiaomiCustomDevice,
22+
XiaomiMotionManufacturerCluster,
23+
XiaomiPowerConfiguration,
24+
)
25+
26+
27+
class MotionE1(XiaomiCustomDevice):
28+
"""Xiaomi motion sensor device lumi.motion.acn001."""
29+
30+
def __init__(self, *args, **kwargs):
31+
"""Init."""
32+
self.battery_size = 11
33+
self.motion_bus = Bus()
34+
super().__init__(*args, **kwargs)
35+
36+
signature = {
37+
MODELS_INFO: [(LUMI, "lumi.motion.acn001")],
38+
ENDPOINTS: {
39+
1: {
40+
PROFILE_ID: zha.PROFILE_ID,
41+
DEVICE_TYPE: zha.DeviceType.IAS_ZONE,
42+
INPUT_CLUSTERS: [
43+
BasicCluster.cluster_id,
44+
XiaomiPowerConfiguration.cluster_id,
45+
Identify.cluster_id,
46+
XiaomiAqaraE1Cluster.cluster_id,
47+
],
48+
OUTPUT_CLUSTERS: [Identify.cluster_id, Ota.cluster_id],
49+
}
50+
},
51+
}
52+
53+
replacement = {
54+
ENDPOINTS: {
55+
1: {
56+
INPUT_CLUSTERS: [
57+
BasicCluster,
58+
XiaomiPowerConfiguration,
59+
Identify.cluster_id,
60+
LocalOccupancyCluster,
61+
MotionCluster,
62+
IlluminanceMeasurementCluster,
63+
XiaomiMotionManufacturerCluster,
64+
],
65+
OUTPUT_CLUSTERS: [Identify.cluster_id, Ota.cluster_id],
66+
}
67+
},
68+
}

zhaquirks/xiaomi/aqara/motion_agl02.py

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
from zigpy.profiles import zha
55
from zigpy.zcl.clusters.general import Identify, Ota
6-
from zigpy.zcl.clusters.measurement import IlluminanceMeasurement, OccupancySensing
6+
from zigpy.zcl.clusters.measurement import OccupancySensing
77

88
from zhaquirks import Bus
99
from zhaquirks.const import (
@@ -20,29 +20,11 @@
2020
IlluminanceMeasurementCluster,
2121
LocalOccupancyCluster,
2222
MotionCluster,
23-
XiaomiAqaraE1Cluster,
2423
XiaomiCustomDevice,
24+
XiaomiMotionManufacturerCluster,
2525
XiaomiPowerConfiguration,
2626
)
2727

28-
XIAOMI_CLUSTER_ID = 0xFCC0
29-
30-
31-
class XiaomiManufacturerCluster(XiaomiAqaraE1Cluster):
32-
"""Xiaomi manufacturer cluster."""
33-
34-
def _update_attribute(self, attrid, value):
35-
super()._update_attribute(attrid, value)
36-
if attrid == 274:
37-
value = value - 65536
38-
self.endpoint.illuminance.update_attribute(
39-
IlluminanceMeasurement.AttributeDefs.measured_value.id, value
40-
)
41-
self.endpoint.occupancy.update_attribute(
42-
OccupancySensing.AttributeDefs.occupancy.id,
43-
OccupancySensing.Occupancy.Occupied,
44-
)
45-
4628

4729
class MotionT1(XiaomiCustomDevice):
4830
"""Xiaomi motion sensor device."""
@@ -73,6 +55,7 @@ def __init__(self, *args, **kwargs):
7355
}
7456
},
7557
}
58+
7659
replacement = {
7760
ENDPOINTS: {
7861
1: {
@@ -83,7 +66,7 @@ def __init__(self, *args, **kwargs):
8366
LocalOccupancyCluster,
8467
MotionCluster,
8568
IlluminanceMeasurementCluster,
86-
XiaomiManufacturerCluster,
69+
XiaomiMotionManufacturerCluster,
8770
],
8871
OUTPUT_CLUSTERS: [Identify.cluster_id, Ota.cluster_id],
8972
}

0 commit comments

Comments
 (0)