Beispiel #1
0
    async def startup(self, auto_form=False):
        """Perform a complete application startup"""
        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)
        assert status == t.EmberStatus.SUCCESS
        self._in_flight_msg = asyncio.Semaphore(count)
        LOGGER.debug("APS_UNICAST_MESSAGE_COUNT is set to %s", count)

        await self.add_endpoint(
            output_clusters=[zigpy.zcl.clusters.security.IasZone.cluster_id])

        brd_manuf, brd_name, version = await self._ezsp.get_board_info()
        LOGGER.info("EZSP Radio manufacturer: %s", brd_manuf)
        LOGGER.info("EZSP Radio board name: %s", brd_name)
        LOGGER.info("EmberZNet version: %s", version)

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

        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

        LOGGER.info("Node type: %s, Network parameters: %s", node_type,
                    nwk_params)

        await ezsp.update_policies(self.config)
        nwk = await ezsp.getNodeId()
        self._nwk = nwk[0]
        ieee = await ezsp.getEui64()
        self._ieee = ieee[0]

        ezsp.add_callback(self.ezsp_callback_handler)
        self.controller_event.set()
        self._watchdog_task = asyncio.create_task(self._watchdog())

        self.handle_join(self.nwk, self.ieee, 0)
        LOGGER.debug("EZSP nwk=0x%04x, IEEE=%s", self._nwk, str(self._ieee))

        await self.multicast.startup(self.get_device(self.ieee))
Beispiel #2
0
    async def broadcast(self, profile, cluster, src_ep, dst_ep, grpid, radius,
                        sequence, data,
                        broadcast_address=BroadcastAddress.RX_ON_WHEN_IDLE):
        if not self.is_controller_running:
            raise ControllerError("ApplicationController is not running")

        aps_frame = t.EmberApsFrame()
        aps_frame.profileId = t.uint16_t(profile)
        aps_frame.clusterId = t.uint16_t(cluster)
        aps_frame.sourceEndpoint = t.uint8_t(src_ep)
        aps_frame.destinationEndpoint = t.uint8_t(dst_ep)
        aps_frame.options = t.EmberApsOption(
            t.EmberApsOption.APS_OPTION_NONE
        )
        aps_frame.groupId = t.uint16_t(grpid)
        aps_frame.sequence = t.uint8_t(sequence)

        with self._pending.new(sequence) as req:
            async with self._in_flight_msg:
                res = await self._ezsp.sendBroadcast(broadcast_address,
                                                     aps_frame, radius,
                                                     sequence, data)
                if res[0] != t.EmberStatus.SUCCESS:
                    hdr, hdr_args = self._dst_pp(broadcast_address, aps_frame)
                    msg = hdr + "Broadcast failure: %s"
                    msg_args = (hdr_args + (res[0], ))
                    raise DeliveryError(msg % msg_args)

                # Wait for messageSentHandler message
                res = await asyncio.wait_for(req.send,
                                             timeout=APS_ACK_TIMEOUT)
        return res
Beispiel #3
0
    async def request(self, nwk, profile, cluster, src_ep, dst_ep, sequence, data, expect_reply=True,
                      timeout=APS_REPLY_TIMEOUT):
        if not self.is_controller_running:
            raise ControllerError("ApplicationController is not running")

        aps_frame = t.EmberApsFrame()
        aps_frame.profileId = t.uint16_t(profile)
        aps_frame.clusterId = t.uint16_t(cluster)
        aps_frame.sourceEndpoint = t.uint8_t(src_ep)
        aps_frame.destinationEndpoint = t.uint8_t(dst_ep)
        aps_frame.options = t.EmberApsOption(
            t.EmberApsOption.APS_OPTION_RETRY |
            t.EmberApsOption.APS_OPTION_ENABLE_ROUTE_DISCOVERY
        )
        aps_frame.groupId = t.uint16_t(0)
        aps_frame.sequence = t.uint8_t(sequence)

        with self._pending.new(sequence, expect_reply) as req:
            async with self._in_flight_msg:
                res = await self._ezsp.sendUnicast(self.direct, nwk, aps_frame,
                                                   sequence, data)
                if res[0] != t.EmberStatus.SUCCESS:
                    hdr, hdr_args = self._dst_pp(nwk, aps_frame)
                    msg = hdr + "message send failure: %s"
                    msg_args = (hdr_args + (res[0], ))
                    raise DeliveryError(msg % msg_args)

                res = await asyncio.wait_for(req.send, timeout=APS_ACK_TIMEOUT)

            if expect_reply:
                res = await asyncio.wait_for(req.reply, timeout)
        return res
