Exemplo n.º 1
0
class MQTT(Component):

    def init(self, url):
        self.url = url

        host, port = parse_bind(self.url)

        self.client = Client()
        self.client.on_connect = self._on_connect
        self.client.on_message = self._on_message

        self.client.connect(host, port)
        self.client.loop_start()

    def _on_connect(self, client, userdata, flags, rc):
        print("Connected with result code {0}".format(rc))

    def _on_message(self, client, userdata, msg):
        print("{0} {1}".format(msg.topic, msg.payload))
Exemplo n.º 2
0
class MosQuiTTo(IotProvider):
    """
    Child class containing implementations of IotProvider specific to MosQuiTTo.
    """
    def __init__(self, iot_provider_cfg):
        # 1. Set tls_version to correspond to mosquitto broker.
        # 2. Call parent class' __init__
        self.tls_version = eval(
            f"ssl.PROTOCOL_TLSv1_{iot_provider_cfg['tls_version']}")
        super().__init__(iot_provider_cfg)

    def on_connect(self, client, userdata, flags, rc):
        # Event handler for connection event. Subscribe to topic(s) here.
        client.subscribe(self.subscribe_topic, qos=0)

    def onmsg(self, client, userdata, msg):
        # Wraps core event handler for incoming messages
        msg_payload = msg.payload
        super().onmsg(msg_payload)

    def connect(self):
        # A connection to iot is established at the beginning and if publish fails
        self.mosquitto_comm = Client()
        self.mosquitto_comm.on_connect = self.on_connect
        self.mosquitto_comm.on_message = self.onmsg
        self.mosquitto_comm.tls_set(self.iot_ca_cert_path,
                                    self.iot_client_cert_path,
                                    self.iot_client_key_path,
                                    tls_version=self.tls_version,
                                    cert_reqs=ssl.CERT_REQUIRED)
        self.mosquitto_comm.connect(self.iot_broker, self.iot_port)
        self.mosquitto_comm.loop_start()

    def disconnect(self):
        self.mosquitto_comm.disconnect()

    def publish(self, publish_topic, msg_str, qos):
        self.mosquitto_comm.publish(publish_topic, msg_str, qos=qos)
Exemplo n.º 3
0
def mqtt_cmnd(topic,
              payload=None,
              resp_topic=None,
              host=None,
              port=1883,
              username=None,
              password=None):
    resp_q = queue.Queue(1)

    c = MQTTClient(clean_session=True)
    if username is not None:
        c.username_pw_set(username, password)
    c.connect(host, port)
    c.subscribe(resp_topic or topic)
    c.on_message = lambda client, userdata, msg: resp_q.put(msg.payload)
    c.publish(topic, payload)
    c.loop_start()
    try:
        return resp_q.get(timeout=1)
    except queue.Empty:
        raise TimeoutError from None
    finally:
        c.loop_stop()
Exemplo n.º 4
0
class CubeSerializingSocket(object):

    CUBE_TOPIC_NAME = 'RawCubes'

    def __init__(self, on_receive_cube_fn=None):
        self.on_receive_cube_fn = on_receive_cube_fn

        # https://www.infoq.com/articles/practical-mqtt-with-paho
        self.mqtt_client = MqttClient()

        if self.on_receive_cube_fn is not None:

            def on_connect(client, userdata, flags, rc):
                self.mqtt_client.subscribe(
                    CubeSerializingSocket.CUBE_TOPIC_NAME)

            self.mqtt_client.on_connect = on_connect
            self.mqtt_client.message_callback_add(
                CubeSerializingSocket.CUBE_TOPIC_NAME,
                self.receive_zipped_cube)

        self.mqtt_client.connect('127.0.0.1')
        self.mqtt_client.loop_start()

    def send_zipped_cube(self, cube_dict):
        dict = cPickle.dumps(cube_dict, protocol=cPickle.HIGHEST_PROTOCOL)
        dict = zlib.compress(dict)
        logging.debug("SEND Cube: Topic: {}".format(
            CubeSerializingSocket.CUBE_TOPIC_NAME))
        return self.mqtt_client.publish(CubeSerializingSocket.CUBE_TOPIC_NAME,
                                        bytearray(dict))

    def receive_zipped_cube(self, client, userdata, msg):
        data = msg.payload
        pobj = zlib.decompress(data)
        pobj = cPickle.loads(pobj)
        self.on_receive_cube_fn(msg.topic, pobj)
Exemplo n.º 5
0
def create_client(
    hostname: str = leader_hostname,
    last_will: Optional[dict] = None,
    client_id: Optional[str] = None,
    keepalive=60,
    max_retries=3,
) -> Client:
    """
    Create a MQTT client and connect to a host.
    """

    def on_connect(client: Client, userdata, flags, rc: int, properties=None):
        if rc > 1:
            from pioreactor.logging import create_logger

            logger = create_logger("pubsub.create_client", to_mqtt=False)
            logger.error(f"Connection failed with error code {rc=}: {connack_string(rc)}")

    client = Client(client_id=client_id)
    client.on_connect = on_connect

    if last_will is not None:
        client.will_set(**last_will)

    for retries in range(1, max_retries + 1):
        try:
            client.connect(hostname, keepalive=keepalive)
        except (socket.gaierror, OSError):
            if retries == max_retries:
                break
            time.sleep(retries * 2)
        else:
            client.loop_start()
            break

    return client
Exemplo n.º 6
0
def subscribe(
    topics: str | list[str],
    hostname=leader_hostname,
    retries: int = 10,
    timeout: Optional[float] = None,
    allow_retained: bool = True,
    **mqtt_kwargs,
) -> Optional[MQTTMessage]:
    """
    Modeled closely after the paho version, this also includes some try/excepts and
    a timeout. Note that this _does_ disconnect after receiving a single message.

    A failure case occurs if this is called in a thread (eg: a callback) and is waiting
    indefinitely for a message. The parent job may not exit properly.

    """

    retry_count = 1
    for retry_count in range(retries):
        try:

            lock: Optional[threading.Lock]

            def on_connect(client, userdata, flags, rc):
                client.subscribe(userdata["topics"])
                return

            def on_message(client, userdata, message: MQTTMessage):
                if not allow_retained and message.retain:
                    return

                userdata["messages"] = message
                client.disconnect()

                if userdata["lock"]:
                    userdata["lock"].release()

                return

            if timeout:
                lock = threading.Lock()
            else:
                lock = None

            topics = [topics] if isinstance(topics, str) else topics
            userdata: dict[str, Any] = {
                "topics": [(topic, mqtt_kwargs.pop("qos", 0)) for topic in topics],
                "messages": None,
                "lock": lock,
            }

            client = Client(userdata=userdata)
            client.on_connect = on_connect
            client.on_message = on_message
            client.connect(leader_hostname)

            if timeout is None:
                client.loop_forever()
            else:
                assert lock is not None
                lock.acquire()
                client.loop_start()
                lock.acquire(timeout=timeout)
                client.loop_stop()
                client.disconnect()

            return userdata["messages"]

        except (ConnectionRefusedError, socket.gaierror, OSError, socket.timeout):
            from pioreactor.logging import create_logger

            logger = create_logger("pubsub.subscribe", to_mqtt=False)
            logger.debug(
                f"Attempt {retry_count}: Unable to connect to host: {hostname}",
                exc_info=True,
            )

            time.sleep(5 * retry_count)  # linear backoff

    else:
        logger = create_logger("pubsub.subscribe", to_mqtt=False)
        logger.error(f"Unable to connect to host: {hostname}. Exiting.")
        raise ConnectionRefusedError(f"Unable to connect to host: {hostname}.")
Exemplo n.º 7
0
class MQTTEventSink(EventSink):
    def __init__(self, broker,
                topic="iot-1/d/%012x/evt/%s/json",
                hostname=None,
                hostport=1883,
                username=None,
                password=None,
                keepalive=60):
        EventSink.__init__(self, broker)
        self._client = Paho()
        self._client.on_connect = \
                lambda mosq, obj, rc: self._on_connect(mosq, obj, rc)
        self._client.on_disconnect = \
                lambda mosq, obj, rc: self._on_disconnect(mosq, obj, rc)
        self._client.on_publish = \
                lambda mosq, obj, mid: self._on_publish(mosq, obj, mid)
        self._topic_format = topic
        self._topic = self._topic_format % (0, "%s")

        self._hostname = hostname
        self._hostport = hostport
        self._username = username
        self._password = password
        self._keepalive = keepalive

        self._loopflag = False
        self._neta = None

    def _on_connect(self, mosq, obj, rc):
    	self._topic = self._topic_format % (get_mac(), "%s")
        log.debug("MQTT publisher connected: " + str(rc))

    def _on_disconnect(self, mosq, obj, rc):
        log.debug("MQTT publisher disconnected: " + str(rc))

    def _on_publish(self, mosq, obj, mid):
        #log.debug("MQTT publisher published: " + str(mid))
        pass

    def _try_connect(self):
        if self._username is not None and self._password is not None:
            self._client.username_pw_set(self._username, self._password)
        try:
            self._client.connect(self._hostname, self._hostport, self._keepalive)
        except socket.gaierror:
            return False
        self._client.loop_start()
        self._loopflag = True
        return True

    def on_start(self):
        return self._try_connect()

    def send(self, encoded_event):
        # Fill in the blank "%s" left in self._topic
        import json

        # extract the actual topic string
        event = json.loads(encoded_event)
        topic_event_type = event["d"]["event"]
        topic = self._topic % topic_event_type

        # Check to see if event is from neighbors 
        # and need to be published to Mqtt server
        if "published" in event["d"]:
            if event["d"]["published"] == 1:
                return True
            else:
                del event["d"]["published"]

        # Publish message
        res, mid = self._client.publish(topic, encoded_event)
        if res == paho.mqtt.client.MQTT_ERR_SUCCESS:
            log.info("MQTT message published to " + topic)
        elif res == paho.mqtt.client.MQTT_ERR_NO_CONN:
            log.error("MQTT publisher failure: No connection")
            return False
        else:
            log.error("MQTT publisher failure: Unknown error")
            return False
        return True

    def check_available(self, event):
        if self._neta is not None and not self._neta:
            return False
        if not self._loopflag:
            if not self._try_connect():
                log.error("MQTT publisher failure: Cannot connect")
                return False
        return True

    def on_event(self, event, topic):
        et = event.get_type()
        ed = event.get_raw_data()

        if et == "internet_access":
            self._neta = ed

    def encode_event(self, event):
        # return event.to_json()
        return json.dumps({"d": event.to_map()})
Exemplo n.º 8
0
class MqttHandler:
    def __init__(self, client_id='DEFAULT_CLIENT_ID', topic='DEFAULT_TOPIC', broker_host='localhost',
                 broker_port=MQTT_BROKER_PORT):
        self.subscribed = False
        self.client_id = client_id
        self.client = Client(client_id=self.client_id, protocol=MQTT_PROTOCOL_VERSION)
        self.client.on_message = self.on_message_callback
        self.client.on_publish = self.on_publish_callback
        self.client.on_connect = self.connect_callback
        self.client.on_disconnect = self.disconnect_callback
        self.topic = topic
        self.broker_host = broker_host
        self.broker_port = broker_port
        self.message_received = 0
        userdata = {
            USER_DATA_MESSAGE_RECEIVED: 0,
        }
        self.client.user_data_set(userdata)

    def connect(self):
        self.client.connect(host=self.broker_host, port=self.broker_port)

    def connect_async(self):
        self.client.connect_async(host=self.broker_host, port=self.broker_port)

    def connect_callback(self, client, userdata, flags, rc):
        print('connect_callback: result code[' + str(rc) + ']')
        (result, _) = client.subscribe(topic=self.topic)
        self.subscribed = result

    def disconnect(self):
        self.client.disconnect()

    def disconnect_callback(self, client, userdata, rc):
        print('disconnect_callback')

    def is_valid(self, my_json: json):
        if app.config.get('DEBUG', False):
            print("json_validation")
        # try:
        #     if my_json['id'] is None or my_json['byte_stream'] is None:
        #         return False
        # except KeyError:
        #     return False
        return True

    def on_message_callback(self, client, userdata, message):
        from core.socketio_runner import emit_command

        userdata[USER_DATA_MESSAGE_RECEIVED] += 1

        topic = message.topic
        payload = json.loads(message.payload)

        if app.config.get('DEBUG', False):
            print('on_message_callback: topic[' + topic + ']')

        if self.is_valid(payload):
            emit_command(topic, payload)
        else:
            raise Exception('Message payload not valid')

    @staticmethod
    def publish_single_message(topic, payload=None, qos=0, retain=False, hostname="localhost",
                               port=MQTT_BROKER_PORT, client_id="", keepalive=60, will=None, auth=None, tls=None):
        if app.config.get('DEBUG', False):
            print("publish_single_message")
        single(topic=topic, payload=payload, qos=qos, retain=retain, hostname=hostname, port=port, client_id=client_id,
               keepalive=keepalive, will=will, auth=auth, tls=tls)

    def on_publish_callback(self, client, userdata, mid):
        print('on_publish_callback')

    def loop_for_ever(self):
        self.client.loop_forever()

    def loop_start(self):
        self.client.loop_start()

    def loop_stop(self, force=False):
        self.client.loop_stop(force=force)
