Skip to content

Commit

Permalink
Reset after NVRAM changes (#579)
Browse files Browse the repository at this point in the history
* Register new endpoints after starting up

* Register endpoints only after all resets have occurred

* Pass the network initialization enum introduced in v6

* Add a special case for the HUSBZB-1

* Update unit tests
  • Loading branch information
puddly authored Sep 4, 2023
1 parent a3435f6 commit 50636b7
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 33 deletions.
10 changes: 10 additions & 0 deletions bellows/ezsp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,16 @@ async def write_config(self, config: dict) -> None:
],
}

# If we are not joined to a network, old FWs crash if we grow the buffer
if self._ezsp_version < 7:
(state,) = await self.networkState()

if state != self.types.EmberNetworkStatus.JOINED_NETWORK:
LOGGER.debug("Skipping growing packet buffer, not on a network")
del ezsp_config[
self.types.EzspConfigId.CONFIG_PACKET_BUFFER_COUNT.name
]

# First, set the values
for cfg in ezsp_values.values():
# XXX: A read failure does not mean the value is not writeable!
Expand Down
30 changes: 22 additions & 8 deletions bellows/zigbee/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,10 @@ async def _ensure_network_running(self) -> bool:
return False

with self._ezsp.wait_for_stack_status(t.EmberStatus.NETWORK_UP) as stack_status:
(init_status,) = await self._ezsp.networkInit()
if self._ezsp.ezsp_version >= 6:
(init_status,) = await self._ezsp.networkInit(0x0000)
else:
(init_status,) = await self._ezsp.networkInitExtended(0x0000)

if init_status == t.EmberStatus.NOT_JOINED:
raise NetworkNotFormed("Node is not part of a network")
Expand All @@ -172,15 +175,14 @@ async def _ensure_network_running(self) -> bool:
async def start_network(self):
ezsp = self._ezsp

await self.register_endpoints()
await self._ensure_network_running()

if await repairs.fix_invalid_tclk_partner_ieee(ezsp):
# Reboot the stack after modifying NV3
ezsp.stop_ezsp()
await ezsp.startup_reset()
await self._reset()
await self._ensure_network_running()

await self.register_endpoints()

if self.config[zigpy.config.CONF_SOURCE_ROUTING]:
await ezsp.set_source_routing()

