diff --git a/bellows/ezsp/__init__.py b/bellows/ezsp/__init__.py index 9b4521b8..32761829 100644 --- a/bellows/ezsp/__init__.py +++ b/bellows/ezsp/__init__.py @@ -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! diff --git a/bellows/zigbee/application.py b/bellows/zigbee/application.py index 86e13f50..5d33e9dd 100644 --- a/bellows/zigbee/application.py +++ b/bellows/zigbee/application.py @@ -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") @@ -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() @@ -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 @@ -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 @@ -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() diff --git a/tests/test_application.py b/tests/test_application.py index b5ed389a..e95bfa98 100644 --- a/tests/test_application.py +++ b/tests/test_application.py @@ -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) @@ -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 diff --git a/tests/test_ezsp.py b/tests/test_ezsp.py index 15c18218..c2a59388 100644 --- a/tests/test_ezsp.py +++ b/tests/test_ezsp.py @@ -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) @@ -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")) @@ -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",