Skip to content

Commit ca569cf

Browse files
authoredDec 23, 2024··
Fix Tuya BlitzWolf motion sensor (#3629)
* Fix Tuya BlitzWolf motion sensor using wrong cluster * Add custom `TuyaMotionWithReset` cluster This code is somewhat copied from `MotionWithReset` and related clusters from the base `zhaquirks` module. However, that code is outdated and still uses `Bus` somewhat and listens to the IAS zone status change commands. We generally listen to IAS zone status attribute updates now. This is also needed for the Tuya DP conversion. * Expand test to except automatic reset
1 parent 1d038ec commit ca569cf

File tree

2 files changed

+89
-9
lines changed

2 files changed

+89
-9
lines changed
 

‎tests/test_tuya_motion.py

+49-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
"""Tests for Tuya quirks."""
22

3+
import asyncio
4+
35
import pytest
46
from zigpy.zcl import foundation
57
from zigpy.zcl.clusters.measurement import OccupancySensing
8+
from zigpy.zcl.clusters.security import IasZone
69

710
from tests.common import ClusterListener
811
import zhaquirks
@@ -32,12 +35,10 @@
3235
("_TZE200_ztc6ggyl", "TS0601", ZCL_TUYA_MOTION),
3336
("_TZE204_ztc6ggyl", "TS0601", ZCL_TUYA_MOTION),
3437
("_TZE204_ztqnh5cg", "TS0601", ZCL_TUYA_MOTION),
35-
("_TYST11_i5j6ifxj", "5j6ifxj", ZCL_TUYA_MOTION_V3),
36-
("_TYST11_7hfcudw5", "hfcudw5", ZCL_TUYA_MOTION_V3),
3738
],
3839
)
39-
async def test_tuya_motion_quirk(zigpy_device_from_v2_quirk, model, manuf, occ_msg):
40-
"""Test Tuya Motion Quirks."""
40+
async def test_tuya_motion_quirk_occ(zigpy_device_from_v2_quirk, model, manuf, occ_msg):
41+
"""Test Tuya Motion Quirks using Occupancy cluster."""
4142
quirked_device = zigpy_device_from_v2_quirk(model, manuf)
4243
ep = quirked_device.endpoints[1]
4344

@@ -62,3 +63,47 @@ async def test_tuya_motion_quirk(zigpy_device_from_v2_quirk, model, manuf, occ_m
6263
occupancy_listener.attribute_updates[0][1]
6364
== OccupancySensing.Occupancy.Occupied
6465
)
66+
67+
68+
@pytest.mark.parametrize(
69+
"model,manuf,occ_msg",
70+
[
71+
("_TYST11_i5j6ifxj", "5j6ifxj", ZCL_TUYA_MOTION_V3),
72+
("_TYST11_7hfcudw5", "hfcudw5", ZCL_TUYA_MOTION_V3),
73+
],
74+
)
75+
@pytest.mark.asyncio
76+
async def test_tuya_motion_quirk_ias(zigpy_device_from_v2_quirk, model, manuf, occ_msg):
77+
"""Test Tuya Motion Quirks using IasZone cluster."""
78+
quirked_device = zigpy_device_from_v2_quirk(model, manuf)
79+
ep = quirked_device.endpoints[1]
80+
81+
assert ep.tuya_manufacturer is not None
82+
assert isinstance(ep.tuya_manufacturer, TuyaMCUCluster)
83+
84+
assert ep.ias_zone is not None
85+
assert isinstance(ep.ias_zone, IasZone)
86+
87+
# lower reset_s of IasZone cluster
88+
ep.ias_zone.reset_s = 0
89+
90+
ias_zone_listener = ClusterListener(ep.ias_zone)
91+
92+
hdr, data = ep.tuya_manufacturer.deserialize(occ_msg)
93+
status = ep.tuya_manufacturer.handle_get_data(data.data)
94+
95+
assert status == foundation.Status.SUCCESS
96+
97+
zcl_zone_status_id = IasZone.AttributeDefs.zone_status.id
98+
99+
# check that the zone status is set to alarm_1
100+
assert len(ias_zone_listener.attribute_updates) == 1
101+
assert ias_zone_listener.attribute_updates[0][0] == zcl_zone_status_id
102+
assert ias_zone_listener.attribute_updates[0][1] == IasZone.ZoneStatus.Alarm_1
103+
104+
await asyncio.sleep(0.01)
105+
106+
# check that the zone status is reset automatically
107+
assert len(ias_zone_listener.attribute_updates) == 2
108+
assert ias_zone_listener.attribute_updates[1][0] == zcl_zone_status_id
109+
assert ias_zone_listener.attribute_updates[1][1] == 0

