def init_client(): #variabili di sistema settate access_key = os.environ["AWS_ACCESS_KEY_ID"] secret_key = os.environ["AWS_SECRET_ACCESS_KEY"] port = 443 region = "us-east-2" # This is specific to your AWS account host = "a3pwbt0axh6wnd-ats.iot.us-east-2.amazonaws.com".format(region) extra_headers = functools.partial( get_amazon_auth_headers, access_key, secret_key, region, host, port, ) client = Client(transport="websockets") client.ws_set_options(headers=extra_headers) # Use client as normal from here client.on_connect = on_connect client.on_message = on_message client.on_publish = on_publish client.tls_set() client.connect(host, 443,60) return client
def get_client(project_id: str, cloud_region: str, registry_id: str, device_id: str, password: str, mqtt_bridge_hostname: str, mqtt_bridge_port: str, ca_certs: str): client_id = 'projects/{}/locations/{}/registries/{}/devices/{}'.format( project_id, cloud_region, registry_id, device_id) secho('Client ID: ', fg='bright_green', nl=False) echo('\'{}\''.format(client_id)) client = Client(client_id=client_id) client.username_pw_set(username='******', password=password) client.tls_set(ca_certs=ca_certs, tls_version=ssl.PROTOCOL_TLSv1_2) # Assign callbacks client.on_connect = on_connect client.on_publish = on_publish client.on_disconnect = on_disconnect client.on_message = on_message # Connect to MQTT bridge client.connect(mqtt_bridge_hostname, mqtt_bridge_port) client.loop_start() return client
def create_client(self, host, port, username, password, clientid, cafile=None): """Creating an MQTT Client Object""" client = MqttClient(clientid) if username and password: client.username_pw_set(username=username, password=password) else: self.logger.warn("Proceeding without username and password") if cafile: client.tls_set(ca_certs=cafile) else: self.logger.warn("Proceeding without certificate file") try: client.on_connect = self.on_connect client.on_message = self.on_message client.connect(host=host, port=port) except OSError as error: self.logger.error(error) return client
class MqttClient(TransportClient): def __init__(self, host_name: str, settings: MqttSettings): self.host_name = host_name self.settings = settings self.sub_topic_root = f"label_servers/print/{host_name}" self._callback: Optional[Callable[[PrintRequest], Any]] = None # Configure the client self.client = Client(self.host_name) # TLS settings if we have them if self.settings.tls_cafile is not None and self.settings.tls_cafile.strip( ): self.client.tls_set(self.settings.tls_cafile) self.client.connect(settings.mqtt_broker_host, settings.mqtt_broker_port, 60) self.client.on_connect = self._on_connect self.client.on_message = self._on_message def _on_connect(self, client: Client, user_data, flags, reason_code, properties=None): logger.info(f"Connected with reason code {reason_code}") sub_topic = f"{self.sub_topic_root}/#" logger.info(f"Subscribing to: {sub_topic}") client.subscribe(sub_topic) client.will_set(f"label_servers/status/{self.host_name}", json.dumps({"online": False})) def _on_message(self, client: Client, user_data, message: MQTTMessage): logger.info(f"Received Message: {message.topic}") try: # Remove topic root stripped = message.topic.replace(self.sub_topic_root, "").strip("/") # Print requests should be of the form "<printer_serial>/<mode>" parts = stripped.split("/") if len(parts) >= 2: serial, mode, *_ = parts request = PrintRequest(serial, RequestMode.parse(mode), message.payload) if self._callback: self._callback(request) except Exception as e: logger.error("Error processing received message", exc_info=e) def publish(self, status: HostStatus): self.client.publish(f"label_servers/status/{self.host_name}", json.dumps(status.as_dict())) def start(self, on_message: Callable[[PrintRequest], None]): self._callback = on_message self.client.loop_start()
def connect(client: mqtt.Client, args: argparse.Namespace): """Connect to an MQTT broker with supplied arguments.""" if args.username: client.username_pw_set(args.username, args.password) # TLS if args.tls: # TLS is enabled if args.tls_version is None: # Use highest TLS version args.tls_version = ssl.PROTOCOL_TLS if args.tls_ca_certs is not None: args.tls_ca_certs = os.path.expandvars(args.tls_ca_certs) if args.tls_certfile is not None: args.tls_certfile = os.path.expandvars(args.tls_certfile) if args.tls_keyfile is not None: args.tls_keyfile = os.path.expandvars(args.tls_keyfile) client.tls_set( ca_certs=args.tls_ca_certs, certfile=args.tls_certfile, keyfile=args.tls_keyfile, cert_reqs=getattr(ssl, args.tls_cert_reqs), tls_version=args.tls_version, ciphers=(args.tls_ciphers or None), ) client.connect(args.host, args.port)
def main(): influxdb_client = InfluxDBClient(INFLUXDB_HOST, INFLUXDB_PORT, INFLUXDB_USERNAME, INFLUXDB_PASSWORD, INFLUXDB_DATABASE) #First the database is initialized mqtt_client = MQTTClient( MQTT_CLIENT_ID, userdata=influxdb_client) #Then we create a client object mqtt_client.username_pw_set(MQTT_USERNAME, MQTT_PASSWORD) #and set the username and password for the MQTT client mqtt_client.tls_set() mqtt_client.on_connect = mqtt_connect_callback #We tell the client which functions are to be run on connecting mqtt_client.on_message = mqtt_message_callback #and on receiving a message mqtt_client.connect(MQTT_HOST, MQTT_PORT) #we can connect to the broker with the broker host and port mqtt_client.loop_forever()
def main(): influxdb_client = InfluxDBClient(INFLUXDB_HOST, INFLUXDB_PORT, INFLUXDB_USERNAME, INFLUXDB_PASSWORD, INFLUXDB_DATABASE) mqtt_client = MQTTClient(MQTT_CLIENT_ID, userdata=influxdb_client) mqtt_client.username_pw_set(MQTT_USERNAME, MQTT_PASSWORD) mqtt_client.tls_set() mqtt_client.on_connect = mqtt_connect_callback mqtt_client.on_message = mqtt_message_callback mqtt_client.connect(MQTT_HOST, MQTT_PORT) mqtt_client.loop_forever()
def create_client(host, port, username, password, cafile=None): """Creating an MQTT Client Object""" client = MqttClient() if username and password: client.username_pw_set(username=username, password=password) if cafile: client.tls_set(ca_certs=cafile) client.connect(host=host, port=port) return client
def create_client(host, port, username, password, clientid, cafile): """Creating an MQTT Client Object""" client = MqttClient(clientid) if username and password: client.username_pw_set(username=username, password=password) try: client.tls_set(ca_certs=cafile) except: print("Proceeding without certificate file") client.connect(host=host, port=port) return client
class MoodyBLEWrapper(Thread): def __init__(self, mac, host, ca_cert): Thread.__init__(self) self._mac = mac self._host = host self._ca_cert = ca_cert self._running = False self._mutex = Lock() self._client = Client(f"Moody{randint(100, 999)}") self._client.tls_set(ca_certs=ca_cert) # When receiving data with a delegate, you also receive a characteristic handle # This is a mapping of those characteristic for later usage self._handle_mappings = {} def run(self): self._running = True self._connect(host=self._host) with Peripheral(self._mac) as peripheral: for service in list(peripheral.getServices())[2:]: print(service, service.uuid.getCommonName()) char_uuids = [str(c.uuid) for c in service.getCharacteristics()] name_char = service.getCharacteristics(char_uuids[0])[0] value_char = service.getCharacteristics(char_uuids[1])[0] service_name = name_char.read().decode() self._handle_mappings[value_char.valHandle] = service_name mqtt_delegate = _MQTTDelegate(client=self._client, client_mutex=self._mutex, handle_map=self._handle_mappings) peripheral.withDelegate(mqtt_delegate) peripheral.writeCharacteristic(value_char.valHandle + 1, b"\x01\x00") while self._running: peripheral.waitForNotifications(1) peripheral.disconnect() def _connect(self, host, port=None): if not port: port = 8883 self._client.connect(host=host, port=port) self._client.loop_start() def stop(self): self._running = False self._client.loop_stop() self._client.disconnect()
class AzureIoT(IotProvider): def __init__(self, iot_provider_cfg): # 1. Set device_name. # 2. Set tls_version. # 3. Set MQTT Protocol version # 4. Call parent class' __init__ self.device_name = iot_provider_cfg["device_name"] self.tls_version = eval( f"ssl.PROTOCOL_TLSv1_{iot_provider_cfg['tls_version']}") self.mqtt_version = eval( f"mqtt.MQTTv{iot_provider_cfg['mqtt_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 on_disconnect(client, userdata, rc): print(f"Disconnected with code {rc}") 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.azure_iot_comm = Client(client_id=self.device_name, protocol=self.mqtt_version) self.azure_iot_comm.on_connect = self.on_connect self.azure_iot_comm.on_disconnect = self.on_disconnect self.azure_iot_comm.on_message = self.onmsg self.azure_iot_comm.username_pw_set( username=f"{self.iot_broker}/{self.device_name}") self.azure_iot_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.azure_iot_comm.connect(self.iot_broker, self.iot_port) self.azure_iot_comm.loop_start() def disconnect(self): self.azure_iot_comm.disconnect() def publish(self, publish_topic, msg_str, qos): # Overriding qos to 0 because Azure doesn't seem to like any other qos self.azure_iot_comm.publish(publish_topic, msg_str, qos=0)
def mqtt_handler(): global mqtt_client Client.connected_flag = False mqtt_client = Client() # set mosquitto broker password and username mqtt_client.username_pw_set(username=USERNAME, password=PASSWORD) # set TLS cert for the client mqtt_client.tls_set(ca_certs=TLS_CERT) mqtt_client.tls_insecure_set(True) 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/+' % ORDER_STATUS) mqtt_client.loop_forever() mqtt_client.disconnect()
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 _get_client(self, tls_cafile: Optional[str] = None, tls_certfile: Optional[str] = None, tls_keyfile: Optional[str] = None, tls_version: Optional[str] = None, tls_ciphers: Optional[str] = None, tls_insecure: Optional[bool] = None, username: Optional[str] = None, password: Optional[str] = None): from paho.mqtt.client import Client tls_cafile = self._expandpath(tls_cafile or self.tls_cafile) tls_certfile = self._expandpath(tls_certfile or self.tls_certfile) tls_keyfile = self._expandpath(tls_keyfile or self.tls_keyfile) tls_ciphers = tls_ciphers or self.tls_ciphers username = username or self.username password = password or self.password tls_version = tls_version or self.tls_version if tls_version: tls_version = self.get_tls_version(tls_version) if tls_insecure is None: tls_insecure = self.tls_insecure client = Client() if username and password: client.username_pw_set(username, password) if tls_cafile: client.tls_set(ca_certs=tls_cafile, certfile=tls_certfile, keyfile=tls_keyfile, tls_version=tls_version, ciphers=tls_ciphers) client.tls_insecure_set(tls_insecure) return client
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)
def mqtt(n: int, interval: float, host, port, client_id, tls: bool, username, password, interface, topic_prefix): """Publish inverter data to an MQTT broker. The default topic format is inverter/<serial number>/status, e.g. inverter/DW413B8080/status. The message value is a JSON object with all status data from the inverter. Example message value: {"operation_mode":"Normal","total_operation_time":45, "pv1_input_power":2822.0,"pv2_input_power":0.0,"pv1_voltage":586.5, "pv2_voltage":6.7,"pv1_current":4.8,"pv2_current":0.1, "output_power":2589.0,"energy_today":21.2,"energy_total":77.0, "grid_voltage":242.6,"grid_current":3.6,"grid_frequency":50.01, "internal_temperature":35.0} """ MQTTInverter = namedtuple("MQTTInverter", ["inverter", "topic", "serial_number"]) print("Connecting to {} inverter(s)".format(n)) mqtt_inverters = [] with connect_inverters(interface, n) as inverters: for i in inverters: serial_number = i.model()["serial_number"] print("Connected to inverter {} on IP {}".format( serial_number, i.addr)) mqtt_inverters.append( MQTTInverter(inverter=i, topic="{}/{}/status".format( topic_prefix, serial_number), serial_number=serial_number)) print("Connecting to MQTT broker") client = MQTTClient(client_id=client_id) if tls: client.tls_set() if username: client.username_pw_set(username, password) client.connect(host=host, port=port, bind_address=interface or '') client.loop_start() # Starts handling MQTT traffic in separate thread try: # Startup done topics = ", ".join(x.topic for x in mqtt_inverters) print( "Startup complete, now publishing status data every {} seconds to topic(s): {}" .format(interval, topics)) start_time = time() while True: for mqtt_inverter in mqtt_inverters: status = mqtt_inverter.inverter.status() message = json.dumps(status, cls=DecimalEncoder, separators=(',', ':')) # Compact encoding client.publish(topic=mqtt_inverter.topic, payload=message) # This doesn't suffer from drifting, however it will skip messages when # a message takes longer than the interval. sleep(interval - ((time() - start_time) % interval)) finally: # Disconnect MQTT on exception client.disconnect()
print(f"connected to {client._host}:{client._port}") client.subscribe('#') else: print("something went wrong") commissioner_client = Client('commissioner-client', transport='websockets') commissioner_client.on_connect = on_connect commissioner_client.on_message = on_message commissioner_client.connect(commissioner_config['server'], commissioner_config['port']) commissioner_client.loop_start() gw_publisher = Client('gw-publisher', transport='tcp') gw_publisher.on_connect = on_connect gw_publisher.username_pw_set(gateway_config['username'], gateway_config['password']) gw_publisher.tls_set(ca_certs=None, certfile=None, keyfile=None, cert_reqs=None, tls_version=PROTOCOL_TLS, ciphers=None) gw_publisher.connect(gateway_config['server'], gateway_config['port']) gw_publisher.loop_start() try: while True: if messages: topic, payload = messages.pop() print(f'About to publish on {topic} payload: {payload}') res = gw_publisher.publish(topic, payload) except KeyboardInterrupt: commissioner_client.disconnect() commissioner_client.loop_stop() gw_publisher.disconnect() gw_publisher.loop_stop()
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)
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 MqttConnection: def __init__(self, ip, port, username, password, cafile, connection_callback): self.logger = logging.getLogger("mqtt") self.mqtt = Client() if username is not None: self.mqtt.username_pw_set(username, password) if cafile is not None: self.mqtt.tls_set(ca_certs=cafile) self.mqtt.will_set("chromecast/maintenance/_bridge/online", payload="false", retain=True) self.mqtt.on_connect = self._on_connect self.mqtt.on_message = self._on_message self.ip = ip self.port = int(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) self.mqtt.publish("chromecast/maintenance/_bridge/online", "true", retain=True) # 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.publish("chromecast/maintenance/_bridge/online", "false", retain=True) self.mqtt.disconnect() self.mqtt.loop_stop()
def publish(self, topic: str, msg: Any, host: Optional[str] = None, port: int = 1883, reply_topic: Optional[str] = None, timeout: int = 60, tls_cafile: Optional[str] = None, tls_certfile: Optional[str] = None, tls_keyfile: Optional[str] = None, tls_version: Optional[str] = None, tls_ciphers: Optional[str] = None, username: Optional[str] = None, password: Optional[str] = None): """ Sends a message to a topic. :param topic: Topic/channel where the message will be delivered :param msg: Message to be sent. It can be a list, a dict, or a Message object. :param host: MQTT broker hostname/IP. :param port: MQTT broker port (default: 1883). :param reply_topic: If a ``reply_topic`` is specified, then the action will wait for a response on this topic. :param timeout: If ``reply_topic`` is set, use this parameter to specify the maximum amount of time to wait for a response (default: 60 seconds). :param tls_cafile: If TLS/SSL is enabled on the MQTT server and the certificate requires a certificate authority to authenticate it, `ssl_cafile` will point to the provided ca.crt file (default: None). :param tls_certfile: If TLS/SSL is enabled on the MQTT server and a client certificate it required, specify it here (default: None). :param tls_keyfile: If TLS/SSL is enabled on the MQTT server and a client certificate key it required, specify it here (default: None). :param tls_version: If TLS/SSL is enabled on the MQTT server and it requires a certain TLS version, specify it here (default: None). :param tls_ciphers: If TLS/SSL is enabled on the MQTT server and an explicit list of supported ciphers is required, specify it here (default: None). :param username: Specify it if the MQTT server requires authentication (default: None). :param password: Specify it if the MQTT server requires authentication (default: None). """ from paho.mqtt.client import Client if not host and not self.host: raise RuntimeError( 'No host specified and no default host configured') if not host: tls_cafile = self.tls_cafile tls_certfile = self.tls_certfile tls_keyfile = self.tls_keyfile tls_version = self.tls_version tls_ciphers = self.tls_ciphers username = self.username password = self.password client = Client() if username and password: client.username_pw_set(username, password) if tls_cafile: client.tls_set(ca_certs=tls_cafile, certfile=tls_certfile, keyfile=tls_keyfile, tls_version=tls_version, ciphers=tls_ciphers) # Try to parse it as a platypush message or dump it to JSON from a dict/list if isinstance(msg, dict) or isinstance(msg, list): msg = json.dumps(msg) # noinspection PyBroadException try: msg = Message.build(json.loads(msg)) except: pass client.connect(host, port, keepalive=timeout) response_buffer = io.BytesIO() try: response_received = threading.Event() if reply_topic: client.on_message = self._response_callback( reply_topic=reply_topic, event=response_received, buffer=response_buffer) client.subscribe(reply_topic) client.publish(topic, str(msg)) if not reply_topic: return client.loop_start() ok = response_received.wait(timeout=timeout) if not ok: raise TimeoutError('Response timed out') return response_buffer.getvalue() finally: response_buffer.close() # noinspection PyBroadException try: client.loop_stop() except: pass client.disconnect()
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)
except Exception as e: print(e) with open("config.json") as fd: config = json.load(fd) ttnConfig = config["thethingsnetwork"] influxConfig = config["influxdb"] canDB = cantools.database.load_file(config["can-spec"]) messageWhitelist = [] for key in config["messages"]: val = config["messages"][key] entry = (re.compile(fnmatch.translate(key)), val) messageWhitelist.append(entry) db = InfluxDBClient(influxConfig["host"], influxConfig["port"], influxConfig["username"], influxConfig["password"], influxConfig["database"]) mqtt = MQTTClient() mqtt.on_connect = on_connect mqtt.on_message = on_message if "tls_ca" in ttnConfig: mqtt.tls_set(ttnConfig["tls_ca"]) mqtt.username_pw_set(ttnConfig["username"], ttnConfig["password"]) mqtt.connect(ttnConfig["host"], ttnConfig["port"], 60) mqtt.loop_forever()
print("serial port reconnected") break except TypeError as e: print("type exc:"+str(e)) break sys.stdout.flush() client = Client(client_id = "Client485") devouscire=False # serialportname="/dev/cu.wchusbserialfa1340" serialportname="/dev/serial/by-path/pci-0000:00:1d.1-usb-0:2:1.0-port0" ser = serial.Serial(serialportname, 9600) client.username_pw_set("U1289$hr", "I8234%yu") #client.tls_set("/Volumes/Hdd/Users/ac/lavori/SolarThermostat/certs/ca.crt") client.tls_set("/home/andrea/HAServer/conf/mosquitto/ca.crt") client.connect("ha.caveve.it",8883) client.on_message = on_message client.on_connect = on_connect client.on_disconnect = on_disconnect #client.on_log = on_log client.loop_start() #client.publish(topic = "sta", payload = "485cli started") #client.subscribe("485gateway") t = threading.Thread(target=read_serial,daemon=None) t.start() #client.loop_forever(retry_first_connection=True) """ while True: value = input("Comando:\n") value=value.split() if value[0]=="Q":
token = {"device_id": device_name} token_str = base64.b64encode(json.dumps(token)) command = "/bin/echo -n %s | openssl dgst -sha256 -sign %s 2>/dev/null| openssl base64 2>/dev/null" % ( token_str, private_key) return_code, return_str = commands.getstatusoutput(command) signature = return_str.strip().replace('\n', '') aws_headers = { "IoTDemoAuthorizerToken": token_str, "X-Amz-CustomAuthorizer-Signature": signature, "X-Amz-CustomAuthorizer-Name": authorizer_name } client = Client(device_name, transport="websockets") client.ws_set_options(headers=aws_headers) client.tls_set(ca_certs=ca_certs_file) client.on_connect = on_connect client.on_message = on_message client.connect(iot_endpoint, 443, 60) def pub_msg(): try: pri_loopCount = 0 while True: print 'please input:', msg = raw_input() private_data = msg message = {} message['message'] = json.dumps({ "source": device_name,
class Translator(object): """Translates messages between the LifeSOS and MQTT interfaces.""" # Default interval to wait before resetting Trigger device state to Off AUTO_RESET_INTERVAL = 30 # Keys for Home Assistant MQTT discovery configuration HA_AVAILABILITY_TOPIC = 'availability_topic' HA_COMMAND_TOPIC = 'command_topic' HA_DEVICE_CLASS = 'device_class' HA_ICON = 'icon' HA_NAME = 'name' HA_PAYLOAD_ARM_AWAY = 'payload_arm_away' HA_PAYLOAD_ARM_HOME = 'payload_arm_home' HA_PAYLOAD_AVAILABLE = 'payload_available' HA_PAYLOAD_DISARM = 'payload_disarm' HA_PAYLOAD_NOT_AVAILABLE = 'payload_not_available' HA_PAYLOAD_OFF = 'payload_off' HA_PAYLOAD_ON = 'payload_on' HA_STATE_TOPIC = 'state_topic' HA_UNIQUE_ID = 'unique_id' HA_UNIT_OF_MEASUREMENT = 'unit_of_measurement' # Device class to classify the sensor type in Home Assistant HA_DC_DOOR = 'door' HA_DC_GAS = 'gas' HA_DC_HUMIDITY = 'humidity' HA_DC_ILLUMINANCE = 'illuminance' HA_DC_MOISTURE = 'moisture' HA_DC_MOTION = 'motion' HA_DC_SAFETY = 'safety' HA_DC_SMOKE = 'smoke' HA_DC_TEMPERATURE = 'temperature' HA_DC_VIBRATION = 'vibration' HA_DC_WINDOW = 'window' HA_DC_BATTERY = 'battery' # Icons in Home Assistant HA_ICON_RSSI = 'mdi:wifi' # Platforms in Home Assistant to represent our devices HA_PLATFORM_ALARM_CONTROL_PANEL = 'alarm_control_panel' HA_PLATFORM_BINARY_SENSOR = 'binary_sensor' HA_PLATFORM_SENSOR = 'sensor' HA_PLATFORM_SWITCH = 'switch' # Alarm states in Home Assistant HA_STATE_ARMED_AWAY = 'armed_away' HA_STATE_ARMED_HOME = 'armed_home' HA_STATE_DISARMED = 'disarmed' HA_STATE_PENDING = 'pending' HA_STATE_TRIGGERED = 'triggered' # Unit of measurement for Home Assistant sensors HA_UOM_CURRENT = 'A' HA_UOM_HUMIDITY = '%' HA_UOM_ILLUMINANCE = 'Lux' HA_UOM_RSSI = 'dB' HA_UOM_TEMPERATURE = '°C' # Ping MQTT broker this many seconds apart to check we're connected KEEP_ALIVE = 30 # Attempt reconnection this many seconds apart # (starts at min, doubles on retry until max reached) RECONNECT_MAX_DELAY = 120 RECONNECT_MIN_DELAY = 15 # Sub-topic to clear the alarm/warning LEDs on base unit and stop siren TOPIC_CLEAR_STATUS = 'clear_status' # Sub-topic to access the remote date/time TOPIC_DATETIME = 'datetime' # Sub-topic to provide alarm state that is recognised by Home Assistant TOPIC_HASTATE = 'ha_state' # Sub-topic that will be subscribed to on topics that can be set TOPIC_SET = 'set' def __init__(self, config: Config): self._config = config self._loop = asyncio.get_event_loop() self._shutdown = False self._get_task = None self._auto_reset_handles = {} self._state = None self._ha_state = None # Create LifeSOS base unit instance and attach callbacks self._baseunit = BaseUnit(self._config.lifesos.host, self._config.lifesos.port) if self._config.lifesos.password: self._baseunit.password = self._config.lifesos.password self._baseunit.on_device_added = self._baseunit_device_added self._baseunit.on_device_deleted = self._baseunit_device_deleted self._baseunit.on_event = self._baseunit_event self._baseunit.on_properties_changed = self._baseunit_properties_changed self._baseunit.on_switch_state_changed = self._baseunit_switch_state_changed # Create MQTT client instance self._mqtt = MQTTClient(client_id=self._config.mqtt.client_id, clean_session=False) self._mqtt.enable_logger() self._mqtt.will_set( '{}/{}'.format(self._config.translator.baseunit.topic, BaseUnit.PROP_IS_CONNECTED), str(False).encode(), QOS_1, True) self._mqtt.reconnect_delay_set(Translator.RECONNECT_MIN_DELAY, Translator.RECONNECT_MAX_DELAY) if self._config.mqtt.uri.username: self._mqtt.username_pw_set(self._config.mqtt.uri.username, self._config.mqtt.uri.password) if self._config.mqtt.uri.scheme == SCHEME_MQTTS: self._mqtt.tls_set() self._mqtt.on_connect = self._mqtt_on_connect self._mqtt.on_disconnect = self._mqtt_on_disconnect self._mqtt.on_message = self._mqtt_on_message self._mqtt_was_connected = False self._mqtt_last_connection = None self._mqtt_last_disconnection = None # Generate a list of topics we'll need to subscribe to self._subscribetopics = [] self._subscribetopics.append( SubscribeTopic( '{}/{}'.format(self._config.translator.baseunit.topic, Translator.TOPIC_CLEAR_STATUS), self._on_message_clear_status)) self._subscribetopics.append( SubscribeTopic( '{}/{}/{}'.format(self._config.translator.baseunit.topic, Translator.TOPIC_DATETIME, Translator.TOPIC_SET), self._on_message_set_datetime)) names = [BaseUnit.PROP_OPERATION_MODE] for name in names: self._subscribetopics.append( SubscribeTopic('{}/{}/{}'.format( self._config.translator.baseunit.topic, name, Translator.TOPIC_SET), self._on_message_baseunit, args=name)) for switch_number in self._config.translator.switches.keys(): switch_config = self._config.translator.switches.get(switch_number) if switch_config and switch_config.topic: self._subscribetopics.append( SubscribeTopic('{}/{}'.format(switch_config.topic, Translator.TOPIC_SET), self._on_message_switch, args=switch_number)) if self._config.translator.ha_birth_topic: self._subscribetopics.append( SubscribeTopic(self._config.translator.ha_birth_topic, self._on_ha_message)) # Also create a lookup dict for the topics to subscribe to self._subscribetopics_lookup = \ {st.topic: st for st in self._subscribetopics} # Create queue to store pending messages from our subscribed topics self._pending_messages = Queue() # # METHODS - Public # async def async_start(self) -> None: """Starts up the LifeSOS interface and connects to MQTT broker.""" self._shutdown = False # Start up the LifeSOS interface self._baseunit.start() # Connect to the MQTT broker self._mqtt_was_connected = False if self._config.mqtt.uri.port: self._mqtt.connect_async(self._config.mqtt.uri.hostname, self._config.mqtt.uri.port, keepalive=Translator.KEEP_ALIVE) else: self._mqtt.connect_async(self._config.mqtt.uri.hostname, keepalive=Translator.KEEP_ALIVE) # Start processing MQTT messages self._mqtt.loop_start() async def async_loop(self) -> None: """Loop indefinitely to process messages from our subscriptions.""" # Trap SIGINT and SIGTERM so that we can shutdown gracefully signal.signal(signal.SIGINT, self.signal_shutdown) signal.signal(signal.SIGTERM, self.signal_shutdown) try: while not self._shutdown: # Wait for next message self._get_task = self._loop.create_task( self._pending_messages.async_q.get()) try: message = await self._get_task except asyncio.CancelledError: _LOGGER.debug('Translator loop cancelled.') continue except Exception: # pylint: disable=broad-except # Log any exception but keep going _LOGGER.error( "Exception waiting for message to be delivered", exc_info=True) continue finally: self._get_task = None # Do subscribed topic callback to handle message try: subscribetopic = self._subscribetopics_lookup[ message.topic] subscribetopic.on_message(subscribetopic, message) except Exception: # pylint: disable=broad-except _LOGGER.error( "Exception processing message from subscribed topic: %s", message.topic, exc_info=True) finally: self._pending_messages.async_q.task_done() # Turn off is_connected flag before leaving self._publish_baseunit_property(BaseUnit.PROP_IS_CONNECTED, False) await asyncio.sleep(0) finally: signal.signal(signal.SIGINT, signal.SIG_DFL) async def async_stop(self) -> None: """Shuts down the LifeSOS interface and disconnects from MQTT broker.""" # Stop the LifeSOS interface self._baseunit.stop() # Cancel any outstanding auto reset tasks for item in self._auto_reset_handles.copy().items(): item[1].cancel() self._auto_reset_handles.pop(item[0]) # Stop processing MQTT messages self._mqtt.loop_stop() # Disconnect from the MQTT broker self._mqtt.disconnect() def signal_shutdown(self, sig, frame): """Flag shutdown when signal received.""" _LOGGER.debug('%s received; shutting down...', signal.Signals(sig).name) # pylint: disable=no-member self._shutdown = True if self._get_task: self._get_task.cancel() # Issue #8 - Cancel not processed until next message added to queue. # Just put a dummy object on the queue to ensure it is handled immediately. self._pending_messages.sync_q.put_nowait(None) # # METHODS - Private / Internal # def _mqtt_on_connect(self, client: MQTTClient, userdata: Any, flags: Dict[str, Any], result_code: int) -> None: # On error, log it and don't go any further; client will retry if result_code != CONNACK_ACCEPTED: _LOGGER.warning(connack_string(result_code)) # pylint: disable=no-member return # Successfully connected self._mqtt_last_connection = datetime.now() if not self._mqtt_was_connected: _LOGGER.debug("MQTT client connected to broker") self._mqtt_was_connected = True else: try: outage = self._mqtt_last_connection - self._mqtt_last_disconnection _LOGGER.warning( "MQTT client reconnected to broker. " "Outage duration was %s", str(outage)) except Exception: # pylint: disable=broad-except _LOGGER.warning("MQTT client reconnected to broker") # Republish the 'is_connected' state; this will have automatically # been set to False on MQTT client disconnection due to our will # (even though this app might still be connected to the LifeSOS unit) self._publish( '{}/{}'.format(self._config.translator.baseunit.topic, BaseUnit.PROP_IS_CONNECTED), self._baseunit.is_connected, True) # Subscribe to topics we are capable of actioning for subscribetopic in self._subscribetopics: self._mqtt.subscribe(subscribetopic.topic, subscribetopic.qos) def _mqtt_on_disconnect(self, client: MQTTClient, userdata: Any, result_code: int) -> None: # When disconnected from broker and we didn't initiate it... if result_code != MQTT_ERR_SUCCESS: _LOGGER.warning( "MQTT client lost connection to broker (RC: %i). " "Will attempt to reconnect periodically", result_code) self._mqtt_last_disconnection = datetime.now() def _mqtt_on_message(self, client: MQTTClient, userdata: Any, message: MQTTMessage): # Add message to our queue, to be processed on main thread self._pending_messages.sync_q.put_nowait(message) def _baseunit_device_added(self, baseunit: BaseUnit, device: Device) -> None: # Hook up callbacks for device that was added / discovered device.on_event = self._device_on_event device.on_properties_changed = self._device_on_properties_changed # Get configuration settings for device; don't go any further when # device is not included in the config device_config = self._config.translator.devices.get(device.device_id) if not device_config: _LOGGER.warning( "Ignoring device as it was not listed in the config file: %s", device) return # Publish initial property values for device if device_config.topic: props = device.as_dict() for name in props.keys(): self._publish_device_property(device_config.topic, device, name, getattr(device, name)) # When HA discovery is enabled, publish device configuration to it if self._config.translator.ha_discovery_prefix: if device_config.ha_name: self._publish_ha_device_config(device, device_config) if device_config.ha_name_rssi: self._publish_ha_device_rssi_config(device, device_config) if device_config.ha_name_battery: self._publish_ha_device_battery_config(device, device_config) def _baseunit_device_deleted( self, baseunit: BaseUnit, device: Device) -> None: # pylint: disable=no-self-use # Remove callbacks from deleted device device.on_event = None device.on_properties_changed = None def _baseunit_event(self, baseunit: BaseUnit, contact_id: ContactID): # When base unit event occurs, publish the event data # (don't bother retaining; events are time sensitive) event_data = json.dumps(contact_id.as_dict()) self._publish( '{}/event'.format(self._config.translator.baseunit.topic), event_data, False) # For clients that can't handle json, we will also provide the event # qualifier and code via these topics if contact_id.event_code: if contact_id.event_qualifier == EventQualifier.Event: self._publish( '{}/event_code'.format( self._config.translator.baseunit.topic), contact_id.event_code, False) elif contact_id.event_qualifier == EventQualifier.Restore: self._publish( '{}/restore_code'.format( self._config.translator.baseunit.topic), contact_id.event_code, False) # This is just for Home Assistant; the 'alarm_control_panel.mqtt' # component currently requires these hard-coded state values if contact_id.event_qualifier == EventQualifier.Event and \ contact_id.event_category == EventCategory.Alarm: self._ha_state = Translator.HA_STATE_TRIGGERED self._publish( '{}/{}'.format(self._config.translator.baseunit.topic, Translator.TOPIC_HASTATE), self._ha_state, True) def _baseunit_properties_changed( self, baseunit: BaseUnit, changes: List[PropertyChangedInfo]) -> None: # When base unit properties change, publish them has_connected = False for change in changes: self._publish_baseunit_property(change.name, change.new_value) # Also check if connection has just been established if change.name == BaseUnit.PROP_IS_CONNECTED and change.new_value: has_connected = True # On connection, publish config for Home Assistant if needed if has_connected: self._publish_ha_config() def _baseunit_switch_state_changed(self, baseunit: BaseUnit, switch_number: SwitchNumber, state: Optional[bool]) -> None: # When switch state changes, publish it switch_config = self._config.translator.switches.get(switch_number) if switch_config and switch_config.topic: self._publish(switch_config.topic, OnOff.parse_value(state), True) def _device_on_event(self, device: Device, event_code: DeviceEventCode) -> None: device_config = self._config.translator.devices.get(device.device_id) if device_config and device_config.topic: # When device event occurs, publish the event code # (don't bother retaining; events are time sensitive) self._publish('{}/event_code'.format(device_config.topic), event_code, False) # When it is a Trigger event, set state to On and schedule an # auto reset callback to occur after specified interval if event_code == DeviceEventCode.Trigger: self._publish(device_config.topic, OnOff.parse_value(True), True) handle = self._auto_reset_handles.get(device.device_id) if handle: handle.cancel() handle = self._loop.call_later( device_config.auto_reset_interval or Translator.AUTO_RESET_INTERVAL, self._auto_reset, device.device_id) self._auto_reset_handles[device.device_id] = handle def _auto_reset(self, device_id: int): # Auto reset a Trigger device to Off state device_config = self._config.translator.devices.get(device_id) if device_config and device_config.topic: self._publish(device_config.topic, OnOff.parse_value(False), True) self._auto_reset_handles.pop(device_id) def _device_on_properties_changed(self, device: Device, changes: List[PropertyChangedInfo]): # When device properties change, publish them device_config = self._config.translator.devices.get(device.device_id) if device_config and device_config.topic: for change in changes: self._publish_device_property(device_config.topic, device, change.name, change.new_value) def _publish_baseunit_property(self, name: str, value: Any) -> None: topic_parent = self._config.translator.baseunit.topic # Base Unit topic holds the state if name == BaseUnit.PROP_STATE: self._state = value self._publish(topic_parent, value, True) # This is just for Home Assistant; the 'alarm_control_panel.mqtt' # component currently requires these hard-coded state values topic = '{}/{}'.format(topic_parent, Translator.TOPIC_HASTATE) if value in {BaseUnitState.Disarm, BaseUnitState.Monitor}: self._ha_state = Translator.HA_STATE_DISARMED self._publish(topic, self._ha_state, True) elif value == BaseUnitState.Home: self._ha_state = Translator.HA_STATE_ARMED_HOME self._publish(topic, self._ha_state, True) elif value == BaseUnitState.Away: self._ha_state = Translator.HA_STATE_ARMED_AWAY self._publish(topic, self._ha_state, True) elif value in { BaseUnitState.AwayExitDelay, BaseUnitState.AwayEntryDelay }: self._ha_state = Translator.HA_STATE_PENDING self._publish(topic, self._ha_state, True) # Other supported properties in a topic using property name elif name in { BaseUnit.PROP_IS_CONNECTED, BaseUnit.PROP_ROM_VERSION, BaseUnit.PROP_EXIT_DELAY, BaseUnit.PROP_ENTRY_DELAY, BaseUnit.PROP_OPERATION_MODE }: self._publish('{}/{}'.format(topic_parent, name), value, True) def _publish_device_property(self, topic_parent: str, device: Device, name: str, value: Any) -> None: # Device topic holds the state if (not isinstance(device, SpecialDevice)) and \ name == Device.PROP_IS_CLOSED: # For regular device; this is the Is Closed property for magnet # sensors, otherwise default to Off for trigger-based devices if device.type == DeviceType.DoorMagnet: self._publish(topic_parent, OpenClosed.parse_value(value), True) else: self._publish(topic_parent, OnOff.Off, True) elif isinstance(device, SpecialDevice) and \ name == SpecialDevice.PROP_CURRENT_READING: # For special device, this is the current reading self._publish(topic_parent, value, True) # Category will have sub-topics for it's properties elif name == Device.PROP_CATEGORY: for prop in value.as_dict().items(): if prop[0] in {'code', 'description'}: self._publish( '{}/{}/{}'.format(topic_parent, name, prop[0]), prop[1], True) # Flag enums; expose as sub-topics with a bool state per flag elif name == Device.PROP_CHARACTERISTICS: for item in iter(DCFlags): self._publish('{}/{}/{}'.format(topic_parent, name, item.name), bool(value & item.value), True) elif name == Device.PROP_ENABLE_STATUS: for item in iter(ESFlags): self._publish('{}/{}/{}'.format(topic_parent, name, item.name), bool(value & item.value), True) elif name == Device.PROP_SWITCHES: for item in iter(SwitchFlags): self._publish('{}/{}/{}'.format(topic_parent, name, item.name), bool(value & item.value), True) elif name == SpecialDevice.PROP_SPECIAL_STATUS: for item in iter(SSFlags): self._publish('{}/{}/{}'.format(topic_parent, name, item.name), bool(value & item.value), True) # Device ID; value should be formatted as hex elif name == Device.PROP_DEVICE_ID: self._publish('{}/{}'.format(topic_parent, name), '{:06x}'.format(value), True) # Other supported properties in a topic using property name elif name in { Device.PROP_DEVICE_ID, Device.PROP_ZONE, Device.PROP_TYPE, Device.PROP_RSSI_DB, Device.PROP_RSSI_BARS, SpecialDevice.PROP_HIGH_LIMIT, SpecialDevice.PROP_LOW_LIMIT, SpecialDevice.PROP_CONTROL_LIMIT_FIELDS_EXIST, SpecialDevice.PROP_CONTROL_HIGH_LIMIT, SpecialDevice.PROP_CONTROL_LOW_LIMIT }: self._publish('{}/{}'.format(topic_parent, name), value, True) def _publish_ha_config(self): # Skip if Home Assistant discovery disabled if not self._config.translator.ha_discovery_prefix: return # Publish config for the base unit when enabled if self._config.translator.baseunit.ha_name: self._publish_ha_baseunit_config(self._config.translator.baseunit) # Publish config for each device when enabled for device_id in self._config.translator.devices.keys(): if self._shutdown: return device_config = self._config.translator.devices[device_id] device = self._baseunit.devices.get(device_id) if device: if device_config.ha_name: self._publish_ha_device_config(device, device_config) if device_config.ha_name_rssi: self._publish_ha_device_rssi_config(device, device_config) if device_config.ha_name_battery: self._publish_ha_device_battery_config( device, device_config) # Publish config for each switch when enabled for switch_number in self._config.translator.switches.keys(): if self._shutdown: return switch_config = self._config.translator.switches[switch_number] if switch_config.ha_name: self._publish_ha_switch_config(switch_number, switch_config) def _publish_ha_baseunit_config(self, baseunit_config: TranslatorBaseUnitConfig): # Generate message that can be used to automatically configure the # alarm control panel in Home Assistant using MQTT Discovery message = { Translator.HA_NAME: baseunit_config.ha_name, Translator.HA_UNIQUE_ID: '{}'.format(PROJECT_NAME), Translator.HA_STATE_TOPIC: '{}/{}'.format(baseunit_config.topic, Translator.TOPIC_HASTATE), Translator.HA_COMMAND_TOPIC: '{}/{}/{}'.format(baseunit_config.topic, BaseUnit.PROP_OPERATION_MODE, Translator.TOPIC_SET), Translator.HA_PAYLOAD_DISARM: str(OperationMode.Disarm), Translator.HA_PAYLOAD_ARM_HOME: str(OperationMode.Home), Translator.HA_PAYLOAD_ARM_AWAY: str(OperationMode.Away), Translator.HA_AVAILABILITY_TOPIC: '{}/{}'.format(baseunit_config.topic, BaseUnit.PROP_IS_CONNECTED), Translator.HA_PAYLOAD_AVAILABLE: str(True), Translator.HA_PAYLOAD_NOT_AVAILABLE: str(False), } self._publish( '{}/{}/{}/config'.format( self._config.translator.ha_discovery_prefix, Translator.HA_PLATFORM_ALARM_CONTROL_PANEL, message[Translator.HA_UNIQUE_ID]), json.dumps(message), False) def _publish_ha_device_config(self, device: Device, device_config: TranslatorDeviceConfig): # Generate message that can be used to automatically configure the # device in Home Assistant using MQTT Discovery message = { Translator.HA_NAME: device_config.ha_name, Translator.HA_UNIQUE_ID: '{}_{:06x}'.format(PROJECT_NAME, device.device_id), Translator.HA_STATE_TOPIC: device_config.topic, Translator.HA_AVAILABILITY_TOPIC: '{}/{}'.format(self._config.translator.baseunit.topic, BaseUnit.PROP_IS_CONNECTED), Translator.HA_PAYLOAD_AVAILABLE: str(True), Translator.HA_PAYLOAD_NOT_AVAILABLE: str(False), } if device.type in { DeviceType.FloodDetector, DeviceType.FloodDetector2 }: ha_platform = Translator.HA_PLATFORM_BINARY_SENSOR message[Translator.HA_DEVICE_CLASS] = Translator.HA_DC_MOISTURE message[Translator.HA_PAYLOAD_ON] = str(OnOff.On) message[Translator.HA_PAYLOAD_OFF] = str(OnOff.Off) elif device.type in {DeviceType.MedicalButton}: ha_platform = Translator.HA_PLATFORM_BINARY_SENSOR message[Translator.HA_DEVICE_CLASS] = Translator.HA_DC_SAFETY message[Translator.HA_PAYLOAD_ON] = str(OnOff.On) message[Translator.HA_PAYLOAD_OFF] = str(OnOff.Off) elif device.type in { DeviceType.AnalogSensor, DeviceType.AnalogSensor2 }: ha_platform = Translator.HA_PLATFORM_BINARY_SENSOR message[Translator.HA_PAYLOAD_ON] = str(OnOff.On) message[Translator.HA_PAYLOAD_OFF] = str(OnOff.Off) elif device.type in {DeviceType.SmokeDetector}: ha_platform = Translator.HA_PLATFORM_BINARY_SENSOR message[Translator.HA_DEVICE_CLASS] = Translator.HA_DC_SMOKE message[Translator.HA_PAYLOAD_ON] = str(OnOff.On) message[Translator.HA_PAYLOAD_OFF] = str(OnOff.Off) elif device.type in { DeviceType.PressureSensor, DeviceType.PressureSensor2 }: ha_platform = Translator.HA_PLATFORM_BINARY_SENSOR message[Translator.HA_DEVICE_CLASS] = Translator.HA_DC_MOTION message[Translator.HA_PAYLOAD_ON] = str(OnOff.On) message[Translator.HA_PAYLOAD_OFF] = str(OnOff.Off) elif device.type in { DeviceType.CODetector, DeviceType.CO2Sensor, DeviceType.CO2Sensor2, DeviceType.GasDetector }: ha_platform = Translator.HA_PLATFORM_BINARY_SENSOR message[Translator.HA_DEVICE_CLASS] = Translator.HA_DC_GAS message[Translator.HA_PAYLOAD_ON] = str(OnOff.On) message[Translator.HA_PAYLOAD_OFF] = str(OnOff.Off) elif device.type in {DeviceType.DoorMagnet}: ha_platform = Translator.HA_PLATFORM_BINARY_SENSOR message[Translator.HA_DEVICE_CLASS] = Translator.HA_DC_DOOR message[Translator.HA_PAYLOAD_ON] = str(OpenClosed.Open) message[Translator.HA_PAYLOAD_OFF] = str(OpenClosed.Closed) elif device.type in {DeviceType.VibrationSensor}: ha_platform = Translator.HA_PLATFORM_BINARY_SENSOR message[Translator.HA_DEVICE_CLASS] = Translator.HA_DC_VIBRATION message[Translator.HA_PAYLOAD_ON] = str(OnOff.On) message[Translator.HA_PAYLOAD_OFF] = str(OnOff.Off) elif device.type in {DeviceType.PIRSensor}: ha_platform = Translator.HA_PLATFORM_BINARY_SENSOR message[Translator.HA_DEVICE_CLASS] = Translator.HA_DC_MOTION message[Translator.HA_PAYLOAD_ON] = str(OnOff.On) message[Translator.HA_PAYLOAD_OFF] = str(OnOff.Off) elif device.type in {DeviceType.GlassBreakDetector}: ha_platform = Translator.HA_PLATFORM_BINARY_SENSOR message[Translator.HA_DEVICE_CLASS] = Translator.HA_DC_WINDOW message[Translator.HA_PAYLOAD_ON] = str(OnOff.On) message[Translator.HA_PAYLOAD_OFF] = str(OnOff.Off) elif device.type in {DeviceType.HumidSensor, DeviceType.HumidSensor2}: ha_platform = Translator.HA_PLATFORM_SENSOR message[Translator.HA_DEVICE_CLASS] = Translator.HA_DC_HUMIDITY message[ Translator.HA_UNIT_OF_MEASUREMENT] = Translator.HA_UOM_HUMIDITY elif device.type in {DeviceType.TempSensor, DeviceType.TempSensor2}: ha_platform = Translator.HA_PLATFORM_SENSOR message[Translator.HA_DEVICE_CLASS] = Translator.HA_DC_TEMPERATURE message[Translator. HA_UNIT_OF_MEASUREMENT] = Translator.HA_UOM_TEMPERATURE elif device.type in {DeviceType.LightSensor, DeviceType.LightDetector}: ha_platform = Translator.HA_PLATFORM_SENSOR message[Translator.HA_DEVICE_CLASS] = Translator.HA_DC_ILLUMINANCE message[Translator. HA_UNIT_OF_MEASUREMENT] = Translator.HA_UOM_ILLUMINANCE elif device.type in { DeviceType.ACCurrentMeter, DeviceType.ACCurrentMeter2, DeviceType.ThreePhaseACMeter }: ha_platform = Translator.HA_PLATFORM_SENSOR message[ Translator.HA_UNIT_OF_MEASUREMENT] = Translator.HA_UOM_CURRENT else: _LOGGER.warning( "Device type '%s' cannot be represented in Home " "Assistant and will be skipped.", str(device.type)) return self._publish( '{}/{}/{}/config'.format( self._config.translator.ha_discovery_prefix, ha_platform, message[Translator.HA_UNIQUE_ID]), json.dumps(message), False) def _publish_ha_device_rssi_config(self, device: Device, device_config: TranslatorDeviceConfig): # Generate message that can be used to automatically configure a sensor # for the device's RSSI in Home Assistant using MQTT Discovery message = { Translator.HA_NAME: device_config.ha_name_rssi, Translator.HA_UNIQUE_ID: '{}_{:06x}_RSSI'.format(PROJECT_NAME, device.device_id), Translator.HA_ICON: Translator.HA_ICON_RSSI, Translator.HA_STATE_TOPIC: '{}/{}'.format(device_config.topic, Device.PROP_RSSI_DB), Translator.HA_UNIT_OF_MEASUREMENT: Translator.HA_UOM_RSSI, Translator.HA_AVAILABILITY_TOPIC: '{}/{}'.format(self._config.translator.baseunit.topic, BaseUnit.PROP_IS_CONNECTED), Translator.HA_PAYLOAD_AVAILABLE: str(True), Translator.HA_PAYLOAD_NOT_AVAILABLE: str(False), } self._publish( '{}/{}/{}/config'.format( self._config.translator.ha_discovery_prefix, Translator.HA_PLATFORM_SENSOR, message[Translator.HA_UNIQUE_ID]), json.dumps(message), False) def _publish_ha_device_battery_config( self, device: Device, device_config: TranslatorDeviceConfig): # Generate message that can be used to automatically configure a binary # sensor for the device's battery state in Home Assistant using # MQTT Discovery message = { Translator.HA_NAME: device_config.ha_name_battery, Translator.HA_UNIQUE_ID: '{}_{:06x}_battery'.format(PROJECT_NAME, device.device_id), Translator.HA_DEVICE_CLASS: Translator.HA_DC_BATTERY, Translator.HA_PAYLOAD_ON: str(DeviceEventCode.BatteryLow), Translator.HA_PAYLOAD_OFF: str(DeviceEventCode.PowerOnReset), Translator.HA_STATE_TOPIC: '{}/event_code'.format(device_config.topic), Translator.HA_AVAILABILITY_TOPIC: '{}/{}'.format(self._config.translator.baseunit.topic, BaseUnit.PROP_IS_CONNECTED), Translator.HA_PAYLOAD_AVAILABLE: str(True), Translator.HA_PAYLOAD_NOT_AVAILABLE: str(False), } self._publish( '{}/{}/{}/config'.format( self._config.translator.ha_discovery_prefix, Translator.HA_PLATFORM_BINARY_SENSOR, message[Translator.HA_UNIQUE_ID]), json.dumps(message), False) def _publish_ha_switch_config(self, switch_number: SwitchNumber, switch_config: TranslatorSwitchConfig): # Generate message that can be used to automatically configure the # switch in Home Assistant using MQTT Discovery message = { Translator.HA_NAME: switch_config.ha_name, Translator.HA_UNIQUE_ID: '{}_{}'.format(PROJECT_NAME, str(switch_number).lower()), Translator.HA_STATE_TOPIC: switch_config.topic, Translator.HA_COMMAND_TOPIC: '{}/{}'.format(switch_config.topic, Translator.TOPIC_SET), Translator.HA_PAYLOAD_ON: str(OnOff.On), Translator.HA_PAYLOAD_OFF: str(OnOff.Off), Translator.HA_AVAILABILITY_TOPIC: '{}/{}'.format(self._config.translator.baseunit.topic, BaseUnit.PROP_IS_CONNECTED), Translator.HA_PAYLOAD_AVAILABLE: str(True), Translator.HA_PAYLOAD_NOT_AVAILABLE: str(False), } self._publish( '{}/{}/{}/config'.format( self._config.translator.ha_discovery_prefix, Translator.HA_PLATFORM_SWITCH, message[Translator.HA_UNIQUE_ID]), json.dumps(message), False) def _publish(self, topic: str, payload: Any, retain: bool) -> None: self._mqtt.publish(topic, payload, QOS_1, retain) def _on_message_baseunit(self, subscribetopic: SubscribeTopic, message: MQTTMessage) -> None: if subscribetopic.args == BaseUnit.PROP_OPERATION_MODE: # Set operation mode name = None if not message.payload else message.payload.decode() operation_mode = OperationMode.parse_name(name) if operation_mode is None: _LOGGER.warning("Cannot set operation_mode to '%s'", name) return if operation_mode == OperationMode.Disarm and \ self._state == BaseUnitState.Disarm and \ self._ha_state == Translator.HA_STATE_TRIGGERED: # Special case to ensure HA can return from triggered state # when triggered by an alarm in Disarm mode (eg. panic, # tamper)... the set disarm operation will not generate a # response from the base unit as there is no change, so we # need to reset 'ha_state' here. _LOGGER.debug("Resetting triggered ha_state in disarmed mode") self._ha_state = Translator.HA_STATE_DISARMED self._publish( '{}/{}'.format(self._config.translator.baseunit.topic, Translator.TOPIC_HASTATE), self._ha_state, True) self._loop.create_task( self._baseunit.async_set_operation_mode(operation_mode)) else: raise NotImplementedError def _on_message_clear_status(self, subscribetopic: SubscribeTopic, message: MQTTMessage) -> None: # Clear the alarm/warning LEDs on base unit and stop siren self._loop.create_task(self._baseunit.async_clear_status()) def _on_message_set_datetime(self, subscribetopic: SubscribeTopic, message: MQTTMessage) -> None: # Set remote date/time to specified date/time (or current if None) value = None if not message.payload else message.payload.decode() if value: value = dateutil.parser.parse(value) self._loop.create_task(self._baseunit.async_set_datetime(value)) def _on_message_switch(self, subscribetopic: SubscribeTopic, message: MQTTMessage) -> None: # Turn a switch on / off switch_number = subscribetopic.args name = None if not message.payload else message.payload.decode() state = OnOff.parse_name(name) if state is None: _LOGGER.warning("Cannot set switch %s to '%s'", switch_number, name) return self._loop.create_task( self._baseunit.async_set_switch_state(switch_number, bool(state.value))) def _on_ha_message(self, subscribetopic: SubscribeTopic, message: MQTTMessage) -> None: # When Home Assistant comes online, publish our configuration to it payload = None if not message.payload else message.payload.decode() if not payload: return if payload == self._config.translator.ha_birth_payload: self._publish_ha_config()
class Daemon(): """MQTTToRDD Daemon.""" def __init__(self, config, foreground=False): self.cfg = config self.logger = logging.getLogger('MQTToRRD') self.logger.setLevel(self.cfg.log_level) formatter = logging.Formatter(self.cfg.log_format) self.client = None if foreground: self.handler = logging.StreamHandler() self.cfg.log_handler = "stderr" elif self.cfg.log_handler == "file": if sys.platform == 'windows': self.handler = logging.FileHandler(self.cfg.log_file, encoding="utf-8") else: self.handler = WatchedFileHandler(self.cfg.log_file, encoding="utf-8") else: self.handler = SysLogHandler(self.cfg.log_syslog, SysLogHandler.LOG_DAEMON) for hdlr in logger.root.handlers: # reset root logger handlers logger.root.removeHandler(hdlr) logger.root.addHandler(self.handler) self.handler.setFormatter(formatter) def check(self): """Check configuration.""" for section in self.cfg.sections(): # this check configuration values if section.startswith("/"): self.cfg.get_topic(section) # read from config elif section.startswith("$SYS/"): self.cfg.get_topic(section) # read from config self.logger.info("Configuration looks OK") if not isdir(self.cfg.data_dir): raise RuntimeError("Data dir `%s' does not exist." % self.cfg.data_dir) if not access(self.cfg.data_dir, R_OK | W_OK): raise RuntimeError("Data dir `%s' is not readable and writable" % self.cfg.data_dir) if self.cfg.log_handler == "file" and \ access(self.cfg.log_file, R_OK | W_OK) and \ isdir(dirname(self.cfg.log_file)) and \ access(dirname(self.cfg.log_file), R_OK | W_OK): raise RuntimeError("Could not write to log") @staticmethod def on_connect(client, daemon, flags, res): """connect mqtt handler.""" # pylint: disable=unused-argument daemon.logger.info("Connected to server") for sub in daemon.cfg.subscriptions: daemon.logger.info("Subscribing to topic: %s", sub) client.subscribe(sub) @staticmethod def on_message(client, daemon, msg): # pylint: disable=unused-argument """message mqtt handler.""" daemon.logger.info( "Message received on topic %s with QoS %s and payload `%s'", msg.topic, msg.qos, msg.payload) try: value = float(msg.payload) except ValueError: daemon.logger.warning( "Unable to get float from topic %s and payload %s", msg.topic, msg.payload) return topic = msg.topic.replace('.', '_') topic = topic[1:] if topic.startswith('/') else topic rrd_path = join(daemon.cfg.data_dir, dirname(topic), "%s.rrd" % basename(topic)) daemon.rrd(rrd_path, msg.topic, value) def rrd(self, rrd_path, topic, value): """Create or update RRD file.""" dir_path = dirname(rrd_path) if not isdir(dir_path): self.logger.debug("Creating topic directory %s", dir_path) makedirs(dir_path) if not exists(rrd_path): self.logger.debug("Creatting RRD file %s", rrd_path) # pylint: disable=invalid-name step, ds, rra = self.cfg.find_topic(topic) ds = ds.format(topic=basename(topic)) try: create_rrd(rrd_path, "--step", str(step), "--start", "0", ds, *rra) except (ProgrammingError, OperationalError) as exc: self.logger.error("Could not create RRD for topic %s: %s", topic, str(exc)) self.logger.info("Updating %s with value %f", topic, value) try: update_rrd(rrd_path, "N:%f" % value) except (ProgrammingError, OperationalError) as exc: self.logger.error("Could not log value %f to RRD for topic %s: %s", value, topic, str(exc)) def run(self, daemon=True): """Run daemon.""" self.check() while True: try: self.client = Client(client_id=self.cfg.client_id, userdata=self) self.client.on_connect = Daemon.on_connect self.client.on_message = Daemon.on_message if self.cfg.tls: self.client.tls_set(ca_certs=self.cfg.ca_certs, certfile=self.cfg.certfile, keyfile=self.cfg.keyfile) self.logger.debug("Attempting to connect to server %s:%s", self.cfg.hostname, self.cfg.port) self.client.connect(self.cfg.hostname, self.cfg.port, self.cfg.keepalive) self.logger.info("Connected to %s:%s", self.cfg.hostname, self.cfg.port) self.client.loop_forever() return 0 except Exception as exc: # pylint: disable=broad-except logging.debug("%s", format_exc()) self.logger.debug("%s", format_exc()) self.logger.fatal("%s", exc) if not daemon: return 1 sleep(30) def shutdown(self, signum, frame): """Signal handler for termination.""" # pylint: disable=unused-argument self.logger.info("Shutting down with signal %s", Signals(signum).name) self.client.disconnect() sys.exit(1)
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
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)
def main(): parser = argparse.ArgumentParser() parser.add_argument("--POD-CONFIG", required=True) parser.add_argument("--MQTT-SERVER", required=True, default=None, nargs="?") parser.add_argument("--MQTT-PORT", required=False, default="1881", nargs="?") parser.add_argument("--MQTT-SSL", required=False, default="", nargs="?") parser.add_argument("--MQTT-CLIENTID", required=True, default="", nargs="?") parser.add_argument("--MQTT-TOPIC", required=True, default="", nargs="?") parser.add_argument("--LOG-LEVEL", required=False, default="DEBUG", nargs="?") parser.add_argument("--LOG-FILE", required=False, default=None, nargs="?") args = parser.parse_args() formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") ch = logging.StreamHandler(sys.stdout) ch.setFormatter(formatter) ch.setLevel(level=args.LOG_LEVEL) logging.basicConfig(level=args.LOG_LEVEL, handlers=[ch]) if args.LOG_FILE: fh = logging.FileHandler(filename=args.LOG_FILE) fh.setFormatter(formatter) fh.setLevel(level=args.LOG_LEVEL) logging.getLogger().addHandler(fh) pod = Pod.Load(args.POD_CONFIG) mqtt_client = Client(client_id=args.MQTT_CLIENTID, clean_session=False, protocol=MQTTv311, transport="tcp") if args.MQTT_SSL != "": mqtt_client.tls_set(certfile=None, keyfile=None, cert_reqs=ssl.CERT_REQUIRED, tls_version=ssl.PROTOCOL_TLSv1_2, ciphers=None) mqtt_client.tls_insecure_set(True) mqtt_client.reconnect_delay_set(min_delay=5, max_delay=120) mqtt_client.retry_first_connection = True pdm = Pdm(pod) processor = Processor(mqtt_client, args.MQTT_TOPIC, pdm) processor.start(args.MQTT_SERVER, int(args.MQTT_PORT), 30) try: while not exit_event.wait(timeout=10): pass except KeyboardInterrupt: pass processor.stop()