Exemple #1
0
async def test_znp_auto_connect(mocker, event_loop, pingable_serial_port):
    AUTO_DETECTED_PORT = "/dev/ttyWorkingUSB1"

    uart_guess_port = mocker.patch(
        "zigpy_znp.uart.guess_port", return_value=AUTO_DETECTED_PORT
    )

    async def fixed_uart_connect(config, api):
        protocol = await connect(config, api)
        protocol.transport.serial.name = AUTO_DETECTED_PORT

        return protocol

    mock = mocker.patch("zigpy_znp.uart.connect", side_effect=fixed_uart_connect)

    api = ZNP(config_for_port_path("auto"))

    await api.connect()
    assert uart_guess_port.call_count == 1
    assert mock.call_count == 1

    api.close()
    await api.connect()

    # We should not detect the port again
    # The user might have multiple matching devices
    assert uart_guess_port.call_count == 1
    assert mock.call_count == 2
Exemple #2
0
async def test_api_close(znp, event_loop):
    closed_future = event_loop.create_future()

    znp_connection_lost = znp.connection_lost

    def intercepted_connection_lost(exc):
        closed_future.set_result(exc)
        return znp_connection_lost(exc)

    znp._reconnect_task = Mock()
    znp._reconnect_task.done = lambda: False
    znp.connection_lost = intercepted_connection_lost
    znp.close()

    # connection_lost with no exc indicates the port was closed
    assert (await closed_future) is None

    # Make sure our UART was actually closed
    assert znp._uart is None

    # ZNP.close should not throw any errors
    znp2 = ZNP(TEST_APP_CONFIG)
    znp2.close()
    znp2.close()

    znp.close()
    znp.close()
Exemple #3
0
async def test_znp_connect_without_test(mocker, event_loop,
                                        pingable_serial_port):
    api = ZNP(TEST_APP_CONFIG)
    api.request = mocker.Mock(wraps=api.request)

    await api.connect(test_port=False)

    # Nothing should have been sent
    assert api.request.call_count == 0
Exemple #4
0
def znp(mocker):
    api = ZNP(TEST_APP_CONFIG)
    transport = mocker.Mock()
    transport.close = lambda: api._uart.connection_lost(exc=None)

    api._uart = ZnpMtProtocol(api)
    api._uart.send = mocker.Mock(wraps=api._uart.send)
    api._uart.connection_made(transport)

    return api
Exemple #5
0
    async def probe(cls, device_config: conf.ConfigType) -> bool:
        znp = ZNP(conf.CONFIG_SCHEMA({conf.CONF_DEVICE: device_config}))
        LOGGER.debug("Probing %s", znp._port_path)

        try:
            await znp.connect()
            return True
        except Exception as e:
            LOGGER.warning("Failed to probe ZNP radio with config %s",
                           device_config,
                           exc_info=e)
            return False
        finally:
            znp.close()
Exemple #6
0
    async def probe(cls, device_config: conf.ConfigType) -> bool:
        new_schema = conf.CONFIG_SCHEMA({
            conf.CONF_DEVICE: device_config,
            conf.CONF_ZNP_CONFIG: {
                conf.CONF_AUTO_RECONNECT: False
            },
        })

        znp = ZNP(new_schema)
        LOGGER.debug("Probing %s", znp._port_path)

        try:
            await znp.connect()
            return True
        except Exception:
            return False
        finally:
            znp.close()
Exemple #7
0
def pingable_serial_port(mocker):
    port_name = "/dev/ttyWorkingUSB1"
    transport = mocker.Mock()
    protocol = None

    api = ZNP(config_for_port_path(port_name))
    api.set_application(mocker.Mock())
    api._app.startup = Mock(return_value=lambda: asyncio.sleep(0))

    def ping_responder(data):
        # XXX: this assumes that our UART will send packets perfectly framed
        if data == bytes.fromhex("FE  00  21 01  20"):
            protocol.data_received(bytes.fromhex("FE  02  61 01  00 01  63"))
        elif data == bytes.fromhex("FE  00  21 02  23"):
            protocol.data_received(b"\xFE\x0E\x61\x02\x02\x01\x02\x07\x01\xE1"
                                   b"\x3B\x34\x01\x00\xFF\xFF\xFF\xFF\x85")

    transport.write = mocker.Mock(side_effect=ping_responder)

    old_serial_connect = serial_asyncio.create_serial_connection

    def dummy_serial_conn(loop, protocol_factory, url, *args, **kwargs):
        # Only our virtual port is handled differently
        if url != port_name:
            return old_serial_connect(loop, protocol_factory, url, *args,
                                      **kwargs)

        fut = loop.create_future()
        assert url == port_name

        nonlocal protocol
        protocol = protocol_factory()
        protocol.connection_made(transport)

        fut.set_result((transport, protocol))

        return fut

    mocker.patch("serial_asyncio.create_serial_connection",
                 new=dummy_serial_conn)

    return port_name