‎zhaquirks/tuya/ts0601_motion.py

+40-5
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
"""BlitzWolf IS-3/Tuya motion rechargeable occupancy sensor."""
22

3+
import asyncio
34
import math
5+
from typing import Any
46

57
from zigpy.quirks.v2 import EntityType
68
from zigpy.quirks.v2.homeassistant import UnitOfLength, UnitOfTime
79
from zigpy.quirks.v2.homeassistant.sensor import SensorDeviceClass, SensorStateClass
810
import zigpy.types as t
911
from zigpy.zcl.clusters.measurement import IlluminanceMeasurement, OccupancySensing
12+
from zigpy.zcl.clusters.security import IasZone
1013

1114
from zhaquirks.tuya import TuyaLocalCluster
1215
from zhaquirks.tuya.builder import TuyaQuirkBuilder
@@ -20,6 +23,40 @@ class TuyaOccupancySensing(OccupancySensing, TuyaLocalCluster):
2023
"""Tuya local OccupancySensing cluster."""
2124

2225

26+
class TuyaMotionWithReset(IasZone, TuyaLocalCluster):
27+
"""Tuya local IAS motion cluster with reset."""
28+
29+
_CONSTANT_ATTRIBUTES = {
30+
IasZone.AttributeDefs.zone_type.id: IasZone.ZoneType.Motion_Sensor
31+
}
32+
reset_s: int = 15
33+
34+
def __init__(self, *args, **kwargs):
35+
"""Init."""
36+
super().__init__(*args, **kwargs)
37+
self._loop = asyncio.get_running_loop()
38+
self._timer_handle = None
39+
40+
def _turn_off(self) -> None:
41+
"""Reset IAS zone status."""
42+
self._timer_handle = None
43+
self.debug("%s - Resetting Tuya motion sensor", self.endpoint.device.ieee)
44+
self._update_attribute(IasZone.AttributeDefs.zone_status.id, 0)
45+
46+
def _update_attribute(self, attrid: int | t.uint16_t, value: Any) -> None:
47+
"""Catch zone status updates and potentially schedule reset."""
48+
if (
49+
attrid == IasZone.AttributeDefs.zone_status.id
50+
and value == IasZone.ZoneStatus.Alarm_1
51+
):
52+
self.debug("%s - Received Tuya motion event", self.endpoint.device.ieee)
53+
if self._timer_handle:
54+
self._timer_handle.cancel()
55+
self._timer_handle = self._loop.call_later(self.reset_s, self._turn_off)
56+
57+
super()._update_attribute(attrid, value)
58+
59+
2360
base_tuya_motion = (
2461
TuyaQuirkBuilder()
2562
.adds(TuyaOccupancySensing)
@@ -169,13 +206,11 @@ class TuyaOccupancySensing(OccupancySensing, TuyaLocalCluster):
169206
(
170207
TuyaQuirkBuilder("_TYST11_i5j6ifxj", "5j6ifxj")
171208
.applies_to("_TYST11_7hfcudw5", "hfcudw5")
172-
.tuya_dp(
209+
.tuya_ias(
173210
dp_id=3,
174-
ep_attribute=TuyaOccupancySensing.ep_attribute,
175-
attribute_name=OccupancySensing.AttributeDefs.occupancy.name,
176-
converter=lambda x: x == 2,
211+
ias_cfg=TuyaMotionWithReset,
212+
converter=lambda x: IasZone.ZoneStatus.Alarm_1 if x == 2 else 0,
177213
)
178-
.adds(TuyaOccupancySensing)
179214
.skip_configuration()
180215
.add_to_registry()
181216
)

0 commit comments

Comments
 (0)
Please sign in to comment.