Example #1
0
    async def form_network(self,
                           channels=[15],
                           pan_id=None,
                           extended_pan_id=None):
        # These options are read only on startup so we perform a soft reset right after
        await self._znp.nvram_write(NwkNvIds.STARTUP_OPTION,
                                    t.StartupOptions.ClearState)

        # XXX: the undocumented `znpBasicCfg` request can do this
        await self._znp.nvram_write(NwkNvIds.LOGICAL_TYPE,
                                    t.DeviceLogicalType.Coordinator)
        await self._reset()

        # If zgPreConfigKeys is set to TRUE, all devices should use the same
        # pre-configured security key. If zgPreConfigKeys is set to FALSE, the
        # pre-configured key is set only on the coordinator device, and is handed to
        # joining devices. The key is sent in the clear over the last hop. Upon reset,
        # the device will retrieve the pre-configured key from NV memory if the NV_INIT
        # compile option is defined (the NV item is called ZCD_NV_PRECFGKEY).

        await self.update_network(
            channel=None,
            channels=t.Channels.from_channel_list(channels),
            pan_id=0xFFFF if pan_id is None else pan_id,
            extended_pan_id=ExtendedPanId(
                os.urandom(8) if extended_pan_id is None else extended_pan_id),
            network_key=t.KeyData(os.urandom(16)),
            reset=False,
        )

        # We do not want to receive verbose ZDO callbacks
        # Just pass ZDO callbacks back to Zigpy
        await self._znp.nvram_write(NwkNvIds.ZDO_DIRECT_CB, t.Bool(True))

        await self._znp.request(
            c.APPConfigCommands.BDBStartCommissioning.Req(
                Mode=c.app_config.BDBCommissioningMode.NetworkFormation),
            RspStatus=t.Status.Success,
        )

        # This may take a while because of some sort of background scanning.
        # This can probably be disabled.
        await self._znp.wait_for_response(
            c.ZDOCommands.StateChangeInd.Callback(
                State=t.DeviceState.StartedAsCoordinator))

        await self._znp.request(
            c.APPConfigCommands.BDBStartCommissioning.Req(
                Mode=c.app_config.BDBCommissioningMode.NetworkSteering),
            RspStatus=t.Status.Success,
        )
Example #2
0
    async def form_network(self):
        # These options are read only on startup so we perform a soft reset right after
        await self._znp.nvram_write(NwkNvIds.STARTUP_OPTION,
                                    t.StartupOptions.ClearState)

        pan_id = self.config[conf.CONF_NWK][conf.CONF_NWK_PAN_ID]
        extended_pan_id = self.config[conf.CONF_NWK][
            conf.CONF_NWK_EXTENDED_PAN_ID]

        await self.update_network(
            channels=self.config[conf.CONF_NWK][conf.CONF_NWK_CHANNELS],
            pan_id=0xFFFF if pan_id is None else pan_id,
            extended_pan_id=ExtendedPanId(os.urandom(8))
            if extended_pan_id is None else extended_pan_id,
            network_key=t.KeyData(os.urandom(16)),
            reset=False,
        )

        # We want to receive all ZDO callbacks to proxy them back go zipgy
        await self._znp.nvram_write(NwkNvIds.ZDO_DIRECT_CB, t.Bool(True))

        # Reset now so that the changes take effect
        await self._reset()

        await self._znp.request(
            c.AppConfig.BDBStartCommissioning.Req(
                Mode=c.app_config.BDBCommissioningMode.NwkFormation),
            RspStatus=t.Status.SUCCESS,
        )

        # This may take a while because of some sort of background scanning.
        # This can probably be disabled.
        await self._znp.wait_for_response(
            c.ZDO.StateChangeInd.Callback(
                State=t.DeviceState.StartedAsCoordinator))

        # Create the NV item that keeps track of whether or not we're configured
        osal_create_rsp = await self._znp.request(
            c.SYS.OSALNVItemInit.Req(Id=NwkNvIds.HAS_CONFIGURED_ZSTACK3,
                                     ItemLen=1,
                                     Value=b"\x55"))

        if osal_create_rsp.Status not in (t.Status.SUCCESS,
                                          t.Status.NV_ITEM_UNINIT):
            raise RuntimeError(
                "Could not create HAS_CONFIGURED_ZSTACK3 NV item"
            )  # pragma: no cover

        # Initializing the item won't guarantee that it holds this exact value
        await self._znp.nvram_write(NwkNvIds.HAS_CONFIGURED_ZSTACK3, b"\x55")