Exemplo n.º 9
0
class MqttConnector(Connector, Thread):
    def __init__(self, gateway, config, connector_type):
        super().__init__()

        self.__gateway = gateway  # Reference to TB Gateway
        self._connector_type = connector_type  # Should be "mqtt"
        self.config = config  # mqtt.json contents

        self.__log = log
        self.statistics = {'MessagesReceived': 0, 'MessagesSent': 0}
        self.__subscribes_sent = {}

        # Extract main sections from configuration ---------------------------------------------------------------------
        self.__broker = config.get('broker')

        self.__mapping = []
        self.__server_side_rpc = []
        self.__connect_requests = []
        self.__disconnect_requests = []
        self.__attribute_requests = []
        self.__attribute_updates = []

        mandatory_keys = {
            "mapping": ['topicFilter', 'converter'],
            "serverSideRpc": [
                'deviceNameFilter', 'methodFilter', 'requestTopicExpression',
                'valueExpression'
            ],
            "connectRequests": ['topicFilter'],
            "disconnectRequests": ['topicFilter'],
            "attributeRequests":
            ['topicFilter', 'topicExpression', 'valueExpression'],
            "attributeUpdates": [
                'deviceNameFilter', 'attributeFilter', 'topicExpression',
                'valueExpression'
            ]
        }

        # Mappings, i.e., telemetry/attributes-push handlers provided by user via configuration file
        self.load_handlers('mapping', mandatory_keys['mapping'],
                           self.__mapping)

        # RPCs, i.e., remote procedure calls (ThingsBoard towards devices) handlers
        self.load_handlers('serverSideRpc', mandatory_keys['serverSideRpc'],
                           self.__server_side_rpc)

        # Connect requests, i.e., telling ThingsBoard that a device is online even if it does not post telemetry
        self.load_handlers('connectRequests',
                           mandatory_keys['connectRequests'],
                           self.__connect_requests)

        # Disconnect requests, i.e., telling ThingsBoard that a device is offline even if keep-alive has not expired yet
        self.load_handlers('disconnectRequests',
                           mandatory_keys['disconnectRequests'],
                           self.__disconnect_requests)

        # Shared attributes direct requests, i.e., asking ThingsBoard for some shared attribute value
        self.load_handlers('attributeRequests',
                           mandatory_keys['attributeRequests'],
                           self.__attribute_requests)

        # Attributes updates requests, i.e., asking ThingsBoard to send updates about an attribute
        self.load_handlers('attributeUpdates',
                           mandatory_keys['attributeUpdates'],
                           self.__attribute_updates)

        # Setup topic substitution lists for each class of handlers ----------------------------------------------------
        self.__mapping_sub_topics = {}
        self.__connect_requests_sub_topics = {}
        self.__disconnect_requests_sub_topics = {}
        self.__attribute_requests_sub_topics = {}

        # Set up external MQTT broker connection -----------------------------------------------------------------------
        client_id = self.__broker.get(
            "clientId",
            ''.join(random.choice(string.ascii_lowercase) for _ in range(23)))
        self._client = Client(client_id)
        self.setName(
            config.get(
                "name",
                self.__broker.get(
                    "name", 'Mqtt Broker ' + ''.join(
                        random.choice(string.ascii_lowercase)
                        for _ in range(5)))))

        if "username" in self.__broker["security"]:
            self._client.username_pw_set(self.__broker["security"]["username"],
                                         self.__broker["security"]["password"])

        if "caCert" in self.__broker["security"] \
                or self.__broker["security"].get("type", "none").lower() == "tls":
            ca_cert = self.__broker["security"].get("caCert")
            private_key = self.__broker["security"].get("privateKey")
            cert = self.__broker["security"].get("cert")

            if ca_cert is None:
                self._client.tls_set_context(
                    ssl.SSLContext(ssl.PROTOCOL_TLSv1_2))
            else:
                try:
                    self._client.tls_set(ca_certs=ca_cert,
                                         certfile=cert,
                                         keyfile=private_key,
                                         cert_reqs=ssl.CERT_REQUIRED,
                                         tls_version=ssl.PROTOCOL_TLSv1_2,
                                         ciphers=None)
                except Exception as e:
                    self.__log.error(
                        "Cannot setup connection to broker %s using SSL. "
                        "Please check your configuration.\nError: ",
                        self.get_name())
                    self.__log.exception(e)
                if self.__broker["security"].get("insecure", False):
                    self._client.tls_insecure_set(True)
                else:
                    self._client.tls_insecure_set(False)

        # Set up external MQTT broker callbacks ------------------------------------------------------------------------
        self._client.on_connect = self._on_connect
        self._client.on_message = self._on_message
        self._client.on_subscribe = self._on_subscribe
        self._client.on_disconnect = self._on_disconnect
        # self._client.on_log = self._on_log

        # Set up lifecycle flags ---------------------------------------------------------------------------------------
        self._connected = False
        self.__stopped = False
        self.daemon = True

        self.__msg_queue = Queue()
        self.__workers_thread_pool = []
        self.__max_msg_number_for_worker = config.get(
            'maxMessageNumberPerWorker', 10)
        self.__max_number_of_workers = config.get('maxNumberOfWorkers', 100)

    def load_handlers(self, handler_flavor, mandatory_keys,
                      accepted_handlers_list):
        if handler_flavor not in self.config:
            self.__log.error("'%s' section missing from configuration",
                             handler_flavor)
        else:
            for handler in self.config.get(handler_flavor):
                discard = False

                for key in mandatory_keys:
                    if key not in handler:
                        # Will report all missing fields to user before discarding the entry => no break here
                        discard = True
                        self.__log.error(
                            "Mandatory key '%s' missing from %s handler: %s",
                            key, handler_flavor, simplejson.dumps(handler))
                    else:
                        self.__log.debug(
                            "Mandatory key '%s' found in %s handler: %s", key,
                            handler_flavor, simplejson.dumps(handler))

                if discard:
                    self.__log.error(
                        "%s handler is missing some mandatory keys => rejected: %s",
                        handler_flavor, simplejson.dumps(handler))
                else:
                    accepted_handlers_list.append(handler)
                    self.__log.debug(
                        "%s handler has all mandatory keys => accepted: %s",
                        handler_flavor, simplejson.dumps(handler))

            self.__log.info("Number of accepted %s handlers: %d",
                            handler_flavor, len(accepted_handlers_list))

            self.__log.info(
                "Number of rejected %s handlers: %d", handler_flavor,
                len(self.config.get(handler_flavor)) -
                len(accepted_handlers_list))

    def is_connected(self):
        return self._connected

    def open(self):
        self.__stopped = False
        self.start()

    def run(self):
        try:
            self.__connect()
        except Exception as e:
            self.__log.exception(e)
            try:
                self.close()
            except Exception as e:
                self.__log.exception(e)

        while True:
            if self.__stopped:
                break
            elif not self._connected:
                self.__connect()
            self.__threads_manager()
            sleep(.2)

    def __connect(self):
        while not self._connected and not self.__stopped:
            try:
                self._client.connect(self.__broker['host'],
                                     self.__broker.get('port', 1883))
                self._client.loop_start()
                if not self._connected:
                    sleep(1)
            except ConnectionRefusedError as e:
                self.__log.error(e)
                sleep(10)

    def close(self):
        self.__stopped = True
        try:
            self._client.disconnect()
        except Exception as e:
            log.exception(e)
        self._client.loop_stop()
        self.__log.info('%s has been stopped.', self.get_name())

    def get_name(self):
        return self.name

    def __subscribe(self, topic, qos):
        message = self._client.subscribe(topic, qos)
        try:
            self.__subscribes_sent[message[1]] = topic
        except Exception as e:
            self.__log.exception(e)

    def _on_connect(self, client, userdata, flags, result_code, *extra_params):

        result_codes = {
            1: "incorrect protocol version",
            2: "invalid client identifier",
            3: "server unavailable",
            4: "bad username or password",
            5: "not authorised",
        }

        if result_code == 0:
            self._connected = True
            self.__log.info('%s connected to %s:%s - successfully.',
                            self.get_name(), self.__broker["host"],
                            self.__broker.get("port", "1883"))

            self.__log.debug(
                "Client %s, userdata %s, flags %s, extra_params %s",
                str(client), str(userdata), str(flags), extra_params)

            self.__mapping_sub_topics = {}

            # Setup data upload requests handling ----------------------------------------------------------------------
            for mapping in self.__mapping:
                try:
                    # Load converter for this mapping entry ------------------------------------------------------------
                    # mappings are guaranteed to have topicFilter and converter fields. See __init__
                    default_converter_class_name = "JsonMqttUplinkConverter"
                    # Get converter class from "extension" parameter or default converter
                    converter_class_name = mapping["converter"].get(
                        "extension", default_converter_class_name)
                    # Find and load required class
                    module = TBModuleLoader.import_module(
                        self._connector_type, converter_class_name)
                    if module:
                        self.__log.debug('Converter %s for topic %s - found!',
                                         converter_class_name,
                                         mapping["topicFilter"])
                        converter = module(mapping)
                    else:
                        self.__log.error("Cannot find converter for %s topic",
                                         mapping["topicFilter"])
                        continue

                    # Setup regexp topic acceptance list ---------------------------------------------------------------
                    regex_topic = TBUtility.topic_to_regex(
                        mapping["topicFilter"])

                    # There may be more than one converter per topic, so I'm using vectors
                    if not self.__mapping_sub_topics.get(regex_topic):
                        self.__mapping_sub_topics[regex_topic] = []

                    self.__mapping_sub_topics[regex_topic].append(converter)

                    # Subscribe to appropriate topic -------------------------------------------------------------------
                    self.__subscribe(mapping["topicFilter"],
                                     mapping.get("subscriptionQos", 1))

                    self.__log.info('Connector "%s" subscribe to %s',
                                    self.get_name(),
                                    TBUtility.regex_to_topic(regex_topic))

                except Exception as e:
                    self.__log.exception(e)

            # Setup connection requests handling -----------------------------------------------------------------------
            for request in [
                    entry for entry in self.__connect_requests
                    if entry is not None
            ]:
                # requests are guaranteed to have topicFilter field. See __init__
                self.__subscribe(request["topicFilter"],
                                 request.get("subscriptionQos", 1))
                topic_filter = TBUtility.topic_to_regex(
                    request.get("topicFilter"))
                self.__connect_requests_sub_topics[topic_filter] = request

            # Setup disconnection requests handling --------------------------------------------------------------------
            for request in [
                    entry for entry in self.__disconnect_requests
                    if entry is not None
            ]:
                # requests are guaranteed to have topicFilter field. See __init__
                self.__subscribe(request["topicFilter"],
                                 request.get("subscriptionQos", 1))
                topic_filter = TBUtility.topic_to_regex(
                    request.get("topicFilter"))
                self.__disconnect_requests_sub_topics[topic_filter] = request

            # Setup attributes requests handling -----------------------------------------------------------------------
            for request in [
                    entry for entry in self.__attribute_requests
                    if entry is not None
            ]:
                # requests are guaranteed to have topicFilter field. See __init__
                self.__subscribe(request["topicFilter"],
                                 request.get("subscriptionQos", 1))
                topic_filter = TBUtility.topic_to_regex(
                    request.get("topicFilter"))
                self.__attribute_requests_sub_topics[topic_filter] = request
        else:
            if result_code in result_codes:
                self.__log.error("%s connection FAIL with error %s %s!",
                                 self.get_name(), result_code,
                                 result_codes[result_code])
            else:
                self.__log.error("%s connection FAIL with unknown error!",
                                 self.get_name())

    def _on_disconnect(self, *args):
        self._connected = False
        self.__log.debug('"%s" was disconnected. %s', self.get_name(),
                         str(args))

    def _on_log(self, *args):
        self.__log.debug(args)

    def _on_subscribe(self, _, __, mid, granted_qos, *args):
        log.info(args)
        try:
            if granted_qos[0] == 128:
                self.__log.error(
                    '"%s" subscription failed to topic %s subscription message id = %i',
                    self.get_name(), self.__subscribes_sent.get(mid), mid)
            else:
                self.__log.info(
                    '"%s" subscription success to topic %s, subscription message id = %i',
                    self.get_name(), self.__subscribes_sent.get(mid), mid)
        except Exception as e:
            self.__log.exception(e)

        # Success or not, remove this topic from the list of pending subscription requests
        if self.__subscribes_sent.get(mid) is not None:
            del self.__subscribes_sent[mid]

    def put_data_to_convert(self, converter, message, content) -> bool:
        if not self.__msg_queue.full():
            self.__msg_queue.put((converter.convert, message.topic, content),
                                 True, 100)
            return True
        return False

    def _save_converted_msg(self, topic, data):
        self.__gateway.send_to_storage(self.name, data)
        self.statistics['MessagesSent'] += 1
        self.__log.debug("Successfully converted message from topic %s", topic)

    def __threads_manager(self):
        if len(self.__workers_thread_pool) == 0:
            worker = MqttConnector.ConverterWorker("Main", self.__msg_queue,
                                                   self._save_converted_msg)
            self.__workers_thread_pool.append(worker)
            worker.start()

        number_of_needed_threads = round(
            self.__msg_queue.qsize() / self.__max_msg_number_for_worker, 0)
        threads_count = len(self.__workers_thread_pool)
        if number_of_needed_threads > threads_count < self.__max_number_of_workers:
            thread = MqttConnector.ConverterWorker(
                "Worker " + ''.join(
                    random.choice(string.ascii_lowercase) for _ in range(5)),
                self.__msg_queue, self._save_converted_msg)
            self.__workers_thread_pool.append(thread)
            thread.start()
        elif number_of_needed_threads < threads_count and threads_count > 1:
            worker: MqttConnector.ConverterWorker = self.__workers_thread_pool[
                -1]
            if not worker.in_progress:
                worker.stopped = True
                self.__workers_thread_pool.remove(worker)

    def _on_message(self, client, userdata, message):
        self.statistics['MessagesReceived'] += 1
        content = TBUtility.decode(message)

        # Check if message topic exists in mappings "i.e., I'm posting telemetry/attributes" ---------------------------
        topic_handlers = [
            regex for regex in self.__mapping_sub_topics
            if fullmatch(regex, message.topic)
        ]

        if topic_handlers:
            # Note: every topic may be associated to one or more converter. This means that a single MQTT message
            # may produce more than one message towards ThingsBoard. This also means that I cannot return after
            # the first successful conversion: I got to use all the available ones.
            # I will use a flag to understand whether at least one converter succeeded
            request_handled = False

            for topic in topic_handlers:
                available_converters = self.__mapping_sub_topics[topic]
                for converter in available_converters:
                    try:
                        if isinstance(content, list):
                            for item in content:
                                request_handled = self.put_data_to_convert(
                                    converter, message, item)
                                if not request_handled:
                                    self.__log.error(
                                        'Cannot find converter for the topic:"%s"! Client: %s, User data: %s',
                                        message.topic, str(client),
                                        str(userdata))
                        else:
                            request_handled = self.put_data_to_convert(
                                converter, message, content)

                    except Exception as e:
                        log.exception(e)

            if not request_handled:
                self.__log.error(
                    'Cannot find converter for the topic:"%s"! Client: %s, User data: %s',
                    message.topic, str(client), str(userdata))

            # Note: if I'm in this branch, this was for sure a telemetry/attribute push message
            # => Execution must end here both in case of failure and success
            return None

        # Check if message topic exists in connection handlers "i.e., I'm connecting a device" -------------------------
        topic_handlers = [
            regex for regex in self.__connect_requests_sub_topics
            if fullmatch(regex, message.topic)
        ]

        if topic_handlers:
            for topic in topic_handlers:
                handler = self.__connect_requests_sub_topics[topic]

                found_device_name = None
                found_device_type = 'default'

                # Get device name, either from topic or from content
                if handler.get("deviceNameTopicExpression"):
                    device_name_match = search(
                        handler["deviceNameTopicExpression"], message.topic)
                    if device_name_match is not None:
                        found_device_name = device_name_match.group(0)
                elif handler.get("deviceNameJsonExpression"):
                    found_device_name = TBUtility.get_value(
                        handler["deviceNameJsonExpression"], content)

                # Get device type (if any), either from topic or from content
                if handler.get("deviceTypeTopicExpression"):
                    device_type_match = search(
                        handler["deviceTypeTopicExpression"], message.topic)
                    found_device_type = device_type_match.group(
                        0) if device_type_match is not None else handler[
                            "deviceTypeTopicExpression"]
                elif handler.get("deviceTypeJsonExpression"):
                    found_device_type = TBUtility.get_value(
                        handler["deviceTypeJsonExpression"], content)

                if found_device_name is None:
                    self.__log.error(
                        "Device name missing from connection request")
                    continue

                # Note: device must be added even if it is already known locally: else ThingsBoard
                # will not send RPCs and attribute updates
                self.__log.info("Connecting device %s of type %s",
                                found_device_name, found_device_type)
                self.__gateway.add_device(found_device_name,
                                          {"connector": self},
                                          device_type=found_device_type)

            # Note: if I'm in this branch, this was for sure a connection message
            # => Execution must end here both in case of failure and success
            return None

        # Check if message topic exists in disconnection handlers "i.e., I'm disconnecting a device" -------------------
        topic_handlers = [
            regex for regex in self.__disconnect_requests_sub_topics
            if fullmatch(regex, message.topic)
        ]
        if topic_handlers:
            for topic in topic_handlers:
                handler = self.__disconnect_requests_sub_topics[topic]

                found_device_name = None
                found_device_type = 'default'

                # Get device name, either from topic or from content
                if handler.get("deviceNameTopicExpression"):
                    device_name_match = search(
                        handler["deviceNameTopicExpression"], message.topic)
                    if device_name_match is not None:
                        found_device_name = device_name_match.group(0)
                elif handler.get("deviceNameJsonExpression"):
                    found_device_name = TBUtility.get_value(
                        handler["deviceNameJsonExpression"], content)

                # Get device type (if any), either from topic or from content
                if handler.get("deviceTypeTopicExpression"):
                    device_type_match = search(
                        handler["deviceTypeTopicExpression"], message.topic)
                    if device_type_match is not None:
                        found_device_type = device_type_match.group(0)
                elif handler.get("deviceTypeJsonExpression"):
                    found_device_type = TBUtility.get_value(
                        handler["deviceTypeJsonExpression"], content)

                if found_device_name is None:
                    self.__log.error(
                        "Device name missing from disconnection request")
                    continue

                if found_device_name in self.__gateway.get_devices():
                    self.__log.info("Disconnecting device %s of type %s",
                                    found_device_name, found_device_type)
                    self.__gateway.del_device(found_device_name)
                else:
                    self.__log.info("Device %s was not connected",
                                    found_device_name)

                break

            # Note: if I'm in this branch, this was for sure a disconnection message
            # => Execution must end here both in case of failure and success
            return None

        # Check if message topic exists in attribute request handlers "i.e., I'm asking for a shared attribute" --------
        topic_handlers = [
            regex for regex in self.__attribute_requests_sub_topics
            if fullmatch(regex, message.topic)
        ]
        if topic_handlers:
            try:
                for topic in topic_handlers:
                    handler = self.__attribute_requests_sub_topics[topic]

                    found_device_name = None
                    found_attribute_name = None

                    # Get device name, either from topic or from content
                    if handler.get("deviceNameTopicExpression"):
                        device_name_match = search(
                            handler["deviceNameTopicExpression"],
                            message.topic)
                        if device_name_match is not None:
                            found_device_name = device_name_match.group(0)
                    elif handler.get("deviceNameJsonExpression"):
                        found_device_name = TBUtility.get_value(
                            handler["deviceNameJsonExpression"], content)

                    # Get attribute name, either from topic or from content
                    if handler.get("attributeNameTopicExpression"):
                        attribute_name_match = search(
                            handler["attributeNameTopicExpression"],
                            message.topic)
                        if attribute_name_match is not None:
                            found_attribute_name = attribute_name_match.group(
                                0)
                    elif handler.get("attributeNameJsonExpression"):
                        found_attribute_name = TBUtility.get_value(
                            handler["attributeNameJsonExpression"], content)

                    if found_device_name is None:
                        self.__log.error(
                            "Device name missing from attribute request")
                        continue

                    if found_attribute_name is None:
                        self.__log.error(
                            "Attribute name missing from attribute request")
                        continue

                    self.__log.info("Will retrieve attribute %s of %s",
                                    found_attribute_name, found_device_name)
                    self.__gateway.tb_client.client.gw_request_shared_attributes(
                        found_device_name, [found_attribute_name],
                        lambda data, *args: self.notify_attribute(
                            data, found_attribute_name,
                            handler.get("topicExpression"),
                            handler.get("valueExpression"),
                            handler.get('retain', False)))

                    break

            except Exception as e:
                log.exception(e)

            # Note: if I'm in this branch, this was for sure an attribute request message
            # => Execution must end here both in case of failure and success
            return None

        # Check if message topic exists in RPC handlers ----------------------------------------------------------------
        # The gateway is expecting for this message => no wildcards here, the topic must be evaluated as is

        if self.__gateway.is_rpc_in_progress(message.topic):
            log.info("RPC response arrived. Forwarding it to thingsboard.")
            self.__gateway.rpc_with_reply_processing(message.topic, content)
            return None

        self.__log.debug(
            "Received message to topic \"%s\" with unknown interpreter data: \n\n\"%s\"",
            message.topic, content)

    def notify_attribute(self, incoming_data, attribute_name, topic_expression,
                         value_expression, retain):
        if incoming_data.get("device") is None or incoming_data.get(
                "value") is None:
            return

        device_name = incoming_data.get("device")
        attribute_value = incoming_data.get("value")

        topic = topic_expression \
            .replace("${deviceName}", str(device_name)) \
            .replace("${attributeKey}", str(attribute_name))

        data = value_expression.replace("${attributeKey}", str(attribute_name)) \
            .replace("${attributeValue}", str(attribute_value))

        self._client.publish(topic, data, retain=retain).wait_for_publish()

    def on_attributes_update(self, content):
        if self.__attribute_updates:
            for attribute_update in self.__attribute_updates:
                if match(attribute_update["deviceNameFilter"],
                         content["device"]):
                    for attribute_key in content["data"]:
                        if match(attribute_update["attributeFilter"],
                                 attribute_key):
                            try:
                                topic = attribute_update["topicExpression"] \
                                    .replace("${deviceName}", str(content["device"])) \
                                    .replace("${attributeKey}", str(attribute_key)) \
                                    .replace("${attributeValue}", str(content["data"][attribute_key]))
                            except KeyError as e:
                                log.exception(
                                    "Cannot form topic, key %s - not found", e)
                                raise e
                            try:
                                data = attribute_update["valueExpression"] \
                                    .replace("${attributeKey}", str(attribute_key)) \
                                    .replace("${attributeValue}", str(content["data"][attribute_key]))
                            except KeyError as e:
                                log.exception(
                                    "Cannot form topic, key %s - not found", e)
                                raise e
                            self._client.publish(
                                topic,
                                data,
                                retain=attribute_update.get(
                                    'retain', False)).wait_for_publish()
                            self.__log.debug(
                                "Attribute Update data: %s for device %s to topic: %s",
                                data, content["device"], topic)
                        else:
                            self.__log.error(
                                "Cannot find attributeName by filter in message with data: %s",
                                content)
                else:
                    self.__log.error(
                        "Cannot find deviceName by filter in message with data: %s",
                        content)
        else:
            self.__log.error("Attribute updates config not found.")

    def server_side_rpc_handler(self, content):
        self.__log.info("Incoming server-side RPC: %s", content)

        # Check whether one of my RPC handlers can handle this request
        for rpc_config in self.__server_side_rpc:
            if search(rpc_config["deviceNameFilter"], content["device"]) \
                    and search(rpc_config["methodFilter"], content["data"]["method"]) is not None:

                # This handler seems able to handle the request
                self.__log.info("Candidate RPC handler found")

                expects_response = rpc_config.get("responseTopicExpression")
                defines_timeout = rpc_config.get("responseTimeout")

                # 2-way RPC setup
                if expects_response and defines_timeout:
                    expected_response_topic = rpc_config["responseTopicExpression"] \
                        .replace("${deviceName}", str(content["device"])) \
                        .replace("${methodName}", str(content["data"]["method"])) \
                        .replace("${requestId}", str(content["data"]["id"]))

                    expected_response_topic = TBUtility.replace_params_tags(
                        expected_response_topic, content)

                    timeout = time() * 1000 + rpc_config.get("responseTimeout")

                    # Start listenting on the response topic
                    self.__log.info("Subscribing to: %s",
                                    expected_response_topic)
                    self.__subscribe(expected_response_topic,
                                     rpc_config.get("responseTopicQoS", 1))

                    # Wait for subscription to be carried out
                    sub_response_timeout = 10

                    while expected_response_topic in self.__subscribes_sent.values(
                    ):
                        sub_response_timeout -= 1
                        sleep(0.1)
                        if sub_response_timeout == 0:
                            break

                    # Ask the gateway to enqueue this as an RPC response
                    self.__gateway.register_rpc_request_timeout(
                        content, timeout, expected_response_topic,
                        self.rpc_cancel_processing)

                    # Wait for RPC to be successfully enqueued, which never fails.
                    while self.__gateway.is_rpc_in_progress(
                            expected_response_topic):
                        sleep(0.1)

                elif expects_response and not defines_timeout:
                    self.__log.info(
                        "2-way RPC without timeout: treating as 1-way")

                # Actually reach out for the device
                request_topic: str = rpc_config.get("requestTopicExpression") \
                    .replace("${deviceName}", str(content["device"])) \
                    .replace("${methodName}", str(content["data"]["method"])) \
                    .replace("${requestId}", str(content["data"]["id"]))

                request_topic = TBUtility.replace_params_tags(
                    request_topic, content)

                data_to_send_tags = TBUtility.get_values(
                    rpc_config.get('valueExpression'),
                    content['data'],
                    'params',
                    get_tag=True)
                data_to_send_values = TBUtility.get_values(
                    rpc_config.get('valueExpression'),
                    content['data'],
                    'params',
                    expression_instead_none=True)

                data_to_send = rpc_config.get('valueExpression')
                for (tag, value) in zip(data_to_send_tags,
                                        data_to_send_values):
                    data_to_send = data_to_send.replace(
                        '${' + tag + '}', str(value))

                try:
                    self.__log.info("Publishing to: %s with data %s",
                                    request_topic, data_to_send)
                    self._client.publish(request_topic,
                                         data_to_send,
                                         retain=rpc_config.get(
                                             'retain', False))

                    if not expects_response or not defines_timeout:
                        self.__log.info(
                            "One-way RPC: sending ack to ThingsBoard immediately"
                        )
                        self.__gateway.send_rpc_reply(
                            device=content["device"],
                            req_id=content["data"]["id"],
                            success_sent=True)

                    # Everything went out smoothly: RPC is served
                    return

                except Exception as e:
                    self.__log.exception(e)

        self.__log.error("RPC not handled: %s", content)

    def rpc_cancel_processing(self, topic):
        log.info("RPC canceled or terminated. Unsubscribing from %s", topic)
        self._client.unsubscribe(topic)

    class ConverterWorker(Thread):
        def __init__(self, name, incoming_queue, send_result):
            super().__init__()
            self.stopped = False
            self.setName(name)
            self.setDaemon(True)
            self.__msg_queue = incoming_queue
            self.in_progress = False
            self.__send_result = send_result

        def run(self):
            while not self.stopped:
                if not self.__msg_queue.empty():
                    self.in_progress = True
                    convert_function, config, incoming_data = self.__msg_queue.get(
                        True, 100)
                    converted_data = convert_function(config, incoming_data)
                    log.debug(converted_data)
                    self.__send_result(config, converted_data)
                    self.in_progress = False
                else:
                    sleep(.2)
