From ddc25f0905afb53a74934b8f2eafd7e326918208 Mon Sep 17 00:00:00 2001 From: zigpy-review-bot <286747149+zigpy-review-bot@users.noreply.github.com> Date: Sun, 13 Sep 2026 18:24:03 +0200 Subject: [PATCH 1/3] Fix EZSP v14+ `leaveNetwork` and `setManufacturerCode` schemas Since EZSP v14, `leaveNetwork` takes a one-byte `options` argument and `setManufacturerCode` returns an `sl_status_t`. The v14 table inherited both v4-era layouts, so bellows sent `leaveNetwork` without the byte the NCP reads and left the `setManufacturerCode` status as trailing data. --- bellows/ezsp/__init__.py | 7 ++++++- bellows/ezsp/v14/commands.py | 20 +++++++++++++++++++ bellows/types/named.py | 8 ++++++++ tests/test_ezsp.py | 19 ++++++++++++++++++ tests/test_ezsp_v14.py | 38 ++++++++++++++++++++++++++++++++++++ 5 files changed, 91 insertions(+), 1 deletion(-) diff --git a/bellows/ezsp/__init__.py b/bellows/ezsp/__init__.py index a158018e3..39f6534f0 100644 --- a/bellows/ezsp/__init__.py +++ b/bellows/ezsp/__init__.py @@ -321,7 +321,12 @@ async def leaveNetwork(self, timeout: float | int = NETWORK_OPS_TIMEOUT) -> None stack_status = asyncio.Future() with self.wait_for_stack_status(t.sl_Status.NETWORK_DOWN) as stack_status: - (status,) = await self._command("leaveNetwork") + if self._ezsp_version >= 14: + (status,) = await self._command( + "leaveNetwork", options=t.SlZigbeeLeaveNetworkOption.WITH_NO_OPTION + ) + else: + (status,) = await self._command("leaveNetwork") if status != t.sl_Status.OK: raise EzspError(f"failed to leave network: {status.name}") diff --git a/bellows/ezsp/v14/commands.py b/bellows/ezsp/v14/commands.py index 0994e3411..c3e774ec1 100644 --- a/bellows/ezsp/v14/commands.py +++ b/bellows/ezsp/v14/commands.py @@ -161,6 +161,26 @@ class GetTokenDataRsp(Struct): "status": t.EmberStatus, }, ), + # Gained an `options` argument, which the NCP reads even if we don't send it + "leaveNetwork": ( + 0x0020, + { + "options": t.SlZigbeeLeaveNetworkOption, + }, + { + "status": t.sl_Status, + }, + ), + # Gained an `sl_status_t` response that `_REPLACEMENTS` can't add for us + "setManufacturerCode": ( + 0x0015, + { + "code": t.uint16_t, + }, + { + "status": t.sl_Status, + }, + ), } diff --git a/bellows/types/named.py b/bellows/types/named.py index 2b7f940fb..f8216bfd7 100644 --- a/bellows/types/named.py +++ b/bellows/types/named.py @@ -2889,6 +2889,14 @@ class RouteRecordConcentratortype(basic.enum8): HIGH_RAM = 2 +class SlZigbeeLeaveNetworkOption(basic.bitmap8): + """Options for leaving the network (EZSP v14+).""" + + WITH_NO_OPTION = 0x00 + WITH_OPTION_REJOIN = 0x20 + IS_REQUESTED = 0x40 + + class SlZigbeeDhcPaAlgorithm(basic.enum8): """Dynamic Hardware Configuration PA descriptor algorithm.""" diff --git a/tests/test_ezsp.py b/tests/test_ezsp.py index e2e26e1d0..de52b3457 100644 --- a/tests/test_ezsp.py +++ b/tests/test_ezsp.py @@ -479,6 +479,25 @@ async def _mock_cmd(*args, **kwargs): cmd_mock.side_effect = _mock_cmd await ezsp_f.leaveNetwork(timeout=0.01) + assert cmd_mock.mock_calls == [call("leaveNetwork")] + + +async def test_leave_network_v14(ezsp_f): + """Test EZSP leaveNetwork command sends the `options` argument on EZSPv14+.""" + ezsp_f._ezsp_version = 14 + + async def _mock_cmd(*args, **kwargs): + ezsp_f.handle_callback("stackStatusHandler", [t.sl_Status.NETWORK_DOWN]) + return [t.sl_Status.OK] + + with patch.object(ezsp_f, "_command", new_callable=AsyncMock) as cmd_mock: + cmd_mock.side_effect = _mock_cmd + await ezsp_f.leaveNetwork(timeout=0.01) + + assert cmd_mock.mock_calls == [ + call("leaveNetwork", options=t.SlZigbeeLeaveNetworkOption.WITH_NO_OPTION) + ] + async def test_xncp_token_override(ezsp_f): ezsp_f.getMfgToken = AsyncMock(return_value=[b"firmware value"]) diff --git a/tests/test_ezsp_v14.py b/tests/test_ezsp_v14.py index 49bf152b4..86b89f205 100644 --- a/tests/test_ezsp_v14.py +++ b/tests/test_ezsp_v14.py @@ -4,6 +4,7 @@ import zigpy.exceptions import zigpy.state +import bellows.ezsp import bellows.ezsp.v14 import bellows.types as t @@ -33,6 +34,43 @@ def test_ezsp_frame_rx(ezsp_f): assert ezsp_f._handle_callback.call_args[0][1] == [0x01, 0x02, 0x1234] +@pytest.mark.parametrize( + "version", + [v for v in bellows.ezsp.EZSP._BY_VERSION if v >= 14], +) +def test_leave_network_tx_schema(version: int) -> None: + """`leaveNetwork` sends its one-byte `options` argument on v14 and newer.""" + ezsp = bellows.ezsp.EZSP._BY_VERSION[version](MagicMock(), MagicMock()) + ezsp._seq = 0x22 + + data = ezsp._ezsp_frame( + "leaveNetwork", options=t.SlZigbeeLeaveNetworkOption.WITH_NO_OPTION + ) + assert data == b"\x22\x00\x01\x20\x00" + b"\x00" + + +@pytest.mark.parametrize( + "version", + [v for v in bellows.ezsp.EZSP._BY_VERSION if v >= 14], +) +@pytest.mark.parametrize( + ("command", "data", "expected"), + [ + ("leaveNetwork", b"\x00\x00\x00\x00", [t.sl_Status.OK]), + ("setManufacturerCode", b"\x00\x00\x00\x00", [t.sl_Status.OK]), + ], +) +def test_status_rx_schemas( + version: int, command: str, data: bytes, expected: list +) -> None: + """The `sl_status_t` responses are fully parsed by v14 and newer.""" + _, _, rx_schema = bellows.ezsp.EZSP._BY_VERSION[version].COMMANDS[command] + result, rest = t.deserialize_dict(data, rx_schema) + + assert list(result.values()) == expected + assert rest == b"" + + async def test_read_address_table(ezsp_f): def get_addr_table_info(index): default = ( From dd592ddb11fb9508f23474fdc9fa1d4fe430c77a Mon Sep 17 00:00:00 2001 From: zigpy-review-bot <286747149+zigpy-review-bot@users.noreply.github.com> Date: Sun, 13 Sep 2026 19:30:37 +0200 Subject: [PATCH 2/3] Move `leaveNetwork` options into a version-specific `leave_network` The shared protocol interface takes zigpy's ZDO `LeaveOptions` instead of branching on the EZSP version. EZSPv4 rejects any option, since its `leaveNetwork` has no options argument. EZSPv14 translates `Rejoin` into `SlZigbeeLeaveNetworkOption.WITH_OPTION_REJOIN` and rejects `RemoveChildren`: EZSP uses the NWK leave command's bit layout, where 0x40 means the leave was requested and 0x80 is a testing-only "leave silently" bit, so the ZDO values can't be passed through. --- bellows/ezsp/__init__.py | 14 +++++----- bellows/ezsp/protocol.py | 8 ++++++ bellows/ezsp/v14/__init__.py | 16 +++++++++++ bellows/ezsp/v4/__init__.py | 11 ++++++++ tests/test_ezsp.py | 52 +++++++++++++++++------------------- tests/test_ezsp_v14.py | 40 +++++++++++++++++++++++++++ tests/test_ezsp_v4.py | 23 ++++++++++++++++ 7 files changed, 129 insertions(+), 35 deletions(-) diff --git a/bellows/ezsp/__init__.py b/bellows/ezsp/__init__.py index 39f6534f0..cf9f8f444 100644 --- a/bellows/ezsp/__init__.py +++ b/bellows/ezsp/__init__.py @@ -14,6 +14,7 @@ import urllib.parse import zigpy.config +import zigpy.zdo from bellows.ash import NcpFailure import bellows.config as conf @@ -316,17 +317,16 @@ def cb(frame_name, response): 0, ) - async def leaveNetwork(self, timeout: float | int = NETWORK_OPS_TIMEOUT) -> None: + async def leaveNetwork( + self, + timeout: float | int = NETWORK_OPS_TIMEOUT, + options: zigpy.zdo.ZDO.LeaveOptions = zigpy.zdo.ZDO.LeaveOptions.NONE, + ) -> None: """Send leaveNetwork command and wait for stackStatusHandler frame.""" stack_status = asyncio.Future() with self.wait_for_stack_status(t.sl_Status.NETWORK_DOWN) as stack_status: - if self._ezsp_version >= 14: - (status,) = await self._command( - "leaveNetwork", options=t.SlZigbeeLeaveNetworkOption.WITH_NO_OPTION - ) - else: - (status,) = await self._command("leaveNetwork") + status = await self._protocol.leave_network(options=options) if status != t.sl_Status.OK: raise EzspError(f"failed to leave network: {status.name}") diff --git a/bellows/ezsp/protocol.py b/bellows/ezsp/protocol.py index 30006dc4d..3fc4b939a 100644 --- a/bellows/ezsp/protocol.py +++ b/bellows/ezsp/protocol.py @@ -12,6 +12,7 @@ from zigpy.datastructures import PriorityDynamicBoundedSemaphore import zigpy.state +import zigpy.zdo from bellows.config import CONF_EZSP_POLICIES from bellows.exception import InvalidCommandError @@ -336,6 +337,13 @@ async def initialize_network(self) -> t.sl_Status: async def factory_reset(self) -> None: raise NotImplementedError + @abc.abstractmethod + async def leave_network( + self, + options: zigpy.zdo.ZDO.LeaveOptions = zigpy.zdo.ZDO.LeaveOptions.NONE, + ) -> t.sl_Status: + raise NotImplementedError + @abc.abstractmethod async def send_unicast( self, diff --git a/bellows/ezsp/v14/__init__.py b/bellows/ezsp/v14/__init__.py index 16dbeec7f..9ff7d5ba1 100644 --- a/bellows/ezsp/v14/__init__.py +++ b/bellows/ezsp/v14/__init__.py @@ -6,6 +6,7 @@ import voluptuous as vol from zigpy.exceptions import NetworkNotFormed import zigpy.state +import zigpy.zdo import bellows.config import bellows.types as t @@ -87,6 +88,21 @@ async def get_tc_link_key(self) -> zigpy.state.Key: return zigpy.state.Key(key=tc_link_key_data) + async def leave_network( + self, + options: zigpy.zdo.ZDO.LeaveOptions = zigpy.zdo.ZDO.LeaveOptions.NONE, + ) -> t.sl_Status: + # EZSP uses the NWK leave command's bit layout, which differs from ZDO's + if options == zigpy.zdo.ZDO.LeaveOptions.NONE: + ezsp_options = t.SlZigbeeLeaveNetworkOption.WITH_NO_OPTION + elif options == zigpy.zdo.ZDO.LeaveOptions.Rejoin: + ezsp_options = t.SlZigbeeLeaveNetworkOption.WITH_OPTION_REJOIN + else: + raise ValueError(f"Leave options are not supported: {options!r}") + + (status,) = await self.leaveNetwork(options=ezsp_options) + return status + async def send_unicast( self, nwk: t.NWK, diff --git a/bellows/ezsp/v4/__init__.py b/bellows/ezsp/v4/__init__.py index 3b454ecd7..458254f80 100644 --- a/bellows/ezsp/v4/__init__.py +++ b/bellows/ezsp/v4/__init__.py @@ -7,6 +7,7 @@ import voluptuous as vol import zigpy.state +import zigpy.zdo import bellows.config import bellows.types as t @@ -127,6 +128,16 @@ async def initialize_network(self) -> t.sl_Status: async def factory_reset(self) -> None: await self.clearKeyTable() + async def leave_network( + self, + options: zigpy.zdo.ZDO.LeaveOptions = zigpy.zdo.ZDO.LeaveOptions.NONE, + ) -> t.sl_Status: + if options != zigpy.zdo.ZDO.LeaveOptions.NONE: + raise ValueError(f"Leave options are not supported: {options!r}") + + (status,) = await self.leaveNetwork() + return t.sl_Status.from_ember_status(status) + async def send_unicast( self, nwk: t.NWK, diff --git a/tests/test_ezsp.py b/tests/test_ezsp.py index de52b3457..96238b830 100644 --- a/tests/test_ezsp.py +++ b/tests/test_ezsp.py @@ -8,6 +8,7 @@ import pytest import zigpy.config +import zigpy.zdo from bellows import config, uart from bellows.ash import NcpFailure @@ -451,8 +452,8 @@ async def test_set_disable_source_routing(ezsp_f): async def test_leave_network_error(ezsp_f): """Test EZSP leaveNetwork command failure.""" - with patch.object(ezsp_f, "_command", new_callable=AsyncMock) as cmd_mock: - cmd_mock.return_value = [t.EmberStatus.ERR_FATAL] + with patch.object(ezsp_f._protocol, "leave_network", new_callable=AsyncMock) as m: + m.return_value = t.sl_Status.FAIL with pytest.raises(EzspError): await ezsp_f.leaveNetwork(timeout=0.01) @@ -460,43 +461,38 @@ async def test_leave_network_error(ezsp_f): async def test_leave_network_no_stack_status(ezsp_f): """Test EZSP leaveNetwork command, no stackStatusHandler callback.""" - with patch.object(ezsp_f, "_command", new_callable=AsyncMock) as cmd_mock: - cmd_mock.return_value = [t.EmberStatus.SUCCESS] + with patch.object(ezsp_f._protocol, "leave_network", new_callable=AsyncMock) as m: + m.return_value = t.sl_Status.OK with pytest.raises(TimeoutError): await ezsp_f.leaveNetwork(timeout=0.01) -async def test_leave_network(ezsp_f): +@pytest.mark.parametrize( + ("kwargs", "options"), + [ + ({}, zigpy.zdo.ZDO.LeaveOptions.NONE), + ( + {"options": zigpy.zdo.ZDO.LeaveOptions.Rejoin}, + zigpy.zdo.ZDO.LeaveOptions.Rejoin, + ), + ], +) +async def test_leave_network( + ezsp_f, kwargs: dict, options: zigpy.zdo.ZDO.LeaveOptions +) -> None: """Test EZSP leaveNetwork command.""" - async def _mock_cmd(*args, **kwargs): + async def _mock_leave(*args, **kwargs): ezsp_f.handle_callback("stackStatusHandler", [t.EmberStatus.NETWORK_UP]) ezsp_f.handle_callback("stackStatusHandler", [t.EmberStatus.NETWORK_UP]) ezsp_f.handle_callback("stackStatusHandler", [t.EmberStatus.NETWORK_DOWN]) - return [t.EmberStatus.SUCCESS] - - with patch.object(ezsp_f, "_command", new_callable=AsyncMock) as cmd_mock: - cmd_mock.side_effect = _mock_cmd - await ezsp_f.leaveNetwork(timeout=0.01) - - assert cmd_mock.mock_calls == [call("leaveNetwork")] - + return t.sl_Status.OK -async def test_leave_network_v14(ezsp_f): - """Test EZSP leaveNetwork command sends the `options` argument on EZSPv14+.""" - ezsp_f._ezsp_version = 14 + with patch.object(ezsp_f._protocol, "leave_network", new_callable=AsyncMock) as m: + m.side_effect = _mock_leave + await ezsp_f.leaveNetwork(timeout=0.01, **kwargs) - async def _mock_cmd(*args, **kwargs): - ezsp_f.handle_callback("stackStatusHandler", [t.sl_Status.NETWORK_DOWN]) - return [t.sl_Status.OK] - - with patch.object(ezsp_f, "_command", new_callable=AsyncMock) as cmd_mock: - cmd_mock.side_effect = _mock_cmd - await ezsp_f.leaveNetwork(timeout=0.01) - - assert cmd_mock.mock_calls == [ - call("leaveNetwork", options=t.SlZigbeeLeaveNetworkOption.WITH_NO_OPTION) - ] + assert m.mock_calls == [call(options=options)] async def test_xncp_token_override(ezsp_f): diff --git a/tests/test_ezsp_v14.py b/tests/test_ezsp_v14.py index 86b89f205..0fe22e654 100644 --- a/tests/test_ezsp_v14.py +++ b/tests/test_ezsp_v14.py @@ -3,6 +3,7 @@ import pytest import zigpy.exceptions import zigpy.state +import zigpy.zdo import bellows.ezsp import bellows.ezsp.v14 @@ -71,6 +72,45 @@ def test_status_rx_schemas( assert rest == b"" +@pytest.mark.parametrize( + ("options", "ezsp_options"), + [ + ( + zigpy.zdo.ZDO.LeaveOptions.NONE, + t.SlZigbeeLeaveNetworkOption.WITH_NO_OPTION, + ), + ( + zigpy.zdo.ZDO.LeaveOptions.Rejoin, + t.SlZigbeeLeaveNetworkOption.WITH_OPTION_REJOIN, + ), + ], +) +async def test_leave_network( + ezsp_f, + options: zigpy.zdo.ZDO.LeaveOptions, + ezsp_options: t.SlZigbeeLeaveNetworkOption, +) -> None: + ezsp_f.leaveNetwork.return_value = (t.sl_Status.OK,) + assert await ezsp_f.leave_network(options=options) == t.sl_Status.OK + assert ezsp_f.leaveNetwork.mock_calls == [call(options=ezsp_options)] + + +@pytest.mark.parametrize( + "options", + [ + zigpy.zdo.ZDO.LeaveOptions.RemoveChildren, + zigpy.zdo.ZDO.LeaveOptions.RemoveChildren | zigpy.zdo.ZDO.LeaveOptions.Rejoin, + ], +) +async def test_leave_network_options_unsupported( + ezsp_f, options: zigpy.zdo.ZDO.LeaveOptions +) -> None: + with pytest.raises(ValueError): + await ezsp_f.leave_network(options=options) + + assert ezsp_f.leaveNetwork.mock_calls == [] + + async def test_read_address_table(ezsp_f): def get_addr_table_info(index): default = ( diff --git a/tests/test_ezsp_v4.py b/tests/test_ezsp_v4.py index fc31d2510..f6ed48243 100644 --- a/tests/test_ezsp_v4.py +++ b/tests/test_ezsp_v4.py @@ -4,6 +4,7 @@ import pytest import zigpy.state +import zigpy.zdo import bellows.ezsp.v4 import bellows.types as t @@ -277,6 +278,28 @@ async def test_factory_reset(ezsp_f) -> None: assert ezsp_f.clearKeyTable.mock_calls == [call()] +async def test_leave_network(ezsp_f) -> None: + ezsp_f.leaveNetwork.return_value = (t.EmberStatus.SUCCESS,) + assert await ezsp_f.leave_network() == t.sl_Status.OK + assert ezsp_f.leaveNetwork.mock_calls == [call()] + + +@pytest.mark.parametrize( + "options", + [ + zigpy.zdo.ZDO.LeaveOptions.Rejoin, + zigpy.zdo.ZDO.LeaveOptions.RemoveChildren, + ], +) +async def test_leave_network_options_unsupported( + ezsp_f, options: zigpy.zdo.ZDO.LeaveOptions +) -> None: + with pytest.raises(ValueError): + await ezsp_f.leave_network(options=options) + + assert ezsp_f.leaveNetwork.mock_calls == [] + + async def test_send_unicast(ezsp_f) -> None: ezsp_f.sendUnicast.return_value = (t.EmberStatus.SUCCESS, 0x42) status, message_tag = await ezsp_f.send_unicast( From 8c8fbba3c6f04939903be15483d5492afdad0208 Mon Sep 17 00:00:00 2001 From: zigpy-review-bot <286747149+zigpy-review-bot@users.noreply.github.com> Date: Sun, 13 Sep 2026 19:45:54 +0200 Subject: [PATCH 3/3] Use `SlZigbeeLeaveNetworkOption` directly in `leave_network` Drop the ZDO `LeaveOptions` translation: `ProtocolHandler.leave_network` and `EZSP.leaveNetwork` now take the SiLabs enum. v14+ passes it through to the NCP unchanged; v4 raises `ValueError` for anything other than `WITH_NO_OPTION`. --- bellows/ezsp/__init__.py | 5 ++-- bellows/ezsp/protocol.py | 5 ++-- bellows/ezsp/v14/__init__.py | 15 +++-------- bellows/ezsp/v4/__init__.py | 7 ++--- tests/test_ezsp.py | 9 +++---- tests/test_ezsp_v14.py | 51 +++++++++++------------------------- tests/test_ezsp_v4.py | 7 +++-- 7 files changed, 37 insertions(+), 62 deletions(-) diff --git a/bellows/ezsp/__init__.py b/bellows/ezsp/__init__.py index cf9f8f444..16a5d9c1a 100644 --- a/bellows/ezsp/__init__.py +++ b/bellows/ezsp/__init__.py @@ -14,7 +14,6 @@ import urllib.parse import zigpy.config -import zigpy.zdo from bellows.ash import NcpFailure import bellows.config as conf @@ -320,7 +319,9 @@ def cb(frame_name, response): async def leaveNetwork( self, timeout: float | int = NETWORK_OPS_TIMEOUT, - options: zigpy.zdo.ZDO.LeaveOptions = zigpy.zdo.ZDO.LeaveOptions.NONE, + options: t.SlZigbeeLeaveNetworkOption = ( + t.SlZigbeeLeaveNetworkOption.WITH_NO_OPTION + ), ) -> None: """Send leaveNetwork command and wait for stackStatusHandler frame.""" stack_status = asyncio.Future() diff --git a/bellows/ezsp/protocol.py b/bellows/ezsp/protocol.py index 3fc4b939a..ecd20837c 100644 --- a/bellows/ezsp/protocol.py +++ b/bellows/ezsp/protocol.py @@ -12,7 +12,6 @@ from zigpy.datastructures import PriorityDynamicBoundedSemaphore import zigpy.state -import zigpy.zdo from bellows.config import CONF_EZSP_POLICIES from bellows.exception import InvalidCommandError @@ -340,7 +339,9 @@ async def factory_reset(self) -> None: @abc.abstractmethod async def leave_network( self, - options: zigpy.zdo.ZDO.LeaveOptions = zigpy.zdo.ZDO.LeaveOptions.NONE, + options: t.SlZigbeeLeaveNetworkOption = ( + t.SlZigbeeLeaveNetworkOption.WITH_NO_OPTION + ), ) -> t.sl_Status: raise NotImplementedError diff --git a/bellows/ezsp/v14/__init__.py b/bellows/ezsp/v14/__init__.py index 9ff7d5ba1..e80a4e738 100644 --- a/bellows/ezsp/v14/__init__.py +++ b/bellows/ezsp/v14/__init__.py @@ -6,7 +6,6 @@ import voluptuous as vol from zigpy.exceptions import NetworkNotFormed import zigpy.state -import zigpy.zdo import bellows.config import bellows.types as t @@ -90,17 +89,11 @@ async def get_tc_link_key(self) -> zigpy.state.Key: async def leave_network( self, - options: zigpy.zdo.ZDO.LeaveOptions = zigpy.zdo.ZDO.LeaveOptions.NONE, + options: t.SlZigbeeLeaveNetworkOption = ( + t.SlZigbeeLeaveNetworkOption.WITH_NO_OPTION + ), ) -> t.sl_Status: - # EZSP uses the NWK leave command's bit layout, which differs from ZDO's - if options == zigpy.zdo.ZDO.LeaveOptions.NONE: - ezsp_options = t.SlZigbeeLeaveNetworkOption.WITH_NO_OPTION - elif options == zigpy.zdo.ZDO.LeaveOptions.Rejoin: - ezsp_options = t.SlZigbeeLeaveNetworkOption.WITH_OPTION_REJOIN - else: - raise ValueError(f"Leave options are not supported: {options!r}") - - (status,) = await self.leaveNetwork(options=ezsp_options) + (status,) = await self.leaveNetwork(options=options) return status async def send_unicast( diff --git a/bellows/ezsp/v4/__init__.py b/bellows/ezsp/v4/__init__.py index 458254f80..340186bd4 100644 --- a/bellows/ezsp/v4/__init__.py +++ b/bellows/ezsp/v4/__init__.py @@ -7,7 +7,6 @@ import voluptuous as vol import zigpy.state -import zigpy.zdo import bellows.config import bellows.types as t @@ -130,9 +129,11 @@ async def factory_reset(self) -> None: async def leave_network( self, - options: zigpy.zdo.ZDO.LeaveOptions = zigpy.zdo.ZDO.LeaveOptions.NONE, + options: t.SlZigbeeLeaveNetworkOption = ( + t.SlZigbeeLeaveNetworkOption.WITH_NO_OPTION + ), ) -> t.sl_Status: - if options != zigpy.zdo.ZDO.LeaveOptions.NONE: + if options != t.SlZigbeeLeaveNetworkOption.WITH_NO_OPTION: raise ValueError(f"Leave options are not supported: {options!r}") (status,) = await self.leaveNetwork() diff --git a/tests/test_ezsp.py b/tests/test_ezsp.py index 96238b830..c4b6e7a1b 100644 --- a/tests/test_ezsp.py +++ b/tests/test_ezsp.py @@ -8,7 +8,6 @@ import pytest import zigpy.config -import zigpy.zdo from bellows import config, uart from bellows.ash import NcpFailure @@ -470,15 +469,15 @@ async def test_leave_network_no_stack_status(ezsp_f): @pytest.mark.parametrize( ("kwargs", "options"), [ - ({}, zigpy.zdo.ZDO.LeaveOptions.NONE), + ({}, t.SlZigbeeLeaveNetworkOption.WITH_NO_OPTION), ( - {"options": zigpy.zdo.ZDO.LeaveOptions.Rejoin}, - zigpy.zdo.ZDO.LeaveOptions.Rejoin, + {"options": t.SlZigbeeLeaveNetworkOption.WITH_OPTION_REJOIN}, + t.SlZigbeeLeaveNetworkOption.WITH_OPTION_REJOIN, ), ], ) async def test_leave_network( - ezsp_f, kwargs: dict, options: zigpy.zdo.ZDO.LeaveOptions + ezsp_f, kwargs: dict, options: t.SlZigbeeLeaveNetworkOption ) -> None: """Test EZSP leaveNetwork command.""" diff --git a/tests/test_ezsp_v14.py b/tests/test_ezsp_v14.py index 0fe22e654..813599e4f 100644 --- a/tests/test_ezsp_v14.py +++ b/tests/test_ezsp_v14.py @@ -3,7 +3,6 @@ import pytest import zigpy.exceptions import zigpy.state -import zigpy.zdo import bellows.ezsp import bellows.ezsp.v14 @@ -72,43 +71,25 @@ def test_status_rx_schemas( assert rest == b"" -@pytest.mark.parametrize( - ("options", "ezsp_options"), - [ - ( - zigpy.zdo.ZDO.LeaveOptions.NONE, - t.SlZigbeeLeaveNetworkOption.WITH_NO_OPTION, - ), - ( - zigpy.zdo.ZDO.LeaveOptions.Rejoin, - t.SlZigbeeLeaveNetworkOption.WITH_OPTION_REJOIN, - ), - ], -) -async def test_leave_network( - ezsp_f, - options: zigpy.zdo.ZDO.LeaveOptions, - ezsp_options: t.SlZigbeeLeaveNetworkOption, -) -> None: +async def test_leave_network(ezsp_f) -> None: ezsp_f.leaveNetwork.return_value = (t.sl_Status.OK,) - assert await ezsp_f.leave_network(options=options) == t.sl_Status.OK - assert ezsp_f.leaveNetwork.mock_calls == [call(options=ezsp_options)] - + assert await ezsp_f.leave_network() == t.sl_Status.OK + assert ezsp_f.leaveNetwork.mock_calls == [ + call(options=t.SlZigbeeLeaveNetworkOption.WITH_NO_OPTION) + ] -@pytest.mark.parametrize( - "options", - [ - zigpy.zdo.ZDO.LeaveOptions.RemoveChildren, - zigpy.zdo.ZDO.LeaveOptions.RemoveChildren | zigpy.zdo.ZDO.LeaveOptions.Rejoin, - ], -) -async def test_leave_network_options_unsupported( - ezsp_f, options: zigpy.zdo.ZDO.LeaveOptions -) -> None: - with pytest.raises(ValueError): - await ezsp_f.leave_network(options=options) - assert ezsp_f.leaveNetwork.mock_calls == [] +async def test_leave_network_options(ezsp_f) -> None: + ezsp_f.leaveNetwork.return_value = (t.sl_Status.OK,) + assert ( + await ezsp_f.leave_network( + options=t.SlZigbeeLeaveNetworkOption.WITH_OPTION_REJOIN + ) + == t.sl_Status.OK + ) + assert ezsp_f.leaveNetwork.mock_calls == [ + call(options=t.SlZigbeeLeaveNetworkOption.WITH_OPTION_REJOIN) + ] async def test_read_address_table(ezsp_f): diff --git a/tests/test_ezsp_v4.py b/tests/test_ezsp_v4.py index f6ed48243..7a8826f8c 100644 --- a/tests/test_ezsp_v4.py +++ b/tests/test_ezsp_v4.py @@ -4,7 +4,6 @@ import pytest import zigpy.state -import zigpy.zdo import bellows.ezsp.v4 import bellows.types as t @@ -287,12 +286,12 @@ async def test_leave_network(ezsp_f) -> None: @pytest.mark.parametrize( "options", [ - zigpy.zdo.ZDO.LeaveOptions.Rejoin, - zigpy.zdo.ZDO.LeaveOptions.RemoveChildren, + t.SlZigbeeLeaveNetworkOption.WITH_OPTION_REJOIN, + t.SlZigbeeLeaveNetworkOption.IS_REQUESTED, ], ) async def test_leave_network_options_unsupported( - ezsp_f, options: zigpy.zdo.ZDO.LeaveOptions + ezsp_f, options: t.SlZigbeeLeaveNetworkOption ) -> None: with pytest.raises(ValueError): await ezsp_f.leave_network(options=options)