Example #3
0
async def test_update_network(mocker, caplog, application):
    app, znp_server = application

    await app.startup(auto_form=False)
    mocker.patch.object(app, "_reset", new=CoroutineMock())

    channel = t.uint8_t(15)
    pan_id = t.PanId(0x1234)
    extended_pan_id = t.ExtendedPanId(range(8))
    channels = t.Channels.from_channel_list([11, 15, 20])
    network_key = t.KeyData(range(16))

    channels_updated = znp_server.reply_once_to(
        request=c.Util.SetChannels.Req(Channels=channels),
        responses=[c.Util.SetChannels.Rsp(Status=t.Status.SUCCESS)],
    )

    bdb_set_primary_channel = znp_server.reply_once_to(
        request=c.AppConfig.BDBSetChannel.Req(IsPrimary=True,
                                              Channel=channels),
        responses=[c.AppConfig.BDBSetChannel.Rsp(Status=t.Status.SUCCESS)],
    )

    bdb_set_secondary_channel = znp_server.reply_once_to(
        request=c.AppConfig.BDBSetChannel.Req(IsPrimary=False,
                                              Channel=t.Channels.NO_CHANNELS),
        responses=[c.AppConfig.BDBSetChannel.Rsp(Status=t.Status.SUCCESS)],
    )

    set_pan_id = znp_server.reply_once_to(
        request=c.Util.SetPanId.Req(PanId=pan_id),
        responses=[c.Util.SetPanId.Rsp(Status=t.Status.SUCCESS)],
    )

    set_extended_pan_id = znp_server.reply_once_to(
        request=c.SYS.OSALNVWrite.Req(Id=NwkNvIds.EXTENDED_PAN_ID,
                                      Offset=0,
                                      Value=extended_pan_id.serialize()),
        responses=[c.SYS.OSALNVWrite.Rsp(Status=t.Status.SUCCESS)],
    )

    set_network_key_util = znp_server.reply_once_to(
        request=c.Util.SetPreConfigKey.Req(PreConfigKey=network_key),
        responses=[c.Util.SetPreConfigKey.Rsp(Status=t.Status.SUCCESS)],
    )

    set_network_key_nvram = znp_server.reply_once_to(
        request=c.SYS.OSALNVWrite.Req(Id=NwkNvIds.PRECFGKEYS_ENABLE,
                                      Offset=0,
                                      Value=t.Bool(True).serialize()),
        responses=[c.SYS.OSALNVWrite.Rsp(Status=t.Status.SUCCESS)],
    )

    set_nib_nvram = znp_server.reply_once_to(
        request=c.SYS.OSALNVWrite.Req(Id=NwkNvIds.NIB, Offset=0, partial=True),
        responses=[c.SYS.OSALNVWrite.Rsp(Status=t.Status.SUCCESS)],
    )

    # But it does succeed with a warning if you explicitly allow it
    with caplog.at_level(logging.WARNING):
        await app.update_network(
            channel=channel,
            channels=channels,
            extended_pan_id=extended_pan_id,
            network_key=network_key,
            pan_id=pan_id,
            tc_address=t.EUI64(range(8)),
            tc_link_key=t.KeyData(range(8)),
            update_id=0,
            reset=True,
        )

    # We should receive a few warnings for `tc_` stuff
    assert len(caplog.records) >= 2

    await channels_updated
    await bdb_set_primary_channel
    await bdb_set_secondary_channel
    await set_pan_id
    await set_extended_pan_id
    await set_network_key_util
    await set_network_key_nvram
    await set_nib_nvram

    app._reset.assert_called_once_with()

    # Ensure we set everything we could
    assert app.nwk_update_id is None  # We can't use it
    assert app.channel == channel
    assert app.channels == channels
    assert app.pan_id == pan_id
    assert app.extended_pan_id == extended_pan_id
