async def mqtt_publish(self, topic, data, qos, retain, ack_timeout=None): """ Sends a MQTT publish message and manages messages flows. This methods doesn't return until the message has been acknowledged by receiver or timeout occur :param topic: MQTT topic to publish :param data: data to send on topic :param qos: quality of service to use for message flow. Can be QOS_0, QOS_1 or QOS_2 :param retain: retain message flag :param ack_timeout: acknowledge timeout. If set, this method will return a TimeOut error if the acknowledgment is not completed before ack_timeout second :return: ApplicationMessage used during inflight operations """ if qos in (QOS_1, QOS_2): packet_id = self.session.next_packet_id if packet_id in self.session.inflight_out: raise HBMQTTException( "A message with the same packet ID '%d' is already in flight" % packet_id) else: packet_id = None message = OutgoingApplicationMessage(packet_id, topic, qos, data, retain) # Handle message flow if ack_timeout is not None and ack_timeout > 0: await asyncio.wait_for(self._handle_message_flow(message), ack_timeout, loop=self._loop) else: await self._handle_message_flow(message) return message
def __init__(self, fixed: MQTTFixedHeader = None): if fixed is None: header = MQTTFixedHeader(PINGRESP, 0x00) else: if fixed.packet_type is not PINGRESP: raise HBMQTTException( "Invalid fixed packet type %s for PingRespPacket init" % fixed.packet_type) header = fixed super().__init__(header) self.variable_header = None self.payload = None
def __init__(self, fixed: MQTTFixedHeader = None): if fixed is None: header = MQTTFixedHeader(DISCONNECT, 0x00) else: if fixed.packet_type is not DISCONNECT: raise HBMQTTException( "Invalid fixed packet type %s for DisconnectPacket init" % fixed.packet_type) header = fixed super().__init__(header) self.variable_header = None self.payload = None
def next_packet_id(self): self._packet_id += 1 if self._packet_id > 65535: self._packet_id = 1 while (self._packet_id in self.inflight_in or self._packet_id in self.inflight_out): self._packet_id += 1 if self._packet_id > 65535: raise HBMQTTException( "More than 65525 messages pending. No free packet ID") return self._packet_id
def __init__( self, fixed: MQTTFixedHeader = None, variable_header: PacketIdVariableHeader = None, ): if fixed is None: header = MQTTFixedHeader(PUBREL, 0x02) # [MQTT-3.6.1-1] else: if fixed.packet_type is not PUBREL: raise HBMQTTException( "Invalid fixed packet type %s for PubrelPacket init" % fixed.packet_type) header = fixed super().__init__(header) self.variable_header = variable_header self.payload = None
async def _handle_message_flow(self, app_message): """ Handle protocol flow for incoming and outgoing messages, depending on service level and according to MQTT spec. paragraph 4.3-Quality of Service levels and protocol flows :param app_message: PublishMessage to handle :return: nothing. """ if app_message.qos == QOS_0: await self._handle_qos0_message_flow(app_message) elif app_message.qos == QOS_1: await self._handle_qos1_message_flow(app_message) elif app_message.qos == QOS_2: await self._handle_qos2_message_flow(app_message) else: raise HBMQTTException("Unexcepted QOS value '%d" % str(app_message.qos))
def __init__( self, fixed: MQTTFixedHeader = None, vh: ConnectVariableHeader = None, payload: ConnectPayload = None, ): if fixed is None: header = MQTTFixedHeader(CONNECT, 0x00) else: if fixed.packet_type is not CONNECT: raise HBMQTTException( "Invalid fixed packet type %s for ConnectPacket init" % fixed.packet_type) header = fixed super().__init__(header) self.variable_header = vh self.payload = payload
def __init__( self, fixed: MQTTFixedHeader = None, variable_header: PacketIdVariableHeader = None, ): if fixed is None: header = MQTTFixedHeader(PUBCOMP, 0x00) else: if fixed.packet_type is not PUBCOMP: raise HBMQTTException( "Invalid fixed packet type %s for PubcompPacket init" % fixed.packet_type ) header = fixed super().__init__(header) self.variable_header = variable_header self.payload = None
def __init__( self, fixed: MQTTFixedHeader = None, variable_header: PacketIdVariableHeader = None, payload=None, ): if fixed is None: header = MQTTFixedHeader(SUBSCRIBE, 0x02) # [MQTT-3.8.1-1] else: if fixed.packet_type is not SUBSCRIBE: raise HBMQTTException( "Invalid fixed packet type %s for SubscribePacket init" % fixed.packet_type) header = fixed super().__init__(header) self.variable_header = variable_header self.payload = payload
async def _handle_qos1_message_flow(self, app_message): """ Handle QOS_1 application message acknowledgment For incoming messages, this method stores the message and reply with PUBACK For outgoing messages, this methods sends PUBLISH and waits for the corresponding PUBACK :param app_message: :return: """ assert app_message.qos == QOS_1 if app_message.puback_packet: raise HBMQTTException( "Message '%d' has already been acknowledged" % app_message.packet_id) if app_message.direction == OUTGOING: if app_message.packet_id not in self.session.inflight_out: # Store message in session self.session.inflight_out[app_message.packet_id] = app_message if app_message.publish_packet is not None: # A Publish packet has already been sent, this is a retry publish_packet = app_message.build_publish_packet(dup=True) else: publish_packet = app_message.build_publish_packet() # Send PUBLISH packet await self._send_packet(publish_packet) app_message.publish_packet = publish_packet # Wait for puback waiter = asyncio.Future(loop=self._loop) self._puback_waiters[app_message.packet_id] = waiter await waiter del self._puback_waiters[app_message.packet_id] app_message.puback_packet = waiter.result() # Discard inflight message del self.session.inflight_out[app_message.packet_id] elif app_message.direction == INCOMING: # Initiate delivery self.logger.debug("Add message to delivery") await self.session.delivered_message_queue.put(app_message) # Send PUBACK puback = PubackPacket.build(app_message.packet_id) await self._send_packet(puback) app_message.puback_packet = puback
def __init__( self, fixed: MQTTFixedHeader = None, variable_header: PacketIdVariableHeader = None, payload=None, ): if fixed is None: header = MQTTFixedHeader(UNSUBACK, 0x00) else: if fixed.packet_type is not UNSUBACK: raise HBMQTTException( "Invalid fixed packet type %s for UnsubackPacket init" % fixed.packet_type ) header = fixed super().__init__(header) self.variable_header = variable_header self.payload = payload
def packet_class(fixed_header: MQTTFixedHeader): try: cls = packet_dict[fixed_header.packet_type] return cls except KeyError: raise HBMQTTException("Unexpected packet Type '%s'" % fixed_header.packet_type)
async def _handle_qos2_message_flow(self, app_message): """ Handle QOS_2 application message acknowledgment For incoming messages, this method stores the message, sends PUBREC, waits for PUBREL, initiate delivery and send PUBCOMP For outgoing messages, this methods sends PUBLISH, waits for PUBREC, discards messages and wait for PUBCOMP :param app_message: :return: """ assert app_message.qos == QOS_2 if app_message.direction == OUTGOING: if app_message.pubrel_packet and app_message.pubcomp_packet: raise HBMQTTException( "Message '%d' has already been acknowledged" % app_message.packet_id) if not app_message.pubrel_packet: # Store message if app_message.publish_packet is not None: # This is a retry flow, no need to store just check the message exists in session if app_message.packet_id not in self.session.inflight_out: raise HBMQTTException( "Unknown inflight message '%d' in session" % app_message.packet_id) publish_packet = app_message.build_publish_packet(dup=True) else: # Store message in session self.session.inflight_out[ app_message.packet_id] = app_message publish_packet = app_message.build_publish_packet() # Send PUBLISH packet await self._send_packet(publish_packet) app_message.publish_packet = publish_packet # Wait PUBREC if app_message.packet_id in self._pubrec_waiters: # PUBREC waiter already exists for this packet ID message = ( "Can't add PUBREC waiter, a waiter already exists for message Id '%s'" % app_message.packet_id) self.logger.warning(message) raise HBMQTTException(message) waiter = asyncio.Future(loop=self._loop) self._pubrec_waiters[app_message.packet_id] = waiter await waiter del self._pubrec_waiters[app_message.packet_id] app_message.pubrec_packet = waiter.result() if not app_message.pubcomp_packet: # Send pubrel app_message.pubrel_packet = PubrelPacket.build( app_message.packet_id) await self._send_packet(app_message.pubrel_packet) # Wait for PUBCOMP waiter = asyncio.Future(loop=self._loop) self._pubcomp_waiters[app_message.packet_id] = waiter await waiter del self._pubcomp_waiters[app_message.packet_id] app_message.pubcomp_packet = waiter.result() # Discard inflight message del self.session.inflight_out[app_message.packet_id] elif app_message.direction == INCOMING: self.session.inflight_in[app_message.packet_id] = app_message # Send pubrec pubrec_packet = PubrecPacket.build(app_message.packet_id) await self._send_packet(pubrec_packet) app_message.pubrec_packet = pubrec_packet # Wait PUBREL if (app_message.packet_id in self._pubrel_waiters and not self._pubrel_waiters[app_message.packet_id].done()): # PUBREL waiter already exists for this packet ID message = ( "A waiter already exists for message Id '%s', canceling it" % app_message.packet_id) self.logger.warning(message) self._pubrel_waiters[app_message.packet_id].cancel() try: waiter = asyncio.Future(loop=self._loop) self._pubrel_waiters[app_message.packet_id] = waiter await waiter del self._pubrel_waiters[app_message.packet_id] app_message.pubrel_packet = waiter.result() # Initiate delivery and discard message await self.session.delivered_message_queue.put(app_message) del self.session.inflight_in[app_message.packet_id] # Send pubcomp pubcomp_packet = PubcompPacket.build(app_message.packet_id) await self._send_packet(pubcomp_packet) app_message.pubcomp_packet = pubcomp_packet except asyncio.CancelledError: self.logger.debug("Message flow cancelled")