Exemple #8
0
async def restore(radio_path, backup):
    znp = ZNP(CONFIG_SCHEMA({"device": {"path": radio_path}}))

    await znp.connect()

    for nwk_nvid, value in backup["nwk"].items():
        nvid = NwkNvIds[nwk_nvid]
        value = bytes.fromhex(value)

        # XXX: are any NVIDs not filled all the way?
        try:
            await znp.request(
                c.SYS.OSALNVItemInit.Req(Id=nvid,
                                         ItemLen=len(value),
                                         Value=value),
                RspStatus=t.Status.SUCCESS,
            )

            await znp.nvram_write(nvid, value)
        except InvalidCommandResponse:
            LOGGER.warning("Write failed for %s = %s", nvid, value)

    for osal_nvid, value in backup["osal"].items():
        nvid = OsalExNvIds[osal_nvid]
        value = bytes.fromhex(value)

        try:
            await znp.request(
                c.SYS.NVWrite.Req(SysId=1,
                                  ItemId=nvid,
                                  SubId=0,
                                  Offset=0,
                                  Value=value),
                RspStatus=t.Status.SUCCESS,
            )
        except InvalidCommandResponse:
            LOGGER.warning("Write failed for %s = %s", nvid, value)

    # Reset afterwards to have the new values take effect
    await znp.request_callback_rsp(
        request=c.SYS.ResetReq.Req(Type=t.ResetType.Soft),
        callback=c.SYS.ResetInd.Callback(partial=True),
    )
Exemple #9
0
async def read_firmware(radio_path: str) -> bytearray:
    znp = ZNP(CONFIG_SCHEMA({"device": {"path": radio_path}}))

    # The bootloader handshake must be the very first command
    await znp.connect(test_port=False)

    try:
        async with async_timeout.timeout(5):
            handshake_rsp = await znp.request_callback_rsp(
                request=c.UBL.HandshakeReq.Req(),
                callback=c.UBL.HandshakeRsp.Callback(partial=True),
            )
    except asyncio.TimeoutError:
        raise RuntimeError(
            "Did not receive a bootloader handshake response!"
            " Make sure your adapter has just been plugged in and"
            " nothing else has had a chance to communicate with it.")

    if handshake_rsp.Status != c.ubl.BootloaderStatus.SUCCESS:
        raise RuntimeError(
            f"Bad bootloader handshake response: {handshake_rsp}")

    # All reads and writes are this size
    buffer_size = handshake_rsp.BufferSize

    data = bytearray()

    for offset in range(0, c.ubl.IMAGE_SIZE, buffer_size):
        address = offset // c.ubl.FLASH_WORD_SIZE
        LOGGER.info("Progress: %0.2f%%", (100.0 * offset) / c.ubl.IMAGE_SIZE)

        read_rsp = await znp.request_callback_rsp(
            request=c.UBL.ReadReq.Req(FlashWordAddr=address),
            callback=c.UBL.ReadRsp.Callback(partial=True),
        )

        assert read_rsp.Status == c.ubl.BootloaderStatus.SUCCESS
        assert read_rsp.FlashWordAddr == address
        assert len(read_rsp.Data) == buffer_size

        data.extend(read_rsp.Data)

    return data
