Repository: zigpy/zha-device-handlers

Issue:
[Device Support Request] for Aqara T1 door and window sensor
              hey, I got 2  Aqara T1 door and window sensor( new version that had been launched in the past month, and it seems it is not supported either. neither the status or the battery. Any thoughts on how to fix this?

Device is initialized <Device model='lumi.magnet.agl02' manuf='LUMI' nwk=0x91F1 ieee=54:ef:44:10:00:92:52:06 is_initialized=True>
Checking quirks for LUMI lumi.magnet.agl02 (54:ef:44:10:00:92:52:06)
Considering <class 'zhaquirks.xiaomi.aqara.opple_switch.XiaomiOpple2ButtonSwitchFace2'>
Fail because endpoint list mismatch: {1, 2, 242} {1}
Considering <class 'zhaquirks.xiaomi.aqara.opple_switch.XiaomiOpple2ButtonSwitchFace1'>
Fail because endpoint list mismatch: {1, 2, 41, 42, 242, 51, 21, 31} {1}
Considering <class 'zhaquirks.xbee.xbee_io.XBeeSensor'>
Fail because endpoint list mismatch: {232, 230} {1}
Considering <class 'zhaquirks.xbee.xbee3_io.XBee3Sensor'>
Fail because endpoint list mismatch: {232, 230} {1}
Considering <class 'zhaquirks.tuya.ts0201.MoesTemperatureHumidtySensorWithScreen'>
Fail because device_type mismatch on at least one endpoint
Considering <class 'zhaquirks.smartthings.tag_v4.SmartThingsTagV4'>
Fail because device_type mismatch on at least one endpoint
Considering <class 'zhaquirks.smartthings.multi.SmartthingsMultiPurposeSensor'>
Fail because input cluster mismatch on at least one endpoint
Considering <class 'zhaquirks.netvox.z308e3ed.Z308E3ED'>
Fail because input cluster mismatch on at least one endpoint
Considering <class 'zhaquirks.gledopto.soposhgu10.SoposhGU10'>
Fail because endpoint list mismatch: {11, 13} {1}

this is the spec page i found digging around 
https://home.miot-spec.com/spec?type=urn:miot-spec-v2:device:magnet-sensor:0000A016:lumi-agl02:1:0000C80D

_Originally posted by @vistvanv in https://github.com/zigpy/zha-device-handlers/issues/2582#issuecomment-1733241147_
            


Discussion:

Resolving patch:
diff --git a/zhaquirks/xiaomi/aqara/magnet_agl02.py b/zhaquirks/xiaomi/aqara/magnet_agl02.py
new file mode 100644
--- /dev/null
+++ b/zhaquirks/xiaomi/aqara/magnet_agl02.py
@@ -0,0 +1,67 @@
+"""Quirk for Aqara T1 door sensor lumi.magnet.agl02."""
+
+from zigpy.profiles import zha
+from zigpy.zcl.clusters.general import Basic, Identify, OnOff, Ota, PowerConfiguration
+from zigpy.zcl.clusters.security import IasZone
+
+from zhaquirks.const import (
+    DEVICE_TYPE,
+    ENDPOINTS,
+    INPUT_CLUSTERS,
+    MODELS_INFO,
+    OUTPUT_CLUSTERS,
+    PROFILE_ID,
+)
+from zhaquirks.xiaomi import (
+    BasicCluster,
+    XiaomiAqaraE1Cluster,
+    XiaomiCustomDevice,
+    XiaomiPowerConfiguration,
+)
+
+
+class MagnetT1(XiaomiCustomDevice):
+    """Aqara T1 door sensor quirk."""
+
+    def __init__(self, *args, **kwargs):
+        """Init."""
+        self.battery_size = 11
+        super().__init__(*args, **kwargs)
+
+    signature = {
+        MODELS_INFO: [("LUMI", "lumi.magnet.agl02")],
+        ENDPOINTS: {
+            1: {
+                PROFILE_ID: zha.PROFILE_ID,
+                DEVICE_TYPE: zha.DeviceType.IAS_ZONE,
+                INPUT_CLUSTERS: [
+                    Basic.cluster_id,
+                    PowerConfiguration.cluster_id,
+                    Identify.cluster_id,
+                    IasZone.cluster_id,
+                ],
+                OUTPUT_CLUSTERS: [
+                    Ota.cluster_id,
+                ],
+            },
+        },
+    }
+
+    replacement = {
+        ENDPOINTS: {
+            1: {
+                PROFILE_ID: zha.PROFILE_ID,
+                DEVICE_TYPE: zha.DeviceType.IAS_ZONE,
+                INPUT_CLUSTERS: [
+                    BasicCluster,
+                    XiaomiPowerConfiguration,
+                    Identify.cluster_id,
+                    XiaomiAqaraE1Cluster,
+                ],
+                OUTPUT_CLUSTERS: [
+                    OnOff.cluster_id,
+                    Ota.cluster_id,
+                ],
+            },
+        },
+    }


Test patch:
diff --git a/tests/test_xiaomi.py b/tests/test_xiaomi.py
--- a/tests/test_xiaomi.py
+++ b/tests/test_xiaomi.py
@@ -11,6 +11,7 @@
 from zigpy.zcl.clusters.general import (
     AnalogInput,
     DeviceTemperature,
+    OnOff,
     PowerConfiguration,
 )
 from zigpy.zcl.clusters.homeautomation import ElectricalMeasurement
@@ -45,6 +46,7 @@
     XIAOMI_AQARA_ATTRIBUTE_E1,
     XIAOMI_NODE_DESC,
     BasicCluster,
+    XiaomiAqaraE1Cluster,
     XiaomiCustomDevice,
     XiaomiQuickInitDevice,
     handle_quick_init,
@@ -65,6 +67,7 @@
     AqaraFeederAcn001,
     OppleCluster,
 )
