Repository: zigpy/zha-device-handlers
Pull request #3674 (2025-01-04 23:16:00)

Fix missing endpoints for Aqara H1 wireless remotes
## Proposed change
Fix missing endpoints for Aqara H1 wireless remotes.

The device sometimes doesn't report these endpoints as available, so quirks v2 code that `adds` or `replaces` clusters on those endpoints will cause ZHA to break.
Zigpy now allows us to add these endpoints to the zigpy device, so we can also add clusters to the endpoints in the same quirk without issues.


## Additional information

Addresses https://github.com/home-assistant/core/issues/134686 and https://github.com/home-assistant/core/issues/134691.
Follow-up to https://github.com/zigpy/zha-device-handlers/pull/3564

~~This needs https://github.com/zigpy/zigpy/pull/1524 and a corresponding [zigpy bump](https://github.com/zigpy/zha-device-handlers/pull/3675), then a rebase before tests will pass.~~ (done)

## Checklist

- [ ] The changes are tested and work correctly
- [x] `pre-commit` checks pass / the code has been formatted using Black
- [x] Tests have been added to verify that the new code works


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/zigpy/zha-device-handlers/pull/3674?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=zigpy) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 89.85%. Comparing base [(`5b001f1`)](https://app.codecov.io/gh/zigpy/zha-device-handlers/commit/5b001f140f520d7287034e8137c85866ac4bfbda?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=zigpy) to head [(`3a661f4`)](https://app.codecov.io/gh/zigpy/zha-device-handlers/commit/3a661f4a7e03e8b5784831709a5e1181ecf6d6cd?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=zigpy).

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##              dev    #3674   +/-   ##
=======================================
  Coverage   89.85%   89.85%           
=======================================
  Files         322      322           
  Lines       10379    10380    +1     
=======================================
+ Hits         9326     9327    +1     
  Misses       1053     1053           
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/zigpy/zha-device-handlers/pull/3674?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=zigpy).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=zigpy).


Patch:
diff --git a/zhaquirks/tuya/ts0601_motion.py b/zhaquirks/tuya/ts0601_motion.py
index 2c12194..65ac852 100644
--- a/zhaquirks/tuya/ts0601_motion.py
+++ b/zhaquirks/tuya/ts0601_motion.py
@@ -126,6 +126,7 @@ base_tuya_motion = (
 (
     base_tuya_motion.clone()
     .applies_to("_TZE200_ya4ft0w4", "TS0601")
+    .applies_to("_TZE204_ya4ft0w4", "TS0601")
     .tuya_dp(
         dp_id=1,
         ep_attribute=TuyaOccupancySensing.ep_attribute,
diff --git a/zhaquirks/xiaomi/aqara/remote_h1.py b/zhaquirks/xiaomi/aqara/remote_h1.py
index 786bd15..e15ab52 100644
--- a/zhaquirks/xiaomi/aqara/remote_h1.py
+++ b/zhaquirks/xiaomi/aqara/remote_h1.py
@@ -1,6 +1,7 @@
 """Aqara H1-series wireless remote."""
 
 from zigpy import types
+from zigpy.profiles import zha
 from zigpy.quirks.v2 import ClusterType, QuirkBuilder
 from zigpy.zcl.clusters.general import Identify, OnOff
 from zigpy.zcl.foundation import BaseAttributeDefs, DataTypeId, ZCLAttributeDef
@@ -144,6 +145,9 @@ class PowerConfigurationClusterH1Remote(PowerConfigurationCluster):
     # .friendly_name(
     #     manufacturer="Aqara", model="Wireless Remote Switch H1 (Double Rocker)"
     # )
+    # add endpoints that are present but not exposed sometimes
+    .adds_endpoint(2, device_type=zha.DeviceType.ON_OFF_LIGHT_SWITCH)
+    .adds_endpoint(3, device_type=zha.DeviceType.ON_OFF_LIGHT_SWITCH)
     .replaces(AqaraRemoteManuSpecificCluster)
     .adds(Identify)
     .replaces(MultistateInputCluster)


Test patch:
diff --git a/tests/test_tuya_motion.py b/tests/test_tuya_motion.py
index 10e1d08..a11f9d2 100644
--- a/tests/test_tuya_motion.py
+++ b/tests/test_tuya_motion.py
@@ -24,6 +24,7 @@ zhaquirks.setup()
     "model,manuf,occ_msg",
     [
         ("_TZE200_ya4ft0w4", "TS0601", ZCL_TUYA_MOTION),
+        ("_TZE204_ya4ft0w4", "TS0601", ZCL_TUYA_MOTION),
         ("_TZE200_7hfcudw5", "TS0601", ZCL_TUYA_MOTION_V2),
         ("_TZE200_ppuj1vem", "TS0601", ZCL_TUYA_MOTION_V2),
         ("_TZE200_ar0slwnd", "TS0601", ZCL_TUYA_MOTION),
diff --git a/tests/test_xiaomi.py b/tests/test_xiaomi.py
index ef2c7ad..811a667 100644
--- a/tests/test_xiaomi.py
+++ b/tests/test_xiaomi.py
@@ -1804,3 +1804,20 @@ async def test_aqara_fp1e_sensor(
     assert len(ias_listener.attribute_updates) == 1
     assert ias_listener.attribute_updates[0][0] == IasZone.AttributeDefs.zone_status.id
     assert ias_listener.attribute_updates[0][1] == expected_motion_status
+
+
+def test_h1_wireless_remotes(zigpy_device_from_v2_quirk):
+    """Test Aqara H1 wireless remote quirk adds missing endpoints."""
+    # create device with endpoint 1 only and verify we don't get a KeyError
+    quirk = zigpy_device_from_v2_quirk(LUMI, "lumi.remote.b28ac1")
+
+    # verify the quirk adds endpoints 2 and 3
+    assert 2 in quirk.endpoints
+    assert 3 in quirk.endpoints
+
+    # verify the quirk adds the correct clusters to the new endpoints
+    assert OnOff.cluster_id in quirk.endpoints[2].out_clusters
+    assert OnOff.cluster_id in quirk.endpoints[3].out_clusters
+
+    assert MultistateInput.cluster_id in quirk.endpoints[2].in_clusters
+    assert MultistateInput.cluster_id in quirk.endpoints[3].in_clusters