Exemplo n.º 10
0
class HotWord(object):
    def __init__(self):
        self.received_lamp_state = None
        self.color_database = json.load(open('color.json'))
        self.client = Client(client_id='google_home')
        self.client.on_connect = self.on_connect
        self.client.connect('localhost', port=1883, keepalive=60)
        self._wait_for_lamp_state()
        self.client.loop_start()

    def _receive_lamp_state(self, client, userdata, message):
        print(message.payload)
        self.received_lamp_state = json.loads(message.payload.decode("utf-8"))

    def on_connect(self, client, userdata, flags, rc):
        client.message_callback_add('/lamp/changed', self._receive_lamp_state)
        client.subscribe('/lamp/changed', qos=1)

    def _wait_for_lamp_state(self):
        for i in range(10):
            if self.received_lamp_state:
                return
            self.client.loop(0.05)
        raise Exception("Timeout waiting for lamp state")

    def process_device_actions(self, event, device_id):
        if 'inputs' in event.args:
            for i in event.args['inputs']:
                if i['intent'] == 'action.devices.EXECUTE':
                    for c in i['payload']['commands']:
                        for device in c['devices']:
                            if device['id'] == device_id:
                                if 'execution' in c:
                                    for e in c['execution']:
                                        if 'params' in e:
                                            yield e['command'], e['params']
                                        else:
                                            yield e['command'], None

    def process_event(self, event, device_id):
        """Pretty prints events.

        Prints all events that occur with two spaces between each new
        conversation and a single space between turns of a conversation.

        Args:
            event(event.Event): The current event to process.
            device_id(str): The device ID of the new instance.
        """
        if event.type == EventType.ON_CONVERSATION_TURN_STARTED:
            print()

        print(event)

        if (event.type == EventType.ON_CONVERSATION_TURN_FINISHED
                and event.args and not event.args['with_follow_on_turn']):
            print()
        if event.type == EventType.ON_DEVICE_ACTION:
            for command, params in self.process_device_actions(
                    event, device_id):
                print('Do command', command, 'with params', str(params))
                if command == "action.devices.commands.OnOff":
                    if params['on']:
                        self.received_lamp_state['client'] = 'google_home'
                        self.received_lamp_state['on'] = True
                        print('Turning the LED on.')
                    else:
                        self.received_lamp_state['client'] = 'google_home'
                        self.received_lamp_state['on'] = False
                        print('Turning the LED off.')
                    self.client.publish('/lamp/set_config',
                                        json.dumps(self.received_lamp_state),
                                        qos=1)
                if command == "action.devices.commands.ColorAbsolute":
                    if params['color']:
                        print("hello it is me color")
                        color = params['color'].get('name')
                        hue = self.color_database[color]['hue']
                        saturation = self.color_database[color]['saturation']
                        self.received_lamp_state['color']['h'] = round(hue, 2)
                        self.received_lamp_state['color']['s'] = round(
                            saturation, 2)
                        self.received_lamp_state['client'] = 'google_home'
                        self.client.publish('/lamp/set_config',
                                            json.dumps(
                                                self.received_lamp_state),
                                            qos=1)
                if command == "action.devices.commands.BrightnessAbsolute":
                    if params['brightness']:
                        print("hello")
                        brightness = (params['brightness']) / 100
                        print(brightness)
                        self.received_lamp_state['brightness'] = brightness
                        self.received_lamp_state['client'] = 'google_home'
                        self.client.publish('/lamp/set_config',
                                            json.dumps(
                                                self.received_lamp_state),
                                            qos=1)
                sleep(0.1)
                self.client.loop_stop()

    def register_device(self, project_id, credentials, device_model_id,
                        device_id):
        """Register the device if needed.

        Registers a new assistant device if an instance with the given id
        does not already exists for this model.

        Args:
           project_id(str): The project ID used to register device instance.
           credentials(google.oauth2.credentials.Credentials): The Google
                    OAuth2 credentials of the user to associate the device
                    instance with.
           device_model_id(str): The registered device model ID.
           device_id(str): The device ID of the new instance.
        """
        base_url = '/'.join(
            [DEVICE_API_URL, 'projects', project_id, 'devices'])
        device_url = '/'.join([base_url, device_id])
        session = google.auth.transport.requests.AuthorizedSession(credentials)
        r = session.get(device_url)
        print(device_url, r.status_code)
        if r.status_code == 404:
            print('Registering....')
            r = session.post(base_url,
                             data=json.dumps({
                                 'id': device_id,
                                 'model_id': device_model_id,
                                 'client_type': 'SDK_LIBRARY'
                             }))
            if r.status_code != 200:
                raise Exception('failed to register device: ' + r.text)
            print('\rDevice registered.')

    def main(self):
        parser = argparse.ArgumentParser(
            formatter_class=argparse.RawTextHelpFormatter)
        parser.add_argument('--credentials',
                            type=existing_file,
                            metavar='OAUTH2_CREDENTIALS_FILE',
                            default=os.path.join(
                                os.path.expanduser('~/.config'),
                                'google-oauthlib-tool', 'credentials.json'),
                            help='Path to store and read OAuth2 credentials')
        parser.add_argument('--device_model_id',
                            type=str,
                            metavar='DEVICE_MODEL_ID',
                            required=True,
                            help='The device model ID registered with Google')
        parser.add_argument(
            '--project_id',
            type=str,
            metavar='PROJECT_ID',
            required=False,
            help='The project ID used to register device instances.')
        parser.add_argument('-v',
                            '--version',
                            action='version',
                            version='%(prog)s ' + Assistant.__version_str__())

        args = parser.parse_args()
        with open(args.credentials, 'r') as f:
            credentials = google.oauth2.credentials.Credentials(token=None,
                                                                **json.load(f))

        with Assistant(credentials, args.device_model_id) as assistant:
            events = assistant.start()

            print('device_model_id:',
                  args.device_model_id + '\n' + 'device_id:',
                  assistant.device_id + '\n')

            if args.project_id:
                register_device(args.project_id, credentials,
                                args.device_model_id, assistant.device_id)

            for event in events:
                self.process_event(event, assistant.device_id)
Exemplo n.º 11
0
class mqtt_client_connect():
    def __init__(self,
                 broker="10.129.7.199",
                 port=1883,
                 username="******",
                 password="******",
                 client_id="12345"):
        self.broker = broker
        self.port = port
        self.username = username
        self.password = password
        self.payload = None
        self.client_id = client_id
        self.num = 1
        self.flag = 0
        while True:
            try:
                # self.mqttc=Client(clean_session=False,client_id="12345")
                self.mqttc = Client(client_id=self.client_id)
                self.mqttc.on_connect = self.on_connect
                self.mqttc.on_publish = self.on_publish
                self.mqttc.on_subscribe = self.on_subscribe
                self.mqttc.username_pw_set(self.username, self.password)
                self.mqttc.connect(self.broker, port=self.port)
                self.mqttc.loop_start()
                break
            except:
                print(
                    "mqtt_client_connect error: mqttc connect failed Please check Broker and Port...."
                )
                continue

    # ======================================================
    def on_connect(self, client, userdata, flags, rc):
        #rc为0 返回连接成功
        if rc == 0:
            self.flag = 1
            print("OnConnetc, rc: " + str(rc),
                  'successful  ' + str(client._username))
        else:
            self.flag = 0
            print("OnConnetc, rc: " + str(rc),
                  'unsuccessful  ' + str(client._username))

    def on_disconnect(self, client, userdata, rc):
        if rc != 0:
            self.flag = 0
            print("Unexpected MQTT disconnection. Will auto-reconnect")

    def on_publish(self, client, userdata, mid):
        print("OnPublish, mid: " + str(mid) + " " + str(client._username))

    def on_subscribe(self, client, userdata, mid, granted_qos):
        print("Subscribed: " + str(mid) + "   " + str(granted_qos) +
              "  订阅成功 " + str(client._username))
        self.mqttc.on_message = self.on_message

    def on_message(self, client, userdata, msg):
        strcurtime = time.strftime("%Y-%m-%d %H:%M:%S")
        print(strcurtime + ": " + msg.topic + " " + str(msg.qos) + " " +
              str(msg.payload))
Exemplo n.º 12
0
def on_Soc(client, userdata, message):
    try:
        val = json.loads(message.payload)
        print("SOC = %s" % val["value"])
    except:
        print("SOC Exception")
        reconnect()


def on_connect(client, userdata, rc, *args):
    client.subscribe(bat_topic)
    client.subscribe(soc_topic)
    #client.subscribe("N/%s/+/+/ProductId" % portal_id)
    #client.subscribe("N/%s/#" % portal_id)


client = Client("P1")
client.tls_set(cert_reqs=ssl.CERT_NONE)
client.tls_insecure_set(True)
client.username_pw_set(username, password=password)
client.connect(mqtt_broker, port=8883)
client.on_connect = on_connect
client.on_message = on_message
client.message_callback_add(bat_topic, on_BatVoltage)
client.message_callback_add(soc_topic, on_Soc)

client.loop_start()

while True:
    sleep(1)
Exemplo n.º 13
0

def on_connect(client, userdata, flags, rc):
    client.loop_start()  # start the loop
    client.subscribe("pazienti/bloodPressure", qos=1)
    client.subscribe("pazienti/heartbeat", qos=1)
    client.subscribe("pazienti/bloodOxygen", qos=1)
    client.subscribe("pazienti/movement", qos=1)
    time.sleep(1)


############
def on_message(mqttClient, userdata, message):
    # print("message received from sensor", str(message.payload.decode("utf-8")))
    send(json.loads(message.payload))
    # time.sleep(0.1)


client = Client("MqttSubscriber", clean_session=False)
broker_address = "broker"
client.on_message = on_message  # attach function to callback
client.on_connect = on_connect
client.connect(broker_address, keepalive=320)  # connect to broker
client.loop_start()  # start the loop
time.sleep(3)

if __name__ == "__main__":
    # broker_address = "localhost"

    mqttSubscriber.run(host='0.0.0.0', port=8080)
