async def main(broker_host): print("creating client2") client = MQTTClient("client2") client.on_connect = on_connect client.on_message = on_message client.on_disconnect = on_disconnect client.on_subscribe = on_subscribe client.on_publish = on_publish await client.connect(broker_host) client.subscribe("org/responses/client2") await asyncio.sleep(5) client.publish('org/common',"client2 message",\ response_topic="org/responses/client2") await asyncio.sleep(50) #wait to receive message await client.disconnect()
class Mqtt(Connector): def __init__(self, config, asm=None): self.name = "mqtt" self.config = config self.asm = asm self.default_room = "MyDefaultRoom" self.client = None async def connect(self): self.client = MQTTClient(self.asm.name) self.client.on_message = on_message self.client.set_auth_credentials( os.getenv('MQTT_USER', "arcus"), os.getenv('MQTT_PASSWORD', "arcusarcus")) await self.client.connect(os.getenv('MQTT_HOST', "mqtt"), 1883, keepalive=60, version=MQTTv311) _LOGGER.info("Connected to MQTT") async def listen(self): self.client.subscribe(self.asm.name + "/#", qos=1) stop = asyncio.Event() await stop.wait() @register_event(Message) async def respond(self, message): self.client.publish(self.asm.name, 'Message payload', response_topic='RESPONSE/TOPIC') async def disconnect(self): # Disconnect from the service await self.client.disconnect()
async def main(broker_host): print("creating client") client = MQTTClient("client1") client.on_connect = on_connect client.on_message = on_message client.on_disconnect = on_disconnect client.on_subscribe = on_subscribe client.on_publish = on_publish await client.connect(broker_host) client.subscribe('org/common', no_local=True) await asyncio.sleep(5) print("Publish response topic") msg_out1 = "test message" client.publish('org/common', "aa", response_topic="org/responses/client1") await asyncio.sleep(50) #wait to receive message if len(messages) == 0: print("test failed") else: msg = messages.pop() if msg == msg_out1: print("test succeeded") await client.disconnect()
class MQTTCli: def __init__(self, loop: asyncio.AbstractEventLoop, device_registry): self.loop = loop self.device_registry = device_registry self.client = MQTTClient("client-id") self.client.on_connect = self.on_connect self.client.on_message = self.on_message self.client.on_disconnect = self.on_disconnect self.client.on_subscribe = self.on_subscribe async def connect(): user = "******" pw = "oochi8eengehuYohgeBu1foobooceeZ7to5ieng7pis8saephaetah0hoaphiK3F" broker_host = "192.168.50.95" self.client.set_auth_credentials(user, pw) await self.client.connect(broker_host) def on_connect(client, flags, rc, properties): print('Connected') self.client.subscribe('home-assistant/command', qos=0) def on_message(client, topic, payload, qos, properties): print('RECV MSG:', payload) device_registry.bluetooth_devices.send_message(payload, True, False) publish('home-assistant/response', payload, qos=1) def on_disconnect(client, packet, exc=None): print('Disconnected') def on_subscribe(client, mid, qos, properties): print('SUBSCRIBED') def ask_exit(*args): STOP.set()
class SmartPlug(object): def __init__(self, sensor_id=None, event_buffer=None, settings=None): print("SmartPlug init()", sensor_id) #debug will put these in settings self.broker_host = 'localhost' self.broker_port = 1883 self.sensor_id = sensor_id self.STOP = asyncio.Event() self.client = MQTTClient(self.sensor_id + "_node") self.client.on_connect = self.on_connect self.client.on_message = self.on_message self.client.on_disconnect = self.on_disconnect self.client.on_subscribe = self.on_subscribe async def begin(self): #client.set_auth_credentials(token, None) await self.client.connect(self.broker_host, self.broker_port, version=MQTTv311) async def finish(self): #await self.STOP.wait() await self.client.disconnect() def on_connect(self, client, flags, rc, properties): print('{} Connected to {}'.format( self.sensor_id, self.broker_host + ':' + str(self.broker_port))) #print("{} Publishing".format(self.sensor_id)) #self.client.publish('TEST/TIME', "{:.3f} {} {}".format(time.time(),self.sensor_id,'connected mqtt'), qos=1) subscribe_str = '{}/tele/SENSOR'.format(self.sensor_id) print("{} Subscribing to {}".format(self.sensor_id, subscribe_str)) self.client.subscribe(subscribe_str, qos=0) def on_message(self, client, topic, payload, qos, properties): self.handle_input(payload) def on_disconnect(self, client, packet, exc=None): print('{} Disconnected'.format(self.sensor_id)) def on_subscribe(self, client, mid, qos): print("{} Subscribed".format(self.sensor_id)) def ask_exit(self, *args): self.STOP.set() def handle_input(self, input): print("{} got input {}".format(self.sensor_id, input))
async def main(broker_host): client = MQTTClient(client_id) assign_callbacks_to_client(client) await client.connect(broker_host) print('Subscribe topic for the Last Will and Testament (LWT)...') client.subscribe(status_topic) print('Subscribe response topic...') client.subscribe(res_topic) print('Publish request message with response topic...') client.publish(teds_topic, req_msg, response_topic=res_topic) await STOP.wait() await client.disconnect()
class DIDCommTeeMQTTClient: def __init__(self, their_vk, endpoint): self.client = MQTTClient("client-id") self.client.on_connect = self.on_connect self.client.on_message = self.on_message self.client.on_disconnect = self.on_disconnect self.client.on_subscribe = self.on_subscribe self.did_conn = StaticConnection(crypto.create_keypair(), their_vk=their_vk, endpoint=endpoint) async def mqtt_connect(self, broker_host): await self.client.connect(broker_host) async def mqtt_disconnect(self): await self.client.disconnect() def on_connect(self, client, flags, rc, properties): print('Connected') async def on_message(self, client, topic, payload, qos, properties): await self.did_conn.send_async({ '@type': 'https://didcomm.dbluhm.com/mqtt/0.1/msg', 'topic': topic, 'payload': payload.decode('ascii'), 'properties': properties }) print('RECV MSG:', payload) def on_disconnect(self, client, packet, exc=None): print('Disconnected') def on_subscribe(self, client, mid, qos): print('SUBSCRIBED') def publish(self, *args, **kwargs): self.client.publish(*args, **kwargs) def subscribe(self, *args, **kwargs): self.client.subscribe(*args, **kwargs)
class MQTTClient(Client): ''' Client for subscribing to a broker and pipe incomming messages to configured producer. ''' def __init__( self, **kwargs: Any, ) -> None: super().__init__(**kwargs) self._inner_client = GMQTTClient(None) self._inner_client.on_connect = self.on_connect self._inner_client.on_message = self.on_message self._inner_client.on_disconnect = self.on_disconnect self._inner_client.on_subscribe = self.on_subscribe self._inner_client.pipe_message = self.pipe_message if not self.username is None: self._inner_client.set_auth_credentials(self.username, self.password) async def connect(self, topics: Tuple[str, int]) -> None: ''' Connects to broker. ''' LOG.debug('Connecting to MQTT broker.') try: await self._inner_client.connect(self.uri, 1883, keepalive=60, version=MQTTv311) subscriptions = [Subscription(t[0], qos=t[1]) for t in topics] self._inner_client.subscribe(subscriptions, subscription_identifier=1) await self.producer.connect() self.connected = True except: self.connected = False async def disconnect(self) -> None: await self._inner_client.disconnect() await self.producer.disconnect() self.connected = False
async def discover( broker: str, prefix_filter: str, discovery_timeout: float = 0.1, ) -> List[str]: """ Get a list of available Miniconf devices. Args: * `broker` - The broker to search for clients on. * `prefix_filter` - An MQTT-specific topic filter for device prefixes. Note that this will be appended to with the default status topic name `/alive`. * `discovery_timeout` - The duration to search for clients in seconds. Returns: A list of discovered client prefixes that match the provided filter. """ discovered_devices = [] suffix = '/alive' def handle_message(_client, topic, payload, _qos, _properties): logging.debug('Got message from %s: %s', topic, payload) if json.loads(payload): discovered_devices.append(topic[:-len(suffix)]) client = MqttClient(client_id='') client.on_message = handle_message await client.connect(broker) client.subscribe(f'{prefix_filter}{suffix}') await asyncio.sleep(discovery_timeout) return discovered_devices
async def main(broker_host): print("creating server") client = MQTTClient("server") client.on_connect = on_connect client.on_message = on_message client.on_disconnect = on_disconnect client.on_subscribe = on_subscribe client.on_publish = on_publish await client.connect(broker_host) client.subscribe('org/common',no_local=True) await asyncio.sleep(5) print("subscribed to common topic") await asyncio.sleep(500) #wait to receive message await client.disconnect()
class GmqttClient(Client): counter = 0 def __init__(self, config: BrokerConfig): self.config: BrokerConfig = config self.client = None self.thread = None self.all_listeners: Dict[str, Subscriber] = {} self.active_listeners: Set = set() self.loop = None self.ready = threading.Event() @property def is_connected(self): return self.client.is_connected def publish(self, topic: str, payload): # At most once (0) # At least once (1) # Exactly once (2). self.ready.wait() assert self.client, 'you need to activate connection' self.client.publish(topic, payload, qos=0, message_expiry_interval=10) def subscribe(self, topic: str, listener: Subscriber): if self.all_listeners.get(topic, None): raise Exception(f'Topic {topic} already registered') self.all_listeners[topic] = listener self.ready.wait() self.PROCESS.set() def unsubscribe(self, topic): listener = self.all_listeners.get(topic, None) if not listener: raise Exception(f'Topic {topic} was not registered') self.all_listeners.pop(topic) self.active_listeners.discard(topic) def connect(self) -> Client: if self.client: return self self.thread = Thread(target=self._loop, daemon=True) self.thread.start() # print('ready received') return self def _loop(self): try: self.loop = asyncio.new_event_loop() self.loop.run_until_complete(self._connect()) # asyncio.run(self._connect()) except KeyboardInterrupt: if self.client: self.client.disconnect() # print("Received exit, exiting") async def _connect(self): GmqttClient.counter += 1 client_id = f'client-id/{platform.node()}/pid_{os.getpid()}/{uuid.getnode()}/{GmqttClient.counter}' self.client = MQTTClient(client_id) self.PROCESS = asyncio.Event() self.client.on_connect = self.on_connect self.client.on_message = self.on_message self.client.on_disconnect = self.on_disconnect self.client.on_subscribe = self.on_subscribe # print('set') self.client.set_auth_credentials(self.config.username, self.config.password) while True: try: await self.client.connect(self.config.hostname, port=self.config.port, ssl=self.config.ssl, version=self.config.mqtt_version) break except Exception as ex: print('connect failed', ex) time.sleep(1) self.ready.set() while True: await self.PROCESS.wait() self.PROCESS.clear() # print('process signaled') self._sync_subscriptions() # await self.STOP.wait() await self.client.disconnect() def on_connect(self, client, flags, rc, properties): self.connected = True print('Connected', id(self)) self.PROCESS.set() self._sync_subscriptions() def _sync_subscriptions(self): if not self.client.is_connected: return unregistered = self.all_listeners.keys() - self.active_listeners for topic in unregistered: self.client.subscribe(topic, qos=1) self.active_listeners.add(topic) def _no_subscriber(self, message): print('RECV MSG with no subscriber:', message.topic, message.payload) def on_message(self, client, topic, payload, qos, properties): s = self.all_listeners.get(topic, self._no_subscriber) s(topic, payload) def on_disconnect(self, client, packet, exc=None): self.connected = False self.active_listeners.clear() print('Disconnected', id(self)) self.PROCESS.set() def on_subscribe(self, client, mid, qos, *args, **kwargs): # print('SUBSCRIBED') pass
class MQTTClient(Entity): """ A helper class for MQTT. Handles all the connection details. Returned to the library or module that calls self._MQTTYombo.new(). .. code-block:: python self.my_mqtt = self._MQTT.new(on_message_callback=self.mqtt_incoming, client_id="my_client_name") self.my_mqtt.subscribe("yombo/devices/+/get") # subscribe to a topic. + is a wildcard for a single section. """ def __init__(self, parent, hostname: Optional[str] = None, port: Optional[int] = None, username: Optional[str] = None, password: Optional[str] = None, use_ssl: Optional[bool] = None, version: Optional[str] = None, keepalive: Optional[int] = None, session_expiry: Optional[int] = None, receive_maximum: Optional[int] = None, user_property: Optional[Union[tuple, List[tuple]]] = None, last_will: Optional = None, maximum_packet_size: Optional[int] = None, on_message_callback: Callable = None, subscribe_callback: Callable = None, unsubscribe_callback: Callable = None, connected_callback: Optional[Callable] = None, disconnected_callback: Optional[Callable] = None, error_callback: Optional[Callable] = None, client_id: Optional[str] = None, password2: Optional[str] = None): """ Creates a new client connection to an MQTT broker. :param parent: A reference to the MQTT library. :param hostname: IP address or hostname to connect to. :param port: Port number to connect to. :param username: Username to connect as. Use "" to not use a username & password. :param password: Password to to connect with. Use "" to not use a password. :param use_ssl: Use SSL when attempting to connect to server, default is True. :param version: MQTT version to use, default: MQTTv50. Other: MQTTv311 :param keepalive: How often the connection should be checked that it's still alive. :param session_expiry: How many seconds the session should be valid. Defaults to 0. :param receive_maximum: The Client uses this value to limit the number of QoS 1 and QoS 2 publications that it is willing to process concurrently. :param user_property: Connection user_property. A tuple or list of tuples. :param last_will: Last will message generated by 'will()'. :param maximum_packet_size: The maximum size the mqtt payload should be, in size. :param on_message_callback: (required) method - Method to send messages to. :param connected_callback: method - If you want a function called when connected to server. :param disconnected_callback: method - If you want a function called when disconnected from server. :param subscribe_callback: method - This method will be called when successfully subscribed to topic. :param unsubscribe_callback: method - This method will be called when successfully unsubscribed from topic. :param error_callback: method - A function to call if something goes wrong. :param client_id: (default - random) - A client id to use for logging. :param password2: A second password to try. Used by MQTTYombo. :return: """ self._Entity_type: str = "MQTTClient" self._Entity_label_attribute: str = "client_id" super().__init__(parent) self.connected = False self.incoming_duplicates = deque([], 150) self.send_queue = deque() self.subscriptions = {} self.unsubscriptions = {} self.topics = {} # Store topics to resubscribe to self.hostname = hostname self.port = port self.username = username self.password = password self.password2 = password2 self.use_ssl = use_ssl self.version = version self.keepalive = keepalive self.session_expiry = session_expiry self.receive_maximum = receive_maximum self.user_property = user_property self.last_will = last_will self.maximum_packet_size = maximum_packet_size self.on_message_callback = on_message_callback self.subscribe_callback = subscribe_callback self.unsubscribe_callback = unsubscribe_callback self.connected_callback = connected_callback self.disconnected_callback = disconnected_callback self.error_callback = error_callback self.client_id = client_id client_options = { "receive_maximum": receive_maximum, "session_expiry_interval": session_expiry, "maximum_packet_size": maximum_packet_size, "user_property": user_property, } self.client = QClient( client_id, **{k: v for k, v in client_options.items() if v is not None}) self.client.set_auth_credentials(username, password.encode()) self.client.on_connect = self.on_connect self.client.on_message = self.on_message_callback self.client.on_disconnect = self.on_disconnect self.client.on_subscribe = self.on_subscribe @inlineCallbacks def connect(self): """Connects to the mqtt broker.""" d = self.as_deferred(self.do_connect()) yield d def as_deferred(self, f): return Deferred.fromFuture(asyncio.ensure_future(f)) async def do_connect(self): """Connects to the mqtt broker.""" await asyncio.create_task( self.client.connect(host=self.hostname, port=self.port)) def on_connect(self, client, flags, rc, properties): """Received a message.""" self.connected = True # Do subscribes for topic, kwargs in self.subscriptions.items(): self.client.subscribe(topic, **kwargs) for topic, kwargs in self.unsubscriptions.items(): self.client.unsubscribe(topic, **kwargs) # Do messages for message in self.send_queue: self.client.publish(message["topic"], **message["kwargs"]) if callable(self.connected_callback): self.connected_callback(properties=properties) def on_disconnect(self, client, packet, exc=None): """Disconnected notification.""" self.connected = False if callable(self.disconnected_callback): self.disconnected_callback(client=client, packet=packet) def on_message(self, client, topic, body, qos, properties): """Received a message.""" if callable(self.on_message_callback): self.on_message_callback(client=client, topic=topic, body=body, qos=qos, properties=properties) def on_subscribe(self, client, mid, qos, properties): """Received subscribe confirmation.""" if callable(self.subscribe_callback): self.subscribe_callback(client=client, mid=mid, qos=qos, properties=properties) def on_unsubscribe(self, client, mid, qos): """Received unsubscribe confirmation.""" if callable(self.unsubscribe_callback): self.unsubscribe_callback(client=client, mid=mid, qos=qos) def subscribe(self, topic: str, **kwargs): """ Subscribe to a topic. :param topic: :param kwargs: :return: """ if "qos" not in kwargs: kwargs["qos"] = 1 if self.session_expiry == 0: self.subscriptions[topic] = kwargs if self.connected is True: self.client.subscribe(topic, **kwargs) def unsubscribe(self, topic: str, **kwargs): """ Unsubscribe from topic. :param topic: Topic to unsubscribe from. :param kwargs: :return: """ if "qos" not in kwargs: kwargs["qos"] = 1 if self.connected is True: self.client.unsubscribe(topic, **kwargs) if self.session_expiry == 0: self.unsubscriptions[topic] = kwargs def publish(self, topic: str, message: Optional[str] = None, qos: Optional[int] = None, **kwargs): """ Publish a message to the MQTT broker. If not connected yet, will hold in a queue for later. :param topic: Topic to publish too. :param message: Message to send. :param qos: quality of service. :param kwargs: Any additional items to send to the qmqtt publish command. :return: """ if qos is None: qos = 1 if self.connected is True: self.client.publish(topic, payload=message, qos=qos, **kwargs) else: kwargs["message"] = message kwargs["qos"] = qos self.send_queue.append({"topic": topic, "kwargs": kwargs})
class MQTTGateway(GatewayBase): """Gateway application for MQTT nodes on a network.""" PROTOCOL = "MQTT" def __init__(self, keys, options): self.host = options.mqtt_host self.port = options.mqtt_port self.max_time = options.max_time self.options = options self.node_mapping = {} # map node id to its uuid (TODO: FIXME) super().__init__(keys, options) # Connect to the MQTT broker self.mqtt_client = MQTTClient("client-id") self.mqtt_client.on_connect = self.on_connect self.mqtt_client.on_message = self.on_message self.mqtt_client.on_disconnect = self.on_disconnect self.mqtt_client.on_subscribe = self.on_subscribe asyncio.get_event_loop().create_task(self.start()) # Start the node cleanup task PeriodicCallback(self.check_dead_nodes, 1000).start() PeriodicCallback(self.request_alive, 30000).start() logger.info('MQTT gateway application started') async def start(self): await self.mqtt_client.connect('{}:{}'.format(self.host, self.port)) def on_connect(self, client, flags, rc, properties): self.mqtt_client.subscribe('node/check', 1) def on_message(self, client, topic, payload, qos, properties): try: data = json.loads(payload) except Exception: # Skip data if not valid return logger.debug("Received message from node: {} => {}".format( topic, data)) if topic.endswith("/check"): asyncio.get_event_loop().create_task(self.handle_node_check(data)) elif topic.endswith("/resources"): asyncio.get_event_loop().create_task( self.handle_node_resources(topic, data)) else: self.handle_node_update(topic, data) def on_disconnect(self, client, packet, exc=None): print('Disconnected') def on_subscribe(self, client, mid, qos, properties): print('SUBSCRIBED') def close(self): loop = asyncio.get_event_loop() loop.run_until_complete(self._disconnect()) async def _disconnect(self): for node in self.nodes: await self._disconnect_from_node(node) await self.mqtt_client.disconnect() async def discover_node(self, node): discover_topic = 'gateway/{}/discover'.format(node.resources['id']) await self.mqtt_client.publish(discover_topic, "resources", qos=1) logger.debug("Published '{}' to topic: {}".format( "resources", discover_topic)) def update_node_resource(self, node, endpoint, payload): node_id = node.resources['id'] asyncio.get_event_loop().create_task( self.mqtt_client.publish('gateway/{}/{}/set'.format( node_id, endpoint), payload, qos=1)) async def handle_node_check(self, data): """Handle alive message received from coap node.""" node_id = data['id'] if node_id not in self.node_mapping: node = Node(str(uuid.uuid4()), id=node_id) self.node_mapping.update({node_id: node.uid}) resources_topic = 'node/{}/resources'.format(node_id) await self.mqtt_client.subscribe(resources_topic, 1) logger.debug("Subscribed to topic: {}".format(resources_topic)) self.add_node(node) else: # The node simply sent a check message to notify that it's still # online. node = self.get_node(self.node_mapping[node_id]) node.update_last_seen() async def handle_node_resources(self, topic, data): """Process resources published by a node.""" node_id = topic.split("/")[1] if node_id not in self.node_mapping: return for resource in data: await self.mqtt_client.subscribe( 'node/{}/{}'.format(node_id, resource), 1) await self.mqtt_client.publish('gateway/{}/discover'.format(node_id), "values", qos=1) def handle_node_update(self, topic_name, data): """Handle CoAP post message sent from coap node.""" _, node_id, resource = topic_name.split("/") value = data['value'] if self.node_mapping[node_id] not in self.nodes: return node = self.get_node(self.node_mapping[node_id]) self.forward_data_from_node(node, resource, value) def request_alive(self): """Publish a request to trigger a check publish from nodes.""" logger.debug("Request check message from all MQTT nodes") asyncio.get_event_loop().create_task( self.mqtt_client.publish('gateway/check', '', qos=1)) def check_dead_nodes(self): """Check and remove nodes that are not alive anymore.""" to_remove = [ node for node in self.nodes.values() if int(time.time()) > node.last_seen + self.max_time ] for node in to_remove: logger.info("Removing inactive node {}".format(node.uid)) asyncio.get_event_loop().create_task( self._disconnect_from_node(node)) self.node_mapping.pop(node.resources['id']) self.remove_node(node) async def _disconnect_from_node(self, node): node_id = node.resources['id'] await self.mqtt_client.unsubscribe( ['node/{}/resource'.format(node_id)]) for resource in node.resources: await self.mqtt_client.unsubscribe( ['node/{}/{}'.format(node_id, resource)])
class EcovacsMqtt: """Handle mqtt connections.""" def __init__(self, *, continent: str, country: str): self._subscribers: MutableMapping[str, VacuumBot] = {} self._port = 443 self._hostname = f"mq-{continent}.ecouser.net" if country.lower() == "cn": self._hostname = "mq.ecouser.net" self._client: Optional[Client] = None self._received_set_commands: MutableMapping[str, SetCommand] = TTLCache( maxsize=60 * 60, ttl=60) # pylint: disable=unused-argument async def _on_message(client: Client, topic: str, payload: bytes, qos: int, properties: Dict) -> None: _LOGGER.debug("Got message: topic=%s; payload=%s;", topic, payload.decode()) topic_split = topic.split("/") if topic.startswith("iot/atr"): await self._handle_atr(topic_split, payload) elif topic.startswith("iot/p2p"): self._handle_p2p(topic_split, payload) else: _LOGGER.debug("Got unsupported topic: %s", topic) self.__on_message = _on_message async def initialize(self, auth: RequestAuth) -> None: """Initialize MQTT.""" if self._client is not None: self.disconnect() client_id = f"{auth.user_id}@ecouser/{auth.resource}" self._client = Client(client_id) self._client.on_message = self.__on_message self._client.set_auth_credentials(auth.user_id, auth.token) ssl_ctx = ssl.create_default_context() ssl_ctx.check_hostname = False ssl_ctx.verify_mode = ssl.CERT_NONE await self._client.connect(self._hostname, self._port, ssl=ssl_ctx, version=MQTTv311) async def subscribe(self, vacuum_bot: VacuumBot) -> None: """Subscribe for messages for given vacuum.""" if self._client is None: raise NotInitializedError vacuum = vacuum_bot.vacuum self._client.subscribe(_get_subscriptions(vacuum)) self._subscribers[vacuum.did] = vacuum_bot def unsubscribe(self, vacuum_bot: VacuumBot) -> None: """Unsubscribe given vacuum.""" vacuum = vacuum_bot.vacuum if self._subscribers.pop(vacuum.did, None) and self._client: for subscription in _get_subscriptions(vacuum): self._client.unsubscribe(subscription.topic) def disconnect(self) -> None: """Disconnect from MQTT.""" if self._client: self._client.disconnect() self._subscribers.clear() async def _handle_atr(self, topic_split: List[str], payload: bytes) -> None: try: bot = self._subscribers.get(topic_split[3]) if bot: data = json.loads(payload) await bot.handle(topic_split[2], data) except Exception: # pylint: disable=broad-except _LOGGER.error("An exception occurred during handling atr message", exc_info=True) def _handle_p2p(self, topic_split: List[str], payload: bytes) -> None: try: command_name = topic_split[2] if command_name not in SET_COMMAND_NAMES: # command doesn't need special treatment or is not supported yet return is_request = topic_split[9] == "q" request_id = topic_split[10] if is_request: payload_json = json.loads(payload) try: data = payload_json["body"]["data"] except KeyError: _LOGGER.warning( "Could not parse p2p payload: topic=%s; payload=%s", "/".join(topic_split), payload_json, ) return command_class = COMMANDS.get(command_name) if command_class and issubclass(command_class, SetCommand): self._received_set_commands[request_id] = command_class( **data) else: command = self._received_set_commands.get(request_id, None) if not command: _LOGGER.debug( "Response to setCommand came in probably to late. requestId=%s, commandName=%s", request_id, command_name, ) return bot = self._subscribers.get(topic_split[3]) if bot: data = json.loads(payload) if command.handle(bot.events, data) and isinstance( command.args, dict): command.get_command.handle(bot.events, command.args) except Exception: # pylint: disable=broad-except _LOGGER.error("An exception occurred during handling p2p message", exc_info=True)
class GMQTT_Client(MQTT_Base): def __init__(self, mqtt_settings): MQTT_Base.__init__(self, mqtt_settings) self.mqtt_client = None def connect(self): MQTT_Base.connect(self) self.mqtt_client = MQTTClient( 'gmqtt' #self.mqtt_settings["MQTT_CLIENT_ID"] ) self.mqtt_client.on_connect = self._on_connect self.mqtt_client.on_message = self._on_message self.mqtt_client.on_disconnect = self._on_disconnect if self.mqtt_settings["MQTT_USERNAME"]: self.mqtt_client.set_auth_credentials( self.mqtt_settings["MQTT_USERNAME"], self.mqtt_settings["MQTT_PASSWORD"], ) def start(): try: logger.warning('Connecting to MQTT') asyncio.set_event_loop(self.event_loop) # self.event_loop.run_until_complete( # self.mqtt_client.connect(self.mqtt_settings["MQTT_BROKER"], self.mqtt_settings["MQTT_PORT"],keepalive=self.mqtt_settings["MQTT_KEEPALIVE"], version=MQTTv311) # ) logger.warning('Looping forever') self.event_loop.run_forever() logger.warning('Event loop stopped') #self.session.close() except Exception as e: logger.error('Error in event loop {}'.format(e)) self.event_loop = asyncio.new_event_loop() logger.warning("Starting MQTT thread") self._ws_thread = threading.Thread(target=start, args=()) self._ws_thread.daemon = True self._ws_thread.start() future = asyncio.run_coroutine_threadsafe( self.mqtt_client.connect( self.mqtt_settings["MQTT_BROKER"], self.mqtt_settings["MQTT_PORT"], keepalive=self.mqtt_settings["MQTT_KEEPALIVE"], version=MQTTv311), self.event_loop) def publish(self, topic, payload, retain, qos): MQTT_Base.publish(self, topic, payload, retain, qos) if self.mqtt_connected is True: wrapped = functools.partial(self.mqtt_client.publish, topic, payload, retain=retain, qos=qos) self.event_loop.call_soon_threadsafe(wrapped) else: logger.warning( "Device MQTT publish NOT CONNECTED: {}, retain {}, qos {}, payload: {}" .format(topic, retain, qos, payload)) # future = asyncio.run_coroutine_threadsafe( # self.mqtt_client.publish(topic, payload, retain=retain, qos=qos), # self.event_loop # ) def subscribe(self, topic, qos): # subclass to provide MQTT_Base.subscribe(self, topic, qos) self.mqtt_client.subscribe(topic, qos) def unsubscribe(self, topic): # subclass to provide MQTT_Base.unsubscribe(self, topic) self.mqtt_client.unsubscribe(topic) def set_will(self, will, topic, retain, qos): MQTT_Base.set_will(self, will, topic, retain, qos) #self.mqtt_client.will_set(will, topic, retain, qos) def _on_connect(self, client, flags, rc, properties): logger.info("MQTT On Connect: {}".format(rc)) self.mqtt_connected = rc == 0 def _on_message(self, client, topic, payload, qos, properties): #topic = msg.topic #payload = msg.payload.decode("utf-8") MQTT_Base._on_message(self, topic, payload, False, qos) def _on_disconnect(self, client, packet, exc=None): self.mqtt_connected = False # note, change this uses the property setter, do not really need to catch this in the base class logger.warning("MQTT Disconnection {} {} {}".format( client, packet, exc)) MQTT_Base._on_disconnect(self, 0)
class MqttClient: def __init__(self, host: str, port: int, user: str, password: typing.Optional[str] = None): self.subscriptions: typing.Dict[str, typing.List[ValueCallback]] = {} self.host = host self.port = port self.client = Client('sorokdva-dialogs') self.client.set_auth_credentials(user, password) self.client.on_connect = self._on_connect self.client.on_message = self._on_message async def _on_message( self, client: Client, topic: str, payload: bytes, qos, properties, ) -> constants.PubRecReasonCode: log = logging.getLogger('mqtt') futures = [] value = payload.decode() for cb in self.subscriptions.get(topic, []): log.info('passing (%r, %r) to %s', topic, value, cb) futures.append(cb(topic, value)) if futures: await asyncio.wait(futures, return_when=asyncio.ALL_COMPLETED) return constants.PubRecReasonCode.SUCCESS def _on_connect(self, client: Client, flags: int, result: int, properties) -> None: # FIXME make base path configurable self.client.subscribe('/devices/#') def subscribe(self, topic: str, callback: ValueCallback) -> None: self.subscriptions.setdefault(topic, []).append(callback) def send(self, topic: str, message): self.client.publish(topic, message) async def run(self): await self.client.connect(self.host, self.port, version=constants.MQTTv311, keepalive=30) while True: self.client.publish('smarthome', b'ping') await asyncio.sleep(10) @classmethod def from_config(cls, cfg: dict) -> "MqttClient": return cls( host=cfg.get('host', 'localhost'), port=cfg.get('port', 1883), user=cfg.get('login', ''), password=cfg.get('password', None), )
class LinkGMQTT(object): def __init__(self, settings=None): print("LinkGMQTT __init__()") self.settings = settings self.client = MQTTClient(None) # None => autogenerated client id self.client.on_connect = self.on_connect self.client.on_message = self.on_message self.client.on_disconnect = self.on_disconnect self.client.on_subscribe = self.on_subscribe self.subscription_queue = asyncio.Queue() print("LinkGMQTT __init__ completed") async def start(self, server_settings): """ Connects to broker """ print('LinkGMQTT.start() connecting as user {}'.format( server_settings["user"])) self.client.set_auth_credentials(server_settings["user"], server_settings["password"]) try: await self.client.connect(server_settings["host"], keepalive=60, version=MQTTv311) except Exception as e: print("LinkGMQTT connect exception: {}".format(e)) return print('LinkGMQTT.start() connected {}'.format(server_settings["host"])) async def put(self, sensor_id, event): """ Sends sensor_id/event to MQTT broker. sensor_id is string, used as MQTT topic event is dictionary which will be converted to bytes for MQTT message """ #print('LinkGMQTT.put() sending {}'.format(sensor_id)) message = json.dumps(event) self.client.publish(sensor_id, message, qos=0) print("LinkGMQTT.put() published {} {}".format(sensor_id, message)) async def subscribe(self, subscribe_settings): """ Subscribes to sensor events. """ try: self.client.subscribe(subscribe_settings["topic"], qos=0) except Exception as e: print("LinkGMQTT subscribe exception: {}".format(e)) return print("LinkGMQTT.subscribed() {}".format(subscribe_settings["topic"])) async def get(self): print("LinkGMQTT get requested from client, awaiting queue") message = await self.subscription_queue.get() print("LinkGMQTT get returned from queue") return message def on_connect(self, client, flags, rc, properties): print('LinkGMQTT Connected') def on_message(self, client, topic, payload, qos, properties): print('LinkGMQTT RECV MSG:', topic, payload) message = payload.decode('utf-8') message_dict = {} try: message_dict = json.loads(message) except JSONDecodeError: message_dict["message"] = message print("remote_sensors() json msg error: {} => {}".format( topic, message)) message_dict["topic"] = topic self.subscription_queue.put_nowait(message_dict) def on_disconnect(self, client, packet, exc=None): print('LinkGMQTT Disconnected') def on_subscribe(self, client, mid, qos, properties): print('LinkGMQTT Subscribed') async def finish(self): await self.client.disconnect()
class GMQTTConnector(BaseConnector): """GMQTTConnector uses gmqtt library for connectors running over MQTT. """ def __init__(self, host, port, subscribe_topic, publish_topic, **kwargs): self.host = host self.port = port # topics self.subscribe_topic = subscribe_topic self.publish_topic = publish_topic # connection self.connection_id = uuid.uuid4().hex[:8] self.is_connected = False self.client = MQTTClient(self.connection_id) # callbacks self.client.on_connect = self.on_connect self.client.on_message = self.on_message self.client.on_disconnect = self.on_disconnect self.client.on_subscribe = self.on_subscribe self.STOP = asyncio.Event() # options self.ack_topic = kwargs.get('ack_topic') self.enable_ssl = kwargs.get('enable_ssl', False) self.enable_auth = kwargs.get('enable_auth', False) self.username = kwargs.get('username') self.password = kwargs.get('password') self.client_cert = kwargs.get('client_cert') self.client_key = kwargs.get('client_key') self.qos = kwargs.get('qos', 2) def get_connection_details(self): """get_connection_details returns the details about the current MQTT connection. """ return dict( connection_id=self.connection_id, host=self.host, port=self.port, is_connected=self.is_connected, subscribe_topic=self.subscribe_topic, publish_topic=self.publish_topic ) def on_connect(self, *args): """on_connect is a callback that gets exectued after the connection is made. Arguments: client {MQTTClient} -- gmqtt.MQTTClient flags {int} -- connection flags rc {int} -- connection result code properties {dict} -- config of the current connection """ logger.info("Connected with result code %s", str(args[2])) # Subscribing in on_connect() means that if we lose the connection and # reconnect then subscriptions will be renewed. # client.subscribe("$SYS/#", qos=0) if isinstance(self.subscribe_topic, str): self.client.subscribe(self.subscribe_topic, qos=self.qos) elif isinstance(self.subscribe_topic, list): for topic in self.subscribe_topic: self.client.subscribe(topic, qos=self.qos) else: logger.warning('subscribe_topic is either None or an unknown data type.' ' Currently subscribed to 0 topics.') async def on_message(self, *args): """on_message callback gets executed when the connection receives a message. Arguments: client {MQTTClient} -- gmqtt.MQTTClient topic {string} -- topic from which message was received payload {bytes} -- actual message bytes received qos {string} -- message QOS level (0,1,2) properties {dict} -- message properties """ logger.info("%s %s", args[1], str(args[2])) return 0 @staticmethod def on_disconnect(*args): """on_disconnect is a callback that gets executed after a disconnection occurs""" logger.info('Disconnected') @staticmethod def on_subscribe(*args): """on_subscribe is a callback that gets executed after a subscription is succesful""" logger.info('Subscribed') def ask_exit(self): """sets the STOP variable so that a signal gets sent to disconnect the client """ self.STOP.set() async def start(self): """starts initiates the connnection with the broker Raises: DestinationNotAvailable: If broker is not available ConnectionFailed: If connection failed due to any other reason """ try: conn_kwargs = dict(host=self.host, port=self.port) if self.enable_auth: self.client.set_auth_credentials(self.username, self.password) if self.enable_ssl: assert self.client_cert and self.client_key, \ "Cannot enable ssl without specifying client_cert and client_key" ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2) ssl_context.load_cert_chain(self.client_cert, keyfile=self.client_key) conn_kwargs.update(dict(ssl=ssl_context)) await self.client.connect(**conn_kwargs) self.is_connected = True except ConnectionRefusedError as e: # raising from None suppresses the exception chain raise DestinationNotAvailable( f'Connection Failed: Error connecting to' f' {self.host}:{self.port} - {e}' ) from None except Exception as e: raise ConnectionFailed(e) async def publish(self, *args, **kwargs): """publishes the message to the topic using client.publish""" self.client.publish(*args, **kwargs) async def stop(self): """force stop the connection with the MQTT broker.""" await self.client.disconnect() self.is_connected = False