|
11 | 11 | import async_timeout
|
12 | 12 | import zigpy.zdo.types as zdo_t
|
13 | 13 |
|
| 14 | +import zigpy_znp.const as const |
14 | 15 | import zigpy_znp.types as t
|
15 | 16 | import zigpy_znp.config as conf
|
16 | 17 | import zigpy_znp.logger as log
|
|
27 | 28 | from zigpy_znp.types.nvids import ExNvIds, OsalNvIds
|
28 | 29 |
|
29 | 30 | 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 |
32 | 36 |
|
33 | 37 |
|
34 | 38 | class ZNP:
|
@@ -171,6 +175,160 @@ async def load_network_info(self, *, load_keys=False):
|
171 | 175 | self.network_info = network_info
|
172 | 176 | self.node_info = node_info
|
173 | 177 |
|
| 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 | + |
174 | 332 | async def reset(self) -> None:
|
175 | 333 | """
|
176 | 334 | Performs a soft reset within Z-Stack.
|
|
0 commit comments