Exemplo n.º 14
0
class MqttClient:
    """A wrapper around the paho mqtt client specifically for device automation bus

    This wrapper is mean to handle connect/disconnect as well as sending and
    receiving messages.
    Clients can register handlers to be called when certain messages are received.
    Clients also use this class to publish messages onto the bus.
    """
    class MqttClientEvents:
        """Container for MqttClient thread events"""
        def __init__(self):
            self.connected_event = Event()
            self.disconnected_event = Event()
            self.apps_discovered_event = Event()
            self.capabilities_discovered_event = Event()

    def __init__(self, host, port):
        self.host = host
        self.port = port
        self.mqtt_client_events = MqttClient.MqttClientEvents()
        self.thread = None
        self.client = Client(userdata=self.mqtt_client_events)

        self.app_registry = {}
        self.app_registry_timer = None
        self.app_registry_timer_lock = Lock()

        self.capabilities_registry = {}
        self.capabilities_registry_timer = None
        self.capabilities_registry_timer_lock = Lock()

        self.topic_handlers = [{
            'topic': 'apps/+',
            'regex': 'apps/[^/]*',
            'handler': self.on_app_message
        }, {
            'topic': 'platform/+',
            'regex': 'platform/[^/]*',
            'handler': self.on_app_message
        }, {
            'topic': 'platform/telemetry/monitor/#',
            'regex': 'platform/telemetry/monitor/.*',
            'handler': self.on_monitor_message
        }]
        self.logger = logging.getLogger(__name__)

    def on_connect(self, client, mqtt_client_events, flags, rc):
        """Set the connected state and subscribe to existing known topics."""
        del mqtt_client_events, flags, rc  # Delete unused parameters to prevent warnings.
        for topic_handler in self.topic_handlers:
            client.subscribe(topic_handler["topic"])
        self.mqtt_client_events.connected_event.set()
        self.mqtt_client_events.disconnected_event.clear()

    def on_disconnect(self, client, user_data, rc):
        """Set the disconnected state."""
        del client, user_data, rc  # Delete unused parameters to prevent warnings.
        self.mqtt_client_events.connected_event.clear()
        self.mqtt_client_events.disconnected_event.set()

    def on_message(self, client, user_data, packet):
        """The main message dispatcher

        This function is called for all messages on all registered topics.
        It handles dispatching messages to the registered handlers.
        """
        del client, user_data  # Delete unused parameters to prevent warnings.
        try:
            if packet:
                self.logger.info("topic: " + packet.topic)
                # Message payload can come in as a char array instead of string.
                # Normalize it to a string for easier access by handlers.
                if type(packet.payload) is str:
                    message = packet.payload
                else:
                    message = packet.payload.decode("utf-8")
                self.logger.info("message: " + message)

                # Since this function receives messages for all topics, use the specified regex
                # to call the correct handler(s).
                for topic_handler in self.topic_handlers:
                    if re.fullmatch(topic_handler["regex"], packet.topic):
                        topic_handler["handler"](packet.topic, message)
        # Since paho eats all the exceptions, catch them here beforehand
        # and make sure a stack trace is printed.
        except Exception:
            traceback.print_exc()
            raise

    def on_app_message(self, topic, message):
        """Local handler for handling registration of media apps.

        This function receives a message for each registered media application.
        Since these messages are published as retained, normally this will be
        called with all the messages in on batch.
        To know that we have received all of the initial batch, use a simple timeout
        to measure how long it has been since the last message. When enough time goes
        by, allow callers to get the list of apps from the registry.
        """
        app = json.loads(message)
        app["app_id"] = os.path.basename(topic)
        self.app_registry.update({app["name"]: app})
        self.app_registry_timer_lock.acquire(True)
        if self.app_registry_timer:
            self.app_registry_timer.cancel()
            while self.app_registry_timer.is_alive():
                pass
        self.app_registry_timer = Timer(
            1.0, self.mqtt_client_events.apps_discovered_event.set).start()
        self.app_registry_timer_lock.release()

    def on_platform_message(self, topic, message):
        """Local handler for handling platform messages

        This function receives a message for each registered platform capability.
        """
        capability = json.loads(message)
        capability_id = os.path.basename(topic)
        self.capabilities_registry.update({capability_id: capability})

        self.capabilities_registry_timer_lock.acquire(True)
        if self.capabilities_registry_timer:
            self.capabilities_registry_timer.cancel()
            while self.capabilities_registry_timer.is_alive():
                pass
        self.capabilities_registry_timer = Timer(
            1.0,
            self.mqtt_client_events.capabilities_discovered_event.set).start()
        self.capabilities_registry_timer_lock.release()

    def on_monitor_message(self, topic, message):
        """Local handler for handling process monitor messages

        """
        message = json.loads(message)

    def get_discovered_apps(self):
        """Method to get the discovered apps.

        TODO: Get the app registry out of here into it's own class.
        """
        self.mqtt_client_events.apps_discovered_event.wait()
        self.app_registry_timer_lock.acquire(True)
        discovered_apps = self.app_registry.copy().values()
        self.app_registry_timer_lock.release()
        return discovered_apps

    def get_discovered_capabilities(self):
        """Method to get the discovered platform capabilities.

        TODO: Get the registry out of here into it's own class.
        """
        self.mqtt_client_events.capabilities_discovered_event.wait()
        self.capabilities_registry_timer_lock.acquire(True)
        discovered_capabilities = self.capabilities_registry.copy().values()
        self.capabilities_registry_timer_lock.release()
        return discovered_capabilities

    def _start(self):
        """Private start method that does th actual work of starting the client in a new thread."""
        self.client.enable_logger(self.logger)
        self.client.on_connect = self.on_connect
        self.client.on_message = self.on_message
        self.client.on_disconnect = self.on_disconnect
        self.client.connect(self.host, self.port)
        self.client.loop_start()
        self.mqtt_client_events.connected_event.wait(15.0)
        if not self.mqtt_client_events.connected_event.is_set():
            raise Exception("Connect timed out after 15 seconds.")

        self.mqtt_client_events.disconnected_event.wait()  # yes, forever

    def start(self):
        """Public start method used by callers to start the client."""
        self.thread = Thread(target=self._start)
        self.thread.start()

    def stop(self):
        """Stop method used to shutdown the client."""
        for topic_handler in self.topic_handlers:
            self.client.unsubscribe(topic_handler["topic"])
        self.client.disconnect()
        self.mqtt_client_events.apps_discovered_event.set()

    def publish(self, topic, message):
        """Publish a message to the specified topic."""
        self.logger.info("publish: Sending '{}' to topic '{}'".format(
            message, topic))
        self.client.publish(topic, message)

    def subscribe(self, topic, handler, regex=None):
        """Add a handler for a particular topic

        The handler is a function that will be called when a message is received on the specified topic.
        An optional regex can be provided to filter the topic even more.
        """
        if not regex:
            regex = topic
        self.topic_handlers.append({
            'topic': topic,
            'handler': handler,
            'regex': regex
        })
        self.client.subscribe(topic)
Exemplo n.º 15
0
def subscribe_and_callback(
    callback: Callable[[MQTTMessage], None],
    topics: str | list[str],
    hostname: str = leader_hostname,
    last_will: dict = None,
    job_name: str = None,
    allow_retained: bool = True,
    **mqtt_kwargs,
) -> Client:
    """
    Creates a new thread, wrapping around paho's subscribe.callback. Callbacks only accept a single parameter, message.

    Parameters
    -------------
    last_will: dict
        a dictionary describing the last will details: topic, qos, retain, msg.
    job_name:
        Optional: provide the job name, and logging will include it.
    allow_retained: bool
        if True, all messages are allowed, including messages that the broker has retained. Note
        that client can fire a msg with retain=True, but because the broker is serving it to a
        subscriber "fresh", it will have retain=False on the client side. More here:
        https://github.com/eclipse/paho.mqtt.python/blob/master/src/paho/mqtt/client.py#L364
    """
    assert callable(
        callback
    ), "callback should be callable - do you need to change the order of arguments?"

    def on_connect(client: Client, userdata: dict, *args):
        client.subscribe(userdata["topics"])

    def wrap_callback(actual_callback: Callable) -> Callable:
        def _callback(client: Client, userdata: dict, message):
            try:

                if not allow_retained and message.retain:
                    return

                return actual_callback(message)

            except Exception as e:
                from pioreactor.logging import create_logger

                logger = create_logger(userdata.get("job_name", "pioreactor"))
                logger.error(e, exc_info=True)
                raise e

        return _callback

    topics = [topics] if isinstance(topics, str) else topics
    userdata = {
        "topics": [(topic, mqtt_kwargs.pop("qos", 0)) for topic in topics],
        "job_name": job_name,
    }

    client = Client(userdata=userdata)
    client.on_connect = on_connect
    client.on_message = wrap_callback(callback)

    if last_will is not None:
        client.will_set(**last_will)

    client.connect(leader_hostname, **mqtt_kwargs)
    client.loop_start()

    return client
Exemplo n.º 16
0
class MQTTConnection():
    client: Client
    _instance = None

    @classmethod
    def get_instance(cls) -> "MQTTConnection":
        if cls._instance is None:
            cls._instance = MQTTConnection()

        return cls._instance

    def __init__(self):
        self.client = Client(
            "pai" + os.urandom(8).hex(),
            protocol=protocol_map.get(str(cfg.MQTT_PROTOCOL), MQTTv311),
            transport=cfg.MQTT_TRANSPORT,
        )
        self._last_pai_status = "unknown"
        self.pai_status_topic = "{}/{}/{}".format(cfg.MQTT_BASE_TOPIC,
                                                  cfg.MQTT_INTERFACE_TOPIC,
                                                  "pai_status")
        self.availability_topic = "{}/{}/{}".format(cfg.MQTT_BASE_TOPIC,
                                                    cfg.MQTT_INTERFACE_TOPIC,
                                                    "availability")
        self.client.on_connect = self._on_connect_cb
        self.client.on_disconnect = self._on_disconnect_cb
        self.state = ConnectionState.NEW
        # self.client.enable_logger(logger)

        # self.client.on_subscribe = lambda client, userdata, mid, granted_qos: logger.debug("Subscribed: %s" %(mid))
        # self.client.on_message = lambda client, userdata, message: logger.debug("Message received: %s" % str(message))
        # self.client.on_publish = lambda client, userdata, mid: logger.debug("Message published: %s" % str(mid))

        ps.subscribe(self.on_run_state_change, "run-state")

        self.registrars = []

        if cfg.MQTT_USERNAME is not None and cfg.MQTT_PASSWORD is not None:
            self.client.username_pw_set(username=cfg.MQTT_USERNAME,
                                        password=cfg.MQTT_PASSWORD)

        if cfg.MQTT_TLS_CERT_PATH is not None:
            self.client.tls_set(
                ca_certs=cfg.MQTT_TLS_CERT_PATH,
                certfile=None,
                keyfile=None,
                cert_reqs=ssl.CERT_REQUIRED,
                tls_version=ssl.PROTOCOL_TLSv1_2,
                ciphers=None,
            )
            self.client.tls_insecure_set(False)

        self.client.will_set(self.availability_topic,
                             "offline",
                             0,
                             retain=True)

        self.client.on_log = self.on_client_log

    def on_client_log(self, client, userdata, level, buf):
        level_std = LOGGING_LEVEL[level]
        exc_info = None

        type_, exc, trace = sys.exc_info()
        if exc:  # Can be (socket.error, OSError, WebsocketConnectionError, ...)
            if hasattr(exc, "errno"):
                exc_msg = f"{os.strerror(exc.errno)}({exc.errno})"
                if exc.errno in [22, 49]:
                    level_std = logging.ERROR
                    buf = f"{buf}: Please check MQTT connection settings. Especially MQTT_BIND_ADDRESS and MQTT_BIND_PORT"
            else:
                exc_msg = str(exc)

            buf = f"{buf}: {exc_msg}"
            if "Connection failed" in buf:
                level_std = logging.WARNING

        if level_std > logging.DEBUG:
            logger.log(level_std, buf, exc_info=exc_info)

    def on_run_state_change(self, state: RunState):
        v = RUN_STATE_2_PAYLOAD.get(state, "unknown")
        self._report_pai_status(v)

    def start(self):
        if self.state == ConnectionState.NEW:
            self.client.loop_start()

            # TODO: Some initial connection retry mechanism required
            try:
                self.client.connect_async(
                    host=cfg.MQTT_HOST,
                    port=cfg.MQTT_PORT,
                    keepalive=cfg.MQTT_KEEPALIVE,
                    bind_address=cfg.MQTT_BIND_ADDRESS,
                    bind_port=cfg.MQTT_BIND_PORT,
                )

                self.state = ConnectionState.CONNECTING

                logger.info("MQTT loop started")
            except socket.gaierror:
                logger.exception("Failed to connect to MQTT (%s:%d)",
                                 cfg.MQTT_HOST, cfg.MQTT_PORT)

    def stop(self):
        if self.state in [
                ConnectionState.CONNECTING, ConnectionState.CONNECTED
        ]:
            self.disconnect()
            self.client.loop_stop()
            logger.info("MQTT loop stopped")

    def publish(self, topic, payload=None, *args, **kwargs):
        logger.debug("MQTT: {}={}".format(topic, payload))

        self.client.publish(topic, payload, *args, **kwargs)

    def _call_registars(self, method, *args, **kwargs):
        for r in self.registrars:
            try:
                if hasattr(r, method) and isinstance(getattr(r, method),
                                                     typing.Callable):
                    getattr(r, method)(*args, **kwargs)
            except:
                logger.exception('Failed to call "%s" on "%s"', method,
                                 r.__class__.__name__)

    def register(self, cls):
        self.registrars.append(cls)

        self.start()

    def unregister(self, cls):
        self.registrars.remove(cls)

        if len(self.registrars) == 0:
            self.stop()

    @property
    def connected(self):
        return self.state == ConnectionState.CONNECTED

    def _report_pai_status(self, status):
        self._last_pai_status = status
        self.publish(self.pai_status_topic,
                     status,
                     qos=cfg.MQTT_QOS,
                     retain=True)
        self.publish(
            self.availability_topic,
            "online" if status in ["online", "paused"] else "offline",
            qos=cfg.MQTT_QOS,
            retain=True,
        )

    def _on_connect_cb(self, client, userdata, flags, result, properties=None):
        # called on Thread-6
        if result == MQTT_ERR_SUCCESS:
            logger.info("MQTT Broker Connected")
            self.state = ConnectionState.CONNECTED
            self._report_pai_status(self._last_pai_status)
            self._call_registars("on_connect", client, userdata, flags, result)
        else:
            logger.error(
                f"Failed to connect to MQTT: {connack_string(result)} ({result})"
            )

    def _on_disconnect_cb(self, client, userdata, rc):
        # called on Thread-6
        if rc == MQTT_ERR_SUCCESS:
            logger.info("MQTT Broker Disconnected")
        else:
            logger.error(f"MQTT Broker unexpectedly disconnected. Code: {rc}")

        self.state = ConnectionState.NEW
        self._call_registars("on_disconnect", client, userdata, rc)

    def disconnect(self, reasoncode=None, properties=None):
        self.state = ConnectionState.DISCONNECTING
        self._report_pai_status("offline")
        self.client.disconnect()

    def message_callback_add(self, *args, **kwargs):
        self.client.message_callback_add(*args, **kwargs)

    def subscribe(self, *args, **kwargs):
        self.client.subscribe(*args, **kwargs)
Exemplo n.º 17
0
class Bridge(object):
    WEBSOCKETS = "websocket"
    SSE = "sse"

    def __init__(self, args, ioloop, dynamic_subscriptions):
        """ parse config values and setup Routes.
        """
        self.mqtt_topics = []
        try:
            self.mqtt_host = args["mqtt-to-server"]["broker"]["host"]
            self.mqtt_port = args["mqtt-to-server"]["broker"]["port"]
            self.bridge_port = args["server-to-client"]["port"]
            self.stream_protocol = args["server-to-client"]["protocol"]
            logger.info("Using protocol %s" % self.stream_protocol.lower())
            if self.stream_protocol.lower(
            ) != "websocket" and self.stream_protocol.lower() != "sse":
                raise ConfigException("Invalid protocol")
            self.dynamic_subscriptions = dynamic_subscriptions
            if not self.dynamic_subscriptions:
                self.mqtt_topics = args["mqtt-to-server"]["topics"]
        except KeyError as e:
            raise ConfigException("Error when accessing field", e) from e
        logger.info("connecting to mqtt")
        self.topic_dict = {}
        self.ioloop = ioloop
        self.mqtt_client = Client()
        self.mqtt_client.on_message = self.on_mqtt_message
        self.mqtt_client.on_connect = self.on_mqtt_connect
        self.mqtt_client.connect_async(host=self.mqtt_host,
                                       port=self.mqtt_port)
        self.mqtt_client.loop_start()
        self._app = web.Application([
            (r'.*', WebsocketHandler if self.stream_protocol == "websocket"
             else ServeSideEventsHandler, dict(parent=self)),
        ],
                                    debug=True)

    def get_app(self):
        return self._app

    def get_port(self):
        return self.bridge_port

    async def parse_req_path(self, req_path):
        candidate_path = req_path
        if len(candidate_path) is 1 and candidate_path[0] is "/":
            return "#"
        if candidate_path[len(req_path) - 1] is "/":
            candidate_path = candidate_path + "#"
        if candidate_path[0] == "/":
            candidate_path = candidate_path[1:]
        return candidate_path

    def on_mqtt_message(self, client, userdata, message):
        logger.debug("received message on topic %s" % message.topic)

    async def socket_write_message(self, socket, message):
        try:
            await socket.write_message(json.dumps(message))
        except Exception as e:
            logger.error(e)
            try:
                await socket.write_message(message)
            except Exception as e:
                logger.error(e)

    def append_dynamic(self, topic):
        logger.info("adding dynamic subscription for %s " % topic)
        self.message_callback_add_with_sub_topic(topic, dynamic=True)

    def remove_dynamic(self, topic):
        logger.info("removing dynamic subscription for %s " % topic)
        self.topic_dict.pop(topic, None)
        self.mqtt_client.unsubscribe(topic)

    def message_callback_add_with_sub_topic(self, sub_topic, dynamic):
        logger.info("adding callback for mqtt topic: %s" % sub_topic)

        def message_callback(client, userdata, message):
            logger.debug(
                "Recieved Mqtt Message on %s as result of subscription on %s" %
                (message.topic, sub_topic))
            if sub_topic is not message.topic:
                if message.topic not in self.topic_dict[sub_topic]["matches"]:
                    self.topic_dict[sub_topic]["matches"].append(message.topic)
            for topic in self.topic_dict:
                if topic == message.topic:
                    for socket in self.topic_dict[topic]["sockets"]:
                        logger.debug("sending to socket:")
                        logger.debug(socket)
                        self.ioloop.add_callback(
                            self.socket_write_message,
                            socket=socket,
                            message={
                                "topic": message.topic,
                                "payload": message.payload.decode('utf-8')
                            })
                elif message.topic in self.topic_dict[topic]["matches"]:
                    for socket in self.topic_dict[topic]["sockets"]:
                        logger.debug("sending to socket:")
                        logger.debug(socket)
                        self.ioloop.add_callback(
                            self.socket_write_message,
                            socket=socket,
                            message={
                                "topic": message.topic,
                                "payload": message.payload.decode('utf-8')
                            })

        if sub_topic not in self.topic_dict:
            self.mqtt_client.message_callback_add(sub_topic, message_callback)
            self.topic_dict[sub_topic] = {
                "matches": [],
                "sockets": [],
                "dynamic": dynamic
            }
            self.mqtt_client.subscribe(sub_topic)

    def on_mqtt_connect(self, client, userdata, flags, rc):
        logger.info("mqtt connected to broker %s:%s" %
                    (self.mqtt_host, str(self.mqtt_port)))
        for topic in self.mqtt_topics:
            self.message_callback_add_with_sub_topic(topic, dynamic=False)

    async def mqtt_disconnect(self):
        t = Thread(target=self.mqtt_client.disconnect, daemon=True)
        t.start()
        self.mqtt_client.loop_stop()