Exemple #10
0
async def backup(radio_path):
    znp = ZNP(CONFIG_SCHEMA({"device": {"path": radio_path}}))

    await znp.connect()

    data = {
        "osal": {},
        "nwk": {},
    }

    for nwk_nvid in NwkNvIds:
        try:
            value = await znp.nvram_read(nwk_nvid)
            LOGGER.info("%s = %s", nwk_nvid, value)

            data["nwk"][nwk_nvid.name] = value.hex()
        except InvalidCommandResponse:
            LOGGER.warning("Read failed for %s", nwk_nvid)
            continue

    for osal_nvid in OsalExNvIds:
        length_rsp = await znp.request(
            c.SYS.NVLength.Req(SysId=1, ItemId=osal_nvid, SubId=0))
        length = length_rsp.Length

        if length == 0:
            LOGGER.warning("Read failed for %s", osal_nvid)
            continue

        value = (await znp.request(
            c.SYS.NVRead.Req(SysId=1,
                             ItemId=osal_nvid,
                             SubId=0,
                             Offset=0,
                             Length=length),
            RspStatus=t.Status.SUCCESS,
        )).Value
        LOGGER.info("%s = %s", osal_nvid, value)

        data["osal"][osal_nvid.name] = value.hex()

    return data
Exemple #11
0
async def test_request_callback_rsp(pingable_serial_port, event_loop):
    api = ZNP(TEST_APP_CONFIG)
    await api.connect()

    def send_responses():
        api._uart.data_received(
            TransportFrame(
                c.AFCommands.DataRequest.Rsp(Status=t.Status.Success).to_frame()
            ).serialize()
            + TransportFrame(
                c.AFCommands.DataConfirm.Callback(
                    Endpoint=56, TSN=1, Status=t.Status.Success
                ).to_frame()
            ).serialize()
        )

    event_loop.call_later(0.1, send_responses)

    # The UART sometimes replies with a SRSP and an AREQ faster than
    # we can register callbacks for both. This method is a workaround.
    response = await api.request_callback_rsp(
        request=c.AFCommands.DataRequest.Req(
            DstAddr=0x1234,
            DstEndpoint=56,
            SrcEndpoint=78,
            ClusterId=90,
            TSN=1,
            Options=c.af.TransmitOptions.RouteDiscovery,
            Radius=30,
            Data=b"hello",
        ),
        RspStatus=t.Status.Success,
        callback=c.AFCommands.DataConfirm.Callback(partial=True, Endpoint=56, TSN=1),
    )

    # Our response is the callback, not the confirmation response
    assert response == c.AFCommands.DataConfirm.Callback(
        Endpoint=56, TSN=1, Status=t.Status.Success
    )
Exemple #12
0
async def test_znp_connect(mocker, event_loop, pingable_serial_port):
    api = ZNP(TEST_APP_CONFIG)
    await api.connect()
Exemple #13
0
async def test_api_reconnect(event_loop, mocker):
    SREQ_TIMEOUT = 0.2
    port_path = "/dev/ttyUSB1"

    config = conf.CONFIG_SCHEMA(
        {
            conf.CONF_DEVICE: {conf.CONF_DEVICE_PATH: port_path},
            conf.CONF_ZNP_CONFIG: {
                conf.CONF_SREQ_TIMEOUT: SREQ_TIMEOUT,
                conf.CONF_AUTO_RECONNECT_RETRY_DELAY: 0.01,
            },
        }
    )

    transport = mocker.Mock()

    def dummy_serial_conn(loop, protocol_factory, url, *args, **kwargs):
        fut = loop.create_future()
        assert url == port_path

        protocol = protocol_factory()
        protocol.connection_made(transport)

        fut.set_result((transport, protocol))

        return fut

    mocker.patch("serial_asyncio.create_serial_connection", new=dummy_serial_conn)
    mocker.patch("zigpy_znp.uart.connect", wraps=zigpy_znp.uart.connect)

    app = mocker.Mock()
    app.startup = Mock(return_value=asyncio.sleep(0))

    api = ZNP(config)
    api.set_application(app)

    connect_fut = event_loop.create_future()
    connect_task = asyncio.create_task(api.connect())
    connect_task.add_done_callback(lambda _: connect_fut.set_result(None))

    while transport.write.call_count < 1:
        await asyncio.sleep(0.01)  # XXX: not ideal

    # We should have receiving a ping
    transport.write.assert_called_once_with(bytes.fromhex("FE  00  21 01  20"))

    # Send a ping response
    api._uart.data_received(bytes.fromhex("FE  02  61 01  00 01  63"))

    # Wait to connect
    await connect_fut

    assert api._port_path == port_path

    transport.reset_mock()

    # Now that we're connected, close the connection due to an error
    assert transport.write.call_count == 0
    api.connection_lost(RuntimeError("Uh oh"))

    # We should get another ping request soon
    while transport.write.call_count != 1:
        await asyncio.sleep(0.01)  # XXX: not ideal

    transport.write.assert_called_once_with(bytes.fromhex("FE  00  21 01  20"))

    # Reply incorrectly to the ping request
    api._uart.data_received(b"bad response")

    # We should still have the old connection info
    assert api._port_path == port_path

    # Wait for the SREQ_TIMEOUT to pass, we should fail to reconnect
    await asyncio.sleep(SREQ_TIMEOUT + 0.1)

    transport.reset_mock()

    # We wait a bit again for another ping
    while transport.write.call_count != 1:
        await asyncio.sleep(0.01)  # XXX: not ideal

    transport.write.assert_called_once_with(bytes.fromhex("FE  00  21 01  20"))

    # Our reconnect task should complete after we send the ping reply
    reconnect_fut = event_loop.create_future()
    api._reconnect_task.add_done_callback(lambda _: reconnect_fut.set_result(None))

    # App re-startup should not have happened, we've never reconnected before
    assert api._app.startup.call_count == 0
    api._uart.data_received(bytes.fromhex("FE  02  61 01  00 01  63"))

    # We should be reconnected soon and the app should have been restarted
    await reconnect_fut
    assert api._app.startup.call_count == 1
