Skip to content

Commit

Permalink
[WIP] Implement new zigpy network state
Browse files Browse the repository at this point in the history
  • Loading branch information
puddly committed Dec 12, 2021
1 parent acf47f6 commit fcf0b49
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 45 deletions.
22 changes: 22 additions & 0 deletions bellows/types/named.py
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,9 @@ class EmberStatus(basic.enum8):
# An index was passed into the function that was larger than the valid
# range.
INDEX_OUT_OF_RANGE = 0xB1
# The passed key data is not valid. A key of all zeros or all F's are reserved
# values and cannot be used.
KEY_INVALID = 0xB2
# There are no empty entries left in the table.
TABLE_FULL = 0xB4
# The requested table entry has been erased and contains no valid data.
Expand Down Expand Up @@ -1182,3 +1185,22 @@ class EmberSignature283k1Data(basic.fixed_list(72, basic.uint8_t)):

class EmberMessageDigest(basic.fixed_list(16, basic.uint8_t)):
"""The calculated digest of a message"""


class EmberDistinguishedNodeId(basic.enum16):
"""A distinguished network ID that will never be assigned to any node"""

# This value is used when getting the remote node ID from the address or binding
# tables. It indicates that the address or binding table entry is currently in use
# and network address discovery is underway.
DISCOVERY_ACTIVE = 0xFFFC

# This value is used when getting the remote node ID from the address or binding
# tables. It indicates that the address or binding table entry is currently in use
# but the node ID corresponding to the EUI64 in the table is currently unknown.
UNKNOWN = 0xFFFD

# This value is used when setting or getting the remote node ID in the address table
# or getting the remote node ID from the binding table. It indicates that the
# address or binding table entry is not in use.
TABLE_ENTRY_UNUSED = 0xFFFF
15 changes: 0 additions & 15 deletions bellows/types/struct.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
import inspect
import typing

import zigpy.state as app_state

from . import basic, named

NoneType = type(None)
Expand Down Expand Up @@ -282,19 +280,6 @@ class EmberNetworkParameters(EzspStruct):
# method.
channels: named.Channels

@property
def zigpy_network_information(self) -> app_state.NetworkInformation:
"""Convert to NetworkInformation."""
r = app_state.NetworkInformation(
self.extendedPanId,
app_state.t.PanId(self.panId),
self.nwkUpdateId,
app_state.t.NWK(self.nwkManagerId),
self.radioChannel,
channel_mask=self.channels,
)
return r


class EmberZigbeeNetwork(EzspStruct):
# The parameters of a ZigBee network.
Expand Down
142 changes: 112 additions & 30 deletions bellows/zigbee/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
import zigpy.application
import zigpy.config
import zigpy.device
from zigpy.exceptions import NetworkNotFormed
from zigpy.quirks import CustomDevice, CustomEndpoint
import zigpy.state as app_state
from zigpy.types import Addressing, BroadcastAddress
from zigpy.types import Addressing, BroadcastAddress, KeyData
import zigpy.util
import zigpy.zdo.types as zdo_t

Expand Down Expand Up @@ -127,13 +127,10 @@ async def cleanup_tc_link_key(self, ieee: t.EmberEUI64) -> None:
status = await self._ezsp.eraseKeyTableEntry(index)
LOGGER.debug("Cleaned up TC link key for %s device: %s", ieee, status)

async def startup(self, auto_form=False):
"""Perform a complete application startup"""
async def connect(self):
self._ezsp = await bellows.ezsp.EZSP.initialize(self.config)
ezsp = self._ezsp

self._multicast = bellows.multicast.Multicast(ezsp)

status, count = await ezsp.getConfigurationValue(
ezsp.types.EzspConfigId.CONFIG_APS_UNICAST_MESSAGE_COUNT
)
Expand All @@ -150,35 +147,20 @@ async def startup(self, auto_form=False):
LOGGER.info("EZSP Radio board name: %s", brd_name)
LOGGER.info("EmberZNet version: %s", version)

async def start_network(self):
ezsp = self._ezsp

v = await ezsp.networkInit()
if v[0] != t.EmberStatus.SUCCESS:
if not auto_form:
raise ControllerError("Could not initialize network")
await self.form_network()
raise NetworkNotFormed()

status, node_type, nwk_params = await ezsp.getNetworkParameters()
assert status == t.EmberStatus.SUCCESS # TODO: Better check
if node_type != t.EmberNodeType.COORDINATOR:
if not auto_form:
raise ControllerError("Network not configured as coordinator")

LOGGER.info(
"Leaving current network as %s and forming new network", node_type.name
)
(status,) = await self._ezsp.leaveNetwork()
assert status == t.EmberStatus.NETWORK_DOWN
await self.form_network()
status, node_type, nwk_params = await ezsp.getNetworkParameters()
assert status == t.EmberStatus.SUCCESS
raise NetworkNotFormed("Network not configured as coordinator")

LOGGER.info("Node type: %s, Network parameters: %s", node_type, nwk_params)
await ezsp.update_policies(self.config)
(nwk,) = await ezsp.getNodeId()
(ieee,) = await ezsp.getEui64()
await self.load_network_info(load_devices=False)

node_info = app_state.NodeInfo(nwk, ieee, node_type.zdo_logical_type)
self.state.node_information = node_info
self.state.network_information = nwk_params.zigpy_network_information
for cnt_group in self.state.counters:
cnt_group.reset()