Exemplo n.º 18
0
class MqttClient:
    def __init__(self) -> None:
        self.client = Client()
        self.client.on_connect = self.on_connect
        self.client.on_message = self.on_message
        self.client.on_publish = self.on_publish
        self.client.on_disconnect = self.on_disconnect
        self.client.on_unsubscribe = self.on_unsubscribe
        self.client.on_subscribe = self.on_subscribe

    def connect(self,
                host: str,
                port: int,
                user: str,
                passwd: str,
                keepalive: int = 600):
        """connect server"""

        # set user passwd
        self.client.username_pw_set(user, passwd)
        # connect
        self.client.connect(host=host, port=port, keepalive=keepalive)

    def loop_start(self):
        """loop start"""

        self.client.loop_start()

    def loop_forever(self):
        """loop forever"""

        self.client.loop_forever()

    def subscribe(self, topic: str):
        """subscribe topic"""

        self.client.subscribe(topic)

    def publish(self, topic, msg, qos=0, retain=False, properties=None):
        """publish msg"""

        payload = json.dumps(msg, ensure_ascii=False)
        self.client.publish(topic=topic,
                            payload=payload,
                            qos=qos,
                            retain=retain,
                            properties=properties)

    def add_callback(self, topic, callback):
        """message_callback_add"""

        self.client.message_callback_add(topic, callback)

    def on_connect(self, client, userdata, flags, rc):
        """连接事件"""

        logger.info('on_connect'.center(40, '*'))
        logger.info(f'Connected with result code: {rc}')

    def on_disconnect(self, client, userdata, rc):
        """断开连接事件"""

        # logger.info('on_disconnect'.center(40, '*'))
        # logger.info('Unexpected disconnected rc = {rc}')
        pass

    def on_subscribe(self, client, userdata, mid, granted_qos):
        """订阅事件"""

        logger.info('on_subscribe'.center(40, '*'))
        logger.info('on_subscribe: qos = {granted_qos}')

    def on_unsubscribe(self, client, userdata, mid):
        """取消订阅事件"""

        # logger.info('on_unsubscribe'.center(40, '*'))
        # logger.info('on_unsubscribe: qos = {granted_qos}')
        pass

    def on_publish(self, client, userdata, mid):
        """发布消息事件"""

        logger.info('on_publish'.center(40, '*'))
        logger.info(f'on_publish: mid = {mid}')

    def on_message(self, client, userdata, msg):
        """获得消息事件,触发动作,匹配不到 message_callback_add 时使用这个"""

        logger.info('on_message'.center(40, '*'))
        payload = msg.payload.decode('utf-8')
        logger.info(f'on_message topic: {msg.topic}')
        logger.info(payload)
Exemplo n.º 19
0
class MqttApplication(Application):
    """
    An abstract base Application for running some type of MQTT client-based Application.
    """

    def __init__(self, broker,
                hostname=None,
                hostport=1883,
                username=None,
                password=None,
                keepalive=60,
                **kwargs):
        super(MqttApplication, self).__init__(broker=broker, **kwargs)
        self._client = MqttClient()
        self._client.on_connect = \
                lambda mqtt_client, obj, rc: self._on_connect(mqtt_client, obj, rc)
        self._client.on_disconnect = \
                lambda mqtt_client, obj, rc: self._on_disconnect(mqtt_client, obj, rc)
        self._client.on_publish = \
                lambda mqtt_client, obj, mid: self._on_publish(mqtt_client, obj, mid)
        self._client.on_subscribe = \
                lambda mqtt_client, userdata, mid, qos: self._on_subscribe(mqtt_client, mid, qos)
        self._client.on_message = \
                lambda mqtt_client, userdata, msg: self._on_message(mqtt_client, msg.payload, msg.topic, msg.qos, msg.retain)

        self._hostname = hostname
        self._hostport = hostport
        self._username = username
        self._password = password
        self._keepalive = keepalive

        self._is_connected = False

    @property
    def is_connected(self):
        return self._is_connected

    # Your API for pub-sub via MQTT:

    def mqtt_publish(self, raw_data, topic, **kwargs):
        """
        Publishes the given data to the specified topic.
        :param raw_data:
        :type raw_data: str
        :param topic:
        :param kwargs: e.g. qos, retain; passed to self._client.publish
        :return: err_code, msg_id
        """
        return self._client.publish(topic, raw_data, **kwargs)

    def mqtt_subscribe(self, topic, **kwargs):
        """
        Subscribes to the specified topic.
        :param topic:
        :param kwargs: e.g. qos; passed to self._client.publish
        :return: err_code, msg_id
        """
        return self._client.subscribe(topic, **kwargs)

    # To make use of either publish or subscribe, make sure to implement these!

    def _on_publish(self, mqtt_client, obj, mid):
        log.debug("MQTT app %s published msg %d: %s" % (self.name, mid, obj))

    def _on_subscribe(self, mqtt_client, mid, qos):
        log.debug("MQTT app %s subscribe response msg %d: qos=%s" % (self.name, mid, qos))

    def _on_message(self, mqtt_client, payload, topic, qos, retain):
        """Called when a message arrives on a topic we subscribed to."""
        log.debug("MQTT app %s received message on topic %s: %s" % (self.name, topic, payload))

    # You may want to override these functions in your concrete Application, but make sure to call super()!

    def _on_connect(self, mqtt_client, obj, rc):
        log.debug("MQTT app %s connected: %s" % (self.name, str(rc)))
        # need to start AFTER connecting, which is async!
        self._is_connected = True

    def _on_disconnect(self, mqtt_client, obj, rc):
        # sink will try reconnecting once EventReporter queries if it's available.
        self._is_connected = False
        log.debug("MQTT app %s disconnected: %s" % (self.name, str(rc)))

    def _try_connect(self):
        if self._username is not None and self._password is not None:
            self._client.username_pw_set(self._username, self._password)
        try:
            # NOTE: this is an async connection!
            self._client.connect(self._hostname, self._hostport, self._keepalive)
            self._client.loop_start()
        except socket.gaierror:
            return False
        return True

    def on_start(self):
        super(MqttApplication, self).on_start()
        return self._try_connect()
Exemplo n.º 20
0
class LampiApp(App):
    _updated = False
    _updatingUI = False
    _hue = NumericProperty()
    _saturation = NumericProperty()
    _brightness = NumericProperty()
    lamp_is_on = BooleanProperty()

    def _get_hue(self):
        return self._hue

    def _set_hue(self, value):
        self._hue = value

    def _get_saturation(self):
        return self._saturation

    def _set_saturation(self, value):
        self._saturation = value

    def _get_brightness(self):
        return self._brightness

    def _set_brightness(self, value):
        self._brightness = value

    hue = AliasProperty(_get_hue, _set_hue, bind=['_hue'])
    saturation = AliasProperty(_get_saturation,
                               _set_saturation,
                               bind=['_saturation'])
    brightness = AliasProperty(_get_brightness,
                               _set_brightness,
                               bind=['_brightness'])
    gpio17_pressed = BooleanProperty(False)
    device_associated = BooleanProperty(True)

    def on_start(self):
        self.keen = KeenEventRecorder(PROJECT_ID, WRITE_KEY, get_device_id())
        self._publish_clock = None
        self.mqtt_broker_bridged = False
        self._associated = True
        self.association_code = None
        self.mqtt = Client(client_id=MQTT_CLIENT_ID)
        self.mqtt.will_set(client_state_topic(MQTT_CLIENT_ID),
                           "0",
                           qos=2,
                           retain=True)
        self.mqtt.on_connect = self.on_connect
        self.mqtt.connect(MQTT_BROKER_HOST,
                          port=MQTT_BROKER_PORT,
                          keepalive=MQTT_BROKER_KEEP_ALIVE_SECS)
        self.mqtt.loop_start()
        self.set_up_GPIO_and_device_status_popup()
        self.associated_status_popup = self._build_associated_status_popup()
        self.associated_status_popup.bind(on_open=self.update_popup_associated)
        Clock.schedule_interval(self._poll_associated, 0.1)

    def _build_associated_status_popup(self):
        return Popup(title='Associate your Lamp',
                     content=Label(text='Msg here', font_size='30sp'),
                     size_hint=(1, 1),
                     auto_dismiss=False)

    def on_hue(self, instance, value):
        if self._updatingUI:
            return
        evt = self._create_event_record('hue-slider', value)
        self.keen.record_event('ui', evt)
        if self._publish_clock is None:
            self._publish_clock = Clock.schedule_once(
                lambda dt: self._update_leds(), 0.01)

    def on_saturation(self, instance, value):
        if self._updatingUI:
            return
        evt = self._create_event_record('saturation-slider', value)
        self.keen.record_event('ui', evt)
        if self._publish_clock is None:
            self._publish_clock = Clock.schedule_once(
                lambda dt: self._update_leds(), 0.01)

    def on_brightness(self, instance, value):
        if self._updatingUI:
            return
        evt = self._create_event_record('brightness-slider', value)
        self.keen.record_event('ui', evt)
        if self._publish_clock is None:
            self._publish_clock = Clock.schedule_once(
                lambda dt: self._update_leds(), 0.01)

    def on_lamp_is_on(self, instance, value):
        if self._updatingUI:
            return
        evt = self._create_event_record('power', value)
        self.keen.record_event('ui', evt)
        if self._publish_clock is None:
            self._publish_clock = Clock.schedule_once(
                lambda dt: self._update_leds(), 0.01)

    def _create_event_record(self, element, value):
        return {'element': {'id': element, 'value': value}}

    def on_connect(self, client, userdata, flags, rc):
        self.mqtt.publish(client_state_topic(MQTT_CLIENT_ID),
                          "1",
                          qos=2,
                          retain=True)
        self.mqtt.message_callback_add(TOPIC_LAMP_CHANGE_NOTIFICATION,
                                       self.receive_new_lamp_state)
        self.mqtt.message_callback_add(broker_bridge_connection_topic(),
                                       self.receive_bridge_connection_status)
        self.mqtt.message_callback_add(TOPIC_LAMP_ASSOCIATED,
                                       self.receive_associated)
        self.mqtt.subscribe(broker_bridge_connection_topic(), qos=1)
        self.mqtt.subscribe(TOPIC_LAMP_CHANGE_NOTIFICATION, qos=1)
        self.mqtt.subscribe(TOPIC_LAMP_ASSOCIATED, qos=2)

    def _poll_associated(self, dt):
        # this polling loop allows us to synchronize changes from the
        #  MQTT callbacks (which happen in a different thread) to the
        #  Kivy UI
        self.device_associated = self._associated

    def receive_associated(self, client, userdata, message):
        # this is called in MQTT event loop thread
        new_associated = json.loads(message.payload)
        if self._associated != new_associated['associated']:
            if not new_associated['associated']:
                self.association_code = new_associated['code']
            else:
                self.association_code = None
            self._associated = new_associated['associated']

    def on_device_associated(self, instance, value):
        if value:
            self.associated_status_popup.dismiss()
        else:
            self.associated_status_popup.open()

    def update_popup_associated(self, instance):
        code = self.association_code[0:6]
        instance.content.text = ("Please use the\n"
                                 "following code\n"
                                 "to associate\n"
                                 "your device\n"
                                 "on the Web\n{}".format(code))

    def receive_bridge_connection_status(self, client, userdata, message):
        # monitor if the MQTT bridge to our cloud broker is up
        if message.payload == "1":
            self.mqtt_broker_bridged = True
        else:
            self.mqtt_broker_bridged = False

    def receive_new_lamp_state(self, client, userdata, message):
        new_state = json.loads(message.payload)
        Clock.schedule_once(lambda dt: self._update_ui(new_state), 0.01)

    def _update_ui(self, new_state):
        if self._updated and new_state['client'] == MQTT_CLIENT_ID:
            # ignore updates generated by this client, except the first to
            #   make sure the UI is syncrhonized with the lamp_service
            return
        self._updatingUI = True
        try:
            if 'color' in new_state:
                self.hue = new_state['color']['h']
                self.saturation = new_state['color']['s']
            if 'brightness' in new_state:
                self.brightness = new_state['brightness']
            if 'on' in new_state:
                self.lamp_is_on = new_state['on']
        finally:
            self._updatingUI = False

        self._updated = True

    def _update_leds(self):
        msg = {
            'color': {
                'h': self._hue,
                's': self._saturation
            },
            'brightness': self._brightness,
            'on': self.lamp_is_on,
            'client': MQTT_CLIENT_ID
        }
        self.mqtt.publish(TOPIC_SET_LAMP_CONFIG, json.dumps(msg), qos=1)
        self._publish_clock = None

    def set_up_GPIO_and_device_status_popup(self):
        self.pi = pigpio.pi()
        self.pi.set_mode(17, pigpio.INPUT)
        self.pi.set_pull_up_down(17, pigpio.PUD_UP)
        Clock.schedule_interval(self._poll_GPIO, 0.05)
        self.network_status_popup = self._build_network_status_popup()
        self.network_status_popup.bind(on_open=self.update_device_status_popup)

    def _build_network_status_popup(self):
        return Popup(title='Device Status',
                     content=Label(text='IP ADDRESS WILL GO HERE'),
                     size_hint=(1, 1),
                     auto_dismiss=False)

    def update_device_status_popup(self, instance):
        interface = "wlan0"
        ipaddr = lampi_util.get_ip_address(interface)
        deviceid = lampi_util.get_device_id()
        msg = ("Version: {}\n"
               "{}: {}\n"
               "DeviceID: {}\n"
               "Broker Bridged: {}\n"
               "threaded").format(LAMPI_APP_VERSION, interface, ipaddr,
                                  deviceid, self.mqtt_broker_bridged)
        instance.content.text = msg

    def on_gpio17_pressed(self, instance, value):
        if value:
            self.network_status_popup.open()
        else:
            self.network_status_popup.dismiss()

    def _poll_GPIO(self, dt):
        # GPIO17 is the rightmost button when looking front of LAMPI
        self.gpio17_pressed = not self.pi.read(17)
Exemplo n.º 21
0
class MqttClientConnector():
    '''
    this class is to configure the MQTT connection. Define the method of publish and subscribe.
    and define what to when the message is arrived or after publishing and subscribing successfully.
    :param host: the MQTT broker we want to use
    :param port: the default port is 1883
    '''

    _host = None
    _port = None
    _brokerAddr = None
    _mqttClient = None

    def __init__(self, host, port, on_connect, on_message, on_publish,
                 on_subscribe):

        self._brokerAddr = host + ":" + str(port)
        self._host = host
        self._port = port

        self._mqttClient = Client()
        self._mqttClient.on_connect = on_connect
        self._mqttClient.on_message = on_message
        self._mqttClient.on_publish = on_publish
        self._mqttClient.on_subscribe = on_subscribe

    def connect(self):

        try:

            print("Connecting to broker:" + self._brokerAddr + ".....")
            self._mqttClient.connect(self._host, self._port, 60)
            self._mqttClient.loop_start()

        except Exception as e:

            print("Cloud not connect to broker " + self._brokerAddr + " " +
                  str(e))

    def disconnect(self):

        self._mqttClient.disconnect()
        self._mqttClient.loop_stop()

    '''
    :param topic : the topic we want to publish
    :param message : the content we want to publish
    :param qos: the qos we want to use
    '''

    def publishMessage(self, topic, message, qos):

        print("Publishing message:" + message + " to broker: " +
              self._brokerAddr + " Topic:" + topic)
        self._mqttClient.publish(topic, message, qos)

    '''
    :param topic : the topic we want to subscribe
    :param qos: the qos we want to use
    '''

    def subscribeTopic(self, topic, qos):

        print("Subscribing to topic:" + topic + ".....")
        self._mqttClient.subscribe(topic, qos)

        def on_connect(mqttc, obj, flags, rc):
            print("Successfully Connect !!  : " + str(rc))

    # when the message is arrived, the message will trigger the actuator
    def on_message(mqttc, obj, msg):

        print(" ActuatorData arrived from topic:" + msg.topic + " QoS:" +
              str(msg.qos) + " Message:" + str(msg.payload.decode("utf-8")))
        sh = sense_hat.SenseHat()
        enableLED = True
        while enableLED:
            mes = str(msg.payload)
            sh.show_message(str(msg.payload), scroll_speed=0.05)
            enableLED = False

    # it will run when publish message successfully
    def on_publish(mqttc, obj, mid):
        print("Successfully published SensorData to topic     - mid: " +
              str(mid))

    #it will run when subscribe message successfully
    def on_subscribe(mqttc, obj, mid, granted_qos):
        print("Successfully Subscribed to topic  " + str(mid) +
              " Granted_QoS:" + str(granted_qos))
Exemplo n.º 22
0
print('The simulator will publish random datas evers 2 seconds on the ' + topic + 'topic')

# DEFINE MQTT EVENTS
def on_connect(client, userdata, flags, rc):
    print("Connected to broker")

def on_disconnect(client, userdata, rc):
    print("Connexion with broker closed")

# Create the mqtt client
mqtt_c = Client(username)

# Assign events
mqtt_c.on_connect = on_connect
mqtt_c.on_disconnect = on_disconnect

# Connecting to the mqtt broker
mqtt_c.username_pw_set(username, password)
mqtt_c.connect(ip, int(port))
mqtt_c.loop_start()

while True:
  payload = randint(0,10)

  # Send the mesured value to the Broker
  mqtt_c.publish(topic, payload)
  print ("Sended " + str(payload) + " to broker at : " + str(datetime.now()))

  sleep(2)
Exemplo n.º 23
0
Client.connected_flag = False
mqtt_client = Client()


def on_connect(client, userdata, flags, rc):
    if rc == 0:
        mqtt_client.connected_flag = True
    else:
        mqtt_client.connected_flag = False


def on_message(client, userdata, message):
    info = simplejson.loads(message.payload)
    print('Customer Order:')
    info['Order_Status'] = 'Confirmed'
    print(info)
    client.publish(topic='%s/%s' % (ORDER_STATUS, info['Room']),
                   payload=simplejson.dumps(info))


mqtt_client.on_connect = on_connect
mqtt_client.on_message = on_message
mqtt_client.loop_start()
mqtt_client.connect(host=MQTT_ADDR, port=MQTT_PRT)
while not mqtt_client.connected_flag:  # wait in loop
    print("In wait loop")
    time.sleep(1)
