Beispiel #1
0
    def __init__(self, configFile):
        super(Homie, self).__init__()
        atexit.register(self._exitus)
        signal.signal(signal.SIGTERM, self._sigTerm)
        signal.signal(signal.SIGHUP, self._sigHup)

        self._initAttrs(configFile)
        if not self.host:
            raise ValueError("No host specified.")

        self.startTime = time.time()
        self.fwname = None
        self.fwversion = None
        self.nodes = []
        self.timers = []
        self.subscriptions = []
        self.subscribe_all_forced = False

        self.mqtt_topic = str("/".join([
            self.baseTopic,
            self.deviceId,
        ]))

        self._setupCalled = False
        self._mqtt_connected = False  # connected
        self._mqtt_subscribed = False  # subscribed
        clientId = "Homie-" + str(self.deviceId)

        try:
            self.mqtt = HomieMqtt(self, clientId)
        except Exception as e:
            raise e
Beispiel #2
0
    def __init__(self, **kwargs):
        super(Homie, self).__init__()
        logger.debug("kwargs: {}".format(kwargs))
        self.startTime = time.time()
        self.fwname = None
        self.fwversion = None
        self.baseTopic = kwargs.get("TOPIC")
        self.deviceId = kwargs.get("DEVICE_ID")
        self.deviceName = kwargs.get("DEVICE_NAME")
        self.nodes = []

        self.mqtt_topic = "/".join([
            self.baseTopic,
            self.deviceId,
        ])

        self.mqtt = HomieMqtt(self, self.deviceId)
        self.host = kwargs.get("HOST")
        self.port = kwargs.get("PORT", 1883)
        self.keepalive = kwargs.get("KEEPALIVE", 60)

        if not self.host:
            raise ValueError("No host specified.")

        self.mqttRun()

        uptime = HomieTimer(60, self.mqttUptime)
        signal = HomieTimer(60, self.mqttSignal)
        uptime.start()
        signal.start()
Beispiel #3
0
    def __init__(self, config):
        super(Device, self).__init__()
        atexit.register(self._exitus)

        self.deviceName = None
        self.host = None
        self.port = None
        self.username = None
        self.password = None
        self.qos = None
        self.keepalive = None
        self.baseTopic = None
        self.ca_certs = None
        self.subscribe_all = None

        self._initAttrs(config)
        if not self.host:
            raise ValueError("No host specified.")

        self.startTime = time.time()
        self.fwname = None
        self.fwversion = None
        self.nodes = {}
        self.timers = []
        self.subscriptions = []
        self.subscriptions_handlers = {}
        self.subscribe_all_forced = False
        self.statsInterval = 60

        self.mqtt_topic = str("/".join([
            self.baseTopic,
            self.deviceId,
        ]))

        self._setupCalled = False
        self._mqtt_connected = False  # connected
        self._mqtt_subscribed = False  # subscribed
        clientId = "Homie-" + str(self.deviceId)

        try:
            self.mqtt = HomieMqtt(self, clientId)
        except Exception as e:
            raise e
Beispiel #4
0
    def __init__(self, config):
        super(Homie, self).__init__()
        atexit.register(self._exitus)

        self.deviceId = None
        self.deviceName = None
        self.host = None
        self.port = None
        self.username = None
        self.password = None
        self.qos = None
        self.keepalive = None
        self.baseTopic = None
        self.ca_certs = None
        self.subscribe_all = None

        self._initAttrs(config)
        if not self.host:
            raise ValueError("No host specified.")

        self.startTime = time.time()
        self.fwname = None
        self.fwversion = None
        self.nodes = []
        self.timers = []
        self.subscriptions = []
        self.subscribe_all_forced = False
        self.statsInterval = 60

        self.mqtt_topic = str("/".join([
            self.baseTopic,
            self.deviceId,
        ]))

        self._setupCalled = False
        self._mqtt_connected = False  # connected
        self._mqtt_subscribed = False  # subscribed
        clientId = "Homie-" + str(self.deviceId)

        try:
            self.mqtt = HomieMqtt(self, clientId)
        except Exception as e:
            raise e