Example #4
0
async def test_auto_form_necessary(application, mocker):
    app, znp_server = application
    nvram = {}

    mocker.patch.object(app, "update_network", new=CoroutineMock())
    mocker.patch.object(app, "_reset", new=CoroutineMock())

    def nvram_writer(req):
        nvram[req.Id] = req.Value

        return c.SYS.OSALNVWrite.Rsp(Status=t.Status.SUCCESS)

    def nvram_init(req):
        nvram[req.Id] = req.Value

        return c.SYS.OSALNVItemInit.Rsp(Status=t.Status.SUCCESS)

    # Prevent the fixture's default NVRAM responses, except for the NIB
    listeners = znp_server._response_listeners[c.SYS.OSALNVRead.Req.header]
    znp_server._response_listeners[c.SYS.OSALNVRead.Req.header] = [
        listener for listener in listeners if listener.matching_commands[0] ==
        c.SYS.OSALNVRead.Req(Id=NwkNvIds.NIB, Offset=0)
    ]

    read_zstack_configured = znp_server.reply_once_to(
        request=c.SYS.OSALNVRead.Req(Id=NwkNvIds.HAS_CONFIGURED_ZSTACK3,
                                     Offset=0),
        responses=[
            c.SYS.OSALNVRead.Rsp(Status=t.Status.INVALID_PARAMETER, Value=b"")
        ],
    )

    znp_server.reply_to(request=c.SYS.OSALNVWrite.Req(Offset=0, partial=True),
                        responses=[nvram_writer])

    znp_server.reply_to(request=c.SYS.OSALNVItemInit.Req(partial=True),
                        responses=[nvram_init])

    znp_server.reply_to(
        request=c.AppConfig.BDBStartCommissioning.Req(
            Mode=c.app_config.BDBCommissioningMode.NwkFormation),
        responses=[
            c.AppConfig.BDBStartCommissioning.Rsp(Status=t.Status.SUCCESS),
            c.ZDO.StateChangeInd.Callback(
                State=t.DeviceState.StartedAsCoordinator),
        ],
    )

    znp_server.reply_to(
        request=c.AppConfig.BDBStartCommissioning.Req(
            Mode=c.app_config.BDBCommissioningMode.NwkSteering),
        responses=[
            c.AppConfig.BDBStartCommissioning.Rsp(Status=t.Status.SUCCESS)
        ],
    )

    await app.startup(auto_form=True)

    await read_zstack_configured

    assert app.update_network.call_count == 1
    assert app._reset.call_count == 2

    assert nvram[NwkNvIds.HAS_CONFIGURED_ZSTACK3] == b"\x55"
    assert nvram[
        NwkNvIds.STARTUP_OPTION] == t.StartupOptions.ClearState.serialize()
    assert nvram[
        NwkNvIds.LOGICAL_TYPE] == t.DeviceLogicalType.Coordinator.serialize()
    assert nvram[NwkNvIds.ZDO_DIRECT_CB] == t.Bool(True).serialize()
Example #5
0
    async def update_network(
        self,
        *,
        channel: typing.Optional[t.uint8_t] = None,
        channels: typing.Optional[t.Channels] = None,
        extended_pan_id: typing.Optional[t.ExtendedPanId] = None,
        network_key: typing.Optional[t.KeyData] = None,
        pan_id: typing.Optional[t.PanId] = None,
        tc_address: typing.Optional[t.EUI64] = None,
        tc_link_key: typing.Optional[t.KeyData] = None,
        update_id: int = 0,
        reset: bool = True,
    ):
        if (channel is not None and channels is not None
                and not t.Channels.from_channel_list([channel]) & channels):
            raise ValueError("Channel does not overlap with channel mask")

        if tc_link_key is not None:
            LOGGER.warning(
                "Trust center link key in config is not yet supported")

        if tc_address is not None:
            LOGGER.warning(
                "Trust center address in config is not yet supported")

        if channels is not None:
            await self._znp.request(
                c.Util.SetChannels.Req(Channels=channels),
                RspStatus=t.Status.SUCCESS,
            )
            await self._znp.request(
                c.AppConfig.BDBSetChannel.Req(IsPrimary=True,
                                              Channel=channels),
                RspStatus=t.Status.SUCCESS,
            )
            await self._znp.request(
                c.AppConfig.BDBSetChannel.Req(IsPrimary=False,
                                              Channel=t.Channels.NO_CHANNELS),
                RspStatus=t.Status.SUCCESS,
            )

            self._channels = channels

        if channel is not None:
            # We modify the logical channel value directly in the NIB
            nib = bytearray(await self._znp.nvram_read(NwkNvIds.NIB))
            nib[24] = channel
            await self._znp.nvram_write(NwkNvIds.NIB, nib)

            self._channel = channel

        if pan_id is not None:
            await self._znp.request(c.Util.SetPanId.Req(PanId=pan_id),
                                    RspStatus=t.Status.SUCCESS)

            self._pan_id = pan_id

        if extended_pan_id is not None:
            # There is no Util request to do this
            await self._znp.nvram_write(NwkNvIds.EXTENDED_PAN_ID,
                                        extended_pan_id)

            self._ext_pan_id = extended_pan_id

        if network_key is not None:
            await self._znp.request(
                c.Util.SetPreConfigKey.Req(PreConfigKey=network_key),
                RspStatus=t.Status.SUCCESS,
            )

            # XXX: The Util request does not actually write to this NV address
            await self._znp.nvram_write(NwkNvIds.PRECFGKEYS_ENABLE,
                                        t.Bool(True))

        if reset:
            # We have to reset afterwards
            await self._reset()