mqtt_client.subscribe(topic='%s/+' % FD_TOPIC)
mqtt_client.loop_forever()
mqtt_client.disconnect()
Exemplo n.º 24
0
    lambda t: json.dumps({
        "lat": 43.650883 + random.uniform(-0.005, 0.005),
        "lng": -96.201642 + random.uniform(-0.005, 0.005)
    })
}

c = Client()
c.connect('127.0.0.1', 1883)


def on_publish(client, userdata, result):
    print("gateway_sim: {} published: {}".format(client.name, result))


c.on_publish = on_publish
c.loop_start()

config_io = json.loads(sys.stdin.read())


class ChannelSim(StoppableThread):
    def __init__(self, **kwargs):
        StoppableThread.__init__(self, name=kwargs.get('name'))
        self.client = kwargs.get('client')
        setattr(self.client, 'name', kwargs.get('name'))
        self.sim_lambda = sim_lambdas[self.name.split('/')[2]]

    def run(self):
        print("running channel simulator: {}".format(self.name))
        nums = range(1, 10)
        while not self.is_stopped():
Exemplo n.º 25
0
class MQTTEventSink(EventSink):
    def __init__(self,
                 broker,
                 topic="iot-1/d/%012x/evt/%s/json",
                 hostname=None,
                 hostport=1883,
                 username=None,
                 password=None,
                 keepalive=60):
        super(MQTTEventSink, self).__init__(broker=broker)
        self._client = Paho()
        self._client.on_connect = \
                lambda mosq, obj, rc: self._on_connect(mosq, obj, rc)
        self._client.on_disconnect = \
                lambda mosq, obj, rc: self._on_disconnect(mosq, obj, rc)
        self._client.on_publish = \
                lambda mosq, obj, mid: self._on_publish(mosq, obj, mid)
        self._topic_format = topic
        self._topic = self._topic_format % (0, "%s")

        self._hostname = hostname
        self._hostport = hostport
        self._username = username
        self._password = password
        self._keepalive = keepalive

        self._is_connected = False

    def _on_connect(self, mosq, obj, rc):
        self._topic = self._topic_format % (get_mac(), "%s")
        log.debug("MQTT publisher connected: " + str(rc))
        self._is_connected = True

    def _on_disconnect(self, mosq, obj, rc):
        # sink will try reconnecting once EventReporter queries if it's available.
        self._is_connected = False
        log.debug("MQTT publisher disconnected: " + str(rc))

    def _on_publish(self, mosq, obj, mid):
        #log.debug("MQTT publisher published: " + str(mid))
        pass

    def _try_connect(self):
        if self._username is not None and self._password is not None:
            self._client.username_pw_set(self._username, self._password)
        try:
            self._client.connect(self._hostname, self._hostport,
                                 self._keepalive)
        except socket.gaierror:
            return False
        self._client.loop_start()
        return True

    def on_start(self):
        return self._try_connect()

    def send_event(self, event):
        encoded_event = self.encode_event(event)
        # Fill in the blank "%s" left in self._topic
        topic_event_type = event.get_type()
        topic = self._topic % topic_event_type

        # HACK: for mesh networking feature,
        # Check to see if event is from neighbors
        # and need to be published to Mqtt server
        # TODO: move this logic to event_reporter.  Maybe we run a different mesh_event_reporter?
        try:
            if "published" in event.data:
                if event.data["published"] == 1:
                    return True
                else:
                    del event.data["published"]
        except KeyError as e:
            log.error(
                "event encoding not as expected for original scale client mesh networking!\n%s"
                % e.message)

        # Publish message
        res, mid = self._client.publish(topic, encoded_event)
        if res == paho.mqtt.client.MQTT_ERR_SUCCESS:
            log.info("MQTT message published to " + topic)
        elif res == paho.mqtt.client.MQTT_ERR_NO_CONN:
            log.error("MQTT publisher failure: No connection")
            return False
        else:
            log.error("MQTT publisher failure: Unknown error")
            return False
        return True

    def check_available(self, event):
        # If we aren't currently running, try connecting.
        if not self._is_connected:
            if not self._try_connect():
                log.error("MQTT publisher failure: Cannot connect")
                return False
        return self._is_connected
Exemplo n.º 26
0
class Mqtt():
    """Main Mqtt class.

    :param app:  flask application object
    :param connect_async:  if True then connect_aync will be used to connect to MQTT broker
    :param mqtt_logging: if True then messages from MQTT client will be logged

    """
    def __init__(self, app=None, connect_async=False, mqtt_logging=False):
        # type: (Flask, bool, bool) -> None
        self.app = app
        self._connect_async = connect_async  # type: bool
        self._connect_handler = None  # type: Optional[Callable]
        self._disconnect_handler = None  # type: Optional[Callable]
        self.topics = {}  # type: Dict[str, TopicQos]
        self.connected = False
        self.client = Client()
        if mqtt_logging:
            self.client.enable_logger(logger)

        if app is not None:
            self.init_app(app)

    def init_app(self, app):
        # type: (Flask) -> None
        """Init the Flask-MQTT addon."""
        self.client_id = app.config.get("MQTT_CLIENT_ID", "")
        self.clean_session = app.config.get("MQTT_CLEAN_SESSION", True)

        if isinstance(self.client_id, unicode):
            self.client._client_id = self.client_id.encode('utf-8')
        else:
            self.client._client_id = self.client_id

        self.client._clean_session = self.clean_session
        self.client._transport = app.config.get("MQTT_TRANSPORT",
                                                "tcp").lower()
        self.client._protocol = app.config.get("MQTT_PROTOCOL_VERSION",
                                               MQTTv311)

        self.client.on_connect = self._handle_connect
        self.client.on_disconnect = self._handle_disconnect
        self.username = app.config.get("MQTT_USERNAME")
        self.password = app.config.get("MQTT_PASSWORD")
        self.broker_url = app.config.get("MQTT_BROKER_URL", "localhost")
        self.broker_port = app.config.get("MQTT_BROKER_PORT", 1883)
        self.tls_enabled = app.config.get("MQTT_TLS_ENABLED", False)
        self.keepalive = app.config.get("MQTT_KEEPALIVE", 60)
        self.last_will_topic = app.config.get("MQTT_LAST_WILL_TOPIC")
        self.last_will_message = app.config.get("MQTT_LAST_WILL_MESSAGE")
        self.last_will_qos = app.config.get("MQTT_LAST_WILL_QOS", 0)
        self.last_will_retain = app.config.get("MQTT_LAST_WILL_RETAIN", False)

        if self.tls_enabled:
            self.tls_ca_certs = app.config["MQTT_TLS_CA_CERTS"]
            self.tls_certfile = app.config.get("MQTT_TLS_CERTFILE")
            self.tls_keyfile = app.config.get("MQTT_TLS_KEYFILE")
            self.tls_cert_reqs = app.config.get("MQTT_TLS_CERT_REQS",
                                                ssl.CERT_REQUIRED)
            self.tls_version = app.config.get("MQTT_TLS_VERSION",
                                              ssl.PROTOCOL_TLSv1)
            self.tls_ciphers = app.config.get("MQTT_TLS_CIPHERS")
            self.tls_insecure = app.config.get("MQTT_TLS_INSECURE", False)

        # set last will message
        if self.last_will_topic is not None:
            self.client.will_set(
                self.last_will_topic,
                self.last_will_message,
                self.last_will_qos,
                self.last_will_retain,
            )

        self._connect()

    def _connect(self):
        # type: () -> None

        if self.username is not None:
            self.client.username_pw_set(self.username, self.password)

        # security
        if self.tls_enabled:
            self.client.tls_set(
                ca_certs=self.tls_ca_certs,
                certfile=self.tls_certfile,
                keyfile=self.tls_keyfile,
                cert_reqs=self.tls_cert_reqs,
                tls_version=self.tls_version,
                ciphers=self.tls_ciphers,
            )

            if self.tls_insecure:
                self.client.tls_insecure_set(self.tls_insecure)

        if self._connect_async:
            # if connect_async is used
            self.client.connect_async(self.broker_url,
                                      self.broker_port,
                                      keepalive=self.keepalive)
        else:
            res = self.client.connect(self.broker_url,
                                      self.broker_port,
                                      keepalive=self.keepalive)

            if res == 0:
                logger.debug("Connected client '{0}' to broker {1}:{2}".format(
                    self.client_id, self.broker_url, self.broker_port))
            else:
                logger.error(
                    "Could not connect to MQTT Broker, Error Code: {0}".format(
                        res))
        self.client.loop_start()

    def _disconnect(self):
        # type: () -> None
        self.client.loop_stop()
        self.client.disconnect()
        logger.debug('Disconnected from Broker')

    def _handle_connect(self, client, userdata, flags, rc):
        # type: (Client, Any, Dict, int) -> None
        if rc == MQTT_ERR_SUCCESS:
            self.connected = True
            for key, item in self.topics.items():
                self.client.subscribe(topic=item.topic, qos=item.qos)
        if self._connect_handler is not None:
            self._connect_handler(client, userdata, flags, rc)

    def _handle_disconnect(self, client, userdata, rc):
        # type: (str, Any, int) -> None
        self.connected = False
        if self._disconnect_handler is not None:
            self._disconnect_handler()

    def on_topic(self, topic):
        # type: (str) -> Callable
        """Decorator.

        Decorator to add a callback function that is called when a certain
        topic has been published. The callback function is expected to have the
        following form: `handle_topic(client, userdata, message)`

        :parameter topic: a string specifying the subscription topic to
            subscribe to

        The topic still needs to be subscribed via mqtt.subscribe() before the
        callback function can be used to handle a certain topic. This way it is
        possible to subscribe and unsubscribe during runtime.

        **Example usage:**::

            app = Flask(__name__)
            mqtt = Mqtt(app)
            mqtt.subscribe('home/mytopic')

            @mqtt.on_topic('home/mytopic')
            def handle_mytopic(client, userdata, message):
                print('Received message on topic {}: {}'
                      .format(message.topic, message.payload.decode()))
        """
        def decorator(handler):
            # type: (Callable[[str], None]) -> Callable[[str], None]
            self.client.message_callback_add(topic, handler)
            return handler

        return decorator

    def subscribe(self, topic, qos=0):
        # type: (str, int) -> Tuple[int, int]
        """
        Subscribe to a certain topic.

        :param topic: a string specifying the subscription topic to
            subscribe to.
        :param qos: the desired quality of service level for the subscription.
                    Defaults to 0.

        :rtype: (int, int)
        :result: (result, mid)

        A topic is a UTF-8 string, which is used by the broker to filter
        messages for each connected client. A topic consists of one or more
        topic levels. Each topic level is separated by a forward slash
        (topic level separator).

        The function returns a tuple (result, mid), where result is
        MQTT_ERR_SUCCESS to indicate success or (MQTT_ERR_NO_CONN, None) if the
        client is not currently connected.  mid is the message ID for the
        subscribe request. The mid value can be used to track the subscribe
        request by checking against the mid argument in the on_subscribe()
        callback if it is defined.

        **Topic example:** `myhome/groundfloor/livingroom/temperature`

        """
        # TODO: add support for list of topics

        # don't subscribe if already subscribed
        # try to subscribe
        result, mid = self.client.subscribe(topic=topic, qos=qos)

        # if successful add to topics
        if result == MQTT_ERR_SUCCESS:
            self.topics[topic] = TopicQos(topic=topic, qos=qos)
            logger.debug('Subscribed to topic: {0}, qos: {1}'.format(
                topic, qos))
        else:
            logger.error('Error {0} subscribing to topic: {1}'.format(
                result, topic))

        return (result, mid)

    def unsubscribe(self, topic):
        # type: (str) -> Optional[Tuple[int, int]]
        """
        Unsubscribe from a single topic.

        :param topic: a single string that is the subscription topic to
                      unsubscribe from

        :rtype: (int, int)
        :result: (result, mid)

        Returns a tuple (result, mid), where result is MQTT_ERR_SUCCESS
        to indicate success or (MQTT_ERR_NO_CONN, None) if the client is not
        currently connected.
        mid is the message ID for the unsubscribe request. The mid value can be
        used to track the unsubscribe request by checking against the mid
        argument in the on_unsubscribe() callback if it is defined.

        """
        # don't unsubscribe if not in topics
        if topic in self.topics:
            result, mid = self.client.unsubscribe(topic)

            if result == MQTT_ERR_SUCCESS:
                self.topics.pop(topic)
                logger.debug('Unsubscribed from topic: {0}'.format(topic))
            else:
                logger.debug('Error {0} unsubscribing from topic: {1}'.format(
                    result, topic))

            # if successful remove from topics
            return result, mid
        return None

    def unsubscribe_all(self):
        # type: () -> None
        """Unsubscribe from all topics."""
        topics = list(self.topics.keys())
        for topic in topics:
            self.unsubscribe(topic)

    def publish(self, topic, payload=None, qos=0, retain=False):
        # type: (str, bytes, int, bool) -> Tuple[int, int]
        """
        Send a message to the broker.

        :param topic: the topic that the message should be published on
        :param payload: the actual message to send. If not given, or set to
                        None a zero length message will be used. Passing an
                        int or float will result in the payload being
                        converted to a string representing that number.
                        If you wish to send a true int/float, use struct.pack()
                        to create the payload you require.
        :param qos: the quality of service level to use
        :param retain: if set to True, the message will be set as the
                       "last known good"/retained message for the topic

        :returns: Returns a tuple (result, mid), where result is
                  MQTT_ERR_SUCCESS to indicate success or MQTT_ERR_NO_CONN
                  if the client is not currently connected. mid is the message
                  ID for the publish request.

        """
        if not self.connected:
            self.client.reconnect()

        result, mid = self.client.publish(topic, payload, qos, retain)
        if result == MQTT_ERR_SUCCESS:
            logger.debug('Published topic {0}: {1}'.format(topic, payload))
        else:
            logger.error('Error {0} publishing topic {1}'.format(
                result, topic))

        return (result, mid)

    def on_connect(self):
        # type: () -> Callable
        """Decorator.

        Decorator to handle the event when the broker responds to a connection
        request. Only the last decorated function will be called.

        """
        def decorator(handler):
            # type: (Callable) -> Callable
            self._connect_handler = handler
            return handler

        return decorator

    def on_disconnect(self):
        # type: () -> Callable
        """Decorator.

        Decorator to handle the event when client disconnects from broker. Only
        the last decorated function will be called.

        """
        def decorator(handler):
            # type: (Callable) -> Callable
            self._disconnect_handler = handler
            return handler

        return decorator

    def on_message(self):
        # type: () -> Callable
        """Decorator.

        Decorator to handle all messages that have been subscribed and that
        are not handled via the `on_message` decorator.

        **Note:** Unlike as written in the paho mqtt documentation this
        callback will not be called if there exists an topic-specific callback
        added by the `on_topic` decorator.

        **Example Usage:**::

            @mqtt.on_message()
            def handle_messages(client, userdata, message):
                print('Received message on topic {}: {}'
                      .format(message.topic, message.payload.decode()))
        """
        def decorator(handler):
            # type: (Callable) -> Callable
            self.client.on_message = handler
            return handler

        return decorator

    def on_publish(self):
        # type: () -> Callable
        """Decorator.

        Decorator to handle all messages that have been published by the
        client.

        **Example Usage:**::

            @mqtt.on_publish()
            def handle_publish(client, userdata, mid):
                print('Published message with mid {}.'
                      .format(mid))
        """
        def decorator(handler):
            # type: (Callable) -> Callable
            self.client.on_publish = handler
            return handler

        return decorator

    def on_subscribe(self):
        # type: () -> Callable
        """Decorate a callback function to handle subscritions.

        **Usage:**::

            @mqtt.on_subscribe()
            def handle_subscribe(client, userdata, mid, granted_qos):
                print('Subscription id {} granted with qos {}.'
                      .format(mid, granted_qos))
        """
        def decorator(handler):
            # type: (Callable) -> Callable
            self.client.on_subscribe = handler
            return handler

        return decorator

    def on_unsubscribe(self):
        # type: () -> Callable
        """Decorate a callback funtion to handle unsubscribtions.

        **Usage:**::

            @mqtt.unsubscribe()
            def handle_unsubscribe(client, userdata, mid)
                print('Unsubscribed from topic (id: {})'
                      .format(mid)')
        """
        def decorator(handler):
            # type: (Callable) -> Callable
            self.client.on_unsubscribe = handler
            return handler

        return decorator

    def on_log(self):
        # type: () -> Callable
        """Decorate a callback function to handle MQTT logging.

        **Example Usage:**

        ::

            @mqtt.on_log()
            def handle_logging(client, userdata, level, buf):
                print(client, userdata, level, buf)
        """
        def decorator(handler):
            # type: (Callable) -> Callable
            self.client.on_log = handler
            return handler

        return decorator
