diff --git a/bellows/cli/backup.py b/bellows/cli/backup.py index 2c1a0cb2..12e73b69 100644 --- a/bellows/cli/backup.py +++ b/bellows/cli/backup.py @@ -85,10 +85,10 @@ async def backup(ctx): async def _backup(ezsp): (status,) = await ezsp.networkInit() LOGGER.debug("Network init status: %s", status) - assert status == t.EmberStatus.SUCCESS + assert t.sl_Status.from_ember_status(status) == t.sl_Status.OK (status, node_type, network) = await ezsp.getNetworkParameters() - assert status == t.EmberStatus.SUCCESS + assert t.sl_Status.from_ember_status(status) == t.sl_Status.OK assert node_type == ezsp.types.EmberNodeType.COORDINATOR LOGGER.debug("Network params: %s", network) @@ -112,7 +112,7 @@ async def _backup(ezsp): (ATTR_KEY_NWK, ezsp.types.EmberKeyType.CURRENT_NETWORK_KEY), ): (status, key) = await ezsp.getKey(key_type) - assert status == t.EmberStatus.SUCCESS + assert t.sl_Status.from_ember_status(status) == t.sl_Status.OK LOGGER.debug("%s key: %s", key_name, key) result[key_name] = key.as_dict() result[key_name][ATTR_KEY_PARTNER] = str(key.partnerEUI64) @@ -248,7 +248,7 @@ async def _restore( (status,) = await ezsp.setInitialSecurityState(init_sec_state) LOGGER.debug("Set initial security state: %s", status) - assert status == t.EmberStatus.SUCCESS + assert t.sl_Status.from_ember_status(status) == t.sl_Status.OK if backup_data[ATTR_KEY_TABLE]: await _restore_keys(ezsp, backup_data[ATTR_KEY_TABLE]) @@ -259,7 +259,7 @@ async def _restore( t.uint32_t(network_key[ATTR_KEY_FRAME_COUNTER_OUT]).serialize(), ) LOGGER.debug("Set network frame counter: %s", status) - assert status == t.EmberStatus.SUCCESS + assert t.sl_Status.from_ember_status(status) == t.sl_Status.OK tc_key = backup_data[ATTR_KEY_GLOBAL] (status,) = await ezsp.setValue( @@ -267,7 +267,7 @@ async def _restore( t.uint32_t(tc_key[ATTR_KEY_FRAME_COUNTER_OUT]).serialize(), ) LOGGER.debug("Set network frame counter: %s", status) - assert status == t.EmberStatus.SUCCESS + assert t.sl_Status.from_ember_status(status) == t.sl_Status.OK await _form_network(ezsp, backup_data) await asyncio.sleep(2) @@ -279,7 +279,7 @@ async def _restore_keys(ezsp, key_table): (status,) = await ezsp.setConfigurationValue( ezsp.types.EzspConfigId.CONFIG_KEY_TABLE_SIZE, len(key_table) ) - assert status == t.EmberStatus.SUCCESS + assert t.sl_Status.from_ember_status(status) == t.sl_Status.OK for key in key_table: is_link_key = key[ATTR_KEY_TYPE] in ( @@ -312,7 +312,7 @@ async def _form_network(ezsp, backup_data): (status,) = await ezsp.setValue(ezsp.types.EzspValueId.VALUE_STACK_TOKEN_WRITING, 1) LOGGER.debug("Set token writing: %s", status) - assert status == t.EmberStatus.SUCCESS + assert t.sl_Status.from_ember_status(status) == t.sl_Status.OK async def _update_nwk_id(ezsp, nwk_update_id): @@ -338,7 +338,7 @@ async def _update_nwk_id(ezsp, nwk_update_id): 0x01, payload, ) - assert status == t.EmberStatus.SUCCESS + assert t.sl_Status.from_ember_status(status) == t.sl_Status.OK await asyncio.sleep(1) diff --git a/bellows/ezsp/__init__.py b/bellows/ezsp/__init__.py index bc1e39fe..72855dcb 100644 --- a/bellows/ezsp/__init__.py +++ b/bellows/ezsp/__init__.py @@ -27,9 +27,9 @@ import bellows.types as t import bellows.uart -from . import v4, v5, v6, v7, v8, v9, v10, v11, v12, v13 +from . import v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14 -EZSP_LATEST = v13.EZSPv13.VERSION +EZSP_LATEST = v14.EZSPv14.VERSION LOGGER = logging.getLogger(__name__) MTOR_MIN_INTERVAL = 60 MTOR_MAX_INTERVAL = 3600 @@ -56,6 +56,7 @@ class EZSP: v11.EZSPv11.VERSION: v11.EZSPv11, v12.EZSPv12.VERSION: v12.EZSPv12, v13.EZSPv13.VERSION: v13.EZSPv13, + v14.EZSPv14.VERSION: v14.EZSPv14, } def __init__(self, device_config: dict): @@ -68,7 +69,7 @@ def __init__(self, device_config: dict): self._send_sem = PriorityDynamicBoundedSemaphore(value=MAX_COMMAND_CONCURRENCY) self._stack_status_listeners: collections.defaultdict[ - t.EmberStatus, list[asyncio.Future] + t.sl_Status, list[asyncio.Future] ] = collections.defaultdict(list) self.add_callback(self.stack_status_callback) @@ -78,13 +79,13 @@ def stack_status_callback(self, frame_name: str, args: list[Any]) -> None: if frame_name != "stackStatusHandler": return - status = args[0] + status = t.sl_Status.from_ember_status(args[0]) for listener in self._stack_status_listeners[status]: listener.set_result(status) @contextlib.contextmanager - def wait_for_stack_status(self, status: t.EmberStatus) -> Generator[asyncio.Future]: + def wait_for_stack_status(self, status: t.sl_Status) -> Generator[asyncio.Future]: """Waits for a `stackStatusHandler` to come in with the provided status.""" listeners = self._stack_status_listeners[status] @@ -228,10 +229,10 @@ def cb(frame_name, response): cbid = self.add_callback(cb) try: v = await self._command(name, *args) - if v[0] != t.EmberStatus.SUCCESS: + if t.sl_Status.from_ember_status(v[0]) != t.sl_Status.OK: raise Exception(v) v = await fut - if v[spos] != t.EmberStatus.SUCCESS: + if t.sl_Status.from_ember_status(v[spos]) != t.sl_Status.OK: raise Exception(v) finally: self.remove_callback(cbid) @@ -267,9 +268,9 @@ async def leaveNetwork(self, timeout: float | int = NETWORK_OPS_TIMEOUT) -> None """Send leaveNetwork command and wait for stackStatusHandler frame.""" stack_status = asyncio.Future() - with self.wait_for_stack_status(t.EmberStatus.NETWORK_DOWN) as stack_status: + with self.wait_for_stack_status(t.sl_Status.NETWORK_DOWN) as stack_status: (status,) = await self._command("leaveNetwork") - if status != t.EmberStatus.SUCCESS: + if status != t.sl_Status.OK: raise EzspError(f"failed to leave network: {status.name}") async with asyncio_timeout(timeout): @@ -302,10 +303,10 @@ def __getattr__(self, name: str) -> Callable: return functools.partial(self._command, name) async def formNetwork(self, parameters: t.EmberNetworkParameters) -> None: - with self.wait_for_stack_status(t.EmberStatus.NETWORK_UP) as stack_status: + with self.wait_for_stack_status(t.sl_Status.NETWORK_UP) as stack_status: v = await self._command("formNetwork", parameters) - if v[0] != self.types.EmberStatus.SUCCESS: + if t.sl_Status.from_ember_status(v[0]) != t.sl_Status.OK: raise zigpy.exceptions.FormationFailure(f"Failure forming network: {v}") async with asyncio_timeout(NETWORK_OPS_TIMEOUT): @@ -361,7 +362,7 @@ async def get_board_info( ) version = None - if status == t.EmberStatus.SUCCESS: + if t.sl_Status.from_ember_status(status) == t.sl_Status.OK: build, ver_info_bytes = t.uint16_t.deserialize(ver_info_bytes) major, ver_info_bytes = t.uint8_t.deserialize(ver_info_bytes) minor, ver_info_bytes = t.uint8_t.deserialize(ver_info_bytes) @@ -388,7 +389,7 @@ async def _get_nv3_restored_eui64_key(self) -> t.NV3KeyId | None: # is not implemented in the firmware return None - if rsp.status == t.EmberStatus.SUCCESS: + if t.sl_Status.from_ember_status(rsp.status) == t.sl_Status.OK: nv3_restored_eui64, _ = t.EUI64.deserialize(rsp.value) LOGGER.debug("NV3 restored EUI64: %s=%s", key, nv3_restored_eui64) @@ -434,7 +435,7 @@ async def reset_custom_eui64(self) -> None: 0, t.LVBytes32(t.EUI64.convert("FF:FF:FF:FF:FF:FF:FF:FF").serialize()), ) - assert status == t.EmberStatus.SUCCESS + assert t.sl_Status.from_ember_status(status) == t.sl_Status.OK async def write_custom_eui64( self, ieee: t.EUI64, *, burn_into_userdata: bool = False @@ -460,12 +461,12 @@ async def write_custom_eui64( 0, t.LVBytes32(ieee.serialize()), ) - assert status == t.EmberStatus.SUCCESS + assert t.sl_Status.from_ember_status(status) == t.sl_Status.OK elif mfg_custom_eui64 is None and burn_into_userdata: (status,) = await self.setMfgToken( t.EzspMfgTokenId.MFG_CUSTOM_EUI_64, ieee.serialize() ) - assert status == t.EmberStatus.SUCCESS + assert t.sl_Status.from_ember_status(status) == t.sl_Status.OK elif mfg_custom_eui64 is None and not burn_into_userdata: raise EzspError( f"Firmware does not support NV3 tokens. Custom IEEE {ieee} will not be" @@ -507,7 +508,7 @@ async def set_source_routing(self) -> None: 0, ) LOGGER.debug("Set concentrator type: %s", res) - if res[0] != self.types.EmberStatus.SUCCESS: + if t.sl_Status.from_ember_status(res[0]) != t.sl_Status.OK: LOGGER.warning("Couldn't set concentrator type %s: %s", True, res) if self._ezsp_version >= 8: @@ -579,7 +580,7 @@ async def write_config(self, config: dict) -> None: # XXX: A read failure does not mean the value is not writeable! status, current_value = await self.getValue(cfg.value_id) - if status == self.types.EmberStatus.SUCCESS: + if t.sl_Status.from_ember_status(status) == t.sl_Status.OK: current_value, _ = type(cfg.value).deserialize(current_value) else: current_value = None @@ -593,7 +594,7 @@ async def write_config(self, config: dict) -> None: (status,) = await self.setValue(cfg.value_id, cfg.value.serialize()) - if status != self.types.EmberStatus.SUCCESS: + if t.sl_Status.from_ember_status(status) != t.sl_Status.OK: LOGGER.debug( "Could not set value %s = %s: %s", cfg.value_id.name, @@ -608,7 +609,7 @@ async def write_config(self, config: dict) -> None: # Only grow some config entries, all others should be set if ( - status == self.types.EmberStatus.SUCCESS + t.sl_Status.from_ember_status(status) == t.sl_Status.OK and cfg.minimum and current_value >= cfg.value ): @@ -628,7 +629,7 @@ async def write_config(self, config: dict) -> None: ) (status,) = await self.setConfigurationValue(cfg.config_id, cfg.value) - if status != self.types.EmberStatus.SUCCESS: + if t.sl_Status.from_ember_status(status) != t.sl_Status.OK: LOGGER.debug( "Could not set config %s = %s: %s", cfg.config_id, diff --git a/bellows/ezsp/config.py b/bellows/ezsp/config.py index 052e12e2..a04e2e52 100644 --- a/bellows/ezsp/config.py +++ b/bellows/ezsp/config.py @@ -131,4 +131,5 @@ class ValueConfig: 11: DEFAULT_CONFIG_NEW, 12: DEFAULT_CONFIG_NEW, 13: DEFAULT_CONFIG_NEW, + 14: DEFAULT_CONFIG_NEW, } diff --git a/bellows/ezsp/protocol.py b/bellows/ezsp/protocol.py index d744a21c..95fb80ee 100644 --- a/bellows/ezsp/protocol.py +++ b/bellows/ezsp/protocol.py @@ -6,7 +6,9 @@ import functools import logging import sys -from typing import TYPE_CHECKING, Any, Callable +from typing import TYPE_CHECKING, Any, AsyncGenerator, Callable, Iterable + +import zigpy.state if sys.version_info[:2] < (3, 11): from async_timeout import timeout as asyncio_timeout # pragma: no cover @@ -56,14 +58,6 @@ def _ezsp_frame_rx(self, data: bytes) -> tuple[int, int, bytes]: def _ezsp_frame_tx(self, name: str) -> bytes: """Serialize the named frame.""" - async def pre_permit(self, time_s: int) -> None: - """Schedule task before allowing new joins.""" - - async def add_transient_link_key( - self, ieee: t.EUI64, key: t.KeyData - ) -> t.EmberStatus: - """Add a transient link key.""" - async def command(self, name, *args) -> Any: """Serialize command and send it.""" LOGGER.debug("Sending command %s: %s", name, args) @@ -85,7 +79,9 @@ async def update_policies(self, policy_config: dict) -> None: for policy, value in policies.items(): (status,) = await self.setPolicy(self.types.EzspPolicyId[policy], value) - assert status == self.types.EmberStatus.SUCCESS # TODO: Better check + assert ( + t.sl_Status.from_ember_status(status) == t.sl_Status.OK + ) # TODO: Better check def __call__(self, data: bytes) -> None: """Handler for received data frame.""" @@ -148,3 +144,90 @@ def __getattr__(self, name: str) -> Callable: raise AttributeError(f"{name} not found in COMMANDS") return functools.partial(self.command, name) + + async def pre_permit(self, time_s: int) -> None: + """Schedule task before allowing new joins.""" + + async def add_transient_link_key( + self, ieee: t.EUI64, key: t.KeyData + ) -> t.sl_Status: + """Add a transient link key.""" + + @abc.abstractmethod + async def read_child_data( + self, + ) -> AsyncGenerator[tuple[t.NWK, t.EUI64, t.EmberNodeType], None]: + raise NotImplementedError + + @abc.abstractmethod + async def read_link_keys(self) -> AsyncGenerator[zigpy.state.Key, None]: + raise NotImplementedError + + @abc.abstractmethod + async def read_address_table(self) -> AsyncGenerator[tuple[t.NWK, t.EUI64], None]: + raise NotImplementedError + + @abc.abstractmethod + async def get_network_key(self) -> zigpy.state.Key: + raise NotImplementedError + + @abc.abstractmethod + async def get_tc_link_key(self) -> zigpy.state.Key: + raise NotImplementedError + + @abc.abstractmethod + async def write_nwk_frame_counter(self, frame_counter: t.uint32_t) -> None: + raise NotImplementedError + + @abc.abstractmethod + async def write_aps_frame_counter(self, frame_counter: t.uint32_t) -> None: + raise NotImplementedError + + @abc.abstractmethod + async def write_link_keys(self, keys: Iterable[zigpy.state.Key]) -> None: + raise NotImplementedError + + @abc.abstractmethod + async def write_child_data(self, children: dict[t.EUI64, t.NWK]) -> None: + raise NotImplementedError + + @abc.abstractmethod + async def initialize_network(self) -> t.sl_Status: + raise NotImplementedError + + @abc.abstractmethod + async def factory_reset(self) -> None: + raise NotImplementedError + + @abc.abstractmethod + async def send_unicast( + self, + nwk: t.NWK, + aps_frame: t.EmberApsFrame, + message_tag: t.uint8_t, + data: bytes, + ) -> tuple[t.sl_Status, t.uint8_t]: + raise NotImplementedError + + @abc.abstractmethod + async def send_multicast( + self, + aps_frame: t.EmberApsFrame, + radius: t.uint8_t, + non_member_radius: t.uint8_t, + message_tag: t.uint8_t, + data: bytes, + ) -> tuple[t.sl_Status, t.uint8_t]: + raise NotImplementedError + + @abc.abstractmethod + async def send_broadcast( + self, + address: t.BroadcastAddress, + aps_frame: t.EmberApsFrame, + radius: t.uint8_t, + message_tag: t.uint8_t, + aps_sequence: t.uint8_t, + data: bytes, + ) -> tuple[t.sl_Status, t.uint8_t]: + raise NotImplementedError diff --git a/bellows/ezsp/v10/__init__.py b/bellows/ezsp/v10/__init__.py index 147303a9..e8f9d6b0 100644 --- a/bellows/ezsp/v10/__init__.py +++ b/bellows/ezsp/v10/__init__.py @@ -1,9 +1,12 @@ """"EZSP Protocol version 10 protocol handler.""" +from __future__ import annotations + import logging import voluptuous import bellows.config +import bellows.types as t from . import commands, config, types as v10_types from ..v9 import EZSPv9 @@ -21,3 +24,19 @@ class EZSPv10(EZSPv9): bellows.config.CONF_EZSP_POLICIES: voluptuous.Schema(config.EZSP_POLICIES_SCH), } types = v10_types + + async def write_child_data(self, children: dict[t.EUI64, t.NWK]) -> None: + for index, (eui64, nwk) in enumerate(children.items()): + await self.setChildData( + index, + self.types.EmberChildData( + eui64=eui64, + type=t.EmberNodeType.SLEEPY_END_DEVICE, + id=nwk, + # The rest are unused when setting child data + phy=0, + power=0, + timeout=0, + timeout_remaining=0, + ), + ) diff --git a/bellows/ezsp/v13/__init__.py b/bellows/ezsp/v13/__init__.py index 73979b41..2be80fa2 100644 --- a/bellows/ezsp/v13/__init__.py +++ b/bellows/ezsp/v13/__init__.py @@ -1,7 +1,12 @@ """"EZSP Protocol version 13 protocol handler.""" from __future__ import annotations +import logging +from typing import AsyncGenerator, Iterable + import voluptuous as vol +from zigpy.exceptions import NetworkNotFormed +import zigpy.state import bellows.config import bellows.types as t @@ -9,6 +14,8 @@ from . import commands, config, types as v13_types from ..v12 import EZSPv12 +LOGGER = logging.getLogger(__name__) + class EZSPv13(EZSPv12): """EZSP Version 13 Protocol version handler.""" @@ -23,11 +30,89 @@ class EZSPv13(EZSPv12): async def add_transient_link_key( self, ieee: t.EUI64, key: t.KeyData - ) -> t.EmberStatus: + ) -> t.sl_Status: (status,) = await self.importTransientKey( ieee, key, - v13_types.sl_zb_sec_man_flags_t.NONE, + self.types.sl_zb_sec_man_flags_t.NONE, + ) + + return t.sl_Status.from_ember_status(status) + + async def read_link_keys(self) -> AsyncGenerator[zigpy.state.Key, None]: + (status, key_table_size) = await self.getConfigurationValue( + self.types.EzspConfigId.CONFIG_KEY_TABLE_SIZE ) - return status + for index in range(key_table_size): + ( + eui64, + plaintext_key, + key_data, + status, + ) = await self.exportLinkKeyByIndex(index) + + if status != t.sl_Status.OK: + continue + + yield zigpy.state.Key( + key=plaintext_key, + tx_counter=key_data.outgoing_frame_counter, + rx_counter=key_data.incoming_frame_counter, + partner_ieee=eui64, + ) + + async def get_network_key(self) -> zigpy.state.Key: + network_key_data, status = await self.exportKey( + self.types.sl_zb_sec_man_context_t( + core_key_type=self.types.sl_zb_sec_man_key_type_t.NETWORK, + key_index=0, + derived_type=self.types.sl_zb_sec_man_derived_key_type_t.NONE, + eui64=t.EUI64.convert("00:00:00:00:00:00:00:00"), + multi_network_index=0, + flags=self.types.sl_zb_sec_man_flags_t.NONE, + psa_key_alg_permission=0, + ) + ) + + assert t.sl_Status.from_ember_status(status) == t.sl_Status.OK + + (status, network_key_info) = await self.getNetworkKeyInfo() + assert t.sl_Status.from_ember_status(status) == t.sl_Status.OK + + if not network_key_info.network_key_set: + raise NetworkNotFormed("Network key is not set") + + return zigpy.state.Key( + key=network_key_data, + tx_counter=network_key_info.network_key_frame_counter, + seq=network_key_info.network_key_sequence_number, + ) + + async def get_tc_link_key(self) -> zigpy.state.Key: + tc_link_key_data, status = await self.exportKey( + self.types.sl_zb_sec_man_context_t( + core_key_type=self.types.sl_zb_sec_man_key_type_t.TC_LINK, + key_index=0, + derived_type=self.types.sl_zb_sec_man_derived_key_type_t.NONE, + eui64=t.EUI64.convert("00:00:00:00:00:00:00:00"), + multi_network_index=0, + flags=self.types.sl_zb_sec_man_flags_t.NONE, + psa_key_alg_permission=0, + ) + ) + + assert t.sl_Status.from_ember_status(status) == t.sl_Status.OK + + return zigpy.state.Key(key=tc_link_key_data) + + async def write_link_keys(self, keys: Iterable[zigpy.state.Key]) -> None: + for index, key in enumerate(keys): + (status,) = await self.importLinkKey(index, key.partner_ieee, key.key) + + if t.sl_Status.from_ember_status(status) != t.sl_Status.OK: + LOGGER.warning("Couldn't add %s key: %s", key, status) + + async def factory_reset(self) -> None: + await self.tokenFactoryReset(False, False) + await self.clearKeyTable() diff --git a/bellows/ezsp/v14/__init__.py b/bellows/ezsp/v14/__init__.py new file mode 100644 index 00000000..777a6704 --- /dev/null +++ b/bellows/ezsp/v14/__init__.py @@ -0,0 +1,110 @@ +""""EZSP Protocol version 14 protocol handler.""" +from __future__ import annotations + +from typing import AsyncGenerator + +import voluptuous as vol +from zigpy.exceptions import NetworkNotFormed +import zigpy.state + +import bellows.config +import bellows.types as t + +from . import commands, config, types as v14_types +from ..v13 import EZSPv13 + + +class EZSPv14(EZSPv13): + """EZSP Version 14 Protocol version handler.""" + + VERSION = 14 + COMMANDS = commands.COMMANDS + SCHEMAS = { + bellows.config.CONF_EZSP_CONFIG: vol.Schema(config.EZSP_SCHEMA), + bellows.config.CONF_EZSP_POLICIES: vol.Schema(config.EZSP_POLICIES_SCH), + } + types = v14_types + + async def read_address_table(self) -> AsyncGenerator[tuple[t.NWK, t.EUI64], None]: + (status, addr_table_size) = await self.getConfigurationValue( + self.types.EzspConfigId.CONFIG_ADDRESS_TABLE_SIZE + ) + + for idx in range(addr_table_size + 100): + (status, nwk, eui64) = await self.getAddressTableInfo(idx) + + if status != t.sl_Status.OK: + continue + + if eui64 in ( + t.EUI64.convert("00:00:00:00:00:00:00:00"), + t.EUI64.convert("FF:FF:FF:FF:FF:FF:FF:FF"), + ): + continue + + yield nwk, eui64 + + async def get_network_key(self) -> zigpy.state.Key: + status, network_key_data, _ = await self.exportKey( + self.types.sl_zb_sec_man_context_t( + core_key_type=self.types.sl_zb_sec_man_key_type_t.NETWORK, + key_index=0, + derived_type=self.types.sl_zb_sec_man_derived_key_type_t.NONE, + eui64=t.EUI64.convert("00:00:00:00:00:00:00:00"), + multi_network_index=0, + flags=self.types.sl_zb_sec_man_flags_t.NONE, + psa_key_alg_permission=0, + ) + ) + + assert status == t.sl_Status.OK + + (status, network_key_info) = await self.getNetworkKeyInfo() + assert status == t.sl_Status.OK + + if not network_key_info.network_key_set: + raise NetworkNotFormed("Network key is not set") + + return zigpy.state.Key( + key=network_key_data, + tx_counter=network_key_info.network_key_frame_counter, + seq=network_key_info.network_key_sequence_number, + ) + + async def get_tc_link_key(self) -> zigpy.state.Key: + status, tc_link_key_data, _ = await self.exportKey( + self.types.sl_zb_sec_man_context_t( + core_key_type=self.types.sl_zb_sec_man_key_type_t.TC_LINK, + key_index=0, + derived_type=self.types.sl_zb_sec_man_derived_key_type_t.NONE, + eui64=t.EUI64.convert("00:00:00:00:00:00:00:00"), + multi_network_index=0, + flags=self.types.sl_zb_sec_man_flags_t.NONE, + psa_key_alg_permission=0, + ) + ) + + assert status == t.sl_Status.OK + + return zigpy.state.Key(key=tc_link_key_data) + + async def send_broadcast( + self, + address: t.BroadcastAddress, + aps_frame: t.EmberApsFrame, + radius: t.uint8_t, + message_tag: t.uint8_t, + aps_sequence: t.uint8_t, + data: bytes, + ) -> tuple[t.sl_Status, t.uint8_t]: + status, sequence = await self.sendBroadcast( + 0x0000, + address, + aps_sequence, + aps_frame, + radius, + message_tag, + data, + ) + + return status, sequence diff --git a/bellows/ezsp/v14/commands.py b/bellows/ezsp/v14/commands.py new file mode 100644 index 00000000..f4b4cdfd --- /dev/null +++ b/bellows/ezsp/v14/commands.py @@ -0,0 +1,184 @@ +from zigpy.types import EUI64, NWK, BroadcastAddress, Struct + +from . import types as t +from ..v13.commands import COMMANDS as COMMANDS_v13 + + +class GetTokenDataRsp(Struct): + status: t.sl_Status + value: t.LVBytes32 + + +# EmberStatus and EzspStatus have been replaced with sl_Status globally. +# The `status` field is also moved to be the first parameter in most responses. +_REPLACEMENTS = { + t.EmberStatus: t.sl_Status, + t.EzspStatus: t.sl_Status, +} + +COMMANDS = { + "setExtendedTimeout": ( + 0x007E, + tuple( + { + "remoteEui64": t.EUI64, + "extendedTimeout": t.Bool, + }.values() + ), + tuple( + { + "status": t.sl_Status, + }.values() + ), + ), + "getTokenData": ( + 0x0102, + tuple({"token": t.uint32_t, "index": t.uint32_t}.values()), + GetTokenDataRsp, + ), + "exportLinkKeyByIndex": ( + 0x010F, + tuple( + { + "index": t.uint8_t, + }.values() + ), + tuple( + { + "status": t.sl_Status, + "context": t.sl_zb_sec_man_context_t, + "plaintext_key": t.KeyData, + "key_data": t.sl_zb_sec_man_aps_key_metadata_t, + }.values() + ), + ), + "exportKey": ( + 0x0114, + tuple( + { + "context": t.sl_zb_sec_man_context_t, + }.values() + ), + tuple( + { + "status": t.sl_Status, + "key": t.KeyData, + "context": t.sl_zb_sec_man_context_t, + }.values() + ), + ), + "getAddressTableInfo": ( + 0x005E, + tuple( + { + "index": t.uint8_t, + }.values() + ), + tuple( + { + "status": t.sl_Status, + "nwk": NWK, + "eui64": EUI64, + }.values() + ), + ), + "incomingMessageHandler": ( + 0x0045, + tuple({}.values()), + tuple( + { + "message_type": t.EmberIncomingMessageType, + "aps_frame": t.EmberApsFrame, + "nwk": NWK, + "eui64": EUI64, + "binding_index": t.uint8_t, + "address_index": t.uint8_t, + "lqi": t.uint8_t, + "rssi": t.int8s, + "timestamp": t.uint32_t, + "message": t.LVBytes, + }.values() + ), + ), + "messageSentHandler": ( + 0x003F, + tuple({}.values()), + tuple( + { + "status": t.sl_Status, + "message_type": t.EmberOutgoingMessageType, + "nwk": NWK, + "aps_frame": t.EmberApsFrame, + "message_tag": t.uint16_t, + "message": t.LVBytes, + }.values() + ), + ), + "sendUnicast": ( + 0x0034, + tuple( + { + "message_type": t.EmberOutgoingMessageType, + "nwk": NWK, + "aps_frame": t.EmberApsFrame, + "message_tag": t.uint16_t, + "message": t.LVBytes, + }.values() + ), + tuple( + { + "status": t.sl_Status, + "sequence": t.uint8_t, + }.values() + ), + ), + "sendBroadcast": ( + 0x0034, + tuple( + { + "alias": t.uint16_t, + "destination": BroadcastAddress, + "sequence": t.uint8_t, + "aps_frame": t.EmberApsFrame, + "radius": t.uint8_t, + "message_tag": t.uint16_t, + "message": t.LVBytes, + }.values() + ), + tuple( + { + "status": t.sl_Status, + "sequence": t.uint8_t, + }.values() + ), + ), + "launchStandaloneBootloader": ( + 0x008F, + tuple( + { + "mode": t.uint8_t, + }.values() + ), + tuple( + { + # XXX: One of the few commands that does *not* migrate to `sl_Status`! + "status": t.EmberStatus, + }.values() + ), + ), +} + + +for name, (command_id, tx_schema, rx_schema) in COMMANDS_v13.items(): + if name in COMMANDS: + continue + + if not isinstance(tx_schema, Struct): + tx_schema = tuple([_REPLACEMENTS.get(s, s) for s in tx_schema]) + + if not isinstance(tx_schema, Struct): + rx_schema = tuple([_REPLACEMENTS.get(s, s) for s in rx_schema]) + + COMMANDS[name] = (command_id, tx_schema, rx_schema) + +del COMMANDS["getAddressTableRemoteEui64"] diff --git a/bellows/ezsp/v14/config.py b/bellows/ezsp/v14/config.py new file mode 100644 index 00000000..eabfe8d3 --- /dev/null +++ b/bellows/ezsp/v14/config.py @@ -0,0 +1,16 @@ +import voluptuous as vol + +from bellows.config import cv_uint16 + +from ..v4.config import EZSP_POLICIES_SHARED +from ..v13 import config as v13_config +from .types import EzspPolicyId + +EZSP_SCHEMA = { + **v13_config.EZSP_SCHEMA, +} + +EZSP_POLICIES_SCH = { + **EZSP_POLICIES_SHARED, + **{vol.Optional(policy.name): cv_uint16 for policy in EzspPolicyId}, +} diff --git a/bellows/ezsp/v14/types.py b/bellows/ezsp/v14/types.py new file mode 100644 index 00000000..d379da2f --- /dev/null +++ b/bellows/ezsp/v14/types.py @@ -0,0 +1,2 @@ +""""EZSP Protocol version 14 protocol handler.""" +from ..v13.types import * # noqa: F401, F403 diff --git a/bellows/ezsp/v4/__init__.py b/bellows/ezsp/v4/__init__.py index ed2ec6b6..b5dcc6bc 100644 --- a/bellows/ezsp/v4/__init__.py +++ b/bellows/ezsp/v4/__init__.py @@ -1,10 +1,15 @@ """"EZSP Protocol version 4 command.""" +from __future__ import annotations + import logging -from typing import Tuple +from typing import AsyncGenerator, Iterable -import voluptuous +import voluptuous as vol +import zigpy.state import bellows.config +import bellows.types as t +from bellows.zigbee.util import ezsp_key_to_zigpy_key from . import commands, config, types as v4_types from .. import protocol @@ -18,19 +23,153 @@ class EZSPv4(protocol.ProtocolHandler): VERSION = 4 COMMANDS = commands.COMMANDS SCHEMAS = { - bellows.config.CONF_EZSP_CONFIG: voluptuous.Schema(config.EZSP_SCHEMA), - bellows.config.CONF_EZSP_POLICIES: voluptuous.Schema(config.EZSP_POLICIES_SCH), + bellows.config.CONF_EZSP_CONFIG: vol.Schema(config.EZSP_SCHEMA), + bellows.config.CONF_EZSP_POLICIES: vol.Schema(config.EZSP_POLICIES_SCH), } types = v4_types def _ezsp_frame_tx(self, name: str) -> bytes: """Serialize the frame id.""" c = self.COMMANDS[name] - return bytes([self._seq & 0xFF, 0, c[0]]) # Frame control. TODO. # Frame ID + return bytes([self._seq & 0xFF, 0, c[0]]) - def _ezsp_frame_rx(self, data: bytes) -> Tuple[int, int, bytes]: + def _ezsp_frame_rx(self, data: bytes) -> tuple[int, int, bytes]: """Handler for received data frame.""" return data[0], data[2], data[3:] async def pre_permit(self, time_s: int) -> None: pass + + async def read_child_data( + self, + ) -> AsyncGenerator[tuple[t.NWK, t.EUI64, t.EmberNodeType], None]: + for idx in range(0, 255 + 1): + (status, nwk, eui64, node_type) = await self.getChildData(idx) + status = t.sl_Status.from_ember_status(status) + + if status == t.sl_Status.NOT_JOINED: + continue + + yield nwk, eui64, node_type + + async def read_link_keys(self) -> AsyncGenerator[zigpy.state.Key, None]: + (status, key_table_size) = await self.getConfigurationValue( + self.types.EzspConfigId.CONFIG_KEY_TABLE_SIZE + ) + + for index in range(key_table_size): + (status, key) = await self.getKeyTableEntry(index) + status = t.sl_Status.from_ember_status(status) + + if status == t.sl_Status.INVALID_INDEX: + break + elif status == t.sl_Status.NOT_FOUND: + continue + + assert t.sl_Status.from_ember_status(status) == t.sl_Status.OK + yield ezsp_key_to_zigpy_key(key, self) + + async def read_address_table(self) -> AsyncGenerator[tuple[t.NWK, t.EUI64], None]: + # v4 can crash when getAddressTableRemoteNodeId(32) is received: undefined_0x8a + # We need this function to be an async generator even if it does nothing + if False: + yield + + async def get_network_key(self) -> zigpy.state.Key: + (status, ezsp_network_key) = await self.getKey( + self.types.EmberKeyType.CURRENT_NETWORK_KEY + ) + assert t.sl_Status.from_ember_status(status) == t.sl_Status.OK + return ezsp_key_to_zigpy_key(ezsp_network_key, self) + + async def get_tc_link_key(self) -> zigpy.state.Key: + (status, ezsp_tc_link_key) = await self.getKey( + self.types.EmberKeyType.TRUST_CENTER_LINK_KEY + ) + assert t.sl_Status.from_ember_status(status) == t.sl_Status.OK + return ezsp_key_to_zigpy_key(ezsp_tc_link_key, self) + + async def write_nwk_frame_counter(self, frame_counter: t.uint32_t) -> None: + # Not supported in EZSPv4 + pass + + async def write_aps_frame_counter(self, frame_counter: t.uint32_t) -> None: + # Not supported in EZSPv4 + pass + + async def write_link_keys(self, keys: Iterable[zigpy.state.Key]) -> None: + for key in keys: + # XXX: is there no way to set the outgoing frame counter or seq? + (status,) = await self.addOrUpdateKeyTableEntry( + key.partner_ieee, True, key.key + ) + + if t.sl_Status.from_ember_status(status) != t.sl_Status.OK: + LOGGER.warning("Couldn't add %s key: %s", key, status) + + async def write_child_data(self, children: dict[t.EUI64, t.NWK]) -> None: + # Not supported in EZSPv4 + pass + + async def initialize_network(self) -> t.sl_Status: + (init_status,) = await self.networkInitExtended(0x0000) + return t.sl_Status.from_ember_status(init_status) + + async def factory_reset(self) -> None: + await self.clearKeyTable() + + async def send_unicast( + self, + nwk: t.NWK, + aps_frame: t.EmberApsFrame, + message_tag: t.uint8_t, + data: bytes, + ) -> tuple[t.sl_Status, t.uint8_t]: + status, sequence = await self.sendUnicast( + t.EmberOutgoingMessageType.OUTGOING_DIRECT, + t.EmberNodeId(nwk), + aps_frame, + message_tag, + data, + ) + + return t.sl_Status.from_ember_status(status), sequence + + async def send_multicast( + self, + aps_frame: t.EmberApsFrame, + radius: t.uint8_t, + non_member_radius: t.uint8_t, + message_tag: t.uint8_t, + data: bytes, + ) -> tuple[t.sl_Status, t.uint8_t]: + status, sequence = await self.sendMulticast( + aps_frame, + radius, + non_member_radius, + message_tag, + data, + ) + + return t.sl_Status.from_ember_status(status), sequence + + async def send_broadcast( + self, + address: t.BroadcastAddress, + aps_frame: t.EmberApsFrame, + radius: t.uint8_t, + message_tag: t.uint8_t, + aps_sequence: t.uint8_t, + data: bytes, + ) -> tuple[t.sl_Status, t.uint8_t]: + # `aps_sequence` is not used + + status, sequence = await self.sendBroadcast( + address, + aps_frame, + radius, + message_tag, + data, + ) + + return t.sl_Status.from_ember_status(status), sequence diff --git a/bellows/ezsp/v5/__init__.py b/bellows/ezsp/v5/__init__.py index 01d286a6..efb4b86a 100644 --- a/bellows/ezsp/v5/__init__.py +++ b/bellows/ezsp/v5/__init__.py @@ -2,8 +2,9 @@ from __future__ import annotations import logging +from typing import AsyncGenerator -import voluptuous +import voluptuous as vol import bellows.config import bellows.types as t @@ -20,8 +21,8 @@ class EZSPv5(EZSPv4): VERSION = 5 COMMANDS = commands.COMMANDS SCHEMAS = { - bellows.config.CONF_EZSP_CONFIG: voluptuous.Schema(config.EZSP_SCHEMA), - bellows.config.CONF_EZSP_POLICIES: voluptuous.Schema(config.EZSP_POLICIES_SCH), + bellows.config.CONF_EZSP_CONFIG: vol.Schema(config.EZSP_SCHEMA), + bellows.config.CONF_EZSP_POLICIES: vol.Schema(config.EZSP_POLICIES_SCH), } types = v5_types @@ -37,12 +38,56 @@ def _ezsp_frame_rx(self, data: bytes) -> tuple[int, int, bytes]: async def add_transient_link_key( self, ieee: t.EUI64, key: t.KeyData - ) -> t.EmberStatus: + ) -> t.sl_Status: (status,) = await self.addTransientLinkKey(ieee, key) - return status + return t.sl_Status.from_ember_status(status) async def pre_permit(self, time_s: int) -> None: """Add pre-shared TC Link key.""" - wild_card_ieee = v5_types.EUI64([0xFF] * 8) - tc_link_key = v5_types.KeyData(b"ZigBeeAlliance09") + wild_card_ieee = t.EUI64.convert("FF:FF:FF:FF:FF:FF:FF:FF") + tc_link_key = t.KeyData(b"ZigBeeAlliance09") await self.add_transient_link_key(wild_card_ieee, tc_link_key) + + async def read_address_table(self) -> AsyncGenerator[tuple[t.NWK, t.EUI64], None]: + (status, addr_table_size) = await self.getConfigurationValue( + self.types.EzspConfigId.CONFIG_ADDRESS_TABLE_SIZE + ) + + for idx in range(addr_table_size): + (nwk,) = await self.getAddressTableRemoteNodeId(idx) + + # Ignore invalid NWK entries + if nwk in t.EmberDistinguishedNodeId.__members__.values(): + continue + + (eui64,) = await self.getAddressTableRemoteEui64(idx) + + if eui64 in ( + t.EUI64.convert("00:00:00:00:00:00:00:00"), + t.EUI64.convert("FF:FF:FF:FF:FF:FF:FF:FF"), + ): + continue + + yield nwk, eui64 + + async def write_nwk_frame_counter(self, frame_counter: t.uint32_t) -> None: + # Frame counters can only be set *before* we have joined a network + (state,) = await self.networkState() + assert state == self.types.EmberNetworkStatus.NO_NETWORK + + (status,) = await self.setValue( + self.types.EzspValueId.VALUE_NWK_FRAME_COUNTER, + t.uint32_t(frame_counter).serialize(), + ) + assert t.sl_Status.from_ember_status(status) == t.sl_Status.OK + + async def write_aps_frame_counter(self, frame_counter: t.uint32_t) -> None: + # Frame counters can only be set *before* we have joined a network + (state,) = await self.networkState() + assert state == self.types.EmberNetworkStatus.NO_NETWORK + + (status,) = await self.setValue( + self.types.EzspValueId.VALUE_APS_FRAME_COUNTER, + t.uint32_t(frame_counter).serialize(), + ) + assert t.sl_Status.from_ember_status(status) == t.sl_Status.OK diff --git a/bellows/ezsp/v6/__init__.py b/bellows/ezsp/v6/__init__.py index e15b4b0b..785688f6 100644 --- a/bellows/ezsp/v6/__init__.py +++ b/bellows/ezsp/v6/__init__.py @@ -4,6 +4,7 @@ import voluptuous import bellows.config +import bellows.types as t from . import commands, config, types as v6_types from ..v5 import EZSPv5 @@ -21,3 +22,7 @@ class EZSPv6(EZSPv5): bellows.config.CONF_EZSP_POLICIES: voluptuous.Schema(config.EZSP_POLICIES_SCH), } types = v6_types + + async def initialize_network(self) -> t.sl_Status: + (init_status,) = await self.networkInit(0x0000) + return t.sl_Status.from_ember_status(init_status) diff --git a/bellows/ezsp/v7/__init__.py b/bellows/ezsp/v7/__init__.py index da0a0018..942fab70 100644 --- a/bellows/ezsp/v7/__init__.py +++ b/bellows/ezsp/v7/__init__.py @@ -1,9 +1,13 @@ """"EZSP Protocol version 7 protocol handler.""" +from __future__ import annotations + import logging +from typing import AsyncGenerator import voluptuous import bellows.config +import bellows.types as t from . import commands, config, types as v7_types from ..v6 import EZSPv6 @@ -21,3 +25,15 @@ class EZSPv7(EZSPv6): bellows.config.CONF_EZSP_POLICIES: voluptuous.Schema(config.EZSP_POLICIES_SCH), } types = v7_types + + async def read_child_data( + self, + ) -> AsyncGenerator[tuple[t.NWK, t.EUI64, t.EmberNodeType], None]: + for idx in range(0, 255 + 1): + (status, rsp) = await self.getChildData(idx) + status = t.sl_Status.from_ember_status(status) + + if status == t.sl_Status.NOT_JOINED: + continue + + yield rsp.id, rsp.eui64, rsp.type diff --git a/bellows/ezsp/v9/__init__.py b/bellows/ezsp/v9/__init__.py index 1bd04afa..ce50f3e7 100644 --- a/bellows/ezsp/v9/__init__.py +++ b/bellows/ezsp/v9/__init__.py @@ -1,9 +1,12 @@ """"EZSP Protocol version 9 protocol handler.""" +from __future__ import annotations + import logging -import voluptuous +import voluptuous as vol import bellows.config +import bellows.types as t from . import commands, config, types as v9_types from ..v8 import EZSPv8 @@ -17,7 +20,22 @@ class EZSPv9(EZSPv8): VERSION = 9 COMMANDS = commands.COMMANDS SCHEMAS = { - bellows.config.CONF_EZSP_CONFIG: voluptuous.Schema(config.EZSP_SCHEMA), - bellows.config.CONF_EZSP_POLICIES: voluptuous.Schema(config.EZSP_POLICIES_SCH), + bellows.config.CONF_EZSP_CONFIG: vol.Schema(config.EZSP_SCHEMA), + bellows.config.CONF_EZSP_POLICIES: vol.Schema(config.EZSP_POLICIES_SCH), } types = v9_types + + async def write_child_data(self, children: dict[t.EUI64, t.NWK]) -> None: + for index, (eui64, nwk) in enumerate(children.items()): + await self.setChildData( + index, + self.types.EmberChildData( + eui64=eui64, + type=t.EmberNodeType.SLEEPY_END_DEVICE, + id=nwk, + # The rest are unused when setting child data + phy=0, + power=0, + timeout=0, + ), + ) diff --git a/bellows/multicast.py b/bellows/multicast.py index db0ba3cf..a623f956 100644 --- a/bellows/multicast.py +++ b/bellows/multicast.py @@ -20,12 +20,12 @@ async def _initialize(self) -> None: status, size = await self._ezsp.getConfigurationValue( self._ezsp.types.EzspConfigId.CONFIG_MULTICAST_TABLE_SIZE ) - if status != t.EmberStatus.SUCCESS: + if t.sl_Status.from_ember_status(status) != t.sl_Status.OK: return for i in range(0, size): status, entry = await self._ezsp.getMulticastTableEntry(i) - if status != t.EmberStatus.SUCCESS: + if t.sl_Status.from_ember_status(status) != t.sl_Status.OK: LOGGER.error("Couldn't get MulticastTableEntry #%s: %s", i, status) continue LOGGER.debug("MulticastTableEntry[%s] = %s", i, entry) @@ -42,22 +42,22 @@ async def startup(self, coordinator) -> None: for group_id in ep.member_of: await self.subscribe(group_id) - async def subscribe(self, group_id) -> t.EmberStatus: + async def subscribe(self, group_id) -> t.sl_Status: if group_id in self._multicast: LOGGER.debug("%s is already subscribed", t.EmberMulticastId(group_id)) - return t.EmberStatus.SUCCESS + return t.sl_Status.OK try: idx = self._available.pop() except KeyError: LOGGER.error("No more available slots MulticastId subscription") - return t.EmberStatus.INDEX_OUT_OF_RANGE + return t.sl_Status.INVALID_INDEX entry = t.EmberMulticastTableEntry() entry.endpoint = t.uint8_t(1) entry.multicastId = t.EmberMulticastId(group_id) entry.networkIndex = t.uint8_t(0) status = await self._ezsp.setMulticastTableEntry(idx, entry) - if status[0] != t.EmberStatus.SUCCESS: + if t.sl_Status.from_ember_status(status[0]) != t.sl_Status.OK: LOGGER.warning( "Set MulticastTableEntry #%s for %s multicast id: %s", idx, @@ -76,18 +76,18 @@ async def subscribe(self, group_id) -> t.EmberStatus: ) return status[0] - async def unsubscribe(self, group_id) -> t.EmberStatus: + async def unsubscribe(self, group_id) -> t.sl_Status: try: entry, idx = self._multicast[group_id] except KeyError: LOGGER.error( "Couldn't find MulticastTableEntry for %s multicast_id", group_id ) - return t.EmberStatus.INDEX_OUT_OF_RANGE + return t.sl_Status.INVALID_INDEX entry.endpoint = t.uint8_t(0) status = await self._ezsp.setMulticastTableEntry(idx, entry) - if status[0] != t.EmberStatus.SUCCESS: + if t.sl_Status.from_ember_status(status[0]) != t.sl_Status.OK: LOGGER.warning( "Set MulticastTableEntry #%s for %s multicast id: %s", idx, diff --git a/bellows/types/named.py b/bellows/types/named.py index 15ff4055..3ce1c035 100644 --- a/bellows/types/named.py +++ b/bellows/types/named.py @@ -1,11 +1,19 @@ +from __future__ import annotations + +import logging + import zigpy.types as ztypes import zigpy.zdo.types as zdo_t from . import basic +LOGGER = logging.getLogger(__name__) + Channels = ztypes.Channels EmberEUI64 = ztypes.EUI64 EUI64 = ztypes.EUI64 +NWK = ztypes.NWK +BroadcastAddress = ztypes.BroadcastAddress class NcpResetCode(basic.enum8): @@ -1284,571 +1292,334 @@ class EmberMessageDigest(basic.FixedList[basic.uint8_t, 16]): class sl_Status(basic.enum32): - # SL Status Codes. - # - # Status Defines - # Generic Errors - # No error. - SL_STATUS_OK = 0x0000 - # Generic error. - SL_STATUS_FAIL = 0x0001 - # State Errors - # Generic invalid state error. - SL_STATUS_INVALID_STATE = 0x0002 - # Module is not ready for requested operation. - SL_STATUS_NOT_READY = 0x0003 - # Module is busy and cannot carry out requested operation. - SL_STATUS_BUSY = 0x0004 - # Operation is in progress and not yet complete (pass or fail). - SL_STATUS_IN_PROGRESS = 0x0005 - # Operation aborted. - SL_STATUS_ABORT = 0x0006 - # Operation timed out. - SL_STATUS_TIMEOUT = 0x0007 - # Operation not allowed per permissions. - SL_STATUS_PERMISSION = 0x0008 - # Non-blocking operation would block. - SL_STATUS_WOULD_BLOCK = 0x0009 - # Operation/module is Idle, cannot carry requested operation. - SL_STATUS_IDLE = 0x000A - # Operation cannot be done while construct is waiting. - SL_STATUS_IS_WAITING = 0x000B - # No task/construct waiting/pending for that action/event. - SL_STATUS_NONE_WAITING = 0x000C - # Operation cannot be done while construct is suspended. - SL_STATUS_SUSPENDED = 0x000D - # Feature not available due to software configuration. - SL_STATUS_NOT_AVAILABLE = 0x000E - # Feature not supported. - SL_STATUS_NOT_SUPPORTED = 0x000F - # Initialization failed. - SL_STATUS_INITIALIZATION = 0x0010 - # Module has not been initialized. - SL_STATUS_NOT_INITIALIZED = 0x0011 - # Module has already been initialized. - SL_STATUS_ALREADY_INITIALIZED = 0x0012 - # Object/construct has been deleted. - SL_STATUS_DELETED = 0x0013 - # Illegal call from ISR. - SL_STATUS_ISR = 0x0014 - # Illegal call because network is up. - SL_STATUS_NETWORK_UP = 0x0015 - # Illegal call because network is down. - SL_STATUS_NETWORK_DOWN = 0x0016 - # Failure due to not being joined in a network. - SL_STATUS_NOT_JOINED = 0x0017 - # Invalid operation as there are no beacons. - SL_STATUS_NO_BEACONS = 0x0018 - # Allocation/ownership Errors - # Generic allocation error. - SL_STATUS_ALLOCATION_FAILED = 0x0019 - # No more resource available to perform the operation. - SL_STATUS_NO_MORE_RESOURCE = 0x001A - # Item/list/queue is empty. - SL_STATUS_EMPTY = 0x001B - # Item/list/queue is full. - SL_STATUS_FULL = 0x001C - # Item would overflow. - SL_STATUS_WOULD_OVERFLOW = 0x001D - # Item/list/queue has been overflowed. - SL_STATUS_HAS_OVERFLOWED = 0x001E - # Generic ownership error. - SL_STATUS_OWNERSHIP = 0x001F - # Already/still owning resource. - SL_STATUS_IS_OWNER = 0x0020 - # Invalid Parameters Errors - # Generic invalid argument or consequence of invalid argument. - SL_STATUS_INVALID_PARAMETER = 0x0021 - # Invalid null pointer received as argument. - SL_STATUS_NULL_POINTER = 0x0022 - # Invalid configuration provided. - SL_STATUS_INVALID_CONFIGURATION = 0x0023 - # Invalid mode. - SL_STATUS_INVALID_MODE = 0x0024 - # Invalid handle. - SL_STATUS_INVALID_HANDLE = 0x0025 - # Invalid type for operation. - SL_STATUS_INVALID_TYPE = 0x0026 - # Invalid index. - SL_STATUS_INVALID_INDEX = 0x0027 - # Invalid range. - SL_STATUS_INVALID_RANGE = 0x0028 - # Invalid key. - SL_STATUS_INVALID_KEY = 0x0029 - # Invalid credentials. - SL_STATUS_INVALID_CREDENTIALS = 0x002A - # Invalid count. - SL_STATUS_INVALID_COUNT = 0x002B - # Invalid signature / verification failed. - SL_STATUS_INVALID_SIGNATURE = 0x002C - # Item could not be found. - SL_STATUS_NOT_FOUND = 0x002D - # Item already exists. - SL_STATUS_ALREADY_EXISTS = 0x002E - # IO/Communication Errors - # Generic I/O failure. - SL_STATUS_IO = 0x002F - # I/O failure due to timeout. - SL_STATUS_IO_TIMEOUT = 0x0030 - # Generic transmission error. - SL_STATUS_TRANSMIT = 0x0031 - # Transmit underflowed. - SL_STATUS_TRANSMIT_UNDERFLOW = 0x0032 - # Transmit is incomplete. - SL_STATUS_TRANSMIT_INCOMPLETE = 0x0033 - # Transmit is busy. - SL_STATUS_TRANSMIT_BUSY = 0x0034 - # Generic reception error. - SL_STATUS_RECEIVE = 0x0035 - # Failed to read on/via given object. - SL_STATUS_OBJECT_READ = 0x0036 - # Failed to write on/via given object. - SL_STATUS_OBJECT_WRITE = 0x0037 - # Message is too long. - SL_STATUS_MESSAGE_TOO_LONG = 0x0038 - # EEPROM/Flash Errors - SL_STATUS_EEPROM_MFG_VERSION_MISMATCH = 0x0039 - SL_STATUS_EEPROM_STACK_VERSION_MISMATCH = 0x003A - # Flash write is inhibited. - SL_STATUS_FLASH_WRITE_INHIBITED = 0x003B - # Flash verification failed. - SL_STATUS_FLASH_VERIFY_FAILED = 0x003C - # Flash programming failed. - SL_STATUS_FLASH_PROGRAM_FAILED = 0x003D - # Flash erase failed. - SL_STATUS_FLASH_ERASE_FAILED = 0x003E - # MAC Errors - SL_STATUS_MAC_NO_DATA = 0x003F - SL_STATUS_MAC_NO_ACK_RECEIVED = 0x0040 - SL_STATUS_MAC_INDIRECT_TIMEOUT = 0x0041 - SL_STATUS_MAC_UNKNOWN_HEADER_TYPE = 0x0042 - SL_STATUS_MAC_ACK_HEADER_TYPE = 0x0043 - SL_STATUS_MAC_COMMAND_TRANSMIT_FAILURE = 0x0044 - # CLI_STORAGE Errors - # Error in open NVM - SL_STATUS_CLI_STORAGE_NVM_OPEN_ERROR = 0x0045 - # Security status codes - # Image checksum is not valid. - SL_STATUS_SECURITY_IMAGE_CHECKSUM_ERROR = 0x0046 - # Decryption failed - SL_STATUS_SECURITY_DECRYPT_ERROR = 0x0047 - # Command status codes - # Command was not recognized - SL_STATUS_COMMAND_IS_INVALID = 0x0048 - # Command or parameter maximum length exceeded - SL_STATUS_COMMAND_TOO_LONG = 0x0049 - # Data received does not form a complete command - SL_STATUS_COMMAND_INCOMPLETE = 0x004A - # Misc Errors - # Bus error, e.g. invalid DMA address - SL_STATUS_BUS_ERROR = 0x004B - # Unified MAC Errors - SL_STATUS_CCA_FAILURE = 0x004C # - # Scan errors - SL_STATUS_MAC_SCANNING = 0x004D - SL_STATUS_MAC_INCORRECT_SCAN_TYPE = 0x004E - SL_STATUS_INVALID_CHANNEL_MASK = 0x004F - SL_STATUS_BAD_SCAN_DURATION = 0x0050 - # Bluetooth status codes - # Bonding procedure can't be started because device has no space - # left for bond. - SL_STATUS_BT_OUT_OF_BONDS = 0x0402 - # Unspecified error - SL_STATUS_BT_UNSPECIFIED = 0x0403 - # Hardware failure - SL_STATUS_BT_HARDWARE = 0x0404 - # The bonding does not exist. - SL_STATUS_BT_NO_BONDING = 0x0406 - # Error using crypto functions - SL_STATUS_BT_CRYPTO = 0x0407 - # Data was corrupted. - SL_STATUS_BT_DATA_CORRUPTED = 0x0408 - # Invalid periodic advertising sync handle - SL_STATUS_BT_INVALID_SYNC_HANDLE = 0x040A - # Bluetooth cannot be used on this hardware - SL_STATUS_BT_INVALID_MODULE_ACTION = 0x040B - # Error received from radio - SL_STATUS_BT_RADIO = 0x040C - # Returned when remote disconnects the connection-oriented channel by sending - # disconnection request. - SL_STATUS_BT_L2CAP_REMOTE_DISCONNECTED = 0x040D - # Returned when local host disconnect the connection-oriented channel by sending - # disconnection request. - SL_STATUS_BT_L2CAP_LOCAL_DISCONNECTED = 0x040E - # Returned when local host did not find a connection-oriented channel with given - # destination CID. - SL_STATUS_BT_L2CAP_CID_NOT_EXIST = 0x040F - # Returned when connection-oriented channel disconnected due to LE connection is dropped. - SL_STATUS_BT_L2CAP_LE_DISCONNECTED = 0x0410 - # Returned when connection-oriented channel disconnected due to remote end send data - # even without credit. - SL_STATUS_BT_L2CAP_FLOW_CONTROL_VIOLATED = 0x0412 - # Returned when connection-oriented channel disconnected due to remote end send flow - # control credits exceed 65535. - SL_STATUS_BT_L2CAP_FLOW_CONTROL_CREDIT_OVERFLOWED = 0x0413 - # Returned when connection-oriented channel has run out of flow control credit and - # local application still trying to send data. - SL_STATUS_BT_L2CAP_NO_FLOW_CONTROL_CREDIT = 0x0414 - # Returned when connection-oriented channel has not received connection response message - # within maximum timeout. - SL_STATUS_BT_L2CAP_CONNECTION_REQUEST_TIMEOUT = 0x0415 - # Returned when local host received a connection-oriented channel connection response - # with an invalid destination CID. - SL_STATUS_BT_L2CAP_INVALID_CID = 0x0416 - # Returned when local host application tries to send a command which is not suitable - # for L2CAP channel's current state. - SL_STATUS_BT_L2CAP_WRONG_STATE = 0x0417 - # Flash reserved for PS store is full - SL_STATUS_BT_PS_STORE_FULL = 0x041B - # PS key not found - SL_STATUS_BT_PS_KEY_NOT_FOUND = 0x041C - # Mismatched or insufficient security level - SL_STATUS_BT_APPLICATION_MISMATCHED_OR_INSUFFICIENT_SECURITY = 0x041D - # Encrypion/decryption operation failed. - SL_STATUS_BT_APPLICATION_ENCRYPTION_DECRYPTION_ERROR = 0x041E - # Bluetooth controller status codes - # Connection does not exist, or connection open request was cancelled. - SL_STATUS_BT_CTRL_UNKNOWN_CONNECTION_IDENTIFIER = 0x1002 - # Pairing or authentication failed due to incorrect results in the pairing or - # authentication procedure. This could be due to an incorrect PIN or Link Key - SL_STATUS_BT_CTRL_AUTHENTICATION_FAILURE = 0x1005 - # Pairing failed because of missing PIN, or authentication failed because of missing Key - SL_STATUS_BT_CTRL_PIN_OR_KEY_MISSING = 0x1006 - # Controller is out of memory. - SL_STATUS_BT_CTRL_MEMORY_CAPACITY_EXCEEDED = 0x1007 - # Link supervision timeout has expired. - SL_STATUS_BT_CTRL_CONNECTION_TIMEOUT = 0x1008 - # Controller is at limit of connections it can support. - SL_STATUS_BT_CTRL_CONNECTION_LIMIT_EXCEEDED = 0x1009 - # The Synchronous Connection Limit to a Device Exceeded error code indicates that - # the Controller has reached the limit to the number of synchronous connections that - # can be achieved to a device. - SL_STATUS_BT_CTRL_SYNCHRONOUS_CONNECTION_LIMIT_EXCEEDED = 0x100A - # The ACL Connection Already Exists error code indicates that an attempt to create - # a new ACL Connection to a device when there is already a connection to this device. - SL_STATUS_BT_CTRL_ACL_CONNECTION_ALREADY_EXISTS = 0x100B - # Command requested cannot be executed because the Controller is in a state where - # it cannot process this command at this time. - SL_STATUS_BT_CTRL_COMMAND_DISALLOWED = 0x100C - # The Connection Rejected Due To Limited Resources error code indicates that an - # incoming connection was rejected due to limited resources. - SL_STATUS_BT_CTRL_CONNECTION_REJECTED_DUE_TO_LIMITED_RESOURCES = 0x100D - # The Connection Rejected Due To Security Reasons error code indicates that a - # connection was rejected due to security requirements not being fulfilled, like - # authentication or pairing. - SL_STATUS_BT_CTRL_CONNECTION_REJECTED_DUE_TO_SECURITY_REASONS = 0x100E - # The Connection was rejected because this device does not accept the BD_ADDR. - # This may be because the device will only accept connections from specific BD_ADDRs. - SL_STATUS_BT_CTRL_CONNECTION_REJECTED_DUE_TO_UNACCEPTABLE_BD_ADDR = 0x100F - # The Connection Accept Timeout has been exceeded for this connection attempt. - SL_STATUS_BT_CTRL_CONNECTION_ACCEPT_TIMEOUT_EXCEEDED = 0x1010 - # A feature or parameter value in the HCI command is not supported. - SL_STATUS_BT_CTRL_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE = 0x1011 - # Command contained invalid parameters. - SL_STATUS_BT_CTRL_INVALID_COMMAND_PARAMETERS = 0x1012 - # User on the remote device terminated the connection. - SL_STATUS_BT_CTRL_REMOTE_USER_TERMINATED = 0x1013 - # The remote device terminated the connection because of low resources - SL_STATUS_BT_CTRL_REMOTE_DEVICE_TERMINATED_CONNECTION_DUE_TO_LOW_RESOURCES = 0x1014 - # Remote Device Terminated Connection due to Power Off - SL_STATUS_BT_CTRL_REMOTE_POWERING_OFF = 0x1015 - # Local device terminated the connection. - SL_STATUS_BT_CTRL_CONNECTION_TERMINATED_BY_LOCAL_HOST = 0x1016 - # The Controller is disallowing an authentication or pairing procedure because - # too little time has elapsed since the last authentication or pairing attempt failed. - SL_STATUS_BT_CTRL_REPEATED_ATTEMPTS = 0x1017 - # The device does not allow pairing. This can be for example, when a device only - # allows pairing during a certain time window after some user input allows pairing - SL_STATUS_BT_CTRL_PAIRING_NOT_ALLOWED = 0x1018 - # The remote device does not support the feature associated with the issued command. - SL_STATUS_BT_CTRL_UNSUPPORTED_REMOTE_FEATURE = 0x101A - # No other error code specified is appropriate to use. - SL_STATUS_BT_CTRL_UNSPECIFIED_ERROR = 0x101F - # Connection terminated due to link-layer procedure timeout. - SL_STATUS_BT_CTRL_LL_RESPONSE_TIMEOUT = 0x1022 - # LL procedure has collided with the same transaction or procedure that is already - # in progress. - SL_STATUS_BT_CTRL_LL_PROCEDURE_COLLISION = 0x1023 - # The requested encryption mode is not acceptable at this time. - SL_STATUS_BT_CTRL_ENCRYPTION_MODE_NOT_ACCEPTABLE = 0x1025 - # Link key cannot be changed because a fixed unit key is being used. - SL_STATUS_BT_CTRL_LINK_KEY_CANNOT_BE_CHANGED = 0x1026 - # LMP PDU or LL PDU that includes an instant cannot be performed because the instan - # when this would have occurred has passed. - SL_STATUS_BT_CTRL_INSTANT_PASSED = 0x1028 - # It was not possible to pair as a unit key was requested and it is not supported. - SL_STATUS_BT_CTRL_PAIRING_WITH_UNIT_KEY_NOT_SUPPORTED = 0x1029 - # LMP transaction was started that collides with an ongoing transaction. - SL_STATUS_BT_CTRL_DIFFERENT_TRANSACTION_COLLISION = 0x102A - # The Controller cannot perform channel assessment because it is not supported. - SL_STATUS_BT_CTRL_CHANNEL_ASSESSMENT_NOT_SUPPORTED = 0x102E - # The HCI command or LMP PDU sent is only possible on an encrypted link. - SL_STATUS_BT_CTRL_INSUFFICIENT_SECURITY = 0x102F - # A parameter value requested is outside the mandatory range of parameters for the - # given HCI command or LMP PDU. - SL_STATUS_BT_CTRL_PARAMETER_OUT_OF_MANDATORY_RANGE = 0x1030 - # The IO capabilities request or response was rejected because the sending Host does - # not support Secure Simple Pairing even though the receiving Link Manager does. - SL_STATUS_BT_CTRL_SIMPLE_PAIRING_NOT_SUPPORTED_BY_HOST = 0x1037 - # The Host is busy with another pairing operation and unable to support the requested - # pairing. The receiving device should retry pairing again later. - SL_STATUS_BT_CTRL_HOST_BUSY_PAIRING = 0x1038 - # The Controller could not calculate an appropriate value for the Channel selection operation. - SL_STATUS_BT_CTRL_CONNECTION_REJECTED_DUE_TO_NO_SUITABLE_CHANNEL_FOUND = 0x1039 - # Operation was rejected because the controller is busy and unable to process the request. - SL_STATUS_BT_CTRL_CONTROLLER_BUSY = 0x103A - # Remote device terminated the connection because of an unacceptable connection interval. - SL_STATUS_BT_CTRL_UNACCEPTABLE_CONNECTION_INTERVAL = 0x103B - # Ddvertising for a fixed duration completed or, for directed advertising, that advertising - # completed without a connection being created. - SL_STATUS_BT_CTRL_ADVERTISING_TIMEOUT = 0x103C - # Connection was terminated because the Message Integrity Check (MIC) failed on a - # received packet. - SL_STATUS_BT_CTRL_CONNECTION_TERMINATED_DUE_TO_MIC_FAILURE = 0x103D - # LL initiated a connection but the connection has failed to be established. Controller did not receive - # any packets from remote end. - SL_STATUS_BT_CTRL_CONNECTION_FAILED_TO_BE_ESTABLISHED = 0x103E - # The MAC of the 802.11 AMP was requested to connect to a peer, but the connection failed. - SL_STATUS_BT_CTRL_MAC_CONNECTION_FAILED = 0x103F - # The master, at this time, is unable to make a coarse adjustment to the piconet clock, - # using the supplied parameters. Instead the master will attempt to move the clock using clock dragging. - SL_STATUS_BT_CTRL_COARSE_CLOCK_ADJUSTMENT_REJECTED_BUT_WILL_TRY_TO_ADJUST_USING_CLOCK_DRAGGING = ( - 0x1040 - ) - # A command was sent from the Host that should identify an Advertising or Sync handle, but the - # Advertising or Sync handle does not exist. - SL_STATUS_BT_CTRL_UNKNOWN_ADVERTISING_IDENTIFIER = 0x1042 - # Number of operations requested has been reached and has indicated the completion of the activity - # (e.g., advertising or scanning). - SL_STATUS_BT_CTRL_LIMIT_REACHED = 0x1043 - # A request to the Controller issued by the Host and still pending was successfully canceled. - SL_STATUS_BT_CTRL_OPERATION_CANCELLED_BY_HOST = 0x1044 - # An attempt was made to send or receive a packet that exceeds the maximum allowed packet l - SL_STATUS_BT_CTRL_PACKET_TOO_LONG = 0x1045 - # Bluetooth attribute status codes - # The attribute handle given was not valid on this server - SL_STATUS_BT_ATT_INVALID_HANDLE = 0x1101 - # The attribute cannot be read - SL_STATUS_BT_ATT_READ_NOT_PERMITTED = 0x1102 - # The attribute cannot be written - SL_STATUS_BT_ATT_WRITE_NOT_PERMITTED = 0x1103 - # The attribute PDU was invalid - SL_STATUS_BT_ATT_INVALID_PDU = 0x1104 - # The attribute requires authentication before it can be read or written. - SL_STATUS_BT_ATT_INSUFFICIENT_AUTHENTICATION = 0x1105 - # Attribute Server does not support the request received from the client. - SL_STATUS_BT_ATT_REQUEST_NOT_SUPPORTED = 0x1106 - # Offset specified was past the end of the attribute - SL_STATUS_BT_ATT_INVALID_OFFSET = 0x1107 - # The attribute requires authorization before it can be read or written. - SL_STATUS_BT_ATT_INSUFFICIENT_AUTHORIZATION = 0x1108 - # Too many prepare writes have been queued - SL_STATUS_BT_ATT_PREPARE_QUEUE_FULL = 0x1109 - # No attribute found within the given attribute handle range. - SL_STATUS_BT_ATT_ATT_NOT_FOUND = 0x110A - # The attribute cannot be read or written using the Read Blob Request - SL_STATUS_BT_ATT_ATT_NOT_LONG = 0x110B - # The Encryption Key Size used for encrypting this link is insufficient. - SL_STATUS_BT_ATT_INSUFFICIENT_ENC_KEY_SIZE = 0x110C - # The attribute value length is invalid for the operation - SL_STATUS_BT_ATT_INVALID_ATT_LENGTH = 0x110D - # The attribute request that was requested has encountered an error that was unlikely, and - # therefore could not be completed as requested. - SL_STATUS_BT_ATT_UNLIKELY_ERROR = 0x110E - # The attribute requires encryption before it can be read or written. - SL_STATUS_BT_ATT_INSUFFICIENT_ENCRYPTION = 0x110F - # The attribute type is not a supported grouping attribute as defined by a higher layer - # specification. - SL_STATUS_BT_ATT_UNSUPPORTED_GROUP_TYPE = 0x1110 - # Insufficient Resources to complete the request - SL_STATUS_BT_ATT_INSUFFICIENT_RESOURCES = 0x1111 - # The server requests the client to rediscover the database. - SL_STATUS_BT_ATT_OUT_OF_SYNC = 0x1112 - # The attribute parameter value was not allowed. - SL_STATUS_BT_ATT_VALUE_NOT_ALLOWED = 0x1113 - # When this is returned in a BGAPI response, the application tried to read or write the - # value of a user attribute from the GATT databa - SL_STATUS_BT_ATT_APPLICATION = 0x1180 - # The requested write operation cannot be fulfilled for reasons other than permissions. - SL_STATUS_BT_ATT_WRITE_REQUEST_REJECTED = 0x11FC - # The Client Characteristic Configuration descriptor is not configured according to the - # requirements of the profile or service. - SL_STATUS_BT_ATT_CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR_IMPROPERLY_CONFIGURED = ( - 0x11FD - ) - # The profile or service request cannot be serviced because an operation that has been - # previously triggered is still in progress. - SL_STATUS_BT_ATT_PROCEDURE_ALREADY_IN_PROGRESS = 0x11FE - # The attribute value is out of range as defined by a profile or service specification. - SL_STATUS_BT_ATT_OUT_OF_RANGE = 0x11FF - # Bluetooth Security Manager Protocol status codes - # The user input of passkey failed, for example, the user cancelled the operation - SL_STATUS_BT_SMP_PASSKEY_ENTRY_FAILED = 0x1201 - # Out of Band data is not available for authentication - SL_STATUS_BT_SMP_OOB_NOT_AVAILABLE = 0x1202 - # The pairing procedure cannot be performed as authentication requirements cannot be - # met due to IO capabilities of one or both devices - SL_STATUS_BT_SMP_AUTHENTICATION_REQUIREMENTS = 0x1203 - # The confirm value does not match the calculated compare value - SL_STATUS_BT_SMP_CONFIRM_VALUE_FAILED = 0x1204 - # Pairing is not supported by the device - SL_STATUS_BT_SMP_PAIRING_NOT_SUPPORTED = 0x1205 - # The resultant encryption key size is insufficient for the security requirements of this device - SL_STATUS_BT_SMP_ENCRYPTION_KEY_SIZE = 0x1206 - # The SMP command received is not supported on this device - SL_STATUS_BT_SMP_COMMAND_NOT_SUPPORTED = 0x1207 - # Pairing failed due to an unspecified reason - SL_STATUS_BT_SMP_UNSPECIFIED_REASON = 0x1208 - # Pairing or authentication procedure is disallowed because too little time has elapsed - # since last pairing request or security request - SL_STATUS_BT_SMP_REPEATED_ATTEMPTS = 0x1209 - # The Invalid Parameters error code indicates: the command length is invalid or a parameter - # is outside of the specified range. - SL_STATUS_BT_SMP_INVALID_PARAMETERS = 0x120A - # Indicates to the remote device that the DHKey Check value received doesn't match the one - # calculated by the local device. - SL_STATUS_BT_SMP_DHKEY_CHECK_FAILED = 0x120B - # Indicates that the confirm values in the numeric comparison protocol do not match. - SL_STATUS_BT_SMP_NUMERIC_COMPARISON_FAILED = 0x120C - # Indicates that the pairing over the LE transport failed due to a Pairing Request - # sent over the BR/EDR transport in process. - SL_STATUS_BT_SMP_BREDR_PAIRING_IN_PROGRESS = 0x120D - # Indicates that the BR/EDR Link Key generated on the BR/EDR transport cannot be used - # to derive and distribute keys for the LE transport. - SL_STATUS_BT_SMP_CROSS_TRANSPORT_KEY_DERIVATION_GENERATION_NOT_ALLOWED = 0x120E - # Indicates that the device chose not to accept a distributed key. - SL_STATUS_BT_SMP_KEY_REJECTED = 0x120F - # Bluetooth Mesh status codes - # Returned when trying to add a key or some other unique resource with an ID which already exists - SL_STATUS_BT_MESH_ALREADY_EXISTS = 0x0501 - # Returned when trying to manipulate a key or some other resource with an ID which does not exist - SL_STATUS_BT_MESH_DOES_NOT_EXIST = 0x0502 - # Returned when an operation cannot be executed because a pre-configured limit for keys, - # key bindings, elements, models, virtual addresses, provisioned devices, or provisioning sessions is reached - SL_STATUS_BT_MESH_LIMIT_REACHED = 0x0503 - # Returned when trying to use a reserved address or add a "pre-provisioned" device - # using an address already used by some other device - SL_STATUS_BT_MESH_INVALID_ADDRESS = 0x0504 - # In a BGAPI response, the user supplied malformed data; in a BGAPI event, the remote - # end responded with malformed or unrecognized data - SL_STATUS_BT_MESH_MALFORMED_DATA = 0x0505 - # An attempt was made to initialize a subsystem that was already initialized. - SL_STATUS_BT_MESH_ALREADY_INITIALIZED = 0x0506 - # An attempt was made to use a subsystem that wasn't initialized yet. Call the - # subsystem's init function first. - SL_STATUS_BT_MESH_NOT_INITIALIZED = 0x0507 - # Returned when trying to establish a friendship as a Low Power Node, but no acceptable - # friend offer message was received. - SL_STATUS_BT_MESH_NO_FRIEND_OFFER = 0x0508 - # Provisioning link was unexpectedly closed before provisioning was complete. - SL_STATUS_BT_MESH_PROV_LINK_CLOSED = 0x0509 - # An unrecognized provisioning PDU was received. - SL_STATUS_BT_MESH_PROV_INVALID_PDU = 0x050A - # A provisioning PDU with wrong length or containing field values that are out of - # bounds was received. - SL_STATUS_BT_MESH_PROV_INVALID_PDU_FORMAT = 0x050B - # An unexpected (out of sequence) provisioning PDU was received. - SL_STATUS_BT_MESH_PROV_UNEXPECTED_PDU = 0x050C - # The computed confirmation value did not match the expected value. - SL_STATUS_BT_MESH_PROV_CONFIRMATION_FAILED = 0x050D - # Provisioning could not be continued due to insufficient resources. - SL_STATUS_BT_MESH_PROV_OUT_OF_RESOURCES = 0x050E - # The provisioning data block could not be decrypted. - SL_STATUS_BT_MESH_PROV_DECRYPTION_FAILED = 0x050F - # An unexpected error happened during provisioning. - SL_STATUS_BT_MESH_PROV_UNEXPECTED_ERROR = 0x0510 - # Device could not assign unicast addresses to all of its elements. - SL_STATUS_BT_MESH_PROV_CANNOT_ASSIGN_ADDR = 0x0511 - # Returned when trying to reuse an address of a previously deleted device before an - # IV Index Update has been executed. - SL_STATUS_BT_MESH_ADDRESS_TEMPORARILY_UNAVAILABLE = 0x0512 - # Returned when trying to assign an address that is used by one of the devices in the - # Device Database, or by the Provisioner itself. - SL_STATUS_BT_MESH_ADDRESS_ALREADY_USED = 0x0513 - # Application key or publish address are not set - SL_STATUS_BT_MESH_PUBLISH_NOT_CONFIGURED = 0x0514 - # Application key is not bound to a model - SL_STATUS_BT_MESH_APP_KEY_NOT_BOUND = 0x0515 - # Bluetooth Mesh foundation status codes - # Returned when address in request was not valid - SL_STATUS_BT_MESH_FOUNDATION_INVALID_ADDRESS = 0x1301 - # Returned when model identified is not found for a given element - SL_STATUS_BT_MESH_FOUNDATION_INVALID_MODEL = 0x1302 - # Returned when the key identified by AppKeyIndex is not stored in the node - SL_STATUS_BT_MESH_FOUNDATION_INVALID_APP_KEY = 0x1303 - # Returned when the key identified by NetKeyIndex is not stored in the node - SL_STATUS_BT_MESH_FOUNDATION_INVALID_NET_KEY = 0x1304 - # Returned when The node cannot serve the request due to insufficient resources - SL_STATUS_BT_MESH_FOUNDATION_INSUFFICIENT_RESOURCES = 0x1305 - # Returned when the key identified is already stored in the node and the new - # NetKey value is different - SL_STATUS_BT_MESH_FOUNDATION_KEY_INDEX_EXISTS = 0x1306 - # Returned when the model does not support the publish mechanism - SL_STATUS_BT_MESH_FOUNDATION_INVALID_PUBLISH_PARAMS = 0x1307 - # Returned when the model does not support the subscribe mechanism - SL_STATUS_BT_MESH_FOUNDATION_NOT_SUBSCRIBE_MODEL = 0x1308 - # Returned when storing of the requested parameters failed - SL_STATUS_BT_MESH_FOUNDATION_STORAGE_FAILURE = 0x1309 - # Returned when requested setting is not supported - SL_STATUS_BT_MESH_FOUNDATION_NOT_SUPPORTED = 0x130A - # Returned when the requested update operation cannot be performed due to general constraints - SL_STATUS_BT_MESH_FOUNDATION_CANNOT_UPDATE = 0x130B - # Returned when the requested delete operation cannot be performed due to general constraints - SL_STATUS_BT_MESH_FOUNDATION_CANNOT_REMOVE = 0x130C - # Returned when the requested bind operation cannot be performed due to general constraints - SL_STATUS_BT_MESH_FOUNDATION_CANNOT_BIND = 0x130D - # Returned when The node cannot start advertising with Node Identity or Proxy since the - # maximum number of parallel advertising is reached - SL_STATUS_BT_MESH_FOUNDATION_TEMPORARILY_UNABLE = 0x130E - # Returned when the requested state cannot be set - SL_STATUS_BT_MESH_FOUNDATION_CANNOT_SET = 0x130F - # Returned when an unspecified error took place - SL_STATUS_BT_MESH_FOUNDATION_UNSPECIFIED = 0x1310 - # Returned when the NetKeyIndex and AppKeyIndex combination is not valid for a Config AppKey Update - SL_STATUS_BT_MESH_FOUNDATION_INVALID_BINDING = 0x1311 - # Wi-Fi Errors - # Invalid firmware keyset - SL_STATUS_WIFI_INVALID_KEY = 0x0B01 - # The firmware download took too long - SL_STATUS_WIFI_FIRMWARE_DOWNLOAD_TIMEOUT = 0x0B02 - # Unknown request ID or wrong interface ID used - SL_STATUS_WIFI_UNSUPPORTED_MESSAGE_ID = 0x0B03 - # The request is successful but some parameters have been ignored - SL_STATUS_WIFI_WARNING = 0x0B04 - # No Packets waiting to be received - SL_STATUS_WIFI_NO_PACKET_TO_RECEIVE = 0x0B05 - # The sleep mode is granted - SL_STATUS_WIFI_SLEEP_GRANTED = 0x0B08 - # The WFx does not go back to sleep - SL_STATUS_WIFI_SLEEP_NOT_GRANTED = 0x0B09 - # The SecureLink MAC key was not found - SL_STATUS_WIFI_SECURE_LINK_MAC_KEY_ERROR = 0x0B10 - # The SecureLink MAC key is already installed in OTP - SL_STATUS_WIFI_SECURE_LINK_MAC_KEY_ALREADY_BURNED = 0x0B11 - # The SecureLink MAC key cannot be installed in RAM - SL_STATUS_WIFI_SECURE_LINK_RAM_MODE_NOT_ALLOWED = 0x0B12 - # The SecureLink MAC key installation failed - SL_STATUS_WIFI_SECURE_LINK_FAILED_UNKNOWN_MODE = 0x0B13 - # SecureLink key (re)negotiation failed - SL_STATUS_WIFI_SECURE_LINK_EXCHANGE_FAILED = 0x0B14 - # The device is in an inappropriate state to perform the request - SL_STATUS_WIFI_WRONG_STATE = 0x0B18 - # The request failed due to regulatory limitations - SL_STATUS_WIFI_CHANNEL_NOT_ALLOWED = 0x0B19 - # The connection request failed because no suitable AP was found - SL_STATUS_WIFI_NO_MATCHING_AP = 0x0B1A - # The connection request was aborted by host - SL_STATUS_WIFI_CONNECTION_ABORTED = 0x0B1B - # The connection request failed because of a timeout - SL_STATUS_WIFI_CONNECTION_TIMEOUT = 0x0B1C - # The connection request failed because the AP rejected the device - SL_STATUS_WIFI_CONNECTION_REJECTED_BY_AP = 0x0B1D - # The connection request failed because the WPA handshake did not complete successfully - SL_STATUS_WIFI_CONNECTION_AUTH_FAILURE = 0x0B1E - # The request failed because the retry limit was exceeded - SL_STATUS_WIFI_RETRY_EXCEEDED = 0x0B1F - # The request failed because the MSDU life time was exceeded - SL_STATUS_WIFI_TX_LIFETIME_EXCEEDED = 0x0B20 + ## Generic Errors + # No error. + OK = 0x0000 + # Generic error. + FAIL = 0x0001 + + ## State Errors + # Generic invalid state error. + INVALID_STATE = 0x0002 + # Module is not ready for requested operation. + NOT_READY = 0x0003 + # Module is busy and cannot carry out requested operation. + BUSY = 0x0004 + # Operation is in progress and not yet complete (pass or fail). + IN_PROGRESS = 0x0005 + # Operation aborted. + ABORT = 0x0006 + # Operation timed out. + TIMEOUT = 0x0007 + # Operation not allowed per permissions. + PERMISSION = 0x0008 + # Non-blocking operation would block. + WOULD_BLOCK = 0x0009 + # Operation/module is Idle, cannot carry requested operation. + IDLE = 0x000A + # Operation cannot be done while construct is waiting. + IS_WAITING = 0x000B + # No task/construct waiting/pending for that action/event. + NONE_WAITING = 0x000C + # Operation cannot be done while construct is suspended. + SUSPENDED = 0x000D + # Feature not available due to software configuration. + NOT_AVAILABLE = 0x000E + # Feature not supported. + NOT_SUPPORTED = 0x000F + # Initialization failed. + INITIALIZATION = 0x0010 + # Module has not been initialized. + NOT_INITIALIZED = 0x0011 + # Module has already been initialized. + ALREADY_INITIALIZED = 0x0012 + # Object/construct has been deleted. + DELETED = 0x0013 + # Illegal call from ISR. + ISR = 0x0014 + # Illegal call because network is up. + NETWORK_UP = 0x0015 + # Illegal call because network is down. + NETWORK_DOWN = 0x0016 + # Failure due to not being joined in a network. + NOT_JOINED = 0x0017 + # Invalid operation as there are no beacons. + NO_BEACONS = 0x0018 + + ## Allocation/ownership Errors + # Generic allocation error. + ALLOCATION_FAILED = 0x0019 + # No more resource available to perform the operation. + NO_MORE_RESOURCE = 0x001A + # Item/list/queue is empty. + EMPTY = 0x001B + # Item/list/queue is full. + FULL = 0x001C + # Item would overflow. + WOULD_OVERFLOW = 0x001D + # Item/list/queue has been overflowed. + HAS_OVERFLOWED = 0x001E + # Generic ownership error. + OWNERSHIP = 0x001F + # Already/still owning resource. + IS_OWNER = 0x0020 + + ## Invalid Parameters Errors + # Generic invalid argument or consequence of invalid argument. + INVALID_PARAMETER = 0x0021 + # Invalid null pointer received as argument. + NULL_POINTER = 0x0022 + # Invalid configuration provided. + INVALID_CONFIGURATION = 0x0023 + # Invalid mode. + INVALID_MODE = 0x0024 + # Invalid handle. + INVALID_HANDLE = 0x0025 + # Invalid type for operation. + INVALID_TYPE = 0x0026 + # Invalid index. + INVALID_INDEX = 0x0027 + # Invalid range. + INVALID_RANGE = 0x0028 + # Invalid key. + INVALID_KEY = 0x0029 + # Invalid credentials. + INVALID_CREDENTIALS = 0x002A + # Invalid count. + INVALID_COUNT = 0x002B + # Invalid signature / verification failed. + INVALID_SIGNATURE = 0x002C + # Item could not be found. + NOT_FOUND = 0x002D + # Item already exists. + ALREADY_EXISTS = 0x002E + + ## IO/Communication Errors + # Generic I/O failure. + IO = 0x002F + # I/O failure due to timeout. + IO_TIMEOUT = 0x0030 + # Generic transmission error. + TRANSMIT = 0x0031 + # Transmit underflowed. + TRANSMIT_UNDERFLOW = 0x0032 + # Transmit is incomplete. + TRANSMIT_INCOMPLETE = 0x0033 + # Transmit is busy. + TRANSMIT_BUSY = 0x0034 + # Generic reception error. + RECEIVE = 0x0035 + # Failed to read on/via given object. + OBJECT_READ = 0x0036 + # Failed to write on/via given object. + OBJECT_WRITE = 0x0037 + # Message is too long. + MESSAGE_TOO_LONG = 0x0038 + + ## EEPROM/Flash Errors + # EEPROM MFG version mismatch. + EEPROM_MFG_VERSION_MISMATCH = 0x0039 + # EEPROM Stack version mismatch. + EEPROM_STACK_VERSION_MISMATCH = 0x003A + # Flash write is inhibited. + FLASH_WRITE_INHIBITED = 0x003B + # Flash verification failed. + FLASH_VERIFY_FAILED = 0x003C + # Flash programming failed. + FLASH_PROGRAM_FAILED = 0x003D + # Flash erase failed. + FLASH_ERASE_FAILED = 0x003E + + ## MAC Errors + # MAC no data. + MAC_NO_DATA = 0x003F + # MAC no ACK received. + MAC_NO_ACK_RECEIVED = 0x0040 + # MAC indirect timeout. + MAC_INDIRECT_TIMEOUT = 0x0041 + # MAC unknown header type. + MAC_UNKNOWN_HEADER_TYPE = 0x0042 + # MAC ACK unknown header type. + MAC_ACK_HEADER_TYPE = 0x0043 + # MAC command transmit failure. + MAC_COMMAND_TRANSMIT_FAILURE = 0x0044 + + ## CLI_STORAGE Errors + # Error in open NVM + CLI_STORAGE_NVM_OPEN_ERROR = 0x0045 + + ## Security status codes + # Image checksum is not valid. + SECURITY_IMAGE_CHECKSUM_ERROR = 0x0046 + # Decryption failed + SECURITY_DECRYPT_ERROR = 0x0047 + + ## Command status codes + # Command was not recognized + COMMAND_IS_INVALID = 0x0048 + # Command or parameter maximum length exceeded + COMMAND_TOO_LONG = 0x0049 + # Data received does not form a complete command + COMMAND_INCOMPLETE = 0x004A + + ## Misc Errors + # Bus error, e.g. invalid DMA address + BUS_ERROR = 0x004B + + ## Unified MAC Errors + # CCA failure. + CCA_FAILURE = 0x004C + + ## Scan errors + # MAC scanning. + MAC_SCANNING = 0x004D + # MAC incorrect scan type. + MAC_INCORRECT_SCAN_TYPE = 0x004E + # Invalid channel mask. + INVALID_CHANNEL_MASK = 0x004F + # Bad scan duration. + BAD_SCAN_DURATION = 0x0050 + + ## MAC transmit related status + # The MAC transmit queue is full + MAC_TRANSMIT_QUEUE_FULL = 0x0053 + # The transmit attempt failed because the radio scheduler could not find a slot to transmit this packet in or a higher priority event interrupted it + TRANSMIT_SCHEDULER_FAIL = 0x0054 + # An unsupported channel setting was specified + TRANSMIT_INVALID_CHANNEL = 0x0055 + # An unsupported power setting was specified + TRANSMIT_INVALID_POWER = 0x0056 + # The expected ACK was received after the last transmission + TRANSMIT_ACK_RECEIVED = 0x0057 + # The transmit attempt was blocked from going over the air. Typically this is due to the Radio Hold Off (RHO) or Coexistence plugins as they can prevent transmits based on external signals. + TRANSMIT_BLOCKED = 0x0058 + + ## NVM3 specific errors + # The initialization was aborted as the NVM3 instance is not aligned properly in memory + NVM3_ALIGNMENT_INVALID = 0x0059 + # The initialization was aborted as the size of the NVM3 instance is too small + NVM3_SIZE_TOO_SMALL = 0x005A + # The initialization was aborted as the NVM3 page size is not supported + NVM3_PAGE_SIZE_NOT_SUPPORTED = 0x005B + # The application that there was an error initializing some of the tokens + NVM3_TOKEN_INIT_FAILED = 0x005C + # The initialization was aborted as the NVM3 instance was already opened with other parameters + NVM3_OPENED_WITH_OTHER_PARAMETERS = 0x005D + + ## Zigbee status codes + # Packet is dropped by packet-handoff callbacks + ZIGBEE_PACKET_HANDOFF_DROPPED = 0x0C01 + # The APS layer attempted to send or deliver a message and failed + ZIGBEE_DELIVERY_FAILED = 0x0C02 + # The maximum number of in-flight messages ::EMBER_APS_UNICAST_MESSAGE_COUNT has been reached + ZIGBEE_MAX_MESSAGE_LIMIT_REACHED = 0x0C03 + # The application is trying to delete or overwrite a binding that is in use + ZIGBEE_BINDING_IS_ACTIVE = 0x0C04 + # The application is trying to overwrite an address table entry that is in use + ZIGBEE_ADDRESS_TABLE_ENTRY_IS_ACTIVE = 0x0C05 + # After moving, a mobile node's attempt to re-establish contact with the network failed + ZIGBEE_MOVE_FAILED = 0x0C06 + # The local node ID has changed. The application can get the new node ID by calling ::sl_zigbee_get_node_id() + ZIGBEE_NODE_ID_CHANGED = 0x0C07 + # The chosen security level is not supported by the stack + ZIGBEE_INVALID_SECURITY_LEVEL = 0x0C08 + # An error occurred when trying to encrypt at the APS Level + ZIGBEE_IEEE_ADDRESS_DISCOVERY_IN_PROGRESS = 0x0C09 + # An error occurred when trying to encrypt at the APS Level + ZIGBEE_APS_ENCRYPTION_ERROR = 0x0C0A + # There was an attempt to form or join a network with security without calling ::sl_zigbee_set_initial_security_state() first + ZIGBEE_SECURITY_STATE_NOT_SET = 0x0C0B + # There was an attempt to broadcast a key switch too quickly after broadcasting the next network key. The Trust Center must wait at least a period equal to the broadcast timeout so that all routers have a chance to receive the broadcast of the new network key + ZIGBEE_TOO_SOON_FOR_SWITCH_KEY = 0x0C0C + # The received signature corresponding to the message that was passed to the CBKE Library failed verification and is not valid + ZIGBEE_SIGNATURE_VERIFY_FAILURE = 0x0C0D + # The message could not be sent because the link key corresponding to the destination is not authorized for use in APS data messages + ZIGBEE_KEY_NOT_AUTHORIZED = 0x0C0E + # The application tried to use a binding that has been remotely modified and the change has not yet been reported to the application + ZIGBEE_BINDING_HAS_CHANGED = 0x0C0F + # The EUI of the Trust center has changed due to a successful rejoin after TC Swapout + ZIGBEE_TRUST_CENTER_SWAP_EUI_HAS_CHANGED = 0x0C10 + # A Trust Center Swapout Rejoin has occurred without the EUI of the TC changing + ZIGBEE_TRUST_CENTER_SWAP_EUI_HAS_NOT_CHANGED = 0x0C11 + # An attempt to generate random bytes failed because of insufficient random data from the radio + ZIGBEE_INSUFFICIENT_RANDOM_DATA = 0x0C12 + # A Zigbee route error command frame was received indicating that a source routed message from this node failed en route + ZIGBEE_SOURCE_ROUTE_FAILURE = 0x0C13 + # A Zigbee route error command frame was received indicating that a message sent to this node along a many-to-one route failed en route + ZIGBEE_MANY_TO_ONE_ROUTE_FAILURE = 0x0C14 + # A critical and fatal error indicating that the version of the stack trying to run does not match with the chip it's running on + ZIGBEE_STACK_AND_HARDWARE_MISMATCH = 0x0C15 + # The local PAN ID has changed. The application can get the new PAN ID by calling ::emberGetPanId() + ZIGBEE_PAN_ID_CHANGED = 0x0C16 + # The channel has changed. + ZIGBEE_CHANNEL_CHANGED = 0x0C17 + # The network has been opened for joining. + ZIGBEE_NETWORK_OPENED = 0x0C18 + # The network has been closed for joining. + ZIGBEE_NETWORK_CLOSED = 0x0C19 + # An attempt was made to join a Secured Network using a pre-configured key, but the Trust Center sent back a Network Key in-the-clear when an encrypted Network Key was required. (::EMBER_REQUIRE_ENCRYPTED_KEY) + ZIGBEE_RECEIVED_KEY_IN_THE_CLEAR = 0x0C1A + # An attempt was made to join a Secured Network, but the device did not receive a Network Key. + ZIGBEE_NO_NETWORK_KEY_RECEIVED = 0x0C1B + # After a device joined a Secured Network, a Link Key was requested (::EMBER_GET_LINK_KEY_WHEN_JOINING) but no response was ever received. + ZIGBEE_NO_LINK_KEY_RECEIVED = 0x0C1C + # An attempt was made to join a Secured Network without a pre-configured key, but the Trust Center sent encrypted data using a pre-configured key. + ZIGBEE_PRECONFIGURED_KEY_REQUIRED = 0x0C1D + # A Zigbee EZSP error has occurred. Track the origin and corresponding EzspStatus for more info. + ZIGBEE_EZSP_ERROR = 0x0C1E + + @classmethod + def from_ember_status( + cls: type[sl_Status], status: EmberStatus | EzspStatus + ) -> sl_Status: + if isinstance(status, cls): + return status + + key = type(status), status + + if key not in SL_STATUS_MAP: + LOGGER.warning( + "Unknown status %r, converting to generic %r", + status, + cls.FAIL, + stacklevel=2, + ) + return cls.FAIL + + return SL_STATUS_MAP[key] + + +# Generic mapping that standardizes status codes +# fmt: off +SL_STATUS_MAP: dict[EzspStatus | EmberStatus, sl_Status] = { + (type(k), k): v for k, v in [ + # EZSP status + (EzspStatus.SUCCESS, sl_Status.OK), + (EzspStatus.ERROR_INVALID_ID, sl_Status.INVALID_PARAMETER), + (EzspStatus.ERROR_OUT_OF_MEMORY, sl_Status.NO_MORE_RESOURCE), + (EzspStatus.ERROR_INVALID_CALL, sl_Status.INVALID_PARAMETER), + # Ember status + (EmberStatus.SUCCESS, sl_Status.OK), + (EmberStatus.ERR_FATAL, sl_Status.FAIL), + (EmberStatus.NOT_FOUND, sl_Status.NOT_FOUND), + (EmberStatus.TABLE_ENTRY_ERASED, sl_Status.NOT_FOUND), + (EmberStatus.INDEX_OUT_OF_RANGE, sl_Status.INVALID_INDEX), + (EmberStatus.NOT_JOINED, sl_Status.NOT_JOINED), + (EmberStatus.NETWORK_UP, sl_Status.NETWORK_UP), + (EmberStatus.NETWORK_DOWN, sl_Status.NETWORK_DOWN), + (EmberStatus.NETWORK_OPENED, sl_Status.ZIGBEE_NETWORK_OPENED), + (EmberStatus.NETWORK_CLOSED, sl_Status.ZIGBEE_NETWORK_CLOSED), + # Network status codes + (EmberStatus.MAC_INDIRECT_TIMEOUT, sl_Status.MAC_INDIRECT_TIMEOUT), + (EmberStatus.SOURCE_ROUTE_FAILURE, sl_Status.ZIGBEE_SOURCE_ROUTE_FAILURE), + (EmberStatus.MANY_TO_ONE_ROUTE_FAILURE, sl_Status.ZIGBEE_MANY_TO_ONE_ROUTE_FAILURE), + (EmberStatus.MAX_MESSAGE_LIMIT_REACHED, sl_Status.ZIGBEE_MAX_MESSAGE_LIMIT_REACHED), + (EmberStatus.NETWORK_BUSY, sl_Status.ZIGBEE_MAX_MESSAGE_LIMIT_REACHED), + (EmberStatus.DELIVERY_FAILED, sl_Status.ZIGBEE_DELIVERY_FAILED), + (EmberStatus.NO_BUFFERS, sl_Status.ALLOCATION_FAILED), # TODO: see what the actual mapping is + ] +} +# fmt: on class EmberDistinguishedNodeId(basic.enum16): diff --git a/bellows/zigbee/application.py b/bellows/zigbee/application.py index 3159f052..7571fb2a 100644 --- a/bellows/zigbee/application.py +++ b/bellows/zigbee/application.py @@ -119,7 +119,7 @@ async def add_endpoint(self, descriptor: zdo_t.SimpleDescriptor) -> None: descriptor.output_clusters, ) - if status != t.EmberStatus.SUCCESS: + if t.sl_Status.from_ember_status(status) != t.sl_Status.OK: raise StackAlreadyRunning() self._created_device_endpoints.append(descriptor) @@ -168,15 +168,12 @@ async def _ensure_network_running(self) -> bool: if state == self._ezsp.types.EmberNetworkStatus.JOINED_NETWORK: return False - with self._ezsp.wait_for_stack_status(t.EmberStatus.NETWORK_UP) as stack_status: - if self._ezsp.ezsp_version >= 6: - (init_status,) = await self._ezsp.networkInit(0x0000) - else: - (init_status,) = await self._ezsp.networkInitExtended(0x0000) + with self._ezsp.wait_for_stack_status(t.sl_Status.NETWORK_UP) as stack_status: + init_status = await self._ezsp.initialize_network() - if init_status == t.EmberStatus.NOT_JOINED: + if init_status == t.sl_Status.NOT_JOINED: raise NetworkNotFormed("Node is not part of a network") - elif init_status != t.EmberStatus.SUCCESS: + elif init_status != t.sl_Status.OK: raise ControllerError(f"Failed to initialize network: {init_status!r}") async with asyncio_timeout(NETWORK_UP_TIMEOUT_S): @@ -244,7 +241,7 @@ async def load_network_info(self, *, load_devices=False) -> None: await self._ensure_network_running() status, node_type, nwk_params = await ezsp.getNetworkParameters() - assert status == t.EmberStatus.SUCCESS + assert t.sl_Status.from_ember_status(status) == t.sl_Status.OK if node_type != t.EmberNodeType.COORDINATOR: raise NetworkNotFormed("Device not configured as coordinator") @@ -266,64 +263,14 @@ async def load_network_info(self, *, load_devices=False) -> None: (status, security_level) = await ezsp.getConfigurationValue( ezsp.types.EzspConfigId.CONFIG_SECURITY_LEVEL ) - assert status == t.EmberStatus.SUCCESS + assert t.sl_Status.from_ember_status(status) == t.sl_Status.OK security_level = zigpy.types.uint8_t(security_level) - if ezsp.ezsp_version < 13: - (status, ezsp_network_key) = await ezsp.getKey( - ezsp.types.EmberKeyType.CURRENT_NETWORK_KEY - ) - assert status == t.EmberStatus.SUCCESS - network_key = util.ezsp_key_to_zigpy_key(ezsp_network_key, ezsp) - - (status, ezsp_tc_link_key) = await ezsp.getKey( - ezsp.types.EmberKeyType.TRUST_CENTER_LINK_KEY - ) - assert status == t.EmberStatus.SUCCESS - tc_link_key = util.ezsp_key_to_zigpy_key(ezsp_tc_link_key, ezsp) - else: - (network_key_data, status) = await ezsp.exportKey( - ezsp.types.sl_zb_sec_man_context_t( - core_key_type=ezsp.types.sl_zb_sec_man_key_type_t.NETWORK, - key_index=0, - derived_type=ezsp.types.sl_zb_sec_man_derived_key_type_t.NONE, - eui64=t.EUI64.convert("00:00:00:00:00:00:00:00"), - multi_network_index=0, - flags=ezsp.types.sl_zb_sec_man_flags_t.NONE, - psa_key_alg_permission=0, - ) - ) - assert status == t.EmberStatus.SUCCESS - - (status, network_key_info) = await ezsp.getNetworkKeyInfo() - assert status == t.EmberStatus.SUCCESS - - if not network_key_info.network_key_set: - raise NetworkNotFormed("Network key is not set") - - network_key = zigpy.state.Key( - key=network_key_data, - tx_counter=network_key_info.network_key_frame_counter, - seq=network_key_info.network_key_sequence_number, - ) - - (tc_link_key_data, status) = await ezsp.exportKey( - ezsp.types.sl_zb_sec_man_context_t( - core_key_type=ezsp.types.sl_zb_sec_man_key_type_t.TC_LINK, - key_index=0, - derived_type=ezsp.types.sl_zb_sec_man_derived_key_type_t.NONE, - eui64=t.EUI64.convert("00:00:00:00:00:00:00:00"), - multi_network_index=0, - flags=ezsp.types.sl_zb_sec_man_flags_t.NONE, - psa_key_alg_permission=0, - ) - ) - assert status == t.EmberStatus.SUCCESS - - tc_link_key = zigpy.state.Key(key=tc_link_key_data) + network_key = await ezsp.get_network_key() + tc_link_key = await ezsp.get_tc_link_key() (status, state) = await ezsp.getCurrentSecurityState() - assert status == t.EmberStatus.SUCCESS + assert t.sl_Status.from_ember_status(status) == t.sl_Status.OK stack_specific = {} @@ -368,84 +315,15 @@ async def load_network_info(self, *, load_devices=False) -> None: if not load_devices: return - (status, key_table_size) = await ezsp.getConfigurationValue( - ezsp.types.EzspConfigId.CONFIG_KEY_TABLE_SIZE - ) - - if ezsp.ezsp_version < 13: - for index in range(key_table_size): - (status, key) = await ezsp.getKeyTableEntry(index) - - if status == t.EmberStatus.INDEX_OUT_OF_RANGE: - break - elif status in ( - t.EmberStatus.TABLE_ENTRY_ERASED, - t.EmberStatus.NOT_FOUND, - ): - continue - - assert status == t.EmberStatus.SUCCESS - - self.state.network_info.key_table.append( - util.ezsp_key_to_zigpy_key(key, ezsp) - ) - else: - for index in range(key_table_size): - ( - eui64, - plaintext_key, - key_data, - status, - ) = await ezsp.exportLinkKeyByIndex(index) - if status != t.sl_Status.SL_STATUS_OK: - continue - - self.state.network_info.key_table.append( - zigpy.state.Key( - key=plaintext_key, - tx_counter=key_data.outgoing_frame_counter, - rx_counter=key_data.incoming_frame_counter, - partner_ieee=eui64, - ) - ) - - for idx in range(0, 255 + 1): - (status, *rsp) = await ezsp.getChildData(idx) - - if status == t.EmberStatus.NOT_JOINED: - continue - - if ezsp.ezsp_version >= 7: - nwk = rsp[0].id - eui64 = rsp[0].eui64 - node_type = rsp[0].type - else: - nwk, eui64, node_type = rsp + async for link_key in ezsp.read_link_keys(): + self.state.network_info.key_table.append(link_key) + async for nwk, eui64, _node_type in ezsp.read_child_data(): self.state.network_info.children.append(eui64) self.state.network_info.nwk_addresses[eui64] = nwk - # v4 can crash when getAddressTableRemoteNodeId(32) is received - # Error code: undefined_0x8a - if ezsp.ezsp_version > 4: - (status, addr_table_size) = await ezsp.getConfigurationValue( - ezsp.types.EzspConfigId.CONFIG_ADDRESS_TABLE_SIZE - ) - - for idx in range(addr_table_size): - (nwk,) = await ezsp.getAddressTableRemoteNodeId(idx) - - # Ignore invalid NWK entries - if nwk in t.EmberDistinguishedNodeId.__members__.values(): - continue - - (eui64,) = await ezsp.getAddressTableRemoteEui64(idx) - if eui64 == t.EUI64.convert("00:00:00:00:00:00:00:00"): - continue - - self.state.network_info.nwk_addresses[ - zigpy.types.EUI64(eui64) - ] = zigpy.types.NWK(nwk) + async for nwk, eui64 in ezsp.read_address_table(): + self.state.network_info.nwk_addresses[eui64] = nwk async def write_network_info( self, *, network_info: zigpy.state.NetworkInfo, node_info: zigpy.state.NodeInfo @@ -491,24 +369,8 @@ async def write_network_info( node_info.ieee = current_eui64 network_info.tc_link_key.partner_ieee = current_eui64 - if ezsp.ezsp_version > 4: - # Frame counters can only be set *before* we have joined a network - (state,) = await self._ezsp.networkState() - assert state == ezsp.types.EmberNetworkStatus.NO_NETWORK - - # Set NWK frame counter - (status,) = await ezsp.setValue( - ezsp.types.EzspValueId.VALUE_NWK_FRAME_COUNTER, - t.uint32_t(network_info.network_key.tx_counter).serialize(), - ) - assert status == t.EmberStatus.SUCCESS - - # Set APS frame counter - (status,) = await ezsp.setValue( - ezsp.types.EzspValueId.VALUE_APS_FRAME_COUNTER, - t.uint32_t(network_info.tc_link_key.tx_counter).serialize(), - ) - assert status == t.EmberStatus.SUCCESS + await self._ezsp.write_nwk_frame_counter(network_info.network_key.tx_counter) + await self._ezsp.write_aps_frame_counter(network_info.tc_link_key.tx_counter) use_hashed_tclk = ezsp.ezsp_version > 4 @@ -523,43 +385,17 @@ async def write_network_info( use_hashed_tclk=use_hashed_tclk, ) (status,) = await ezsp.setInitialSecurityState(initial_security_state) - assert status == t.EmberStatus.SUCCESS - - # Write APS link keys - for index, key in enumerate(network_info.key_table): - if ezsp.ezsp_version < 13: - # XXX: is there no way to set the outgoing frame counter or seq? - (status,) = await ezsp.addOrUpdateKeyTableEntry( - key.partner_ieee, True, key.key - ) - else: - (status,) = await ezsp.importLinkKey(index, key.partner_ieee, key.key) - - if status != t.EmberStatus.SUCCESS: - LOGGER.warning("Couldn't add %s key: %s", key, status) - - # Write the child table - if ezsp.ezsp_version >= 9: - index = 0 - - for child_eui64 in network_info.children: - if child_eui64 not in network_info.nwk_addresses: - continue - - await ezsp.setChildData( - index, - ezsp.types.EmberChildData( - eui64=child_eui64, - type=t.EmberNodeType.SLEEPY_END_DEVICE, - id=network_info.nwk_addresses[child_eui64], - # The rest are unused when setting child data - phy=0, - power=0, - timeout=0, - **({"timeout_remaining": 0} if ezsp.ezsp_version >= 10 else {}), - ), - ) - index += 1 + assert t.sl_Status.from_ember_status(status) == t.sl_Status.OK + + await ezsp.write_link_keys(network_info.key_table) + + children_with_nwk_addresses = { + eui64: network_info.nwk_addresses[eui64] + for eui64 in network_info.children + if eui64 in network_info.nwk_addresses + } + + await ezsp.write_child_data(children_with_nwk_addresses) # Set the network settings parameters = t.EmberNetworkParameters() @@ -576,17 +412,7 @@ async def write_network_info( await self._ensure_network_running() async def reset_network_info(self): - # The network must be running before we can leave it - try: - await self._ensure_network_running() - except zigpy.exceptions.NetworkNotFormed: - return - - await self._ezsp.leaveNetwork() - - # Clear the key table - (status,) = await self._ezsp.clearKeyTable() - assert status == t.EmberStatus.SUCCESS + await self._ezsp.factory_reset() # Reset the custom EUI64 await self._ezsp.reset_custom_eui64() @@ -594,6 +420,14 @@ async def reset_network_info(self): # We must reset when NV3 has changed await self._reset() + # The network must be running before we can leave it + try: + await self._ensure_network_running() + except zigpy.exceptions.NetworkNotFormed: + pass + else: + await self._ezsp.leaveNetwork() + async def _reset(self): self._ezsp.stop_ezsp() await self._ezsp.startup_reset() @@ -614,15 +448,78 @@ async def force_remove(self, dev): def ezsp_callback_handler(self, frame_name, args): LOGGER.debug("Received %s frame with %s", frame_name, args) if frame_name == "incomingMessageHandler": - self._handle_frame(*args) + if self._ezsp.ezsp_version >= 14: + ( + message_type, + aps_frame, + nwk, + _eui64, + binding_index, + address_index, + lqi, + rssi, + _timestamp, + message, + ) = args + else: + ( + message_type, + aps_frame, + lqi, + rssi, + nwk, + binding_index, + address_index, + message, + ) = args + + self._handle_frame( + message_type=message_type, + aps_frame=aps_frame, + lqi=lqi, + rssi=rssi, + sender=nwk, + binding_index=binding_index, + address_index=address_index, + message=message, + ) elif frame_name == "messageSentHandler": - self._handle_frame_sent(*args) + if self._ezsp.ezsp_version >= 14: + ( + status, + message_type, + destination, + aps_frame, + message_tag, + message, + ) = args + else: + ( + message_type, + destination, + aps_frame, + message_tag, + status, + message, + ) = args + status = t.sl_Status.from_ember_status(status) + + self._handle_frame_sent( + message_type=message_type, + destination=destination, + aps_frame=aps_frame, + message_tag=message_tag, + status=status, + message=message, + ) elif frame_name == "trustCenterJoinHandler": self._handle_tc_join_handler(*args) elif frame_name == "incomingRouteRecordHandler": self.handle_route_record(*args) elif frame_name == "incomingRouteErrorHandler": - self.handle_route_error(*args) + status, nwk = args + status = t.sl_Status.from_ember_status(status) + self.handle_route_error(status, nwk) elif frame_name == "_reset_controller_application": self.connection_lost(args[0]) elif frame_name == "idConflictHandler": @@ -683,10 +580,10 @@ def _handle_frame_sent( destination: t.EmberNodeId, aps_frame: t.EmberApsFrame, message_tag: int, - status: t.EmberStatus, + status: t.sl_Status, message: bytes, ): - if status == t.EmberStatus.SUCCESS: + if status == t.sl_Status.OK: msg = "success" else: msg = "failure" @@ -733,7 +630,9 @@ def _handle_frame_sent( async def _handle_no_such_device(self, sender: int) -> None: """Try to match unknown device by its EUI64 address.""" status, ieee = await self._ezsp.lookupEui64ByNodeId(sender) - if status == t.EmberStatus.SUCCESS: + status = t.sl_Status.from_ember_status(status) + + if status == t.sl_Status.OK: LOGGER.debug("Found %s ieee for %s sender", ieee, sender) self.handle_join(sender, ieee, 0) return @@ -777,7 +676,7 @@ async def _set_source_route( self, nwk: zigpy.types.NWK, relays: list[zigpy.types.NWK] ) -> bool: (res,) = await self._ezsp.setSourceRoute(nwk, relays) - return res == t.EmberStatus.SUCCESS + return t.sl_Status.from_ember_status(res) == t.sl_Status.OK async def energy_scan( self, channels: t.Channels, duration_exp: int, count: int @@ -866,36 +765,36 @@ async def send_packet(self, packet: zigpy.types.ZigbeePacket) -> None: t.EmberApsOption.APS_OPTION_ENABLE_ROUTE_DISCOVERY ) - status, _ = await self._ezsp.sendUnicast( - t.EmberOutgoingMessageType.OUTGOING_DIRECT, - t.EmberNodeId(packet.dst.address), - aps_frame, - message_tag, - packet.data.serialize(), + status, _ = await self._ezsp.send_unicast( + nwk=packet.dst.address, + aps_frame=aps_frame, + message_tag=message_tag, + data=packet.data.serialize(), ) elif packet.dst.addr_mode == zigpy.types.AddrMode.Group: - status, _ = await self._ezsp.sendMulticast( - aps_frame, - packet.radius, - packet.non_member_radius, - message_tag, - packet.data.serialize(), + status, _ = await self._ezsp.send_multicast( + aps_frame=aps_frame, + radius=packet.radius, + non_member_radius=packet.non_member_radius, + message_tag=message_tag, + data=packet.data.serialize(), ) elif packet.dst.addr_mode == zigpy.types.AddrMode.Broadcast: - status, _ = await self._ezsp.sendBroadcast( - t.EmberNodeId(packet.dst.address), - aps_frame, - packet.radius, - message_tag, - packet.data.serialize(), + status, _ = await self._ezsp.send_broadcast( + address=packet.dst.address, + aps_frame=aps_frame, + radius=packet.radius, + message_tag=message_tag, + aps_sequence=packet.tsn, + data=packet.data.serialize(), ) - if status == t.EmberStatus.SUCCESS: + if status == t.sl_Status.OK: break elif status not in ( - t.EmberStatus.MAX_MESSAGE_LIMIT_REACHED, - t.EmberStatus.NO_BUFFERS, - t.EmberStatus.NETWORK_BUSY, + t.sl_Status.ZIGBEE_MAX_MESSAGE_LIMIT_REACHED, + t.sl_Status.TRANSMIT_BUSY, + t.sl_Status.ALLOCATION_FAILED, ): raise zigpy.exceptions.DeliveryError( f"Failed to enqueue message: {status!r}", status @@ -928,7 +827,7 @@ async def send_packet(self, packet: zigpy.types.ZigbeePacket) -> None: async with asyncio_timeout(APS_ACK_TIMEOUT): send_status, _ = await req.result - if send_status != t.EmberStatus.SUCCESS: + if t.sl_Status.from_ember_status(send_status) != t.sl_Status.OK: raise zigpy.exceptions.DeliveryError( f"Failed to deliver message: {send_status!r}", send_status ) @@ -949,7 +848,7 @@ async def permit_with_link_key( status = await self._ezsp.add_transient_link_key(node, link_key) - if status != t.EmberStatus.SUCCESS: + if t.sl_Status.from_ember_status(status) != t.sl_Status.OK: raise ControllerError("Failed to set link key") if self._ezsp.ezsp_version >= 8: @@ -1050,5 +949,5 @@ def handle_route_record( ) self.handle_relays(nwk=nwk, relays=relays) - def handle_route_error(self, status: t.EmberStatus, nwk: t.EmberNodeId) -> None: + def handle_route_error(self, status: t.sl_Status, nwk: t.EmberNodeId) -> None: LOGGER.debug("Processing route error: status=%s, nwk=%s", status, nwk) diff --git a/bellows/zigbee/device.py b/bellows/zigbee/device.py index 9e4a6e7b..f34f3b80 100644 --- a/bellows/zigbee/device.py +++ b/bellows/zigbee/device.py @@ -56,29 +56,25 @@ def model(self) -> str: """Model.""" return "EZSP" - async def add_to_group(self, grp_id: int, name: str = None) -> t.EmberStatus: + async def add_to_group(self, grp_id: int, name: str = None) -> None: if grp_id in self.member_of: - return t.EmberStatus.SUCCESS + return app = self.device.application status = await app.multicast.subscribe(grp_id) - if status != t.EmberStatus.SUCCESS: - self.debug("Couldn't subscribe to 0x%04x group", grp_id) - return status + if status != t.sl_Status.OK: + raise ValueError(f"Couldn't subscribe to 0x{grp_id:04x} group") group = app.groups.add_group(grp_id, name) group.add_member(self) - return status - async def remove_from_group(self, grp_id: int) -> t.EmberStatus: + async def remove_from_group(self, grp_id: int) -> None: if grp_id not in self.member_of: - return t.EmberStatus.SUCCESS + return app = self.device.application status = await app.multicast.unsubscribe(grp_id) - if status != t.EmberStatus.SUCCESS: - self.debug("Couldn't unsubscribe 0x%04x group", grp_id) - return status + if status != t.sl_Status.OK: + raise ValueError(f"Couldnt't unsubscribe from 0x{grp_id:04x} group") app.groups[grp_id].remove_member(self) - return status diff --git a/bellows/zigbee/repairs.py b/bellows/zigbee/repairs.py index 671f864d..4a33ed7a 100644 --- a/bellows/zigbee/repairs.py +++ b/bellows/zigbee/repairs.py @@ -17,7 +17,7 @@ async def fix_invalid_tclk_partner_ieee(ezsp: EZSP) -> bool: ieee = zigpy.types.EUI64(ieee) (status, state) = await ezsp.getCurrentSecurityState() - assert status == t.EmberStatus.SUCCESS + assert t.sl_Status.from_ember_status(status) == t.sl_Status.OK if state.trustCenterLongAddress == ieee: return False @@ -30,7 +30,7 @@ async def fix_invalid_tclk_partner_ieee(ezsp: EZSP) -> bool: try: rsp = await ezsp.getTokenData(t.NV3KeyId.NVM3KEY_STACK_TRUST_CENTER, 0) - assert rsp.status == t.EmberStatus.SUCCESS + assert t.sl_Status.from_ember_status(rsp.status) == t.sl_Status.OK except (InvalidCommandError, AttributeError, AssertionError): LOGGER.warning("NV3 interface not available in this firmware, please upgrade!") return False @@ -44,6 +44,6 @@ async def fix_invalid_tclk_partner_ieee(ezsp: EZSP) -> bool: 0, token.replace(eui64=ieee).serialize(), ) - assert status == t.EmberStatus.SUCCESS + assert t.sl_Status.from_ember_status(status) == t.sl_Status.OK return True diff --git a/pyproject.toml b/pyproject.toml index 9ecfb1f2..714c51aa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,6 +65,7 @@ max-line-length = 88 ignore = "W503,E203,E501,D202" per-file-ignores = """ tests/*:F811,F401,F403 + bellows/types/named.py:E266 """ [tool.coverage.run] diff --git a/tests/async_mock.py b/tests/async_mock.py deleted file mode 100644 index 8257ddd3..00000000 --- a/tests/async_mock.py +++ /dev/null @@ -1,9 +0,0 @@ -"""Mock utilities that are async aware.""" -import sys - -if sys.version_info[:2] < (3, 8): - from asynctest.mock import * # noqa - - AsyncMock = CoroutineMock # noqa: F405 -else: - from unittest.mock import * # noqa diff --git a/tests/test_application.py b/tests/test_application.py index ebcb32dd..f0ab3caa 100644 --- a/tests/test_application.py +++ b/tests/test_application.py @@ -1,9 +1,11 @@ import asyncio import contextlib +import importlib.metadata import logging from unittest.mock import AsyncMock, MagicMock, PropertyMock, call, patch, sentinel import pytest +import zigpy.backups import zigpy.config import zigpy.device import zigpy.exceptions @@ -19,6 +21,7 @@ import bellows.ezsp.v7.types as ezsp_t7 import bellows.ezsp.v8.types as ezsp_t8 from bellows.ezsp.v9.commands import GetTokenDataRsp +import bellows.types import bellows.types.struct import bellows.uart as uart import bellows.zigbee.application @@ -121,7 +124,7 @@ def _create_app_for_startup( nwk_type, ieee, auto_form=False, - init=0, + init=bellows.types.sl_Status.OK, ezsp_version=4, board_info=True, network_state=t.EmberNetworkStatus.JOINED_NETWORK, @@ -161,9 +164,10 @@ async def nop_mock(): ezsp_mock._command = AsyncMock(return_value=t.EmberStatus.SUCCESS) 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.initialize_network = AsyncMock(return_value=init) + ezsp_mock.getNetworkParameters = AsyncMock( + return_value=[t.EmberStatus.SUCCESS, 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) ezsp_mock.startScan = AsyncMock(return_value=[[c, 1] for c in range(11, 26 + 1)]) @@ -183,23 +187,35 @@ async def nop_mock(): ezsp_mock.leaveNetwork = AsyncMock(side_effect=mock_leave) ezsp_mock.reset = AsyncMock() ezsp_mock.version = AsyncMock() - ezsp_mock.getConfigurationValue = AsyncMock(return_value=(0, 1)) + ezsp_mock.getConfigurationValue = AsyncMock(return_value=[t.EmberStatus.SUCCESS, 1]) ezsp_mock.update_policies = AsyncMock() ezsp_mock.networkState = AsyncMock(return_value=[network_state]) - ezsp_mock.getKey = AsyncMock( - return_value=[ - t.EmberStatus.SUCCESS, - t.EmberKeyStruct( - bitmask=t.EmberKeyStructBitmask.KEY_HAS_SEQUENCE_NUMBER - | t.EmberKeyStructBitmask.KEY_HAS_OUTGOING_FRAME_COUNTER, - type=t.EmberKeyType.CURRENT_NETWORK_KEY, - key=t.KeyData(b"ActualNetworkKey"), - outgoingFrameCounter=t.uint32_t(0x12345678), - incomingFrameCounter=t.uint32_t(0x00000000), - sequenceNumber=t.uint8_t(1), - partnerEUI64=t.EUI64.convert("ff:ff:ff:ff:ff:ff:ff:ff"), - ), - ] + ezsp_mock.factory_reset = AsyncMock() + ezsp_mock.write_nwk_frame_counter = AsyncMock() + ezsp_mock.write_aps_frame_counter = AsyncMock() + ezsp_mock.setInitialSecurityState = AsyncMock(return_value=[t.EmberStatus.SUCCESS]) + ezsp_mock.write_link_keys = AsyncMock() + ezsp_mock.write_child_data = AsyncMock() + ezsp_mock.formNetwork = AsyncMock() + + ezsp_mock.get_network_key = AsyncMock( + return_value=zigpy.state.Key( + key=t.KeyData(b"ActualNetworkKey"), + tx_counter=t.uint32_t(0x12345678), + rx_counter=t.uint32_t(0x00000000), + seq=t.uint8_t(1), + partner_ieee=t.EUI64.convert("ff:ff:ff:ff:ff:ff:ff:ff"), + ) + ) + + ezsp_mock.get_tc_link_key = AsyncMock( + return_value=zigpy.state.Key( + key=t.KeyData(b"thehashedlinkkey"), + tx_counter=t.uint32_t(0x87654321), + rx_counter=t.uint32_t(0x00000000), + seq=t.uint8_t(0), + partner_ieee=t.EUI64.convert("ff:ff:ff:ff:ff:ff:ff:ff"), + ) ) ezsp_mock.getCurrentSecurityState = AsyncMock( @@ -223,11 +239,49 @@ async def nop_mock(): ) ezsp_mock.setMulticastTableEntry = AsyncMock(return_value=[t.EmberStatus.SUCCESS]) + ezsp_mock.read_link_keys = MagicMock() + ezsp_mock.read_link_keys.return_value.__aiter__.return_value = [ + zigpy.state.Key( + key=t.KeyData(b"test_link_key_01"), + tx_counter=12345, + rx_counter=67890, + seq=1, + partner_ieee=t.EUI64.convert("aa:bb:cc:dd:ee:ff:00:11"), + ), + zigpy.state.Key( + key=t.KeyData(b"test_link_key_02"), + tx_counter=54321, + rx_counter=98765, + seq=2, + partner_ieee=t.EUI64.convert("11:22:33:44:55:66:77:88"), + ), + ] + + ezsp_mock.read_child_data = MagicMock() + ezsp_mock.read_child_data.return_value.__aiter__.return_value = [ + ( + 0x1234, + t.EUI64.convert("aa:bb:cc:dd:ee:ff:00:11"), + t.EmberNodeType.END_DEVICE, + ), + ( + 0x5678, + t.EUI64.convert("11:22:33:44:55:66:77:88"), + t.EmberNodeType.END_DEVICE, + ), + ] + + ezsp_mock.read_address_table = MagicMock() + ezsp_mock.read_address_table.return_value.__aiter__.return_value = [ + (0xABCD, t.EUI64.convert("ab:cd:00:11:22:33:44:55")), + (0xDCBA, t.EUI64.convert("dc:ba:00:11:22:33:44:55")), + ] + app.permit = AsyncMock() def form_network(): ezsp_mock.getNetworkParameters.return_value = [ - 0, + t.EmberStatus.SUCCESS, t.EmberNodeType.COORDINATOR, nwk_params, ] @@ -273,11 +327,6 @@ async def _test_startup( ) as ezsp_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 @@ -320,7 +369,7 @@ async def test_startup_status_not_joined(app, ieee): t.EmberNodeType.COORDINATOR, ieee, auto_form=False, - init=t.EmberStatus.NOT_JOINED, + init=bellows.types.sl_Status.NOT_JOINED, network_state=t.EmberNetworkStatus.NO_NETWORK, ) @@ -333,7 +382,7 @@ async def test_startup_status_unknown(app, ieee): t.EmberNodeType.COORDINATOR, ieee, auto_form=False, - init=t.EmberStatus.ERR_FATAL, + init=bellows.types.sl_Status.FAIL, network_state=t.EmberNetworkStatus.NO_NETWORK, ) @@ -502,11 +551,11 @@ def test_frame_handler_ignored(app, aps_frame): def test_send_failure(app, aps, ieee, msg_type): req = app._pending[(0xBEED, 254)] = MagicMock() app.ezsp_callback_handler( - "messageSentHandler", [msg_type, 0xBEED, aps, 254, sentinel.status, b""] + "messageSentHandler", [msg_type, 0xBEED, aps, 254, t.EmberStatus.SUCCESS, b""] ) assert req.result.set_exception.call_count == 0 assert req.result.set_result.call_count == 1 - assert req.result.set_result.call_args[0][0][0] is sentinel.status + assert req.result.set_result.call_args[0][0][0] is bellows.types.sl_Status.OK def test_dup_send_failure(app, aps, ieee): @@ -550,13 +599,13 @@ def test_send_success(app, aps, ieee): 0xBEED, aps, 253, - sentinel.success, + t.EmberStatus.SUCCESS, b"", ], ) assert req.result.set_exception.call_count == 0 assert req.result.set_result.call_count == 1 - assert req.result.set_result.call_args[0][0][0] is sentinel.success + assert req.result.set_result.call_args[0][0][0] is bellows.types.sl_Status.OK def test_unexpected_send_success(app, aps, ieee): @@ -693,7 +742,16 @@ def packet(): ) -async def _test_send_packet_unicast(app, packet, *, statuses=(t.EmberStatus.SUCCESS,)): +async def _test_send_packet_unicast( + app, + packet, + *, + statuses=(bellows.types.sl_Status.OK,), + options=( + t.EmberApsOption.APS_OPTION_RETRY + | t.EmberApsOption.APS_OPTION_ENABLE_ROUTE_DISCOVERY + ), +): def send_unicast(*args, **kwargs): nonlocal statuses @@ -719,29 +777,30 @@ def send_unicast(*args, **kwargs): return [status, 0x12] - app._ezsp.sendUnicast = AsyncMock(side_effect=send_unicast) + app._ezsp.send_unicast = AsyncMock(side_effect=send_unicast) app.get_sequence = MagicMock(return_value=sentinel.msg_tag) expected_unicast_calls = len(statuses) await app.send_packet(packet) - assert app._ezsp.sendUnicast.call_count == expected_unicast_calls - assert ( - app._ezsp.sendUnicast.mock_calls[-1].args[0] - == t.EmberOutgoingMessageType.OUTGOING_DIRECT - ) - assert app._ezsp.sendUnicast.mock_calls[-1].args[1] == t.EmberNodeId(0x1234) - - aps_frame = app._ezsp.sendUnicast.mock_calls[-1].args[2] - assert aps_frame.profileId == packet.profile_id - assert aps_frame.clusterId == packet.cluster_id - assert aps_frame.sourceEndpoint == packet.src_ep - assert aps_frame.destinationEndpoint == packet.dst_ep - assert aps_frame.sequence == packet.tsn - assert aps_frame.groupId == 0x0000 + assert app._ezsp.send_unicast.call_count == expected_unicast_calls - assert app._ezsp.sendUnicast.mock_calls[-1].args[3] == sentinel.msg_tag - assert app._ezsp.sendUnicast.mock_calls[-1].args[4] == b"some data" + assert app._ezsp.send_unicast.mock_calls[-1] == ( + call( + nwk=t.EmberNodeId(0x1234), + aps_frame=t.EmberApsFrame( + profileId=packet.profile_id, + clusterId=packet.cluster_id, + sourceEndpoint=packet.src_ep, + destinationEndpoint=packet.dst_ep, + options=options, + groupId=0x0000, + sequence=packet.tsn, + ), + message_tag=sentinel.msg_tag, + data=b"some data", + ) + ) assert len(app._pending) == 0 @@ -775,7 +834,7 @@ async def test_send_packet_unicast_ieee_no_fallback(app, packet, caplog): with pytest.raises(ValueError): await _test_send_packet_unicast(app, packet) - assert app._ezsp.sendUnicast.call_count == 0 + assert app._ezsp.send_unicast.call_count == 0 async def test_send_packet_unicast_source_route_ezsp7(make_app, packet): @@ -784,7 +843,9 @@ async def test_send_packet_unicast_source_route_ezsp7(make_app, packet): app._ezsp.setSourceRoute = AsyncMock(return_value=(t.EmberStatus.SUCCESS,)) packet.source_route = [0x0001, 0x0002] - await _test_send_packet_unicast(app, packet) + await _test_send_packet_unicast( + app, packet, options=(t.EmberApsOption.APS_OPTION_RETRY) + ) assert app._ezsp.setSourceRoute.await_count == 1 app._ezsp.setSourceRoute.assert_called_once_with( @@ -797,9 +858,9 @@ async def test_send_packet_unicast_retries_success(app, packet): app, packet, statuses=( - t.EmberStatus.NO_BUFFERS, - t.EmberStatus.NO_BUFFERS, - t.EmberStatus.SUCCESS, + bellows.types.sl_Status.ALLOCATION_FAILED, + bellows.types.sl_Status.ALLOCATION_FAILED, + bellows.types.sl_Status.OK, ), ) @@ -817,9 +878,9 @@ async def test_send_packet_unicast_retries_failure(app, packet): app, packet, statuses=( - t.EmberStatus.NO_BUFFERS, - t.EmberStatus.NO_BUFFERS, - t.EmberStatus.NO_BUFFERS, + bellows.types.sl_Status.ALLOCATION_FAILED, + bellows.types.sl_Status.ALLOCATION_FAILED, + bellows.types.sl_Status.ALLOCATION_FAILED, ), ) @@ -856,7 +917,7 @@ async def send_message_sent_reply( ), ) - async def send_unicast(type, indexOrDestination, apsFrame, messageTag, message): + async def send_unicast(nwk, aps_frame, message_tag, data): nonlocal max_concurrency, in_flight_requests in_flight_requests += 1 @@ -864,13 +925,17 @@ async def send_unicast(type, indexOrDestination, apsFrame, messageTag, message): asyncio.create_task( send_message_sent_reply( - type, indexOrDestination, apsFrame, messageTag, message + t.EmberOutgoingMessageType.OUTGOING_DIRECT, + nwk, + aps_frame, + message_tag, + data, ) ) - return [t.EmberStatus.SUCCESS, 0x12] + return [bellows.types.sl_Status.OK, 0x12] - app._ezsp.sendUnicast = AsyncMock(side_effect=send_unicast) + app._ezsp.send_unicast = AsyncMock(side_effect=send_unicast) responses = await asyncio.gather(*[app.send_packet(packet) for _ in range(100)]) assert len(responses) == 100 @@ -884,7 +949,9 @@ async def test_send_packet_broadcast(app, packet): ) packet.radius = 30 - app._ezsp.sendBroadcast = AsyncMock(return_value=(t.EmberStatus.SUCCESS, 0x12)) + app._ezsp.send_broadcast = AsyncMock( + return_value=(bellows.types.named.sl_Status.OK, 0x12) + ) app.get_sequence = MagicMock(return_value=sentinel.msg_tag) asyncio.get_running_loop().call_soon( @@ -903,20 +970,27 @@ async def test_send_packet_broadcast(app, packet): ) await app.send_packet(packet) - assert app._ezsp.sendBroadcast.call_count == 1 - assert app._ezsp.sendBroadcast.mock_calls[0].args[0] == t.EmberNodeId(0xFFFE) - - aps_frame = app._ezsp.sendBroadcast.mock_calls[0].args[1] - assert aps_frame.profileId == packet.profile_id - assert aps_frame.clusterId == packet.cluster_id - assert aps_frame.sourceEndpoint == packet.src_ep - assert aps_frame.destinationEndpoint == packet.dst_ep - assert aps_frame.sequence == packet.tsn - assert aps_frame.groupId == 0x0000 - - assert app._ezsp.sendBroadcast.mock_calls[0].args[2] == packet.radius - assert app._ezsp.sendBroadcast.mock_calls[0].args[3] == sentinel.msg_tag - assert app._ezsp.sendBroadcast.mock_calls[0].args[4] == b"some data" + assert app._ezsp.send_broadcast.mock_calls == [ + call( + address=bellows.types.named.BroadcastAddress(0xFFFE), + aps_frame=t.EmberApsFrame( + profileId=packet.profile_id, + clusterId=packet.cluster_id, + sourceEndpoint=packet.src_ep, + destinationEndpoint=packet.dst_ep, + options=( + t.EmberApsOption.APS_OPTION_RETRY + | t.EmberApsOption.APS_OPTION_ENABLE_ROUTE_DISCOVERY + ), + groupId=0x0000, + sequence=packet.tsn, + ), + radius=packet.radius, + message_tag=sentinel.msg_tag, + aps_sequence=packet.tsn, + data=b"some data", + ) + ] assert len(app._pending) == 0 @@ -927,7 +1001,9 @@ async def test_send_packet_broadcast_ignored_delivery_failure(app, packet): ) packet.radius = 30 - app._ezsp.sendBroadcast = AsyncMock(return_value=(t.EmberStatus.SUCCESS, 0x12)) + app._ezsp.send_broadcast = AsyncMock( + return_value=(bellows.types.sl_Status.OK, 0x12) + ) app.get_sequence = MagicMock(return_value=sentinel.msg_tag) asyncio.get_running_loop().call_soon( @@ -948,20 +1024,27 @@ async def test_send_packet_broadcast_ignored_delivery_failure(app, packet): # Does not throw an error await app.send_packet(packet) - assert app._ezsp.sendBroadcast.call_count == 1 - assert app._ezsp.sendBroadcast.mock_calls[0].args[0] == t.EmberNodeId(0xFFFE) - - aps_frame = app._ezsp.sendBroadcast.mock_calls[0].args[1] - assert aps_frame.profileId == packet.profile_id - assert aps_frame.clusterId == packet.cluster_id - assert aps_frame.sourceEndpoint == packet.src_ep - assert aps_frame.destinationEndpoint == packet.dst_ep - assert aps_frame.sequence == packet.tsn - assert aps_frame.groupId == 0x0000 - - assert app._ezsp.sendBroadcast.mock_calls[0].args[2] == packet.radius - assert app._ezsp.sendBroadcast.mock_calls[0].args[3] == sentinel.msg_tag - assert app._ezsp.sendBroadcast.mock_calls[0].args[4] == b"some data" + assert app._ezsp.send_broadcast.mock_calls == [ + call( + address=bellows.types.named.BroadcastAddress(0xFFFE), + aps_frame=t.EmberApsFrame( + profileId=packet.profile_id, + clusterId=packet.cluster_id, + sourceEndpoint=packet.src_ep, + destinationEndpoint=packet.dst_ep, + options=( + t.EmberApsOption.APS_OPTION_RETRY + | t.EmberApsOption.APS_OPTION_ENABLE_ROUTE_DISCOVERY + ), + groupId=0x0000, + sequence=packet.tsn, + ), + radius=packet.radius, + message_tag=sentinel.msg_tag, + aps_sequence=packet.tsn, + data=b"some data", + ) + ] assert len(app._pending) == 0 @@ -973,7 +1056,9 @@ async def test_send_packet_multicast(app, packet): packet.radius = 5 packet.non_member_radius = 6 - app._ezsp.sendMulticast = AsyncMock(return_value=(t.EmberStatus.SUCCESS, 0x12)) + app._ezsp.send_multicast = AsyncMock( + return_value=(bellows.types.sl_Status.OK, 0x12) + ) app.get_sequence = MagicMock(return_value=sentinel.msg_tag) asyncio.get_running_loop().call_soon( @@ -992,20 +1077,26 @@ async def test_send_packet_multicast(app, packet): ) await app.send_packet(packet) - assert app._ezsp.sendMulticast.call_count == 1 - - aps_frame = app._ezsp.sendMulticast.mock_calls[0].args[0] - assert aps_frame.profileId == packet.profile_id - assert aps_frame.clusterId == packet.cluster_id - assert aps_frame.sourceEndpoint == packet.src_ep - assert aps_frame.destinationEndpoint == packet.dst_ep - assert aps_frame.sequence == packet.tsn - assert aps_frame.groupId == 0x1234 - - assert app._ezsp.sendMulticast.mock_calls[0].args[1] == packet.radius - assert app._ezsp.sendMulticast.mock_calls[0].args[2] == packet.non_member_radius - assert app._ezsp.sendMulticast.mock_calls[0].args[3] == sentinel.msg_tag - assert app._ezsp.sendMulticast.mock_calls[0].args[4] == b"some data" + assert app._ezsp.send_multicast.mock_calls == [ + call( + aps_frame=t.EmberApsFrame( + profileId=packet.profile_id, + clusterId=packet.cluster_id, + sourceEndpoint=packet.src_ep, + destinationEndpoint=packet.dst_ep, + options=( + t.EmberApsOption.APS_OPTION_RETRY + | t.EmberApsOption.APS_OPTION_ENABLE_ROUTE_DISCOVERY + ), + groupId=0x1234, + sequence=packet.tsn, + ), + radius=packet.radius, + non_member_radius=packet.non_member_radius, + message_tag=sentinel.msg_tag, + data=b"some data", + ) + ] assert len(app._pending) == 0 @@ -1226,15 +1317,13 @@ async def test_ezsp_add_to_group_ep(coordinator): grp_id = 0x2345 assert grp_id not in coordinator.endpoints[1].member_of - ret = await coordinator.endpoints[1].add_to_group(grp_id) - assert ret == t.EmberStatus.SUCCESS + await coordinator.endpoints[1].add_to_group(grp_id) assert mc.subscribe.call_count == 1 assert mc.subscribe.call_args[0][0] == grp_id assert grp_id in coordinator.endpoints[1].member_of mc.reset_mock() - ret = await coordinator.endpoints[1].add_to_group(grp_id) - assert ret == t.EmberStatus.SUCCESS + await coordinator.endpoints[1].add_to_group(grp_id) assert mc.subscribe.call_count == 0 @@ -1245,8 +1334,8 @@ async def test_ezsp_add_to_group_fail(coordinator): grp_id = 0x2345 assert grp_id not in coordinator.endpoints[1].member_of - ret = await coordinator.add_to_group(grp_id) - assert ret is None + with pytest.raises(ValueError): + await coordinator.add_to_group(grp_id) assert mc.subscribe.call_count == 1 assert mc.subscribe.call_args[0][0] == grp_id assert grp_id not in coordinator.endpoints[1].member_of @@ -1259,9 +1348,8 @@ async def test_ezsp_add_to_group_ep_fail(coordinator): grp_id = 0x2345 assert grp_id not in coordinator.endpoints[1].member_of - ret = await coordinator.endpoints[1].add_to_group(grp_id) - assert ret != t.EmberStatus.SUCCESS - assert ret is not None + with pytest.raises(ValueError): + await coordinator.endpoints[1].add_to_group(grp_id) assert mc.subscribe.call_count == 1 assert mc.subscribe.call_args[0][0] == grp_id assert grp_id not in coordinator.endpoints[1].member_of @@ -1277,8 +1365,7 @@ async def test_ezsp_remove_from_group(coordinator): grp.add_member(coordinator.endpoints[1]) assert grp_id in coordinator.endpoints[1].member_of - ret = await coordinator.remove_from_group(grp_id) - assert ret is None + await coordinator.remove_from_group(grp_id) assert mc.unsubscribe.call_count == 1 assert mc.unsubscribe.call_args[0][0] == grp_id assert grp_id not in coordinator.endpoints[1].member_of @@ -1294,15 +1381,13 @@ async def test_ezsp_remove_from_group_ep(coordinator): grp.add_member(coordinator.endpoints[1]) assert grp_id in coordinator.endpoints[1].member_of - ret = await coordinator.endpoints[1].remove_from_group(grp_id) - assert ret == t.EmberStatus.SUCCESS + await coordinator.endpoints[1].remove_from_group(grp_id) assert mc.unsubscribe.call_count == 1 assert mc.unsubscribe.call_args[0][0] == grp_id assert grp_id not in coordinator.endpoints[1].member_of mc.reset_mock() - ret = await coordinator.endpoints[1].remove_from_group(grp_id) - assert ret == t.EmberStatus.SUCCESS + await coordinator.endpoints[1].remove_from_group(grp_id) assert mc.subscribe.call_count == 0 @@ -1316,8 +1401,9 @@ async def test_ezsp_remove_from_group_fail(coordinator): grp.add_member(coordinator.endpoints[1]) assert grp_id in coordinator.endpoints[1].member_of - ret = await coordinator.remove_from_group(grp_id) - assert ret is None + + with pytest.raises(ValueError): + await coordinator.remove_from_group(grp_id) assert mc.unsubscribe.call_count == 1 assert mc.unsubscribe.call_args[0][0] == grp_id @@ -1332,9 +1418,10 @@ async def test_ezsp_remove_from_group_fail_ep(coordinator): grp.add_member(coordinator.endpoints[1]) assert grp_id in coordinator.endpoints[1].member_of - ret = await coordinator.endpoints[1].remove_from_group(grp_id) - assert ret != t.EmberStatus.SUCCESS - assert ret is not None + + with pytest.raises(ValueError): + await coordinator.endpoints[1].remove_from_group(grp_id) + assert mc.unsubscribe.call_count == 1 assert mc.unsubscribe.call_args[0][0] == grp_id @@ -1493,8 +1580,11 @@ async def test_ensure_network_running_joined(app): ezsp = app._ezsp # Make initialization take two attempts - ezsp.networkInit = AsyncMock( - side_effect=[(t.EmberStatus.NETWORK_BUSY,), (t.EmberStatus.SUCCESS,)] + ezsp.initialize_network = AsyncMock( + side_effect=[ + bellows.types.sl_Status.INVALID_PARAMETER, + bellows.types.sl_Status.OK, + ] ) ezsp.networkState = AsyncMock( return_value=[ezsp.types.EmberNetworkStatus.JOINED_NETWORK] @@ -1504,7 +1594,7 @@ async def test_ensure_network_running_joined(app): assert not rsp - ezsp.networkInit.assert_not_called() + ezsp.initialize_network.assert_not_called() async def test_ensure_network_running_not_joined_failure(app): @@ -1512,13 +1602,15 @@ async def test_ensure_network_running_not_joined_failure(app): ezsp.networkState = AsyncMock( return_value=[ezsp.types.EmberNetworkStatus.NO_NETWORK] ) - ezsp.networkInit = AsyncMock(return_value=[ezsp.types.EmberStatus.INVALID_CALL]) + ezsp.initialize_network = AsyncMock( + return_value=bellows.types.sl_Status.INVALID_PARAMETER + ) with pytest.raises(zigpy.exceptions.ControllerException): await app._ensure_network_running() ezsp.networkState.assert_called_once() - ezsp.networkInit.assert_called_once() + ezsp.initialize_network.assert_called_once() async def test_ensure_network_running_not_joined_success(app): @@ -1526,13 +1618,13 @@ async def test_ensure_network_running_not_joined_success(app): ezsp.networkState = AsyncMock( return_value=[ezsp.types.EmberNetworkStatus.NO_NETWORK] ) - ezsp.networkInit = AsyncMock(return_value=[ezsp.types.EmberStatus.SUCCESS]) + ezsp.initialize_network = AsyncMock(return_value=bellows.types.sl_Status.OK) rsp = await app._ensure_network_running() assert rsp ezsp.networkState.assert_called_once() - ezsp.networkInit.assert_called_once() + ezsp.initialize_network.assert_called_once() async def test_startup_coordinator_existing_groups_joined(app, ieee): @@ -1673,3 +1765,124 @@ async def test_repair_tclk_partner_ieee( await app.start_network() assert len(app._reset.mock_calls) == 1 + + +@pytest.fixture +def zigpy_backup() -> zigpy.backups.NetworkBackup: + return zigpy.backups.NetworkBackup( + node_info=zigpy.state.NodeInfo( + nwk=zigpy_t.NWK(0x0000), + ieee=t.EUI64.convert("07:06:05:04:03:02:01:00"), + logical_type=zdo_t.LogicalType.Coordinator, + model="Mock board", + manufacturer="Mock Manufacturer", + version="Mock version", + ), + network_info=zigpy.state.NetworkInfo( + extended_pan_id=zigpy_t.ExtendedPanId.convert("aa:bb:cc:dd:ee:ff:aa:bb"), + pan_id=zigpy_t.PanId(0x55AA), + nwk_update_id=1, + nwk_manager_id=zigpy_t.NWK(0x0000), + channel=t.uint8_t(25), + channel_mask=t.Channels.ALL_CHANNELS, + security_level=t.uint8_t(1), + network_key=zigpy.state.Key( + key=t.KeyData.convert( + "41:63:74:75:61:6c:4e:65:74:77:6f:72:6b:4b:65:79" + ), + seq=1, + tx_counter=305419896, + ), + tc_link_key=zigpy.state.Key( + key=t.KeyData(b"ZigBeeAlliance09"), + partner_ieee=t.EUI64.convert("07:06:05:04:03:02:01:00"), + tx_counter=2271560481, + ), + key_table=[ + zigpy.state.Key( + key=t.KeyData.convert( + "74:65:73:74:5f:6c:69:6e:6b:5f:6b:65:79:5f:30:31" + ), + tx_counter=12345, + rx_counter=67890, + seq=1, + partner_ieee=t.EUI64.convert("aa:bb:cc:dd:ee:ff:00:11"), + ), + zigpy.state.Key( + key=t.KeyData.convert( + "74:65:73:74:5f:6c:69:6e:6b:5f:6b:65:79:5f:30:32" + ), + tx_counter=54321, + rx_counter=98765, + seq=2, + partner_ieee=t.EUI64.convert("11:22:33:44:55:66:77:88"), + ), + ], + children=[ + t.EUI64.convert("aa:bb:cc:dd:ee:ff:00:11"), + t.EUI64.convert("11:22:33:44:55:66:77:88"), + ], + nwk_addresses={ + t.EUI64.convert("aa:bb:cc:dd:ee:ff:00:11"): zigpy_t.NWK(0x1234), + t.EUI64.convert("dc:ba:00:11:22:33:44:55"): zigpy_t.NWK(0xDCBA), + t.EUI64.convert("ab:cd:00:11:22:33:44:55"): zigpy_t.NWK(0xABCD), + t.EUI64.convert("11:22:33:44:55:66:77:88"): zigpy_t.NWK(0x5678), + }, + stack_specific={"ezsp": {"hashed_tclk": b"thehashedlinkkey".hex()}}, + source=f"bellows@{importlib.metadata.version('bellows')}", + metadata={ + "ezsp": { + "stack_version": 4, + "can_burn_userdata_custom_eui64": True, + "can_rewrite_custom_eui64": True, + } + }, + ), + ) + + +async def test_load_network_info( + app: ControllerApplication, + ieee: zigpy_t.EUI64, + zigpy_backup: zigpy.backups.NetworkBackup, +) -> None: + with mock_for_startup(app, ieee): + await app.connect() + await app.load_network_info(load_devices=True) + + assert app.state.node_info == zigpy_backup.node_info + assert app.state.network_info == zigpy_backup.network_info + + +async def test_write_network_info( + app: ControllerApplication, + ieee: zigpy_t.EUI64, + zigpy_backup: zigpy.backups.NetworkBackup, +) -> None: + with mock_for_startup(app, ieee): + await app.connect() + await app.write_network_info( + node_info=zigpy_backup.node_info, + network_info=zigpy_backup.network_info, + ) + + assert app._ezsp.write_nwk_frame_counter.mock_calls == [ + call(zigpy_backup.network_info.network_key.tx_counter) + ] + assert app._ezsp.write_aps_frame_counter.mock_calls == [ + call(zigpy_backup.network_info.tc_link_key.tx_counter) + ] + assert app._ezsp.formNetwork.mock_calls == [ + call( + t.EmberNetworkParameters( + panId=zigpy_backup.network_info.pan_id, + extendedPanId=zigpy_backup.network_info.extended_pan_id, + radioTxPower=t.uint8_t(8), + radioChannel=zigpy_backup.network_info.channel, + joinMethod=t.EmberJoinMethod.USE_MAC_ASSOCIATION, + nwkManagerId=t.EmberNodeId(0x0000), + nwkUpdateId=zigpy_backup.network_info.nwk_update_id, + channels=zigpy_backup.network_info.channel_mask, + ) + ) + ] diff --git a/tests/test_application_network_state.py b/tests/test_application_network_state.py deleted file mode 100644 index 0689e783..00000000 --- a/tests/test_application_network_state.py +++ /dev/null @@ -1,619 +0,0 @@ -import importlib.metadata - -import pytest -import zigpy.exceptions -import zigpy.state -import zigpy.types as zigpy_t -import zigpy.zdo.types as zdo_t - -from bellows.exception import EzspError -from bellows.ezsp import EZSP -from bellows.ezsp.v9.commands import GetTokenDataRsp -import bellows.types as t - -from tests.async_mock import AsyncMock, PropertyMock -from tests.test_application import app, ezsp_mock, ieee, make_app - - -@pytest.fixture -def node_info(): - return zigpy.state.NodeInfo( - nwk=zigpy_t.NWK(0x0000), - ieee=zigpy_t.EUI64.convert("00:12:4b:00:1c:a1:b8:46"), - logical_type=zdo_t.LogicalType.Coordinator, - model="Mock board", - manufacturer="Mock Manufacturer", - version="Mock version", - ) - - -@pytest.fixture -def network_info(node_info): - return zigpy.state.NetworkInfo( - extended_pan_id=zigpy_t.ExtendedPanId.convert("bd:27:0b:38:37:95:dc:87"), - pan_id=zigpy_t.PanId(0x9BB0), - nwk_update_id=18, - nwk_manager_id=zigpy_t.NWK(0x0000), - channel=zigpy_t.uint8_t(15), - channel_mask=zigpy_t.Channels.ALL_CHANNELS, - security_level=zigpy_t.uint8_t(5), - network_key=zigpy.state.Key( - key=zigpy_t.KeyData.convert("2ccade06b3090c310315b3d574d3c85a"), - seq=108, - tx_counter=118785, - ), - tc_link_key=zigpy.state.Key( - key=zigpy_t.KeyData(b"ZigBeeAlliance09"), - partner_ieee=node_info.ieee, - tx_counter=8712428, - ), - key_table=[ - zigpy.state.Key( - key=zigpy_t.KeyData.convert( - "85:7C:05:00:3E:76:1A:F9:68:9A:49:41:6A:60:5C:76" - ), - tx_counter=3792973670, - rx_counter=1083290572, - seq=147, - partner_ieee=zigpy_t.EUI64.convert("CC:CC:CC:FF:FE:E6:8E:CA"), - ), - zigpy.state.Key( - key=zigpy_t.KeyData.convert( - "CA:02:E8:BB:75:7C:94:F8:93:39:D3:9C:B3:CD:A7:BE" - ), - tx_counter=2597245184, - rx_counter=824424412, - seq=19, - partner_ieee=zigpy_t.EUI64.convert("EC:1B:BD:FF:FE:2F:41:A4"), - ), - ], - children=[ - zigpy_t.EUI64.convert("00:0B:57:FF:FE:2B:D4:57"), - ], - # If exposed by the stack, NWK addresses of other connected devices on the network - nwk_addresses={ - # Two routers - zigpy_t.EUI64.convert("CC:CC:CC:FF:FE:E6:8E:CA"): zigpy_t.NWK(0x44CB), - zigpy_t.EUI64.convert("EC:1B:BD:FF:FE:2F:41:A4"): zigpy_t.NWK(0x0702), - # Child device - zigpy_t.EUI64.convert("00:0b:57:ff:fe:2b:d4:57"): zigpy_t.NWK(0xC06B), - }, - stack_specific={"ezsp": {"hashed_tclk": "abcdabcdabcdabcdabcdabcdabcdabcd"}}, - source=f"bellows@{importlib.metadata.version('bellows')}", - ) - - -def _mock_app_for_load(app, ezsp_ver=7): - """Mock methods on the application and EZSP objects to run network state code.""" - ezsp = app._ezsp - ezsp.ezsp_version = ezsp_ver - type(ezsp).types = EZSP._BY_VERSION[ezsp_ver].types - - app._ensure_network_running = AsyncMock() - ezsp.getNetworkParameters = AsyncMock( - return_value=[ - t.EmberStatus.SUCCESS, - t.EmberNodeType.COORDINATOR, - t.EmberNetworkParameters( - extendedPanId=t.ExtendedPanId.convert("bd:27:0b:38:37:95:dc:87"), - panId=t.EmberPanId(0x9BB0), - radioTxPower=8, - radioChannel=15, - joinMethod=t.EmberJoinMethod.USE_MAC_ASSOCIATION, - nwkManagerId=t.EmberNodeId(0x0000), - nwkUpdateId=18, - channels=t.Channels.ALL_CHANNELS, - ), - ] - ) - - ezsp.getNodeId = AsyncMock(return_value=[t.EmberNodeId(0x0000)]) - ezsp.getEui64 = AsyncMock(return_value=[t.EUI64.convert("00:12:4b:00:1c:a1:b8:46")]) - - def get_configuration_value(config_id): - size = { - app._ezsp.types.EzspConfigId.CONFIG_ADDRESS_TABLE_SIZE: t.uint8_t(20), - app._ezsp.types.EzspConfigId.CONFIG_KEY_TABLE_SIZE: t.uint8_t(13), - app._ezsp.types.EzspConfigId.CONFIG_SECURITY_LEVEL: t.uint8_t(5), - }[config_id] - - return [app._ezsp.types.EmberStatus.SUCCESS, size] - - ezsp.getConfigurationValue = AsyncMock(side_effect=get_configuration_value) - - ezsp.getCurrentSecurityState = AsyncMock( - return_value=( - t.EmberStatus.SUCCESS, - ezsp.types.EmberCurrentSecurityState( - bitmask=( - t.EmberCurrentSecurityBitmask.TRUST_CENTER_USES_HASHED_LINK_KEY - | 64 - | 32 - | t.EmberCurrentSecurityBitmask.HAVE_TRUST_CENTER_LINK_KEY - | t.EmberCurrentSecurityBitmask.GLOBAL_LINK_KEY - ), - trustCenterLongAddress=t.EUI64.convert("00:12:4b:00:1c:a1:b8:46"), - ), - ) - ) - - def get_key(key_type): - key = { - ezsp.types.EmberKeyType.CURRENT_NETWORK_KEY: ezsp.types.EmberKeyStruct( - bitmask=( - t.EmberKeyStructBitmask.KEY_HAS_OUTGOING_FRAME_COUNTER - | t.EmberKeyStructBitmask.KEY_HAS_SEQUENCE_NUMBER - ), - type=ezsp.types.EmberKeyType.CURRENT_NETWORK_KEY, - key=t.KeyData.convert("2ccade06b3090c310315b3d574d3c85a"), - outgoingFrameCounter=118785, - incomingFrameCounter=0, - sequenceNumber=108, - partnerEUI64=t.EUI64.convert("00:00:00:00:00:00:00:00"), - ), - ezsp.types.EmberKeyType.TRUST_CENTER_LINK_KEY: ezsp.types.EmberKeyStruct( - bitmask=( - t.EmberKeyStructBitmask.KEY_IS_AUTHORIZED - | t.EmberKeyStructBitmask.KEY_HAS_PARTNER_EUI64 - | t.EmberKeyStructBitmask.KEY_HAS_OUTGOING_FRAME_COUNTER - ), - type=ezsp.types.EmberKeyType.TRUST_CENTER_LINK_KEY, - key=t.KeyData.convert("abcdabcdabcdabcdabcdabcdabcdabcd"), - outgoingFrameCounter=8712428, - incomingFrameCounter=0, - sequenceNumber=0, - partnerEUI64=t.EUI64.convert("00:12:4b:00:1c:a1:b8:46"), - ), - }[key_type] - - return (t.EmberStatus.SUCCESS, key) - - ezsp.getKey = AsyncMock(side_effect=get_key) - - if ezsp_ver >= 13: - - def export_key(security_context): - key = { - ezsp.types.sl_zb_sec_man_key_type_t.NETWORK: t.KeyData.convert( - "2ccade06b3090c310315b3d574d3c85a" - ), - ezsp.types.sl_zb_sec_man_key_type_t.TC_LINK: t.KeyData.convert( - "abcdabcdabcdabcdabcdabcdabcdabcd" - ), - }[security_context.core_key_type] - - return (key, t.EmberStatus.SUCCESS) - - ezsp.exportKey = AsyncMock(side_effect=export_key) - ezsp.getNetworkKeyInfo = AsyncMock( - return_value=[ - ezsp.types.sl_Status.SL_STATUS_OK, - ezsp.types.sl_zb_sec_man_network_key_info_t( - network_key_set=True, - alternate_network_key_set=False, - network_key_sequence_number=108, - alt_network_key_sequence_number=0, - network_key_frame_counter=118785, - ), - ] - ) - - -async def test_load_network_info_no_devices(app, network_info, node_info): - """Test `load_network_info(load_devices=False)`""" - _mock_app_for_load(app) - - await app.load_network_info(load_devices=False) - - assert app.state.node_info == node_info - assert app.state.network_info == network_info.replace( - key_table=[], - children=[], - nwk_addresses={}, - metadata=app.state.network_info.metadata, - ) - - -async def test_load_network_info_no_key_set(app, network_info, node_info): - """Test loading network info in v13+ when no network key is set.""" - _mock_app_for_load(app, ezsp_ver=13) - - app._ezsp.getNetworkKeyInfo = AsyncMock( - return_value=[ - app._ezsp.types.sl_Status.SL_STATUS_OK, - app._ezsp.types.sl_zb_sec_man_network_key_info_t( - network_key_set=False, # Not set - alternate_network_key_set=False, - network_key_sequence_number=108, - alt_network_key_sequence_number=0, - network_key_frame_counter=118785, - ), - ] - ) - - with pytest.raises(zigpy.exceptions.NetworkNotFormed): - await app.load_network_info(load_devices=False) - - -@pytest.mark.parametrize("ezsp_ver", [4, 6, 7, 13]) -async def test_load_network_info_with_devices(app, network_info, node_info, ezsp_ver): - """Test `load_network_info(load_devices=True)`""" - _mock_app_for_load(app, ezsp_ver) - - def get_child_data_v6(index): - if index == 0: - status = t.EmberStatus.SUCCESS - else: - status = t.EmberStatus.NOT_JOINED - - return ( - status, - t.EmberNodeId(0xC06B), - t.EUI64.convert("00:0b:57:ff:fe:2b:d4:57"), - t.EmberNodeType.SLEEPY_END_DEVICE, - ) - - def get_child_data_v7(index): - if index == 0: - status = t.EmberStatus.SUCCESS - else: - status = t.EmberStatus.NOT_JOINED - - return ( - status, - app._ezsp.types.EmberChildData( - eui64=t.EUI64.convert("00:0b:57:ff:fe:2b:d4:57"), - type=t.EmberNodeType.SLEEPY_END_DEVICE, - id=t.EmberNodeId(0xC06B), - phy=0, - power=128, - timeout=3, - ), - ) - - app._ezsp.ezsp_version = ezsp_ver - app._ezsp.getChildData = AsyncMock( - side_effect={ - 13: get_child_data_v7, - 7: get_child_data_v7, - 6: get_child_data_v6, - 4: get_child_data_v6, - }[ezsp_ver] - ) - - if ezsp_ver < 13: - - def get_key_table_entry(index): - if index == 0: - return ( - t.EmberStatus.SUCCESS, - app._ezsp.types.EmberKeyStruct( - bitmask=( - t.EmberKeyStructBitmask.KEY_IS_AUTHORIZED - | t.EmberKeyStructBitmask.KEY_HAS_PARTNER_EUI64 - | t.EmberKeyStructBitmask.KEY_HAS_INCOMING_FRAME_COUNTER - | t.EmberKeyStructBitmask.KEY_HAS_OUTGOING_FRAME_COUNTER - ), - type=app._ezsp.types.EmberKeyType.APPLICATION_LINK_KEY, - key=t.KeyData.convert("857C05003E761AF9689A49416A605C76"), - outgoingFrameCounter=3792973670, - incomingFrameCounter=1083290572, - sequenceNumber=147, - partnerEUI64=t.EUI64.convert("CC:CC:CC:FF:FE:E6:8E:CA"), - ), - ) - elif index == 1: - return ( - t.EmberStatus.SUCCESS, - app._ezsp.types.EmberKeyStruct( - bitmask=( - t.EmberKeyStructBitmask.KEY_IS_AUTHORIZED - | t.EmberKeyStructBitmask.KEY_HAS_PARTNER_EUI64 - | t.EmberKeyStructBitmask.KEY_HAS_INCOMING_FRAME_COUNTER - | t.EmberKeyStructBitmask.KEY_HAS_OUTGOING_FRAME_COUNTER - ), - type=app._ezsp.types.EmberKeyType.APPLICATION_LINK_KEY, - key=t.KeyData.convert("CA02E8BB757C94F89339D39CB3CDA7BE"), - outgoingFrameCounter=2597245184, - incomingFrameCounter=824424412, - sequenceNumber=19, - partnerEUI64=t.EUI64.convert("EC:1B:BD:FF:FE:2F:41:A4"), - ), - ) - elif index >= 12: - status = t.EmberStatus.INDEX_OUT_OF_RANGE - else: - status = t.EmberStatus.TABLE_ENTRY_ERASED - - return ( - status, - app._ezsp.types.EmberKeyStruct( - bitmask=t.EmberKeyStructBitmask(244), - type=app._ezsp.types.EmberKeyType(0x46), - key=t.KeyData.convert("b8a11c004b1200cdabcdabcdabcdabcd"), - outgoingFrameCounter=8192, - incomingFrameCounter=0, - sequenceNumber=0, - partnerEUI64=t.EUI64.convert("00:12:4b:00:1c:a1:b8:46"), - ), - ) - - app._ezsp.getKeyTableEntry = AsyncMock(side_effect=get_key_table_entry) - else: - - def export_link_key_by_index(index): - if index == 0: - return ( - t.EUI64.convert("CC:CC:CC:FF:FE:E6:8E:CA"), - t.KeyData.convert("857C05003E761AF9689A49416A605C76"), - app._ezsp.types.sl_zb_sec_man_aps_key_metadata_t( - bitmask=( - t.EmberKeyStructBitmask.KEY_IS_AUTHORIZED - | t.EmberKeyStructBitmask.KEY_HAS_PARTNER_EUI64 - | t.EmberKeyStructBitmask.KEY_HAS_INCOMING_FRAME_COUNTER - | t.EmberKeyStructBitmask.KEY_HAS_OUTGOING_FRAME_COUNTER - ), - outgoing_frame_counter=3792973670, - incoming_frame_counter=1083290572, - ttl_in_seconds=0, - ), - app._ezsp.types.sl_Status.SL_STATUS_OK, - ) - elif index == 1: - return ( - t.EUI64.convert("EC:1B:BD:FF:FE:2F:41:A4"), - t.KeyData.convert("CA02E8BB757C94F89339D39CB3CDA7BE"), - app._ezsp.types.sl_zb_sec_man_aps_key_metadata_t( - bitmask=( - t.EmberKeyStructBitmask.KEY_IS_AUTHORIZED - | t.EmberKeyStructBitmask.KEY_HAS_PARTNER_EUI64 - | t.EmberKeyStructBitmask.KEY_HAS_INCOMING_FRAME_COUNTER - | t.EmberKeyStructBitmask.KEY_HAS_OUTGOING_FRAME_COUNTER - ), - outgoing_frame_counter=2597245184, - incoming_frame_counter=824424412, - ttl_in_seconds=0, - ), - app._ezsp.types.sl_Status.SL_STATUS_OK, - ) - - return ( - t.EUI64.convert("7f:c9:35:e1:b0:00:00:00"), - t.KeyData.convert("80:45:38:73:55:00:00:00:08:e4:35:c9:7f:00:00:00"), - app._ezsp.types.sl_zb_sec_man_aps_key_metadata_t( - bitmask=t.EmberKeyStructBitmask(43976), - outgoing_frame_counter=85, - incoming_frame_counter=0, - ttl_in_seconds=0, - ), - app._ezsp.types.sl_Status.SL_STATUS_NOT_FOUND, - ) - - app._ezsp.exportLinkKeyByIndex = AsyncMock(side_effect=export_link_key_by_index) - - def get_addr_table_node_id(index): - return ( - { - 16: t.EmberNodeId(0x44CB), - 17: t.EmberNodeId(0x0702), - 18: t.EmberNodeId(0x0000), # bogus entry - }.get(index, t.EmberNodeId(0xFFFF)), - ) - - app._ezsp.getAddressTableRemoteNodeId = AsyncMock( - side_effect=get_addr_table_node_id - ) - - def get_addr_table_eui64(index): - if index < 16: - return (t.EUI64.convert("ff:ff:ff:ff:ff:ff:ff:ff"),) - elif 16 <= index <= 18: - return ( - { - 16: t.EUI64.convert("cc:cc:cc:ff:fe:e6:8e:ca"), - 17: t.EUI64.convert("ec:1b:bd:ff:fe:2f:41:a4"), - 18: t.EUI64.convert("00:00:00:00:00:00:00:00"), - }[index], - ) - else: - return (t.EUI64.convert("00:00:00:00:00:00:00:00"),) - - app._ezsp.getAddressTableRemoteEui64 = AsyncMock(side_effect=get_addr_table_eui64) - - await app.load_network_info(load_devices=True) - - nwk_addresses = network_info.nwk_addresses - - # v4 doesn't support address table reads so the only known addresses are from the - # `getChildData` call - if ezsp_ver == 4: - nwk_addresses = { - ieee: addr - for ieee, addr in network_info.nwk_addresses.items() - if ieee in network_info.children - } - else: - nwk_addresses = network_info.nwk_addresses - - # EZSP doesn't provide a command to set the key sequence number - assert app.state.network_info == network_info.replace( - key_table=[key.replace(seq=0) for key in network_info.key_table], - nwk_addresses=nwk_addresses, - metadata=app.state.network_info.metadata, - # TC link key does not have a TX counter - tc_link_key=network_info.tc_link_key.replace( - tx_counter=app.state.network_info.tc_link_key.tx_counter - ), - ) - assert app.state.node_info == node_info - - # No crash-prone calls were made - if ezsp_ver == 4: - app._ezsp.getAddressTableRemoteNodeId.assert_not_called() - app._ezsp.getAddressTableRemoteEui64.assert_not_called() - - -def _mock_app_for_write(app, network_info, node_info, ezsp_ver=7): - ezsp = app._ezsp - ezsp.ezsp_version = ezsp_ver - type(ezsp).types = EZSP._BY_VERSION[ezsp_ver].types - - network_state = ezsp.types.EmberNetworkStatus.JOINED_NETWORK - ezsp.networkState = AsyncMock(side_effect=lambda: [network_state]) - - def leave_network(): - nonlocal network_state - network_state = ezsp.types.EmberNetworkStatus.NO_NETWORK - - return [t.EmberStatus.NETWORK_DOWN] - - def form_network(params): - nonlocal network_state - network_state = ezsp.types.EmberNetworkStatus.JOINED_NETWORK - - return [t.EmberStatus.SUCCESS] - - ezsp.leaveNetwork = AsyncMock(side_effect=leave_network) - ezsp.formNetwork = AsyncMock(side_effect=form_network) - - ezsp.getEui64 = AsyncMock(return_value=[t.EUI64.convert("00:12:4b:00:1c:a1:b8:46")]) - - ezsp.setInitialSecurityState = AsyncMock(return_value=[t.EmberStatus.SUCCESS]) - ezsp.clearKeyTable = AsyncMock(return_value=[t.EmberStatus.SUCCESS]) - ezsp.getConfigurationValue = AsyncMock( - return_value=[t.EmberStatus.SUCCESS, t.uint8_t(200)] - ) - - if ezsp_ver >= 13: - ezsp.importLinkKey = AsyncMock( - side_effect=[ - # Only the first one succeeds - (t.EmberStatus.SUCCESS,), - ] - + [ - # The rest will fail - (t.EmberStatus.TABLE_FULL,), - ] - * 20 - ) - else: - ezsp.addOrUpdateKeyTableEntry = AsyncMock( - side_effect=[ - # Only the first one succeeds - (t.EmberStatus.SUCCESS,), - ] - + [ - # The rest will fail - (t.EmberStatus.TABLE_FULL,), - ] - * 20 - ) - - if ezsp_ver == 4: - ezsp.setValue = AsyncMock(return_value=[t.EmberStatus.BAD_ARGUMENT]) - else: - ezsp.setValue = AsyncMock(return_value=[t.EmberStatus.SUCCESS]) - - if ezsp_ver >= 9: - ezsp.setChildData = AsyncMock(return_value=[t.EmberStatus.SUCCESS]) - - ezsp.setValue = AsyncMock(return_value=[t.EmberStatus.SUCCESS]) - ezsp.setMfgToken = AsyncMock(return_value=[t.EmberStatus.SUCCESS]) - ezsp.getTokenData = AsyncMock( - return_value=GetTokenDataRsp(status=t.EmberStatus.LIBRARY_NOT_PRESENT) - ) - - -@pytest.mark.parametrize("ezsp_ver", [4, 7, 13]) -async def test_write_network_info(app, network_info, node_info, ezsp_ver): - _mock_app_for_write(app, network_info, node_info, ezsp_ver) - - await app.write_network_info( - network_info=network_info.replace( - children=network_info.children - + [ - # Bogus child that can't be restored - zigpy_t.EUI64.convert("FF:FF:57:FF:FE:2B:D4:57") - ] - ), - node_info=node_info, - ) - - -@pytest.mark.parametrize("can_burn", [True, False]) -@pytest.mark.parametrize("can_rewrite", [True, False]) -@pytest.mark.parametrize("force_write", [True, False]) -async def test_write_network_info_write_eui64( - caplog, app, network_info, node_info, can_burn, can_rewrite, force_write -): - _mock_app_for_write(app, network_info, node_info) - - # Differs from what is in `node_info` - app._ezsp.getEui64.return_value = [t.EUI64.convert("AA:AA:AA:AA:AA:AA:AA:AA")] - app._ezsp.can_burn_userdata_custom_eui64 = AsyncMock(return_value=can_burn) - app._ezsp.can_rewrite_custom_eui64 = AsyncMock(return_value=can_rewrite) - - await app.write_network_info( - network_info=network_info.replace( - stack_specific={ - "ezsp": { - "i_understand_i_can_update_eui64_only_once_and_i_still_want_to_do_it": force_write, - **network_info.stack_specific["ezsp"], - } - } - ), - node_info=node_info, - ) - - if can_rewrite: - # Everything else is always ignored - app._ezsp.write_custom_eui64.assert_called_once_with(node_info.ieee) - elif can_burn and force_write: - # Can only be forced - app._ezsp.write_custom_eui64.assert_called_once_with( - node_info.ieee, burn_into_userdata=True - ) - else: - # It is not written otherwise - app._ezsp.write_custom_eui64.assert_not_called() - - if can_burn and not force_write: - assert "does not match" in caplog.text - elif not can_burn and force_write: - assert "has already been written" in caplog.text - elif not can_burn and not force_write: - assert "does not match" in caplog.text - else: - assert False - - -async def test_write_network_info_generate_hashed_tclk(app, network_info, node_info): - _mock_app_for_write(app, network_info, node_info) - - seen_keys = set() - - for i in range(10): - app._ezsp.setInitialSecurityState.reset_mock() - - await app.write_network_info( - network_info=network_info.replace(stack_specific={}), - node_info=node_info, - ) - - call = app._ezsp.setInitialSecurityState.mock_calls[0] - seen_keys.add(tuple(call[1][0].preconfiguredKey)) - - # A new hashed key is randomly generated each time if none is provided - assert len(seen_keys) == 10 - - -async def test_reset_network_with_no_formed_network(app): - _mock_app_for_write(app, network_info, node_info) - - app._ezsp.networkState = AsyncMock( - return_value=[app._ezsp.types.EmberNetworkStatus.NO_NETWORK] - ) - - app._ezsp.networkInit = AsyncMock(return_value=[t.EmberStatus.NOT_JOINED]) - - await app.reset_network_info() diff --git a/tests/test_ezsp.py b/tests/test_ezsp.py index 0773a138..1581d4f4 100644 --- a/tests/test_ezsp.py +++ b/tests/test_ezsp.py @@ -129,7 +129,7 @@ async def mockcommand(name, *args): ezsp_f.frame_received(b"\x02\x00\x1b" + b"\x00" * 20) ezsp_f.frame_received(b"\x03\x00\x1c" + b"\x00" * 20) - return [0] + return [t.EmberStatus.SUCCESS] result = await _test_list_command(ezsp_f, mockcommand) assert len(result) == 2 @@ -138,7 +138,7 @@ async def mockcommand(name, *args): async def test_list_command_initial_failure(ezsp_f): async def mockcommand(name, *args): assert name == "startScan" - return [1] + return [t.EmberStatus.FAILURE] with pytest.raises(Exception): await _test_list_command(ezsp_f, mockcommand) @@ -151,7 +151,7 @@ async def mockcommand(name, *args): ezsp_f.frame_received(b"\x02\x00\x1b" + b"\x00" * 20) ezsp_f.frame_received(b"\x03\x00\x1c\x01\x01") - return [0] + return [t.EmberStatus.SUCCESS] with pytest.raises(Exception): await _test_list_command(ezsp_f, mockcommand) @@ -169,17 +169,17 @@ async def mockcommand(name, *args): async def test_form_network(ezsp_f): - await _test_form_network(ezsp_f, [0], b"\x90") + await _test_form_network(ezsp_f, [t.EmberStatus.SUCCESS], b"\x90") async def test_form_network_fail(ezsp_f): with pytest.raises(Exception): - await _test_form_network(ezsp_f, [1], b"\x90") + await _test_form_network(ezsp_f, [t.EmberStatus.FAILURE], b"\x90") async def test_form_network_fail_stack_status(ezsp_f): with pytest.raises(Exception): - await _test_form_network(ezsp_f, [0], b"\x00") + await _test_form_network(ezsp_f, [t.EmberStatus.SUCCESS], b"\x00") def test_receive_new(ezsp_f): @@ -338,7 +338,7 @@ async def replacement(*args): b"Manufacturer\xff\xff\xff", ), ("getValue", ezsp_f.types.EzspValueId.VALUE_VERSION_INFO): ( - 0x00, + t.EmberStatus.SUCCESS, b"\x01\x02\x03\x04\x05\x06", ), } @@ -362,7 +362,7 @@ async def replacement(*args): b"Manufacturer\xff\xff\xff", ), ("getValue", ezsp_f.types.EzspValueId.VALUE_VERSION_INFO): ( - 0x01, + t.EmberStatus.ERR_FATAL, b"\x01\x02\x03\x04\x05\x06", ), } @@ -386,7 +386,7 @@ async def replacement(*args): b"Nabu Casa\x00\xff\xff\xff\xff\xff\xff", ), ("getValue", ezsp_f.types.EzspValueId.VALUE_VERSION_INFO): ( - 0x00, + t.EmberStatus.SUCCESS, b"\xbf\x00\x07\x01\x00\x00\xaa", ), } @@ -406,7 +406,7 @@ async def replacement(*args): ("getMfgToken", t.EzspMfgTokenId.MFG_BOARD_NAME): (b"\xff" * 16,), ("getMfgToken", t.EzspMfgTokenId.MFG_STRING): (b"\xff" * 16,), ("getValue", ezsp_f.types.EzspValueId.VALUE_VERSION_INFO): ( - 0x00, + t.EmberStatus.SUCCESS, b"\xbf\x00\x07\x01\x00\x00\xaa", ), } @@ -696,26 +696,26 @@ async def wait_forever(*args, **kwargs): async def test_wait_for_stack_status(ezsp_f): - assert not ezsp_f._stack_status_listeners[t.EmberStatus.NETWORK_DOWN] + assert not ezsp_f._stack_status_listeners[t.sl_Status.NETWORK_DOWN] # Cancellation clears handlers - with ezsp_f.wait_for_stack_status(t.EmberStatus.NETWORK_DOWN) as stack_status: + with ezsp_f.wait_for_stack_status(t.sl_Status.NETWORK_DOWN) as stack_status: with pytest.raises(asyncio.TimeoutError): async with asyncio_timeout(0.1): - assert ezsp_f._stack_status_listeners[t.EmberStatus.NETWORK_DOWN] + assert ezsp_f._stack_status_listeners[t.sl_Status.NETWORK_DOWN] await stack_status - assert not ezsp_f._stack_status_listeners[t.EmberStatus.NETWORK_DOWN] + assert not ezsp_f._stack_status_listeners[t.sl_Status.NETWORK_DOWN] # Receiving multiple also works - with ezsp_f.wait_for_stack_status(t.EmberStatus.NETWORK_DOWN) as stack_status: + with ezsp_f.wait_for_stack_status(t.sl_Status.NETWORK_DOWN) as stack_status: ezsp_f.handle_callback("stackStatusHandler", [t.EmberStatus.NETWORK_UP]) ezsp_f.handle_callback("stackStatusHandler", [t.EmberStatus.NETWORK_DOWN]) ezsp_f.handle_callback("stackStatusHandler", [t.EmberStatus.NETWORK_DOWN]) await stack_status - assert not ezsp_f._stack_status_listeners[t.EmberStatus.NETWORK_DOWN] + assert not ezsp_f._stack_status_listeners[t.sl_Status.NETWORK_DOWN] def test_ezsp_versions(ezsp_f): diff --git a/tests/test_ezsp_protocol.py b/tests/test_ezsp_protocol.py index 798881af..d77d0d07 100644 --- a/tests/test_ezsp_protocol.py +++ b/tests/test_ezsp_protocol.py @@ -1,5 +1,6 @@ import asyncio import logging +from unittest.mock import AsyncMock, MagicMock, call, patch import pytest @@ -11,8 +12,6 @@ from bellows.types import NV3KeyId from bellows.uart import Gateway -from .async_mock import ANY, AsyncMock, MagicMock, call, patch - @pytest.fixture def prot_hndl(): diff --git a/tests/test_ezsp_v10.py b/tests/test_ezsp_v10.py index 6b8ffdf1..c1cc2946 100644 --- a/tests/test_ezsp_v10.py +++ b/tests/test_ezsp_v10.py @@ -1,8 +1,9 @@ +from unittest.mock import AsyncMock, MagicMock, call, patch + import pytest import bellows.ezsp.v10 - -from .async_mock import AsyncMock, MagicMock, patch +import bellows.types as t @pytest.fixture @@ -39,274 +40,39 @@ async def test_pre_permit(ezsp_f): assert tclk_mock.await_count == 1 -def test_command_frames(ezsp_f): - """Test alphabetical list of frames matches the commands.""" - assert set(ezsp_f.COMMANDS) == set(command_frames) - for name, frame_id in command_frames.items(): - assert ezsp_f.COMMANDS[name][0] == frame_id - assert ezsp_f.COMMANDS_BY_ID[frame_id][0] == name +async def test_write_child_data(ezsp_f) -> None: + ezsp_f.setChildData = AsyncMock(return_value=[ezsp_f.types.EmberStatus.SUCCESS]) + await ezsp_f.write_child_data( + { + t.EUI64.convert("00:0b:57:ff:fe:2b:d4:57"): 0xC06B, + t.EUI64.convert("00:18:4b:00:1c:a1:b8:46"): 0x1234, + } + ) -command_frames = { - "addEndpoint": 0x0002, - "addOrUpdateKeyTableEntry": 0x0066, - "addTransientLinkKey": 0x00AF, - "addressTableEntryIsActive": 0x005B, - "aesEncrypt": 0x0094, - "aesMmoHash": 0x006F, - "becomeTrustCenter": 0x0077, - "bindingIsActive": 0x002E, - "bootloadTransmitCompleteHandler": 0x0093, - "broadcastNetworkKeySwitch": 0x0074, - "broadcastNextNetworkKey": 0x0073, - "calculateSmacs": 0x009F, - "calculateSmacs283k1": 0x00EA, - "calculateSmacsHandler": 0x00A0, - "calculateSmacsHandler283k1": 0x00EB, - "callback": 0x0006, - "incomingNetworkStatusHandler": 0x00C4, - "childJoinHandler": 0x0023, - "clearBindingTable": 0x002A, - "clearKeyTable": 0x00B1, - "clearStoredBeacons": 0x003C, - "clearTemporaryDataMaybeStoreLinkKey": 0x00A1, - "clearTemporaryDataMaybeStoreLinkKey283k1": 0x00EE, - "clearTransientLinkKeys": 0x006B, - "counterRolloverHandler": 0x00F2, - "customFrame": 0x0047, - "customFrameHandler": 0x0054, - "dGpSend": 0x00C6, - "dGpSentHandler": 0x00C7, - "debugWrite": 0x0012, - "delayTest": 0x009D, - "deleteBinding": 0x002D, - "dsaSign": 0x00A6, - "dsaSignHandler": 0x00A7, - "dsaVerify": 0x00A3, - "dsaVerify283k1": 0x00B0, - "dsaVerifyHandler": 0x0078, - "dutyCycleHandler": 0x004D, - "echo": 0x0081, - "energyScanRequest": 0x009C, - "energyScanResultHandler": 0x0048, - "eraseKeyTableEntry": 0x0076, - "findAndRejoinNetwork": 0x0021, - "findKeyTableEntry": 0x0075, - "findUnusedPanId": 0x00D3, - "formNetwork": 0x001E, - "generateCbkeKeys": 0x00A4, - "generateCbkeKeys283k1": 0x00E8, - "generateCbkeKeysHandler": 0x009E, - "generateCbkeKeysHandler283k1": 0x00E9, - "getAddressTableRemoteEui64": 0x005E, - "getAddressTableRemoteNodeId": 0x005F, - "getBinding": 0x002C, - "getBeaconClassificationParams": 0x00F3, - "getBindingRemoteNodeId": 0x002F, - "getCertificate": 0x00A5, - "getCertificate283k1": 0x00EC, - "getChildData": 0x004A, - "setChildData": 0x00AC, - "getConfigurationValue": 0x0052, - "getCurrentDutyCycle": 0x004C, - "getCurrentSecurityState": 0x0069, - "getEui64": 0x0026, - "getDutyCycleLimits": 0x004B, - "getDutyCycleState": 0x0035, - "getExtendedTimeout": 0x007F, - "getExtendedValue": 0x0003, - "getFirstBeacon": 0x003D, - "getKey": 0x006A, - "getKeyTableEntry": 0x0071, - "getLibraryStatus": 0x0001, - "getLogicalChannel": 0x00BA, - "getMfgToken": 0x000B, - "getMulticastTableEntry": 0x0063, - "getNeighbor": 0x0079, - "getNeighborFrameCounter": 0x003E, - "setNeighborFrameCounter": 0x00AD, - "getNetworkParameters": 0x0028, - "getNextBeacon": 0x0004, - "getNodeId": 0x0027, - "getNumStoredBeacons": 0x0008, - "getParentChildParameters": 0x0029, - "getParentClassificationEnabled": 0x00F0, - "getPhyInterfaceCount": 0x00FC, - "getPolicy": 0x0056, - "getRadioParameters": 0x00FD, - "setRadioIeee802154CcaMode": 0x0095, - "getRandomNumber": 0x0049, - "getRouteTableEntry": 0x007B, - "getRoutingShortcutThreshold": 0x00D1, - "getSecurityKeyStatus": 0x00CD, - "getSourceRouteTableEntry": 0x00C1, - "getSourceRouteTableFilledSize": 0x00C2, - "getSourceRouteTableTotalSize": 0x00C3, - "getStandaloneBootloaderVersionPlatMicroPhy": 0x0091, - "getTimer": 0x004E, - "getToken": 0x000A, - "getTokenCount": 0x0100, - "getTokenInfo": 0x0101, - "getTokenData": 0x0102, - "setTokenData": 0x0103, - "resetNode": 0x0104, - "getTransientKeyTableEntry": 0x006D, - "getTransientLinkKey": 0x00CE, - "getTrueRandomEntropySource": 0x004F, - "getValue": 0x00AA, - "getXncpInfo": 0x0013, - "getZllPrimaryChannelMask": 0x00D9, - "getZllSecondaryChannelMask": 0x00DA, - "gpProxyTableGetEntry": 0x00C8, - "gpProxyTableLookup": 0x00C0, - "gpProxyTableProcessGpPairing": 0x00C9, - "gpSinkTableClearAll": 0x00E2, - "gpSinkTableFindOrAllocateEntry": 0x00E1, - "gpSinkTableGetEntry": 0x00DD, - "gpSinkTableInit": 0x0070, - "gpSinkTableLookup": 0x00DE, - "gpSinkTableRemoveEntry": 0x00E0, - "gpSinkTableSetEntry": 0x00DF, - "gpepIncomingMessageHandler": 0x00C5, - "idConflictHandler": 0x007C, - "incomingBootloadMessageHandler": 0x0092, - "incomingManyToOneRouteRequestHandler": 0x007D, - "incomingMessageHandler": 0x0045, - "incomingRouteErrorHandler": 0x0080, - "incomingRouteRecordHandler": 0x0059, - "incomingSenderEui64Handler": 0x0062, - "invalidCommand": 0x0058, - "isHubConnected": 0x00E6, - "isUpTimeLong": 0x00E5, - "isZllNetwork": 0x00BE, - "joinNetwork": 0x001F, - "joinNetworkDirectly": 0x003B, - "launchStandaloneBootloader": 0x008F, - "leaveNetwork": 0x0020, - "lookupEui64ByNodeId": 0x0061, - "lookupNodeIdByEui64": 0x0060, - "macFilterMatchMessageHandler": 0x0046, - "macPassthroughMessageHandler": 0x0097, - "maximumPayloadLength": 0x0033, - "messageSentHandler": 0x003F, - "mfglibEnd": 0x0084, - "mfglibGetChannel": 0x008B, - "mfglibGetPower": 0x008D, - "mfglibRxHandler": 0x008E, - "mfglibSendPacket": 0x0089, - "mfglibSetChannel": 0x008A, - "mfglibSetPower": 0x008C, - "mfglibStart": 0x0083, - "mfglibStartStream": 0x0087, - "mfglibStartTone": 0x0085, - "mfglibStopStream": 0x0088, - "mfglibStopTone": 0x0086, - "multiPhySetRadioChannel": 0x00FB, - "multiPhySetRadioPower": 0x00FA, - "multiPhyStart": 0x00F8, - "multiPhyStop": 0x00F9, - "neighborCount": 0x007A, - "networkFoundHandler": 0x001B, - "networkInit": 0x0017, - "networkState": 0x0018, - "noCallbacks": 0x0007, - "nop": 0x0005, - "permitJoining": 0x0022, - "pollCompleteHandler": 0x0043, - "pollForData": 0x0042, - "pollHandler": 0x0044, - "proxyBroadcast": 0x0037, - "rawTransmitCompleteHandler": 0x0098, - "readAndClearCounters": 0x0065, - "readCounters": 0x00F1, - "remoteDeleteBindingHandler": 0x0032, - "remoteSetBindingHandler": 0x0031, - "removeDevice": 0x00A8, - "replaceAddressTableEntry": 0x0082, - "requestLinkKey": 0x0014, - "resetToFactoryDefaults": 0x00CC, - "scanCompleteHandler": 0x001C, - "sendBootloadMessage": 0x0090, - "sendBroadcast": 0x0036, - "sendLinkPowerDeltaRequest": 0x00F7, - "sendManyToOneRouteRequest": 0x0041, - "sendMulticast": 0x0038, - "sendMulticastWithAlias": 0x003A, - "sendPanIdUpdate": 0x0057, - "sendRawMessage": 0x0096, - "sendRawMessageExtended": 0x0051, - "sendReply": 0x0039, - "sendTrustCenterLinkKey": 0x0067, - "sendUnicast": 0x0034, - "setAddressTableRemoteEui64": 0x005C, - "setAddressTableRemoteNodeId": 0x005D, - "setBeaconClassificationParams": 0x00EF, - "setBinding": 0x002B, - "setBindingRemoteNodeId": 0x0030, - "setBrokenRouteErrorCode": 0x0011, - "setChildData": 0x00AC, - "setConcentrator": 0x0010, - "setConfigurationValue": 0x0053, - "setDutyCycleLimitsInStack": 0x0040, - "setExtendedTimeout": 0x007E, - "setHubConnectivity": 0x00E4, - "setInitialSecurityState": 0x0068, - "setKeyTableEntry": 0x0072, - "setLogicalAndRadioChannel": 0x00B9, - "setLongUpTime": 0x00E3, - "setMacPollFailureWaitTime": 0x00F4, - "setManufacturerCode": 0x0015, - "setMfgToken": 0x000C, - "setMulticastTableEntry": 0x0064, - "setNeighborFrameCounter": 0x00AD, - "setParentClassificationEnabled": 0x00E7, - "setPolicy": 0x0055, - "setPowerDescriptor": 0x0016, - "setPreinstalledCbkeData": 0x00A2, - "setPreinstalledCbkeData283k1": 0x00ED, - "setRadioChannel": 0x009A, - "setRadioIeee802154CcaMode": 0x0095, - "setRadioPower": 0x0099, - "setRoutingShortcutThreshold": 0x00D0, - "setSecurityKey": 0x00CA, - "setSecurityParameters": 0x00CB, - "setSourceRoute": 0x00AE, - "setSourceRouteDiscoveryMode": 0x005A, - "setTimer": 0x000E, - "setToken": 0x0009, - "setValue": 0x00AB, - "setZllAdditionalState": 0x00D6, - "setZllNodeType": 0x00D5, - "setZllPrimaryChannelMask": 0x00DB, - "setZllSecondaryChannelMask": 0x00DC, - "stackStatusHandler": 0x0019, - "stackTokenChangedHandler": 0x000D, - "startScan": 0x001A, - "stopScan": 0x001D, - "switchNetworkKeyHandler": 0x006E, - "timerHandler": 0x000F, - "trustCenterJoinHandler": 0x0024, - "unicastCurrentNetworkKey": 0x0050, - "unicastNwkKeyUpdate": 0x00A9, - "unusedPanIdFoundHandler": 0x00D2, - "updateTcLinkKey": 0x006C, - "version": 0x0000, - "writeNodeData": 0x00FE, - "zigbeeKeyEstablishmentHandler": 0x009B, - "zllAddressAssignmentHandler": 0x00B8, - "zllClearTokens": 0x0025, - "zllGetTokens": 0x00BC, - "zllNetworkFoundHandler": 0x00B6, - "zllNetworkOps": 0x00B2, - "zllOperationInProgress": 0x00D7, - "zllRxOnWhenIdleGetActive": 0x00D8, - "zllScanCompleteHandler": 0x00B7, - "zllSetDataToken": 0x00BD, - "zllSetInitialSecurityState": 0x00B3, - "zllSetNonZllNetwork": 0x00BF, - "zllSetRadioIdleMode": 0x00D4, - "zllSetRxOnWhenIdle": 0x00B5, - "zllSetSecurityStateWithoutKey": 0x00CF, - "zllStartScan": 0x00B4, - "zllTouchLinkTargetHandler": 0x00BB, -} + assert ezsp_f.setChildData.mock_calls == [ + call( + 0, + ezsp_f.types.EmberChildData( + eui64=t.EUI64.convert("00:0b:57:ff:fe:2b:d4:57"), + type=t.EmberNodeType.SLEEPY_END_DEVICE, + id=0xC06B, + phy=0, + power=0, + timeout=0, + timeout_remaining=0, + ), + ), + call( + 1, + ezsp_f.types.EmberChildData( + eui64=t.EUI64.convert("00:18:4b:00:1c:a1:b8:46"), + type=t.EmberNodeType.SLEEPY_END_DEVICE, + id=0x1234, + phy=0, + power=0, + timeout=0, + timeout_remaining=0, + ), + ), + ] diff --git a/tests/test_ezsp_v11.py b/tests/test_ezsp_v11.py index e387d491..a94b11aa 100644 --- a/tests/test_ezsp_v11.py +++ b/tests/test_ezsp_v11.py @@ -1,9 +1,9 @@ +from unittest.mock import MagicMock + import pytest import bellows.ezsp.v11 -from .async_mock import AsyncMock, MagicMock, patch - @pytest.fixture def ezsp_f(): @@ -23,290 +23,3 @@ def test_ezsp_frame_rx(ezsp_f): assert ezsp_f._handle_callback.call_count == 1 assert ezsp_f._handle_callback.call_args[0][0] == "version" assert ezsp_f._handle_callback.call_args[0][1] == [0x01, 0x02, 0x1234] - - -async def test_pre_permit(ezsp_f): - """Test pre permit.""" - p1 = patch.object(ezsp_f, "setPolicy", new=AsyncMock()) - p2 = patch.object( - ezsp_f, - "addTransientLinkKey", - new=AsyncMock(return_value=[ezsp_f.types.EmberStatus.SUCCESS]), - ) - with p1 as pre_permit_mock, p2 as tclk_mock: - await ezsp_f.pre_permit(-1.9) - assert pre_permit_mock.await_count == 2 - assert tclk_mock.await_count == 1 - - -def test_command_frames(ezsp_f): - """Test alphabetical list of frames matches the commands.""" - assert set(ezsp_f.COMMANDS) == set(command_frames) - for name, frame_id in command_frames.items(): - assert ezsp_f.COMMANDS[name][0] == frame_id - assert ezsp_f.COMMANDS_BY_ID[frame_id][0] == name - - -command_frames = { - "addEndpoint": 0x0002, - "addOrUpdateKeyTableEntry": 0x0066, - "addTransientLinkKey": 0x00AF, - "addressTableEntryIsActive": 0x005B, - "aesEncrypt": 0x0094, - "aesMmoHash": 0x006F, - "becomeTrustCenter": 0x0077, - "bindingIsActive": 0x002E, - "bootloadTransmitCompleteHandler": 0x0093, - "broadcastNetworkKeySwitch": 0x0074, - "broadcastNextNetworkKey": 0x0073, - "calculateSmacs": 0x009F, - "calculateSmacs283k1": 0x00EA, - "calculateSmacsHandler": 0x00A0, - "calculateSmacsHandler283k1": 0x00EB, - "callback": 0x0006, - "incomingNetworkStatusHandler": 0x00C4, - "childJoinHandler": 0x0023, - "clearBindingTable": 0x002A, - "clearKeyTable": 0x00B1, - "clearStoredBeacons": 0x003C, - "clearTemporaryDataMaybeStoreLinkKey": 0x00A1, - "clearTemporaryDataMaybeStoreLinkKey283k1": 0x00EE, - "clearTransientLinkKeys": 0x006B, - "counterRolloverHandler": 0x00F2, - "customFrame": 0x0047, - "customFrameHandler": 0x0054, - "dGpSend": 0x00C6, - "dGpSentHandler": 0x00C7, - "debugWrite": 0x0012, - "delayTest": 0x009D, - "deleteBinding": 0x002D, - "dsaSign": 0x00A6, - "dsaSignHandler": 0x00A7, - "dsaVerify": 0x00A3, - "dsaVerify283k1": 0x00B0, - "dsaVerifyHandler": 0x0078, - "dutyCycleHandler": 0x004D, - "echo": 0x0081, - "energyScanRequest": 0x009C, - "energyScanResultHandler": 0x0048, - "eraseKeyTableEntry": 0x0076, - "findAndRejoinNetwork": 0x0021, - "findKeyTableEntry": 0x0075, - "findUnusedPanId": 0x00D3, - "formNetwork": 0x001E, - "generateCbkeKeys": 0x00A4, - "generateCbkeKeys283k1": 0x00E8, - "generateCbkeKeysHandler": 0x009E, - "generateCbkeKeysHandler283k1": 0x00E9, - "getAddressTableRemoteEui64": 0x005E, - "getAddressTableRemoteNodeId": 0x005F, - "getBinding": 0x002C, - "getBeaconClassificationParams": 0x00F3, - "getBindingRemoteNodeId": 0x002F, - "getCertificate": 0x00A5, - "getCertificate283k1": 0x00EC, - "getChildData": 0x004A, - "setChildData": 0x00AC, - "getConfigurationValue": 0x0052, - "getCurrentDutyCycle": 0x004C, - "getCurrentSecurityState": 0x0069, - "getEui64": 0x0026, - "getDutyCycleLimits": 0x004B, - "getDutyCycleState": 0x0035, - "getExtendedTimeout": 0x007F, - "getExtendedValue": 0x0003, - "getFirstBeacon": 0x003D, - "getKey": 0x006A, - "getKeyTableEntry": 0x0071, - "getLibraryStatus": 0x0001, - "getLogicalChannel": 0x00BA, - "getMfgToken": 0x000B, - "getMulticastTableEntry": 0x0063, - "getNeighbor": 0x0079, - "getNeighborFrameCounter": 0x003E, - "setNeighborFrameCounter": 0x00AD, - "getNetworkParameters": 0x0028, - "getNextBeacon": 0x0004, - "getNodeId": 0x0027, - "getNumStoredBeacons": 0x0008, - "getParentChildParameters": 0x0029, - "getParentClassificationEnabled": 0x00F0, - "getPhyInterfaceCount": 0x00FC, - "getPolicy": 0x0056, - "getRadioParameters": 0x00FD, - "setRadioIeee802154CcaMode": 0x0095, - "getRandomNumber": 0x0049, - "getRouteTableEntry": 0x007B, - "getRoutingShortcutThreshold": 0x00D1, - "getSecurityKeyStatus": 0x00CD, - "getSourceRouteTableEntry": 0x00C1, - "getSourceRouteTableFilledSize": 0x00C2, - "getSourceRouteTableTotalSize": 0x00C3, - "getStandaloneBootloaderVersionPlatMicroPhy": 0x0091, - "getTimer": 0x004E, - "getToken": 0x000A, - "getTokenCount": 0x0100, - "getTokenInfo": 0x0101, - "getTokenData": 0x0102, - "setTokenData": 0x0103, - "resetNode": 0x0104, - "getTransientKeyTableEntry": 0x006D, - "getTransientLinkKey": 0x00CE, - "getTrueRandomEntropySource": 0x004F, - "getValue": 0x00AA, - "getXncpInfo": 0x0013, - "getZllPrimaryChannelMask": 0x00D9, - "getZllSecondaryChannelMask": 0x00DA, - "gpProxyTableGetEntry": 0x00C8, - "gpProxyTableLookup": 0x00C0, - "gpProxyTableProcessGpPairing": 0x00C9, - "gpSinkTableClearAll": 0x00E2, - "gpSinkTableFindOrAllocateEntry": 0x00E1, - "gpSinkTableGetEntry": 0x00DD, - "gpSinkTableInit": 0x0070, - "gpSinkTableLookup": 0x00DE, - "gpSinkTableRemoveEntry": 0x00E0, - "gpSinkTableSetEntry": 0x00DF, - "gpepIncomingMessageHandler": 0x00C5, - "idConflictHandler": 0x007C, - "incomingBootloadMessageHandler": 0x0092, - "incomingManyToOneRouteRequestHandler": 0x007D, - "incomingMessageHandler": 0x0045, - "incomingRouteErrorHandler": 0x0080, - "incomingRouteRecordHandler": 0x0059, - "incomingSenderEui64Handler": 0x0062, - "invalidCommand": 0x0058, - "isHubConnected": 0x00E6, - "isUpTimeLong": 0x00E5, - "isZllNetwork": 0x00BE, - "joinNetwork": 0x001F, - "joinNetworkDirectly": 0x003B, - "launchStandaloneBootloader": 0x008F, - "leaveNetwork": 0x0020, - "lookupEui64ByNodeId": 0x0061, - "lookupNodeIdByEui64": 0x0060, - "macFilterMatchMessageHandler": 0x0046, - "macPassthroughMessageHandler": 0x0097, - "maximumPayloadLength": 0x0033, - "messageSentHandler": 0x003F, - "mfglibEnd": 0x0084, - "mfglibGetChannel": 0x008B, - "mfglibGetPower": 0x008D, - "mfglibRxHandler": 0x008E, - "mfglibSendPacket": 0x0089, - "mfglibSetChannel": 0x008A, - "mfglibSetPower": 0x008C, - "mfglibStart": 0x0083, - "mfglibStartStream": 0x0087, - "mfglibStartTone": 0x0085, - "mfglibStopStream": 0x0088, - "mfglibStopTone": 0x0086, - "multiPhySetRadioChannel": 0x00FB, - "multiPhySetRadioPower": 0x00FA, - "multiPhyStart": 0x00F8, - "multiPhyStop": 0x00F9, - "neighborCount": 0x007A, - "networkFoundHandler": 0x001B, - "networkInit": 0x0017, - "networkState": 0x0018, - "noCallbacks": 0x0007, - "nop": 0x0005, - "permitJoining": 0x0022, - "pollCompleteHandler": 0x0043, - "pollForData": 0x0042, - "pollHandler": 0x0044, - "proxyBroadcast": 0x0037, - "rawTransmitCompleteHandler": 0x0098, - "readAndClearCounters": 0x0065, - "readCounters": 0x00F1, - "remoteDeleteBindingHandler": 0x0032, - "remoteSetBindingHandler": 0x0031, - "removeDevice": 0x00A8, - "replaceAddressTableEntry": 0x0082, - "requestLinkKey": 0x0014, - "resetToFactoryDefaults": 0x00CC, - "scanCompleteHandler": 0x001C, - "sendBootloadMessage": 0x0090, - "sendBroadcast": 0x0036, - "sendLinkPowerDeltaRequest": 0x00F7, - "sendManyToOneRouteRequest": 0x0041, - "sendMulticast": 0x0038, - "sendMulticastWithAlias": 0x003A, - "sendPanIdUpdate": 0x0057, - "sendRawMessage": 0x0096, - "sendRawMessageExtended": 0x0051, - "sendReply": 0x0039, - "sendTrustCenterLinkKey": 0x0067, - "sendUnicast": 0x0034, - "setAddressTableRemoteEui64": 0x005C, - "setAddressTableRemoteNodeId": 0x005D, - "setBeaconClassificationParams": 0x00EF, - "setBinding": 0x002B, - "setBindingRemoteNodeId": 0x0030, - "setBrokenRouteErrorCode": 0x0011, - "setChildData": 0x00AC, - "setConcentrator": 0x0010, - "setConfigurationValue": 0x0053, - "setDutyCycleLimitsInStack": 0x0040, - "setExtendedTimeout": 0x007E, - "setHubConnectivity": 0x00E4, - "setInitialSecurityState": 0x0068, - "setKeyTableEntry": 0x0072, - "setLogicalAndRadioChannel": 0x00B9, - "setLongUpTime": 0x00E3, - "setMacPollFailureWaitTime": 0x00F4, - "setManufacturerCode": 0x0015, - "setMfgToken": 0x000C, - "setMulticastTableEntry": 0x0064, - "setNeighborFrameCounter": 0x00AD, - "setParentClassificationEnabled": 0x00E7, - "setPolicy": 0x0055, - "setPowerDescriptor": 0x0016, - "setPreinstalledCbkeData": 0x00A2, - "setPreinstalledCbkeData283k1": 0x00ED, - "setRadioChannel": 0x009A, - "setRadioIeee802154CcaMode": 0x0095, - "setRadioPower": 0x0099, - "setRoutingShortcutThreshold": 0x00D0, - "setSecurityKey": 0x00CA, - "setSecurityParameters": 0x00CB, - "setSourceRoute": 0x00AE, - "setSourceRouteDiscoveryMode": 0x005A, - "setTimer": 0x000E, - "setToken": 0x0009, - "setValue": 0x00AB, - "setZllAdditionalState": 0x00D6, - "setZllNodeType": 0x00D5, - "setZllPrimaryChannelMask": 0x00DB, - "setZllSecondaryChannelMask": 0x00DC, - "stackStatusHandler": 0x0019, - "stackTokenChangedHandler": 0x000D, - "startScan": 0x001A, - "stopScan": 0x001D, - "switchNetworkKeyHandler": 0x006E, - "timerHandler": 0x000F, - "trustCenterJoinHandler": 0x0024, - "unicastCurrentNetworkKey": 0x0050, - "unicastNwkKeyUpdate": 0x00A9, - "unusedPanIdFoundHandler": 0x00D2, - "updateTcLinkKey": 0x006C, - "version": 0x0000, - "writeNodeData": 0x00FE, - "zigbeeKeyEstablishmentHandler": 0x009B, - "zllAddressAssignmentHandler": 0x00B8, - "zllClearTokens": 0x0025, - "zllGetTokens": 0x00BC, - "zllNetworkFoundHandler": 0x00B6, - "zllNetworkOps": 0x00B2, - "zllOperationInProgress": 0x00D7, - "zllRxOnWhenIdleGetActive": 0x00D8, - "zllScanCompleteHandler": 0x00B7, - "zllSetDataToken": 0x00BD, - "zllSetInitialSecurityState": 0x00B3, - "zllSetNonZllNetwork": 0x00BF, - "zllSetRadioIdleMode": 0x00D4, - "zllSetRxOnWhenIdle": 0x00B5, - "zllSetSecurityStateWithoutKey": 0x00CF, - "zllStartScan": 0x00B4, - "zllTouchLinkTargetHandler": 0x00BB, -} diff --git a/tests/test_ezsp_v12.py b/tests/test_ezsp_v12.py index 9bb9fe7b..fc727fc8 100644 --- a/tests/test_ezsp_v12.py +++ b/tests/test_ezsp_v12.py @@ -1,9 +1,9 @@ +from unittest.mock import MagicMock + import pytest import bellows.ezsp.v12 -from .async_mock import AsyncMock, MagicMock, patch - @pytest.fixture def ezsp_f(): @@ -23,306 +23,3 @@ def test_ezsp_frame_rx(ezsp_f): assert ezsp_f._handle_callback.call_count == 1 assert ezsp_f._handle_callback.call_args[0][0] == "version" assert ezsp_f._handle_callback.call_args[0][1] == [0x01, 0x02, 0x1234] - - -async def test_pre_permit(ezsp_f): - """Test pre permit.""" - p1 = patch.object(ezsp_f, "setPolicy", new=AsyncMock()) - p2 = patch.object( - ezsp_f, - "addTransientLinkKey", - new=AsyncMock(return_value=[ezsp_f.types.EmberStatus.SUCCESS]), - ) - with p1 as pre_permit_mock, p2 as tclk_mock: - await ezsp_f.pre_permit(-1.9) - assert pre_permit_mock.await_count == 2 - assert tclk_mock.await_count == 1 - - -def test_command_frames(ezsp_f): - """Test alphabetical list of frames matches the commands.""" - assert set(ezsp_f.COMMANDS) == set(command_frames) - for name, frame_id in command_frames.items(): - assert ezsp_f.COMMANDS[name][0] == frame_id - assert ezsp_f.COMMANDS_BY_ID[frame_id][0] == name - - -command_frames = { - "addEndpoint": 0x0002, - "addOrUpdateKeyTableEntry": 0x0066, - "addressTableEntryIsActive": 0x005B, - "addTransientLinkKey": 0x00AF, - "aesEncrypt": 0x0094, - "aesMmoHash": 0x006F, - "becomeTrustCenter": 0x0077, - "bindingIsActive": 0x002E, - "bootloadTransmitCompleteHandler": 0x0093, - "broadcastNetworkKeySwitch": 0x0074, - "broadcastNextNetworkKey": 0x0073, - "calculateSmacs": 0x009F, - "calculateSmacs283k1": 0x00EA, - "calculateSmacsHandler": 0x00A0, - "calculateSmacsHandler283k1": 0x00EB, - "callback": 0x0006, - "checkKeyContext": 0x0110, - "childId": 0x0106, - "childJoinHandler": 0x0023, - "clearBindingTable": 0x002A, - "clearKeyTable": 0x00B1, - "clearStoredBeacons": 0x003C, - "clearTemporaryDataMaybeStoreLinkKey": 0x00A1, - "clearTemporaryDataMaybeStoreLinkKey283k1": 0x00EE, - "clearTransientLinkKeys": 0x006B, - "counterRolloverHandler": 0x00F2, - "customFrame": 0x0047, - "customFrameHandler": 0x0054, - "debugWrite": 0x0012, - "delayTest": 0x009D, - "deleteBinding": 0x002D, - "dGpSend": 0x00C6, - "dGpSentHandler": 0x00C7, - "dsaSign": 0x00A6, - "dsaSignHandler": 0x00A7, - "dsaVerify": 0x00A3, - "dsaVerify283k1": 0x00B0, - "dsaVerifyHandler": 0x0078, - "dutyCycleHandler": 0x004D, - "echo": 0x0081, - "energyScanRequest": 0x009C, - "energyScanResultHandler": 0x0048, - "eraseKeyTableEntry": 0x0076, - "exportKey": 0x0114, - "exportLinkKeyByEui": 0x010D, - "exportLinkKeyByIndex": 0x010F, - "exportTransientKeyByEui": 0x0113, - "exportTransientKeyByIndex": 0x0112, - "findAndRejoinNetwork": 0x0021, - "findKeyTableEntry": 0x0075, - "findUnusedPanId": 0x00D3, - "formNetwork": 0x001E, - "generateCbkeKeys": 0x00A4, - "generateCbkeKeys283k1": 0x00E8, - "generateCbkeKeysHandler": 0x009E, - "generateCbkeKeysHandler283k1": 0x00E9, - "getAddressTableRemoteEui64": 0x005E, - "getAddressTableRemoteNodeId": 0x005F, - "getApsKeyInfo": 0x010C, - "getBeaconClassificationParams": 0x00F3, - "getBinding": 0x002C, - "getBindingRemoteNodeId": 0x002F, - "getCertificate": 0x00A5, - "getCertificate283k1": 0x00EC, - "getChildData": 0x004A, - "getConfigurationValue": 0x0052, - "getCurrentDutyCycle": 0x004C, - "getCurrentSecurityState": 0x0069, - "getDutyCycleLimits": 0x004B, - "getDutyCycleState": 0x0035, - "getEui64": 0x0026, - "getExtendedTimeout": 0x007F, - "getExtendedValue": 0x0003, - "getFirstBeacon": 0x003D, - "getKey": 0x006A, - "getKeyTableEntry": 0x0071, - "getLibraryStatus": 0x0001, - "getLogicalChannel": 0x00BA, - "getMfgToken": 0x000B, - "getMulticastTableEntry": 0x0063, - "getNeighbor": 0x0079, - "getNeighborFrameCounter": 0x003E, - "getNetworkParameters": 0x0028, - "getNextBeacon": 0x0004, - "getNodeId": 0x0027, - "getNumStoredBeacons": 0x0008, - "getParentChildParameters": 0x0029, - "getParentClassificationEnabled": 0x00F0, - "getPhyInterfaceCount": 0x00FC, - "getPolicy": 0x0056, - "getRadioParameters": 0x00FD, - "getRandomNumber": 0x0049, - "getRouteTableEntry": 0x007B, - "getRoutingShortcutThreshold": 0x00D1, - "getSecurityKeyStatus": 0x00CD, - "getSourceRouteTableEntry": 0x00C1, - "getSourceRouteTableFilledSize": 0x00C2, - "getSourceRouteTableTotalSize": 0x00C3, - "getStandaloneBootloaderVersionPlatMicroPhy": 0x0091, - "getTimer": 0x004E, - "getToken": 0x000A, - "getTokenCount": 0x0100, - "getTokenData": 0x0102, - "getTokenInfo": 0x0101, - "getTransientKeyTableEntry": 0x006D, - "getTransientLinkKey": 0x00CE, - "getTrueRandomEntropySource": 0x004F, - "getValue": 0x00AA, - "getXncpInfo": 0x0013, - "getZllPrimaryChannelMask": 0x00D9, - "getZllSecondaryChannelMask": 0x00DA, - "gpepIncomingMessageHandler": 0x00C5, - "gpProxyTableGetEntry": 0x00C8, - "gpProxyTableLookup": 0x00C0, - "gpProxyTableProcessGpPairing": 0x00C9, - "gpSinkCommission": 0x010A, - "gpSinkTableClearAll": 0x00E2, - "gpSinkTableFindOrAllocateEntry": 0x00E1, - "gpSinkTableGetEntry": 0x00DD, - "gpSinkTableInit": 0x0070, - "gpSinkTableLookup": 0x00DE, - "gpSinkTableRemoveEntry": 0x00E0, - "gpSinkTableSetEntry": 0x00DF, - "gpTranslationTableClear": 0x010B, - "idConflictHandler": 0x007C, - "importKey": 0x0115, - "importLinkKey": 0x010E, - "importTransientKey": 0x0111, - "incomingBootloadMessageHandler": 0x0092, - "incomingManyToOneRouteRequestHandler": 0x007D, - "incomingMessageHandler": 0x0045, - "incomingNetworkStatusHandler": 0x00C4, - "incomingRouteErrorHandler": 0x0080, - "incomingRouteRecordHandler": 0x0059, - "incomingSenderEui64Handler": 0x0062, - "invalidCommand": 0x0058, - "isHubConnected": 0x00E6, - "isUpTimeLong": 0x00E5, - "isZllNetwork": 0x00BE, - "joinNetwork": 0x001F, - "joinNetworkDirectly": 0x003B, - "launchStandaloneBootloader": 0x008F, - "leaveNetwork": 0x0020, - "lookupEui64ByNodeId": 0x0061, - "lookupNodeIdByEui64": 0x0060, - "macFilterMatchMessageHandler": 0x0046, - "macPassthroughMessageHandler": 0x0097, - "maximumPayloadLength": 0x0033, - "messageSentHandler": 0x003F, - "mfglibEnd": 0x0084, - "mfglibGetChannel": 0x008B, - "mfglibGetPower": 0x008D, - "mfglibRxHandler": 0x008E, - "mfglibSendPacket": 0x0089, - "mfglibSetChannel": 0x008A, - "mfglibSetPower": 0x008C, - "mfglibStart": 0x0083, - "mfglibStartStream": 0x0087, - "mfglibStartTone": 0x0085, - "mfglibStopStream": 0x0088, - "mfglibStopTone": 0x0086, - "multiPhySetRadioChannel": 0x00FB, - "multiPhySetRadioPower": 0x00FA, - "multiPhyStart": 0x00F8, - "multiPhyStop": 0x00F9, - "neighborCount": 0x007A, - "networkFoundHandler": 0x001B, - "networkInit": 0x0017, - "networkState": 0x0018, - "noCallbacks": 0x0007, - "nop": 0x0005, - "permitJoining": 0x0022, - "pollCompleteHandler": 0x0043, - "pollForData": 0x0042, - "pollHandler": 0x0044, - "proxyBroadcast": 0x0037, - "rawTransmitCompleteHandler": 0x0098, - "readAndClearCounters": 0x0065, - "readAttribute": 0x0108, - "readCounters": 0x00F1, - "remoteDeleteBindingHandler": 0x0032, - "remoteSetBindingHandler": 0x0031, - "removeDevice": 0x00A8, - "replaceAddressTableEntry": 0x0082, - "requestLinkKey": 0x0014, - "resetNode": 0x0104, - "resetToFactoryDefaults": 0x00CC, - "scanCompleteHandler": 0x001C, - "sendBootloadMessage": 0x0090, - "sendBroadcast": 0x0036, - "sendLinkPowerDeltaRequest": 0x00F7, - "sendManyToOneRouteRequest": 0x0041, - "sendMulticast": 0x0038, - "sendMulticastWithAlias": 0x003A, - "sendPanIdUpdate": 0x0057, - "sendRawMessage": 0x0096, - "sendRawMessageExtended": 0x0051, - "sendReply": 0x0039, - "sendTrustCenterLinkKey": 0x0067, - "sendUnicast": 0x0034, - "setAddressTableRemoteEui64": 0x005C, - "setAddressTableRemoteNodeId": 0x005D, - "setBeaconClassificationParams": 0x00EF, - "setBinding": 0x002B, - "setBindingRemoteNodeId": 0x0030, - "setBrokenRouteErrorCode": 0x0011, - "setChildData": 0x00AC, - "setChildData": 0x00AC, - "setConcentrator": 0x0010, - "setConfigurationValue": 0x0053, - "setDutyCycleLimitsInStack": 0x0040, - "setExtendedTimeout": 0x007E, - "setHubConnectivity": 0x00E4, - "setInitialSecurityState": 0x0068, - "setKeyTableEntry": 0x0072, - "setLogicalAndRadioChannel": 0x00B9, - "setLongUpTime": 0x00E3, - "setMacPollFailureWaitTime": 0x00F4, - "setManufacturerCode": 0x0015, - "setMfgToken": 0x000C, - "setMulticastTableEntry": 0x0064, - "setNeighborFrameCounter": 0x00AD, - "setNeighborFrameCounter": 0x00AD, - "setParentClassificationEnabled": 0x00E7, - "setPassiveAckConfig": 0x0105, - "setPolicy": 0x0055, - "setPowerDescriptor": 0x0016, - "setPreinstalledCbkeData": 0x00A2, - "setPreinstalledCbkeData283k1": 0x00ED, - "setRadioChannel": 0x009A, - "setRadioIeee802154CcaMode": 0x0095, - "setRadioIeee802154CcaMode": 0x0095, - "setRadioPower": 0x0099, - "setRoutingShortcutThreshold": 0x00D0, - "setSecurityKey": 0x00CA, - "setSecurityParameters": 0x00CB, - "setSourceRoute": 0x00AE, - "setSourceRouteDiscoveryMode": 0x005A, - "setTimer": 0x000E, - "setToken": 0x0009, - "setTokenData": 0x0103, - "setValue": 0x00AB, - "setZllAdditionalState": 0x00D6, - "setZllNodeType": 0x00D5, - "setZllPrimaryChannelMask": 0x00DB, - "setZllSecondaryChannelMask": 0x00DC, - "stackStatusHandler": 0x0019, - "stackTokenChangedHandler": 0x000D, - "startScan": 0x001A, - "stopScan": 0x001D, - "switchNetworkKeyHandler": 0x006E, - "timerHandler": 0x000F, - "trustCenterJoinHandler": 0x0024, - "unicastCurrentNetworkKey": 0x0050, - "unicastNwkKeyUpdate": 0x00A9, - "unusedPanIdFoundHandler": 0x00D2, - "updateTcLinkKey": 0x006C, - "version": 0x0000, - "writeAttribute": 0x0109, - "writeNodeData": 0x00FE, - "zigbeeKeyEstablishmentHandler": 0x009B, - "zllAddressAssignmentHandler": 0x00B8, - "zllClearTokens": 0x0025, - "zllGetTokens": 0x00BC, - "zllNetworkFoundHandler": 0x00B6, - "zllNetworkOps": 0x00B2, - "zllOperationInProgress": 0x00D7, - "zllRxOnWhenIdleGetActive": 0x00D8, - "zllScanCompleteHandler": 0x00B7, - "zllSetDataToken": 0x00BD, - "zllSetInitialSecurityState": 0x00B3, - "zllSetNonZllNetwork": 0x00BF, - "zllSetRadioIdleMode": 0x00D4, - "zllSetRxOnWhenIdle": 0x00B5, - "zllSetSecurityStateWithoutKey": 0x00CF, - "zllStartScan": 0x00B4, - "zllTouchLinkTargetHandler": 0x00BB, -} diff --git a/tests/test_ezsp_v13.py b/tests/test_ezsp_v13.py index 407f620f..dcbc1e70 100644 --- a/tests/test_ezsp_v13.py +++ b/tests/test_ezsp_v13.py @@ -1,8 +1,11 @@ +from unittest.mock import AsyncMock, MagicMock, call, patch + import pytest +import zigpy.exceptions +import zigpy.state import bellows.ezsp.v13 - -from .async_mock import AsyncMock, MagicMock, patch +import bellows.types as t @pytest.fixture @@ -31,7 +34,7 @@ async def test_pre_permit(ezsp_f): p2 = patch.object( ezsp_f, "importTransientKey", - new=AsyncMock(return_value=[ezsp_f.types.sl_Status.SL_STATUS_OK]), + new=AsyncMock(return_value=[t.sl_Status.OK]), ) with p1 as pre_permit_mock, p2 as tclk_mock: await ezsp_f.pre_permit(-1.9) @@ -39,282 +42,189 @@ async def test_pre_permit(ezsp_f): assert tclk_mock.await_count == 1 -def test_command_frames(ezsp_f): - """Test alphabetical list of frames matches the commands.""" - assert set(ezsp_f.COMMANDS) == set(command_frames) - for name, frame_id in command_frames.items(): - assert ezsp_f.COMMANDS[name][0] == frame_id - assert ezsp_f.COMMANDS_BY_ID[frame_id][0] == name - - -command_frames = { - "addEndpoint": 0x0002, - "addressTableEntryIsActive": 0x005B, - "aesEncrypt": 0x0094, - "aesMmoHash": 0x006F, - "bindingIsActive": 0x002E, - "bootloadTransmitCompleteHandler": 0x0093, - "broadcastNetworkKeySwitch": 0x0074, - "broadcastNextNetworkKey": 0x0073, - "calculateSmacs": 0x009F, - "calculateSmacs283k1": 0x00EA, - "calculateSmacsHandler": 0x00A0, - "calculateSmacsHandler283k1": 0x00EB, - "callback": 0x0006, - "checkKeyContext": 0x0110, - "childId": 0x0106, - "childJoinHandler": 0x0023, - "clearBindingTable": 0x002A, - "clearKeyTable": 0x00B1, - "clearStoredBeacons": 0x003C, - "clearTemporaryDataMaybeStoreLinkKey": 0x00A1, - "clearTemporaryDataMaybeStoreLinkKey283k1": 0x00EE, - "clearTransientLinkKeys": 0x006B, - "counterRolloverHandler": 0x00F2, - "customFrame": 0x0047, - "customFrameHandler": 0x0054, - "debugWrite": 0x0012, - "delayTest": 0x009D, - "deleteBinding": 0x002D, - "dGpSend": 0x00C6, - "dGpSentHandler": 0x00C7, - "dsaSign": 0x00A6, - "dsaSignHandler": 0x00A7, - "dsaVerify": 0x00A3, - "dsaVerify283k1": 0x00B0, - "dsaVerifyHandler": 0x0078, - "dutyCycleHandler": 0x004D, - "echo": 0x0081, - "energyScanRequest": 0x009C, - "energyScanResultHandler": 0x0048, - "eraseKeyTableEntry": 0x0076, - "exportKey": 0x0114, - "exportLinkKeyByEui": 0x010D, - "exportLinkKeyByIndex": 0x010F, - "exportTransientKeyByEui": 0x0113, - "exportTransientKeyByIndex": 0x0112, - "findAndRejoinNetwork": 0x0021, - "findKeyTableEntry": 0x0075, - "findUnusedPanId": 0x00D3, - "formNetwork": 0x001E, - "generateCbkeKeys": 0x00A4, - "generateCbkeKeys283k1": 0x00E8, - "generateCbkeKeysHandler": 0x009E, - "generateCbkeKeysHandler283k1": 0x00E9, - "getAddressTableRemoteEui64": 0x005E, - "getAddressTableRemoteNodeId": 0x005F, - "getApsKeyInfo": 0x010C, - "getBeaconClassificationParams": 0x00F3, - "getBinding": 0x002C, - "getBindingRemoteNodeId": 0x002F, - "getCertificate": 0x00A5, - "getCertificate283k1": 0x00EC, - "getChildData": 0x004A, - "getConfigurationValue": 0x0052, - "getCurrentDutyCycle": 0x004C, - "getCurrentSecurityState": 0x0069, - "getDutyCycleLimits": 0x004B, - "getDutyCycleState": 0x0035, - "getEui64": 0x0026, - "getExtendedTimeout": 0x007F, - "getExtendedValue": 0x0003, - "getFirstBeacon": 0x003D, - "getLibraryStatus": 0x0001, - "getLogicalChannel": 0x00BA, - "getMfgToken": 0x000B, - "getMulticastTableEntry": 0x0063, - "getNeighbor": 0x0079, - "getNeighborFrameCounter": 0x003E, - "getNetworkKeyInfo": 0x0116, - "getNetworkParameters": 0x0028, - "getNextBeacon": 0x0004, - "getNodeId": 0x0027, - "getNumStoredBeacons": 0x0008, - "getParentChildParameters": 0x0029, - "getParentClassificationEnabled": 0x00F0, - "getPhyInterfaceCount": 0x00FC, - "getPolicy": 0x0056, - "getRadioParameters": 0x00FD, - "getRandomNumber": 0x0049, - "getRouteTableEntry": 0x007B, - "getRoutingShortcutThreshold": 0x00D1, - "getSourceRouteTableEntry": 0x00C1, - "getSourceRouteTableFilledSize": 0x00C2, - "getSourceRouteTableTotalSize": 0x00C3, - "getStandaloneBootloaderVersionPlatMicroPhy": 0x0091, - "getTimer": 0x004E, - "getToken": 0x000A, - "getTokenCount": 0x0100, - "getTokenData": 0x0102, - "getTokenInfo": 0x0101, - "getTrueRandomEntropySource": 0x004F, - "getValue": 0x00AA, - "getXncpInfo": 0x0013, - "getZllPrimaryChannelMask": 0x00D9, - "getZllSecondaryChannelMask": 0x00DA, - "gpepIncomingMessageHandler": 0x00C5, - "gpProxyTableGetEntry": 0x00C8, - "gpProxyTableLookup": 0x00C0, - "gpProxyTableProcessGpPairing": 0x00C9, - "gpSecurityTestVectors": 0x0117, - "gpSinkCommission": 0x010A, - "gpSinkTableClearAll": 0x00E2, - "gpSinkTableFindOrAllocateEntry": 0x00E1, - "gpSinkTableGetEntry": 0x00DD, - "gpSinkTableGetNumberOfActiveEntries": 0x0118, - "gpSinkTableInit": 0x0070, - "gpSinkTableLookup": 0x00DE, - "gpSinkTableRemoveEntry": 0x00E0, - "gpSinkTableSetEntry": 0x00DF, - "gpTranslationTableClear": 0x010B, - "idConflictHandler": 0x007C, - "importKey": 0x0115, - "importLinkKey": 0x010E, - "importTransientKey": 0x0111, - "incomingBootloadMessageHandler": 0x0092, - "incomingManyToOneRouteRequestHandler": 0x007D, - "incomingMessageHandler": 0x0045, - "incomingNetworkStatusHandler": 0x00C4, - "incomingRouteErrorHandler": 0x0080, - "incomingRouteRecordHandler": 0x0059, - "incomingSenderEui64Handler": 0x0062, - "invalidCommand": 0x0058, - "isHubConnected": 0x00E6, - "isUpTimeLong": 0x00E5, - "isZllNetwork": 0x00BE, - "joinNetwork": 0x001F, - "joinNetworkDirectly": 0x003B, - "launchStandaloneBootloader": 0x008F, - "leaveNetwork": 0x0020, - "lookupEui64ByNodeId": 0x0061, - "lookupNodeIdByEui64": 0x0060, - "macFilterMatchMessageHandler": 0x0046, - "macPassthroughMessageHandler": 0x0097, - "maximumPayloadLength": 0x0033, - "messageSentHandler": 0x003F, - "mfglibEnd": 0x0084, - "mfglibGetChannel": 0x008B, - "mfglibGetPower": 0x008D, - "mfglibRxHandler": 0x008E, - "mfglibSendPacket": 0x0089, - "mfglibSetChannel": 0x008A, - "mfglibSetPower": 0x008C, - "mfglibStart": 0x0083, - "mfglibStartStream": 0x0087, - "mfglibStartTone": 0x0085, - "mfglibStopStream": 0x0088, - "mfglibStopTone": 0x0086, - "multiPhySetRadioChannel": 0x00FB, - "multiPhySetRadioPower": 0x00FA, - "multiPhyStart": 0x00F8, - "multiPhyStop": 0x00F9, - "neighborCount": 0x007A, - "networkFoundHandler": 0x001B, - "networkInit": 0x0017, - "networkState": 0x0018, - "noCallbacks": 0x0007, - "nop": 0x0005, - "permitJoining": 0x0022, - "pollCompleteHandler": 0x0043, - "pollForData": 0x0042, - "pollHandler": 0x0044, - "proxyBroadcast": 0x0037, - "rawTransmitCompleteHandler": 0x0098, - "readAndClearCounters": 0x0065, - "readAttribute": 0x0108, - "readCounters": 0x00F1, - "remoteDeleteBindingHandler": 0x0032, - "remoteSetBindingHandler": 0x0031, - "removeDevice": 0x00A8, - "replaceAddressTableEntry": 0x0082, - "requestLinkKey": 0x0014, - "resetNode": 0x0104, - "scanCompleteHandler": 0x001C, - "sendBootloadMessage": 0x0090, - "sendBroadcast": 0x0036, - "sendLinkPowerDeltaRequest": 0x00F7, - "sendManyToOneRouteRequest": 0x0041, - "sendMulticast": 0x0038, - "sendMulticastWithAlias": 0x003A, - "sendPanIdUpdate": 0x0057, - "sendRawMessage": 0x0096, - "sendRawMessageExtended": 0x0051, - "sendReply": 0x0039, - "sendTrustCenterLinkKey": 0x0067, - "sendUnicast": 0x0034, - "setAddressTableRemoteEui64": 0x005C, - "setAddressTableRemoteNodeId": 0x005D, - "setBeaconClassificationParams": 0x00EF, - "setBinding": 0x002B, - "setBindingRemoteNodeId": 0x0030, - "setBrokenRouteErrorCode": 0x0011, - "setChildData": 0x00AC, - "setChildData": 0x00AC, - "setConcentrator": 0x0010, - "setConfigurationValue": 0x0053, - "setDutyCycleLimitsInStack": 0x0040, - "setExtendedTimeout": 0x007E, - "setHubConnectivity": 0x00E4, - "setInitialSecurityState": 0x0068, - "setLogicalAndRadioChannel": 0x00B9, - "setLongUpTime": 0x00E3, - "setMacPollFailureWaitTime": 0x00F4, - "setManufacturerCode": 0x0015, - "setMfgToken": 0x000C, - "setMulticastTableEntry": 0x0064, - "setNeighborFrameCounter": 0x00AD, - "setNeighborFrameCounter": 0x00AD, - "setParentClassificationEnabled": 0x00E7, - "setPassiveAckConfig": 0x0105, - "setPolicy": 0x0055, - "setPowerDescriptor": 0x0016, - "setPreinstalledCbkeData": 0x00A2, - "setPreinstalledCbkeData283k1": 0x00ED, - "setRadioChannel": 0x009A, - "setRadioIeee802154CcaMode": 0x0095, - "setRadioIeee802154CcaMode": 0x0095, - "setRadioPower": 0x0099, - "setRoutingShortcutThreshold": 0x00D0, - "setSourceRoute": 0x00AE, - "setSourceRouteDiscoveryMode": 0x005A, - "setTimer": 0x000E, - "setToken": 0x0009, - "setTokenData": 0x0103, - "setValue": 0x00AB, - "setZllAdditionalState": 0x00D6, - "setZllNodeType": 0x00D5, - "setZllPrimaryChannelMask": 0x00DB, - "setZllSecondaryChannelMask": 0x00DC, - "stackStatusHandler": 0x0019, - "stackTokenChangedHandler": 0x000D, - "startScan": 0x001A, - "stopScan": 0x001D, - "switchNetworkKeyHandler": 0x006E, - "timerHandler": 0x000F, - "tokenFactoryReset": 0x0077, - "trustCenterJoinHandler": 0x0024, - "unicastCurrentNetworkKey": 0x0050, - "unicastNwkKeyUpdate": 0x00A9, - "unusedPanIdFoundHandler": 0x00D2, - "updateTcLinkKey": 0x006C, - "version": 0x0000, - "writeAttribute": 0x0109, - "writeNodeData": 0x00FE, - "zigbeeKeyEstablishmentHandler": 0x009B, - "zllAddressAssignmentHandler": 0x00B8, - "zllClearTokens": 0x0025, - "zllGetTokens": 0x00BC, - "zllNetworkFoundHandler": 0x00B6, - "zllNetworkOps": 0x00B2, - "zllOperationInProgress": 0x00D7, - "zllRxOnWhenIdleGetActive": 0x00D8, - "zllScanCompleteHandler": 0x00B7, - "zllSetDataToken": 0x00BD, - "zllSetInitialSecurityState": 0x00B3, - "zllSetNonZllNetwork": 0x00BF, - "zllSetRadioIdleMode": 0x00D4, - "zllSetRxOnWhenIdle": 0x00B5, - "zllSetSecurityStateWithoutKey": 0x00CF, - "zllStartScan": 0x00B4, - "zllTouchLinkTargetHandler": 0x00BB, -} +async def test_read_link_keys(ezsp_f): + def export_link_key_by_index(index): + if index == 0: + return ( + t.EUI64.convert("CC:CC:CC:FF:FE:E6:8E:CA"), + t.KeyData.convert("857C05003E761AF9689A49416A605C76"), + ezsp_f.types.sl_zb_sec_man_aps_key_metadata_t( + bitmask=( + t.EmberKeyStructBitmask.KEY_IS_AUTHORIZED + | t.EmberKeyStructBitmask.KEY_HAS_PARTNER_EUI64 + | t.EmberKeyStructBitmask.KEY_HAS_INCOMING_FRAME_COUNTER + | t.EmberKeyStructBitmask.KEY_HAS_OUTGOING_FRAME_COUNTER + ), + outgoing_frame_counter=3792973670, + incoming_frame_counter=1083290572, + ttl_in_seconds=0, + ), + t.sl_Status.OK, + ) + elif index == 1: + return ( + t.EUI64.convert("EC:1B:BD:FF:FE:2F:41:A4"), + t.KeyData.convert("CA02E8BB757C94F89339D39CB3CDA7BE"), + ezsp_f.types.sl_zb_sec_man_aps_key_metadata_t( + bitmask=( + t.EmberKeyStructBitmask.KEY_IS_AUTHORIZED + | t.EmberKeyStructBitmask.KEY_HAS_PARTNER_EUI64 + | t.EmberKeyStructBitmask.KEY_HAS_INCOMING_FRAME_COUNTER + | t.EmberKeyStructBitmask.KEY_HAS_OUTGOING_FRAME_COUNTER + ), + outgoing_frame_counter=2597245184, + incoming_frame_counter=824424412, + ttl_in_seconds=0, + ), + t.sl_Status.OK, + ) + + return ( + t.EUI64.convert("7f:c9:35:e1:b0:00:00:00"), + t.KeyData.convert("80:45:38:73:55:00:00:00:08:e4:35:c9:7f:00:00:00"), + ezsp_f.types.sl_zb_sec_man_aps_key_metadata_t( + bitmask=t.EmberKeyStructBitmask(43976), + outgoing_frame_counter=85, + incoming_frame_counter=0, + ttl_in_seconds=0, + ), + t.sl_Status.NOT_FOUND, + ) + + ezsp_f.exportLinkKeyByIndex = AsyncMock(side_effect=export_link_key_by_index) + ezsp_f.getConfigurationValue = AsyncMock(return_value=(t.EmberStatus.SUCCESS, 13)) + + link_keys = [key async for key in ezsp_f.read_link_keys()] + assert link_keys == [ + zigpy.state.Key( + key=t.KeyData.convert("85:7C:05:00:3E:76:1A:F9:68:9A:49:41:6A:60:5C:76"), + tx_counter=3792973670, + rx_counter=1083290572, + seq=0, # Sequence number is 0 + partner_ieee=t.EUI64.convert("CC:CC:CC:FF:FE:E6:8E:CA"), + ), + zigpy.state.Key( + key=t.KeyData.convert("CA:02:E8:BB:75:7C:94:F8:93:39:D3:9C:B3:CD:A7:BE"), + tx_counter=2597245184, + rx_counter=824424412, + seq=0, # Sequence number is 0 + partner_ieee=t.EUI64.convert("EC:1B:BD:FF:FE:2F:41:A4"), + ), + ] + + +async def test_get_network_key_and_tc_link_key(ezsp_f): + def export_key(security_context): + key = { + ezsp_f.types.sl_zb_sec_man_key_type_t.NETWORK: t.KeyData.convert( + "2ccade06b3090c310315b3d574d3c85a" + ), + ezsp_f.types.sl_zb_sec_man_key_type_t.TC_LINK: t.KeyData.convert( + "abcdabcdabcdabcdabcdabcdabcdabcd" + ), + }[security_context.core_key_type] + + return (key, t.EmberStatus.SUCCESS) + + ezsp_f.exportKey = AsyncMock(side_effect=export_key) + ezsp_f.getNetworkKeyInfo = AsyncMock( + return_value=[ + ezsp_f.types.sl_Status.OK, + ezsp_f.types.sl_zb_sec_man_network_key_info_t( + network_key_set=True, + alternate_network_key_set=False, + network_key_sequence_number=108, + alt_network_key_sequence_number=0, + network_key_frame_counter=118785, + ), + ] + ) + + assert (await ezsp_f.get_network_key()) == zigpy.state.Key( + key=t.KeyData.convert("2ccade06b3090c310315b3d574d3c85a"), + seq=108, + tx_counter=118785, + ) + + assert (await ezsp_f.get_tc_link_key()) == zigpy.state.Key( + key=t.KeyData.convert("abcdabcdabcdabcdabcdabcdabcdabcd"), + ) + + +async def test_get_network_key_without_network(ezsp_f): + ezsp_f.getNetworkKeyInfo = AsyncMock( + return_value=[ + ezsp_f.types.sl_Status.OK, + ezsp_f.types.sl_zb_sec_man_network_key_info_t( + network_key_set=False, # Not set + alternate_network_key_set=False, + network_key_sequence_number=108, + alt_network_key_sequence_number=0, + network_key_frame_counter=118785, + ), + ] + ) + + ezsp_f.exportKey = AsyncMock( + return_value=[ + t.KeyData.convert("2ccade06b3090c310315b3d574d3c85a"), + t.EmberStatus.SUCCESS, + ] + ) + + with pytest.raises(zigpy.exceptions.NetworkNotFormed): + await ezsp_f.get_network_key() + + +async def test_write_link_keys(ezsp_f): + ezsp_f.importLinkKey = AsyncMock( + side_effect=[ + (t.EmberStatus.SUCCESS,), + (t.EmberStatus.INVALID_CALL,), + ] + ) + + await ezsp_f.write_link_keys( + [ + zigpy.state.Key( + key=t.KeyData.convert( + "85:7C:05:00:3E:76:1A:F9:68:9A:49:41:6A:60:5C:76" + ), + tx_counter=3792973670, + rx_counter=1083290572, + seq=0, # Sequence number is 0 + partner_ieee=t.EUI64.convert("CC:CC:CC:FF:FE:E6:8E:CA"), + ), + zigpy.state.Key( + key=t.KeyData.convert( + "CA:02:E8:BB:75:7C:94:F8:93:39:D3:9C:B3:CD:A7:BE" + ), + tx_counter=2597245184, + rx_counter=824424412, + seq=0, # Sequence number is 0 + partner_ieee=t.EUI64.convert("EC:1B:BD:FF:FE:2F:41:A4"), + ), + ] + ) + + assert ezsp_f.importLinkKey.mock_calls == [ + call( + 0, + t.EUI64.convert("CC:CC:CC:FF:FE:E6:8E:CA"), + t.KeyData.convert("85:7C:05:00:3E:76:1A:F9:68:9A:49:41:6A:60:5C:76"), + ), + call( + 1, + t.EUI64.convert("EC:1B:BD:FF:FE:2F:41:A4"), + t.KeyData.convert("CA:02:E8:BB:75:7C:94:F8:93:39:D3:9C:B3:CD:A7:BE"), + ), + ] + + +async def test_factory_reset(ezsp_f) -> None: + ezsp_f.clearKeyTable = AsyncMock(return_value=(t.EmberStatus.SUCCESS,)) + ezsp_f.tokenFactoryReset = AsyncMock(return_value=(t.EmberStatus.SUCCESS,)) + await ezsp_f.factory_reset() + + assert ezsp_f.clearKeyTable.mock_calls == [call()] + assert ezsp_f.tokenFactoryReset.mock_calls == [call(False, False)] diff --git a/tests/test_ezsp_v14.py b/tests/test_ezsp_v14.py new file mode 100644 index 00000000..1265d772 --- /dev/null +++ b/tests/test_ezsp_v14.py @@ -0,0 +1,188 @@ +from unittest.mock import AsyncMock, MagicMock, call + +import pytest +import zigpy.exceptions +import zigpy.state + +import bellows.ezsp.v14 +import bellows.types as t + + +@pytest.fixture +def ezsp_f(): + """EZSP v14 protocol handler.""" + return bellows.ezsp.v14.EZSPv14(MagicMock(), MagicMock()) + + +def test_ezsp_frame(ezsp_f): + ezsp_f._seq = 0x22 + data = ezsp_f._ezsp_frame("version", 14) + assert data == b"\x22\x00\x01\x00\x00\x0e" + + +def test_ezsp_frame_rx(ezsp_f): + """Test receiving a version frame.""" + ezsp_f(b"\x01\x01\x80\x00\x00\x01\x02\x34\x12") + assert ezsp_f._handle_callback.call_count == 1 + assert ezsp_f._handle_callback.call_args[0][0] == "version" + assert ezsp_f._handle_callback.call_args[0][1] == [0x01, 0x02, 0x1234] + + +async def test_read_address_table(ezsp_f): + def get_addr_table_info(index): + default = ( + t.sl_Status.OK, + t.EmberNodeId(0xFFFF), + t.EUI64.convert("ff:ff:ff:ff:ff:ff:ff:ff"), + ) + return { + 16: ( + t.sl_Status.OK, + t.EmberNodeId(0x44CB), + t.EUI64.convert("cc:cc:cc:ff:fe:e6:8e:ca"), + ), + 17: ( + t.sl_Status.OK, + t.EmberNodeId(0x0702), + t.EUI64.convert("ec:1b:bd:ff:fe:2f:41:a4"), + ), + # Not actually seen with a real adapter + 18: ( + t.sl_Status.FAIL, + t.EmberNodeId(0x1234), + t.EUI64.convert("ab:cd:ab:cd:ab:cd:ab:cd"), + ), + }.get(index, default) + + ezsp_f.getAddressTableInfo = AsyncMock(side_effect=get_addr_table_info) + ezsp_f.getConfigurationValue = AsyncMock(return_value=(t.sl_Status.OK, 20)) + + address_table = [key async for key in ezsp_f.read_address_table()] + assert address_table == [ + (0x44CB, t.EUI64.convert("cc:cc:cc:ff:fe:e6:8e:ca")), + (0x0702, t.EUI64.convert("ec:1b:bd:ff:fe:2f:41:a4")), + ] + + +async def test_get_network_key_and_tc_link_key(ezsp_f): + def export_key(security_context): + if ( + security_context.core_key_type + == ezsp_f.types.sl_zb_sec_man_key_type_t.NETWORK + ): + return ( + t.sl_Status.OK, + t.KeyData.convert("2ccade06b3090c310315b3d574d3c85a"), + ezsp_f.types.sl_zb_sec_man_context_t( + core_key_type=ezsp_f.types.sl_zb_sec_man_key_type_t.NETWORK, + key_index=0, + derived_type=ezsp_f.types.sl_zb_sec_man_derived_key_type_t.NONE, + eui64=t.EUI64.convert("00:00:00:00:00:00:00:00"), + multi_network_index=0, + flags=ezsp_f.types.sl_zb_sec_man_flags_t.NONE, + psa_key_alg_permission=0, + ), + ) + elif ( + security_context.core_key_type + == ezsp_f.types.sl_zb_sec_man_key_type_t.TC_LINK + ): + return ( + t.sl_Status.OK, + t.KeyData.convert("abcdabcdabcdabcdabcdabcdabcdabcd"), + ezsp_f.types.sl_zb_sec_man_context_t( + core_key_type=ezsp_f.types.sl_zb_sec_man_key_type_t.NETWORK, + key_index=0, + derived_type=ezsp_f.types.sl_zb_sec_man_derived_key_type_t.NONE, + eui64=t.EUI64.convert("00:00:00:00:00:00:00:00"), + multi_network_index=0, + flags=ezsp_f.types.sl_zb_sec_man_flags_t.NONE, + psa_key_alg_permission=0, + ), + ) + else: + pytest.fail("Invalid core_key_type") + + ezsp_f.exportKey = AsyncMock(side_effect=export_key) + ezsp_f.getNetworkKeyInfo = AsyncMock( + return_value=[ + ezsp_f.types.sl_Status.OK, + ezsp_f.types.sl_zb_sec_man_network_key_info_t( + network_key_set=True, + alternate_network_key_set=False, + network_key_sequence_number=108, + alt_network_key_sequence_number=0, + network_key_frame_counter=118785, + ), + ] + ) + + assert (await ezsp_f.get_network_key()) == zigpy.state.Key( + key=t.KeyData.convert("2ccade06b3090c310315b3d574d3c85a"), + seq=108, + tx_counter=118785, + ) + + assert (await ezsp_f.get_tc_link_key()) == zigpy.state.Key( + key=t.KeyData.convert("abcdabcdabcdabcdabcdabcdabcdabcd"), + ) + + +async def test_get_network_key_without_network(ezsp_f): + ezsp_f.getNetworkKeyInfo = AsyncMock( + return_value=[ + ezsp_f.types.sl_Status.OK, + ezsp_f.types.sl_zb_sec_man_network_key_info_t( + network_key_set=False, # Not set + alternate_network_key_set=False, + network_key_sequence_number=108, + alt_network_key_sequence_number=0, + network_key_frame_counter=118785, + ), + ] + ) + + ezsp_f.exportKey = AsyncMock( + return_value=[ + t.sl_Status.OK, + t.KeyData.convert("00000000000000000000000000000000"), + ezsp_f.types.sl_zb_sec_man_context_t( + core_key_type=ezsp_f.types.sl_zb_sec_man_key_type_t.NETWORK, + key_index=0, + derived_type=ezsp_f.types.sl_zb_sec_man_derived_key_type_t.NONE, + eui64=t.EUI64.convert("00:00:00:00:00:00:00:00"), + multi_network_index=0, + flags=ezsp_f.types.sl_zb_sec_man_flags_t.NONE, + psa_key_alg_permission=0, + ), + ] + ) + + with pytest.raises(zigpy.exceptions.NetworkNotFormed): + await ezsp_f.get_network_key() + + +async def test_send_broadcast(ezsp_f) -> None: + ezsp_f.sendBroadcast = AsyncMock(return_value=(t.sl_Status.OK, 0x42)) + status, message_tag = await ezsp_f.send_broadcast( + address=t.BroadcastAddress.ALL_ROUTERS_AND_COORDINATOR, + aps_frame=t.EmberApsFrame(), + radius=12, + message_tag=0x42, + aps_sequence=34, + data=b"hello", + ) + + assert status == t.sl_Status.OK + assert message_tag == 0x42 + assert ezsp_f.sendBroadcast.mock_calls == [ + call( + 0x0000, + t.BroadcastAddress.ALL_ROUTERS_AND_COORDINATOR, + 34, + t.EmberApsFrame(), + 12, + 0x42, + b"hello", + ) + ] diff --git a/tests/test_ezsp_v4.py b/tests/test_ezsp_v4.py index 0fe5b237..a755b465 100644 --- a/tests/test_ezsp_v4.py +++ b/tests/test_ezsp_v4.py @@ -1,14 +1,17 @@ -from unittest import mock +import logging +from unittest.mock import AsyncMock, MagicMock, call import pytest +import zigpy.state import bellows.ezsp.v4 +import bellows.types as t @pytest.fixture def ezsp_f(): """EZSP v4 protocol handler.""" - return bellows.ezsp.v4.EZSPv4(mock.MagicMock(), mock.MagicMock()) + return bellows.ezsp.v4.EZSPv4(MagicMock(), MagicMock()) def test_ezsp_frame(ezsp_f): @@ -30,226 +33,305 @@ async def test_pre_permit(ezsp_f): await ezsp_f.pre_permit(1.9) -command_frames = { - "addEndpoint": 0x02, - "addOrUpdateKeyTableEntry": 0x66, - "addTransientLinkKey": 0xAF, - "addressTableEntryIsActive": 0x5B, - "aesEncrypt": 0x94, - "aesMmoHash": 0x6F, - "becomeTrustCenter": 0x77, - "bindingIsActive": 0x2E, - "bootloadTransmitCompleteHandler": 0x93, - "broadcastNetworkKeySwitch": 0x74, - "broadcastNextNetworkKey": 0x73, - "calculateSmacs": 0x9F, - "calculateSmacs283k1": 0xEA, - "calculateSmacsHandler": 0xA0, - "calculateSmacsHandler283k1": 0xEB, - "callback": 0x06, - "childJoinHandler": 0x23, - "clearBindingTable": 0x2A, - "clearKeyTable": 0xB1, - "clearTemporaryDataMaybeStoreLinkKey": 0xA1, - "clearTemporaryDataMaybeStoreLinkKey283k1": 0xEE, - "clearTransientLinkKeys": 0x6B, - "counterRolloverHandler": 0xF2, - "customFrame": 0x47, - "customFrameHandler": 0x54, - "dGpSend": 0xC6, - "dGpSentHandler": 0xC7, - "debugWrite": 0x12, - "delayTest": 0x9D, - "deleteBinding": 0x2D, - "dsaSign": 0xA6, - "dsaSignHandler": 0xA7, - "dsaVerify": 0xA3, - "dsaVerify283k1": 0xB0, - "dsaVerifyHandler": 0x78, - "echo": 0x81, - "energyScanRequest": 0x9C, - "energyScanResultHandler": 0x48, - "eraseKeyTableEntry": 0x76, - "findAndRejoinNetwork": 0x21, - "findKeyTableEntry": 0x75, - "formNetwork": 0x1E, - "generateCbkeKeys": 0xA4, - "generateCbkeKeys283k1": 0xE8, - "generateCbkeKeysHandler": 0x9E, - "generateCbkeKeysHandler283k1": 0xE9, - "getAddressTableRemoteEui64": 0x5E, - "getAddressTableRemoteNodeId": 0x5F, - "getBinding": 0x2C, - "getBindingRemoteNodeId": 0x2F, - "getCertificate": 0xA5, - "getCertificate283k1": 0xEC, - "getChildData": 0x4A, - "getConfigurationValue": 0x52, - "getCtune": 0xF6, - "getCurrentSecurityState": 0x69, - "getEui64": 0x26, - "getExtendedTimeout": 0x7F, - "getExtendedValue": 0x03, - "getKey": 0x6A, - "getKeyTableEntry": 0x71, - "getLibraryStatus": 0x01, - "getLogicalChannel": 0xBA, - "getMfgToken": 0x0B, - "getMulticastTableEntry": 0x63, - "getNeighbor": 0x79, - "getNetworkParameters": 0x28, - "getNodeId": 0x27, - "getParentChildParameters": 0x29, - "getPolicy": 0x56, - "getRandomNumber": 0x49, - "getRouteTableEntry": 0x7B, - "getStandaloneBootloaderVersionPlatMicroPhy": 0x91, - "getTimer": 0x4E, - "getToken": 0x0A, - "getValue": 0xAA, - "getXncpInfo": 0x13, - "gpProxyTableProcessGpPairing": 0xC9, - "gpepIncomingMessageHandler": 0xC5, - "idConflictHandler": 0x7C, - "incomingBootloadMessageHandler": 0x92, - "incomingManyToOneRouteRequestHandler": 0x7D, - "incomingMessageHandler": 0x45, - "incomingRouteErrorHandler": 0x80, - "incomingRouteRecordHandler": 0x59, - "incomingSenderEui64Handler": 0x62, - "invalidCommand": 0x58, - "isZllNetwork": 0xBE, - "joinNetwork": 0x1F, - "launchStandaloneBootloader": 0x8F, - "leaveNetwork": 0x20, - "lookupEui64ByNodeId": 0x61, - "lookupNodeIdByEui64": 0x60, - "macFilterMatchMessageHandler": 0x46, - "macPassthroughMessageHandler": 0x97, - "maximumPayloadLength": 0x33, - "messageSentHandler": 0x3F, - "mfglibEnd": 0x84, - "mfglibGetChannel": 0x8B, - "mfglibGetPower": 0x8D, - "mfglibRxHandler": 0x8E, - "mfglibSendPacket": 0x89, - "mfglibSetChannel": 0x8A, - "mfglibSetPower": 0x8C, - "mfglibStart": 0x83, - "mfglibStartStream": 0x87, - "mfglibStartTone": 0x85, - "mfglibStopStream": 0x88, - "mfglibStopTone": 0x86, - "neighborCount": 0x7A, - "networkFoundHandler": 0x1B, - "networkInit": 0x17, - "networkInitExtended": 0x70, - "networkState": 0x18, - "noCallbacks": 0x07, - "nop": 0x05, - "overrideCurrentChannel": 0x95, - "permitJoining": 0x22, - "pollCompleteHandler": 0x43, - "pollForData": 0x42, - "pollHandler": 0x44, - "proxyBroadcast": 0x37, - "rawTransmitCompleteHandler": 0x98, - "readAndClearCounters": 0x65, - "readCounters": 0xF1, - "remoteDeleteBindingHandler": 0x32, - "remoteSetBindingHandler": 0x31, - "removeDevice": 0xA8, - "replaceAddressTableEntry": 0x82, - "requestLinkKey": 0x14, - "rf4ceAutoDiscoveryResponseCompleteHandler": 0xDE, - "rf4ceDeletePairingTableEntry": 0xD2, - "rf4ceDiscovery": 0xD9, - "rf4ceDiscoveryCompleteHandler": 0xDA, - "rf4ceDiscoveryRequestHandler": 0xDB, - "rf4ceDiscoveryResponseHandler": 0xDC, - "rf4ceEnableAutoDiscoveryResponse": 0xDD, - "rf4ceGetApplicationInfo": 0xEF, - "rf4ceGetMaxPayload": 0xF3, - "rf4ceGetNetworkParameters": 0xF4, - "rf4ceGetPairingTableEntry": 0xD1, - "rf4ceIncomingMessageHandler": 0xD5, - "rf4ceKeyUpdate": 0xD3, - "rf4ceMessageSentHandler": 0xD6, - "rf4cePair": 0xDF, - "rf4cePairCompleteHandler": 0xE0, - "rf4cePairRequestHandler": 0xE1, - "rf4ceSend": 0xD4, - "rf4ceSetApplicationInfo": 0xE7, - "rf4ceSetFrequencyAgilityParameters": 0xE6, - "rf4ceSetPairingTableEntry": 0xD0, - "rf4ceSetPowerSavingParameters": 0xE5, - "rf4ceStart": 0xD7, - "rf4ceStop": 0xD8, - "rf4ceUnpair": 0xE2, - "rf4ceUnpairCompleteHandler": 0xE4, - "rf4ceUnpairHandler": 0xE3, - "scanCompleteHandler": 0x1C, - "sendBootloadMessage": 0x90, - "sendBroadcast": 0x36, - "sendManyToOneRouteRequest": 0x41, - "sendMulticast": 0x38, - "sendRawMessage": 0x96, - "sendReply": 0x39, - "sendUnicast": 0x34, - "setAddressTableRemoteEui64": 0x5C, - "setAddressTableRemoteNodeId": 0x5D, - "setBinding": 0x2B, - "setBindingRemoteNodeId": 0x30, - "setConcentrator": 0x10, - "setConfigurationValue": 0x53, - "setCtune": 0xF5, - "setExtendedTimeout": 0x7E, - "setGpioCurrentConfiguration": 0xAC, - "setGpioPowerUpDownConfiguration": 0xAD, - "setGpioRadioPowerMask": 0xAE, - "setInitialSecurityState": 0x68, - "setKeyTableEntry": 0x72, - "setLogicalAndRadioChannel": 0xB9, - "setManufacturerCode": 0x15, - "setMfgToken": 0x0C, - "setMulticastTableEntry": 0x64, - "setPolicy": 0x55, - "setPowerDescriptor": 0x16, - "setPreinstalledCbkeData": 0xA2, - "setPreinstalledCbkeData283k1": 0xED, - "setRadioChannel": 0x9A, - "setRadioPower": 0x99, - "setSourceRoute": 0x5A, - "setTimer": 0x0E, - "setToken": 0x09, - "setValue": 0xAB, - "stackStatusHandler": 0x19, - "stackTokenChangedHandler": 0x0D, - "startScan": 0x1A, - "stopScan": 0x1D, - "switchNetworkKeyHandler": 0x6E, - "timerHandler": 0x0F, - "trustCenterJoinHandler": 0x24, - "unicastNwkKeyUpdate": 0xA9, - "version": 0x00, - "zigbeeKeyEstablishmentHandler": 0x9B, - "zllAddressAssignmentHandler": 0xB8, - "zllGetTokens": 0xBC, - "zllNetworkFoundHandler": 0xB6, - "zllNetworkOps": 0xB2, - "zllScanCompleteHandler": 0xB7, - "zllSetDataToken": 0xBD, - "zllSetInitialSecurityState": 0xB3, - "zllSetNonZllNetwork": 0xBF, - "zllSetRxOnWhenIdle": 0xB5, - "zllStartScan": 0xB4, - "zllTouchLinkTargetHandler": 0xBB, -} - - -def test_command_frames(ezsp_f): - """Test alphabetical list of frames matches the commands.""" - assert set(command_frames) == set(ezsp_f.COMMANDS) - for name, frame_id in command_frames.items(): - assert ezsp_f.COMMANDS[name][0] == frame_id - assert ezsp_f.COMMANDS_BY_ID[frame_id][0] == name +async def test_read_child_data(ezsp_f): + def get_child_data(index): + if index == 0: + status = t.EmberStatus.SUCCESS + else: + status = t.EmberStatus.NOT_JOINED + + return ( + status, + t.EmberNodeId(0xC06B), + t.EUI64.convert("00:0b:57:ff:fe:2b:d4:57"), + t.EmberNodeType.SLEEPY_END_DEVICE, + ) + + ezsp_f.getChildData = AsyncMock(side_effect=get_child_data) + + child_data = [row async for row in ezsp_f.read_child_data()] + assert child_data == [ + ( + 0xC06B, + t.EUI64.convert("00:0b:57:ff:fe:2b:d4:57"), + t.EmberNodeType.SLEEPY_END_DEVICE, + ) + ] + + +async def test_read_link_keys(ezsp_f): + def get_key_table_entry(index): + if index == 0: + return ( + t.EmberStatus.SUCCESS, + ezsp_f.types.EmberKeyStruct( + bitmask=( + t.EmberKeyStructBitmask.KEY_IS_AUTHORIZED + | t.EmberKeyStructBitmask.KEY_HAS_PARTNER_EUI64 + | t.EmberKeyStructBitmask.KEY_HAS_INCOMING_FRAME_COUNTER + | t.EmberKeyStructBitmask.KEY_HAS_OUTGOING_FRAME_COUNTER + ), + type=ezsp_f.types.EmberKeyType.APPLICATION_LINK_KEY, + key=t.KeyData.convert("857C05003E761AF9689A49416A605C76"), + outgoingFrameCounter=3792973670, + incomingFrameCounter=1083290572, + sequenceNumber=147, + partnerEUI64=t.EUI64.convert("CC:CC:CC:FF:FE:E6:8E:CA"), + ), + ) + elif index == 1: + return ( + t.EmberStatus.SUCCESS, + ezsp_f.types.EmberKeyStruct( + bitmask=( + t.EmberKeyStructBitmask.KEY_IS_AUTHORIZED + | t.EmberKeyStructBitmask.KEY_HAS_PARTNER_EUI64 + | t.EmberKeyStructBitmask.KEY_HAS_INCOMING_FRAME_COUNTER + | t.EmberKeyStructBitmask.KEY_HAS_OUTGOING_FRAME_COUNTER + ), + type=ezsp_f.types.EmberKeyType.APPLICATION_LINK_KEY, + key=t.KeyData.convert("CA02E8BB757C94F89339D39CB3CDA7BE"), + outgoingFrameCounter=2597245184, + incomingFrameCounter=824424412, + sequenceNumber=19, + partnerEUI64=t.EUI64.convert("EC:1B:BD:FF:FE:2F:41:A4"), + ), + ) + elif index >= 12: + status = t.EmberStatus.INDEX_OUT_OF_RANGE + else: + status = t.EmberStatus.TABLE_ENTRY_ERASED + + return ( + status, + ezsp_f.types.EmberKeyStruct( + bitmask=t.EmberKeyStructBitmask(244), + type=ezsp_f.types.EmberKeyType(0x46), + key=t.KeyData.convert("b8a11c004b1200cdabcdabcdabcdabcd"), + outgoingFrameCounter=8192, + incomingFrameCounter=0, + sequenceNumber=0, + partnerEUI64=t.EUI64.convert("00:12:4b:00:1c:a1:b8:46"), + ), + ) + + ezsp_f.getKeyTableEntry = AsyncMock(side_effect=get_key_table_entry) + ezsp_f.getConfigurationValue = AsyncMock(return_value=(t.EmberStatus.SUCCESS, 13)) + + link_keys = [key async for key in ezsp_f.read_link_keys()] + assert link_keys == [ + zigpy.state.Key( + key=t.KeyData.convert("85:7C:05:00:3E:76:1A:F9:68:9A:49:41:6A:60:5C:76"), + tx_counter=3792973670, + rx_counter=1083290572, + seq=0, # Sequence number is 0 + partner_ieee=t.EUI64.convert("CC:CC:CC:FF:FE:E6:8E:CA"), + ), + zigpy.state.Key( + key=t.KeyData.convert("CA:02:E8:BB:75:7C:94:F8:93:39:D3:9C:B3:CD:A7:BE"), + tx_counter=2597245184, + rx_counter=824424412, + seq=0, # Sequence number is 0 + partner_ieee=t.EUI64.convert("EC:1B:BD:FF:FE:2F:41:A4"), + ), + ] + + +async def test_get_network_key_and_tc_link_key(ezsp_f): + def get_key(key_type): + key = { + ezsp_f.types.EmberKeyType.CURRENT_NETWORK_KEY: ezsp_f.types.EmberKeyStruct( + bitmask=( + t.EmberKeyStructBitmask.KEY_HAS_OUTGOING_FRAME_COUNTER + | t.EmberKeyStructBitmask.KEY_HAS_SEQUENCE_NUMBER + ), + type=ezsp_f.types.EmberKeyType.CURRENT_NETWORK_KEY, + key=t.KeyData.convert("2ccade06b3090c310315b3d574d3c85a"), + outgoingFrameCounter=118785, + incomingFrameCounter=0, + sequenceNumber=108, + partnerEUI64=t.EUI64.convert("00:00:00:00:00:00:00:00"), + ), + ezsp_f.types.EmberKeyType.TRUST_CENTER_LINK_KEY: ezsp_f.types.EmberKeyStruct( + bitmask=( + t.EmberKeyStructBitmask.KEY_IS_AUTHORIZED + | t.EmberKeyStructBitmask.KEY_HAS_PARTNER_EUI64 + | t.EmberKeyStructBitmask.KEY_HAS_OUTGOING_FRAME_COUNTER + ), + type=ezsp_f.types.EmberKeyType.TRUST_CENTER_LINK_KEY, + key=t.KeyData.convert("abcdabcdabcdabcdabcdabcdabcdabcd"), + outgoingFrameCounter=8712428, + incomingFrameCounter=0, + sequenceNumber=0, + partnerEUI64=t.EUI64.convert("00:12:4b:00:1c:a1:b8:46"), + ), + }[key_type] + + return (t.EmberStatus.SUCCESS, key) + + ezsp_f.getKey = AsyncMock(side_effect=get_key) + + assert (await ezsp_f.get_network_key()) == zigpy.state.Key( + key=t.KeyData.convert("2ccade06b3090c310315b3d574d3c85a"), + seq=108, + tx_counter=118785, + ) + + assert (await ezsp_f.get_tc_link_key()) == zigpy.state.Key( + key=t.KeyData.convert("abcdabcdabcdabcdabcdabcdabcdabcd"), + seq=0, + tx_counter=8712428, + partner_ieee=t.EUI64.convert("00:12:4b:00:1c:a1:b8:46"), + ) + + +async def test_write_child_data(ezsp_f) -> None: + # It's a no-op + await ezsp_f.write_child_data( + { + t.EUI64.convert("00:0b:57:ff:fe:2b:d4:57"): 0xC06B, + t.EUI64.convert("00:18:4b:00:1c:a1:b8:46"): 0x1234, + } + ) + + +async def test_read_address_table(ezsp_f) -> None: + # It's a no-op but still an async generator + async for nwk, eui64 in ezsp_f.read_address_table(): + pass + + +async def test_write_link_keys(ezsp_f, caplog) -> None: + ezsp_f.addOrUpdateKeyTableEntry = AsyncMock( + side_effect=[(t.EmberStatus.SUCCESS,), (t.EmberStatus.ERR_FATAL,)] + ) + + with caplog.at_level(logging.WARNING): + await ezsp_f.write_link_keys( + [ + zigpy.state.Key( + key=t.KeyData.convert("2ccade06b3090c310315b3d574d3c85a"), + seq=108, + tx_counter=1234, + rx_counter=5678, + partner_ieee=t.EUI64.convert("11:11:11:11:11:11:11:11"), + ), + zigpy.state.Key( + key=t.KeyData.convert("abcdabcdabcdabcdabcdabcdabcdabcd"), + seq=123, + tx_counter=2345, + rx_counter=98314, + partner_ieee=t.EUI64.convert("22:22:22:22:22:22:22:22"), + ), + ] + ) + + assert ezsp_f.addOrUpdateKeyTableEntry.mock_calls == [ + call( + t.EUI64.convert("11:11:11:11:11:11:11:11"), + True, + t.KeyData.convert("2ccade06b3090c310315b3d574d3c85a"), + ), + call( + t.EUI64.convert("22:22:22:22:22:22:22:22"), + True, + t.KeyData.convert("abcdabcdabcdabcdabcdabcdabcdabcd"), + ), + ] + + assert ( + "Couldn't add Key(key=ab:cd:ab:cd:ab:cd:ab:cd:ab:cd:ab:cd:ab:cd:ab:cd" + in caplog.text + ) + + +async def test_initialize_network(ezsp_f) -> None: + ezsp_f.networkInitExtended = AsyncMock(return_value=(t.EmberStatus.SUCCESS,)) + assert await ezsp_f.initialize_network() == t.sl_Status.OK + assert ezsp_f.networkInitExtended.mock_calls == [call(0x0000)] + + +async def test_write_nwk_frame_counter(ezsp_f) -> None: + # No-op + await ezsp_f.write_nwk_frame_counter(12345678) + + +async def test_write_aps_frame_counter(ezsp_f) -> None: + # No-op + await ezsp_f.write_aps_frame_counter(12345678) + + +async def test_factory_reset(ezsp_f) -> None: + ezsp_f.clearKeyTable = AsyncMock(return_value=(t.EmberStatus.SUCCESS,)) + await ezsp_f.factory_reset() + + assert ezsp_f.clearKeyTable.mock_calls == [call()] + + +async def test_send_unicast(ezsp_f) -> None: + ezsp_f.sendUnicast = AsyncMock(return_value=(t.EmberStatus.SUCCESS, 0x42)) + status, message_tag = await ezsp_f.send_unicast( + nwk=0x1234, + aps_frame=t.EmberApsFrame(), + message_tag=0x42, + data=b"hello", + ) + + assert status == t.sl_Status.OK + assert message_tag == 0x42 + assert ezsp_f.sendUnicast.mock_calls == [ + call( + t.EmberOutgoingMessageType.OUTGOING_DIRECT, + t.EmberNodeId(0x1234), + t.EmberApsFrame(), + 0x42, + b"hello", + ) + ] + + +async def test_send_multicast(ezsp_f) -> None: + ezsp_f.sendMulticast = AsyncMock(return_value=(t.EmberStatus.SUCCESS, 0x42)) + status, message_tag = await ezsp_f.send_multicast( + aps_frame=t.EmberApsFrame(), + radius=12, + non_member_radius=34, + message_tag=0x42, + data=b"hello", + ) + + assert status == t.sl_Status.OK + assert message_tag == 0x42 + assert ezsp_f.sendMulticast.mock_calls == [ + call( + t.EmberApsFrame(), + 12, + 34, + 0x42, + b"hello", + ) + ] + + +async def test_send_broadcast(ezsp_f) -> None: + ezsp_f.sendBroadcast = AsyncMock(return_value=(t.EmberStatus.SUCCESS, 0x42)) + status, message_tag = await ezsp_f.send_broadcast( + address=t.BroadcastAddress.ALL_ROUTERS_AND_COORDINATOR, + aps_frame=t.EmberApsFrame(), + radius=12, + message_tag=0x42, + aps_sequence=34, + data=b"hello", + ) + + assert status == t.sl_Status.OK + assert message_tag == 0x42 + assert ezsp_f.sendBroadcast.mock_calls == [ + call( + t.BroadcastAddress.ALL_ROUTERS_AND_COORDINATOR, + t.EmberApsFrame(), + 12, + 0x42, + b"hello", + ) + ] diff --git a/tests/test_ezsp_v5.py b/tests/test_ezsp_v5.py index 5f9b7b12..0b19f2d0 100644 --- a/tests/test_ezsp_v5.py +++ b/tests/test_ezsp_v5.py @@ -1,8 +1,10 @@ +from unittest.mock import AsyncMock, MagicMock, call, patch + import pytest +import zigpy.state import bellows.ezsp.v5 - -from .async_mock import AsyncMock, MagicMock, patch +import bellows.types as t @pytest.fixture @@ -37,233 +39,63 @@ async def test_pre_permit(ezsp_f): assert tclk_mock.await_count == 1 -command_frames = { - "addEndpoint": 0x02, - "addOrUpdateKeyTableEntry": 0x66, - "addTransientLinkKey": 0xAF, - "addressTableEntryIsActive": 0x5B, - "aesEncrypt": 0x94, - "aesMmoHash": 0x6F, - "becomeTrustCenter": 0x77, - "bindingIsActive": 0x2E, - "bootloadTransmitCompleteHandler": 0x93, - "broadcastNetworkKeySwitch": 0x74, - "broadcastNextNetworkKey": 0x73, - "calculateSmacs": 0x9F, - "calculateSmacs283k1": 0xEA, - "calculateSmacsHandler": 0xA0, - "calculateSmacsHandler283k1": 0xEB, - "callback": 0x06, - "changeSourceRouteHandler": 0xC4, - "childJoinHandler": 0x23, - "clearBindingTable": 0x2A, - "clearKeyTable": 0xB1, - "clearTemporaryDataMaybeStoreLinkKey": 0xA1, - "clearTemporaryDataMaybeStoreLinkKey283k1": 0xEE, - "clearTransientLinkKeys": 0x6B, - "counterRolloverHandler": 0xF2, - "customFrame": 0x47, - "customFrameHandler": 0x54, - "dGpSend": 0xC6, - "dGpSentHandler": 0xC7, - "debugWrite": 0x12, - "delayTest": 0x9D, - "deleteBinding": 0x2D, - "dsaSign": 0xA6, - "dsaSignHandler": 0xA7, - "dsaVerify": 0xA3, - "dsaVerify283k1": 0xB0, - "dsaVerifyHandler": 0x78, - "echo": 0x81, - "energyScanRequest": 0x9C, - "energyScanResultHandler": 0x48, - "eraseKeyTableEntry": 0x76, - "findAndRejoinNetwork": 0x21, - "findKeyTableEntry": 0x75, - "formNetwork": 0x1E, - "generateCbkeKeys": 0xA4, - "generateCbkeKeys283k1": 0xE8, - "generateCbkeKeysHandler": 0x9E, - "generateCbkeKeysHandler283k1": 0xE9, - "getAddressTableRemoteEui64": 0x5E, - "getAddressTableRemoteNodeId": 0x5F, - "getBinding": 0x2C, - "getBindingRemoteNodeId": 0x2F, - "getCertificate": 0xA5, - "getCertificate283k1": 0xEC, - "getChildData": 0x4A, - "getConfigurationValue": 0x52, - "getCtune": 0xF6, - "getCurrentSecurityState": 0x69, - "getEui64": 0x26, - "getExtendedTimeout": 0x7F, - "getExtendedValue": 0x03, - "getKey": 0x6A, - "getKeyTableEntry": 0x71, - "getLibraryStatus": 0x01, - "getLogicalChannel": 0xBA, - "getMfgToken": 0x0B, - "getMulticastTableEntry": 0x63, - "getNeighbor": 0x79, - "getNetworkParameters": 0x28, - "getNodeId": 0x27, - "getParentChildParameters": 0x29, - "getPolicy": 0x56, - "getRandomNumber": 0x49, - "getRouteTableEntry": 0x7B, - "getSecurityKeyStatus": 0xCD, - "getStandaloneBootloaderVersionPlatMicroPhy": 0x91, - "getTimer": 0x4E, - "getToken": 0x0A, - "getTransientLinkKey": 0xCE, - "getValue": 0xAA, - "getXncpInfo": 0x13, - "gpProxyTableProcessGpPairing": 0xC9, - "gpepIncomingMessageHandler": 0xC5, - "idConflictHandler": 0x7C, - "incomingBootloadMessageHandler": 0x92, - "incomingManyToOneRouteRequestHandler": 0x7D, - "incomingMessageHandler": 0x45, - "incomingRouteErrorHandler": 0x80, - "incomingRouteRecordHandler": 0x59, - "incomingSenderEui64Handler": 0x62, - "invalidCommand": 0x58, - "isZllNetwork": 0xBE, - "joinNetwork": 0x1F, - "launchStandaloneBootloader": 0x8F, - "leaveNetwork": 0x20, - "lookupEui64ByNodeId": 0x61, - "lookupNodeIdByEui64": 0x60, - "macFilterMatchMessageHandler": 0x46, - "macPassthroughMessageHandler": 0x97, - "maximumPayloadLength": 0x33, - "messageSentHandler": 0x3F, - "mfglibEnd": 0x84, - "mfglibGetChannel": 0x8B, - "mfglibGetPower": 0x8D, - "mfglibRxHandler": 0x8E, - "mfglibSendPacket": 0x89, - "mfglibSetChannel": 0x8A, - "mfglibSetPower": 0x8C, - "mfglibStart": 0x83, - "mfglibStartStream": 0x87, - "mfglibStartTone": 0x85, - "mfglibStopStream": 0x88, - "mfglibStopTone": 0x86, - "neighborCount": 0x7A, - "networkFoundHandler": 0x1B, - "networkInit": 0x17, - "networkInitExtended": 0x70, - "networkState": 0x18, - "noCallbacks": 0x07, - "nop": 0x05, - "overrideCurrentChannel": 0x95, - "permitJoining": 0x22, - "pollCompleteHandler": 0x43, - "pollForData": 0x42, - "pollHandler": 0x44, - "proxyBroadcast": 0x37, - "rawTransmitCompleteHandler": 0x98, - "readAndClearCounters": 0x65, - "readCounters": 0xF1, - "remoteDeleteBindingHandler": 0x32, - "remoteSetBindingHandler": 0x31, - "removeDevice": 0xA8, - "replaceAddressTableEntry": 0x82, - "requestLinkKey": 0x14, - "resetToFactoryDefaults": 0xCC, - "rf4ceAutoDiscoveryResponseCompleteHandler": 0xDE, - "rf4ceDeletePairingTableEntry": 0xD2, - "rf4ceDiscovery": 0xD9, - "rf4ceDiscoveryCompleteHandler": 0xDA, - "rf4ceDiscoveryRequestHandler": 0xDB, - "rf4ceDiscoveryResponseHandler": 0xDC, - "rf4ceEnableAutoDiscoveryResponse": 0xDD, - "rf4ceGetApplicationInfo": 0xEF, - "rf4ceGetMaxPayload": 0xF3, - "rf4ceGetNetworkParameters": 0xF4, - "rf4ceGetPairingTableEntry": 0xD1, - "rf4ceIncomingMessageHandler": 0xD5, - "rf4ceKeyUpdate": 0xD3, - "rf4ceMessageSentHandler": 0xD6, - "rf4cePair": 0xDF, - "rf4cePairCompleteHandler": 0xE0, - "rf4cePairRequestHandler": 0xE1, - "rf4ceSend": 0xD4, - "rf4ceSetApplicationInfo": 0xE7, - "rf4ceSetFrequencyAgilityParameters": 0xE6, - "rf4ceSetPairingTableEntry": 0xD0, - "rf4ceSetPowerSavingParameters": 0xE5, - "rf4ceStart": 0xD7, - "rf4ceStop": 0xD8, - "rf4ceUnpair": 0xE2, - "rf4ceUnpairCompleteHandler": 0xE4, - "rf4ceUnpairHandler": 0xE3, - "scanCompleteHandler": 0x1C, - "sendBootloadMessage": 0x90, - "sendBroadcast": 0x36, - "sendManyToOneRouteRequest": 0x41, - "sendMulticast": 0x38, - "sendRawMessage": 0x96, - "sendReply": 0x39, - "sendUnicast": 0x34, - "setAddressTableRemoteEui64": 0x5C, - "setAddressTableRemoteNodeId": 0x5D, - "setBinding": 0x2B, - "setBindingRemoteNodeId": 0x30, - "setChannelMap": 0xF7, - "setConcentrator": 0x10, - "setConfigurationValue": 0x53, - "setCtune": 0xF5, - "setExtendedTimeout": 0x7E, - "setGpioCurrentConfiguration": 0xAC, - "setGpioPowerUpDownConfiguration": 0xAD, - "setGpioRadioPowerMask": 0xAE, - "setInitialSecurityState": 0x68, - "setKeyTableEntry": 0x72, - "setLogicalAndRadioChannel": 0xB9, - "setManufacturerCode": 0x15, - "setMfgToken": 0x0C, - "setMulticastTableEntry": 0x64, - "setPolicy": 0x55, - "setPowerDescriptor": 0x16, - "setPreinstalledCbkeData": 0xA2, - "setPreinstalledCbkeData283k1": 0xED, - "setRadioChannel": 0x9A, - "setRadioPower": 0x99, - "setSecurityKey": 0xCA, - "setSecurityParameters": 0xCB, - "setSourceRoute": 0x5A, - "setTimer": 0x0E, - "setToken": 0x09, - "setValue": 0xAB, - "stackStatusHandler": 0x19, - "stackTokenChangedHandler": 0x0D, - "startScan": 0x1A, - "stopScan": 0x1D, - "switchNetworkKeyHandler": 0x6E, - "timerHandler": 0x0F, - "trustCenterJoinHandler": 0x24, - "unicastNwkKeyUpdate": 0xA9, - "version": 0x00, - "zigbeeKeyEstablishmentHandler": 0x9B, - "zllAddressAssignmentHandler": 0xB8, - "zllGetTokens": 0xBC, - "zllNetworkFoundHandler": 0xB6, - "zllNetworkOps": 0xB2, - "zllScanCompleteHandler": 0xB7, - "zllSetDataToken": 0xBD, - "zllSetInitialSecurityState": 0xB3, - "zllSetNonZllNetwork": 0xBF, - "zllSetRxOnWhenIdle": 0xB5, - "zllStartScan": 0xB4, - "zllTouchLinkTargetHandler": 0xBB, -} +async def test_read_address_table(ezsp_f): + def get_addr_table_node_id(index): + return ( + { + 16: t.EmberNodeId(0x44CB), + 17: t.EmberNodeId(0x0702), + 18: t.EmberNodeId(0x0000), # bogus entry + }.get(index, t.EmberNodeId(0xFFFF)), + ) + + ezsp_f.getAddressTableRemoteNodeId = AsyncMock(side_effect=get_addr_table_node_id) + + def get_addr_table_eui64(index): + if index < 16: + return (t.EUI64.convert("ff:ff:ff:ff:ff:ff:ff:ff"),) + elif 16 <= index <= 18: + return ( + { + 16: t.EUI64.convert("cc:cc:cc:ff:fe:e6:8e:ca"), + 17: t.EUI64.convert("ec:1b:bd:ff:fe:2f:41:a4"), + 18: t.EUI64.convert("00:00:00:00:00:00:00:00"), + }[index], + ) + else: + return (t.EUI64.convert("00:00:00:00:00:00:00:00"),) + + ezsp_f.getConfigurationValue = AsyncMock(return_value=(t.EmberStatus.SUCCESS, 20)) + ezsp_f.getAddressTableRemoteEui64 = AsyncMock(side_effect=get_addr_table_eui64) + + address_table = [key async for key in ezsp_f.read_address_table()] + assert address_table == [ + (0x44CB, t.EUI64.convert("cc:cc:cc:ff:fe:e6:8e:ca")), + (0x0702, t.EUI64.convert("ec:1b:bd:ff:fe:2f:41:a4")), + ] + + +async def test_write_nwk_frame_counter(ezsp_f) -> None: + ezsp_f.networkState = AsyncMock(return_value=(t.EmberNetworkStatus.NO_NETWORK,)) + ezsp_f.setValue = AsyncMock(return_value=(t.EmberStatus.SUCCESS,)) + await ezsp_f.write_nwk_frame_counter(12345678) + + assert ezsp_f.setValue.mock_calls == [ + call( + ezsp_f.types.EzspValueId.VALUE_NWK_FRAME_COUNTER, + t.uint32_t(12345678).serialize(), + ), + ] + +async def test_write_aps_frame_counter(ezsp_f) -> None: + ezsp_f.networkState = AsyncMock(return_value=(t.EmberNetworkStatus.NO_NETWORK,)) + ezsp_f.setValue = AsyncMock(return_value=(t.EmberStatus.SUCCESS,)) + await ezsp_f.write_aps_frame_counter(12345678) -def test_command_frames(ezsp_f): - """Test alphabetical list of frames matches the commands.""" - assert set(command_frames) == set(ezsp_f.COMMANDS) - for name, frame_id in command_frames.items(): - assert ezsp_f.COMMANDS[name][0] == frame_id - assert ezsp_f.COMMANDS_BY_ID[frame_id][0] == name + assert ezsp_f.setValue.mock_calls == [ + call( + ezsp_f.types.EzspValueId.VALUE_APS_FRAME_COUNTER, + t.uint32_t(12345678).serialize(), + ), + ] diff --git a/tests/test_ezsp_v6.py b/tests/test_ezsp_v6.py index 7e53fbab..c4281c12 100644 --- a/tests/test_ezsp_v6.py +++ b/tests/test_ezsp_v6.py @@ -1,14 +1,16 @@ -from unittest import mock +from unittest.mock import AsyncMock, MagicMock, call import pytest +import zigpy.state import bellows.ezsp.v6 +import bellows.types as t @pytest.fixture def ezsp_f(): """EZSP v6 protocol handler.""" - return bellows.ezsp.v6.EZSPv6(mock.MagicMock(), mock.MagicMock()) + return bellows.ezsp.v6.EZSPv6(MagicMock(), MagicMock()) def test_ezsp_frame(ezsp_f): @@ -25,245 +27,7 @@ def test_ezsp_frame_rx(ezsp_f): assert ezsp_f._handle_callback.call_args[0][1] == [0x01, 0x02, 0x1234] -def test_command_frames(ezsp_f): - """Test alphabetical list of frames matches the commands.""" - assert set(ezsp_f.COMMANDS) == set(command_frames) - for name, frame_id in command_frames.items(): - assert ezsp_f.COMMANDS[name][0] == frame_id - assert ezsp_f.COMMANDS_BY_ID[frame_id][0] == name - - -command_frames = { - "addEndpoint": 0x02, - "addOrUpdateKeyTableEntry": 0x66, - "addTransientLinkKey": 0xAF, - "addressTableEntryIsActive": 0x5B, - "aesEncrypt": 0x94, - "aesMmoHash": 0x6F, - "becomeTrustCenter": 0x77, - "bindingIsActive": 0x2E, - "bootloadTransmitCompleteHandler": 0x93, - "broadcastNetworkKeySwitch": 0x74, - "broadcastNextNetworkKey": 0x73, - "calculateSmacs": 0x9F, - "calculateSmacs283k1": 0xEA, - "calculateSmacsHandler": 0xA0, - "calculateSmacsHandler283k1": 0xEB, - "callback": 0x06, - "changeSourceRouteHandler": 0xC4, - "childJoinHandler": 0x23, - "clearBindingTable": 0x2A, - "clearKeyTable": 0xB1, - "clearTemporaryDataMaybeStoreLinkKey": 0xA1, - "clearTemporaryDataMaybeStoreLinkKey283k1": 0xEE, - "clearTransientLinkKeys": 0x6B, - "counterRolloverHandler": 0xF2, - "customFrame": 0x47, - "customFrameHandler": 0x54, - "dGpSend": 0xC6, - "dGpSentHandler": 0xC7, - "debugWrite": 0x12, - "delayTest": 0x9D, - "deleteBinding": 0x2D, - "dsaSign": 0xA6, - "dsaSignHandler": 0xA7, - "dsaVerify": 0xA3, - "dsaVerify283k1": 0xB0, - "dsaVerifyHandler": 0x78, - "dutyCycleHandler": 0x4D, - "echo": 0x81, - "energyScanRequest": 0x9C, - "energyScanResultHandler": 0x48, - "eraseKeyTableEntry": 0x76, - "findAndRejoinNetwork": 0x21, - "findKeyTableEntry": 0x75, - "findUnusedPanId": 0xD3, - "formNetwork": 0x1E, - "generateCbkeKeys": 0xA4, - "generateCbkeKeys283k1": 0xE8, - "generateCbkeKeysHandler": 0x9E, - "generateCbkeKeysHandler283k1": 0xE9, - "getAddressTableRemoteEui64": 0x5E, - "getAddressTableRemoteNodeId": 0x5F, - "getBinding": 0x2C, - "getBindingRemoteNodeId": 0x2F, - "getCertificate": 0xA5, - "getCertificate283k1": 0xEC, - "getChildData": 0x4A, - "getConfigurationValue": 0x52, - "getCtune": 0xF6, - "getCurrentDutyCycle": 0x4C, - "getCurrentSecurityState": 0x69, - "getEui64": 0x26, - "getDutyCycleLimits": 0x4B, - "getDutyCycleState": 0x35, - "getExtendedTimeout": 0x7F, - "getExtendedValue": 0x03, - "getKey": 0x6A, - "getKeyTableEntry": 0x71, - "getLibraryStatus": 0x01, - "getLogicalChannel": 0xBA, - "getMfgToken": 0x0B, - "getMulticastTableEntry": 0x63, - "getNeighbor": 0x79, - "getNetworkParameters": 0x28, - "getNodeId": 0x27, - "getParentChildParameters": 0x29, - "getPhyInterfaceCount": 0xFC, - "getPolicy": 0x56, - "getRadioParameters": 0xFD, - "getRandomNumber": 0x49, - "getRouteTableEntry": 0x7B, - "getRoutingShortcutThreshold": 0xD1, - "getSecurityKeyStatus": 0xCD, - "getSourceRouteTableEntry": 0xC1, - "getSourceRouteTableFilledSize": 0xC2, - "getSourceRouteTableTotalSize": 0xC3, - "getStandaloneBootloaderVersionPlatMicroPhy": 0x91, - "getTimer": 0x4E, - "getToken": 0x0A, - "getTransientLinkKey": 0xCE, - "getValue": 0xAA, - "getXncpInfo": 0x13, - "getZllPrimaryChannelMask": 0xD9, - "getZllSecondaryChannelMask": 0xDA, - "gpClearSinkTable": 0xE2, - "gpProxyTableGetEntry": 0xC8, - "gpProxyTableLookup": 0xC0, - "gpProxyTableProcessGpPairing": 0xC9, - "gpSinkTableFindOrAllocateEntry": 0xE1, - "gpSinkTableGetEntry": 0xDD, - "gpSinkTableLookup": 0xDE, - "gpSinkTableRemoveEntry": 0xE0, - "gpSinkTableSetEntry": 0xDF, - "gpepIncomingMessageHandler": 0xC5, - "idConflictHandler": 0x7C, - "incomingBootloadMessageHandler": 0x92, - "incomingManyToOneRouteRequestHandler": 0x7D, - "incomingMessageHandler": 0x45, - "incomingRouteErrorHandler": 0x80, - "incomingRouteRecordHandler": 0x59, - "incomingSenderEui64Handler": 0x62, - "invalidCommand": 0x58, - "isZllNetwork": 0xBE, - "joinNetwork": 0x1F, - "launchStandaloneBootloader": 0x8F, - "leaveNetwork": 0x20, - "lookupEui64ByNodeId": 0x61, - "lookupNodeIdByEui64": 0x60, - "macFilterMatchMessageHandler": 0x46, - "macPassthroughMessageHandler": 0x97, - "maximumPayloadLength": 0x33, - "messageSentHandler": 0x3F, - "mfglibEnd": 0x84, - "mfglibGetChannel": 0x8B, - "mfglibGetPower": 0x8D, - "mfglibRxHandler": 0x8E, - "mfglibSendPacket": 0x89, - "mfglibSetChannel": 0x8A, - "mfglibSetPower": 0x8C, - "mfglibStart": 0x83, - "mfglibStartStream": 0x87, - "mfglibStartTone": 0x85, - "mfglibStopStream": 0x88, - "mfglibStopTone": 0x86, - "multiPhySetRadioChannel": 0xFB, - "multiPhySetRadioPower": 0xFA, - "multiPhyStart": 0xF8, - "multiPhyStop": 0xF9, - "neighborCount": 0x7A, - "networkFoundHandler": 0x1B, - "networkInit": 0x17, - "networkState": 0x18, - "noCallbacks": 0x07, - "nop": 0x05, - "overrideCurrentChannel": 0x95, - "permitJoining": 0x22, - "pollCompleteHandler": 0x43, - "pollForData": 0x42, - "pollHandler": 0x44, - "proxyBroadcast": 0x37, - "rawTransmitCompleteHandler": 0x98, - "readAndClearCounters": 0x65, - "readCounters": 0xF1, - "remoteDeleteBindingHandler": 0x32, - "remoteSetBindingHandler": 0x31, - "removeDevice": 0xA8, - "replaceAddressTableEntry": 0x82, - "requestLinkKey": 0x14, - "resetToFactoryDefaults": 0xCC, - "scanCompleteHandler": 0x1C, - "sendBootloadMessage": 0x90, - "sendBroadcast": 0x36, - "sendLinkPowerDeltaRequest": 0xF7, - "sendManyToOneRouteRequest": 0x41, - "sendMulticast": 0x38, - "sendMulticastWithAlias": 0x3A, - "sendRawMessage": 0x96, - "sendReply": 0x39, - "sendTrustCenterLinkKey": 0x67, - "sendUnicast": 0x34, - "setAddressTableRemoteEui64": 0x5C, - "setAddressTableRemoteNodeId": 0x5D, - "setBinding": 0x2B, - "setBindingRemoteNodeId": 0x30, - "setBrokenRouteErrorCode": 0x11, - "setConcentrator": 0x10, - "setConfigurationValue": 0x53, - "setCtune": 0xF5, - "setDutyCycleLimitsInStack": 0x40, - "setExtendedTimeout": 0x7E, - "setGpioCurrentConfiguration": 0xAC, - "setGpioPowerUpDownConfiguration": 0xAD, - "setGpioRadioPowerMask": 0xAE, - "setInitialSecurityState": 0x68, - "setKeyTableEntry": 0x72, - "setLogicalAndRadioChannel": 0xB9, - "setManufacturerCode": 0x15, - "setMfgToken": 0x0C, - "setMulticastTableEntry": 0x64, - "setPolicy": 0x55, - "setPowerDescriptor": 0x16, - "setPreinstalledCbkeData": 0xA2, - "setPreinstalledCbkeData283k1": 0xED, - "setRadioChannel": 0x9A, - "setRadioPower": 0x99, - "setRoutingShortcutThreshold": 0xD0, - "setSecurityKey": 0xCA, - "setSecurityParameters": 0xCB, - "setSourceRoute": 0x5A, - "setTimer": 0x0E, - "setToken": 0x09, - "setValue": 0xAB, - "setZllAdditionalState": 0xD6, - "setZllNodeType": 0xD5, - "setZllPrimaryChannelMask": 0xDB, - "setZllSecondaryChannelMask": 0xDC, - "stackStatusHandler": 0x19, - "stackTokenChangedHandler": 0x0D, - "startScan": 0x1A, - "stopScan": 0x1D, - "switchNetworkKeyHandler": 0x6E, - "timerHandler": 0x0F, - "trustCenterJoinHandler": 0x24, - "unicastNwkKeyUpdate": 0xA9, - "unusedPanIdFoundHandler": 0xD2, - "version": 0x00, - "writeNodeData": 0xFE, - "zigbeeKeyEstablishmentHandler": 0x9B, - "zllAddressAssignmentHandler": 0xB8, - "zllGetTokens": 0xBC, - "zllNetworkFoundHandler": 0xB6, - "zllNetworkOps": 0xB2, - "zllOperationInProgress": 0xD7, - "zllRxOnWhenIdleGetActive": 0xD8, - "zllScanCompleteHandler": 0xB7, - "zllSetDataToken": 0xBD, - "zllSetInitialSecurityState": 0xB3, - "zllSetNonZllNetwork": 0xBF, - "zllSetRadioIdleMode": 0xD4, - "zllSetRxOnWhenIdle": 0xB5, - "zllSetSecurityStateWithoutKey": 0xCF, - "zllStartScan": 0xB4, - "zllTouchLinkTargetHandler": 0xBB, -} +async def test_initialize_network(ezsp_f) -> None: + ezsp_f.networkInit = AsyncMock(return_value=(t.EmberStatus.SUCCESS,)) + assert await ezsp_f.initialize_network() == t.sl_Status.OK + assert ezsp_f.networkInit.mock_calls == [call(0x0000)] diff --git a/tests/test_ezsp_v7.py b/tests/test_ezsp_v7.py index d5844524..bf46e98b 100644 --- a/tests/test_ezsp_v7.py +++ b/tests/test_ezsp_v7.py @@ -3,6 +3,7 @@ import pytest import bellows.ezsp.v7 +import bellows.types as t @pytest.fixture @@ -25,266 +26,32 @@ def test_ezsp_frame_rx(ezsp_f): assert ezsp_f._handle_callback.call_args[0][1] == [0x01, 0x02, 0x1234] -def test_command_frames(ezsp_f): - """Test alphabetical list of frames matches the commands.""" - assert set(ezsp_f.COMMANDS) == set(command_frames) - for name, frame_id in command_frames.items(): - assert ezsp_f.COMMANDS[name][0] == frame_id - assert ezsp_f.COMMANDS_BY_ID[frame_id][0] == name - - -command_frames = { - "addEndpoint": 0x02, - "addOrUpdateKeyTableEntry": 0x66, - "addTransientLinkKey": 0xAF, - "addressTableEntryIsActive": 0x5B, - "aesEncrypt": 0x94, - "aesMmoHash": 0x6F, - "becomeTrustCenter": 0x77, - "bindingIsActive": 0x2E, - "bootloadTransmitCompleteHandler": 0x93, - "broadcastNetworkKeySwitch": 0x74, - "broadcastNextNetworkKey": 0x73, - "calculateSmacs": 0x9F, - "calculateSmacs283k1": 0xEA, - "calculateSmacsHandler": 0xA0, - "calculateSmacsHandler283k1": 0xEB, - "callback": 0x06, - "changeSourceRouteHandler": 0xC4, - "childJoinHandler": 0x23, - "clearBindingTable": 0x2A, - "clearKeyTable": 0xB1, - "clearStoredBeacons": 0x3C, - "clearTemporaryDataMaybeStoreLinkKey": 0xA1, - "clearTemporaryDataMaybeStoreLinkKey283k1": 0xEE, - "clearTransientLinkKeys": 0x6B, - "counterRolloverHandler": 0xF2, - "customFrame": 0x47, - "customFrameHandler": 0x54, - "dGpSend": 0xC6, - "dGpSentHandler": 0xC7, - "debugWrite": 0x12, - "delayTest": 0x9D, - "deleteBinding": 0x2D, - "dsaSign": 0xA6, - "dsaSignHandler": 0xA7, - "dsaVerify": 0xA3, - "dsaVerify283k1": 0xB0, - "dsaVerifyHandler": 0x78, - "dutyCycleHandler": 0x4D, - "echo": 0x81, - "energyScanRequest": 0x9C, - "energyScanResultHandler": 0x48, - "eraseKeyTableEntry": 0x76, - "findAndRejoinNetwork": 0x21, - "findKeyTableEntry": 0x75, - "findUnusedPanId": 0xD3, - "formNetwork": 0x1E, - "generateCbkeKeys": 0xA4, - "generateCbkeKeys283k1": 0xE8, - "generateCbkeKeysHandler": 0x9E, - "generateCbkeKeysHandler283k1": 0xE9, - "getAddressTableRemoteEui64": 0x5E, - "getAddressTableRemoteNodeId": 0x5F, - "getBinding": 0x2C, - "getBeaconClassificationParams": 0xF3, - "getBindingRemoteNodeId": 0x2F, - "getCertificate": 0xA5, - "getCertificate283k1": 0xEC, - "getChildData": 0x4A, - "getConfigurationValue": 0x52, - "getCtune": 0xF6, - "getCurrentDutyCycle": 0x4C, - "getCurrentSecurityState": 0x69, - "getEui64": 0x26, - "getDutyCycleLimits": 0x4B, - "getDutyCycleState": 0x35, - "getExtendedTimeout": 0x7F, - "getExtendedValue": 0x03, - "getFirstBeacon": 0x3D, - "getKey": 0x6A, - "getKeyTableEntry": 0x71, - "getLibraryStatus": 0x01, - "getLogicalChannel": 0xBA, - "getMfgToken": 0x0B, - "getMulticastTableEntry": 0x63, - "getNeighbor": 0x79, - "getNetworkParameters": 0x28, - "getNextBeacon": 0x04, - "getNodeId": 0x27, - "getNumStoredBeacons": 0x08, - "getParentChildParameters": 0x29, - "getParentClassificationEnabled": 0xF0, - "getPhyInterfaceCount": 0xFC, - "getPolicy": 0x56, - "getRadioParameters": 0xFD, - "getRandomNumber": 0x49, - "getRouteTableEntry": 0x7B, - "getRoutingShortcutThreshold": 0xD1, - "getSecurityKeyStatus": 0xCD, - "getSourceRouteTableEntry": 0xC1, - "getSourceRouteTableFilledSize": 0xC2, - "getSourceRouteTableTotalSize": 0xC3, - "getStandaloneBootloaderVersionPlatMicroPhy": 0x91, - "getTimer": 0x4E, - "getToken": 0x0A, - "getTransientKeyTableEntry": 0x6D, - "getTransientLinkKey": 0xCE, - "getTrueRandomEntropySource": 0x4F, - "getValue": 0xAA, - "getXncpInfo": 0x13, - "getZllPrimaryChannelMask": 0xD9, - "getZllSecondaryChannelMask": 0xDA, - "gpProxyTableGetEntry": 0xC8, - "gpProxyTableLookup": 0xC0, - "gpProxyTableProcessGpPairing": 0xC9, - "gpSinkTableClearAll": 0xE2, - "gpSinkTableFindOrAllocateEntry": 0xE1, - "gpSinkTableGetEntry": 0xDD, - "gpSinkTableInit": 0x70, - "gpSinkTableLookup": 0xDE, - "gpSinkTableRemoveEntry": 0xE0, - "gpSinkTableSetEntry": 0xDF, - "gpepIncomingMessageHandler": 0xC5, - "idConflictHandler": 0x7C, - "incomingBootloadMessageHandler": 0x92, - "incomingManyToOneRouteRequestHandler": 0x7D, - "incomingMessageHandler": 0x45, - "incomingRouteErrorHandler": 0x80, - "incomingRouteRecordHandler": 0x59, - "incomingSenderEui64Handler": 0x62, - "invalidCommand": 0x58, - "isHubConnected": 0xE6, - "isUpTimeLong": 0xE5, - "isZllNetwork": 0xBE, - "joinNetwork": 0x1F, - "joinNetworkDirectly": 0x3B, - "launchStandaloneBootloader": 0x8F, - "leaveNetwork": 0x20, - "lookupEui64ByNodeId": 0x61, - "lookupNodeIdByEui64": 0x60, - "macFilterMatchMessageHandler": 0x46, - "macPassthroughMessageHandler": 0x97, - "maximumPayloadLength": 0x33, - "messageSentHandler": 0x3F, - "mfglibEnd": 0x84, - "mfglibGetChannel": 0x8B, - "mfglibGetPower": 0x8D, - "mfglibRxHandler": 0x8E, - "mfglibSendPacket": 0x89, - "mfglibSetChannel": 0x8A, - "mfglibSetPower": 0x8C, - "mfglibStart": 0x83, - "mfglibStartStream": 0x87, - "mfglibStartTone": 0x85, - "mfglibStopStream": 0x88, - "mfglibStopTone": 0x86, - "multiPhySetRadioChannel": 0xFB, - "multiPhySetRadioPower": 0xFA, - "multiPhyStart": 0xF8, - "multiPhyStop": 0xF9, - "neighborCount": 0x7A, - "networkFoundHandler": 0x1B, - "networkInit": 0x17, - "networkState": 0x18, - "noCallbacks": 0x07, - "nop": 0x05, - "overrideCurrentChannel": 0x95, - "permitJoining": 0x22, - "pollCompleteHandler": 0x43, - "pollForData": 0x42, - "pollHandler": 0x44, - "proxyBroadcast": 0x37, - "rawTransmitCompleteHandler": 0x98, - "readAndClearCounters": 0x65, - "readCounters": 0xF1, - "remoteDeleteBindingHandler": 0x32, - "remoteSetBindingHandler": 0x31, - "removeDevice": 0xA8, - "replaceAddressTableEntry": 0x82, - "requestLinkKey": 0x14, - "resetToFactoryDefaults": 0xCC, - "scanCompleteHandler": 0x1C, - "sendBootloadMessage": 0x90, - "sendBroadcast": 0x36, - "sendLinkPowerDeltaRequest": 0xF7, - "sendManyToOneRouteRequest": 0x41, - "sendMulticast": 0x38, - "sendMulticastWithAlias": 0x3A, - "sendPanIdUpdate": 0x57, - "sendRawMessage": 0x96, - "sendReply": 0x39, - "sendTrustCenterLinkKey": 0x67, - "sendUnicast": 0x34, - "setAddressTableRemoteEui64": 0x5C, - "setAddressTableRemoteNodeId": 0x5D, - "setBeaconClassificationParams": 0xEF, - "setBinding": 0x2B, - "setBindingRemoteNodeId": 0x30, - "setBrokenRouteErrorCode": 0x11, - "setConcentrator": 0x10, - "setConfigurationValue": 0x53, - "setCtune": 0xF5, - "setDutyCycleLimitsInStack": 0x40, - "setExtendedTimeout": 0x7E, - "setGpioCurrentConfiguration": 0xAC, - "setGpioPowerUpDownConfiguration": 0xAD, - "setGpioRadioPowerMask": 0xAE, - "setHubConnectivity": 0xE4, - "setInitialSecurityState": 0x68, - "setKeyTableEntry": 0x72, - "setLogicalAndRadioChannel": 0xB9, - "setLongUpTime": 0xE3, - "setMacPollCcaWaitTime": 0xF4, - "setManufacturerCode": 0x15, - "setMfgToken": 0x0C, - "setMulticastTableEntry": 0x64, - "setParentClassificationEnabled": 0xE7, - "setPolicy": 0x55, - "setPowerDescriptor": 0x16, - "setPreinstalledCbkeData": 0xA2, - "setPreinstalledCbkeData283k1": 0xED, - "setRadioChannel": 0x9A, - "setRadioPower": 0x99, - "setRoutingShortcutThreshold": 0xD0, - "setSecurityKey": 0xCA, - "setSecurityParameters": 0xCB, - "setSourceRoute": 0x5A, - "setTimer": 0x0E, - "setToken": 0x09, - "setValue": 0xAB, - "setZllAdditionalState": 0xD6, - "setZllNodeType": 0xD5, - "setZllPrimaryChannelMask": 0xDB, - "setZllSecondaryChannelMask": 0xDC, - "stackStatusHandler": 0x19, - "stackTokenChangedHandler": 0x0D, - "startScan": 0x1A, - "stopScan": 0x1D, - "switchNetworkKeyHandler": 0x6E, - "timerHandler": 0x0F, - "trustCenterJoinHandler": 0x24, - "unicastCurrentNetworkKey": 0x50, - "unicastNwkKeyUpdate": 0xA9, - "unusedPanIdFoundHandler": 0xD2, - "updateTcLinkKey": 0x6C, - "version": 0x00, - "writeNodeData": 0xFE, - "zigbeeKeyEstablishmentHandler": 0x9B, - "zllAddressAssignmentHandler": 0xB8, - "zllClearTokens": 0x25, - "zllGetTokens": 0xBC, - "zllNetworkFoundHandler": 0xB6, - "zllNetworkOps": 0xB2, - "zllOperationInProgress": 0xD7, - "zllRxOnWhenIdleGetActive": 0xD8, - "zllScanCompleteHandler": 0xB7, - "zllSetDataToken": 0xBD, - "zllSetInitialSecurityState": 0xB3, - "zllSetNonZllNetwork": 0xBF, - "zllSetRadioIdleMode": 0xD4, - "zllSetRxOnWhenIdle": 0xB5, - "zllSetSecurityStateWithoutKey": 0xCF, - "zllStartScan": 0xB4, - "zllTouchLinkTargetHandler": 0xBB, -} +async def test_read_child_data(ezsp_f): + def get_child_data(index): + if index == 0: + status = t.EmberStatus.SUCCESS + else: + status = t.EmberStatus.NOT_JOINED + + return ( + status, + bellows.ezsp.v7.types.EmberChildData( + eui64=t.EUI64.convert("00:0b:57:ff:fe:2b:d4:57"), + type=t.EmberNodeType.SLEEPY_END_DEVICE, + id=t.EmberNodeId(0xC06B), + phy=0, + power=128, + timeout=3, + ), + ) + + ezsp_f.getChildData = mock.AsyncMock(side_effect=get_child_data) + + child_data = [row async for row in ezsp_f.read_child_data()] + assert child_data == [ + ( + 0xC06B, + t.EUI64.convert("00:0b:57:ff:fe:2b:d4:57"), + t.EmberNodeType.SLEEPY_END_DEVICE, + ) + ] diff --git a/tests/test_ezsp_v8.py b/tests/test_ezsp_v8.py index 8d26af6b..722fd965 100644 --- a/tests/test_ezsp_v8.py +++ b/tests/test_ezsp_v8.py @@ -1,9 +1,10 @@ +from unittest.mock import AsyncMock, MagicMock, patch + import pytest from bellows.ash import DataFrame import bellows.ezsp.v8 - -from .async_mock import AsyncMock, MagicMock, patch +import bellows.types as t @pytest.fixture @@ -40,14 +41,6 @@ async def test_pre_permit(ezsp_f): assert tclk_mock.await_count == 1 -def test_command_frames(ezsp_f): - """Test alphabetical list of frames matches the commands.""" - assert set(ezsp_f.COMMANDS) == set(command_frames) - for name, frame_id in command_frames.items(): - assert ezsp_f.COMMANDS[name][0] == frame_id - assert ezsp_f.COMMANDS_BY_ID[frame_id][0] == name - - def test_get_key_table_entry_fallback_parsing(ezsp_f): """Test parsing of a getKeyTableEntry response with an invalid length.""" data_frame = DataFrame.from_bytes( @@ -60,260 +53,3 @@ def test_get_key_table_entry_fallback_parsing(ezsp_f): assert len(ezsp_f._handle_callback.mock_calls) == 1 mock_call = ezsp_f._handle_callback.mock_calls[0] assert mock_call.args[0] == "getKeyTableEntry" - - -command_frames = { - "addEndpoint": 0x0002, - "addOrUpdateKeyTableEntry": 0x0066, - "addTransientLinkKey": 0x00AF, - "addressTableEntryIsActive": 0x005B, - "aesEncrypt": 0x0094, - "aesMmoHash": 0x006F, - "becomeTrustCenter": 0x0077, - "bindingIsActive": 0x002E, - "bootloadTransmitCompleteHandler": 0x0093, - "broadcastNetworkKeySwitch": 0x0074, - "broadcastNextNetworkKey": 0x0073, - "calculateSmacs": 0x009F, - "calculateSmacs283k1": 0x00EA, - "calculateSmacsHandler": 0x00A0, - "calculateSmacsHandler283k1": 0x00EB, - "callback": 0x0006, - "childJoinHandler": 0x0023, - "clearBindingTable": 0x002A, - "clearKeyTable": 0x00B1, - "clearStoredBeacons": 0x003C, - "clearTemporaryDataMaybeStoreLinkKey": 0x00A1, - "clearTemporaryDataMaybeStoreLinkKey283k1": 0x00EE, - "clearTransientLinkKeys": 0x006B, - "counterRolloverHandler": 0x00F2, - "customFrame": 0x0047, - "customFrameHandler": 0x0054, - "dGpSend": 0x00C6, - "dGpSentHandler": 0x00C7, - "debugWrite": 0x0012, - "delayTest": 0x009D, - "deleteBinding": 0x002D, - "dsaSign": 0x00A6, - "dsaSignHandler": 0x00A7, - "dsaVerify": 0x00A3, - "dsaVerify283k1": 0x00B0, - "dsaVerifyHandler": 0x0078, - "dutyCycleHandler": 0x004D, - "echo": 0x0081, - "energyScanRequest": 0x009C, - "energyScanResultHandler": 0x0048, - "eraseKeyTableEntry": 0x0076, - "findAndRejoinNetwork": 0x0021, - "findKeyTableEntry": 0x0075, - "findUnusedPanId": 0x00D3, - "formNetwork": 0x001E, - "generateCbkeKeys": 0x00A4, - "generateCbkeKeys283k1": 0x00E8, - "generateCbkeKeysHandler": 0x009E, - "generateCbkeKeysHandler283k1": 0x00E9, - "getAddressTableRemoteEui64": 0x005E, - "getAddressTableRemoteNodeId": 0x005F, - "getBinding": 0x002C, - "getBeaconClassificationParams": 0x00F3, - "getBindingRemoteNodeId": 0x002F, - "getCertificate": 0x00A5, - "getCertificate283k1": 0x00EC, - "getChildData": 0x004A, - "getConfigurationValue": 0x0052, - "getCurrentDutyCycle": 0x004C, - "getCurrentSecurityState": 0x0069, - "getEui64": 0x0026, - "getDutyCycleLimits": 0x004B, - "getDutyCycleState": 0x0035, - "getExtendedTimeout": 0x007F, - "getExtendedValue": 0x0003, - "getFirstBeacon": 0x003D, - "getKey": 0x006A, - "getKeyTableEntry": 0x0071, - "getLibraryStatus": 0x0001, - "getLogicalChannel": 0x00BA, - "getMfgToken": 0x000B, - "getMulticastTableEntry": 0x0063, - "getNeighbor": 0x0079, - "getNeighborFrameCounter": 0x003E, - "getNetworkParameters": 0x0028, - "getNextBeacon": 0x0004, - "getNodeId": 0x0027, - "getNumStoredBeacons": 0x0008, - "getParentChildParameters": 0x0029, - "getParentClassificationEnabled": 0x00F0, - "getPhyInterfaceCount": 0x00FC, - "getPolicy": 0x0056, - "getRadioParameters": 0x00FD, - "getRandomNumber": 0x0049, - "getRouteTableEntry": 0x007B, - "getRoutingShortcutThreshold": 0x00D1, - "getSecurityKeyStatus": 0x00CD, - "getSourceRouteTableEntry": 0x00C1, - "getSourceRouteTableFilledSize": 0x00C2, - "getSourceRouteTableTotalSize": 0x00C3, - "getStandaloneBootloaderVersionPlatMicroPhy": 0x0091, - "getTimer": 0x004E, - "getToken": 0x000A, - "getTransientKeyTableEntry": 0x006D, - "getTransientLinkKey": 0x00CE, - "getTrueRandomEntropySource": 0x004F, - "getValue": 0x00AA, - "getXncpInfo": 0x0013, - "getZllPrimaryChannelMask": 0x00D9, - "getZllSecondaryChannelMask": 0x00DA, - "gpProxyTableGetEntry": 0x00C8, - "gpProxyTableLookup": 0x00C0, - "gpProxyTableProcessGpPairing": 0x00C9, - "gpSinkTableClearAll": 0x00E2, - "gpSinkTableFindOrAllocateEntry": 0x00E1, - "gpSinkTableGetEntry": 0x00DD, - "gpSinkTableInit": 0x0070, - "gpSinkTableLookup": 0x00DE, - "gpSinkTableRemoveEntry": 0x00E0, - "gpSinkTableSetEntry": 0x00DF, - "gpepIncomingMessageHandler": 0x00C5, - "idConflictHandler": 0x007C, - "incomingBootloadMessageHandler": 0x0092, - "incomingManyToOneRouteRequestHandler": 0x007D, - "incomingMessageHandler": 0x0045, - "incomingNetworkStatusHandler": 0x00C4, - "incomingRouteErrorHandler": 0x0080, - "incomingRouteRecordHandler": 0x0059, - "incomingSenderEui64Handler": 0x0062, - "invalidCommand": 0x0058, - "isHubConnected": 0x00E6, - "isUpTimeLong": 0x00E5, - "isZllNetwork": 0x00BE, - "joinNetwork": 0x001F, - "joinNetworkDirectly": 0x003B, - "launchStandaloneBootloader": 0x008F, - "leaveNetwork": 0x0020, - "lookupEui64ByNodeId": 0x0061, - "lookupNodeIdByEui64": 0x0060, - "macFilterMatchMessageHandler": 0x0046, - "macPassthroughMessageHandler": 0x0097, - "maximumPayloadLength": 0x0033, - "messageSentHandler": 0x003F, - "mfglibEnd": 0x0084, - "mfglibGetChannel": 0x008B, - "mfglibGetPower": 0x008D, - "mfglibRxHandler": 0x008E, - "mfglibSendPacket": 0x0089, - "mfglibSetChannel": 0x008A, - "mfglibSetPower": 0x008C, - "mfglibStart": 0x0083, - "mfglibStartStream": 0x0087, - "mfglibStartTone": 0x0085, - "mfglibStopStream": 0x0088, - "mfglibStopTone": 0x0086, - "multiPhySetRadioChannel": 0x00FB, - "multiPhySetRadioPower": 0x00FA, - "multiPhyStart": 0x00F8, - "multiPhyStop": 0x00F9, - "neighborCount": 0x007A, - "networkFoundHandler": 0x001B, - "networkInit": 0x0017, - "networkState": 0x0018, - "noCallbacks": 0x0007, - "nop": 0x0005, - "overrideCurrentChannel": 0x0095, - "permitJoining": 0x0022, - "pollCompleteHandler": 0x0043, - "pollForData": 0x0042, - "pollHandler": 0x0044, - "proxyBroadcast": 0x0037, - "rawTransmitCompleteHandler": 0x0098, - "readAndClearCounters": 0x0065, - "readCounters": 0x00F1, - "remoteDeleteBindingHandler": 0x0032, - "remoteSetBindingHandler": 0x0031, - "removeDevice": 0x00A8, - "replaceAddressTableEntry": 0x0082, - "requestLinkKey": 0x0014, - "resetToFactoryDefaults": 0x00CC, - "scanCompleteHandler": 0x001C, - "sendBootloadMessage": 0x0090, - "sendBroadcast": 0x0036, - "sendLinkPowerDeltaRequest": 0x00F7, - "sendManyToOneRouteRequest": 0x0041, - "sendMulticast": 0x0038, - "sendMulticastWithAlias": 0x003A, - "sendPanIdUpdate": 0x0057, - "sendRawMessage": 0x0096, - "sendRawMessageExtended": 0x0051, - "sendReply": 0x0039, - "sendTrustCenterLinkKey": 0x0067, - "sendUnicast": 0x0034, - "setAddressTableRemoteEui64": 0x005C, - "setAddressTableRemoteNodeId": 0x005D, - "setBeaconClassificationParams": 0x00EF, - "setBinding": 0x002B, - "setBindingRemoteNodeId": 0x0030, - "setBrokenRouteErrorCode": 0x0011, - "setConcentrator": 0x0010, - "setConfigurationValue": 0x0053, - "setDutyCycleLimitsInStack": 0x0040, - "setExtendedTimeout": 0x007E, - "setGpioCurrentConfiguration": 0x00AC, - "setGpioPowerUpDownConfiguration": 0x00AD, - "setHubConnectivity": 0x00E4, - "setInitialSecurityState": 0x0068, - "setKeyTableEntry": 0x0072, - "setLogicalAndRadioChannel": 0x00B9, - "setLongUpTime": 0x00E3, - "setMacPollFailureWaitTime": 0x00F4, - "setManufacturerCode": 0x0015, - "setMfgToken": 0x000C, - "setMulticastTableEntry": 0x0064, - "setParentClassificationEnabled": 0x00E7, - "setPolicy": 0x0055, - "setPowerDescriptor": 0x0016, - "setPreinstalledCbkeData": 0x00A2, - "setPreinstalledCbkeData283k1": 0x00ED, - "setRadioChannel": 0x009A, - "setRadioPower": 0x0099, - "setRoutingShortcutThreshold": 0x00D0, - "setSecurityKey": 0x00CA, - "setSecurityParameters": 0x00CB, - "setSourceRoute": 0x00AE, - "setSourceRouteDiscoveryMode": 0x005A, - "setTimer": 0x000E, - "setToken": 0x0009, - "setValue": 0x00AB, - "setZllAdditionalState": 0x00D6, - "setZllNodeType": 0x00D5, - "setZllPrimaryChannelMask": 0x00DB, - "setZllSecondaryChannelMask": 0x00DC, - "stackStatusHandler": 0x0019, - "stackTokenChangedHandler": 0x000D, - "startScan": 0x001A, - "stopScan": 0x001D, - "switchNetworkKeyHandler": 0x006E, - "timerHandler": 0x000F, - "trustCenterJoinHandler": 0x0024, - "unicastCurrentNetworkKey": 0x0050, - "unicastNwkKeyUpdate": 0x00A9, - "unusedPanIdFoundHandler": 0x00D2, - "updateTcLinkKey": 0x006C, - "version": 0x0000, - "writeNodeData": 0x00FE, - "zigbeeKeyEstablishmentHandler": 0x009B, - "zllAddressAssignmentHandler": 0x00B8, - "zllClearTokens": 0x0025, - "zllGetTokens": 0x00BC, - "zllNetworkFoundHandler": 0x00B6, - "zllNetworkOps": 0x00B2, - "zllOperationInProgress": 0x00D7, - "zllRxOnWhenIdleGetActive": 0x00D8, - "zllScanCompleteHandler": 0x00B7, - "zllSetDataToken": 0x00BD, - "zllSetInitialSecurityState": 0x00B3, - "zllSetNonZllNetwork": 0x00BF, - "zllSetRadioIdleMode": 0x00D4, - "zllSetRxOnWhenIdle": 0x00B5, - "zllSetSecurityStateWithoutKey": 0x00CF, - "zllStartScan": 0x00B4, - "zllTouchLinkTargetHandler": 0x00BB, -} diff --git a/tests/test_ezsp_v9.py b/tests/test_ezsp_v9.py index 80a71b01..3b148227 100644 --- a/tests/test_ezsp_v9.py +++ b/tests/test_ezsp_v9.py @@ -1,8 +1,9 @@ +from unittest.mock import AsyncMock, MagicMock, call, patch + import pytest import bellows.ezsp.v9 - -from .async_mock import AsyncMock, MagicMock, patch +import bellows.types as t @pytest.fixture @@ -39,274 +40,37 @@ async def test_pre_permit(ezsp_f): assert tclk_mock.await_count == 1 -def test_command_frames(ezsp_f): - """Test alphabetical list of frames matches the commands.""" - assert set(ezsp_f.COMMANDS) == set(command_frames) - for name, frame_id in command_frames.items(): - assert ezsp_f.COMMANDS[name][0] == frame_id - assert ezsp_f.COMMANDS_BY_ID[frame_id][0] == name +async def test_write_child_data(ezsp_f) -> None: + ezsp_f.setChildData = AsyncMock(return_value=[ezsp_f.types.EmberStatus.SUCCESS]) + await ezsp_f.write_child_data( + { + t.EUI64.convert("00:0b:57:ff:fe:2b:d4:57"): 0xC06B, + t.EUI64.convert("00:18:4b:00:1c:a1:b8:46"): 0x1234, + } + ) -command_frames = { - "addEndpoint": 0x0002, - "addOrUpdateKeyTableEntry": 0x0066, - "addTransientLinkKey": 0x00AF, - "addressTableEntryIsActive": 0x005B, - "aesEncrypt": 0x0094, - "aesMmoHash": 0x006F, - "becomeTrustCenter": 0x0077, - "bindingIsActive": 0x002E, - "bootloadTransmitCompleteHandler": 0x0093, - "broadcastNetworkKeySwitch": 0x0074, - "broadcastNextNetworkKey": 0x0073, - "calculateSmacs": 0x009F, - "calculateSmacs283k1": 0x00EA, - "calculateSmacsHandler": 0x00A0, - "calculateSmacsHandler283k1": 0x00EB, - "callback": 0x0006, - "childJoinHandler": 0x0023, - "clearBindingTable": 0x002A, - "clearKeyTable": 0x00B1, - "clearStoredBeacons": 0x003C, - "clearTemporaryDataMaybeStoreLinkKey": 0x00A1, - "clearTemporaryDataMaybeStoreLinkKey283k1": 0x00EE, - "clearTransientLinkKeys": 0x006B, - "counterRolloverHandler": 0x00F2, - "customFrame": 0x0047, - "customFrameHandler": 0x0054, - "dGpSend": 0x00C6, - "dGpSentHandler": 0x00C7, - "debugWrite": 0x0012, - "delayTest": 0x009D, - "deleteBinding": 0x002D, - "dsaSign": 0x00A6, - "dsaSignHandler": 0x00A7, - "dsaVerify": 0x00A3, - "dsaVerify283k1": 0x00B0, - "dsaVerifyHandler": 0x0078, - "dutyCycleHandler": 0x004D, - "echo": 0x0081, - "energyScanRequest": 0x009C, - "energyScanResultHandler": 0x0048, - "eraseKeyTableEntry": 0x0076, - "findAndRejoinNetwork": 0x0021, - "findKeyTableEntry": 0x0075, - "findUnusedPanId": 0x00D3, - "formNetwork": 0x001E, - "generateCbkeKeys": 0x00A4, - "generateCbkeKeys283k1": 0x00E8, - "generateCbkeKeysHandler": 0x009E, - "generateCbkeKeysHandler283k1": 0x00E9, - "getAddressTableRemoteEui64": 0x005E, - "getAddressTableRemoteNodeId": 0x005F, - "getBinding": 0x002C, - "getBeaconClassificationParams": 0x00F3, - "getBindingRemoteNodeId": 0x002F, - "getCertificate": 0x00A5, - "getCertificate283k1": 0x00EC, - "getChildData": 0x004A, - "setChildData": 0x00AC, - "getConfigurationValue": 0x0052, - "getCurrentDutyCycle": 0x004C, - "getCurrentSecurityState": 0x0069, - "getEui64": 0x0026, - "getDutyCycleLimits": 0x004B, - "getDutyCycleState": 0x0035, - "getExtendedTimeout": 0x007F, - "getExtendedValue": 0x0003, - "getFirstBeacon": 0x003D, - "getKey": 0x006A, - "getKeyTableEntry": 0x0071, - "getLibraryStatus": 0x0001, - "getLogicalChannel": 0x00BA, - "getMfgToken": 0x000B, - "getMulticastTableEntry": 0x0063, - "getNeighbor": 0x0079, - "getNeighborFrameCounter": 0x003E, - "setNeighborFrameCounter": 0x00AD, - "getNetworkParameters": 0x0028, - "getNextBeacon": 0x0004, - "getNodeId": 0x0027, - "getNumStoredBeacons": 0x0008, - "getParentChildParameters": 0x0029, - "getParentClassificationEnabled": 0x00F0, - "getPhyInterfaceCount": 0x00FC, - "getPolicy": 0x0056, - "getRadioParameters": 0x00FD, - "setRadioIeee802154CcaMode": 0x0095, - "getRandomNumber": 0x0049, - "getRouteTableEntry": 0x007B, - "getRoutingShortcutThreshold": 0x00D1, - "getSecurityKeyStatus": 0x00CD, - "getSourceRouteTableEntry": 0x00C1, - "getSourceRouteTableFilledSize": 0x00C2, - "getSourceRouteTableTotalSize": 0x00C3, - "getStandaloneBootloaderVersionPlatMicroPhy": 0x0091, - "getTimer": 0x004E, - "getToken": 0x000A, - "getTokenCount": 0x0100, - "getTokenInfo": 0x0101, - "getTokenData": 0x0102, - "setTokenData": 0x0103, - "resetNode": 0x0104, - "getTransientKeyTableEntry": 0x006D, - "getTransientLinkKey": 0x00CE, - "getTrueRandomEntropySource": 0x004F, - "getValue": 0x00AA, - "getXncpInfo": 0x0013, - "getZllPrimaryChannelMask": 0x00D9, - "getZllSecondaryChannelMask": 0x00DA, - "gpProxyTableGetEntry": 0x00C8, - "gpProxyTableLookup": 0x00C0, - "gpProxyTableProcessGpPairing": 0x00C9, - "gpSinkTableClearAll": 0x00E2, - "gpSinkTableFindOrAllocateEntry": 0x00E1, - "gpSinkTableGetEntry": 0x00DD, - "gpSinkTableInit": 0x0070, - "gpSinkTableLookup": 0x00DE, - "gpSinkTableRemoveEntry": 0x00E0, - "gpSinkTableSetEntry": 0x00DF, - "gpepIncomingMessageHandler": 0x00C5, - "idConflictHandler": 0x007C, - "incomingBootloadMessageHandler": 0x0092, - "incomingManyToOneRouteRequestHandler": 0x007D, - "incomingMessageHandler": 0x0045, - "incomingNetworkStatusHandler": 0x00C4, - "incomingRouteErrorHandler": 0x0080, - "incomingRouteRecordHandler": 0x0059, - "incomingSenderEui64Handler": 0x0062, - "invalidCommand": 0x0058, - "isHubConnected": 0x00E6, - "isUpTimeLong": 0x00E5, - "isZllNetwork": 0x00BE, - "joinNetwork": 0x001F, - "joinNetworkDirectly": 0x003B, - "launchStandaloneBootloader": 0x008F, - "leaveNetwork": 0x0020, - "lookupEui64ByNodeId": 0x0061, - "lookupNodeIdByEui64": 0x0060, - "macFilterMatchMessageHandler": 0x0046, - "macPassthroughMessageHandler": 0x0097, - "maximumPayloadLength": 0x0033, - "messageSentHandler": 0x003F, - "mfglibEnd": 0x0084, - "mfglibGetChannel": 0x008B, - "mfglibGetPower": 0x008D, - "mfglibRxHandler": 0x008E, - "mfglibSendPacket": 0x0089, - "mfglibSetChannel": 0x008A, - "mfglibSetPower": 0x008C, - "mfglibStart": 0x0083, - "mfglibStartStream": 0x0087, - "mfglibStartTone": 0x0085, - "mfglibStopStream": 0x0088, - "mfglibStopTone": 0x0086, - "multiPhySetRadioChannel": 0x00FB, - "multiPhySetRadioPower": 0x00FA, - "multiPhyStart": 0x00F8, - "multiPhyStop": 0x00F9, - "neighborCount": 0x007A, - "networkFoundHandler": 0x001B, - "networkInit": 0x0017, - "networkState": 0x0018, - "noCallbacks": 0x0007, - "nop": 0x0005, - "permitJoining": 0x0022, - "pollCompleteHandler": 0x0043, - "pollForData": 0x0042, - "pollHandler": 0x0044, - "proxyBroadcast": 0x0037, - "rawTransmitCompleteHandler": 0x0098, - "readAndClearCounters": 0x0065, - "readCounters": 0x00F1, - "remoteDeleteBindingHandler": 0x0032, - "remoteSetBindingHandler": 0x0031, - "removeDevice": 0x00A8, - "replaceAddressTableEntry": 0x0082, - "requestLinkKey": 0x0014, - "resetToFactoryDefaults": 0x00CC, - "scanCompleteHandler": 0x001C, - "sendBootloadMessage": 0x0090, - "sendBroadcast": 0x0036, - "sendLinkPowerDeltaRequest": 0x00F7, - "sendManyToOneRouteRequest": 0x0041, - "sendMulticast": 0x0038, - "sendMulticastWithAlias": 0x003A, - "sendPanIdUpdate": 0x0057, - "sendRawMessage": 0x0096, - "sendRawMessageExtended": 0x0051, - "sendReply": 0x0039, - "sendTrustCenterLinkKey": 0x0067, - "sendUnicast": 0x0034, - "setAddressTableRemoteEui64": 0x005C, - "setAddressTableRemoteNodeId": 0x005D, - "setBeaconClassificationParams": 0x00EF, - "setBinding": 0x002B, - "setBindingRemoteNodeId": 0x0030, - "setBrokenRouteErrorCode": 0x0011, - "setChildData": 0x00AC, - "setConcentrator": 0x0010, - "setConfigurationValue": 0x0053, - "setDutyCycleLimitsInStack": 0x0040, - "setExtendedTimeout": 0x007E, - "setHubConnectivity": 0x00E4, - "setInitialSecurityState": 0x0068, - "setKeyTableEntry": 0x0072, - "setLogicalAndRadioChannel": 0x00B9, - "setLongUpTime": 0x00E3, - "setMacPollFailureWaitTime": 0x00F4, - "setManufacturerCode": 0x0015, - "setMfgToken": 0x000C, - "setMulticastTableEntry": 0x0064, - "setNeighborFrameCounter": 0x00AD, - "setParentClassificationEnabled": 0x00E7, - "setPolicy": 0x0055, - "setPowerDescriptor": 0x0016, - "setPreinstalledCbkeData": 0x00A2, - "setPreinstalledCbkeData283k1": 0x00ED, - "setRadioChannel": 0x009A, - "setRadioIeee802154CcaMode": 0x0095, - "setRadioPower": 0x0099, - "setRoutingShortcutThreshold": 0x00D0, - "setSecurityKey": 0x00CA, - "setSecurityParameters": 0x00CB, - "setSourceRoute": 0x00AE, - "setSourceRouteDiscoveryMode": 0x005A, - "setTimer": 0x000E, - "setToken": 0x0009, - "setValue": 0x00AB, - "setZllAdditionalState": 0x00D6, - "setZllNodeType": 0x00D5, - "setZllPrimaryChannelMask": 0x00DB, - "setZllSecondaryChannelMask": 0x00DC, - "stackStatusHandler": 0x0019, - "stackTokenChangedHandler": 0x000D, - "startScan": 0x001A, - "stopScan": 0x001D, - "switchNetworkKeyHandler": 0x006E, - "timerHandler": 0x000F, - "trustCenterJoinHandler": 0x0024, - "unicastCurrentNetworkKey": 0x0050, - "unicastNwkKeyUpdate": 0x00A9, - "unusedPanIdFoundHandler": 0x00D2, - "updateTcLinkKey": 0x006C, - "version": 0x0000, - "writeNodeData": 0x00FE, - "zigbeeKeyEstablishmentHandler": 0x009B, - "zllAddressAssignmentHandler": 0x00B8, - "zllClearTokens": 0x0025, - "zllGetTokens": 0x00BC, - "zllNetworkFoundHandler": 0x00B6, - "zllNetworkOps": 0x00B2, - "zllOperationInProgress": 0x00D7, - "zllRxOnWhenIdleGetActive": 0x00D8, - "zllScanCompleteHandler": 0x00B7, - "zllSetDataToken": 0x00BD, - "zllSetInitialSecurityState": 0x00B3, - "zllSetNonZllNetwork": 0x00BF, - "zllSetRadioIdleMode": 0x00D4, - "zllSetRxOnWhenIdle": 0x00B5, - "zllSetSecurityStateWithoutKey": 0x00CF, - "zllStartScan": 0x00B4, - "zllTouchLinkTargetHandler": 0x00BB, -} + assert ezsp_f.setChildData.mock_calls == [ + call( + 0, + ezsp_f.types.EmberChildData( + eui64=t.EUI64.convert("00:0b:57:ff:fe:2b:d4:57"), + type=t.EmberNodeType.SLEEPY_END_DEVICE, + id=0xC06B, + phy=0, + power=0, + timeout=0, + ), + ), + call( + 1, + ezsp_f.types.EmberChildData( + eui64=t.EUI64.convert("00:18:4b:00:1c:a1:b8:46"), + type=t.EmberNodeType.SLEEPY_END_DEVICE, + id=0x1234, + phy=0, + power=0, + timeout=0, + ), + ), + ] diff --git a/tests/test_multicast.py b/tests/test_multicast.py index 0bd3c722..95566702 100644 --- a/tests/test_multicast.py +++ b/tests/test_multicast.py @@ -1,3 +1,5 @@ +from unittest.mock import AsyncMock, MagicMock, sentinel + import pytest from zigpy.endpoint import Endpoint @@ -5,15 +7,15 @@ import bellows.multicast import bellows.types as t -from .async_mock import AsyncMock, MagicMock, sentinel - CUSTOM_SIZE = 12 @pytest.fixture def ezsp_f(): e = MagicMock() - e.getConfigurationValue = AsyncMock(return_value=[0, CUSTOM_SIZE]) + e.getConfigurationValue = AsyncMock( + return_value=[t.EmberStatus.SUCCESS, CUSTOM_SIZE] + ) return e diff --git a/tests/test_uart.py b/tests/test_uart.py index 68ac8664..aadacdf8 100644 --- a/tests/test_uart.py +++ b/tests/test_uart.py @@ -1,5 +1,6 @@ import asyncio import threading +from unittest.mock import AsyncMock, MagicMock, call, patch, sentinel import pytest import serial_asyncio @@ -8,8 +9,6 @@ from bellows import uart import bellows.types as t -from .async_mock import AsyncMock, MagicMock, call, patch, sentinel - @pytest.mark.parametrize("flow_control", ["software", "hardware"]) async def test_connect(flow_control, monkeypatch): diff --git a/tests/test_util.py b/tests/test_util.py index f90e119c..c41e0105 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -1,15 +1,83 @@ +import importlib import logging import pytest import zigpy.state -import zigpy.types as t +from zigpy.types import PanId import zigpy.zdo.types as zdo_t -import bellows.types as bellows_t +import bellows.types as t import bellows.zigbee.util as util from tests.test_application import ezsp_mock, ieee -from tests.test_application_network_state import network_info, node_info + + +@pytest.fixture +def node_info(): + return zigpy.state.NodeInfo( + nwk=t.NWK(0x0000), + ieee=t.EUI64.convert("00:12:4b:00:1c:a1:b8:46"), + logical_type=zdo_t.LogicalType.Coordinator, + model="Mock board", + manufacturer="Mock Manufacturer", + version="Mock version", + ) + + +@pytest.fixture +def network_info(node_info): + return zigpy.state.NetworkInfo( + extended_pan_id=t.ExtendedPanId.convert("bd:27:0b:38:37:95:dc:87"), + pan_id=PanId(0x9BB0), + nwk_update_id=18, + nwk_manager_id=t.NWK(0x0000), + channel=t.uint8_t(15), + channel_mask=t.Channels.ALL_CHANNELS, + security_level=t.uint8_t(5), + network_key=zigpy.state.Key( + key=t.KeyData.convert("2ccade06b3090c310315b3d574d3c85a"), + seq=108, + tx_counter=118785, + ), + tc_link_key=zigpy.state.Key( + key=t.KeyData(b"ZigBeeAlliance09"), + partner_ieee=node_info.ieee, + tx_counter=8712428, + ), + key_table=[ + zigpy.state.Key( + key=t.KeyData.convert( + "85:7C:05:00:3E:76:1A:F9:68:9A:49:41:6A:60:5C:76" + ), + tx_counter=3792973670, + rx_counter=1083290572, + seq=147, + partner_ieee=t.EUI64.convert("CC:CC:CC:FF:FE:E6:8E:CA"), + ), + zigpy.state.Key( + key=t.KeyData.convert( + "CA:02:E8:BB:75:7C:94:F8:93:39:D3:9C:B3:CD:A7:BE" + ), + tx_counter=2597245184, + rx_counter=824424412, + seq=19, + partner_ieee=t.EUI64.convert("EC:1B:BD:FF:FE:2F:41:A4"), + ), + ], + children=[ + t.EUI64.convert("00:0B:57:FF:FE:2B:D4:57"), + ], + # If exposed by the stack, NWK addresses of other connected devices on the network + nwk_addresses={ + # Two routers + t.EUI64.convert("CC:CC:CC:FF:FE:E6:8E:CA"): t.NWK(0x44CB), + t.EUI64.convert("EC:1B:BD:FF:FE:2F:41:A4"): t.NWK(0x0702), + # Child device + t.EUI64.convert("00:0b:57:ff:fe:2b:d4:57"): t.NWK(0xC06B), + }, + stack_specific={"ezsp": {"hashed_tclk": "abcdabcdabcdabcdabcdabcdabcdabcd"}}, + source=f"bellows@{importlib.metadata.version('bellows')}", + ) @pytest.fixture @@ -34,7 +102,7 @@ def ezsp_key(ezsp_mock, network_info, node_info, zigpy_key): sequenceNumber=zigpy_key.seq, outgoingFrameCounter=zigpy_key.tx_counter, incomingFrameCounter=zigpy_key.rx_counter, - partnerEUI64=bellows_t.EUI64(node_info.ieee), + partnerEUI64=t.EUI64(node_info.ieee), ) @@ -42,17 +110,14 @@ def test_zha_security_normal(network_info, node_info): security = util.zha_security(network_info=network_info, use_hashed_tclk=True) assert security.preconfiguredTrustCenterEui64 == node_info.ieee - assert ( - bellows_t.EmberInitialSecurityBitmask.HAVE_TRUST_CENTER_EUI64 - in security.bitmask - ) + assert t.EmberInitialSecurityBitmask.HAVE_TRUST_CENTER_EUI64 in security.bitmask assert ( security.preconfiguredKey.serialize().hex() == network_info.stack_specific["ezsp"]["hashed_tclk"] ) assert ( - bellows_t.EmberInitialSecurityBitmask.TRUST_CENTER_USES_HASHED_LINK_KEY + t.EmberInitialSecurityBitmask.TRUST_CENTER_USES_HASHED_LINK_KEY in security.bitmask ) @@ -66,11 +131,8 @@ def test_zha_security_router_unknown_tclk_partner_ieee(network_info): ) # Not set, since we don't know it - assert security.preconfiguredTrustCenterEui64 == bellows_t.EUI64([0x00] * 8) - assert ( - bellows_t.EmberInitialSecurityBitmask.HAVE_TRUST_CENTER_EUI64 - not in security.bitmask - ) + assert security.preconfiguredTrustCenterEui64 == t.EUI64([0x00] * 8) + assert t.EmberInitialSecurityBitmask.HAVE_TRUST_CENTER_EUI64 not in security.bitmask def test_zha_security_hashed_nonstandard_tclk_warning(network_info, caplog):