From d0853f663b8e40bfec31ab7d105f4e634be237ce Mon Sep 17 00:00:00 2001 From: puddly <32534428+puddly@users.noreply.github.com> Date: Tue, 15 Apr 2025 12:05:31 -0400 Subject: [PATCH 1/2] Fail adapter migration if the IEEE address cannot be written --- bellows/zigbee/application.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/bellows/zigbee/application.py b/bellows/zigbee/application.py index 692804e2..e3997d01 100644 --- a/bellows/zigbee/application.py +++ b/bellows/zigbee/application.py @@ -364,15 +364,18 @@ async def write_network_info( elif not stack_specific.get( "i_understand_i_can_update_eui64_only_once_and_i_still_want_to_do_it" ): - LOGGER.warning( - "Current node's IEEE address (%s) does not match the backup's (%s)", - current_eui64, - node_info.ieee, + _, _, version = await self._get_board_info() + raise ControllerError( + f"Please upgrade your adapter firmware. The adapter IEEE address" + f" needs to be replaced and firmware {version!r} does not support" + f" writing it multiple times." ) elif not await ezsp.can_burn_userdata_custom_eui64(): - LOGGER.error( - "Current node's IEEE address has already been written once. It" - " cannot be written again without fully erasing the chip with JTAG." + _, _, version = await self._get_board_info() + raise ControllerError( + f"Please upgrade your adapter firmware. The adapter IEEE address" + f" has been overwritten and firmware {version!r} does not support" + f" writing it a second time." ) else: await ezsp.write_custom_eui64(node_info.ieee, burn_into_userdata=True) From da852dea08f4f19e7b25fe4e71f530ccf96783fa Mon Sep 17 00:00:00 2001 From: puddly <32534428+puddly@users.noreply.github.com> Date: Tue, 15 Apr 2025 12:22:53 -0400 Subject: [PATCH 2/2] Add a unit test --- tests/test_application.py | 75 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/tests/test_application.py b/tests/test_application.py index 8319b093..30475695 100644 --- a/tests/test_application.py +++ b/tests/test_application.py @@ -2155,3 +2155,78 @@ async def test_packet_capture_failure(app: ControllerApplication) -> None: with pytest.raises(zigpy.exceptions.ControllerException): async for packet in app.packet_capture(channel=15): pass + + +async def test_migration_failure_eui64_overwrite_confirmation( + app: ControllerApplication, + zigpy_backup: zigpy.backups.NetworkBackup, +) -> None: + app._ezsp.can_rewrite_custom_eui64 = AsyncMock(return_value=False) + + # Migration explicitly fails if we need to write the EUI64 but the adapter treats it + # as a write-once operation + with pytest.raises( + zigpy.exceptions.ControllerException, + match=( + "Please upgrade your adapter firmware. The adapter IEEE address needs to be" + " replaced and firmware 'Mock version' does not support writing it multiple" + " times." + ), + ): + with patch.object(app, "_reset"): + await app.write_network_info( + node_info=zigpy_backup.node_info.replace( + ieee=t.EUI64.convert("aa:aa:aa:aa:aa:aa:aa:aa") + ), + network_info=zigpy_backup.network_info, + ) + + # Even if we opt in, if the adapter doesn't support it, we can't do anything + with patch.object(app, "_reset"), patch.object( + app._ezsp, "write_custom_eui64", wraps=app._ezsp.write_custom_eui64 + ), patch.object(app._ezsp, "can_burn_userdata_custom_eui64", return_value=False): + with pytest.raises( + zigpy.exceptions.ControllerException, + match=( + "Please upgrade your adapter firmware. The adapter IEEE address has" + " been overwritten and firmware 'Mock version' does not support writing" + " it a second time." + ), + ): + await app.write_network_info( + node_info=zigpy_backup.node_info.replace( + ieee=t.EUI64.convert("aa:aa:aa:aa:aa:aa:aa:aa") + ), + network_info=zigpy_backup.network_info.replace( + stack_specific={ + "ezsp": { + **zigpy_backup.network_info.stack_specific["ezsp"], + "i_understand_i_can_update_eui64_only_once_and_i_still_want_to_do_it": True, + } + } + ), + ) + + assert app._ezsp.write_custom_eui64.mock_calls == [] + + # It works if everything is correct + with patch.object(app, "_reset"), patch.object( + app._ezsp, "write_custom_eui64" + ), patch.object(app._ezsp, "can_burn_userdata_custom_eui64", return_value=True): + await app.write_network_info( + node_info=zigpy_backup.node_info.replace( + ieee=t.EUI64.convert("aa:aa:aa:aa:aa:aa:aa:aa") + ), + network_info=zigpy_backup.network_info.replace( + stack_specific={ + "ezsp": { + **zigpy_backup.network_info.stack_specific["ezsp"], + "i_understand_i_can_update_eui64_only_once_and_i_still_want_to_do_it": True, + } + } + ), + ) + + assert app._ezsp.write_custom_eui64.mock_calls == [ + call(t.EUI64.convert("aa:aa:aa:aa:aa:aa:aa:aa"), burn_into_userdata=True) + ]