Skip to content

Commit 048965a

Browse files
authored
Ensure network reset and new network formation works on old firmwares (#272)
Ensure network reset and new network formation works even on old firmwares
1 parent de71047 commit 048965a

File tree

3 files changed

+118
-14
lines changed

3 files changed

+118
-14
lines changed

tests/test_application.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -556,7 +556,7 @@ async def test_reset_network_info(app):
556556
app.form_network = AsyncMock()
557557
await app.reset_network_info()
558558

559-
app.form_network.assert_called_once()
559+
app.form_network.assert_called_once_with(fast=True)
560560

561561

562562
async def test_energy_scan_conbee_2(app):

tests/test_network_state.py

Lines changed: 103 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
"""Test `load_network_info` and `write_network_info` methods."""
22

33
import importlib.metadata
4+
from unittest.mock import ANY, call
45

56
import pytest
6-
from zigpy.exceptions import ControllerException, NetworkNotFormed
7+
from zigpy.exceptions import CannotWriteNetworkSettings, NetworkNotFormed
78
import zigpy.state as app_state
89
import zigpy.types as t
910
import zigpy.zdo.types as zdo_t
@@ -70,37 +71,74 @@ def network_info(node_info):
7071
@patch.object(application, "CHANGE_NETWORK_POLL_TIME", 0.001)
7172
@patch.object(application, "CHANGE_NETWORK_STATE_DELAY", 0.001)
7273
@pytest.mark.parametrize(
73-
"channel_mask, channel, security_level, fw_supports_fc, logical_type",
74+
(
75+
"channel_mask",
76+
"channel",
77+
"security_level",
78+
"fw_supports_fc",
79+
"logical_type",
80+
"tx_counter",
81+
"should_error",
82+
),
7483
[
84+
# FW supports frame counter
7585
(
7686
t.Channels.from_channel_list([15]),
7787
15,
7888
0,
7989
True,
8090
zdo_t.LogicalType.Coordinator,
91+
39009277,
92+
False,
93+
),
94+
# FW doesn't support but we're writing blank settings (tx_counter == 0)
95+
(
96+
t.Channels.from_channel_list([15]),
97+
15,
98+
5,
99+
False,
100+
zdo_t.LogicalType.Coordinator,
101+
0,
102+
False,
81103
),
104+
# FW doesn't support and we're writing real settings (should error)
82105
(
83106
t.Channels.from_channel_list([15]),
84107
15,
85108
0,
86109
False,
87110
zdo_t.LogicalType.Coordinator,
111+
39009277,
112+
True,
88113
),
114+
# Additional test cases with FW support
89115
(
90116
t.Channels.from_channel_list([15, 20]),
91117
15,
92118
5,
93119
True,
94120
zdo_t.LogicalType.Coordinator,
121+
39009277,
122+
False,
95123
),
96124
(
97125
t.Channels.from_channel_list([15, 20, 25]),
98126
None,
99127
5,
100128
True,
101129
zdo_t.LogicalType.Router,
130+
39009277,
131+
False,
132+
),
133+
(
134+
None,
135+
15,
136+
5,
137+
True,
138+
zdo_t.LogicalType.Coordinator,
139+
39009277,
140+
False,
102141
),
103-
(None, 15, 5, True, zdo_t.LogicalType.Coordinator),
104142
],
105143
)
106144
async def test_write_network_info(
@@ -112,6 +150,8 @@ async def test_write_network_info(
112150
security_level,
113151
fw_supports_fc,
114152
logical_type,
153+
tx_counter,
154+
should_error,
115155
):
116156
"""Test that network info is correctly written."""
117157

@@ -137,13 +177,14 @@ async def write_parameter(param, *args):
137177
channel=channel,
138178
channel_mask=channel_mask,
139179
security_level=security_level,
180+
network_key=network_info.network_key.replace(tx_counter=tx_counter),
140181
)
141182

142183
node_info = node_info.replace(logical_type=logical_type)
143184

144-
if not fw_supports_fc:
185+
if should_error:
145186
with pytest.raises(
146-
ControllerException,
187+
CannotWriteNetworkSettings,
147188
match=(
148189
"Please upgrade your adapter firmware. Firmware version 0x26580700 does"
149190
" not support writing the network key frame counter, which is required"
@@ -166,7 +207,9 @@ async def write_parameter(param, *args):
166207
for call in app._api.write_parameter.await_args_list
167208
}
168209

169-
assert params["nwk_frame_counter"] == (network_info.network_key.tx_counter,)
210+
# Only check frame counter if firmware supports it
211+
if fw_supports_fc:
212+
assert params["nwk_frame_counter"] == (network_info.network_key.tx_counter,)
170213

171214
if node_info.logical_type == zdo_t.LogicalType.Coordinator:
172215
assert params["aps_designed_coordinator"] == (1,)
@@ -328,3 +371,57 @@ async def read_param(param, *args):
328371
assert app.state.network_info == network_info
329372

330373
assert app.state.node_info == node_info.replace(**node_state_changes)
374+
375+
376+
@patch.object(application, "CHANGE_NETWORK_POLL_TIME", 0.001)
377+
@patch.object(application, "CHANGE_NETWORK_STATE_DELAY", 0.001)
378+
async def test_form_network_fast_without_frame_counter_support(app): # noqa: F811
379+
"""Test that form_network(fast=True) works when FW doesn't support frame counter."""
380+
381+
async def write_parameter(param, *args):
382+
if param == zigpy_deconz.api.NetworkParameter.nwk_frame_counter:
383+
raise zigpy_deconz.exception.CommandError(
384+
"Command is unsupported",
385+
status=zigpy_deconz.api.Status.UNSUPPORTED,
386+
command=None,
387+
)
388+
389+
app._change_network_state = AsyncMock()
390+
app._api.write_parameter = AsyncMock(side_effect=write_parameter)
391+
app.backups = AsyncMock()
392+
app.backups.restore_backup = AsyncMock()
393+
394+
# This should not raise an error because fast=True sets form_quickly
395+
await app.form_network(fast=True)
396+
397+
# Verify that restore_backup was called with create_new=False (due to fast=True)
398+
assert app.backups.restore_backup.mock_calls == [
399+
call(backup=ANY, counter_increment=0, allow_incomplete=True, create_new=False)
400+
]
401+
402+
403+
@patch.object(application, "CHANGE_NETWORK_POLL_TIME", 0.001)
404+
@patch.object(application, "CHANGE_NETWORK_STATE_DELAY", 0.001)
405+
async def test_reset_network_info_without_frame_counter_support(app): # noqa: F811
406+
"""Test that reset_network_info works even when FW doesn't support frame counter."""
407+
408+
async def write_parameter(param, *args):
409+
if param == zigpy_deconz.api.NetworkParameter.nwk_frame_counter:
410+
raise zigpy_deconz.exception.CommandError(
411+
"Command is unsupported",
412+
status=zigpy_deconz.api.Status.UNSUPPORTED,
413+
command=None,
414+
)
415+
416+
app._change_network_state = AsyncMock()
417+
app._api.write_parameter = AsyncMock(side_effect=write_parameter)
418+
app.backups = AsyncMock()
419+
app.backups.restore_backup = AsyncMock()
420+
421+
# Should not raise an error because reset_network_info calls form_network(fast=True)
422+
await app.reset_network_info()
423+
424+
# Verify that restore_backup was called once (via form_network)
425+
assert app.backups.restore_backup.mock_calls == [
426+
call(backup=ANY, counter_increment=0, allow_incomplete=True, create_new=False)
427+
]

zigpy_deconz/zigbee/application.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ async def change_loop():
180180

181181
async def reset_network_info(self):
182182
# TODO: There does not appear to be a way to factory reset a Conbee
183-
await self.form_network()
183+
await self.form_network(fast=True)
184184

185185
async def write_network_info(self, *, network_info, node_info):
186186
try:
@@ -189,12 +189,19 @@ async def write_network_info(self, *, network_info, node_info):
189189
)
190190
except zigpy_deconz.exception.CommandError as ex:
191191
assert ex.status == Status.UNSUPPORTED
192-
fw_version = f"{int(self._api.firmware_version):#010x}"
193-
raise zigpy.exceptions.ControllerException(
194-
f"Please upgrade your adapter firmware. Firmware version {fw_version}"
195-
f" does not support writing the network key frame counter, which is"
196-
f" required for migration to succeed."
197-
)
192+
193+
# If we are resetting the adapter or forming a brand new network, we can
194+
# skip this check
195+
if not (
196+
network_info.stack_specific.get("form_quickly", False)
197+
or network_info.network_key.tx_counter == 0
198+
):
199+
fw_version = f"{int(self._api.firmware_version):#010x}"
200+
raise zigpy.exceptions.CannotWriteNetworkSettings(
201+
f"Please upgrade your adapter firmware. Firmware version"
202+
f" {fw_version} does not support writing the network key frame"
203+
f" counter, which is required for migration to succeed."
204+
)
198205

199206
if node_info.logical_type == zdo_t.LogicalType.Coordinator:
200207
await self._api.write_parameter(

0 commit comments

Comments
 (0)