Skip to content

Commit e7f02ff

Browse files
committed
Merge branch 'release/0.0.81'
2 parents eeeee26 + b1e2315 commit e7f02ff

17 files changed

+165
-37
lines changed

.github/workflows/ci.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ on:
1010

1111
env:
1212
CACHE_VERSION: 1
13-
DEFAULT_PYTHON: 3.7
13+
DEFAULT_PYTHON: '3.9.14'
1414
PRE_COMMIT_HOME: ~/.cache/pre-commit
1515

1616
jobs:
@@ -21,7 +21,7 @@ jobs:
2121
runs-on: ubuntu-latest
2222
strategy:
2323
matrix:
24-
python-version: [3.7, 3.8, 3.9]
24+
python-version: ['3.8.14', '3.9.14', '3.10.7']
2525
steps:
2626
- name: Check out code from GitHub
2727
uses: actions/checkout@v2
@@ -280,7 +280,7 @@ jobs:
280280
needs: prepare-base
281281
strategy:
282282
matrix:
283-
python-version: [3.7, 3.8, 3.9]
283+
python-version: ['3.8.14', '3.9.14', '3.10.7']
284284
name: >-
285285
Run tests Python ${{ matrix.python-version }}
286286
steps:

requirements_test_all.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
asynctest
21
isort
32
black
43
flake8
@@ -12,5 +11,6 @@ pylint
1211
pytest-cov
1312
pytest-sugar
1413
pytest-timeout
15-
pytest
14+
pytest-asyncio
15+
pytest>=7.1.3
1616
zigpy

setup.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
[tool:pytest]
2+
asyncio_mode = auto
23
testpaths = tests
34
norecursedirs = .git testing_config
45

setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from setuptools import find_packages, setup
66

7-
VERSION = "0.0.80"
7+
VERSION = "0.0.81"
88

99

1010
setup(
@@ -20,6 +20,6 @@
2020
keywords="zha quirks homeassistant hass",
2121
packages=find_packages(exclude=["tests"]),
2222
python_requires=">=3",
23-
install_requires=["zigpy>=0.45.1"],
23+
install_requires=["zigpy>=0.51.1"],
2424
tests_require=["pytest"],
2525
)

