Example #1
0
class MqttBus(IBus):
    """
    Args transport from pusher to subscribers via MQTT
    """
    _subscribers = dict()

    def __init__(self,
                 host=None,
                 *args,
                 publish_time_out=0.05,
                 publish_waiting=0.1,
                 mid_max_len=64,
                 qos=2,
                 retain=True,
                 **kwargs):
        super().__init__()
        if host is None:
            host = 'localhost'
        self.client = Client(*args, **kwargs)
        self.client.connect(host)
        self.client.loop_start()
        self.client.on_publish = self._on_publish
        self._received_messages = []
        self.mid_max_len = mid_max_len
        self.publish_time_out = publish_time_out
        self.publish_waiting = publish_waiting
        self.qos = qos
        self.retain = retain

    @staticmethod
    def _loads(payload):
        try:
            return pickle.loads(payload,
                                fix_imports=True,
                                encoding="utf-8",
                                errors="strict")
        except Exception as e:
            log.error('mqtt loads error:' + str(e))
            return None

    @staticmethod
    def _dumps(data):
        try:
            return pickle.dumps(data, protocol=3, fix_imports=True)
        except Exception as e:
            log.error('mqtt dumps error:' + str(e))
            return None

    def _on_publish(self, client, userdata, mid):
        if len(self._received_messages) >= self.mid_max_len:
            self._received_messages.pop(0)
            self._received_messages.append(mid)
        else:
            self._received_messages.append(mid)

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

        args, kwargs = self._loads(msg.payload)
        if self._subscribers.get(msg.topic):
            for fn in self._subscribers[msg.topic]:
                fn(*args, **kwargs)
        else:
            log.warning(
                'mqtt Callback _subscribers is not set for topic={}'.format(
                    msg.topic))

    def subscribe(self, topic, fn=None):
        """
        Add function fn to subscribers list
        """
        if fn:
            assert hasattr(fn, '__call__')
            self.client.subscribe(topic, qos=self.qos)
            self.client.message_callback_add(
                topic, lambda client, userdata, msg: self._on_message(
                    client, userdata, msg))
            if self._subscribers.get(topic) is None:
                self._subscribers[topic] = [fn]
                log.info('mqtt Function {} subscribed to topic "{}"'.format(
                    fn.__name__, topic))
            else:
                self._subscribers[topic].append(fn)
                log.info('mqtt Function {} subscribed to topic "{}"'.format(
                    fn.__name__, topic))
        else:
            self.client.message_callback_remove(topic)
            self.client.unsubscribe(topic)

    def push(self, topic, *args, **kwargs):
        """
        Call all the subscribers
        """
        _args = [arg for arg in args if isinstance(arg, (float, int, str))]
        _kwargs = {
            key: arg
            for key, arg in kwargs.items()
            if isinstance(arg, (float, int, str))
        }

        if _args or _kwargs:
            log.debug(
                'pushed topic "{}" with args {} and kwargs {} and {}'.format(
                    topic, str(_args), str(_kwargs),
                    str(set(kwargs.keys()) - set(_kwargs.keys()))))

        send_data = self._dumps((args, kwargs))
        log.info('mqtt Pushing data with size={} to topic={}...'.format(
            len(send_data), topic))
        rc, mid = self.client.publish(topic,
                                      send_data,
                                      qos=self.qos,
                                      retain=self.retain)
        publish_time = time.time()
        while mid not in self._received_messages:
            time.sleep(self.publish_time_out)
            if (time.time() - publish_time) >= self.publish_waiting:
                break
        if rc == 0:
            log.info('mqtt Pushed data to topic={} with rc {}'.format(
                topic, rc))
        else:
            log.warning('mqtt Not pushed data to topic={} with rc {}'.format(
                topic, rc))