+import zhaquirks.xiaomi.aqara.magnet_agl02
 import zhaquirks.xiaomi.aqara.motion_ac02
 import zhaquirks.xiaomi.aqara.motion_aq2
 import zhaquirks.xiaomi.aqara.motion_aq2b
@@ -1283,3 +1286,79 @@ async def test_xiaomi_e1_roller_curtain_battery(zigpy_device_from_quirk, quirk):
     assert power_listener.attribute_updates[0][1] == 28.9
     assert power_listener.attribute_updates[1][0] == zcl_power_percent_id
     assert power_listener.attribute_updates[1][1] == 120
+
+
+@pytest.mark.parametrize(
+    "raw_report, expected_results",
+    (
+        [
+            "1C5F11670AF700412E0121B00C0328190421A8130521090006240D0000000008211E010A2100000C20016410016620036720016821A800",
+            [
+                32.5,  # battery voltage
+                200,  # battery percent * 2
+            ],
+        ],
+    ),
+)
+async def test_xiaomi_t1_door_sensor(
+    zigpy_device_from_quirk, raw_report, expected_results
+):
+    """Test Aqara T1 door sensor."""
+    raw_report = bytes.fromhex(raw_report)
+
+    device = zigpy_device_from_quirk(zhaquirks.xiaomi.aqara.magnet_agl02.MagnetT1)
+
+    on_off_cluster = device.endpoints[1].out_clusters[OnOff.cluster_id]
+    on_off_listener = ClusterListener(on_off_cluster)
+
+    # check open state
+    device.handle_message(
+        260,
+        on_off_cluster.cluster_id,
+        on_off_cluster.endpoint.endpoint_id,
+        on_off_cluster.endpoint.endpoint_id,
+        bytes.fromhex("185D0A00001001"),
+    )
+
+    assert len(on_off_listener.attribute_updates) == 1
+    assert on_off_listener.attribute_updates[0][0] == OnOff.AttributeDefs.on_off.id
+    assert on_off_listener.attribute_updates[0][1] == t.Bool.true
+
+    # check closed state
+    device.handle_message(
+        260,
+        on_off_cluster.cluster_id,
+        on_off_cluster.endpoint.endpoint_id,
+        on_off_cluster.endpoint.endpoint_id,
+        bytes.fromhex("18640A00001000"),
+    )
+
+    assert len(on_off_listener.attribute_updates) == 2
+    assert on_off_listener.attribute_updates[1][0] == OnOff.AttributeDefs.on_off.id
+    assert on_off_listener.attribute_updates[1][1] == t.Bool.false
+
+    opple_cluster = device.endpoints[1].in_clusters[XiaomiAqaraE1Cluster.cluster_id]
+    ClusterListener(opple_cluster)
+
+    power_cluster = device.endpoints[1].power
+    power_listener = ClusterListener(power_cluster)
+
+    zcl_power_voltage_id = PowerConfiguration.AttributeDefs.battery_voltage.id
+    zcl_power_percent_id = (
+        PowerConfiguration.AttributeDefs.battery_percentage_remaining.id
+    )
+
+    # check Xiaomi attribute report
+    device.handle_message(
+        260,
+        opple_cluster.cluster_id,
+        opple_cluster.endpoint.endpoint_id,
+        opple_cluster.endpoint.endpoint_id,
+        raw_report,
+    )
+
+    assert len(power_listener.attribute_updates) == 2
+    assert power_listener.attribute_updates[0][0] == zcl_power_voltage_id
+    assert power_listener.attribute_updates[0][1] == expected_results[0]
+    assert power_listener.attribute_updates[1][0] == zcl_power_percent_id
+    assert power_listener.attribute_updates[1][1] == expected_results[1]