tests/common.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Quirks common helpers."""
2+
import asyncio
23
import datetime
34

45
ZCL_IAS_MOTION_COMMAND = b"\t!\x00\x01\x00\x00\x00\x00\x00"
@@ -37,3 +38,17 @@ def utcnow(cls):
3738
"""Return testvalue."""
3839

3940
return cls(1970, 1, 1, 2, 0, 0)
41+
42+
43+
async def wait_for_zigpy_tasks() -> None:
44+
"""Wait for all running zigpy tasks to finish."""
45+
tasks = []
46+
47+
for task in asyncio.all_tasks():
48+
coro = task.get_coro()
49+
50+
# TODO: track tasks within zigpy
51+
if "CatchingTaskMixin" in coro.__qualname__:
52+
tasks.append(task)
53+
54+
await asyncio.gather(*tasks)

tests/conftest.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
"""Fixtures for all tests."""
22

3-
try:
4-
from unittest.mock import AsyncMock as CoroutineMock
5-
except ImportError:
6-
from asynctest import CoroutineMock
3+
from unittest.mock import AsyncMock
74

85
import pytest
96
import zigpy.application
@@ -64,6 +61,12 @@ async def load_network_info(self, *args, **kwargs):
6461
async def permit_with_key(self, *args, **kwargs):
6562
"""Mock permit_with_key."""
6663

64+
async def reset_network_info(self, *args, **kwargs):
65+
"""Mock reset_network_info."""
66+
67+
async def send_packet(self, *args, **kwargs):
68+
"""Mock send_packet."""
69+
6770
async def start_network(self, *args, **kwargs):
6871
"""Mock start_network."""
6972

@@ -73,8 +76,8 @@ async def write_network_info(self, *args, **kwargs):
7376
async def add_endpoint(self, descriptor):
7477
"""Mock add_endpoint."""
7578

76-
mrequest = CoroutineMock()
77-
request = CoroutineMock(return_value=(foundation.Status.SUCCESS, None))
79+
mrequest = AsyncMock()
80+
request = AsyncMock(return_value=(foundation.Status.SUCCESS, None))
7881

7982

8083
@pytest.fixture(name="MockAppController")

tests/test_kof.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010
import zhaquirks
1111
import zhaquirks.kof.kof_mr101z
1212

13-
from tests.conftest import CoroutineMock
14-
1513
zhaquirks.setup()
1614

1715
Default_Response = foundation.GENERAL_COMMANDS[
@@ -35,7 +33,7 @@ class TestCluster(
3533
}
3634
client_commands = {}
3735

38-
ep = CoroutineMock()
36+
ep = mock.AsyncMock()
3937
ep.device.application.get_sequence = mock.MagicMock(return_value=4)
4038

4139
cluster = TestCluster(ep)

tests/test_tuya.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
import zhaquirks.tuya.ts0601_trv
3232
import zhaquirks.tuya.ts0601_valve
3333

34-
from tests.common import ClusterListener, MockDatetime
34+
from tests.common import ClusterListener, MockDatetime, wait_for_zigpy_tasks
3535

3636
zhaquirks.setup()
3737

@@ -222,6 +222,7 @@ async def test_singleswitch_requests(zigpy_device_from_quirk, quirk):
222222
) as m1:
223223

224224
rsp = await switch_cluster.command(0x0000)
225+
await wait_for_zigpy_tasks()
225226
m1.assert_called_with(
226227
61184,
227228
2,
@@ -232,6 +233,7 @@ async def test_singleswitch_requests(zigpy_device_from_quirk, quirk):
232233
assert rsp.status == 0
233234

234235
rsp = await switch_cluster.command(0x0001)
236+
await wait_for_zigpy_tasks()
235237
m1.assert_called_with(
236238
61184,
237239
4,
@@ -242,6 +244,7 @@ async def test_singleswitch_requests(zigpy_device_from_quirk, quirk):
242244
assert rsp.status == 0
243245

244246
rsp = await switch_cluster.command(0x0002)
247+
await wait_for_zigpy_tasks()
245248
assert rsp.status == foundation.Status.UNSUP_CLUSTER_COMMAND
246249

247250

@@ -1221,6 +1224,7 @@ async def async_success(*args, **kwargs):
12211224

12221225
hdr, args = tuya_cluster.deserialize(ZCL_TUYA_SET_TIME_REQUEST)
12231226
tuya_cluster.handle_message(hdr, args)
1227+
await wait_for_zigpy_tasks()
12241228
m1.assert_called_with(
12251229
61184,
12261230
21,

tests/test_tuya_dimmer.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
import zhaquirks
99

10-
from tests.common import ClusterListener
10+
from tests.common import ClusterListener, wait_for_zigpy_tasks
1111

1212
zhaquirks.setup()
1313

@@ -31,6 +31,7 @@ async def test_command(zigpy_device_from_quirk, quirk):
3131
tuya_cluster.endpoint, "request", return_value=foundation.Status.SUCCESS
3232
) as m1:
3333
rsp = await switch2_cluster.command(0x0001)
34+
await wait_for_zigpy_tasks()
3435

3536
m1.assert_called_with(
3637
61184,
@@ -42,6 +43,7 @@ async def test_command(zigpy_device_from_quirk, quirk):
4243
assert rsp.status == foundation.Status.SUCCESS
4344

4445
rsp = await dimmer1_cluster.command(0x0000, 225)
46+
await wait_for_zigpy_tasks()
4547

4648
m1.assert_called_with(
4749
61184,
@@ -63,18 +65,16 @@ async def test_write_attr(zigpy_device_from_quirk, quirk):
6365
tuya_cluster = dimmer_dev.endpoints[1].tuya_manufacturer
6466
dimmer1_cluster = dimmer_dev.endpoints[1].level
6567

66-
async def async_success(*args, **kwargs):
67-
return foundation.Status.SUCCESS
68-
6968
with mock.patch.object(
70-
tuya_cluster.endpoint, "request", side_effect=async_success
69+
tuya_cluster.endpoint, "request", return_value=foundation.Status.SUCCESS
7170
) as m1:
7271

7372
(status,) = await dimmer1_cluster.write_attributes(
7473
{
7574
"minimum_level": 25,
7675
}
7776
)
77+
await wait_for_zigpy_tasks()
7878
m1.assert_called_with(
7979
61184,
8080
2,

tests/test_tuya_mcu.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,15 @@ async def test_tuya_methods(zigpy_device_from_quirk, quirk):
119119
tcd_dimmer2_on = TuyaClusterData(
120120
endpoint_id=2, cluster_attr="on_off", attr_value=1, expect_reply=True
121121
)
122+
tcd_dimmer2_off = TuyaClusterData(
123+
endpoint_id=2, cluster_attr="on_off", attr_value=0, expect_reply=True
124+
)
122125
tcd_dimmer2_level = TuyaClusterData(
123126
endpoint_id=2, cluster_attr="current_level", attr_value=75, expect_reply=True
124127
)
128+
tcd_dimmer2_level0 = TuyaClusterData(
129+
endpoint_id=2, cluster_attr="current_level", attr_value=0, expect_reply=True
130+
)
125131

126132
result_1 = tuya_cluster.from_cluster_data(tcd_1)
127133
assert result_1
@@ -158,12 +164,35 @@ async def test_tuya_methods(zigpy_device_from_quirk, quirk):
158164
assert m1.call_count == 1
159165

160166
# test `move_to_level_with_on_off` quirk (call on_off + current_level)
161-
rsp = await dimmer2_cluster.command(0x0004, 75, 1)
167+
rsp = await dimmer2_cluster.command(0x0004, 75)
162168
assert rsp.status == foundation.Status.SUCCESS
163169
m1.assert_any_call(tcd_dimmer2_on) # on_off
164170
m1.assert_called_with(tcd_dimmer2_level) # current_level
165171
assert m1.call_count == 3
166172

173+
# test `move_to_level_with_on_off` quirk (call on_off + current_level)
174+
rsp = await dimmer2_cluster.command(
175+
0x0004, 75, 0
176+
) # extra args ¿transition time?. Not on_off for sure
177+
assert rsp.status == foundation.Status.SUCCESS
178+
m1.assert_any_call(tcd_dimmer2_on) # on_off
179+
m1.assert_called_with(tcd_dimmer2_level) # current_level
180+
assert m1.call_count == 5
181+
182+
# test `move_to_level_with_on_off` quirk (call on_off + current_level)
183+
rsp = await dimmer2_cluster.command(0x0004, 0, level=75)
184+
assert rsp.status == foundation.Status.SUCCESS
185+
m1.assert_any_call(tcd_dimmer2_on) # on_off
186+
m1.assert_called_with(tcd_dimmer2_level) # current_level
187+
assert m1.call_count == 7
188+
189+
# test `move_to_level_with_on_off` quirk (call on_off + current_level)
190+
rsp = await dimmer2_cluster.command(0x0004)
191+
assert rsp.status == foundation.Status.SUCCESS
192+
m1.assert_any_call(tcd_dimmer2_off) # on_off
193+
m1.assert_called_with(tcd_dimmer2_level0) # current_level
194+
assert m1.call_count == 9
195+
167196

168197
async def test_tuya_mcu_classes():
169198
"""Test tuya conversion from Data to ztype and reverse."""

tests/test_tuya_valve.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@
77

88
import zhaquirks
99

10-
from tests.common import ClusterListener
10+
from tests.common import ClusterListener, wait_for_zigpy_tasks
1111

1212
zhaquirks.setup()
1313

1414

15+
@mock.patch("zhaquirks.tuya.mcu.EnchantedDevice.spell", mock.AsyncMock())
1516
@pytest.mark.parametrize("quirk", (zhaquirks.tuya.ts0601_valve.ParksidePSBZS,))
1617
async def test_command_psbzs(zigpy_device_from_quirk, quirk):
1718
"""Test executing cluster commands for PARKSIDE water valve."""
@@ -29,6 +30,7 @@ async def test_command_psbzs(zigpy_device_from_quirk, quirk):
2930
) as m1:
3031
rsp = await switch_cluster.command(0x0001)
3132

33+
await wait_for_zigpy_tasks()
3234
m1.assert_called_with(
3335
61184,
3436
2,
@@ -39,24 +41,23 @@ async def test_command_psbzs(zigpy_device_from_quirk, quirk):
3941
assert rsp.status == foundation.Status.SUCCESS
4042

4143

44+
@mock.patch("zhaquirks.tuya.mcu.EnchantedDevice.spell", mock.AsyncMock())
4245
@pytest.mark.parametrize("quirk", (zhaquirks.tuya.ts0601_valve.ParksidePSBZS,))
4346
async def test_write_attr_psbzs(zigpy_device_from_quirk, quirk):
4447
"""Test write cluster attributes for PARKSIDE water valve."""
4548

4649
water_valve_dev = zigpy_device_from_quirk(quirk)
4750
tuya_cluster = water_valve_dev.endpoints[1].tuya_manufacturer
4851

49-
async def async_success(*args, **kwargs):
50-
return foundation.Status.SUCCESS
51-
5252
with mock.patch.object(
53-
tuya_cluster.endpoint, "request", side_effect=async_success
53+
tuya_cluster.endpoint, "request", return_value=foundation.Status.SUCCESS
5454
) as m1:
5555
(status,) = await tuya_cluster.write_attributes(
5656
{
5757
"timer_duration": 15,
5858
}
5959
)
60+
await wait_for_zigpy_tasks()
6061
m1.assert_called_with(
6162
61184,
6263
2,
@@ -73,6 +74,7 @@ async def async_success(*args, **kwargs):
7374
"frost_lock_reset": 0,
7475
}
7576
)
77+
await wait_for_zigpy_tasks()
7678
m1.assert_called_with(
7779
61184,
7880
4,

tox.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[tox]
2-
envlist = py37, py38, lint, black
2+
envlist = py38, py39, py310, lint, black
33
skip_missing_interpreters = True
44

55
[testenv]

zhaquirks/tuya/__init__.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ async def command(
255255
manufacturer: Optional[Union[int, t.uint16_t]] = None,
256256
expect_reply: bool = True,
257257
tsn: Optional[Union[int, t.uint8_t]] = None,
258+
**kwargs: Any,
258259
):
259260
"""Override the default Cluster command."""
260261
self.debug("Setting the NO manufacturer id in command: %s", command_id)
@@ -264,6 +265,7 @@ async def command(
264265
manufacturer=foundation.ZCLHeader.NO_MANUFACTURER_ID,
265266
expect_reply=expect_reply,
266267
tsn=tsn,
268+
**kwargs,
267269
)
268270

269271

@@ -1280,6 +1282,7 @@ def command(
12801282
manufacturer: Optional[Union[int, t.uint16_t]] = None,
12811283
expect_reply: bool = True,
12821284
tsn: Optional[Union[int, t.uint8_t]] = None,
1285+
**kwargs: Any,
12831286
):
12841287
"""Override the default Cluster command."""
12851288
_LOGGER.debug(
@@ -1296,7 +1299,14 @@ def command(
12961299
cmd_payload.tsn = 0
12971300
cmd_payload.command_id = TUYA_LEVEL_COMMAND
12981301
cmd_payload.function = 0
1299-
brightness = (args[0] * 1000) // 255
1302+
1303+
if kwargs and "level" in kwargs:
1304+
level = kwargs["level"]
1305+
elif args:
1306+
level = args[0]
1307+
else:
1308+
level = 0
1309+
brightness = (level * 1000) // 255
13001310
val1 = brightness >> 8
13011311
val2 = brightness & 0xFF
13021312
cmd_payload.data = [4, 0, 0, val1, val2] # Custom Command

0 commit comments

Comments
 (0)