Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reset after NVRAM changes #579

Merged
merged 5 commits into from
Sep 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -718,33 +718,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 @@ -760,6 +767,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 @@ -798,6 +806,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
Loading