class Mqtt():
    def __init__(self, app=None):
        # type: (Flask) -> None
        self.app = app
        self.client = Client()
        self.client.on_connect = self._handle_connect
        self.client.on_disconnect = self._handle_disconnect
        self.topics = []  # type: List[str]
        self.connected = False

        if app is not None:
            self.init_app(app)

    def init_app(self, app):
        # type: (Flask) -> None
        self.username = app.config.get('MQTT_USERNAME')
        self.password = app.config.get('MQTT_PASSWORD')
        self.broker_url = app.config.get('MQTT_BROKER_URL', 'localhost')
        self.broker_port = app.config.get('MQTT_BROKER_PORT', 1883)
        self.tls_enabled = app.config.get('MQTT_TLS_ENABLED', False)
        self.keepalive = app.config.get('MQTT_KEEPALIVE', 60)
        self.last_will_topic = app.config.get('MQTT_LAST_WILL_TOPIC')
        self.last_will_message = app.config.get('MQTT_LAST_WILL_MESSAGE')
        self.last_will_qos = app.config.get('MQTT_LAST_WILL_QOS', 0)
        self.last_will_retain = app.config.get('MQTT_LAST_WILL_RETAIN', False)

        if self.tls_enabled:
            self.tls_ca_certs = app.config['MQTT_TLS_CA_CERTS']
            self.tls_certfile = app.config.get('MQTT_TLS_CERTFILE')
            self.tls_keyfile = app.config.get('MQTT_TLS_KEYFILE')
            self.tls_cert_reqs = app.config.get('MQTT_TLS_CERT_REQS',
                                                ssl.CERT_REQUIRED)
            self.tls_version = app.config.get('MQTT_TLS_VERSION',
                                              ssl.PROTOCOL_TLSv1)
            self.tls_ciphers = app.config.get('MQTT_TLS_CIPHERS')
            self.tls_insecure = app.config.get('MQTT_TLS_INSECURE', False)

        # set last will message
        if self.last_will_topic is not None:
            self.client.will_set(self.last_will_topic, self.last_will_message,
                                 self.last_will_qos, self.last_will_retain)
        self.app = app
        self._connect()

    def _connect(self):
        # type: () -> None

        if self.username is not None:
            self.client.username_pw_set(self.username, self.password)

        # security
        if self.tls_enabled:
            if self.tls_insecure:
                self.client.tls_insecure_set(self.tls_insecure)

            self.client.tls_set(
                ca_certs=self.tls_ca_certs,
                certfile=self.tls_certfile,
                keyfile=self.tls_keyfile,
                cert_reqs=self.tls_cert_reqs,
                tls_version=self.tls_version,
                ciphers=self.tls_ciphers,
            )

        self.client.loop_start()
        res = self.client.connect(self.broker_url,
                                  self.broker_port,
                                  keepalive=self.keepalive)

    def _disconnect(self):
        # type: () -> None
        self.client.loop_stop()
        self.client.disconnect()

    def _handle_connect(self, client, userdata, flags, rc):
        # type: (Client, Any, Dict, int) -> None
        if rc == MQTT_ERR_SUCCESS:
            self.connected = True
            for topic in self.topics:
                self.client.subscribe(topic)

    def _handle_disconnect(self, client, userdata, rc):
        # type: (str, Any, int) -> None
        self.connected = False

    def on_topic(self, topic):
        # type: (str) -> Callable
        """
        Decorator to add a callback function that is called when a certain
        topic has been published. The callback function is expected to have the
        following form: `handle_topic(client, userdata, message)`

        :parameter topic: a string specifying the subscription topic to subscribe to

        The topic still needs to be subscribed via mqtt.subscribe() before the
        callback function can be used to handle a certain topic. This way it is
        possible to subscribe and unsubscribe during runtime.

        **Example usage:**::

            app = Flask(__name__)
            mqtt = Mqtt(app)
            mqtt.subscribe('home/mytopic')

            @mqtt.on_topic('home/mytopic')
            def handle_mytopic(client, userdata, message):
                print('Received message on topic {}: {}'
                      .format(message.topic, message.payload.decode()))

        """
        def decorator(handler):
            # type: (Callable[[str], None]) -> Callable[[str], None]
            self.client.message_callback_add(topic, handler)
            return handler

        return decorator

    def subscribe(self, topic, qos=0):
        # type: (str, int) -> tuple(int, int)
        """
        Subscribe to a certain topic.

        :param topic: a string specifying the subscription topic to subscribe to.
        :param qos: the desired quality of service level for the subscription.
                    Defaults to 0.

        :rtype: (int, int)
        :result: (result, mid)

        A topic is a UTF-8 string, which is used by the broker to filter
        messages for each connected client. A topic consists of one or more
        topic levels. Each topic level is separated by a forward slash
        (topic level separator).

        The function returns a tuple (result, mid), where result is
        MQTT_ERR_SUCCESS to indicate success or (MQTT_ERR_NO_CONN, None) if the
        client is not currently connected.  mid is the message ID for the
        subscribe request. The mid value can be used to track the subscribe
        request by checking against the mid argument in the on_subscribe()
        callback if it is defined.

        **Topic example:** `myhome/groundfloor/livingroom/temperature`

        """
        # TODO: add support for list of topics
        # don't subscribe if already subscribed
        # try to subscribe
        result, mid = self.client.subscribe(topic, qos)

        # if successful add to topics
        if result == MQTT_ERR_SUCCESS:
            if topic not in self.topics:
                self.topics.append(topic)

        return (result, mid)

    def unsubscribe(self, topic):
        # type: (str) -> tuple(int, int)
        """
        Unsubscribe from a single topic.

        :param topic: a single string that is the subscription topic to
                      unsubscribe from

        :rtype: (int, int)
        :result: (result, mid) 

        Returns a tuple (result, mid), where result is MQTT_ERR_SUCCESS
        to indicate success or (MQTT_ERR_NO_CONN, None) if the client is not
        currently connected.
        mid is the message ID for the unsubscribe request. The mid value can be
        used to track the unsubscribe request by checking against the mid
        argument in the on_unsubscribe() callback if it is defined.

        """
        # don't unsubscribe if not in topics
        if topic not in self.topics:
            return

        result, mid = self.client.unsubscribe(topic)

        # if successful remove from topics
        if result == MQTT_ERR_SUCCESS:
            self.topics.remove(topic)

        return result, mid

    def unsubscribe_all(self):
        # type: () -> None
        """
        Unsubscribe from all topics.

        """
        topics = self.topics[:]
        for topic in topics:
            self.unsubscribe(topic)

    def publish(self, topic, payload=None, qos=0, retain=False):
        # type: (str, bytes, int, bool) -> Tuple[int, int]
        """
        Send a message to the broker.

        :param topic: the topic that the message should be published on
        :param payload: the actual message to send. If not given, or set to
                        None a zero length message will be used. Passing an
                        int or float will result in the payload being
                        converted to a string representing that number.
                        If you wish to send a true int/float, use struct.pack()
                        to create the payload you require.
        :param qos: the quality of service level to use
        :param retain: if set to True, the message will be set as the
                       "last known good"/retained message for the topic

        :returns: Returns a tuple (result, mid), where result is
                  MQTT_ERR_SUCCESS to indicate success or MQTT_ERR_NO_CONN
                  if the client is not currently connected. mid is the message
                  ID for the publish request.

        """
        if not self.connected:
            self.client.reconnect()
        return self.client.publish(topic, payload, qos, retain)

    def on_message(self):
        # type: () -> Callable
        """
        Decorator to handle all messages that have been subscribed and that
        are not handled via the `on_message` decorator.

        **Note:** Unlike as written in the paho mqtt documentation this 
        callback will not be called if there exists an topic-specific callback
        added by the `on_topic` decorator.

        **Example Usage:**::

            @mqtt.on_message()
            def handle_messages(client, userdata, message):
                print('Received message on topic {}: {}'
                      .format(message.topic, message.payload.decode()))

        """
        def decorator(handler):
            # type: (Callable) -> Callable
            self.client.on_message = handler
            return handler

        return decorator

    def on_publish(self):
        """
        Decorator to handle all messages that have been published by the
        client.

        **Example Usage:**::

            @mqtt.on_publish()
            def handle_publish(client, userdata, mid):
                print('Published message with mid {}.'
                      .format(mid))
        """
        def decorator(handler):
            self.client.on_publish = handler
            return handler

        return decorator

    def on_subscribe(self):
        """
        Decorator to handle subscribe callbacks.

        **Usage:**::

            @mqtt.on_subscribe()
            def handle_subscribe(client, userdata, mid, granted_qos):
                print('Subscription id {} granted with qos {}.'
                      .format(mid, granted_qos))

        """
        def decorator(handler):
            self.client.on_subscribe = handler
            return handler

        return decorator

    def on_unsubscribe(self):
        """
        Decorator to handle unsubscribe callbacks.

        **Usage:**::

            @mqtt.unsubscribe()
            def handle_unsubscribe(client, userdata, mid)
                print('Unsubscribed from topic (id: {})'
                      .format(mid)')
                
        """
        def decorator(handler):
            self.client.on_unsubscribe = handler
            return handler

        return decorator

    def on_log(self):
        # type: () -> Callable
        """
        Decorator to handle MQTT logging.

        **Example Usage:**

        ::
            
            @mqtt.on_log()
            def handle_logging(client, userdata, level, buf):
                print(client, userdata, level, buf)

        """
        def decorator(handler):
            # type: (Callable) -> Callable
            self.client.on_log = handler
            return handler

        return decorator
Exemplo n.º 28
0
                   payload="DISCONNECT")

    mqttc.connect(broker)

    #suscripciones iniciales del cliente
    mqttc.subscribe(choques + "/jugadores/" + nombre_usuario)
    mqttc.subscribe(choques + "/servidor")
    mqttc.subscribe(choques + "/servidor/" + nombre_usuario)
    mqttc.subscribe(choques + "/servidor/exception")

    #publicación inicial para unirse al juego
    mqttc.publish(choques + "/servidor/" + nombre_usuario,
                  payload="CONNECT_REQUEST")
    print("ESPERANDO AL SERVIDOR...")

    mqttc.loop_start()

    #

    conectado = Value('i', 1)
    indice_partida = Value(
        'i', 0)  #indice de la partida a la que se conecta el jugador
    jugar = Value('i', 0)
    letra = Value('c',
                  b'z')  #el character tiene que ser de tipo byte para el Value

    while conectado.value == 1:
        while jugar.value == 0:
            if conectado.value == 0:
                break
            pass
Exemplo n.º 29
0
class MqttConnection:

    def __init__(self, ip, port, username, password, connection_callback):
        self.logger = logging.getLogger("mqtt")

        self.mqtt = Client()
        if username is not None:
            self.mqtt.username_pw_set(username, password)

        self.mqtt.on_connect = self._on_connect
        self.mqtt.on_message = self._on_message

        self.ip = ip
        self.port = port
        self.connection_callback = connection_callback
        self.queue = []

    def _on_connect(self, client, userdata, flags, rc):
        """
        The callback for when the client receives a CONNACK response from the server.
        """
        self.logger.debug("connected to mqtt with result code %d" % rc)

        # subscribing in on_connect() means that if we lose the connection and
        # reconnect then subscriptions will be renewed.
        self.connection_callback.on_mqtt_connected(self)

        if len(self.queue) > 0:
            self.logger.debug("found %d queued messages" % len(self.queue))
            for msg in self.queue:
                self._internal_send_message(msg[0], msg[1], False)

            self.queue.clear()
            self.logger.debug("handled all queued messages")

    def _on_message(self, client, userdata, msg):
        """
        The callback for when a PUBLISH message is received from the server.
        """
        self.logger.debug("received mqtt publish of %s with data \"%s\"" % (msg.topic, msg.payload))
        self.connection_callback.on_mqtt_message_received(msg.topic, msg.payload)

    def send_message(self, topic, payload):
        return self._internal_send_message(topic, payload, True)

    def subscribe(self, topic):
        self.logger.debug("subscribing to topic %s" % topic)
        result = self.mqtt.subscribe(topic)

        if result[0] == MQTT_ERR_NO_CONN:
            self.logger.warning("no connection while trying to subscribe to topic %s" % topic)
            return False

        return result[0] == MQTT_ERR_SUCCESS

    def unsubscribe(self, topic):
        self.logger.debug("unsubscribing from topic %s" % topic)
        result = self.mqtt.unsubscribe(topic)

        if result[0] == MQTT_ERR_NO_CONN:
            self.logger.warning("no connection while trying to unsubscribe from topic %s" % topic)
            return False

        return result[0] == MQTT_ERR_SUCCESS

    def _internal_send_message(self, topic, payload, queue):
        self.logger.debug("sending topic %s with value \"%s\"" % (topic, payload))
        result = self.mqtt.publish(topic, payload, retain=True)

        if result == MQTT_ERR_NO_CONN and queue:
            self.logger.debug("no connection, saving message with topic %s to queue" % topic)
            self.queue.append([topic, payload])
        elif result[0] != MQTT_ERR_SUCCESS:
            self.logger.warn("failed sending message %s, mqtt error %s" % (topic, result))
            return False

        return True

    def start_connection(self):
        try:
            self.mqtt.connect(self.ip, self.port)
        except ConnectionError:
            self.logger.exception("failed connecting to mqtt")
            return False

        self.mqtt.loop_start()
        return True

    def stop_connection(self):
        self.mqtt.disconnect()
        self.mqtt.loop_stop()
Exemplo n.º 30
0
class MQTTClient:
    """ mqtt客户端的父类
    """

    def __init__(self, host, port,
                 client_id=None,
                 clean_session=False,
                 keepalive=60):

        self.client = Client()
        if client_id:
            self.client._client_id = client_id
            self.client._clean_session = False

        self.host = host
        self.port = port
        self.keepalive = keepalive

        self.connected = False

        self.client.on_connect = self._on_connect
        self.client.on_message = self._on_message
        self.client.on_log = self._on_log

    def connect(self, user='******', password='******'):
        rc = 1
        if self.connected:
            return 0

        self.client.username_pw_set(user, password)
        try:
            rc = self.client.connect(self.host, self.port, self.keepalive)
            assert rc == 0, ConnectionRefusedError
            self.connected = True

        except ConnectionRefusedError:
            log.error('Retry after 1 second.')

        return rc

    def disconnect(self):
        self.connected = False
        self.client.disconnect()

    def loop(self, timeout=None):
        if timeout:
            self.client.loop(timeout=timeout)
        else:
            self.client.loop_forever()

    def loop_start(self):
        return self.client.loop_start()

    def publish(self, topic, data={}, qos=1):
        (rc, final_mid) = self.client.publish(topic, json.dumps(data), qos=qos)
        return rc, final_mid

    def _on_connect(self, client, userdata, flags, rc):
        pass

    def _on_message(self, client, userdata, msg):
        pass

    def _on_log(self, client, userdata, level, buf):
        return buf
class HubCommunicationService:
    def __init__(self):
        self.client = None
        self.client_name = None
        self.rooms_service = None

    def initialize(self, configuration, rooms_service):
        self.rooms_service = rooms_service
        comm_config = configuration.get(CONFIG_CONNECTIVITY)
        self.client_name = comm_config.get(CONFIG_CONNECTIVITY_CLIENTNAME)
        self.client = Client(self.client_name)
        host = comm_config.get(CONFIG_CONNECTIVITY_MQTTIP)
        port = comm_config.get(CONFIG_CONNECTIVITY_MQTTPORT)
        self.client.enable_logger()
        self.client.on_message = self.on_message
        self.client.on_connect = self.on_connect
        self.client.connect(host=host, port=port)
        self.client.loop_start()

    def destroy(self):
        self.client.loop_stop()
        self.client.disconnect()

    def create_topic_name(self):
        # topic format /Actor/In/#
        return "{sep}{name}{sep}{tin}{sep}#".format(sep=NAME_SEPARATOR,
                                                    tin=TOPIC_IN,
                                                    name=self.client_name)

    def get_point_id(self, channel):
        if channel is None:
            return None
        topic_indicator = NAME_SEPARATOR + TOPIC_IN + NAME_SEPARATOR
        idx = channel.find(topic_indicator)
        if idx < 0:
            return None
        return channel[idx + len(topic_indicator) - 1:len(channel)]

    def is_admin_channel(self, channel):
        if channel is None:
            return False
        admin_indicator = NAME_SEPARATOR + TOPIC_ADMIN
        idx = channel.find(admin_indicator)
        if idx < 0:
            return False
        return True

    def get_channel(self, point_id):
        # topic format /Actor/Out/PointId
        return "{sep}{act}{sep}{tout}{id}".format(sep=NAME_SEPARATOR,
                                                  tout=TOPIC_OUT,
                                                  act=self.client_name,
                                                  id=point_id)

    def on_message(self, client, userdata, message):
        topic = message.topic
        payload = str(message.payload.decode("utf-8"))
        logging.debug("Msg rcv: {0} msg: {1}".format(topic, payload))
        self.process_message(channel=topic, message=payload)

    def on_connect(self, client, userdata, flags, rc):
        logging.info("Connected to mqtt service on {0}:{1} ".format(
            client._host, client._port))
        topic = self.create_topic_name()
        logging.info("Subscribing to topic {}".format(topic))
        self.client.subscribe(topic)

    def sendStatusUpdate(self, point_id, message):
        topic = self.get_channel(point_id)
        self.client.publish(topic=topic, payload=message)
        logging.debug("Msg snd: {0} msg: {1}".format(topic, message))

    def process_message(self, channel, message):
        if self.is_admin_channel(channel):
            self.process_admin_message(channel, message)
            return

        point_id = self.get_point_id(channel)
        if point_id is None:
            logging.info(
                "Point_id not determined for channel {0} and message {1}, skipped"
                .format(channel, message))
        else:
            self.rooms_service.updateStatus(point_id=point_id, message=message)

    def process_admin_message(self, channel, message):
        logging.info("Admin message received: channel {0} message {1}".format(
            channel, message))
        if message == COMMAND_RESET:
            self.rooms_service.reset()
        if message == COMMAND_NOTIFY_CURRENT_STATE:
            self.rooms_service.notify_current_state()
