Created
April 8, 2026 10:28
-
-
Save mengwong/b3ca949249405f99f03dce270d3029f5 to your computer and use it in GitHub Desktop.
ZHA quirk for Aqara Vibration Sensor T1 (lumi.vibration.agl01 / DJT12LM) — vibration + triple-tap, binary_sensor entity, dual data path handling
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """Aqara Vibration Sensor T1 (DJT12LM) — lumi.vibration.agl01. | |
| Based on nachtaap's quirk from zigpy/zha-device-handlers#4137, enhanced with | |
| MotionCluster for binary_sensor entity. | |
| Data paths observed: | |
| - IAS Zone attr 0x002D on EP2→dst EP1: value=1 vibration, value=2 triple-tap | |
| - manuSpecificLumi attr 0x0118 (280) on EP2: value=1 vibration | |
| - MultistateInput presentValue on EP2: value=1 triple-tap | |
| """ | |
| from zigpy.profiles import zha | |
| import zigpy.types as t | |
| from zigpy.quirks import CustomCluster | |
| from zigpy.zcl.clusters.general import ( | |
| Basic, | |
| Identify, | |
| MultistateInput, | |
| Ota, | |
| PowerConfiguration, | |
| ) | |
| from zigpy.zcl.clusters.security import IasZone | |
| from zigpy.zcl.foundation import ZCLAttributeDef | |
| from zhaquirks import Bus, EventableCluster, LocalDataCluster, MotionOnEvent | |
| from zhaquirks.const import ( | |
| DEVICE_TYPE, | |
| ENDPOINTS, | |
| INPUT_CLUSTERS, | |
| MODELS_INFO, | |
| MOTION_EVENT, | |
| OUTPUT_CLUSTERS, | |
| PROFILE_ID, | |
| ZHA_SEND_EVENT, | |
| ZONE_TYPE, | |
| ) | |
| from zhaquirks.xiaomi import ( | |
| LUMI, | |
| BasicCluster, | |
| XiaomiAqaraE1Cluster, | |
| XiaomiCustomDevice, | |
| XiaomiPowerConfiguration, | |
| ) | |
| # Event types | |
| VIBRATION = "vibration" | |
| TRIPLE_TAP = "triple_tap" | |
| # IAS Zone attribute 0x002D: 1=vibration, 2=triple-tap | |
| IAS_VIBRATION_ATTR = 0x002D | |
| IAS_VIBRATION_VALUE = 1 | |
| IAS_TRIPLE_TAP_VALUE = 2 | |
| # Xiaomi manufacturer attribute for vibration | |
| XIAOMI_VIBRATION_ATTR = 0x0118 # Decimal 280 | |
| class VibrationIasZoneCluster(CustomCluster, IasZone): | |
| """IAS Zone cluster that handles agl01 proprietary vibration attributes. | |
| The agl01 reports via attr 0x002D on IAS Zone (EP2→dst EP1): | |
| value=1: vibration | |
| value=2: triple-tap | |
| """ | |
| cluster_id = IasZone.cluster_id | |
| class AttributeDefs(IasZone.AttributeDefs): | |
| vibration_status = ZCLAttributeDef( | |
| id=0x002D, type=t.uint16_t | |
| ) | |
| def _update_attribute(self, attrid, value): | |
| super()._update_attribute(attrid, value) | |
| if attrid == IAS_VIBRATION_ATTR: | |
| if value == IAS_VIBRATION_VALUE: | |
| self.endpoint.device.motion_bus.listener_event(MOTION_EVENT) | |
| self.listener_event(ZHA_SEND_EVENT, VIBRATION, {"value": value}) | |
| elif value == IAS_TRIPLE_TAP_VALUE: | |
| self.listener_event(ZHA_SEND_EVENT, TRIPLE_TAP, {"value": value}) | |
| class XiaomiVibrationCluster(XiaomiAqaraE1Cluster): | |
| """Xiaomi manufacturer cluster on EP2 for vibration detection. | |
| From Z2M logs: cluster 'manuSpecificLumi', data '{"280":1}' from endpoint 2. | |
| """ | |
| attributes = XiaomiAqaraE1Cluster.attributes.copy() | |
| attributes.update({ | |
| XIAOMI_VIBRATION_ATTR: ("vibration_detected", t.uint8_t, True), | |
| }) | |
| def _update_attribute(self, attrid, value): | |
| super()._update_attribute(attrid, value) | |
| if attrid == XIAOMI_VIBRATION_ATTR and value == 1: | |
| self.endpoint.device.motion_bus.listener_event(MOTION_EVENT) | |
| self.listener_event(ZHA_SEND_EVENT, VIBRATION, {"value": value}) | |
| class VibrationMultistateInput(EventableCluster, MultistateInput): | |
| """Multistate input for triple-tap detection. | |
| From Z2M logs: cluster 'genMultistateInput', data '{"presentValue":1}' | |
| from endpoint 2. | |
| """ | |
| def _update_attribute(self, attrid, value): | |
| super()._update_attribute(attrid, value) | |
| if attrid == MultistateInput.AttributeDefs.present_value.id and value == 1: | |
| self.listener_event(ZHA_SEND_EVENT, TRIPLE_TAP, {"value": value}) | |
| class MotionCluster(LocalDataCluster, MotionOnEvent): | |
| """Exposes vibration as binary_sensor with device_class: vibration. | |
| Auto-resets to off after 70 seconds. | |
| """ | |
| _CONSTANT_ATTRIBUTES = {ZONE_TYPE: IasZone.ZoneType.Vibration_Movement_Sensor} | |
| reset_s = 70 | |
| def send_event(self, event, *args): | |
| self.listener_event(ZHA_SEND_EVENT, event, *args) | |
| class VibrationAGL01(XiaomiCustomDevice): | |
| """Aqara Vibration Sensor T1 (DJT12LM) — lumi.vibration.agl01.""" | |
| def __init__(self, *args, **kwargs): | |
| self.motion_bus = Bus() | |
| super().__init__(*args, **kwargs) | |
| signature = { | |
| MODELS_INFO: [(LUMI, "lumi.vibration.agl01")], | |
| ENDPOINTS: { | |
| 1: { | |
| PROFILE_ID: zha.PROFILE_ID, | |
| DEVICE_TYPE: 0x0402, | |
| INPUT_CLUSTERS: [ | |
| Basic.cluster_id, # 0x0000 | |
| PowerConfiguration.cluster_id, # 0x0001 | |
| Identify.cluster_id, # 0x0003 | |
| IasZone.cluster_id, # 0x0500 | |
| ], | |
| OUTPUT_CLUSTERS: [ | |
| Identify.cluster_id, # 0x0003 | |
| Ota.cluster_id, # 0x0019 | |
| ], | |
| }, | |
| 2: { | |
| PROFILE_ID: zha.PROFILE_ID, | |
| DEVICE_TYPE: 0x0402, | |
| INPUT_CLUSTERS: [ | |
| MultistateInput.cluster_id, # 0x0012 | |
| IasZone.cluster_id, # 0x0500 | |
| ], | |
| OUTPUT_CLUSTERS: [], | |
| }, | |
| }, | |
| } | |
| replacement = { | |
| ENDPOINTS: { | |
| 1: { | |
| PROFILE_ID: zha.PROFILE_ID, | |
| DEVICE_TYPE: 0x0402, | |
| INPUT_CLUSTERS: [ | |
| BasicCluster, | |
| XiaomiPowerConfiguration, | |
| XiaomiAqaraE1Cluster, | |
| Identify.cluster_id, | |
| MotionCluster, | |
| VibrationIasZoneCluster, | |
| ], | |
| OUTPUT_CLUSTERS: [ | |
| Identify.cluster_id, | |
| Ota.cluster_id, | |
| ], | |
| }, | |
| 2: { | |
| PROFILE_ID: zha.PROFILE_ID, | |
| DEVICE_TYPE: 0x0402, | |
| INPUT_CLUSTERS: [ | |
| VibrationMultistateInput, | |
| XiaomiVibrationCluster, | |
| VibrationIasZoneCluster, | |
| ], | |
| OUTPUT_CLUSTERS: [], | |
| }, | |
| }, | |
| } | |
| device_automation_triggers = { | |
| (VIBRATION, VIBRATION): {"type": VIBRATION, "subtype": VIBRATION}, | |
| (TRIPLE_TAP, TRIPLE_TAP): {"type": TRIPLE_TAP, "subtype": TRIPLE_TAP}, | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment