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()
Exemple #2
0
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()
Exemple #4
0
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()
Exemple #5
0
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))
Exemple #6
0
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()
Exemple #7
0
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)
Exemple #8
0
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
Exemple #9
0
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
Exemple #12
0
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})
Exemple #13
0
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)])
Exemple #14
0
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)
Exemple #15
0
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)
Exemple #16
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