Beispiel #5
0
class Homie(object):
    """docstring for Homie"""
    def __init__(self, configFile):
        super(Homie, self).__init__()
        atexit.register(self._exitus)
        signal.signal(signal.SIGTERM, self._sigTerm)
        signal.signal(signal.SIGHUP, self._sigHup)

        self._initAttrs(configFile)
        if not self.host:
            raise ValueError("No host specified.")

        self.startTime = time.time()
        self.fwname = None
        self.fwversion = None
        self.nodes = []
        self.timers = []
        self.subscriptions = []
        self.subscribe_all_forced = False

        self.mqtt_topic = str("/".join([
            self.baseTopic,
            self.deviceId,
        ]))

        self._setupCalled = False
        self._mqtt_connected = False  # connected
        self._mqtt_subscribed = False  # subscribed
        clientId = "Homie-" + str(self.deviceId)

        try:
            self.mqtt = HomieMqtt(self, clientId)
        except Exception as e:
            raise e

    def Timer(self, *args, **kwargs):
        homieTimer = HomieTimer(*args, **kwargs)
        self.timers.append(homieTimer)
        return (homieTimer)

    def Node(self, *args):
        homeNode = HomieNode(*args)
        self.nodes.append(homeNode)
        return (homeNode)

    def _loadConfig(self, configFile):
        """ load configuration from configFile """
        config = {}
        configFile = os.path.realpath(configFile)
        try:
            fp = open(configFile)
        except EnvironmentError as e:
            logger.debug(e)
        else:
            try:
                config = json.load(fp)
            except Exception as e:
                raise e
            finally:
                fp.close()
        logger.debug("config: {}".format(config))
        return config

    def _initAttrs(self, configFile):
        """ Initialize homie attributes from env/config/defaults """

        # load configuration from configFile
        config = self._loadConfig(configFile)

        # iterate through DEFAULT_PREFS
        for pref in DEFAULT_PREFS:
            key = DEFAULT_PREFS[pref]['key']
            val = getenv(
                "HOMIE_" + pref,  # env
                config.get(
                    pref,  # config
                    DEFAULT_PREFS[pref]['val']  # defaults
                ))

            # set attr self.key = val
            setattr(self, key, val)

            logger.debug("{}: {}".format(key, getattr(self, key)))

    def _checkBeforeSetup(self):
        """ checks whether setup() was called before """
        if self._setupCalled:
            raise BaseException(
                "✖ {}(): has to be called before setup()".format(
                    sys._getframe(1).f_code.co_name  # name of caller
                ))
        else:
            pass

    def _initialize(self):
        """ init and connect MQTT """
        # logger.debug("Initializing MQTT")
        self.mqtt.on_connect = self._connected
        self.mqtt.on_subscribe = self._subscribed
        self.mqtt.on_publish = self._published
        self.mqtt.on_disconnect = self._disconnected

        self.mqtt.will_set(self.mqtt_topic + "/$online",
                           payload="false",
                           retain=True)

        if self.username:
            self.mqtt.username_pw_set(self.username, password=self.password)

        if self.ca_certs:
            self.mqtt.tls_set(self.ca_certs)

        try:
            self.mqtt.connect(self.host, self.port, self.keepalive)
        except EnvironmentError as e:
            sleepSecs = 10
            logger.warning("{} - retrying in {} seconds.".format(e, sleepSecs))
            time.sleep(sleepSecs)
            self.mqtt.connect(self.host, self.port, self.keepalive)

        self.mqtt.loop_start()

    def _subscribe(self):
        logger.debug("Subscriptions: {}".format(self.subscriptions))
        if self.subscriptions:
            self.mqtt.subscribe(self.subscriptions)
            if self.subscribe_all_forced and not self.subscribe_all:
                self._unsubscribe()
        else:
            self.mqtt.subscribe(self.mqtt_topic + "/#", int(self.qos))
            self.subscribe_all_forced = True

    def _unsubscribe(self, topic=None):
        if not topic:
            topic = self.mqtt_topic + "/#"
        logger.debug("_unsubscribe: {}".format(topic))
        self.mqtt.unsubscribe(str(topic))

    def _connected(self, *args):
        # logger.debug("_connected: {}".format(args))
        self.mqtt_connected = True

        if not self.mqtt_subscribed:
            self._subscribe()

        self.publish(self.mqtt_topic + "/$online", payload="true", retain=True)
        self.publish(self.mqtt_topic + "/$name",
                     payload=self.deviceName,
                     retain=True)

        self.publishFwname()
        self.publishFwversion()
        self.publishNodes()
        self.publishLocalip()
        self.publishUptime()
        self.publishSignal()

    def _subscribed(self, *args):
        # logger.debug("_subscribed: {}".format(args))
        if not self.mqtt_subscribed:
            self.mqtt_subscribed = True

    def _published(self, *args):
        # logger.debug("_published: {}".format(args))
        pass

    def _disconnected(self, mqtt, obj, rc):
        self.mqtt_connected = False
        self.mqtt_subscribed = False

    def setup(self):
        """ set homie up """
        self._setupCalled = True

        # init and connect MQTT
        self._initialize()

        self.uptimeTimer = self.Timer(60,
                                      self.publishUptime,
                                      name="uptimeTimer")
        self.signalTimer = self.Timer(60,
                                      self.publishSignal,
                                      name="signalTimer")
        self.uptimeTimer.start()
        self.signalTimer.start()

    def setFirmware(self, name, version):
        """docstring for setFirmware"""
        self._checkBeforeSetup()

        self.fwname = name
        self.fwversion = version
        logger.debug("{}: {}".format(self.fwname, self.fwversion))

    def setNodeProperty(self, homieNode, prop, val, retain=True):
        topic = "/".join([self.mqtt_topic, homieNode.nodeId, prop])
        self.publish(topic, payload=val, retain=retain)

    def subscribe(self, homieNode, attr, callback, qos=None):
        """ Register new subscription and add a callback """
        self._checkBeforeSetup()

        # user qos prefs
        if qos is None:
            qos = int(self.qos)

        subscription = str("/".join(
            [self.mqtt_topic, homieNode.nodeId, attr, "set"]))

        logger.debug("subscribe: {} {}".format(subscription, qos))

        if not self.subscribe_all:
            self.subscriptions.append((subscription, qos))

        if self.mqtt_connected:
            self._subscribe()

        self.mqtt.message_callback_add(subscription, callback)

    def subscribeProperty(self, homieNode, attr, callback, qos=None):
        """ Register new subscription for property and add a callback """
        self._checkBeforeSetup()

        # user qos prefs
        if qos is None:
            qos = int(self.qos)

        subscription = str("/".join([self.mqtt_topic, homieNode.nodeId, attr]))

        logger.debug("subscribe: {} {}".format(subscription, qos))

        if not self.subscribe_all:
            self.subscriptions.append((subscription, qos))

        if self.mqtt_connected:
            self._subscribe()

        self.mqtt.message_callback_add(subscription, callback)

    def publish(self, topic, payload, retain=True, **kwargs):
        """ Publish messages to MQTT, if connected """
        if self.mqtt_connected:
            msgs = [topic, str(payload), str(retain)]

            (result, mid) = self.mqtt.publish(topic,
                                              payload=payload,
                                              retain=retain,
                                              **kwargs)

            logger.debug(str(mid) + " > " + " ".join(msgs))
        else:
            logger.warn("Not connected.")

    def publishNodes(self):
        """ Publish registered nodes to MQTT """
        payload = ",".join([(str(x.nodeId) + ":" + str(x.nodeType))
                            for x in self.nodes])
        self.publish(self.mqtt_topic + "/$nodes", payload=payload, retain=True)

    def publishLocalip(self):
        """ Publish local IP Address to MQTT """
        payload = None
        try:
            s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            s.connect((self.host, self.port))
        except Exception as e:
            logger.warn(e)
        else:
            payload = s.getsockname()[0]
            s.close()

        self.publish(self.mqtt_topic + "/$localip",
                     payload=payload,
                     retain=True)

    def publishUptime(self):
        """ Publish uptime of the script to MQTT """
        payload = int(time.time() - self.startTime)
        self.publish(self.mqtt_topic + "/$uptime",
                     payload=payload,
                     retain=True)

    def publishFwname(self):
        """ Publish fwname of the script to MQTT """
        payload = str(self.fwname)
        if self.fwname:
            self.publish(self.mqtt_topic + "/$fwname",
                         payload=payload,
                         retain=True)

    def publishFwversion(self):
        """ Publish fwversion of the script to MQTT """
        payload = str(self.fwversion)
        if self.fwversion:
            self.publish(self.mqtt_topic + "/$fwversion",
                         payload=payload,
                         retain=True)

    def publishSignal(self):
        """ Publish current signal strength to MQTT """
        # default payload
        payload = 100

        # found on linux
        wireless = "/proc/net/wireless"

        try:
            fp = open(wireless)
        except EnvironmentError as e:
            logger.debug(e)
        else:
            for i, line in enumerate(fp):
                if i == 2:
                    data = line.split()
                    payload = int(float(data[2]))
                elif i > 2:
                    break
            fp.close()

        self.publish(self.mqtt_topic + "/$signal",
                     payload=payload,
                     retain=True)

    @property
    def baseTopic(self):
        return self._baseTopic

    @baseTopic.setter
    def baseTopic(self, baseTopic):
        self._baseTopic = baseTopic

    @property
    def deviceId(self):
        return self._deviceId

    @deviceId.setter
    def deviceId(self, deviceId):
        self._deviceId = deviceId

    @property
    def mqtt_connected(self):
        return self._mqtt_connected

    @mqtt_connected.setter
    def mqtt_connected(self, state):
        logger.debug("connected: {}".format(state))
        self._mqtt_connected = state

    @property
    def mqtt_subscribed(self):
        return self._mqtt_subscribed

    @mqtt_subscribed.setter
    def mqtt_subscribed(self, state):
        logger.debug("subscribed: {}".format(state))
        self._mqtt_subscribed = state

    def _exitus(self):
        """ Clean up before exit """

        self.publish(self.mqtt_topic + "/$online",
                     payload="false",
                     retain=True)

        self.mqtt.loop_stop()
        self.mqtt.disconnect()

    def _sigTerm(self, signal, frame):
        """ let's do a quit, which atexit will notice """
        logger.debug("Received SIGTERM")
        raise SystemExit

    def _sigHup(self, signal, frame):
        """ let's do a quit, which atexit will notice """
        logger.debug("Received SIGHUP")
        raise SystemExit

    def __del__(self):
        logger.debug("Quitting.")
Beispiel #6
0
class Device(object):
    """docstring for Device"""

    def __init__(self, config):
        super(Device, self).__init__()
        atexit.register(self._exitus)

        self.deviceName = None
        self.host = None
        self.port = None
        self.username = None
        self.password = None
        self.qos = None
        self.keepalive = None
        self.baseTopic = None
        self.ca_certs = None
        self.subscribe_all = None

        self._initAttrs(config)
        if not self.host:
            raise ValueError("No host specified.")

        self.startTime = time.time()
        self.fwname = None
        self.fwversion = None
        self.nodes = {}
        self.timers = []
        self.subscriptions = []
        self.subscriptions_handlers = {}
        self.subscribe_all_forced = False
        self.statsInterval = 60

        self.mqtt_topic = str("/".join([
            self.baseTopic,
            self.deviceId,
        ]))

        self._setupCalled = False
        self._mqtt_connected = False  # connected
        self._mqtt_subscribed = False  # subscribed
        clientId = "Homie-" + str(self.deviceId)

        try:
            self.mqtt = HomieMqtt(self, clientId)
        except Exception as e:
            raise e

    def Timer(self, *args, **kwargs):
        homieTimer = HomieTimer(*args, **kwargs)
        self.timers.append(homieTimer)
        return(homieTimer)

    def addNode(self, nodeId, nodeName, nodeType):
        homieNode = HomieNode(self, nodeId, nodeName, nodeType)
        self.nodes[nodeId] = homieNode
        return(homieNode)

    def _initAttrs(self, config):
        """ Initialize homie attributes from env/config/defaults """
        # iterate through DEFAULT_PREFS
        for pref in DEFAULT_PREFS:
            key = DEFAULT_PREFS[pref]['key']
            val = getenv(
                "HOMIE_" + pref,                # env
                config.get(
                    pref,                       # config
                    DEFAULT_PREFS[pref]['val']  # defaults
                )
            )

            # set attr self.key = val
            setattr(self, key, val)

            logger.debug("{}: {}".format(key, getattr(self, key)))

    def _checkBeforeSetup(self):
        """ checks whether setup() was called before """
        if self._setupCalled:
            raise BaseException(
                "✖ {}(): has to be called before setup()".format(
                    sys._getframe(1).f_code.co_name  # name of caller
                ))
        else:
            pass

    def _initialize(self):
        """ init and connect MQTT """
        # logger.debug("Initializing MQTT")
        self.mqtt.on_connect = self._connected
        self.mqtt.on_subscribe = self._subscribed
        self.mqtt.on_publish = self._published
        self.mqtt.on_disconnect = self._disconnected

        self.mqtt.will_set(
            self.mqtt_topic + "/$state", payload="lost", retain=True)

        if self.username:
            self.mqtt.username_pw_set(self.username, password=self.password)

        if self.ca_certs:
            self.mqtt.tls_set(self.ca_certs)

        try:
            self.mqtt.connect(self.host, self.port, self.keepalive)
        except EnvironmentError as e:
            sleepSecs = 10
            logger.warning("{} - retrying in {} seconds.".format(e, sleepSecs))
            time.sleep(sleepSecs)
            self.mqtt.connect(self.host, self.port, self.keepalive)

        self.mqtt.loop_start()

    def _subscribe(self):
        logger.debug("Subscriptions: {}".format(self.subscriptions))
        if self.subscriptions:
            self.mqtt.subscribe(self.subscriptions)
            if self.subscribe_all_forced and not self.subscribe_all:
                self._unsubscribe()
        else:
            self.mqtt.subscribe(self.mqtt_topic + "/#", int(self.qos))
            self.subscribe_all_forced = True

    def _unsubscribe(self, topic=None):
        if not topic:
            topic = self.mqtt_topic + "/#"
        logger.debug("_unsubscribe: {}".format(topic))
        self.mqtt.unsubscribe(str(topic))

    def _connected(self, *args):
        # logger.debug("_connected: {}".format(args))
        self.mqtt_connected = True

        if not self.mqtt_subscribed:
            self._subscribe()

        self.publish(
            self.mqtt_topic + "/$state",
            payload="init", retain=True)
        self.publish(
            self.mqtt_topic + "/$name",
            payload=self.deviceName, retain=True)

        self.publishHomieVersion()
        self.publishLocalipAndMac()
        self.publishFwname()
        self.publishFwversion()
        self.publishImplementation()
        self.publishStats()
        self.publishStatsInterval()
        self.publishUptime()
        self.publishSignal()
        self.publishNodes()

        self.publish(
            self.mqtt_topic + "/$state",
            payload="ready", retain=True
        )

    def _subscribed(self, *args):
        # logger.debug("_subscribed: {}".format(args))
        if not self.mqtt_subscribed:
            self.mqtt_subscribed = True

    def _published(self, *args):
        # logger.debug("_published: {}".format(args))
        pass

    def _disconnected(self, mqtt, obj, rc):
        self.mqtt_connected = False
        self.mqtt_subscribed = False

    def setup(self):
        """ set homie up """
        self._setupCalled = True

        # init and connect MQTT
        self._initialize()

        self.intervalTimer = self.Timer(
            self.statsInterval, self.publishStatsInterval, name="intervalTimer")
        self.uptimeTimer = self.Timer(
            self.statsInterval, self.publishUptime, name="uptimeTimer")
        self.signalTimer = self.Timer(
            self.statsInterval, self.publishSignal, name="signalTimer")
        # self.intervalTimer.start()
        # self.uptimeTimer.start()
        # self.signalTimer.start()

    def setFirmware(self, name, version):
        """docstring for setFirmware"""
        self._checkBeforeSetup()

        self.fwname = name
        self.fwversion = version
        logger.debug("{}: {}".format(self.fwname, self.fwversion))

    def setNodeProperty(self, homieNode, prop, val, retain=True):
        topic = "/".join([
            self.mqtt_topic,
            homieNode.nodeId,
            prop
        ])
        self.publish(topic, payload=val, retain=retain)

    def subscribePropertyHandler(self, mqttc, obj, msg):
        topic = msg.topic
        value = msg.payload.decode("UTF-8")

        if topic in self.subscriptions_handlers:
            nodeId = topic.split("/")[-3]
            propertyId = topic.split("/")[-2]
            property = self.nodes[nodeId].properties[propertyId]
            self.subscriptions_handlers[topic](property, value)

    def subscribeProperty(self, homieNode, attr, callback, qos=None):
        """ Register new subscription for property and add a callback """
        self._checkBeforeSetup()

        # user qos prefs
        if qos is None:
            qos = int(self.qos)

        subscription = str("/".join(
            [
                self.mqtt_topic,
                homieNode.nodeId,
                attr,
                "set"
            ]))

        logger.debug("subscribe: {} {}".format(subscription, qos))

        if not self.subscribe_all:
            self.subscriptions.append((subscription, qos))
            self.subscriptions_handlers[subscription] = callback

        if self.mqtt_connected:
            self._subscribe()

        self.mqtt.message_callback_add(subscription, self.subscribePropertyHandler)

    def publish(self, topic, payload, retain=True, qos=None, **kwargs):
        """ Publish messages to MQTT, if connected """
        if self.mqtt_connected:
            if qos is None:
                qos = int(self.qos)

            msgs = [
                topic,
                str(payload),
                str(retain)
            ]

            (_, mid) = self.mqtt.publish(
                topic,
                payload=payload,
                retain=retain,
                qos=qos,
                **kwargs)

            logger.debug(str(mid) + " > " + " ".join(msgs))
        else:
            logger.warn("Not connected.")

    def publishNodes(self):
        """ Publish registered nodes and their properties to MQTT """
        payload = ",".join(
            list(self.nodes.keys()))
        self.publish(
            self.mqtt_topic + "/$nodes",
            payload=payload, retain=True)

        for node in self.nodes.values():
            node.publishProperties()

    def publishLocalipAndMac(self):
        """Publish local IP and MAC Addresses to MQTT."""
        net_info = NetworkInformation()
        local_ip = net_info.getLocalIp(self.host, self.port)
        local_mac = net_info.getLocalMacForIp(local_ip)

        self.publish(
            self.mqtt_topic + "/$localip",
            payload=local_ip, retain=True)

        self.publish(
            self.mqtt_topic + "/$mac",
            payload=local_mac, retain=True)

    def publishStats(self):
        """Publish stats info"""
        payload = ",".join([
            "signal"
        ])
        self.publish(
            self.mqtt_topic + "/$stats",
            payload=payload, retain=True
        )

    def publishStatsInterval(self):
        """ Publish /$stats/interval to MQTT """
        payload = self.statsInterval
        self.publish(
            self.mqtt_topic + "/$stats/interval",
            payload=payload, retain=True)

    def publishUptime(self):
        """ Publish uptime of the script to MQTT """
        payload = int(time.time() - self.startTime)
        self.publish(
            self.mqtt_topic + "/$stats/uptime",
            payload=payload, retain=True)

    def publishImplementation(self):
        """ Publish identifier for the Homie implementation to MQTT """
        payload = "homie-python"
        self.publish(
            self.mqtt_topic + "/$implementation",
            payload=payload, retain=True)

    def publishHomieVersion(self):
        """ Publish Version of the Homie convention the device conforms to """
        payload = HOMIE_VERSION
        self.publish(
            self.mqtt_topic + "/$homie",
            payload=payload, retain=True)

    def publishFwname(self):
        """ Publish fwname of the script to MQTT """
        payload = str(self.fwname)
        if self.fwname:
            self.publish(
                self.mqtt_topic + "/$fw/name",
                payload=payload, retain=True)

    def publishFwversion(self):
        """ Publish fwversion of the script to MQTT """
        payload = str(self.fwversion)
        if self.fwversion:
            self.publish(
                self.mqtt_topic + "/$fw/version",
                payload=payload, retain=True)

    def publishSignal(self):
        """ Publish current signal strength to MQTT """
        # default payload
        payload = 100

        # found on linux
        wireless = "/proc/net/wireless"

        try:
            fp = open(wireless)
        except EnvironmentError as e:
            logger.debug(e)
        else:
            for i, line in enumerate(fp):
                if i == 2:
                    data = line.split()
                    payload = int(float(data[2]))
                elif i > 2:
                    break
            fp.close()

        self.publish(
            self.mqtt_topic + "/$stats/signal",
            payload=payload, retain=True)

    @property
    def baseTopic(self):
        return self._baseTopic

    @baseTopic.setter
    def baseTopic(self, baseTopic):
        self._baseTopic = baseTopic

    @property
    def deviceId(self):
        return self._deviceId

    @deviceId.setter
    def deviceId(self, deviceId):
        if isValidId(deviceId):
            self._deviceId = deviceId
        else:
            self._deviceId = generateDeviceId()
            logger.warning(
                "Invalid deviceId specified. Using '{}' instead.".format(
                    self._deviceId))

    @property
    def mqtt_connected(self):
        return self._mqtt_connected

    @mqtt_connected.setter
    def mqtt_connected(self, state):
        logger.debug("connected: {}".format(state))
        self._mqtt_connected = state

    @property
    def mqtt_subscribed(self):
        return self._mqtt_subscribed

    @mqtt_subscribed.setter
    def mqtt_subscribed(self, state):
        logger.debug("subscribed: {}".format(state))
        self._mqtt_subscribed = state

    def _exitus(self):
        """ Clean up before exit """

        self.publish(
            self.mqtt_topic + "/$state",
            payload="disconnected", retain=True)

        self.mqtt.loop_stop()
        self.mqtt.disconnect()

    def __del__(self):
        """Deconstruct object."""
        logger.debug("Quitting.")
Beispiel #7
0
class Homie(object):
    """docstring for Homie"""

    def __init__(self, config):
        super(Homie, self).__init__()
        atexit.register(self._exitus)

        self.deviceId = None
        self.deviceName = None
        self.host = None
        self.port = None
        self.username = None
        self.password = None
        self.qos = None
        self.keepalive = None
        self.baseTopic = None
        self.ca_certs = None
        self.subscribe_all = None

        self._initAttrs(config)
        if not self.host:
            raise ValueError("No host specified.")

        self.startTime = time.time()
        self.fwname = None
        self.fwversion = None
        self.nodes = []
        self.timers = []
        self.subscriptions = []
        self.subscribe_all_forced = False
        self.statsInterval = 60

        self.mqtt_topic = str("/".join([
            self.baseTopic,
            self.deviceId,
        ]))

        self._setupCalled = False
        self._mqtt_connected = False  # connected
        self._mqtt_subscribed = False  # subscribed
        clientId = "Homie-" + str(self.deviceId)

        try:
            self.mqtt = HomieMqtt(self, clientId)
        except Exception as e:
            raise e

    def Timer(self, *args, **kwargs):
        homieTimer = HomieTimer(*args, **kwargs)
        self.timers.append(homieTimer)
        return(homieTimer)

    def Node(self, nodeId, nodeType):
        homieNode = HomieNode(self, nodeId, nodeType)
        self.nodes.append(homieNode)
        return(homieNode)

    def _initAttrs(self, config):
        """ Initialize homie attributes from env/config/defaults """
        # iterate through DEFAULT_PREFS
        for pref in DEFAULT_PREFS:
            key = DEFAULT_PREFS[pref]['key']
            val = getenv(
                "HOMIE_" + pref,                # env
                config.get(
                    pref,                       # config
                    DEFAULT_PREFS[pref]['val']  # defaults
                )
            )

            # set attr self.key = val
            setattr(self, key, val)

            logger.debug("{}: {}".format(key, getattr(self, key)))

    def _checkBeforeSetup(self):
        """ checks whether setup() was called before """
        if self._setupCalled:
            raise BaseException(
                "✖ {}(): has to be called before setup()".format(
                    sys._getframe(1).f_code.co_name  # name of caller
                ))
        else:
            pass

    def _initialize(self):
        """ init and connect MQTT """
        # logger.debug("Initializing MQTT")
        self.mqtt.on_connect = self._connected
        self.mqtt.on_subscribe = self._subscribed
        self.mqtt.on_publish = self._published
        self.mqtt.on_disconnect = self._disconnected

        self.mqtt.will_set(
            self.mqtt_topic + "/$online", payload="false", retain=True)

        if self.username:
            self.mqtt.username_pw_set(self.username, password=self.password)

        if self.ca_certs:
            self.mqtt.tls_set(self.ca_certs)

        try:
            self.mqtt.connect(self.host, self.port, self.keepalive)
        except EnvironmentError as e:
            sleepSecs = 10
            logger.warning("{} - retrying in {} seconds.".format(e, sleepSecs))
            time.sleep(sleepSecs)
            self.mqtt.connect(self.host, self.port, self.keepalive)

        self.mqtt.loop_start()

    def _subscribe(self):
        logger.debug("Subscriptions: {}".format(self.subscriptions))
        if self.subscriptions:
            self.mqtt.subscribe(self.subscriptions)
            if self.subscribe_all_forced and not self.subscribe_all:
                self._unsubscribe()
        else:
            self.mqtt.subscribe(self.mqtt_topic + "/#", int(self.qos))
            self.subscribe_all_forced = True

    def _unsubscribe(self, topic=None):
        if not topic:
            topic = self.mqtt_topic + "/#"
        logger.debug("_unsubscribe: {}".format(topic))
        self.mqtt.unsubscribe(str(topic))

    def _connected(self, *args):
        # logger.debug("_connected: {}".format(args))
        self.mqtt_connected = True

        if not self.mqtt_subscribed:
            self._subscribe()

        self.publish(
            self.mqtt_topic + "/$online",
            payload="true", retain=True)
        self.publish(
            self.mqtt_topic + "/$name",
            payload=self.deviceName, retain=True)

        self.publishHomieVersion()
        self.publishFwname()
        self.publishFwversion()
        self.publishNodes()
        self.publishLocalipAndMac()
        self.publishStatsInterval()
        self.publishUptime()
        self.publishSignal()
        self.publishImplementation()

    def _subscribed(self, *args):
        # logger.debug("_subscribed: {}".format(args))
        if not self.mqtt_subscribed:
            self.mqtt_subscribed = True

    def _published(self, *args):
        # logger.debug("_published: {}".format(args))
        pass

    def _disconnected(self, mqtt, obj, rc):
        self.mqtt_connected = False
        self.mqtt_subscribed = False

    def setup(self):
        """ set homie up """
        self._setupCalled = True

        # init and connect MQTT
        self._initialize()

        self.uptimeTimer = self.Timer(
            self.statsInterval, self.publishUptime, name="uptimeTimer")
        self.signalTimer = self.Timer(
            self.statsInterval, self.publishSignal, name="signalTimer")
        self.uptimeTimer.start()
        self.signalTimer.start()

    def setFirmware(self, name, version):
        """docstring for setFirmware"""
        self._checkBeforeSetup()

        self.fwname = name
        self.fwversion = version
        logger.debug("{}: {}".format(self.fwname, self.fwversion))

    def setNodeProperty(self, homieNode, prop, val, retain=True):
        topic = "/".join([
            self.mqtt_topic,
            homieNode.nodeId,
            prop
        ])
        self.publish(topic, payload=val, retain=retain)

    def subscribe(self, homieNode, attr, callback, qos=None):
        """ Register new subscription and add a callback """
        self._checkBeforeSetup()

        # user qos prefs
        if qos is None:
            qos = int(self.qos)

        subscription = str("/".join(
            [
                self.mqtt_topic,
                homieNode.nodeId,
                attr,
                "set"
            ]))

        logger.debug("subscribe: {} {}".format(subscription, qos))

        if not self.subscribe_all:
            self.subscriptions.append((subscription, qos))

        if self.mqtt_connected:
            self._subscribe()

        self.mqtt.message_callback_add(subscription, callback)

    def subscribeProperty(self, homieNode, attr, callback, qos=None):
        """ Register new subscription for property and add a callback """
        self._checkBeforeSetup()

        # user qos prefs
        if qos is None:
            qos = int(self.qos)

        subscription = str("/".join(
            [
                self.mqtt_topic,
                homieNode.nodeId,
                attr
            ]))

        logger.debug("subscribe: {} {}".format(subscription, qos))

        if not self.subscribe_all:
            self.subscriptions.append((subscription, qos))

        if self.mqtt_connected:
            self._subscribe()

        self.mqtt.message_callback_add(subscription, callback)

    def publish(self, topic, payload, retain=True, qos=None, **kwargs):
        """ Publish messages to MQTT, if connected """
        if self.mqtt_connected:
            if qos is None:
                qos = int(self.qos)

            msgs = [
                topic,
                str(payload),
                str(retain)
            ]

            (_, mid) = self.mqtt.publish(
                topic,
                payload=payload,
                retain=retain,
                qos=qos,
                **kwargs)

            logger.debug(str(mid) + " > " + " ".join(msgs))
        else:
            logger.warn("Not connected.")

    def publishNodes(self):
        """ Publish registered nodes and their properties to MQTT """
        payload = ",".join(
            [str(x.nodeId) for x in self.nodes])
        self.publish(
            self.mqtt_topic + "/$nodes",
            payload=payload, retain=True)

        for node in self.nodes:
            node.sendProperties()

    def publishLocalipAndMac(self):
        """Publish local IP and MAC Addresses to MQTT."""
        net_info = NetworkInformation()
        local_ip = net_info.getLocalIp(self.host, self.port)
        local_mac = net_info.getLocalMacForIp(local_ip)

        self.publish(
            self.mqtt_topic + "/$localip",
            payload=local_ip, retain=True)

        self.publish(
            self.mqtt_topic + "/$mac",
            payload=local_mac, retain=True)

    def publishStatsInterval(self):
        """ Publish /$stats/interval to MQTT """
        payload = self.statsInterval
        self.publish(
            self.mqtt_topic + "/$stats/interval",
            payload=payload, retain=True)

    def publishUptime(self):
        """ Publish uptime of the script to MQTT """
        payload = int(time.time() - self.startTime)
        self.publish(
            self.mqtt_topic + "/$stats/uptime",
            payload=payload, retain=True)

    def publishImplementation(self):
        """ Publish identifier for the Homie implementation to MQTT """
        payload = "homie-python"
        self.publish(
            self.mqtt_topic + "/$implementation",
            payload=payload, retain=True)

    def publishHomieVersion(self):
        """ Publish Version of the Homie convention the device conforms to """
        payload = HOMIE_VERSION
        self.publish(
            self.mqtt_topic + "/$homie",
            payload=payload, retain=True)

    def publishFwname(self):
        """ Publish fwname of the script to MQTT """
        payload = str(self.fwname)
        if self.fwname:
            self.publish(
                self.mqtt_topic + "/$fw/name",
                payload=payload, retain=True)

    def publishFwversion(self):
        """ Publish fwversion of the script to MQTT """
        payload = str(self.fwversion)
        if self.fwversion:
            self.publish(
                self.mqtt_topic + "/$fw/version",
                payload=payload, retain=True)

    def publishSignal(self):
        """ Publish current signal strength to MQTT """
        # default payload
        payload = 100

        # found on linux
        wireless = "/proc/net/wireless"

        try:
            fp = open(wireless)
        except EnvironmentError as e:
            logger.debug(e)
        else:
            for i, line in enumerate(fp):
                if i == 2:
                    data = line.split()
                    payload = int(float(data[2]))
                elif i > 2:
                    break
            fp.close()

        self.publish(
            self.mqtt_topic + "/$stats/signal",
            payload=payload, retain=True)

    @property
    def baseTopic(self):
        return self._baseTopic

    @baseTopic.setter
    def baseTopic(self, baseTopic):
        self._baseTopic = baseTopic

    @property
    def deviceId(self):
        return self._deviceId

    @deviceId.setter
    def deviceId(self, deviceId):
        if isIdFormat(deviceId):
            self._deviceId = deviceId
        else:
            self._deviceId = generateDeviceId()
            logger.warning(
                "Invalid deviceId specified. Using '{}' instead.".format(
                    self._deviceId))

    @property
    def mqtt_connected(self):
        return self._mqtt_connected

    @mqtt_connected.setter
    def mqtt_connected(self, state):
        logger.debug("connected: {}".format(state))
        self._mqtt_connected = state

    @property
    def mqtt_subscribed(self):
        return self._mqtt_subscribed

    @mqtt_subscribed.setter
    def mqtt_subscribed(self, state):
        logger.debug("subscribed: {}".format(state))
        self._mqtt_subscribed = state

    def _exitus(self):
        """ Clean up before exit """

        self.publish(
            self.mqtt_topic + "/$online",
            payload="false", retain=True)

        self.mqtt.loop_stop()
        self.mqtt.disconnect()

    def __del__(self):
        """Deconstruct object."""
        logger.debug("Quitting.")
