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))
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)
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()
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)
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
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}.")
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()})
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)
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)
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)
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))
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)
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)
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)
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
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)
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()
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)
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()
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)
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))
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)
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()
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():
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
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
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
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()
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)
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()