Skip to content

Commit 418b4d6

Browse files
authored
Merge pull request #97 from puddly/puddly/new-radio-settings-api
Implement new zigpy radio API
2 parents 19df5c0 + 9dce57e commit 418b4d6

25 files changed

+629
-747
lines changed

.pre-commit-config.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ repos:
1515
- id: flake8
1616
entry: pflake8
1717
additional_dependencies:
18-
- pyproject-flake8==0.0.1a2
18+
- pyproject-flake8==0.0.1a3
1919
- flake8-bugbear==22.1.11
2020
- flake8-comprehensions==3.8.0
2121
- flake8_2020==1.6.1
@@ -29,11 +29,11 @@ repos:
2929
- id: isort
3030

3131
- repo: https://github.com/pre-commit/mirrors-mypy
32-
rev: v0.931
32+
rev: v0.942
3333
hooks:
3434
- id: mypy
3535
additional_dependencies:
36-
- zigpy==0.43.0
36+
- zigpy
3737

3838
- repo: https://github.com/asottile/pyupgrade
3939
rev: v2.31.0
@@ -43,4 +43,4 @@ repos:
4343
- repo: https://github.com/fsouza/autoflake8
4444
rev: v0.3.1
4545
hooks:
46-
- id: autoflake8
46+
- id: autoflake8

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ python_requires = >=3.7
1515
install_requires =
1616
pyserial-asyncio; platform_system!="Windows"
1717
pyserial-asyncio!=0.5; platform_system=="Windows" # 0.5 broke writes
18-
zigpy>=0.40.0
18+
zigpy>=0.47.0
1919
async_timeout
2020
voluptuous
2121
coloredlogs

tests/api/test_network_state.py

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import logging
2-
import dataclasses
32

43
import pytest
54

5+
import zigpy_znp.types as t
66
from zigpy_znp.types.nvids import ExNvIds, OsalNvIds
77