class MqttConnector(Connector, Thread):
    def __init__(self, gateway, config, connector_type):
        super().__init__()
        self.__log = log
        self.config = config
        self.__connector_type = connector_type
        self.statistics = {'MessagesReceived': 0, 'MessagesSent': 0}
        self.__gateway = gateway
        self.__broker = config.get('broker')
        self.__mapping = config.get('mapping')
        self.__server_side_rpc = config.get('serverSideRpc')
        self.__service_config = {
            "connectRequests": None,
            "disconnectRequests": None
        }
        self.__attribute_updates = []
        self.__get_service_config(config)
        self.__sub_topics = {}
        client_id = ''.join(
            random.choice(string.ascii_lowercase) for _ in range(23))
        self._client = Client(client_id)
        self.setName(
            config.get(
                "name",
                self.__broker.get(
                    "name", 'Mqtt Broker ' + ''.join(
                        random.choice(string.ascii_lowercase)
                        for _ in range(5)))))
        if "username" in self.__broker["security"]:
            self._client.username_pw_set(self.__broker["security"]["username"],
                                         self.__broker["security"]["password"])
        if "caCert" in self.__broker["security"] or self.__broker[
                "security"].get("type", "none").lower() == "tls":
            ca_cert = self.__broker["security"].get("caCert")
            private_key = self.__broker["security"].get("privateKey")
            cert = self.__broker["security"].get("cert")
            if ca_cert is None:
                self._client.tls_set_context(
                    ssl.SSLContext(ssl.PROTOCOL_TLSv1_2))
            else:
                try:
                    self._client.tls_set(ca_certs=ca_cert,
                                         certfile=cert,
                                         keyfile=private_key,
                                         cert_reqs=ssl.CERT_REQUIRED,
                                         tls_version=ssl.PROTOCOL_TLSv1_2,
                                         ciphers=None)
                except Exception as e:
                    self.__log.error(
                        "Cannot setup connection to broker %s using SSL. Please check your configuration.\nError: %s",
                        self.get_name(), e)
                self._client.tls_insecure_set(False)
        self._client.on_connect = self._on_connect
        self._client.on_message = self._on_message
        self._client.on_subscribe = self._on_subscribe
        self.__subscribes_sent = {}  # For logging the subscriptions
        self._client.on_disconnect = self._on_disconnect
        self._client.on_log = self._on_log
        self._connected = False
        self.__stopped = False
        self.daemon = True

    def is_connected(self):
        return self._connected

    def open(self):
        self.__stopped = False
        self.start()

    def run(self):
        try:
            while not self._connected and not self.__stopped:
                try:
                    self._client.connect(self.__broker['host'],
                                         self.__broker.get('port', 1883))
                    self._client.loop_start()
                    if not self._connected:
                        time.sleep(1)
                except Exception as e:
                    self.__log.exception(e)
                    time.sleep(10)

        except Exception as e:
            self.__log.exception(e)
            try:
                self.close()
            except Exception as e:
                self.__log.exception(e)
        while True:
            if self.__stopped:
                break
            else:
                time.sleep(1)

    def close(self):
        self.__stopped = True
        try:
            self._client.disconnect()
        except Exception as e:
            log.exception(e)
        self._client.loop_stop()
        self.__log.info('%s has been stopped.', self.get_name())

    def get_name(self):
        return self.name

    def __subscribe(self, topic):
        message = self._client.subscribe(topic)
        try:
            self.__subscribes_sent[message[1]] = topic
        except Exception as e:
            self.__log.exception(e)

    def _on_connect(self, client, userdata, flags, rc, *extra_params):
        result_codes = {
            1: "incorrect protocol version",
            2: "invalid client identifier",
            3: "server unavailable",
            4: "bad username or password",
            5: "not authorised",
        }
        if rc == 0:
            self._connected = True
            self.__log.info('%s connected to %s:%s - successfully.',
                            self.get_name(), self.__broker["host"],
                            self.__broker.get("port", "1883"))
            for mapping in self.__mapping:
                try:
                    converter = None
                    if mapping["converter"]["type"] == "custom":
                        try:
                            module = TBUtility.check_and_import(
                                self.__connector_type,
                                mapping["converter"]["extension"])
                            if module is not None:
                                self.__log.debug(
                                    'Custom converter for topic %s - found!',
                                    mapping["topicFilter"])
                                converter = module(mapping)
                            else:
                                self.__log.error(
                                    "\n\nCannot find extension module for %s topic.\n\Please check your configuration.\n",
                                    mapping["topicFilter"])
                        except Exception as e:
                            self.__log.exception(e)
                    else:
                        converter = JsonMqttUplinkConverter(mapping)
                    if converter is not None:
                        regex_topic = TBUtility.topic_to_regex(
                            mapping.get("topicFilter"))
                        if not self.__sub_topics.get(regex_topic):
                            self.__sub_topics[regex_topic] = []

                        self.__sub_topics[regex_topic].append(
                            {converter: None})
                        # self._client.subscribe(TBUtility.regex_to_topic(regex_topic))
                        self.__subscribe(mapping["topicFilter"])
                        self.__log.info('Connector "%s" subscribe to %s',
                                        self.get_name(),
                                        TBUtility.regex_to_topic(regex_topic))
                    else:
                        self.__log.error("Cannot find converter for %s topic",
                                         mapping["topicFilter"])
                except Exception as e:
                    self.__log.exception(e)
            try:
                for request in self.__service_config:
                    if self.__service_config.get(request) is not None:
                        for request_config in self.__service_config.get(
                                request):
                            self.__subscribe(request_config["topicFilter"])
            except Exception as e:
                self.__log.error(e)

        else:
            if rc in result_codes:
                self.__log.error("%s connection FAIL with error %s %s!",
                                 self.get_name(), rc, result_codes[rc])
            else:
                self.__log.error("%s connection FAIL with unknown error!",
                                 self.get_name())

    def _on_disconnect(self, *args):
        self.__log.debug('"%s" was disconnected.', self.get_name())

    def _on_log(self, *args):
        self.__log.debug(args)
        # pass

    def _on_subscribe(self, client, userdata, mid, granted_qos):
        try:
            if granted_qos[0] == 128:
                self.__log.error(
                    '"%s" subscription failed to topic %s subscription message id = %i',
                    self.get_name(), self.__subscribes_sent.get(mid), mid)
            else:
                self.__log.info(
                    '"%s" subscription success to topic %s, subscription message id = %i',
                    self.get_name(), self.__subscribes_sent.get(mid), mid)
                if self.__subscribes_sent.get(mid) is not None:
                    del self.__subscribes_sent[mid]
        except Exception as e:
            self.__log.exception(e)

    def __get_service_config(self, config):
        for service_config in self.__service_config:
            if service_config != "attributeUpdates" and config.get(
                    service_config):
                self.__service_config[service_config] = config[service_config]
            else:
                self.__attribute_updates = config[service_config]

    def _on_message(self, client, userdata, message):
        self.statistics['MessagesReceived'] += 1
        content = TBUtility.decode(message)
        regex_topic = [
            regex for regex in self.__sub_topics
            if fullmatch(regex, message.topic)
        ]
        if regex_topic:
            try:
                for regex in regex_topic:
                    if self.__sub_topics.get(regex):
                        for converter_value in range(
                                len(self.__sub_topics.get(regex))):
                            if self.__sub_topics[regex][converter_value]:
                                for converter in self.__sub_topics.get(
                                        regex)[converter_value]:
                                    converted_content = converter.convert(
                                        message.topic, content)
                                    if converted_content:
                                        try:
                                            self.__sub_topics[regex][
                                                converter_value][
                                                    converter] = converted_content
                                        except Exception as e:
                                            self.__log.exception(e)
                                        self.__gateway.send_to_storage(
                                            self.name, converted_content)
                                        self.statistics['MessagesSent'] += 1
                                    else:
                                        continue
                            else:
                                self.__log.error(
                                    'Cannot find converter for topic:"%s"!',
                                    message.topic)
                                return
            except Exception as e:
                log.exception(e)
                return
        elif self.__service_config.get("connectRequests"):
            connect_requests = [
                connect_request for connect_request in
                self.__service_config.get("connectRequests")
            ]
            if connect_requests:
                for request in connect_requests:
                    if request.get("topicFilter"):
                        if message.topic in request.get("topicFilter") or\
                                (request.get("deviceNameTopicExpression") is not None and search(request.get("deviceNameTopicExpression"), message.topic)):
                            founded_device_name = None
                            if request.get("deviceNameJsonExpression"):
                                founded_device_name = TBUtility.get_value(
                                    request["deviceNameJsonExpression"],
                                    content)
                            if request.get("deviceNameTopicExpression"):
                                device_name_expression = request[
                                    "deviceNameTopicExpression"]
                                founded_device_name = search(
                                    device_name_expression, message.topic)
                            if founded_device_name is not None and founded_device_name not in self.__gateway.get_devices(
                            ):
                                self.__gateway.add_device(
                                    founded_device_name, {"connector": self})
                        else:
                            self.__log.error(
                                "Cannot find connect request for device from message from topic: %s and with data: %s",
                                message.topic, content)
                    else:
                        self.__log.error(
                            "\"topicFilter\" in connect requests config not found."
                        )
            else:
                self.__log.error("Connection requests in config not found.")

        elif self.__service_config.get("disconnectRequests") is not None:
            disconnect_requests = [
                disconnect_request for disconnect_request in
                self.__service_config.get("disconnectRequests")
            ]
            if disconnect_requests:
                for request in disconnect_requests:
                    if request.get("topicFilter") is not None:
                        if message.topic in request.get("topicFilter") or\
                                (request.get("deviceNameTopicExpression") is not None and search(request.get("deviceNameTopicExpression"), message.topic)):
                            founded_device_name = None
                            if request.get("deviceNameJsonExpression"):
                                founded_device_name = TBUtility.get_value(
                                    request["deviceNameJsonExpression"],
                                    content)
                            if request.get("deviceNameTopicExpression"):
                                device_name_expression = request[
                                    "deviceNameTopicExpression"]
                                founded_device_name = search(
                                    device_name_expression, message.topic)
                            if founded_device_name is not None and founded_device_name in self.__gateway.get_devices(
                            ):
                                self.__gateway.del_device(founded_device_name)
                        else:
                            self.__log.error(
                                "Cannot find connect request for device from message from topic: %s and with data: %s",
                                message.topic, content)
                    else:
                        self.__log.error(
                            "\"topicFilter\" in connect requests config not found."
                        )
            else:
                self.__log.error("Disconnection requests in config not found.")
        elif message.topic in self.__gateway.rpc_requests_in_progress:
            self.__gateway.rpc_with_reply_processing(message.topic, content)
        else:
            self.__log.debug(
                "Received message to topic \"%s\" with unknown interpreter data: \n\n\"%s\"",
                message.topic, content)

    def on_attributes_update(self, content):
        attribute_updates_config = [
            update for update in self.__attribute_updates
        ]
        if attribute_updates_config:
            for attribute_update in attribute_updates_config:
                if match(attribute_update["deviceNameFilter"], content["device"]) and \
                        content["data"].get(attribute_update["attributeFilter"]):
                    topic = attribute_update["topicExpression"]\
                            .replace("${deviceName}", content["device"])\
                            .replace("${attributeKey}", attribute_update["attributeFilter"])\
                            .replace("${attributeValue}", content["data"][attribute_update["attributeFilter"]])
                    data = ''
                    try:
                        data = attribute_update["valueExpression"]\
                                .replace("${attributeKey}", attribute_update["attributeFilter"])\
                                .replace("${attributeValue}", content["data"][attribute_update["attributeFilter"]])
                    except Exception as e:
                        self.__log.error(e)
                    self._client.publish(topic, data).wait_for_publish()
                    self.__log.debug(
                        "Attribute Update data: %s for device %s to topic: %s",
                        data, content["device"], topic)
                else:
                    self.__log.error(
                        "Not found deviceName by filter in message or attributeFilter in message with data: %s",
                        content)
        else:
            self.__log.error("Attribute updates config not found.")

    def server_side_rpc_handler(self, content):
        for rpc_config in self.__server_side_rpc:
            if search(rpc_config["deviceNameFilter"], content["device"]) \
                    and search(rpc_config["methodFilter"], content["data"]["method"]) is not None:
                # Subscribe to RPC response topic
                if rpc_config.get("responseTopicExpression"):
                    topic_for_subscribe = rpc_config["responseTopicExpression"] \
                        .replace("${deviceName}", content["device"]) \
                        .replace("${methodName}", content["data"]["method"]) \
                        .replace("${requestId}", str(content["data"]["id"])) \
                        .replace("${params}", content["data"]["params"])
                    if rpc_config.get("responseTimeout"):
                        timeout = time.time() * 1000 + rpc_config.get(
                            "responseTimeout")
                        self.__gateway.register_rpc_request_timeout(
                            content, timeout, topic_for_subscribe,
                            self.rpc_cancel_processing)
                        # Maybe we need to wait for the command to execute successfully before publishing the request.
                        self._client.subscribe(topic_for_subscribe)
                    else:
                        self.__log.error(
                            "Not found RPC response timeout in config, sending without waiting for response"
                        )
                # Publish RPC request
                if rpc_config.get("requestTopicExpression") is not None\
                        and rpc_config.get("valueExpression"):
                    topic = rpc_config.get("requestTopicExpression")\
                        .replace("${deviceName}", content["device"])\
                        .replace("${methodName}", content["data"]["method"])\
                        .replace("${requestId}", str(content["data"]["id"]))\
                        .replace("${params}", content["data"]["params"])
                    data_to_send = rpc_config.get("valueExpression")\
                        .replace("${deviceName}", content["device"])\
                        .replace("${methodName}", content["data"]["method"])\
                        .replace("${requestId}", str(content["data"]["id"]))\
                        .replace("${params}", content["data"]["params"])
                    try:
                        self._client.publish(topic, data_to_send)
                        self.__log.debug(
                            "Send RPC with no response request to topic: %s with data %s",
                            topic, data_to_send)
                        if rpc_config.get("responseTopicExpression") is None:
                            self.__gateway.send_rpc_reply(
                                device=content["device"],
                                req_id=content["data"]["id"],
                                success_sent=True)
                    except Exception as e:
                        self.__log.exception(e)

    def rpc_cancel_processing(self, topic):
        self._client.unsubscribe(topic)
Exemplo n.º 33
0
class Messenger(object):
    """
    MQTT client for Herald transport.
    """

    def __init__(self, peer):
        """
        Initialize client
        :param peer: The peer behind the MQTT client.
        :return:
        """
        self.__peer = peer
        self.__mqtt = MqttClient()
        self.__mqtt.on_connect = self._on_connect
        self.__mqtt.on_disconnect = self._on_disconnect
        self.__mqtt.on_message = self._on_message
        self.__callback_handler = None
        self.__WILL_TOPIC = "/".join(
            (TOPIC_PREFIX, peer.app_id, RIP_TOPIC))

    def __make_uid_topic(self, subtopic):
        """
        Constructs a complete UID topic.
        :param subtopic: The UID
        :return: Fully qualified topic
        :rtype : str
        """
        return "/".join(
            (TOPIC_PREFIX, self.__peer.app_id, UID_TOPIC, subtopic))

    def __make_group_topic(self, subtopic):
        """
        Constructs a complete group topic.
        :param subtopic: The group name
        :return: Fully qualified topic
        :rtype : str
        """
        return "/".join(
            (TOPIC_PREFIX, self.__peer.app_id, GROUP_TOPIC, subtopic))

    def __handle_will(self, message):
        if self.__callback_handler and self.__callback_handler.on_peer_down:
            self.__callback_handler.on_peer_down(
                message.payload.decode('utf-8'))
        else:
            _log.debug("Missing callback for on_peer_down.")

    def _on_connect(self, *args, **kwargs):
        """
        Handles a connection-established event.
        :param args: unnamed arguments
        :param kwargs: named arguments
        :return:
        """
        _log.info("Connection established.")
        _log.debug("Subscribing for topic %s.",
                   self.__make_uid_topic(self.__peer.uid))
        self.__mqtt.subscribe(self.__make_uid_topic(self.__peer.uid))
        self.__mqtt.subscribe(self.__make_group_topic("all"))
        self.__mqtt.subscribe(self.__WILL_TOPIC)
        for group in self.__peer.groups:
            _log.debug("Subscribing for topic %s.",
                       self.__make_group_topic(group))
            self.__mqtt.subscribe(self.__make_group_topic(group))
        if self.__callback_handler and self.__callback_handler.on_connected:
            self.__callback_handler.on_connected()
        else:
            _log.warning("Missing callback for on_connect.")

    def _on_disconnect(self, *args, **kwargs):
        """
        Handles a connection-lost event.
        :param args: unnamed arguments
        :param kwargs: named arguments
        :return:
        """
        _log.info("Connection lost.")
        if self.__callback_handler and self.__callback_handler.on_disconnected:
            self.__callback_handler.on_disconnected()

    def _on_message(self, client, data, message):
        """
        Handles an incoming message.
        :param client: the client instance for this callback
        :param data: the private user data
        :param message: an instance of MQTTMessage
        :type message: paho.mqtt.client.MQTTMessage
        :return:
        """
        _log.info("Message received.")
        if message.topic == self.__WILL_TOPIC:
            self.__handle_will(message)
            return
        if self.__callback_handler and self.__callback_handler.on_message:
            self.__callback_handler.on_message(message.payload.decode('utf-8'))
        else:
            _log.warning("Missing callback for on_message.")

    def fire(self, peer_uid, message):
        """
        Sends a message to another peer.
        :param peer_uid: Peer UID
        :param message: Message content
        :return:
        """
        self.__mqtt.publish(
            self.__make_uid_topic(peer_uid),
            message,
            1
        )

    def fire_group(self, group, message):
        """
        Sends a message to a group of peers.
        :param group: Group's name
        :param message: Message content
        :return:
        """
        self.__mqtt.publish(
            self.__make_group_topic(group),
            message,
            1
        )

    def set_callback_listener(self, listener):
        """
        Sets callback listener.
        :param listener: the listener
        :return:
        """
        self.__callback_handler = listener

    def login(self, username, password):
        """
        Set credentials for an MQTT broker.
        :param username: Username
        :param password: Password
        :return:
        """
        self.__mqtt.username_pw_set(username, password)

    def connect(self, host, port):
        """
        Connects to an MQTT broker.
        :param host: broker's host name
        :param port: broker's port number
        :return:
        """
        _log.info("Connecting to MQTT broker at %s:%s ...", host, port)
        self.__mqtt.will_set(self.__WILL_TOPIC, self.__peer.uid, 1)
        self.__mqtt.connect(host, port)
        self.__mqtt.loop_start()

    def disconnect(self):
        """
        Diconnects from an MQTT broker.
        :return:
        """
        _log.info("Disconnecting from MQTT broker...")
        self.__mqtt.publish(self.__WILL_TOPIC, self.__peer.uid, 1)
        self.__mqtt.loop_stop()
        self.__mqtt.disconnect()