Skip to content

Commit fd86554

Browse files
committed
Move network formation code into ZNP class
1 parent 2e2d1a1 commit fd86554

File tree

4 files changed

+181
-160
lines changed

4 files changed

+181
-160
lines changed

tests/application/test_connect.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -245,13 +245,13 @@ async def test_reconnect_lockup_pyserial(device, event_loop, make_application, m
245245

246246
did_load_info = asyncio.get_running_loop().create_future()
247247

248-
async def patched_load_network_info(*, old_load=app._load_network_info):
248+
async def patched_load_network_info(*, old_load=app.load_network_info):
249249
try:
250250
return await old_load()
251251
finally:
252252
did_load_info.set_result(True)
253253

254-
with swap_attribute(app, "_load_network_info", patched_load_network_info):
254+
with swap_attribute(app, "load_network_info", patched_load_network_info):
255255
# "Drop" the connection like PySerial
256256
app._znp._uart.connection_lost(exc=None)
257257

zigpy_znp/api.py

Lines changed: 160 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import async_timeout
1212
import zigpy.zdo.types as zdo_t
1313

14+
import zigpy_znp.const as const
1415
import zigpy_znp.types as t
1516
import zigpy_znp.config as conf
1617
import zigpy_znp.logger as log
@@ -27,8 +28,11 @@
2728
from zigpy_znp.types.nvids import ExNvIds, OsalNvIds
2829

2930
LOGGER = logging.getLogger(__name__)
30-
AFTER_CONNECT_DELAY = 1 # seconds
31-
STARTUP_DELAY = 1 # seconds
31+
32+
# All of these are in seconds
33+
AFTER_CONNECT_DELAY = 1
34+
STARTUP_DELAY = 1
35+
NETWORK_COMMISSIONING_TIMEOUT = 30
3236

3337

3438
class ZNP:
@@ -171,6 +175,160 @@ async def load_network_info(self, *, load_keys=False):
171175
self.network_info = network_info
172176
self.node_info = node_info
173177

178+
async def write_network_info(
179+
self,
180+
*,
181+
network_info: zigpy.state.NetworkInformation,
182+
node_info: zigpy.state.NodeInfo,
183+
) -> None:
184+
"""
185+
Writes network and node state to NVRAM.
186+
"""
187+
188+
from zigpy_znp.znp import security
189+
190+
# The default NIB structure is identical for all Z-Stack versions
191+
nib = const.DEFAULT_NIB.replace(
192+
SequenceNum=0,
193+
MaxChildren=21,
194+
MaxRouters=21,
195+
scanDuration=5,
196+
allocatedRouterAddresses=1,
197+
allocatedEndDeviceAddresses=1,
198+
nwkTotalTransmissions=1,
199+
nwkDevAddress=node_info.nwk,
200+
nwkPanId=network_info.pan_id,
201+
extendedPANID=network_info.extended_pan_id,
202+
nwkUpdateId=network_info.nwk_update_id,
203+
nwkLogicalChannel=network_info.channel,
204+
channelList=network_info.channel_mask,
205+
SecurityLevel=network_info.security_level,
206+
nwkManagerAddr=network_info.nwk_manager_id,
207+
nwkState=t.NwkState.NWK_ROUTER,
208+
nwkKeyLoaded=True,
209+
nwkCoordAddress=0x0000,
210+
)
211+
212+
key_info = t.NwkActiveKeyItems(
213+
Active=t.NwkKeyDesc(
214+
KeySeqNum=network_info.network_key.seq,
215+
Key=network_info.network_key.key,
216+
),
217+
FrameCounter=network_info.network_key.tx_counter,
218+
)
219+
220+
nvram = {
221+
OsalNvIds.STARTUP_OPTION: t.StartupOptions.NONE,
222+
OsalNvIds.PANID: t.uint16_t(network_info.pan_id),
223+
OsalNvIds.APS_USE_EXT_PANID: network_info.extended_pan_id,
224+
OsalNvIds.EXTENDED_PAN_ID: network_info.extended_pan_id,
225+
OsalNvIds.PRECFGKEY: key_info.Active,
226+
OsalNvIds.PRECFGKEYS_ENABLE: t.Bool(False), # Required by Z2M
227+
OsalNvIds.CHANLIST: network_info.channel_mask,
228+
OsalNvIds.NIB: nib,
229+
OsalNvIds.EXTADDR: node_info.ieee,
230+
OsalNvIds.LOGICAL_TYPE: t.DeviceLogicalType(node_info.logical_type),
231+
OsalNvIds.NWK_ACTIVE_KEY_INFO: key_info.Active,
232+
OsalNvIds.NWK_ALTERN_KEY_INFO: key_info.Active,
233+
}
234+
235+
if self.version == 1.2:
236+
nvram[OsalNvIds.TCLK_SEED] = const.DEFAULT_TC_LINK_KEY
237+
nvram[OsalNvIds.HAS_CONFIGURED_ZSTACK1] = const.ZSTACK_CONFIGURE_SUCCESS
238+
else:
239+
nvram[OsalNvIds.BDBNODEISONANETWORK] = t.Bool(True)
240+
nvram[OsalNvIds.HAS_CONFIGURED_ZSTACK3] = const.ZSTACK_CONFIGURE_SUCCESS
241+
242+
tclk_seed = None
243+
244+
if (
245+
network_info.stack_specific is not None
246+
and "tclk_seed" in network_info.stack_specific.get("zstack", {})
247+
):
248+
tclk_seed, _ = t.KeyData.deserialize(
249+
bytes.fromhex(network_info.stack_specific["zstack"]["tclk_seed"])
250+
)
251+
nvram[OsalNvIds.TCLK_SEED] = tclk_seed
252+
253+
try:
254+
# Some NVRAM entries are only created when a network is formed.
255+
# We cannot know in advance the compile-time sizes of the arrays so we have
256+
# to actually form a network when configuring a newly-flashed device.
257+
if self.version > 1.2:
258+
await self.nvram.osal_read(OsalNvIds.ADDRMGR, item_type=t.Bytes)
259+
await self.nvram.osal_read(
260+
OsalNvIds.APS_LINK_KEY_TABLE, item_type=t.Bytes
261+
)
262+
except KeyError:
263+
await self.reset()
264+
265+
await self.request(
266+
c.AppConfig.BDBSetChannel.Req(
267+
IsPrimary=True, Channel=t.Channels.ALL_CHANNELS
268+
),
269+
RspStatus=t.Status.SUCCESS,
270+
)
271+
await self.request(
272+
c.AppConfig.BDBSetChannel.Req(
273+
IsPrimary=False, Channel=t.Channels.NO_CHANNELS
274+
),
275+
RspStatus=t.Status.SUCCESS,
276+
)
277+
278+
started_as_coordinator = self.wait_for_response(
279+
c.ZDO.StateChangeInd.Callback(State=t.DeviceState.StartedAsCoordinator)
280+
)
281+
282+
commissioning_rsp = await self.request_callback_rsp(
283+
request=c.AppConfig.BDBStartCommissioning.Req(
284+
Mode=c.app_config.BDBCommissioningMode.NwkFormation
285+
),
286+
RspStatus=t.Status.SUCCESS,
287+
callback=c.AppConfig.BDBCommissioningNotification.Callback(
288+
partial=True,
289+
RemainingModes=c.app_config.BDBCommissioningMode.NONE,
290+
),
291+
timeout=NETWORK_COMMISSIONING_TIMEOUT,
292+
)
293+
294+
if commissioning_rsp.Status != c.app_config.BDBCommissioningStatus.Success:
295+
raise RuntimeError(f"Network formation failed: {commissioning_rsp}")
296+
297+
await started_as_coordinator
298+
await self.reset()
299+
300+
for key, value in nvram.items():
301+
await self.nvram.osal_write(key, value, create=True)
302+
303+
await security.write_tc_frame_counter(
304+
self,
305+
network_info.network_key.tx_counter,
306+
ext_pan_id=network_info.extended_pan_id,
307+
)
308+
309+
devices = {}
310+
311+
for neighbor in network_info.neighbor_table or []:
312+
devices[neighbor.ieee] = security.StoredDevice(
313+
nwk=neighbor.nwk,
314+
ieee=neighbor.ieee,
315+
is_child=True,
316+
)
317+
318+
for key in network_info.key_table or []:
319+
devices[key.partner_ieee] = devices[key.partner_ieee].replace(
320+
aps_link_key=key.key,
321+
tx_counter=key.tx_counter,
322+
rx_counter=key.rx_counter,
323+
)
324+
325+
await security.write_devices(
326+
znp=self,
327+
devices=list(devices.values()),
328+
tclk_seed=tclk_seed,
329+
counter_increment=0,
330+
)
331+
174332
async def reset(self) -> None:
175333
"""
176334
Performs a soft reset within Z-Stack.

zigpy_znp/tools/network_restore.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ async def restore_network(
8484
app = ControllerApplication(config)
8585

8686
await app.startup(force_form=True)
87-
await app.update_network_settings(network_info=network_info, node_info=node_info)
87+
await app.write_network_info(network_info=network_info, node_info=node_info)
8888

8989
await app.pre_shutdown()
9090

0 commit comments

Comments
 (0)