Beispiel #8
0
class Homie(object):
    """docstring for Homie"""

    def __init__(self, **kwargs):
        super(Homie, self).__init__()
        logger.debug("kwargs: {}".format(kwargs))
        self.startTime = time.time()
        self.fwname = None
        self.fwversion = None
        self.baseTopic = kwargs.get("TOPIC")
        self.deviceId = kwargs.get("DEVICE_ID")
        self.deviceName = kwargs.get("DEVICE_NAME")
        self.nodes = []

        self.mqtt_topic = "/".join([
            self.baseTopic,
            self.deviceId,
        ])

        self.mqtt = HomieMqtt(self, self.deviceId)
        self.host = kwargs.get("HOST")
        self.port = kwargs.get("PORT", 1883)
        self.keepalive = kwargs.get("KEEPALIVE", 60)

        if not self.host:
            raise ValueError("No host specified.")

        self.mqttRun()

        uptime = HomieTimer(60, self.mqttUptime)
        signal = HomieTimer(60, self.mqttSignal)
        uptime.start()
        signal.start()

    def Node(self, *args):
        homeNode = HomieNode(*args)
        self.nodes.append(homeNode)
        return(homeNode)

    def setFirmware(self, name, version):
        """docstring for setFirmware"""
        self.fwname = name
        self.fwversion = version
        logger.debug("{}: {}".format(self.fwname, self.fwversion))

    def setNodeProperty(self, homieNode, prop, val, retained=True):
        topic = "/".join([
            self.mqtt_topic,
            homieNode.nodeId,
            prop
        ])

        self.mqtt.publish(topic, payload=val, retain=retained)

        msgs = [
            topic,
            str(val),
            str(retained)
        ]
        logger.debug(" ".join(msgs))

    def subscribe(self, homieNode, attr, callback):
        subscription = "/".join(
            [
                self.mqtt_topic,
                homieNode.nodeId,
                attr,
                "set"
            ])

        logger.debug("subscribe: {}".format(subscription))

        self.mqtt.message_callback_add(
            subscription, callback)

    def mqttRun(self):
        self.mqtt.will_set(
            self.mqtt_topic + "/$online", payload="false", retain=True)
        self.mqtt.connect(self.host, self.port, self.keepalive)
        self.mqtt.loop_start()
        self.mqtt.subscribe(self.mqtt_topic + "/#", 0)

    def mqttNodes(self):
        payload = ",".join(
            [(str(x.nodeId) + ":" + str(x.nodeType)) for x in self.nodes])
        self.mqtt.publish(
            self.mqtt_topic + "/$nodes",
            payload=payload, retain=True)

    def mqttLocalip(self):
        payload = socket.gethostbyname(socket.gethostname())
        self.mqtt.publish(
            self.mqtt_topic + "/$localip",
            payload=payload, retain=True)

    def mqttUptime(self):
        payload = int(time.time() - self.startTime)
        self.mqtt.publish(
            self.mqtt_topic + "/$uptime",
            payload=payload, retain=True)

    def mqttSignal(self):
        # default payload
        payload = 100

        # found on linux
        wireless = "/proc/net/wireless"

        try:
            fp = open(wireless)
        except EnvironmentError as e:
            logger.debug(e)
        else:
            for i, line in enumerate(fp):
                if i == 2:
                    data = line.split()
                    payload = int(float(data[2]))
                elif i > 2:
                    break
            fp.close()

        self.mqtt.publish(
            self.mqtt_topic + "/$signal",
            payload=payload, retain=True)

    def mqttSetup(self):
        self.mqtt.publish(
            self.mqtt_topic + "/$online",
            payload="true", retain=True)
        self.mqtt.publish(
            self.mqtt_topic + "/$name",
            payload=self.deviceName, retain=True)
        self.mqtt.publish(
            self.mqtt_topic + "/$fwname",
            payload=self.fwname, retain=True)
        self.mqtt.publish(
            self.mqtt_topic + "/$fwversion",
            payload=self.fwversion, retain=True)
        self.mqttNodes()
        self.mqttLocalip()
        self.mqttUptime()
        self.mqttSignal()

    @property
    def baseTopic(self):
        return self._baseTopic

    @baseTopic.setter
    def baseTopic(self, baseTopic):
        self._baseTopic = baseTopic

    @property
    def deviceId(self):
        return self._deviceId

    @deviceId.setter
    def deviceId(self, deviceId):
        self._deviceId = deviceId