Beispiel #4
0
    async def request(
        self,
        device,
        profile,
        cluster,
        src_ep,
        dst_ep,
        sequence,
        data,
        expect_reply=True,
        use_ieee=False,
    ):
        """Submit and send data out as an unicast transmission.

        :param device: destination device
        :param profile: Zigbee Profile ID to use for outgoing message
        :param cluster: cluster id where the message is being sent
        :param src_ep: source endpoint id
        :param dst_ep: destination endpoint id
        :param sequence: transaction sequence number of the message
        :param data: Zigbee message payload
        :param expect_reply: True if this is essentially a request
        :param use_ieee: use EUI64 for destination addressing
        :returns: return a tuple of a status and an error_message. Original requestor
                  has more context to provide a more meaningful error message
        """
        if not self.is_controller_running:
            raise ControllerError("ApplicationController is not running")

        aps_frame = t.EmberApsFrame()
        aps_frame.profileId = t.uint16_t(profile)
        aps_frame.clusterId = t.uint16_t(cluster)
        aps_frame.sourceEndpoint = t.uint8_t(src_ep)
        aps_frame.destinationEndpoint = t.uint8_t(dst_ep)
        aps_frame.options = t.EmberApsOption(
            t.EmberApsOption.APS_OPTION_RETRY
            | t.EmberApsOption.APS_OPTION_ENABLE_ROUTE_DISCOVERY)
        aps_frame.groupId = t.uint16_t(0)
        aps_frame.sequence = t.uint8_t(sequence)
        message_tag = self.get_sequence()

        if use_ieee:
            LOGGER.warning(("EUI64 addressing is not currently supported, "
                            "reverting to NWK"))
        if expect_reply and device.node_desc.is_end_device in (True, None):
            LOGGER.debug("Extending timeout for %s/0x%04x", device.ieee,
                         device.nwk)
            await self._ezsp.setExtendedTimeout(device.ieee, True)
        with self._pending.new(message_tag) as req:
            async with self._in_flight_msg:
                res = await self._ezsp.sendUnicast(self.direct, device.nwk,
                                                   aps_frame, message_tag,
                                                   data)
                if res[0] != t.EmberStatus.SUCCESS:
                    return res[0], "EZSP sendUnicast failure: %s" % (res[0], )

                res = await asyncio.wait_for(req.result, APS_ACK_TIMEOUT)
        return res
Beispiel #5
0
    async def broadcast(
        self,
        profile,
        cluster,
        src_ep,
        dst_ep,
        grpid,
        radius,
        sequence,
        data,
        broadcast_address=BroadcastAddress.RX_ON_WHEN_IDLE,
    ):
        """Submit and send data out as an unicast transmission.

        :param profile: Zigbee Profile ID to use for outgoing message
        :param cluster: cluster id where the message is being sent
        :param src_ep: source endpoint id
        :param dst_ep: destination endpoint id
        :param: grpid: group id to address the broadcast to
        :param radius: max radius of the broadcast
        :param sequence: transaction sequence number of the message
        :param data: zigbee message payload
        :param timeout: how long to wait for transmission ACK
        :param broadcast_address: broadcast address.
        :returns: return a tuple of a status and an error_message. Original requestor
                  has more context to provide a more meaningful error message
        """
        if not self.is_controller_running:
            raise ControllerError("ApplicationController is not running")

        aps_frame = t.EmberApsFrame()
        aps_frame.profileId = t.uint16_t(profile)
        aps_frame.clusterId = t.uint16_t(cluster)
        aps_frame.sourceEndpoint = t.uint8_t(src_ep)
        aps_frame.destinationEndpoint = t.uint8_t(dst_ep)
        aps_frame.options = t.EmberApsOption.APS_OPTION_NONE
        aps_frame.groupId = t.uint16_t(grpid)
        aps_frame.sequence = t.uint8_t(sequence)
        message_tag = self.get_sequence()

        with self._pending.new(message_tag) as req:
            async with self._in_flight_msg:
                async with self._req_lock:
                    res = await self._ezsp.sendBroadcast(
                        broadcast_address, aps_frame, radius, message_tag,
                        data)
                if res[0] != t.EmberStatus.SUCCESS:
                    return res[0], "broadcast send failure"

                # Wait for messageSentHandler message
                res = await asyncio.wait_for(req.result,
                                             timeout=APS_ACK_TIMEOUT)
        return res