Exemple #14
0
    async def startup(self, auto_form=False):
        """Perform a complete application startup"""

        self._znp = ZNP(self.config)
        self._bind_callbacks(self._znp)
        await self._znp.connect()

        await self._reset(t.ResetType.Soft)

        if auto_form and False:
            # XXX: actually form a network
            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.SysCommands.SetTxPower.Req(TXPower=dbm),
                                    RspStatus=t.Status.Success)
        """
        # Get our active endpoints
        endpoints = await self._znp.request_callback_rsp(
            request=c.ZDOCommands.ActiveEpReq.Req(
                DstAddr=0x0000, NWKAddrOfInterest=0x0000
            ),
            RspStatus=t.Status.Success,
            callback=c.ZDOCommands.ActiveEpRsp.Callback(partial=True),
        )

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

        # Register our endpoints
        await self._znp.request(
            c.AFCommands.Register.Req(
                Endpoint=1,
                ProfileId=zigpy.profiles.zha.PROFILE_ID,
                DeviceId=zigpy.profiles.zha.DeviceType.CONFIGURATION_TOOL,
                DeviceVersion=0x00,
                LatencyReq=c.af.LatencyReq.NoLatencyReqs,
                InputClusters=[],
                OutputClusters=[],
            ),
            RspStatus=t.Status.Success,
        )
        await self._znp.request(
            c.AFCommands.Register.Req(
                Endpoint=8,
                ProfileId=zigpy.profiles.zha.PROFILE_ID,
                DeviceId=zigpy.profiles.zha.DeviceType.IAS_CONTROL,
                DeviceVersion=0x00,
                LatencyReq=c.af.LatencyReq.NoLatencyReqs,
                InputClusters=[],
                OutputClusters=[IasZone.cluster_id],
            ),
            RspStatus=t.Status.Success,
        )
        await self._znp.request(
            c.AFCommands.Register.Req(
                Endpoint=11,
                ProfileId=zigpy.profiles.zha.PROFILE_ID,
                DeviceId=zigpy.profiles.zha.DeviceType.CONFIGURATION_TOOL,
                DeviceVersion=0x00,
                LatencyReq=c.af.LatencyReq.NoLatencyReqs,
                InputClusters=[],
                OutputClusters=[],
            ),
            RspStatus=t.Status.Success,
        )
        await self._znp.request(
            c.AFCommands.Register.Req(
                Endpoint=12,
                ProfileId=zigpy.profiles.zha.PROFILE_ID,
                DeviceId=zigpy.profiles.zha.DeviceType.CONFIGURATION_TOOL,
                DeviceVersion=0x00,
                LatencyReq=c.af.LatencyReq.NoLatencyReqs,
                InputClusters=[],
                OutputClusters=[],
            ),
            RspStatus=t.Status.Success,
        )
        await self._znp.request(
            c.AFCommands.Register.Req(
                Endpoint=100,
                ProfileId=zigpy.profiles.zll.PROFILE_ID,
                DeviceId=0x0005,
                DeviceVersion=0x00,
                LatencyReq=c.af.LatencyReq.NoLatencyReqs,
                InputClusters=[],
                OutputClusters=[],
            ),
            RspStatus=t.Status.Success,
        )

        # Start commissioning and wait until it's done
        comm_notification = await self._znp.request_callback_rsp(
            request=c.APPConfigCommands.BDBStartCommissioning.Req(
                Mode=c.app_config.BDBCommissioningMode.NetworkFormation),
            RspStatus=t.Status.Success,
            callback=c.APPConfigCommands.BDBCommissioningNotification.Callback(
                partial=True,
                RemainingModes=c.app_config.BDBRemainingCommissioningModes.
                NONE,
            ),
        )

        # XXX: Commissioning fails for me yet I experience no issues
        if comm_notification.Status != c.app_config.BDBCommissioningStatus.Success:
            LOGGER.warning("BDB commissioning did not succeed: %s",
                           comm_notification.Status)
Exemple #15
0
async def write_firmware(firmware: bytes, radio_path: str):
    if len(firmware) != c.ubl.IMAGE_SIZE:
        raise ValueError(f"Firmware is the wrong size."
                         f" Expected {c.ubl.IMAGE_SIZE}, got {len(firmware)}")

    znp = ZNP(CONFIG_SCHEMA({"device": {"path": radio_path}}))

    # The bootloader handshake must be the very first command
    await znp.connect(test_port=False)

    try:
        async with async_timeout.timeout(5):
            handshake_rsp = await znp.request_callback_rsp(
                request=c.UBL.HandshakeReq.Req(),
                callback=c.UBL.HandshakeRsp.Callback(partial=True),
            )
    except asyncio.TimeoutError:
        raise RuntimeError(
            "Did not receive a bootloader handshake response!"
            " Make sure your adapter has just been plugged in and"
            " nothing else has had a chance to communicate with it.")

    if handshake_rsp.Status != c.ubl.BootloaderStatus.SUCCESS:
        raise RuntimeError(
            f"Bad bootloader handshake response: {handshake_rsp}")

    # All reads and writes are this size
    buffer_size = handshake_rsp.BufferSize

    for offset in range(0, c.ubl.IMAGE_SIZE, buffer_size):
        address = offset // c.ubl.FLASH_WORD_SIZE
        LOGGER.info("Write progress: %0.2f%%",
                    (100.0 * offset) / c.ubl.IMAGE_SIZE)

        write_rsp = await znp.request_callback_rsp(
            request=c.UBL.WriteReq.Req(
                FlashWordAddr=address,
                Data=t.TrailingBytes(firmware[offset:offset + buffer_size]),
            ),
            callback=c.UBL.WriteRsp.Callback(partial=True),
        )

        assert write_rsp.Status == c.ubl.BootloaderStatus.SUCCESS

    # Now we have to read it all back
    # TODO: figure out how the CRC is computed!
    for offset in range(0, c.ubl.IMAGE_SIZE, buffer_size):
        address = offset // c.ubl.FLASH_WORD_SIZE
        LOGGER.info("Verification progress: %0.2f%%",
                    (100.0 * offset) / c.ubl.IMAGE_SIZE)

        read_rsp = await znp.request_callback_rsp(
            request=c.UBL.ReadReq.Req(FlashWordAddr=address, ),
            callback=c.UBL.ReadRsp.Callback(partial=True),
        )

        assert read_rsp.Status == c.ubl.BootloaderStatus.SUCCESS
        assert read_rsp.FlashWordAddr == address
        assert read_rsp.Data == firmware[offset:offset + buffer_size]

    # This seems to cause the firmware to compute and verify the CRC
    enable_rsp = await znp.request_callback_rsp(
        request=c.UBL.EnableReq.Req(),
        callback=c.UBL.EnableRsp.Callback(partial=True),
    )

    assert enable_rsp.Status == c.ubl.BootloaderStatus.SUCCESS
Exemple #16
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]