Expand Down Expand Up @@ -396,9 +398,12 @@ async def write_network_info(
await ezsp.write_custom_eui64(node_info.ieee, burn_into_userdata=True)
wrote_eui64 = True

# If we cannot write the new EUI64, don't mess up key entries with the unwritten
# EUI64 address
if not wrote_eui64:
if wrote_eui64:
# Reset after writing the EUI64, as it touches NVRAM
await self._reset()
else:
# If we cannot write the new EUI64, don't mess up key entries with the
# unwritten EUI64 address
node_info.ieee = current_eui64
network_info.tc_link_key.partner_ieee = current_eui64

Expand Down Expand Up @@ -455,6 +460,7 @@ async def write_network_info(
parameters.channels = t.Channels(network_info.channel_mask)

await ezsp.formNetwork(parameters)
await self._ensure_network_running()

async def reset_network_info(self):
# The network must be running before we can leave it
Expand All @@ -472,6 +478,14 @@ async def reset_network_info(self):
# Reset the custom EUI64
await self._ezsp.reset_custom_eui64()

# We must reset when NV3 has changed
await self._reset()

async def _reset(self):
self._ezsp.stop_ezsp()
await self._ezsp.startup_reset()
await self._ezsp.write_config(self.config[CONF_EZSP_CONFIG])

async def disconnect(self):
# TODO: how do you shut down the stack?
self.controller_event.clear()
Expand Down
6 changes: 6 additions & 0 deletions tests/test_application.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ async def mock_leave(*args, **kwargs):
ezsp_mock.addEndpoint = AsyncMock(return_value=t.EmberStatus.SUCCESS)
ezsp_mock.setConfigurationValue = AsyncMock(return_value=t.EmberStatus.SUCCESS)
ezsp_mock.networkInit = AsyncMock(return_value=[init])
ezsp_mock.networkInitExtended = AsyncMock(return_value=[init])
ezsp_mock.getNetworkParameters = AsyncMock(return_value=[0, nwk_type, nwk_params])
ezsp_mock.can_burn_userdata_custom_eui64 = AsyncMock(return_value=True)
ezsp_mock.can_rewrite_custom_eui64 = AsyncMock(return_value=True)
Expand Down Expand Up @@ -222,6 +223,11 @@ def form_network():
with p1, p2 as multicast_mock:
await app.startup(auto_form=auto_form)

if ezsp_version > 6:
assert ezsp_mock.networkInitExtended.call_count == 0
else:
assert ezsp_mock.networkInit.call_count == 0

assert ezsp_mock.write_config.call_count == 1
assert ezsp_mock.addEndpoint.call_count >= 2
assert multicast_mock.await_count == 1
Expand Down
60 changes: 35 additions & 25 deletions tests/test_ezsp.py
Original file line number Diff line number Diff line change
Expand Up @@ -735,33 +735,40 @@ async def test_config_initialize_husbzb1(ezsp_f):

ezsp_f.getConfigurationValue = AsyncMock(return_value=(t.EzspStatus.SUCCESS, 0))
ezsp_f.setConfigurationValue = AsyncMock(return_value=(t.EzspStatus.SUCCESS,))
ezsp_f.networkState = AsyncMock(return_value=(t.EmberNetworkStatus.JOINED_NETWORK,))

expected_calls = [
call(v4_t.EzspConfigId.CONFIG_SOURCE_ROUTE_TABLE_SIZE, 16),
call(v4_t.EzspConfigId.CONFIG_END_DEVICE_POLL_TIMEOUT, 60),
call(v4_t.EzspConfigId.CONFIG_END_DEVICE_POLL_TIMEOUT_SHIFT, 8),
call(v4_t.EzspConfigId.CONFIG_INDIRECT_TRANSMISSION_TIMEOUT, 7680),
call(v4_t.EzspConfigId.CONFIG_STACK_PROFILE, 2),
call(v4_t.EzspConfigId.CONFIG_SUPPORTED_NETWORKS, 1),
call(v4_t.EzspConfigId.CONFIG_MULTICAST_TABLE_SIZE, 16),
call(v4_t.EzspConfigId.CONFIG_TRUST_CENTER_ADDRESS_CACHE_SIZE, 2),
call(v4_t.EzspConfigId.CONFIG_SECURITY_LEVEL, 5),
call(v4_t.EzspConfigId.CONFIG_ADDRESS_TABLE_SIZE, 16),
call(v4_t.EzspConfigId.CONFIG_PAN_ID_CONFLICT_REPORT_THRESHOLD, 2),
call(v4_t.EzspConfigId.CONFIG_KEY_TABLE_SIZE, 4),
call(v4_t.EzspConfigId.CONFIG_MAX_END_DEVICE_CHILDREN, 32),
call(
v4_t.EzspConfigId.CONFIG_APPLICATION_ZDO_FLAGS,
(
v4_t.EmberZdoConfigurationFlags.APP_HANDLES_UNSUPPORTED_ZDO_REQUESTS
| v4_t.EmberZdoConfigurationFlags.APP_RECEIVES_SUPPORTED_ZDO_REQUESTS
),
),
call(v4_t.EzspConfigId.CONFIG_PACKET_BUFFER_COUNT, 255),
]

await ezsp_f.write_config({})
ezsp_f.setConfigurationValue.assert_has_calls(
[
call(v4_t.EzspConfigId.CONFIG_SOURCE_ROUTE_TABLE_SIZE, 16),
call(v4_t.EzspConfigId.CONFIG_END_DEVICE_POLL_TIMEOUT, 60),
call(v4_t.EzspConfigId.CONFIG_END_DEVICE_POLL_TIMEOUT_SHIFT, 8),
call(v4_t.EzspConfigId.CONFIG_INDIRECT_TRANSMISSION_TIMEOUT, 7680),
call(v4_t.EzspConfigId.CONFIG_STACK_PROFILE, 2),
call(v4_t.EzspConfigId.CONFIG_SUPPORTED_NETWORKS, 1),
call(v4_t.EzspConfigId.CONFIG_MULTICAST_TABLE_SIZE, 16),
call(v4_t.EzspConfigId.CONFIG_TRUST_CENTER_ADDRESS_CACHE_SIZE, 2),
call(v4_t.EzspConfigId.CONFIG_SECURITY_LEVEL, 5),
call(v4_t.EzspConfigId.CONFIG_ADDRESS_TABLE_SIZE, 16),
call(v4_t.EzspConfigId.CONFIG_PAN_ID_CONFLICT_REPORT_THRESHOLD, 2),
call(v4_t.EzspConfigId.CONFIG_KEY_TABLE_SIZE, 4),
call(v4_t.EzspConfigId.CONFIG_MAX_END_DEVICE_CHILDREN, 32),
call(
v4_t.EzspConfigId.CONFIG_APPLICATION_ZDO_FLAGS,
(
v4_t.EmberZdoConfigurationFlags.APP_HANDLES_UNSUPPORTED_ZDO_REQUESTS
| v4_t.EmberZdoConfigurationFlags.APP_RECEIVES_SUPPORTED_ZDO_REQUESTS
),
),
call(v4_t.EzspConfigId.CONFIG_PACKET_BUFFER_COUNT, 255),
]
)
assert ezsp_f.setConfigurationValue.mock_calls == expected_calls

# If there is no network, `CONFIG_PACKET_BUFFER_COUNT` won't be set
ezsp_f.setConfigurationValue.reset_mock()
ezsp_f.networkState = AsyncMock(return_value=(t.EmberNetworkStatus.NO_NETWORK,))
await ezsp_f.write_config({})
assert ezsp_f.setConfigurationValue.mock_calls == expected_calls[:-1]


@pytest.mark.parametrize("version", ezsp.EZSP._BY_VERSION)
Expand All @@ -777,6 +784,7 @@ async def test_config_initialize(version: int, ezsp_f, caplog):

ezsp_f.getConfigurationValue = AsyncMock(return_value=(t.EzspStatus.SUCCESS, 0))
ezsp_f.setConfigurationValue = AsyncMock(return_value=(t.EzspStatus.SUCCESS,))
ezsp_f.networkState = AsyncMock(return_value=(t.EmberNetworkStatus.JOINED_NETWORK,))

ezsp_f.setValue = AsyncMock(return_value=(t.EzspStatus.SUCCESS,))
ezsp_f.getValue = AsyncMock(return_value=(t.EzspStatus.SUCCESS, b"\xFF"))
Expand Down Expand Up @@ -815,6 +823,8 @@ async def test_config_initialize(version: int, ezsp_f, caplog):
async def test_cfg_initialize_skip(ezsp_f):
"""Test initialization."""

ezsp_f.networkState = AsyncMock(return_value=(t.EmberNetworkStatus.JOINED_NETWORK,))

p1 = patch.object(
ezsp_f,
"setConfigurationValue",
Expand Down

0 comments on commit 50636b7

Please sign in to comment.