Expand All @@ -195,9 +177,109 @@ async def startup(self, auto_form=False):

await self.multicast.startup(self.get_device(self.ieee))

async def shutdown(self):
"""Shutdown and cleanup ControllerApplication."""
LOGGER.info("Shutting down ControllerApplication")
async def load_network_info(self, *, load_devices=False) -> None:
ezsp = self._ezsp

(status,) = await ezsp.networkInit()
LOGGER.debug("Network init status: %s", status)
assert status == t.EmberStatus.SUCCESS

status, node_type, nwk_params = await ezsp.getNetworkParameters()
assert status == t.EmberStatus.SUCCESS

node_info = self.state.node_info
(node_info.nwk,) = await ezsp.getNodeId()
(node_info.ieee,) = await ezsp.getEui64()
node_info.logical_type = node_type.zdo_logical_type

network_info = self.state.network_info

network_info.extended_pan_id = nwk_params.extendedPanId
network_info.pan_id = nwk_params.panId
network_info.nwk_update_id = nwk_params.nwkUpdateId
network_info.nwk_manager_id = nwk_params.nwkManagerId
network_info.channel = nwk_params.radioChannel
network_info.channel_mask = nwk_params.channels

(status, security_level) = await ezsp.getConfigurationValue(
ezsp.types.EzspConfigId.CONFIG_SECURITY_LEVEL
)
assert status == t.EmberStatus.SUCCESS
network_info.security_level = security_level

# Network key
(status, key) = await ezsp.getKey(ezsp.types.EmberKeyType.CURRENT_NETWORK_KEY)
assert status == t.EmberStatus.SUCCESS
network_info.network_key = bellows.zigbee.util.ezsp_key_struct_to_zigpy_key(
key, ezsp=ezsp
)

# Security state
(status, state) = await ezsp.getCurrentSecurityState()
assert status == t.EmberStatus.SUCCESS

# TCLK
(status, key) = await ezsp.getKey(ezsp.types.EmberKeyType.TRUST_CENTER_LINK_KEY)
assert status == t.EmberStatus.SUCCESS

network_info.tc_link_key = bellows.zigbee.util.ezsp_key_struct_to_zigpy_key(
key, ezsp=ezsp
)

if (
state.bitmask
& ezsp.types.EmberCurrentSecurityBitmask.TRUST_CENTER_USES_HASHED_LINK_KEY
):
network_info.stack_specific = {
"ezsp": {"hashed_tclk": network_info.tc_link_key.key.serialize().hex()}
}
network_info.tc_link_key.key = KeyData(b"ZigBeeAlliance09")

if not load_devices:
return

network_info.key_table = []

for idx in range(0, 192):
(status, key) = await ezsp.getKeyTableEntry(idx)

if status == t.EmberStatus.INDEX_OUT_OF_RANGE:
break
elif status == t.EmberStatus.TABLE_ENTRY_ERASED:
continue

assert status == t.EmberStatus.SUCCESS

network_info.key_table.append(
bellows.zigbee.util.ezsp_key_struct_to_zigpy_key(key, ezsp=ezsp)
)

network_info.children = []
network_info.nwk_addresses = {}

for idx in range(0, 255 + 1):
(status, nwk, eui64, node_type) = await ezsp.getChildData(idx)

if status == t.EmberStatus.NOT_JOINED:
continue

network_info.children.append(eui64)
network_info.nwk_addresses[eui64] = nwk

for idx in range(0, 255 + 1):
(nwk,) = await ezsp.getAddressTableRemoteNodeId(idx)
(eui64,) = await ezsp.getAddressTableRemoteEui64(idx)

# Ignore invalid NWK entries
if nwk in t.EmberDistinguishedNodeId.__members__.values():
continue

network_info.nwk_addresses[eui64] = nwk

async def write_network_info(*, network_info, node_info):
pass

async def disconnect(self):
self.controller_event.clear()
if self._watchdog_task and not self._watchdog_task.done():
LOGGER.debug("Cancelling watchdog")
Expand Down
20 changes: 20 additions & 0 deletions bellows/zigbee/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from typing import Any, Dict

import zigpy.config
import zigpy.state

import bellows.types as t

Expand Down Expand Up @@ -37,3 +38,22 @@ def zha_security(
t.EmberInitialSecurityBitmask.TRUST_CENTER_USES_HASHED_LINK_KEY
)
return isc


def ezsp_key_struct_to_zigpy_key(key, *, ezsp):
zigpy_key = zigpy.state.Key()
zigpy_key.key = key.key

if key.bitmask & ezsp.types.EmberKeyStructBitmask.KEY_HAS_SEQUENCE_NUMBER:
zigpy_key.seq = key.sequenceNumber

if key.bitmask & ezsp.types.EmberKeyStructBitmask.KEY_HAS_OUTGOING_FRAME_COUNTER:
zigpy_key.tx_counter = key.outgoingFrameCounter

if key.bitmask & ezsp.types.EmberKeyStructBitmask.KEY_HAS_INCOMING_FRAME_COUNTER:
zigpy_key.rx_counter = key.outgoingFrameCounter

if key.bitmask & ezsp.types.EmberKeyStructBitmask.KEY_HAS_PARTNER_EUI64:
zigpy_key.partner_ieee = key.partnerEUI64

return zigpy_key

0 comments on commit fcf0b49

Please sign in to comment.