Example #6
0
    async def startup(self, auto_form=False):
        """Perform a complete application startup"""

        znp = ZNP(self.config)
        znp.set_application(self)
        self._bind_callbacks(znp)
        await znp.connect()

        self._znp = znp

        # XXX: To make sure we don't switch to the wrong device upon reconnect,
        #      update our config to point to the last-detected port.
        if self._config[conf.CONF_DEVICE][conf.CONF_DEVICE_PATH] == "auto":
            self._config[conf.CONF_DEVICE][
                conf.CONF_DEVICE_PATH] = self._znp._uart.transport.serial.name

        # It's better to configure these explicitly than rely on the NVRAM defaults
        await self._znp.nvram_write(NwkNvIds.CONCENTRATOR_ENABLE, t.Bool(True))
        await self._znp.nvram_write(NwkNvIds.CONCENTRATOR_DISCOVERY,
                                    t.uint8_t(120))
        await self._znp.nvram_write(NwkNvIds.CONCENTRATOR_RC, t.Bool(True))
        await self._znp.nvram_write(NwkNvIds.SRC_RTG_EXPIRY_TIME,
                                    t.uint8_t(255))
        await self._znp.nvram_write(NwkNvIds.NWK_CHILD_AGE_ENABLE,
                                    t.Bool(False))

        # XXX: the undocumented `znpBasicCfg` request can do this
        await self._znp.nvram_write(NwkNvIds.LOGICAL_TYPE,
                                    t.DeviceLogicalType.Coordinator)

        # Reset to make the above NVRAM writes take effect.
        # This also ensures any previously-started network joins don't continue.
        await self._reset()

        try:
            is_configured = (await self._znp.nvram_read(
                NwkNvIds.HAS_CONFIGURED_ZSTACK3)) == b"\x55"
        except InvalidCommandResponse as e:
            assert e.response.Status == t.Status.INVALID_PARAMETER
            is_configured = False

        if not is_configured and not auto_form:
            raise RuntimeError(
                "Cannot start application, network is not formed")
        elif auto_form and is_configured:
            LOGGER.info(
                "ZNP is already configured, no need to form a network.")
        elif auto_form and not is_configured:
            await self.form_network()

        if self.config[conf.CONF_ZNP_CONFIG][conf.CONF_TX_POWER] is not None:
            dbm = self.config[conf.CONF_ZNP_CONFIG][conf.CONF_TX_POWER]

            await self._znp.request(c.SYS.SetTxPower.Req(TXPower=dbm),
                                    RspStatus=t.Status.SUCCESS)

        device_info = await self._znp.request(c.Util.GetDeviceInfo.Req(),
                                              RspStatus=t.Status.SUCCESS)

        self._ieee = device_info.IEEE

        if device_info.DeviceState != t.DeviceState.StartedAsCoordinator:
            # Start the application and wait until it's ready
            await self._znp.request_callback_rsp(
                request=c.ZDO.StartupFromApp.Req(StartDelay=100),
                RspState=c.zdo.StartupState.RestoredNetworkState,
                callback=c.ZDO.StateChangeInd.Callback(
                    State=t.DeviceState.StartedAsCoordinator),
            )

        # Get our active endpoints
        endpoints = await self._znp.request_callback_rsp(
            request=c.ZDO.ActiveEpReq.Req(DstAddr=0x0000,
                                          NWKAddrOfInterest=0x0000),
            RspStatus=t.Status.SUCCESS,
            callback=c.ZDO.ActiveEpRsp.Callback(partial=True),
        )

        # Clear out the list of active endpoints
        for endpoint in endpoints.ActiveEndpoints:
            await self._znp.request(c.AF.Delete.Req(Endpoint=endpoint),
                                    RspStatus=t.Status.SUCCESS)

        # Register our endpoints
        await self._register_endpoint(endpoint=1)
        await self._register_endpoint(
            endpoint=8,
            device_id=zigpy.profiles.zha.DeviceType.IAS_CONTROL,
            output_clusters=[clusters.security.IasZone.cluster_id],
        )
        await self._register_endpoint(endpoint=11)
        await self._register_endpoint(endpoint=12)
        await self._register_endpoint(
            endpoint=13, input_clusters=[clusters.general.Ota.cluster_id])

        await self._register_endpoint(endpoint=100,
                                      profile_id=zigpy.profiles.zll.PROFILE_ID,
                                      device_id=0x0005)

        # Structure is in `zstack/stack/nwk/nwk.h`
        nib = await self._znp.nvram_read(NwkNvIds.NIB)
        self._channel = nib[24]
        self._channels = t.Channels.deserialize(nib[40:44])[0]