diff --git a/tests/test_thirdreality.py b/tests/test_thirdreality.py new file mode 100644 index 0000000000..0d2af260d1 --- /dev/null +++ b/tests/test_thirdreality.py @@ -0,0 +1,40 @@ +"""Tests for Third Reality quirks.""" + + +import pytest +from zigpy.zcl.clusters.security import IasZone + +import zhaquirks +import zhaquirks.thirdreality.night_light + +from tests.common import ClusterListener + +zhaquirks.setup() + + +@pytest.mark.parametrize("quirk", (zhaquirks.thirdreality.night_light.Nightlight,)) +async def test_third_reality_nightlight(zigpy_device_from_quirk, quirk): + """Test Third Reality night light forwarding motion attribute to IasZone cluster.""" + + device = zigpy_device_from_quirk(quirk) + + ias_zone_cluster = device.endpoints[1].ias_zone + ias_zone_listener = ClusterListener(ias_zone_cluster) + + ias_zone_status_id = IasZone.AttributeDefs.zone_status.id + + third_reality_cluster = device.endpoints[1].in_clusters[0xFC00] + + # 0x0002 is also used on manufacturer specific cluster for motion events + third_reality_cluster.update_attribute(0x0002, IasZone.ZoneStatus.Alarm_1) + + assert len(ias_zone_listener.attribute_updates) == 1 + assert ias_zone_listener.attribute_updates[0][0] == ias_zone_status_id + assert ias_zone_listener.attribute_updates[0][1] == IasZone.ZoneStatus.Alarm_1 + + # turn off motion alarm + third_reality_cluster.update_attribute(0x0002, 0) + + assert len(ias_zone_listener.attribute_updates) == 2 + assert ias_zone_listener.attribute_updates[1][0] == ias_zone_status_id + assert ias_zone_listener.attribute_updates[1][1] == 0 diff --git a/zhaquirks/thirdreality/night_light.py b/zhaquirks/thirdreality/night_light.py new file mode 100644 index 0000000000..990b2b18e3 --- /dev/null +++ b/zhaquirks/thirdreality/night_light.py @@ -0,0 +1,106 @@ +"""Third Reality night light zigbee devices.""" +from zigpy.profiles import zha +from zigpy.quirks import CustomCluster, CustomDevice +from zigpy.zcl.clusters.general import ( + Basic, + Groups, + Identify, + LevelControl, + MultistateInput, + OnOff, + Ota, + Scenes, +) +from zigpy.zcl.clusters.lighting import Color +from zigpy.zcl.clusters.lightlink import LightLink +from zigpy.zcl.clusters.measurement import IlluminanceMeasurement +from zigpy.zcl.clusters.security import IasZone + +from zhaquirks import LocalDataCluster +from zhaquirks.const import ( + DEVICE_TYPE, + ENDPOINTS, + INPUT_CLUSTERS, + MODELS_INFO, + OUTPUT_CLUSTERS, + PROFILE_ID, +) +from zhaquirks.thirdreality import THIRD_REALITY + +THIRD_REALITY_CLUSTER_ID = 0xFC00 +THIRD_REALITY_MOTION_EVENT_ATTR_ID = 0x0002 + + +class ThirdRealitySpecificCluster(CustomCluster): + """Manufacturer specific cluster to relay motion event to IAS Zone cluster.""" + + cluster_id = THIRD_REALITY_CLUSTER_ID + + def _update_attribute(self, attrid, value): + super()._update_attribute(attrid, value) + if attrid == THIRD_REALITY_MOTION_EVENT_ATTR_ID: + self.endpoint.ias_zone.update_attribute( + IasZone.AttributeDefs.zone_status.id, value + ) + + +class LocalIasZone(LocalDataCluster, IasZone): + """Local IAS Zone cluster.""" + + _CONSTANT_ATTRIBUTES = { + IasZone.AttributeDefs.zone_type.id: IasZone.ZoneType.Motion_Sensor + } + + +class Nightlight(CustomDevice): + """Custom device for 3RSNL02043Z.""" + + signature = { + MODELS_INFO: [(THIRD_REALITY, "3RSNL02043Z")], + ENDPOINTS: { + 1: { + PROFILE_ID: zha.PROFILE_ID, + DEVICE_TYPE: zha.DeviceType.COLOR_DIMMABLE_LIGHT, + INPUT_CLUSTERS: [ + Basic.cluster_id, + Identify.cluster_id, + Groups.cluster_id, + Scenes.cluster_id, + OnOff.cluster_id, + LevelControl.cluster_id, + MultistateInput.cluster_id, + Color.cluster_id, + IlluminanceMeasurement.cluster_id, + LightLink.cluster_id, + ThirdRealitySpecificCluster.cluster_id, + ], + OUTPUT_CLUSTERS: [ + Ota.cluster_id, + ], + } + }, + } + replacement = { + ENDPOINTS: { + 1: { + DEVICE_TYPE: zha.DeviceType.COLOR_DIMMABLE_LIGHT, + INPUT_CLUSTERS: [ + Basic.cluster_id, + Identify.cluster_id, + Groups.cluster_id, + Scenes.cluster_id, + OnOff.cluster_id, + LevelControl.cluster_id, + MultistateInput.cluster_id, + Color.cluster_id, + IlluminanceMeasurement.cluster_id, + LightLink.cluster_id, + LocalIasZone, + ThirdRealitySpecificCluster, + ], + OUTPUT_CLUSTERS: [ + Ota.cluster_id, + ], + } + } + }