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))
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
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
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
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
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
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