Beispiel #6
0
    async def mrequest(
        self,
        group_id,
        profile,
        cluster,
        src_ep,
        sequence,
        data,
        *,
        hops=EZSP_DEFAULT_RADIUS,
        non_member_radius=EZSP_MULTICAST_NON_MEMBER_RADIUS
    ):
        """Submit and send data out as a multicast transmission.

        :param group_id: destination multicast address
        :param profile: Zigbee Profile ID to use for outgoing message
        :param cluster: cluster id where the message is being sent
        :param src_ep: source endpoint id
        :param sequence: transaction sequence number of the message
        :param data: Zigbee message payload
        :param hops: the message will be delivered to all nodes within this number of
                     hops of the sender. A value of zero is converted to MAX_HOPS
        :param non_member_radius: the number of hops that the message will be forwarded
                                  by devices that are not members of the group. A value
                                  of 7 or greater is treated as infinite
        :returns: return a tuple of a status and an error_message. Original requestor
                  has more context to provide a more meaningful error message
        """
        if not self.is_controller_running:
            raise ControllerError("ApplicationController is not running")

        aps_frame = t.EmberApsFrame()
        aps_frame.profileId = t.uint16_t(profile)
        aps_frame.clusterId = t.uint16_t(cluster)
        aps_frame.sourceEndpoint = t.uint8_t(src_ep)
        aps_frame.destinationEndpoint = t.uint8_t(src_ep)
        aps_frame.options = t.EmberApsOption(
            t.EmberApsOption.APS_OPTION_ENABLE_ROUTE_DISCOVERY
        )
        aps_frame.groupId = t.uint16_t(group_id)
        aps_frame.sequence = t.uint8_t(sequence)
        message_tag = self.get_sequence()

        with self._pending.new(message_tag) as req:
            async with self._in_flight_msg:
                res = await self._ezsp.sendMulticast(
                    aps_frame, hops, non_member_radius, message_tag, data
                )
                if res[0] != t.EmberStatus.SUCCESS:
                    return res[0], "EZSP sendMulticast failure: %s" % (res[0],)

                res = await asyncio.wait_for(req.result, APS_ACK_TIMEOUT)
        return res
Beispiel #7
0
    async def request(
        self,
        device,
        profile,
        cluster,
        src_ep,
        dst_ep,
        sequence,
        data,
        expect_reply=True,
        use_ieee=False,
    ):
        """Submit and send data out as an unicast transmission.

        :param device: destination device
        :param profile: Zigbee Profile ID to use for outgoing message
        :param cluster: cluster id where the message is being sent
        :param src_ep: source endpoint id
        :param dst_ep: destination endpoint id
        :param sequence: transaction sequence number of the message
        :param data: Zigbee message payload
        :param expect_reply: True if this is essentially a request
        :param use_ieee: use EUI64 for destination addressing
        :returns: return a tuple of a status and an error_message. Original requestor
                  has more context to provide a more meaningful error message
        """
        if not self.is_controller_running:
            raise ControllerError("ApplicationController is not running")

        aps_frame = t.EmberApsFrame()
        aps_frame.profileId = t.uint16_t(profile)
        aps_frame.clusterId = t.uint16_t(cluster)
        aps_frame.sourceEndpoint = t.uint8_t(src_ep)
        aps_frame.destinationEndpoint = t.uint8_t(dst_ep)
        aps_frame.options = self._tx_options
        aps_frame.groupId = t.uint16_t(0)
        aps_frame.sequence = t.uint8_t(sequence)
        message_tag = self.get_sequence()

        if use_ieee:
            LOGGER.warning(("EUI64 addressing is not currently supported, "
                            "reverting to NWK"))
        with self._pending.new(message_tag) as req:
            async with self._in_flight_msg:
                delays = [0.5, 1.0, 1.5]
                while True:
                    async with self._req_lock:
                        if expect_reply and device.node_desc.is_end_device in (
                                True,
                                None,
                        ):
                            LOGGER.debug(
                                "Extending timeout for %s/0x%04x",
                                device.ieee,
                                device.nwk,
                            )
                            await self._ezsp.setExtendedTimeout(
                                device.ieee, True)
                        if self.use_source_routing and self._ezsp.ezsp_version < 8:
                            (res, ) = await self._ezsp.set_source_route(device)
                            if res == t.EmberStatus.SUCCESS:
                                aps_frame.options ^= (
                                    t.EmberApsOption.
                                    APS_OPTION_ENABLE_ROUTE_DISCOVERY)
                                LOGGER.debug(
                                    "Set source route for %s to %s: %s",
                                    device.nwk,
                                    device.relays,
                                    res,
                                )
                            else:
                                LOGGER.debug(
                                    "using route discovery for %s device",
                                    device.nwk,
                                )

                        status, _ = await self._ezsp.sendUnicast(
                            self.direct, device.nwk, aps_frame, message_tag,
                            data)
                    if not (status == t.EmberStatus.MAX_MESSAGE_LIMIT_REACHED
                            and delays):
                        # retry only on MAX_MESSAGE_LIMIT_REACHED if tries are left
                        break

                    delay = delays.pop(0)
                    LOGGER.debug("retrying request %s tag in %ss", message_tag,
                                 delay)
                    await asyncio.sleep(delay)

                if status != t.EmberStatus.SUCCESS:
                    return status, f"EZSP sendUnicast failure: {str(status)}"

                res = await asyncio.wait_for(req.result, APS_ACK_TIMEOUT)
        return res