From 8e5a78c1782708ee2999d698e5b026e3748cf9f1 Mon Sep 17 00:00:00 2001 From: TheJulianJES Date: Sun, 27 Oct 2024 03:58:13 +0100 Subject: [PATCH 1/2] Forward `contact` attribute changes to `OnOff` cluster --- zhaquirks/philips/soc001.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/zhaquirks/philips/soc001.py b/zhaquirks/philips/soc001.py index d51b65b55e..3a7bd8444f 100644 --- a/zhaquirks/philips/soc001.py +++ b/zhaquirks/philips/soc001.py @@ -3,6 +3,7 @@ from zigpy import types from zigpy.quirks import CustomCluster from zigpy.quirks.v2 import BinarySensorDeviceClass, EntityType, QuirkBuilder +from zigpy.zcl.clusters.general import OnOff from zigpy.zcl.foundation import BaseAttributeDefs, ZCLAttributeDef @@ -37,6 +38,22 @@ class AttributeDefs(BaseAttributeDefs): is_manufacturer_specific=True, ) + # catch when contact attribute is updated and forward to OnOff cluster + def _update_attribute(self, attrid, value): + super()._update_attribute(attrid, value) + + on_off_cluster = self.endpoint.out_clusters[OnOff.cluster_id] + if ( + attrid == self.AttributeDefs.contact.id + and on_off_cluster.get(OnOff.AttributeDefs.on_off.id) != value + ): + # This seems to happen after the real OnOff attribute change, + # so we can avoid a duplicate event by checking the current value. + # We'll only update the OnOff cluster if the value is different then, + # this is likely only the case when an update was missed, and we later + # get an attribute report for the custom contact attribute. + on_off_cluster.update_attribute(OnOff.AttributeDefs.on_off.id, value) + ( # Date: Sun, 27 Oct 2024 03:58:23 +0100 Subject: [PATCH 2/2] Add test checking forwarding --- tests/test_philips.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/test_philips.py b/tests/test_philips.py index 3fdebf00a4..fa5cb72d67 100644 --- a/tests/test_philips.py +++ b/tests/test_philips.py @@ -3,8 +3,10 @@ from unittest import mock import pytest +from zigpy.zcl.clusters.general import OnOff from zigpy.zcl.foundation import ZCLHeader +from tests.common import ClusterListener import zhaquirks from zhaquirks.const import ( BUTTON_1, @@ -738,3 +740,31 @@ def test_rdm002_triggers(): actual_triggers = PhilipsRDM002.device_automation_triggers assert actual_triggers == expected_triggers + + +def test_contact_sensor(zigpy_device_from_v2_quirk): + """Test that the Hue contact attribute is forwarded to the OnOff cluster.""" + quirk = zigpy_device_from_v2_quirk( + "Signify Netherlands B.V.", "SOC001", endpoint_ids=[1, 2] + ) + # Add output OnOff cluster to the endpoint. The device has this, + # but the quirk doesn't modify that cluster, so we need to add it manually. + quirk.endpoints[2].add_output_cluster(OnOff.cluster_id) + + hue_cluster = quirk.endpoints[2].philips_contact_cluster + on_off_cluster = quirk.endpoints[2].out_clusters[OnOff.cluster_id] + on_off_listener = ClusterListener(on_off_cluster) + + # update the contact attribute and check that it is forwarded to the OnOff cluster + hue_cluster.update_attribute(hue_cluster.AttributeDefs.contact.id, 0) + assert on_off_listener.attribute_updates[0] == (0, 0) + + hue_cluster.update_attribute(hue_cluster.AttributeDefs.contact.id, 1) + assert on_off_listener.attribute_updates[1] == (0, 1) + + # check we didn't exceed the number of expected updates + assert len(on_off_listener.attribute_updates) == 2 + + # update again with the same value and except no new update + hue_cluster.update_attribute(hue_cluster.AttributeDefs.contact.id, 1) + assert len(on_off_listener.attribute_updates) == 2