Skip to content

Commit a666e46

Browse files
authored
Update Aqara quirks for various Zigbee 3.0 devices (#1151)
* Add quirk for Aqara E1 contact sensor * get model from device * fix temperature * replace magic number * this was intentionally jacked up * more aqara updates * review comment
1 parent 04bff05 commit a666e46

File tree

5 files changed

+209
-31
lines changed

5 files changed

+209
-31
lines changed

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,6 @@ def readme():
2424
keywords="zha quirks homeassistant hass",
2525
packages=find_packages(exclude=["tests"]),
2626
python_requires=">=3",
27-
install_requires=["zigpy>=0.32.0"],
27+
install_requires=["zigpy>=0.42.0"],
2828
tests_require=["pytest"],
2929
)

zhaquirks/xiaomi/__init__.py

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
PowerConfiguration,
1818
)
1919
from zigpy.zcl.clusters.homeautomation import ElectricalMeasurement
20+
from zigpy.zcl.clusters.manufacturer_specific import ManufacturerSpecificCluster
2021
from zigpy.zcl.clusters.measurement import (
2122
IlluminanceMeasurement,
2223
PressureMeasurement,
@@ -66,13 +67,15 @@
6667
STATE = "state"
6768
TEMPERATURE = "temperature"
6869
TEMPERATURE_MEASUREMENT = "temperature_measurement"
70+
TVOC_MEASUREMENT = "tvoc_measurement"
6971
TEMPERATURE_REPORTED = "temperature_reported"
7072
POWER_REPORTED = "power_reported"
7173
CONSUMPTION_REPORTED = "consumption_reported"
7274
VOLTAGE_REPORTED = "voltage_reported"
7375
ILLUMINANCE_MEASUREMENT = "illuminance_measurement"
7476
ILLUMINANCE_REPORTED = "illuminance_reported"
7577
XIAOMI_AQARA_ATTRIBUTE = 0xFF01
78+
XIAOMI_AQARA_ATTRIBUTE_E1 = 0x00F7
7679
XIAOMI_ATTR_3 = "X-attrib-3"
7780
XIAOMI_ATTR_4 = "X-attrib-4"
7881
XIAOMI_ATTR_5 = "X-attrib-5"
@@ -110,10 +113,8 @@ class XiaomiQuickInitDevice(XiaomiCustomDevice, QuickInitDevice):
110113
"""Xiaomi devices eligible for QuickInit."""
111114

112115

113-
class BasicCluster(CustomCluster, Basic):
114-
"""Xiaomi basic cluster implementation."""
115-
116-
cluster_id = Basic.cluster_id
116+
class XiaomiCluster(CustomCluster):
117+
"""Xiaomi cluster implementation."""
117118

118119
def _iter_parse_attr_report(
119120
self, data: bytes
@@ -125,7 +126,12 @@ def _iter_parse_attr_report(
125126
attr_type, data = t.uint8_t.deserialize(data)
126127

127128
if (
128-
attr_id not in (XIAOMI_AQARA_ATTRIBUTE, XIAOMI_MIJA_ATTRIBUTE)
129+
attr_id
130+
not in (
131+
XIAOMI_AQARA_ATTRIBUTE,
132+
XIAOMI_MIJA_ATTRIBUTE,
133+
XIAOMI_AQARA_ATTRIBUTE_E1,
134+
)
129135
or attr_type != 0x42 # "Character String"
130136
):
131137
# Assume other attributes are reported correctly
@@ -199,20 +205,17 @@ def deserialize(self, data):
199205
return super().deserialize(hdr.serialize() + fixed_data)
200206

201207
def _update_attribute(self, attrid, value):
202-
if attrid == XIAOMI_AQARA_ATTRIBUTE:
208+
if attrid in (XIAOMI_AQARA_ATTRIBUTE, XIAOMI_AQARA_ATTRIBUTE_E1):
203209
attributes = self._parse_aqara_attributes(value)
204210
super()._update_attribute(attrid, value)
205-
if (
206-
MODEL in self._attr_cache
207-
and self._attr_cache[MODEL] == "lumi.sensor_switch.aq2"
208-
):
211+
if self.endpoint.device.model == "lumi.sensor_switch.aq2":
209212
if value == b"\x04!\xa8C\n!\x00\x00":
210213
self.listener_event(ZHA_SEND_EVENT, COMMAND_TRIPLE, [])
211214
elif attrid == XIAOMI_MIJA_ATTRIBUTE:
212215
attributes = self._parse_mija_attributes(value)
213216
else:
214217
super()._update_attribute(attrid, value)
215-
if attrid == 0x0005:
218+
if attrid == MODEL:
216219
# 0x0005 = model attribute.
217220
# Xiaomi sensors send the model attribute when their reset button is
218221
# pressed quickly."""
@@ -266,6 +269,10 @@ def _update_attribute(self, attrid, value):
266269
self.endpoint.device.illuminance_bus.listener_event(
267270
ILLUMINANCE_REPORTED, attributes[ILLUMINANCE_MEASUREMENT]
268271
)
272+
if TVOC_MEASUREMENT in attributes:
273+
self.endpoint.voc_level.update_attribute(
274+
0x0000, attributes[TVOC_MEASUREMENT]
275+
)
269276

270277
def _parse_aqara_attributes(self, value):
271278
"""Parse non standard attributes."""
@@ -279,29 +286,29 @@ def _parse_aqara_attributes(self, value):
279286
10: PATH,
280287
}
281288

282-
if MODEL in self._attr_cache and self._attr_cache[MODEL] in [
289+
if self.endpoint.device.model in [
283290
"lumi.sensor_ht",
284291
"lumi.sens",
285292
"lumi.weather",
293+
"lumi.airmonitor.acn01",
286294
]:
287295
# Temperature sensors send temperature/humidity/pressure updates trough this
288296
# cluster instead of the respective clusters
289297
attribute_names.update(
290298
{
291299
100: TEMPERATURE_MEASUREMENT,
292300
101: HUMIDITY_MEASUREMENT,
293-
102: PRESSURE_MEASUREMENT,
301+
102: TVOC_MEASUREMENT
302+
if self.endpoint.device.model == "lumi.airmonitor.acn01"
303+
else PRESSURE_MEASUREMENT,
294304
}
295305
)
296-
elif MODEL in self._attr_cache and self._attr_cache[MODEL] in [
306+
elif self.endpoint.device.model in [
297307
"lumi.plug.maus01",
298308
"lumi.relay.c2acn01",
299309
]:
300310
attribute_names.update({149: CONSUMPTION, 150: VOLTAGE, 152: POWER})
301-
elif (
302-
MODEL in self._attr_cache
303-
and self._attr_cache[MODEL] == "lumi.sensor_motion.aq2"
304-
):
311+
elif self.endpoint.device.model == "lumi.sensor_motion.aq2":
305312
attribute_names.update({11: ILLUMINANCE_MEASUREMENT})
306313

307314
result = {}
@@ -340,6 +347,16 @@ def _parse_mija_attributes(self, value):
340347
return attributes
341348

342349

350+
class BasicCluster(XiaomiCluster, Basic):
351+
"""Xiaomi basic cluster implementation."""
352+
353+
354+
class XiaomiAqaraE1Cluster(XiaomiCluster, ManufacturerSpecificCluster):
355+
"""Xiaomi mfg cluster implementation."""
356+
357+
cluster_id = 0xFCC0
358+
359+
343360
class BinaryOutputInterlock(CustomCluster, BinaryOutput):
344361
"""Xiaomi binaryoutput cluster with added interlock attribute."""
345362

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
"""Xiaomi aqara E1 contact sensor device."""
2+
3+
from zigpy.profiles import zha
4+
from zigpy.zcl.clusters.general import Identify, Ota
5+
from zigpy.zcl.clusters.security import IasZone
6+
7+
from zhaquirks.const import (
8+
DEVICE_TYPE,
9+
ENDPOINTS,
10+
INPUT_CLUSTERS,
11+
MODELS_INFO,
12+
OUTPUT_CLUSTERS,
13+
PROFILE_ID,
14+
)
15+
from zhaquirks.xiaomi import (
16+
LUMI,
17+
BasicCluster,
18+
XiaomiAqaraE1Cluster,
19+
XiaomiCustomDevice,
20+
XiaomiPowerConfiguration,
21+
)
22+
23+
XIAOMI_CLUSTER_ID = 0xFCC0
24+
25+
26+
class MagnetE1(XiaomiCustomDevice):
27+
"""Xiaomi contact sensor device."""
28+
29+
def __init__(self, *args, **kwargs):
30+
"""Init."""
31+
self.battery_size = 11
32+
super().__init__(*args, **kwargs)
33+
34+
signature = {
35+
# <SimpleDescriptor endpoint=1 profile=260 device_type=1026
36+
# device_version=1
37+
# input_clusters=[0, 1, 3, 1280, 64704]
38+
# output_clusters=[3, 19]>
39+
MODELS_INFO: [(LUMI, "lumi.magnet.acn001")],
40+
ENDPOINTS: {
41+
1: {
42+
PROFILE_ID: zha.PROFILE_ID,
43+
DEVICE_TYPE: zha.DeviceType.IAS_ZONE,
44+
INPUT_CLUSTERS: [
45+
BasicCluster.cluster_id,
46+
XiaomiPowerConfiguration.cluster_id,
47+
Identify.cluster_id,
48+
IasZone.cluster_id,
49+
XIAOMI_CLUSTER_ID,
50+
],
51+
OUTPUT_CLUSTERS: [Identify.cluster_id, Ota.cluster_id],
52+
}
53+
},
54+
}
55+
replacement = {
56+
ENDPOINTS: {
57+
1: {
58+
INPUT_CLUSTERS: [
59+
BasicCluster,
60+
XiaomiPowerConfiguration,
61+
Identify.cluster_id,
62+
IasZone.cluster_id,
63+
XiaomiAqaraE1Cluster,
64+
],
65+
OUTPUT_CLUSTERS: [Identify.cluster_id, Ota.cluster_id],
66+
}
67+
},
68+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
"""Xiaomi aqara T1 motion sensor device."""
2+
from zigpy.profiles import zha
3+
from zigpy.zcl.clusters.general import Identify, Ota
4+
from zigpy.zcl.clusters.measurement import OccupancySensing
5+
6+
from zhaquirks import Bus
7+
from zhaquirks.const import (
8+
DEVICE_TYPE,
9+
ENDPOINTS,
10+
INPUT_CLUSTERS,
11+
MODELS_INFO,
12+
OUTPUT_CLUSTERS,
13+
PROFILE_ID,
14+
)
15+
from zhaquirks.xiaomi import (
16+
LUMI,
17+
BasicCluster,
18+
IlluminanceMeasurementCluster,
19+
MotionCluster,
20+
OccupancyCluster,
21+
XiaomiAqaraE1Cluster,
22+
XiaomiCustomDevice,
23+
XiaomiPowerConfiguration,
24+
)
25+
26+
XIAOMI_CLUSTER_ID = 0xFCC0
27+
28+
29+
class XiaomiManufacturerCluster(XiaomiAqaraE1Cluster):
30+
"""Xiaomi manufacturer cluster."""
31+
32+
def _update_attribute(self, attrid, value):
33+
super()._update_attribute(attrid, value)
34+
if attrid == 274:
35+
value = value - 65536
36+
self.endpoint.illuminance.illuminance_reported(value)
37+
self.endpoint.occupancy.update_attribute(0, 1)
38+
39+
40+
class MotionT1(XiaomiCustomDevice):
41+
"""Xiaomi contact sensor device."""
42+
43+
def __init__(self, *args, **kwargs):
44+
"""Init."""
45+
self.battery_size = 11
46+
self.motion_bus = Bus()
47+
self.illuminance_bus = Bus()
48+
super().__init__(*args, **kwargs)
49+
50+
signature = {
51+
# <SimpleDescriptor endpoint=1 profile=260 device_type=263
52+
# device_version=1
53+
# input_clusters=[0, 1, 3, 1030]
54+
# output_clusters=[3, 19]>
55+
MODELS_INFO: [(LUMI, "lumi.motion.agl02")],
56+
ENDPOINTS: {
57+
1: {
58+
PROFILE_ID: zha.PROFILE_ID,
59+
DEVICE_TYPE: zha.DeviceType.OCCUPANCY_SENSOR,
60+
INPUT_CLUSTERS: [
61+
BasicCluster.cluster_id,
62+
XiaomiPowerConfiguration.cluster_id,
63+
Identify.cluster_id,
64+
OccupancySensing.cluster_id,
65+
],
66+
OUTPUT_CLUSTERS: [Identify.cluster_id, Ota.cluster_id],
67+
}
68+
},
69+
}
70+
replacement = {
71+
ENDPOINTS: {
72+
1: {
73+
INPUT_CLUSTERS: [
74+
BasicCluster,
75+
XiaomiPowerConfiguration,
76+
Identify.cluster_id,
77+
OccupancyCluster,
78+
MotionCluster,
79+
IlluminanceMeasurementCluster,
80+
XiaomiManufacturerCluster,
81+
],
82+
OUTPUT_CLUSTERS: [Identify.cluster_id, Ota.cluster_id],
83+
}
84+
},
85+
}

zhaquirks/xiaomi/aqara/tvoc.py

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
"""Quirk for lumi.airmonitor.acn01 tvoc air monitor."""
2-
import logging
32

43
from zigpy.profiles import zha
54
from zigpy.quirks import CustomCluster
@@ -8,7 +7,7 @@
87
from zigpy.zcl.clusters.measurement import RelativeHumidity, TemperatureMeasurement
98
from zigpy.zdo.types import NodeDescriptor
109

11-
from zhaquirks import LocalDataCluster, PowerConfigurationCluster
10+
from zhaquirks import Bus, LocalDataCluster, PowerConfigurationCluster
1211
from zhaquirks.const import (
1312
DEVICE_TYPE,
1413
ENDPOINTS,
@@ -18,17 +17,24 @@
1817
OUTPUT_CLUSTERS,
1918
PROFILE_ID,
2019
)
21-
from zhaquirks.xiaomi import LUMI, BasicCluster, XiaomiCustomDevice
20+
from zhaquirks.xiaomi import (
21+
LUMI,
22+
BasicCluster,
23+
RelativeHumidityCluster,
24+
TemperatureMeasurementCluster,
25+
XiaomiAqaraE1Cluster,
26+
XiaomiCustomDevice,
27+
)
2228

23-
_LOGGER = logging.getLogger(__name__)
29+
MEASURED_VALUE = 0x0000
2430

2531

2632
class AnalogInputCluster(CustomCluster, AnalogInput):
2733
"""Analog input cluster, relay tvoc to the correct cluster."""
2834

2935
def _update_attribute(self, attrid, value):
3036
super()._update_attribute(attrid, value)
31-
self.endpoint.voc_level.update_attribute(value)
37+
self.endpoint.voc_level.update_attribute(MEASURED_VALUE, value)
3238

3339

3440
class EmulatedTVOCMeasurement(LocalDataCluster):
@@ -38,7 +44,6 @@ class EmulatedTVOCMeasurement(LocalDataCluster):
3844
ONE_HOUR = 3600
3945
MIN_CHANGE = 5
4046
TEN_SECONDS = 10
41-
MEASURED_VALUE = 0x0000
4247

4348
cluster_id = 0x042E
4449
name = "VOC Level"
@@ -59,14 +64,16 @@ async def bind(self):
5964
)
6065
return result
6166

62-
def update_attribute(self, value):
63-
"""VOC reported."""
64-
self._update_attribute(self.MEASURED_VALUE, value)
65-
6667

6768
class TVOCMonitor(XiaomiCustomDevice):
6869
"""Aqara LUMI lumi.airmonitor.acn01."""
6970

71+
def __init__(self, *args, **kwargs):
72+
"""Init."""
73+
self.temperature_bus = Bus()
74+
self.humidity_bus = Bus()
75+
super().__init__(*args, **kwargs)
76+
7077
signature = {
7178
# <SimpleDescriptor endpoint=1 profile=260 device_type=770
7279
# device_version=1
@@ -101,11 +108,12 @@ class TVOCMonitor(XiaomiCustomDevice):
101108
INPUT_CLUSTERS: [
102109
BasicCluster,
103110
Identify.cluster_id,
104-
TemperatureMeasurement.cluster_id,
111+
TemperatureMeasurementCluster,
105112
PowerConfigurationCluster,
106-
RelativeHumidity.cluster_id,
113+
RelativeHumidityCluster,
107114
AnalogInputCluster,
108115
EmulatedTVOCMeasurement,
116+
XiaomiAqaraE1Cluster,
109117
],
110118
OUTPUT_CLUSTERS: [Ota.cluster_id],
111119
}

0 commit comments

Comments
 (0)