88
from ..conftest import (
@@ -31,16 +31,22 @@ async def test_state_transfer(from_device, to_device, make_connected_znp):
3131

3232
# Z-Stack 1 devices can't have some security info read out
3333
if issubclass(from_device, BaseZStack1CC2531):
34-
assert formed_znp.network_info == dataclasses.replace(
35-
empty_znp.network_info, stack_specific={}
34+
assert formed_znp.network_info == empty_znp.network_info.replace(
35+
stack_specific={},
36+
metadata=formed_znp.network_info.metadata,
3637
)
3738
elif issubclass(to_device, BaseZStack1CC2531):
3839
assert (
39-
dataclasses.replace(formed_znp.network_info, stack_specific={})
40+
formed_znp.network_info.replace(
41+
stack_specific={},
42+
metadata=empty_znp.network_info.metadata,
43+
)
4044
== empty_znp.network_info
4145
)
4246
else:
43-
assert formed_znp.network_info == empty_znp.network_info
47+
assert formed_znp.network_info == empty_znp.network_info.replace(
48+
metadata=formed_znp.network_info.metadata
49+
)
4450

4551
assert formed_znp.node_info == empty_znp.node_info
4652

@@ -59,3 +65,30 @@ async def test_broken_cc2531_load_state(device, make_connected_znp, caplog):
5965
assert "inconsistent" in caplog.text
6066

6167
znp.close()
68+
69+
70+
@pytest.mark.parametrize("device", [FormedZStack3CC2531])
71+
async def test_state_write_tclk_zstack3(device, make_connected_znp, caplog):
72+
formed_znp, _ = await make_connected_znp(server_cls=device)
73+
74+
await formed_znp.load_network_info()
75+
formed_znp.close()
76+
77+
empty_znp, _ = await make_connected_znp(server_cls=device)
78+
79+
caplog.set_level(logging.WARNING)
80+
await empty_znp.write_network_info(
81+
network_info=formed_znp.network_info.replace(
82+
tc_link_key=formed_znp.network_info.tc_link_key.replace(
83+
# Non-standard TCLK
84+
key=t.KeyData.convert("AA:BB:CC:DD:AA:BB:CC:DD:AA:BB:CC:DD:AA:BB:CC:DD")
85+
)
86+
),
87+
node_info=formed_znp.node_info,
88+
)
89+
assert "TC link key is configured at build time in Z-Stack 3" in caplog.text
90+
91+
await empty_znp.load_network_info()
92+
93+
# TCLK was not changed
94+
assert formed_znp.network_info == empty_znp.network_info

tests/application/test_nvram_migration.py renamed to tests/api/test_nvram_migration.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ async def test_addrmgr_empty_entries(make_connected_znp, device):
4444

4545

4646
@pytest.mark.parametrize("device", [FormedZStack3CC2531])
47-
async def test_addrmgr_rewrite_fix(device, make_application, mocker):
47+
async def test_addrmgr_rewrite_fix(device, make_connected_znp):
4848
# Keep track of reads
4949
addrmgr_reads = []
5050

@@ -60,7 +60,7 @@ async def test_addrmgr_rewrite_fix(device, make_application, mocker):
6060
extAddr=t.EUI64.convert("FF:FF:FF:FF:FF:FF:FF:FF"),
6161
)
6262

63-
app, znp_server = make_application(server_cls=device)
63+
znp, znp_server = await make_connected_znp(server_cls=device)
6464
znp_server.callback_for_response(
6565
c.SYS.OSALNVReadExt.Req(Id=OsalNvIds.ADDRMGR, Offset=0), addrmgr_reads.append
6666
)
@@ -81,8 +81,7 @@ async def test_addrmgr_rewrite_fix(device, make_application, mocker):
8181
assert old_addrmgr != nvram[OsalNvIds.ADDRMGR]
8282

8383
assert len(addrmgr_reads) == 0
84-
await app.startup()
85-
await app.shutdown()
84+
await znp.migrate_nvram()
8685
assert len(addrmgr_reads) == 2
8786

8887
# Bad entries have been fixed
@@ -94,8 +93,7 @@ async def test_addrmgr_rewrite_fix(device, make_application, mocker):
9493

9594
# Will not be read again
9695
assert len(addrmgr_reads) == 2
97-
await app.startup()
98-
await app.shutdown()
96+
await znp.migrate_nvram()
9997
assert len(addrmgr_reads) == 2
10098

10199
# Will be migrated again if the migration NVID is deleted
@@ -104,8 +102,7 @@ async def test_addrmgr_rewrite_fix(device, make_application, mocker):
104102
old_addrmgr2 = nvram[OsalNvIds.ADDRMGR]
105103

106104
assert len(addrmgr_reads) == 2
107-
await app.startup()
108-
await app.shutdown()
105+
await znp.migrate_nvram()
109106
assert len(addrmgr_reads) == 3
110107

111108
# But nothing will change

tests/application/test_connect.py

Lines changed: 87 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import asyncio
2+
from unittest.mock import patch
23

34
import pytest
45

56
import zigpy_znp.config as conf
67
from zigpy_znp.uart import connect as uart_connect
78
from zigpy_znp.zigbee.application import ControllerApplication
89

9-
from ..conftest import FORMED_DEVICES, FormedLaunchpadCC26X2R1, swap_attribute
10+
from ..conftest import FORMED_DEVICES, FormedLaunchpadCC26X2R1
1011

1112

1213
async def test_no_double_connect(make_znp_server, mocker):
@@ -57,7 +58,7 @@ async def test_probe_unsuccessful():
5758

5859

5960
@pytest.mark.parametrize("device", FORMED_DEVICES)
60-
async def test_probe_unsuccessful_slow(device, make_znp_server, mocker):
61+
async def test_probe_unsuccessful_slow1(device, make_znp_server, mocker):
6162
znp_server = make_znp_server(server_cls=device, shorten_delays=False)
6263

6364
# Don't respond to anything
@@ -74,6 +75,24 @@ async def test_probe_unsuccessful_slow(device, make_znp_server, mocker):
7475
assert not any([t._is_connected for t in znp_server._transports])
7576

7677

78+
@pytest.mark.parametrize("device", FORMED_DEVICES)
79+
async def test_probe_unsuccessful_slow2(device, make_znp_server, mocker):
80+
znp_server = make_znp_server(server_cls=device, shorten_delays=False)
81+
82+
# Don't respond to anything
83+
znp_server._listeners.clear()
84+
85+
mocker.patch("zigpy_znp.zigbee.application.PROBE_TIMEOUT", new=0.1)
86+
87+
assert not (
88+
await ControllerApplication.probe(
89+
conf.SCHEMA_DEVICE({conf.CONF_DEVICE_PATH: znp_server.serial_port})
90+
)
91+
)
92+
93+
assert not any([t._is_connected for t in znp_server._transports])
94+
95+
7796
@pytest.mark.parametrize("device", FORMED_DEVICES)
7897
async def test_probe_successful(device, make_znp_server):
7998
znp_server = make_znp_server(server_cls=device, shorten_delays=False)
@@ -100,8 +119,8 @@ async def test_probe_multiple(device, make_znp_server):
100119

101120

102121
@pytest.mark.parametrize("device", FORMED_DEVICES)
103-
async def test_reconnect(device, event_loop, make_application):
104-
app, znp_server = make_application(
122+
async def test_reconnect(device, make_application):
123+
app, znp_server = await make_application(
105124
server_cls=device,
106125
client_config={
107126
# Make auto-reconnection happen really fast
@@ -118,7 +137,7 @@ async def test_reconnect(device, event_loop, make_application):
118137
assert app._znp is not None
119138

120139
# Don't reply to anything for a bit
121-
with swap_attribute(znp_server, "frame_received", lambda _: None):
140+
with patch.object(znp_server, "frame_received", lambda _: None):
122141
# Now that we're connected, have the server close the connection
123142
znp_server._uart._transport.close()
124143

@@ -143,7 +162,7 @@ async def test_reconnect(device, event_loop, make_application):
143162

144163
@pytest.mark.parametrize("device", FORMED_DEVICES)
145164
async def test_shutdown_from_app(device, mocker, make_application):
146-
app, znp_server = make_application(server_cls=device)
165+
app, znp_server = await make_application(server_cls=device)
147166

148167
await app.startup(auto_form=False)
149168

@@ -159,7 +178,7 @@ async def test_shutdown_from_app(device, mocker, make_application):
159178

160179

161180
async def test_clean_shutdown(make_application):
162-
app, znp_server = make_application(server_cls=FormedLaunchpadCC26X2R1)
181+
app, znp_server = await make_application(server_cls=FormedLaunchpadCC26X2R1)
163182
await app.startup(auto_form=False)
164183

165184
# This should not throw
@@ -170,7 +189,7 @@ async def test_clean_shutdown(make_application):
170189

171190

172191
async def test_multiple_shutdown(make_application):
173-
app, znp_server = make_application(server_cls=FormedLaunchpadCC26X2R1)
192+
app, znp_server = await make_application(server_cls=FormedLaunchpadCC26X2R1)
174193
await app.startup(auto_form=False)
175194

176195
await app.shutdown()
@@ -179,10 +198,10 @@ async def test_multiple_shutdown(make_application):
179198

180199

181200
@pytest.mark.parametrize("device", FORMED_DEVICES)
182-
async def test_reconnect_lockup(device, event_loop, make_application, mocker):
201+
async def test_reconnect_lockup(device, make_application, mocker):
183202
mocker.patch("zigpy_znp.zigbee.application.WATCHDOG_PERIOD", 0.1)
184203

185-
app, znp_server = make_application(
204+
app, znp_server = await make_application(
186205
server_cls=device,
187206
client_config={
188207
# Make auto-reconnection happen really fast
@@ -197,7 +216,7 @@ async def test_reconnect_lockup(device, event_loop, make_application, mocker):
197216
await app.startup(auto_form=False)
198217

199218
# Stop responding
200-
with swap_attribute(znp_server, "frame_received", lambda _: None):
219+
with patch.object(znp_server, "frame_received", lambda _: None):
201220
assert app._znp is not None
202221
assert app._reconnect_task.done()
203222

@@ -219,15 +238,16 @@ async def test_reconnect_lockup(device, event_loop, make_application, mocker):
219238
await app.shutdown()
220239

221240

222-
@pytest.mark.parametrize("device", FORMED_DEVICES)
223-
async def test_reconnect_lockup_pyserial(device, event_loop, make_application, mocker):
241+
@pytest.mark.parametrize("device", [FormedLaunchpadCC26X2R1])
242+
async def test_reconnect_lockup_pyserial(device, make_application, mocker):
224243
mocker.patch("zigpy_znp.zigbee.application.WATCHDOG_PERIOD", 0.1)
225244

226-
app, znp_server = make_application(
245+
app, znp_server = await make_application(
227246
server_cls=device,
228247
client_config={
229248
conf.CONF_ZNP_CONFIG: {
230-
conf.CONF_AUTO_RECONNECT_RETRY_DELAY: 0.1,
249+
conf.CONF_AUTO_RECONNECT_RETRY_DELAY: 0.01,
250+
conf.CONF_SREQ_TIMEOUT: 0.1,
231251
}
232252
},
233253
)
@@ -242,20 +262,20 @@ async def test_reconnect_lockup_pyserial(device, event_loop, make_application, m
242262
# We are connected
243263
assert app._znp is not None
244264

245-
did_load_info = asyncio.get_running_loop().create_future()
265+
did_start_network = asyncio.get_running_loop().create_future()
246266

247-
async def patched_load_network_info(*, old_load=app.load_network_info):
267+
async def patched_start_network(old_start_network=app.start_network, **kwargs):
248268
try:
249-
return await old_load()
269+
return await old_start_network(**kwargs)
250270
finally:
251-
did_load_info.set_result(True)
271+
did_start_network.set_result(True)
252272

253-
with swap_attribute(app, "load_network_info", patched_load_network_info):
273+
with patch.object(app, "start_network", patched_start_network):
254274
# "Drop" the connection like PySerial
255275
app._znp._uart.connection_lost(exc=None)
256276

257277
# Wait until we are reconnecting
258-
await did_load_info
278+
await did_start_network
259279

260280
# "Drop" the connection like PySerial again, but during connect
261281
app._znp._uart.connection_lost(exc=None)
@@ -269,3 +289,49 @@ async def patched_load_network_info(*, old_load=app.load_network_info):
269289
assert app._znp and app._znp._uart
270290

271291
await app.shutdown()
292+
293+
294+
@pytest.mark.parametrize("device", [FormedLaunchpadCC26X2R1])
295+
async def test_disconnect(device, make_application):
296+
app, znp_server = await make_application(
297+
server_cls=device,
298+
client_config={
299+
conf.CONF_ZNP_CONFIG: {
300+
conf.CONF_SREQ_TIMEOUT: 0.1,
301+
}
302+
},
303+
)
304+
305+
assert app._znp is None
306+
await app.connect()
307+
308+
assert app._znp is not None
309+
310+
await app.disconnect()
311+
assert app._znp is None
312+
313+
await app.disconnect()
314+
await app.disconnect()
315+
316+
317+
@pytest.mark.parametrize("device", [FormedLaunchpadCC26X2R1])
318+
async def test_disconnect_failure(device, make_application):
319+
app, znp_server = await make_application(
320+
server_cls=device,
321+
client_config={
322+
conf.CONF_ZNP_CONFIG: {
323+
conf.CONF_SREQ_TIMEOUT: 0.1,
324+
}
325+
},
326+
)
327+
328+
assert app._znp is None
329+
await app.connect()
330+
331+
assert app._znp is not None
332+
333+
with patch.object(app._znp, "reset", side_effect=RuntimeError("An error")):
334+
# Runs without error
335+
await app.disconnect()
336+
337+
assert app._znp is None

0 commit comments

Comments
 (0)