Skip to content

Commit d80730f

Browse files
authored
Add Enchanted Device method to TuyaQuirkBuilder (#3661)
1 parent e3d3849 commit d80730f

File tree

3 files changed

+94
-3
lines changed

3 files changed

+94
-3
lines changed

tests/test_tuya_builder.py

+71
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
from tests.common import ClusterListener, wait_for_zigpy_tasks
1414
import zhaquirks
15+
from zhaquirks.tuya import TUYA_QUERY_DATA
1516
from zhaquirks.tuya.builder import (
1617
TuyaIasContact,
1718
TuyaIasFire,
@@ -187,3 +188,73 @@ class TestEnum(t.enum8):
187188

188189
assert tuya_listener.attribute_updates[0][0] == 0xEF0A
189190
assert tuya_listener.attribute_updates[0][1] == TestEnum.B
191+
192+
193+
@pytest.mark.parametrize(
194+
"read_attr_spell,data_query_spell",
195+
[
196+
(True, False),
197+
(False, True),
198+
(True, True),
199+
(False, False),
200+
],
201+
)
202+
async def test_tuya_spell(device_mock, read_attr_spell, data_query_spell):
203+
"""Test that enchanted Tuya devices have their spells applied during configuration."""
204+
registry = DeviceRegistry()
205+
206+
entry = (
207+
TuyaQuirkBuilder(device_mock.manufacturer, device_mock.model, registry=registry)
208+
.tuya_battery(dp_id=1)
209+
.tuya_onoff(dp_id=3)
210+
.tuya_enchantment(
211+
read_attr_spell=read_attr_spell, data_query_spell=data_query_spell
212+
)
213+
.skip_configuration()
214+
.add_to_registry()
215+
)
216+
217+
# coverage for overridden __eq__ method
218+
assert entry.adds_metadata[0] != entry.adds_metadata[1]
219+
assert entry.adds_metadata[0] != entry
220+
221+
quirked = registry.get_device(device_mock)
222+
223+
assert isinstance(quirked, CustomDeviceV2)
224+
assert quirked in registry
225+
226+
request_patch = mock.patch("zigpy.zcl.Cluster.request", mock.AsyncMock())
227+
with request_patch as request_mock:
228+
request_mock.return_value = (foundation.Status.SUCCESS, "done")
229+
230+
# call apply_custom_configuration() on each EnchantedDevice
231+
# ZHA does this during device configuration normally
232+
await quirked.apply_custom_configuration()
233+
234+
# the number of Tuya spells that are allowed to be cast, so the sum of enabled Tuya spells
235+
enabled_tuya_spells_num = (
236+
quirked.tuya_spell_read_attributes + quirked.tuya_spell_data_query
237+
)
238+
239+
# verify request was called the correct number of times
240+
assert request_mock.call_count == enabled_tuya_spells_num
241+
242+
# used to check list of mock calls below
243+
messages = 0
244+
245+
# check 'attribute read spell' was cast correctly (if enabled)
246+
if quirked.tuya_spell_read_attributes:
247+
assert (
248+
request_mock.mock_calls[messages][1][1]
249+
== foundation.GeneralCommand.Read_Attributes
250+
)
251+
assert request_mock.mock_calls[messages][1][3] == [4, 0, 1, 5, 7, 65534]
252+
messages += 1
253+
254+
# check 'query data spell' was cast correctly (if enabled)
255+
if quirked.tuya_spell_data_query:
256+
assert not request_mock.mock_calls[messages][1][0]
257+
assert request_mock.mock_calls[messages][1][1] == TUYA_QUERY_DATA
258+
messages += 1
259+
260+
request_mock.reset_mock()

zhaquirks/tuya/__init__.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import logging
88
from typing import Any, Optional, Union
99

10-
from zigpy.quirks import CustomCluster, CustomDevice
10+
from zigpy.quirks import BaseCustomDevice, CustomCluster, CustomDevice
1111
import zigpy.types as t
1212
from zigpy.zcl import BaseAttributeDefs, foundation
1313
from zigpy.zcl.clusters.closures import WindowCovering
@@ -529,7 +529,7 @@ async def write_attributes(self, attributes, manufacturer=None):
529529
return [[foundation.WriteAttributesStatusRecord(foundation.Status.SUCCESS)]]
530530

531531

532-
class EnchantedDevice(CustomDevice):
532+
class BaseEnchantedDevice(BaseCustomDevice):
533533
"""Class for Tuya devices which need to be unlocked by casting a 'spell'.
534534
535535
The spell is applied during device configuration.
@@ -570,6 +570,10 @@ async def spell_data_query(self):
570570
self.debug("Executed data query spell on Tuya device %s", self.ieee)
571571

572572

573+
class EnchantedDevice(CustomDevice, BaseEnchantedDevice):
574+
"""Enchanted device class for v1 quirks."""
575+
576+
573577
class TuyaOnOff(CustomCluster, OnOff):
574578
"""Tuya On/Off cluster for On/Off device."""
575579

zhaquirks/tuya/builder/__init__.py

+17-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from zigpy.quirks import _DEVICE_REGISTRY
88
from zigpy.quirks.registry import DeviceRegistry
9-
from zigpy.quirks.v2 import QuirkBuilder, QuirksV2RegistryEntry
9+
from zigpy.quirks.v2 import CustomDeviceV2, QuirkBuilder, QuirksV2RegistryEntry
1010
from zigpy.quirks.v2.homeassistant import EntityPlatform, EntityType
1111
from zigpy.quirks.v2.homeassistant.binary_sensor import BinarySensorDeviceClass
1212
from zigpy.quirks.v2.homeassistant.number import NumberDeviceClass
@@ -23,6 +23,7 @@
2323

2424
from zhaquirks.tuya import (
2525
TUYA_CLUSTER_ID,
26+
BaseEnchantedDevice,
2627
PowerConfiguration,
2728
TuyaLocalCluster,
2829
TuyaPowerConfigurationCluster2AAA,
@@ -522,6 +523,21 @@ def tuya_sensor(
522523

523524
return self
524525

526+
def tuya_enchantment(
527+
self, read_attr_spell: bool = True, data_query_spell: bool = False
528+
) -> QuirkBuilder:
529+
"""Set the Tuya enchantment spells."""
530+
531+
class EnchantedDeviceV2(CustomDeviceV2, BaseEnchantedDevice):
532+
"""Enchanted device class for v2 quirks."""
533+
534+
EnchantedDeviceV2.tuya_spell_read_attributes = read_attr_spell
535+
EnchantedDeviceV2.tuya_spell_data_query = data_query_spell
536+
537+
self.device_class(EnchantedDeviceV2)
538+
539+
return self
540+
525541
def add_to_registry(self) -> QuirksV2RegistryEntry:
526542
"""Build the quirks v2 registry entry."""
527543

0 commit comments

Comments
 (0)