""" powermon / libs / mqttbroker.py """ import logging from time import sleep import paho.mqtt.client as mqtt_client from powermon.libs.config import safe_config # Set-up logger log = logging.getLogger("mqttbroker") class MqttBroker: """ Wrapper for mqtt broker connectivity and message proccessing """ def __str__(self): if self.disabled: return "MqttBroker DISABLED" else: return f"MqttBroker name: {self.name}, port: {self.port}, user: {self.username}" @classmethod def from_config(cls, config=None) -> 'MqttBroker': """ build the mqtt broker object from a config dict """ log.debug("mqttbroker config: %s", safe_config(config)) if config: name = config.get("name") port = config.get("port", 1883) username = config.get("username") password = config.get("password") mqtt_broker = cls(name=name, port=port, username=username, password=password) mqtt_broker.adhoc_topic = config.get("adhoc_topic") mqtt_broker.adhoc_result_topic = config.get("adhoc_result_topic") return mqtt_broker else: return cls(name=None) def __init__(self, name, port=None, username=None, password=None): self.name = name self.port = port self.username = username self.password = password self.is_connected = False if self.name is None: self.disabled = True else: self.disabled = False self.mqttc = mqtt_client.Client() @property def adhoc_topic(self) -> str: """ return the adhoc command topic """ return getattr(self, "_adhoc_topic", None) @adhoc_topic.setter def adhoc_topic(self, value): log.debug("setting adhoc topic to: %s", value) self._adhoc_topic = value @property def adhoc_result_topic(self) -> str: """ return the adhoc result topic """ return getattr(self, "_adhoc_result_topic", None) @adhoc_result_topic.setter def adhoc_result_topic(self, value): log.debug("setting adhoc result topic to: %s", value) self._adhoc_result_topic = value def on_connect(self, client, userdata, flags, rc): """ callback for connect """ # 0: Connection successful # 1: Connection refused - incorrect protocol version # 2: Connection refused - invalid client identifier # 3: Connection refused - server unavailable # 4: Connection refused - bad username or password # 5: Connection refused - not authorised # 6-255: Currently unused. log.debug("on_connect called - client: %s, userdata: %s, flags: %s", client, userdata, flags) connection_result = [ "Connection successful", "Connection refused - incorrect protocol version", "Connection refused - invalid client identifier", "Connection refused - server unavailable", "Connection refused - bad username or password", "Connection refused - not authorised", ] log.debug("MqttBroker connection returned result: %s %s", rc, connection_result[rc]) if rc == 0: self.is_connected = True return self.is_connected = False def on_disconnect(self, client, userdata, rc): """ callback for disconnect """ log.debug("on_disconnect called - client: %s, userdata: %s, rc: %s", client, userdata, rc) self.is_connected = False def connect(self): """ connect to mqtt broker """ if self.disabled: log.info("MQTT broker not enabled, was a broker name defined? '%s'", self.name) return if not self.name: log.info("MQTT could not connect as no broker name") return self.mqttc.on_connect = self.on_connect self.mqttc.on_disconnect = self.on_disconnect # if name is screen just return without connecting if self.name == "screen": # allows checking of message formats return try: log.debug("Connecting to %s on port %s", self.name, self.port) if self.username: # auth = {"username": mqtt_user, "password": mqtt_pass} _password = "********" if self.password is not None else "None" log.info("Using mqtt authentication, username: %s, password: %s", self.username, _password) self.mqttc.username_pw_set(self.username, password=self.password) else: log.debug("No mqtt authentication used") # auth = None self.mqttc.connect(self.name, port=self.port, keepalive=60) self.mqttc.loop_start() sleep(1) except (ConnectionRefusedError, OSError) as ex: log.warning("%s connection failed: '%s'", self.name, ex) def start(self): """ start mqtt broker """ if self.disabled: return if self.is_connected: self.mqttc.loop_start() def stop(self): """ stop mqtt broker """ log.debug("Stopping mqttbroker connection") if self.disabled: return self.mqttc.loop_stop() # def set(self, variable, value): # setattr(self, variable, value) # def update(self, variable, value): # # only override if value is not None # if value is None: # return # setattr(self, variable, value) def subscribe(self, topic, callback): """ subscribe to a mqtt topic """ if not self.name: return if self.disabled: return # check if connected, connect if not if not self.is_connected: log.debug("Not connected, connecting") self.connect() # Register callback self.mqttc.on_message = callback if self.is_connected: # Subscribe to command topic log.debug("Subscribing to topic: %s", topic) self.mqttc.subscribe(topic, qos=0) else: log.warning("Did not subscribe to topic: %s as not connected to broker", topic) def post_adhoc_command(self, command_code): """ shortcut function to publish an adhoc command """ self.publish(topic=self.adhoc_topic, payload=command_code) def post_adhoc_result(self, payload): """ shortcut function to publish the results of an adhoc command """ self.publish(topic=self.adhoc_result_topic, payload=payload) def publish(self, topic: str = None, payload: str = None): """ function to publish messages to mqtt broker """ if self.disabled: log.debug("Cannot publish msg as mqttbroker disabled") return # log.debug("Publishing '%s' to '%s'", payload, topic) if self.name == "screen": print(f"mqtt debug - topic: '{topic}', payload: '{payload}'") return # check if connected, connect if not if not self.is_connected: log.debug("Not connected, connecting") self.connect() sleep(1) if not self.is_connected: log.warning("mqtt broker did not connect") return if isinstance(topic, bytes): topic = topic.decode('utf-8') if isinstance(payload, bytes): payload = payload.decode('utf-8') try: infot = self.mqttc.publish(topic, payload) infot.wait_for_publish(5) except (OSError, RuntimeError, ValueError) as e: log.warning("mqtt publish failed: %s", e) # def setAdhocCommands(self, config={}, callback=None): # if not config: # return # if self.disabled: # log.debug("Cannot setAdhocCommands as mqttbroker disabled") # return # adhoc_commands = config.get("adhoc_commands") # # sub to command topic if defined # adhoc_commands_topic = adhoc_commands.get("topic") # if adhoc_commands_topic is not None: # log.info("Setting adhoc commands topic to %s", adhoc_commands_topic) # self.subscribe(adhoc_commands_topic, callback)