From ae5b02bb99aaf63a7b70499da49cc480f214b5b5 Mon Sep 17 00:00:00 2001 From: Aitor Iturrioz Date: Sat, 6 Oct 2018 12:32:23 +0200 Subject: [PATCH 01/31] Move 'property' to its own file --- homie/node.py | 98 +-------------------------------------------- homie/property.py | 100 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 96 deletions(-) create mode 100644 homie/property.py diff --git a/homie/node.py b/homie/node.py index 3f2f877..de79c10 100755 --- a/homie/node.py +++ b/homie/node.py @@ -1,104 +1,10 @@ #!/usr/bin/env python import logging from homie.helpers import isIdFormat +from homie.property import HomieNodeProperty +from homie.property import HomieNodeRange logger = logging.getLogger(__name__) - -class HomieNodeProperty(object): - """docstring for HomieNodeProp""" - - def setSubscribe(self, func): - self.subscribe = func - - def __init__(self, node, propertyId): - super(HomieNodeProperty, self).__init__() - self.node = node # stores ref to node - self._propertyId = None - self.propertyId = propertyId - self.handler = None - self._settable = False - - def settable(self, handler): - self.handler = handler - self.subscribe(self.node, self.propertyId, handler) - self._settable = True - - def send(self, value): - self.node.homie.publish( - "/".join([ - self.node.homie.baseTopic, - self.node.homie.deviceId, - self.node.nodeId, - self.propertyId, - ]), - value, - ) - - def representation(self): - repr = self.propertyId - if self._settable: - repr += ":settable" - return repr - - @property - def propertyId(self): - return self._propertyId - - @propertyId.setter - def propertyId(self, propertyId): - if isIdFormat(propertyId): - self._propertyId = propertyId - else: - logger.warning("'{}' has no valid ID-Format".format(propertyId)) - - -class HomieNodeRange(HomieNodeProperty): - """docstring for HomieNodeRange""" - - def __init__(self, node, propertyId, lower, upper): - super(HomieNodeRange, self).__init__(node, propertyId) - self.node = node - self._range = range(lower, upper + 1) - self.range = None - self.lower = lower - self.upper = upper - self.range_names = [(propertyId + "_" + str(x)) for x in self._range] - - def settable(self, handler): - self.handler = handler - for x in self._range: - self.subscribe(self.node, "{}_{}".format(self.propertyId, x), handler) - - def setRange(self, lower, upper): - # Todo: validate input - if lower in self._range and upper in self._range: - self.range = range(lower, upper + 1) - return self - else: - logger.warning("Specified range out of announced range.") - - def send(self, value): - if self.range is None: - raise ValueError("Please specify a range.") - - for x in self.range: - self.node.homie.publish( - "/".join([ - self.node.homie.baseTopic, - self.node.homie.deviceId, - self.node.nodeId, - self.propertyId + "_" + str(x), - ]), - value, - ) - - def representation(self): - repr = "{}[{}-{}]".format(self.propertyId, self.lower, self.upper) - if self._settable: - repr += ":settable" - return repr - - class HomieNode(object): """docstring for HomieNode""" diff --git a/homie/property.py b/homie/property.py new file mode 100644 index 0000000..2017214 --- /dev/null +++ b/homie/property.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- +import logging +from homie.helpers import isIdFormat +logger = logging.getLogger(__name__) + +class HomieNodeProperty(object): + """docstring for HomieNodeProp""" + + def setSubscribe(self, func): + self.subscribe = func + + def __init__(self, node, propertyId): + super(HomieNodeProperty, self).__init__() + self.node = node # stores ref to node + self._propertyId = None + self.propertyId = propertyId + self.handler = None + self._settable = False + + def settable(self, handler): + self.handler = handler + self.subscribe(self.node, self.propertyId, handler) + self._settable = True + + def send(self, value): + self.node.homie.publish( + "/".join([ + self.node.homie.baseTopic, + self.node.homie.deviceId, + self.node.nodeId, + self.propertyId, + ]), + value, + ) + + def representation(self): + repr = self.propertyId + if self._settable: + repr += ":settable" + return repr + + @property + def propertyId(self): + return self._propertyId + + @propertyId.setter + def propertyId(self, propertyId): + if isIdFormat(propertyId): + self._propertyId = propertyId + else: + logger.warning("'{}' has no valid ID-Format".format(propertyId)) + + +class HomieNodeRange(HomieNodeProperty): + """docstring for HomieNodeRange""" + + def __init__(self, node, propertyId, lower, upper): + super(HomieNodeRange, self).__init__(node, propertyId) + self.node = node + self._range = range(lower, upper + 1) + self.range = None + self.lower = lower + self.upper = upper + self.range_names = [(propertyId + "_" + str(x)) for x in self._range] + + def settable(self, handler): + self.handler = handler + for x in self._range: + self.subscribe(self.node, "{}_{}".format(self.propertyId, x), handler) + + def setRange(self, lower, upper): + # Todo: validate input + if lower in self._range and upper in self._range: + self.range = range(lower, upper + 1) + return self + else: + logger.warning("Specified range out of announced range.") + + def send(self, value): + if self.range is None: + raise ValueError("Please specify a range.") + + for x in self.range: + self.node.homie.publish( + "/".join([ + self.node.homie.baseTopic, + self.node.homie.deviceId, + self.node.nodeId, + self.propertyId + "_" + str(x), + ]), + value, + ) + + def representation(self): + repr = "{}[{}-{}]".format(self.propertyId, self.lower, self.upper) + if self._settable: + repr += ":settable" + return repr + From c4c304b995d6be1d3c9f28005da03507b6cdc6f0 Mon Sep 17 00:00:00 2001 From: Aitor Iturrioz Date: Sat, 6 Oct 2018 14:47:12 +0200 Subject: [PATCH 02/31] Move 'examples' to their own folder --- homie-python.json.example => examples/homie-python.json.example | 0 relay_switch.py => examples/relay_switch.py | 0 .../relay_switch_with_config.py | 0 .../temperatureDS18B20_raspi.py | 0 temperature_raspi.py => examples/temperature_raspi.py | 0 temperature_sensor.py => examples/temperature_sensor.py | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename homie-python.json.example => examples/homie-python.json.example (100%) rename relay_switch.py => examples/relay_switch.py (100%) rename relay_switch_with_config.py => examples/relay_switch_with_config.py (100%) rename temperatureDS18B20_raspi.py => examples/temperatureDS18B20_raspi.py (100%) rename temperature_raspi.py => examples/temperature_raspi.py (100%) rename temperature_sensor.py => examples/temperature_sensor.py (100%) diff --git a/homie-python.json.example b/examples/homie-python.json.example similarity index 100% rename from homie-python.json.example rename to examples/homie-python.json.example diff --git a/relay_switch.py b/examples/relay_switch.py similarity index 100% rename from relay_switch.py rename to examples/relay_switch.py diff --git a/relay_switch_with_config.py b/examples/relay_switch_with_config.py similarity index 100% rename from relay_switch_with_config.py rename to examples/relay_switch_with_config.py diff --git a/temperatureDS18B20_raspi.py b/examples/temperatureDS18B20_raspi.py similarity index 100% rename from temperatureDS18B20_raspi.py rename to examples/temperatureDS18B20_raspi.py diff --git a/temperature_raspi.py b/examples/temperature_raspi.py similarity index 100% rename from temperature_raspi.py rename to examples/temperature_raspi.py diff --git a/temperature_sensor.py b/examples/temperature_sensor.py similarity index 100% rename from temperature_sensor.py rename to examples/temperature_sensor.py From 85ad9392c78ae284fd31b58b45e48a4073ac64f6 Mon Sep 17 00:00:00 2001 From: Aitor Iturrioz Date: Sat, 6 Oct 2018 18:33:09 +0200 Subject: [PATCH 03/31] Add a method to check if datatype is valid --- homie/helpers.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/homie/helpers.py b/homie/helpers.py index 1791da1..ffe82b8 100644 --- a/homie/helpers.py +++ b/homie/helpers.py @@ -19,3 +19,10 @@ def isIdFormat(idString): if isinstance(idString, str): r = re.compile('(^(?!\-)[a-z0-9\-]+(? Date: Sat, 6 Oct 2018 18:40:13 +0200 Subject: [PATCH 04/31] Add more attributes to HomieNodeProperty --- homie/property.py | 40 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/homie/property.py b/homie/property.py index 2017214..024ef4d 100644 --- a/homie/property.py +++ b/homie/property.py @@ -10,11 +10,15 @@ class HomieNodeProperty(object): def setSubscribe(self, func): self.subscribe = func - def __init__(self, node, propertyId): + def __init__(self, node, id, name=None, unit=None, datatype=None, format=None): super(HomieNodeProperty, self).__init__() self.node = node # stores ref to node self._propertyId = None - self.propertyId = propertyId + self.propertyId = id + self.propertyName = name + self.propertyUnit = unit + self.propertyDatatype = datatype + self.propertyFormat = format self.handler = None self._settable = False @@ -51,6 +55,38 @@ def propertyId(self, propertyId): else: logger.warning("'{}' has no valid ID-Format".format(propertyId)) + @property + def propertyName(self): + return self.propertyName + + @propertyName.setter + def propertyName(self, propertyName): + self.propertyName = propertyName + + @property + def propertyUnit(self): + return self.propertyUnit + + @propertyUnit.setter + def propertyUnit(self, propertyUnit): + self.propertyUnit = propertyUnit + + @property + def propertyDatatype(self): + return self.propertyDatatype + + @propertyDatatype.setter + def propertyDatatype(self, propertyDatatype): + self.propertyDatatype = property + + @property + def propertyFormat(self): + return self.propertyFormat + + @propertyFormat.setter + def propertyFormat(self, propertyFormat): + self.propertyFormat = propertyFormat + class HomieNodeRange(HomieNodeProperty): """docstring for HomieNodeRange""" From 0113f9abc646d21d4f018f9770f60cf0e68df816 Mon Sep 17 00:00:00 2001 From: Aitor Iturrioz Date: Sun, 7 Oct 2018 12:07:44 +0200 Subject: [PATCH 05/31] Update HomieNodePropertyRange class --- homie/property.py | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/homie/property.py b/homie/property.py index 024ef4d..a8365fe 100644 --- a/homie/property.py +++ b/homie/property.py @@ -13,8 +13,8 @@ def setSubscribe(self, func): def __init__(self, node, id, name=None, unit=None, datatype=None, format=None): super(HomieNodeProperty, self).__init__() self.node = node # stores ref to node - self._propertyId = None - self.propertyId = id + self._id = None + self.id = id self.propertyName = name self.propertyUnit = unit self.propertyDatatype = datatype @@ -24,7 +24,7 @@ def __init__(self, node, id, name=None, unit=None, datatype=None, format=None): def settable(self, handler): self.handler = handler - self.subscribe(self.node, self.propertyId, handler) + self.subscribe(self.node, self.id, handler) self._settable = True def send(self, value): @@ -33,27 +33,27 @@ def send(self, value): self.node.homie.baseTopic, self.node.homie.deviceId, self.node.nodeId, - self.propertyId, + self.id, ]), value, ) def representation(self): - repr = self.propertyId + repr = self.id if self._settable: repr += ":settable" return repr @property - def propertyId(self): - return self._propertyId + def id(self): + return self._id - @propertyId.setter - def propertyId(self, propertyId): - if isIdFormat(propertyId): - self._propertyId = propertyId + @id.setter + def id(self, id): + if isIdFormat(id): + self._id = id else: - logger.warning("'{}' has no valid ID-Format".format(propertyId)) + logger.warning("'{}' has no valid ID-Format".format(id)) @property def propertyName(self): @@ -88,22 +88,22 @@ def propertyFormat(self, propertyFormat): self.propertyFormat = propertyFormat -class HomieNodeRange(HomieNodeProperty): +class HomieNodePropertyRange(HomieNodeProperty): """docstring for HomieNodeRange""" - def __init__(self, node, propertyId, lower, upper): - super(HomieNodeRange, self).__init__(node, propertyId) + def __init__(self, node, id, lower, upper, name=None, unit=None, datatype=None, format=None): + super(HomieNodePropertyRange, self).__init__(node, id, name, unit, datatype, format) self.node = node self._range = range(lower, upper + 1) self.range = None self.lower = lower self.upper = upper - self.range_names = [(propertyId + "_" + str(x)) for x in self._range] + self.range_names = [(id + "_" + str(x)) for x in self._range] def settable(self, handler): self.handler = handler for x in self._range: - self.subscribe(self.node, "{}_{}".format(self.propertyId, x), handler) + self.subscribe(self.node, "{}_{}".format(self.id, x), handler) def setRange(self, lower, upper): # Todo: validate input @@ -123,13 +123,13 @@ def send(self, value): self.node.homie.baseTopic, self.node.homie.deviceId, self.node.nodeId, - self.propertyId + "_" + str(x), + self.id + "_" + str(x), ]), value, ) def representation(self): - repr = "{}[{}-{}]".format(self.propertyId, self.lower, self.upper) + repr = "{}[{}-{}]".format(self.id, self.lower, self.upper) if self._settable: repr += ":settable" return repr From 1cee0b2d8cf7122552804aa682846bb5990d2fbc Mon Sep 17 00:00:00 2001 From: Aitor Iturrioz Date: Sun, 7 Oct 2018 12:34:55 +0200 Subject: [PATCH 06/31] Rename 'announce' methods to 'addProperty' --- homie/node.py | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/homie/node.py b/homie/node.py index de79c10..7893115 100755 --- a/homie/node.py +++ b/homie/node.py @@ -2,7 +2,7 @@ import logging from homie.helpers import isIdFormat from homie.property import HomieNodeProperty -from homie.property import HomieNodeRange +from homie.property import HomieNodePropertyRange logger = logging.getLogger(__name__) class HomieNode(object): @@ -15,25 +15,32 @@ def __init__(self, homie, nodeId, nodeType): self.nodeType = nodeType self.properties = {} - def advertise(self, propertyId): - if propertyId not in self.properties: - homieNodeProperty = HomieNodeProperty(self, propertyId) + def addProperty(self, id=None, name=None, unit=None, datatype=None, format=None): + if not id: + logger.error("'id' required for HomieNodeProperty") + return + if id not in self.properties: + homieNodeProperty = HomieNodeProperty(id, name, unit, datatype, format) homieNodeProperty.setSubscribe(self.homie.subscribe) if homieNodeProperty: - self.properties[propertyId] = homieNodeProperty - return(homieNodeProperty) + self.properties[id] = homieNodeProperty else: - logger.warning("Property '{}' already announced.".format(propertyId)) - - def advertiseRange(self, propertyId, lower, upper): - if propertyId not in self.properties: - homieNodeRange = HomieNodeRange(self, propertyId, lower, upper) - homieNodeRange.setSubscribe(self.homie.subscribe) - if homieNodeRange: - self.properties[propertyId] = homieNodeRange - return(homieNodeRange) + logger.warning("Property '{}' already created.".format(id)) + + def addPropertyRange(self, id=None, lower=None, upper=None, name=None, unit=None, datatype=None, format=None): + if not id: + logger.error("'id' value required for HomieNodePropertyRange") + return + if not lower or not upper: + logger.error("'lower' and 'upper' values required for HomieNodePropertyRange") + return + if id not in self.properties: + homieNodePropertyRange = HomieNodePropertyRange(id, lower, upper, name, unit, datatype, format) + homieNodePropertyRange.setSubscribe(self.homie.subscribe) + if homieNodePropertyRange: + self.properties[id] = homieNodePropertyRange else: - logger.warning("Property '{}' already announced.".format(propertyId)) + logger.warning("PropertyRange '{}' alread created.".format(id)) def setProperty(self, propertyId): if propertyId not in self.properties: From e1b59badc45b471eb3ac4825edd25352bea1cc3c Mon Sep 17 00:00:00 2001 From: Aitor Iturrioz Date: Sun, 7 Oct 2018 13:00:46 +0200 Subject: [PATCH 07/31] Return 'property' object in the addProperty method --- homie/node.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/homie/node.py b/homie/node.py index 7893115..dad1bba 100755 --- a/homie/node.py +++ b/homie/node.py @@ -24,8 +24,10 @@ def addProperty(self, id=None, name=None, unit=None, datatype=None, format=None) homieNodeProperty.setSubscribe(self.homie.subscribe) if homieNodeProperty: self.properties[id] = homieNodeProperty + return homieNodeProperty else: logger.warning("Property '{}' already created.".format(id)) + return self.properties[id] def addPropertyRange(self, id=None, lower=None, upper=None, name=None, unit=None, datatype=None, format=None): if not id: @@ -39,8 +41,10 @@ def addPropertyRange(self, id=None, lower=None, upper=None, name=None, unit=None homieNodePropertyRange.setSubscribe(self.homie.subscribe) if homieNodePropertyRange: self.properties[id] = homieNodePropertyRange + return homieNodePropertyRange else: logger.warning("PropertyRange '{}' alread created.".format(id)) + return self.properties[id] def setProperty(self, propertyId): if propertyId not in self.properties: From e2b271926e0096ef7978e3ce8383681bcdf29d3e Mon Sep 17 00:00:00 2001 From: Aitor Iturrioz Date: Sun, 7 Oct 2018 13:17:23 +0200 Subject: [PATCH 08/31] Rename 'setProperty' to 'getProperty' --- homie/node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homie/node.py b/homie/node.py index dad1bba..30f0631 100755 --- a/homie/node.py +++ b/homie/node.py @@ -46,7 +46,7 @@ def addPropertyRange(self, id=None, lower=None, upper=None, name=None, unit=None logger.warning("PropertyRange '{}' alread created.".format(id)) return self.properties[id] - def setProperty(self, propertyId): + def getProperty(self, propertyId): if propertyId not in self.properties: raise ValueError("Property '{}' does not exist".format(propertyId)) return self.properties[propertyId] From 30a204e2787287bd9d5701dfa2cb34542dc39ee9 Mon Sep 17 00:00:00 2001 From: Aitor Iturrioz Date: Sun, 7 Oct 2018 13:22:09 +0200 Subject: [PATCH 09/31] Publish property attributes when nodes are published --- homie/node.py | 3 +++ homie/property.py | 24 ++++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/homie/node.py b/homie/node.py index 30f0631..c5504c3 100755 --- a/homie/node.py +++ b/homie/node.py @@ -59,6 +59,9 @@ def sendProperties(self): payload = ",".join([property.representation() for id, property in self.properties.items()]) self.homie.publish(nodeTopic + "/$properties", payload) + for nodeProperty in self.properties.items(): + nodeProperty.publishAttributes() + @property def nodeId(self): return self._nodeId diff --git a/homie/property.py b/homie/property.py index a8365fe..61a5328 100644 --- a/homie/property.py +++ b/homie/property.py @@ -44,6 +44,30 @@ def representation(self): repr += ":settable" return repr + def publishAttribute(self, name, value): + self.node.homie.publish( + "/".join([ + self.node.homie.baseTopic, + self.node.homie.deviceId, + self.node.nodeId, + self.id, + "${}".format(name) + ]), + value, + ) + + def publishAttributes(self): + if self.propertyName: + self.publishAttribute("name", self.propertyName) + if self._settable: + self.publishAttribute("settable", self._settable) + if self.propertyUnit: + self.publishAttribute("unit", self.propertyUnit) + if self.propertyDatatype: + self.publishAttribute("datatype", self.propertyDatatype) + if self.propertyFormat: + self.publishAttribute("format", self.propertyFormat) + @property def id(self): return self._id From 5f00be25f2cfea0af40f5dcf3cd17a5295114b9f Mon Sep 17 00:00:00 2001 From: Aitor Iturrioz Date: Sun, 7 Oct 2018 13:23:56 +0200 Subject: [PATCH 10/31] Rename 'sendProperties' to 'publishProperties' --- homie/node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homie/node.py b/homie/node.py index c5504c3..4c21dab 100755 --- a/homie/node.py +++ b/homie/node.py @@ -51,7 +51,7 @@ def getProperty(self, propertyId): raise ValueError("Property '{}' does not exist".format(propertyId)) return self.properties[propertyId] - def sendProperties(self): + def publishProperties(self): nodeTopic = "/".join([self.homie.baseTopic, self.homie.deviceId, self.nodeId]) self.homie.publish(nodeTopic + "/$type", self.nodeType) From bce5fee011b0ff0bad3a938200e05ebb39c281a1 Mon Sep 17 00:00:00 2001 From: Aitor Iturrioz Date: Sun, 7 Oct 2018 17:41:22 +0200 Subject: [PATCH 11/31] Properly implement fields getters and setters --- homie/property.py | 45 +++++++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/homie/property.py b/homie/property.py index 61a5328..60841b3 100644 --- a/homie/property.py +++ b/homie/property.py @@ -14,6 +14,10 @@ def __init__(self, node, id, name=None, unit=None, datatype=None, format=None): super(HomieNodeProperty, self).__init__() self.node = node # stores ref to node self._id = None + self._propertyName = None + self._propertyUnit = None + self._propertyDatatype = None + self._propertyFormat = None self.id = id self.propertyName = name self.propertyUnit = unit @@ -57,16 +61,17 @@ def publishAttribute(self, name, value): ) def publishAttributes(self): - if self.propertyName: - self.publishAttribute("name", self.propertyName) + if self._propertyName: + self.publishAttribute("name", self._propertyName) if self._settable: self.publishAttribute("settable", self._settable) - if self.propertyUnit: - self.publishAttribute("unit", self.propertyUnit) - if self.propertyDatatype: - self.publishAttribute("datatype", self.propertyDatatype) - if self.propertyFormat: - self.publishAttribute("format", self.propertyFormat) + if self._propertyUnit: + self.publishAttribute("unit", self._propertyUnit) + if self._propertyDatatype: + logger.error("Datatype: {}".format(self._propertyDatatype)) + self.publishAttribute("datatype", self._propertyDatatype) + if self._propertyFormat: + self.publishAttribute("format", self._propertyFormat) @property def id(self): @@ -81,35 +86,35 @@ def id(self, id): @property def propertyName(self): - return self.propertyName + return self._propertyName @propertyName.setter - def propertyName(self, propertyName): - self.propertyName = propertyName + def propertyName(self, name): + self._propertyName = name @property def propertyUnit(self): - return self.propertyUnit + return self._propertyUnit @propertyUnit.setter - def propertyUnit(self, propertyUnit): - self.propertyUnit = propertyUnit + def propertyUnit(self, unit): + self._propertyUnit = unit @property def propertyDatatype(self): - return self.propertyDatatype + return self._propertyDatatype @propertyDatatype.setter - def propertyDatatype(self, propertyDatatype): - self.propertyDatatype = property + def propertyDatatype(self, datatype): + self._propertyDatatype = datatype @property def propertyFormat(self): - return self.propertyFormat + return self._propertyFormat @propertyFormat.setter - def propertyFormat(self, propertyFormat): - self.propertyFormat = propertyFormat + def propertyFormat(self, format): + self._propertyFormat = format class HomieNodePropertyRange(HomieNodeProperty): From c12631d7ee06b4d9c43f9af5aeed3e878f039fa0 Mon Sep 17 00:00:00 2001 From: Aitor Iturrioz Date: Sun, 7 Oct 2018 17:43:10 +0200 Subject: [PATCH 12/31] Add validation functions for format and units --- homie/helpers.py | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/homie/helpers.py b/homie/helpers.py index ffe82b8..ccf530b 100644 --- a/homie/helpers.py +++ b/homie/helpers.py @@ -13,11 +13,11 @@ def generateDeviceId(): return "{:02x}".format(get_mac()) -def isIdFormat(idString): +def isValidId(idString): """Validate device Id.""" logger.debug("isIdFormat") if isinstance(idString, str): - r = re.compile('(^(?!\-)[a-z0-9\-]+(? Date: Sun, 7 Oct 2018 18:12:11 +0200 Subject: [PATCH 13/31] Fix error in isValidId function --- homie/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homie/helpers.py b/homie/helpers.py index ccf530b..f4f2403 100644 --- a/homie/helpers.py +++ b/homie/helpers.py @@ -17,7 +17,7 @@ def isValidId(idString): """Validate device Id.""" logger.debug("isIdFormat") if isinstance(idString, str): - r = re.compile('r(^(?!\-)[a-z0-9\-]+(? Date: Sun, 7 Oct 2018 18:31:44 +0200 Subject: [PATCH 14/31] Improve format validation --- homie/helpers.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/homie/helpers.py b/homie/helpers.py index f4f2403..7192621 100644 --- a/homie/helpers.py +++ b/homie/helpers.py @@ -29,8 +29,11 @@ def isValidDatatype(datatype): def isValidFormat(datatype, format): """Validate format""" + # False if there is a format without datatype if not datatype and format: return False + if not format: + format = "" if datatype == "color": allowed_formats = ("rgb", "hsv") return True if format in allowed_formats else False From b9a285caf350fe2cc6033829c107daa8467a33b6 Mon Sep 17 00:00:00 2001 From: Aitor Iturrioz Date: Sun, 7 Oct 2018 18:32:27 +0200 Subject: [PATCH 15/31] Check if datatype, format and unit are valid --- homie/property.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/homie/property.py b/homie/property.py index 60841b3..868633b 100644 --- a/homie/property.py +++ b/homie/property.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- import logging -from homie.helpers import isIdFormat +from homie.helpers import isValidId, isValidDatatype, isValidFormat, isValidUnit logger = logging.getLogger(__name__) class HomieNodeProperty(object): @@ -68,7 +68,6 @@ def publishAttributes(self): if self._propertyUnit: self.publishAttribute("unit", self._propertyUnit) if self._propertyDatatype: - logger.error("Datatype: {}".format(self._propertyDatatype)) self.publishAttribute("datatype", self._propertyDatatype) if self._propertyFormat: self.publishAttribute("format", self._propertyFormat) @@ -79,7 +78,7 @@ def id(self): @id.setter def id(self, id): - if isIdFormat(id): + if isValidId(id): self._id = id else: logger.warning("'{}' has no valid ID-Format".format(id)) @@ -98,7 +97,11 @@ def propertyUnit(self): @propertyUnit.setter def propertyUnit(self, unit): - self._propertyUnit = unit + if unit: + if isValidUnit(unit): + self._propertyUnit = unit + else: + logger.warning("'{}' is not a valid unit".format(unit)) @property def propertyDatatype(self): @@ -106,7 +109,11 @@ def propertyDatatype(self): @propertyDatatype.setter def propertyDatatype(self, datatype): - self._propertyDatatype = datatype + if datatype: + if isValidDatatype(datatype): + self._propertyDatatype = datatype + else: + logger.warning("'{}' is not a valid datatype".format(datatype)) @property def propertyFormat(self): @@ -114,8 +121,11 @@ def propertyFormat(self): @propertyFormat.setter def propertyFormat(self, format): - self._propertyFormat = format - + if format or self._propertyDatatype: + if isValidFormat(self._propertyDatatype, format): + self._propertyFormat = format + else: + logger.warning("'{}' is not a valid format for {}".format(format, self._propertyDatatype)) class HomieNodePropertyRange(HomieNodeProperty): """docstring for HomieNodeRange""" From ff65f56dc4366aad3e08a85c0a63d4b246530bed Mon Sep 17 00:00:00 2001 From: Aitor Iturrioz Date: Sun, 7 Oct 2018 18:34:39 +0200 Subject: [PATCH 16/31] Fix property creation in node class --- homie/node.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/homie/node.py b/homie/node.py index 4c21dab..eba0072 100755 --- a/homie/node.py +++ b/homie/node.py @@ -1,6 +1,5 @@ #!/usr/bin/env python import logging -from homie.helpers import isIdFormat from homie.property import HomieNodeProperty from homie.property import HomieNodePropertyRange logger = logging.getLogger(__name__) @@ -20,7 +19,7 @@ def addProperty(self, id=None, name=None, unit=None, datatype=None, format=None) logger.error("'id' required for HomieNodeProperty") return if id not in self.properties: - homieNodeProperty = HomieNodeProperty(id, name, unit, datatype, format) + homieNodeProperty = HomieNodeProperty(self, id, name, unit, datatype, format) homieNodeProperty.setSubscribe(self.homie.subscribe) if homieNodeProperty: self.properties[id] = homieNodeProperty @@ -37,7 +36,7 @@ def addPropertyRange(self, id=None, lower=None, upper=None, name=None, unit=None logger.error("'lower' and 'upper' values required for HomieNodePropertyRange") return if id not in self.properties: - homieNodePropertyRange = HomieNodePropertyRange(id, lower, upper, name, unit, datatype, format) + homieNodePropertyRange = HomieNodePropertyRange(self, id, lower, upper, name, unit, datatype, format) homieNodePropertyRange.setSubscribe(self.homie.subscribe) if homieNodePropertyRange: self.properties[id] = homieNodePropertyRange @@ -59,8 +58,8 @@ def publishProperties(self): payload = ",".join([property.representation() for id, property in self.properties.items()]) self.homie.publish(nodeTopic + "/$properties", payload) - for nodeProperty in self.properties.items(): - nodeProperty.publishAttributes() + for property in self.properties.values(): + property.publishAttributes() @property def nodeId(self): From 4efbcea45b02f4c914e36ca8064011e5fb184f9f Mon Sep 17 00:00:00 2001 From: Aitor Iturrioz Date: Sun, 7 Oct 2018 19:09:26 +0200 Subject: [PATCH 17/31] Check if nodeId is valid --- homie/node.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/homie/node.py b/homie/node.py index eba0072..4c9415d 100755 --- a/homie/node.py +++ b/homie/node.py @@ -1,5 +1,6 @@ #!/usr/bin/env python import logging +from homie.helpers import isValidId from homie.property import HomieNodeProperty from homie.property import HomieNodePropertyRange logger = logging.getLogger(__name__) @@ -67,7 +68,10 @@ def nodeId(self): @nodeId.setter def nodeId(self, nodeId): - self._nodeId = nodeId + if isValidId(nodeId): + self._nodeId = nodeId + else: + raise ValueError("'{}' is not a valid ID for a node".format(nodeId)) @property def nodeType(self): From 8fe0097a09bbd5717a4eaa5a8e3bd41c98fce9b0 Mon Sep 17 00:00:00 2001 From: Aitor Iturrioz Date: Sun, 7 Oct 2018 19:10:21 +0200 Subject: [PATCH 18/31] Raise ValueError exception if property id is invalid --- homie/property.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homie/property.py b/homie/property.py index 868633b..44f8de8 100644 --- a/homie/property.py +++ b/homie/property.py @@ -81,7 +81,7 @@ def id(self, id): if isValidId(id): self._id = id else: - logger.warning("'{}' has no valid ID-Format".format(id)) + raise ValueError("'{}' is not a valid ID for a property".format(id)) @property def propertyName(self): From c7685b91280eaa727c9948bce977ccf32013b4fc Mon Sep 17 00:00:00 2001 From: Aitor Iturrioz Date: Sun, 7 Oct 2018 20:14:12 +0200 Subject: [PATCH 19/31] Update the subscribe mechanism for properties --- homie/node.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homie/node.py b/homie/node.py index 4c9415d..a6461f9 100755 --- a/homie/node.py +++ b/homie/node.py @@ -21,7 +21,7 @@ def addProperty(self, id=None, name=None, unit=None, datatype=None, format=None) return if id not in self.properties: homieNodeProperty = HomieNodeProperty(self, id, name, unit, datatype, format) - homieNodeProperty.setSubscribe(self.homie.subscribe) + homieNodeProperty.setSubscribe(self.homie.subscribeProperty) if homieNodeProperty: self.properties[id] = homieNodeProperty return homieNodeProperty @@ -38,7 +38,7 @@ def addPropertyRange(self, id=None, lower=None, upper=None, name=None, unit=None return if id not in self.properties: homieNodePropertyRange = HomieNodePropertyRange(self, id, lower, upper, name, unit, datatype, format) - homieNodePropertyRange.setSubscribe(self.homie.subscribe) + homieNodePropertyRange.setSubscribe(self.homie.subscribeProperty) if homieNodePropertyRange: self.properties[id] = homieNodePropertyRange return homieNodePropertyRange From 9511fcf5a8a85f36c6c67a6a3bda420e8a0f5f80 Mon Sep 17 00:00:00 2001 From: Aitor Iturrioz Date: Sun, 7 Oct 2018 20:16:39 +0200 Subject: [PATCH 20/31] Rename 'send' method to 'update' --- homie/property.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homie/property.py b/homie/property.py index 44f8de8..710caac 100644 --- a/homie/property.py +++ b/homie/property.py @@ -31,7 +31,7 @@ def settable(self, handler): self.subscribe(self.node, self.id, handler) self._settable = True - def send(self, value): + def update(self, value): self.node.homie.publish( "/".join([ self.node.homie.baseTopic, From 2d4cb76f6642e1d1cc52f00e88eb68f81665bdca Mon Sep 17 00:00:00 2001 From: Aitor Iturrioz Date: Sun, 7 Oct 2018 20:30:01 +0200 Subject: [PATCH 21/31] Rework main file - Rename Homie class to Device. - Update Node method to addNode. - Update property subscription mechanism. callback(property, value). --- homie/main.py | 61 ++++++++++++++++++++------------------------------- 1 file changed, 24 insertions(+), 37 deletions(-) diff --git a/homie/main.py b/homie/main.py index ab3196d..c1f0709 100755 --- a/homie/main.py +++ b/homie/main.py @@ -11,7 +11,7 @@ from homie.timer import HomieTimer from homie.node import HomieNode from homie.networkinformation import NetworkInformation -from homie.helpers import isIdFormat, generateDeviceId +from homie.helpers import isValidId, generateDeviceId logger = logging.getLogger(__name__) HOMIE_VERSION = "2.0.1" @@ -49,11 +49,11 @@ def loadConfigFile(configFile): return config -class Homie(object): - """docstring for Homie""" +class Device(object): + """docstring for Device""" def __init__(self, config): - super(Homie, self).__init__() + super(Device, self).__init__() atexit.register(self._exitus) self.deviceId = None @@ -75,9 +75,10 @@ def __init__(self, config): self.startTime = time.time() self.fwname = None self.fwversion = None - self.nodes = [] + self.nodes = {} self.timers = [] self.subscriptions = [] + self.subscriptions_handlers = {} self.subscribe_all_forced = False self.statsInterval = 60 @@ -101,9 +102,9 @@ def Timer(self, *args, **kwargs): self.timers.append(homieTimer) return(homieTimer) - def Node(self, nodeId, nodeType): + def addNode(self, nodeId, nodeType): homieNode = HomieNode(self, nodeId, nodeType) - self.nodes.append(homieNode) + self.nodes[nodeId] = homieNode return(homieNode) def _initAttrs(self, config): @@ -244,31 +245,15 @@ def setNodeProperty(self, homieNode, prop, val, retain=True): ]) 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) + def subscribePropertyHandler(self, mqttc, obj, msg): + topic = msg.topic + value = msg.payload.decode("UTF-8") - 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) + 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 """ @@ -282,18 +267,20 @@ def subscribeProperty(self, homieNode, attr, callback, qos=None): [ self.mqtt_topic, homieNode.nodeId, - attr + 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, callback) + self.mqtt.message_callback_add(subscription, self.subscribePropertyHandler) def publish(self, topic, payload, retain=True, qos=None, **kwargs): """ Publish messages to MQTT, if connected """ @@ -321,13 +308,13 @@ def publish(self, topic, payload, retain=True, qos=None, **kwargs): def publishNodes(self): """ Publish registered nodes and their properties to MQTT """ payload = ",".join( - [str(x.nodeId) for x in self.nodes]) + list(self.nodes.keys())) self.publish( self.mqtt_topic + "/$nodes", payload=payload, retain=True) - for node in self.nodes: - node.sendProperties() + for node in self.nodes.values(): + node.publishProperties() def publishLocalipAndMac(self): """Publish local IP and MAC Addresses to MQTT.""" @@ -426,7 +413,7 @@ def deviceId(self): @deviceId.setter def deviceId(self, deviceId): - if isIdFormat(deviceId): + if isValidId(deviceId): self._deviceId = deviceId else: self._deviceId = generateDeviceId() From 27581ad17accdf832027e708da45e3bc0e5601b9 Mon Sep 17 00:00:00 2001 From: Aitor Iturrioz Date: Sun, 7 Oct 2018 22:21:34 +0200 Subject: [PATCH 22/31] Publish 'stats' information --- homie/main.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/homie/main.py b/homie/main.py index c1f0709..26ea09e 100755 --- a/homie/main.py +++ b/homie/main.py @@ -197,6 +197,7 @@ def _connected(self, *args): self.publishFwversion() self.publishNodes() self.publishLocalipAndMac() + self.publishStats() self.publishStatsInterval() self.publishUptime() self.publishSignal() @@ -330,6 +331,16 @@ def publishLocalipAndMac(self): self.mqtt_topic + "/$mac", payload=local_mac, retain=True) + def publishStats(self): + """Publish stats info""" + payload = ",".join([ + "interval", "uptime", "signal" + ]) + self.publish( + self.mqtt_topic + "/$stats", + payload=payload, retain=True + ) + def publishStatsInterval(self): """ Publish /$stats/interval to MQTT """ payload = self.statsInterval From 0f88fbf38e6dbb21e0b4a3e5630dd4b486a81516 Mon Sep 17 00:00:00 2001 From: Aitor Iturrioz Date: Sun, 7 Oct 2018 22:34:55 +0200 Subject: [PATCH 23/31] Implement 'state' attributes --- homie/main.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/homie/main.py b/homie/main.py index 26ea09e..9fcdb2d 100755 --- a/homie/main.py +++ b/homie/main.py @@ -144,7 +144,7 @@ def _initialize(self): self.mqtt.on_disconnect = self._disconnected self.mqtt.will_set( - self.mqtt_topic + "/$online", payload="false", retain=True) + self.mqtt_topic + "/$online", payload="lost", retain=True) if self.username: self.mqtt.username_pw_set(self.username, password=self.password) @@ -186,22 +186,27 @@ def _connected(self, *args): self._subscribe() self.publish( - self.mqtt_topic + "/$online", - payload="true", retain=True) + 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.publishNodes() - self.publishLocalipAndMac() + self.publishImplementation() self.publishStats() self.publishStatsInterval() self.publishUptime() self.publishSignal() - self.publishImplementation() + self.publishNodes() + + self.publish( + self.mqtt_topic + "/$state", + payload="ready", retain=True + ) def _subscribed(self, *args): # logger.debug("_subscribed: {}".format(args)) @@ -455,7 +460,7 @@ def _exitus(self): self.publish( self.mqtt_topic + "/$online", - payload="false", retain=True) + payload="disconnected", retain=True) self.mqtt.loop_stop() self.mqtt.disconnect() From 840fde0a0dc856ecf87a0dfcdafa16d1daf797cf Mon Sep 17 00:00:00 2001 From: Aitor Iturrioz Date: Sun, 7 Oct 2018 22:42:25 +0200 Subject: [PATCH 24/31] Implement 'name' attribute for nodes --- homie/main.py | 4 ++-- homie/node.py | 12 +++++++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/homie/main.py b/homie/main.py index 9fcdb2d..6badad4 100755 --- a/homie/main.py +++ b/homie/main.py @@ -102,8 +102,8 @@ def Timer(self, *args, **kwargs): self.timers.append(homieTimer) return(homieTimer) - def addNode(self, nodeId, nodeType): - homieNode = HomieNode(self, nodeId, nodeType) + def addNode(self, nodeId, nodeName, nodeType): + homieNode = HomieNode(self, nodeId, nodeName, nodeType) self.nodes[nodeId] = homieNode return(homieNode) diff --git a/homie/node.py b/homie/node.py index a6461f9..8cc4605 100755 --- a/homie/node.py +++ b/homie/node.py @@ -8,10 +8,11 @@ class HomieNode(object): """docstring for HomieNode""" - def __init__(self, homie, nodeId, nodeType): + def __init__(self, homie, nodeId, nodeName, nodeType): super(HomieNode, self).__init__() self.homie = homie self.nodeId = nodeId + self.nodeName = nodeName self.nodeType = nodeType self.properties = {} @@ -54,6 +55,7 @@ def getProperty(self, propertyId): def publishProperties(self): nodeTopic = "/".join([self.homie.baseTopic, self.homie.deviceId, self.nodeId]) + self.homie.publish(nodeTopic + "/$name", self.nodeName) self.homie.publish(nodeTopic + "/$type", self.nodeType) payload = ",".join([property.representation() for id, property in self.properties.items()]) @@ -73,6 +75,14 @@ def nodeId(self, nodeId): else: raise ValueError("'{}' is not a valid ID for a node".format(nodeId)) + @property + def nodeName(self): + return self._nodeName + + @nodeName.setter + def nodeName(self, nodeName): + self._nodeName = nodeName + @property def nodeType(self): return self._nodeType From 2dc396841aa18ec48ef692052f46471484b80bdd Mon Sep 17 00:00:00 2001 From: Aitor Iturrioz Date: Sun, 7 Oct 2018 22:43:57 +0200 Subject: [PATCH 25/31] Update homie version to 3.0.0 --- homie/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homie/main.py b/homie/main.py index 6badad4..2f3d2b9 100755 --- a/homie/main.py +++ b/homie/main.py @@ -14,7 +14,7 @@ from homie.helpers import isValidId, generateDeviceId logger = logging.getLogger(__name__) -HOMIE_VERSION = "2.0.1" +HOMIE_VERSION = "3.0.0" DEFAULT_PREFS = { "CA_CERTS": {"key": "ca_certs", "val": None}, "DEVICE_ID": {"key": "deviceId", "val": "xxxxxxxx"}, From 83e556bd5250279f29400e255fd592908ea563d6 Mon Sep 17 00:00:00 2001 From: Aitor Iturrioz Date: Sun, 7 Oct 2018 23:03:45 +0200 Subject: [PATCH 26/31] Update all example files --- examples/relay_switch.py | 22 +++++++++++----------- examples/relay_switch_with_config.py | 21 ++++++++++----------- examples/temperatureDS18B20_raspi.py | 12 ++++++------ examples/temperature_raspi.py | 12 ++++++------ examples/temperature_sensor.py | 20 ++++++++++---------- 5 files changed, 43 insertions(+), 44 deletions(-) diff --git a/examples/relay_switch.py b/examples/relay_switch.py index 2be6514..14dc307 100755 --- a/examples/relay_switch.py +++ b/examples/relay_switch.py @@ -6,24 +6,24 @@ logger = logging.getLogger(__name__) config = homie.loadConfigFile("homie-python.json") -Homie = homie.Homie(config) -switchNode = Homie.Node("switch", "switch") +device = homie.Device(config) +switchNode = device.addNode("switch", "switch", "switch") +switchProperty = switchNode.addProperty("on") - -def switchOnHandler(mqttc, obj, msg): - payload = msg.payload.decode("UTF-8").lower() - if payload == 'true': +def switchOnHandler(property, value): + if value == 'true': logger.info("Switch: ON") - switchNode.setProperty("on").send("true") + property.update("true") else: logger.info("Switch: OFF") - switchNode.setProperty("on").send("false") + property.update("false") def main(): - Homie.setFirmware("relay-switch", "1.0.0") - switchNode.advertise("on").settable(switchOnHandler) - Homie.setup() + device.setFirmware("relay-switch", "1.0.0") + switchProperty.settable(switchOnHandler) + + device.setup() while True: time.sleep(1) diff --git a/examples/relay_switch_with_config.py b/examples/relay_switch_with_config.py index 538dfcf..986536b 100755 --- a/examples/relay_switch_with_config.py +++ b/examples/relay_switch_with_config.py @@ -17,24 +17,23 @@ "TOPIC": "homie" } -Homie = homie.Homie(config) -switchNode = Homie.Node("switch", "switch") +device = homie.Device(config) +switchNode = device.addNode("switch", "switch", "switch") +switchProperty = switchNode.addProperty("on") - -def switchOnHandler(mqttc, obj, msg): - payload = msg.payload.decode("UTF-8").lower() - if payload == 'true': +def switchOnHandler(property, value): + if value == 'true': logger.info("Switch: ON") - switchNode.setProperty("on").send("true") + property.update("true") else: logger.info("Switch: OFF") - switchNode.setProperty("on").send("false") + property.update("false") def main(): - Homie.setFirmware("relay-switch", "1.0.0") - switchNode.advertise("on").settable(switchOnHandler) - Homie.setup() + device.setFirmware("relay-switch", "1.0.0") + switchProperty.settable(switchOnHandler) + device.setup() while True: time.sleep(1) diff --git a/examples/temperatureDS18B20_raspi.py b/examples/temperatureDS18B20_raspi.py index 23433e6..a815942 100644 --- a/examples/temperatureDS18B20_raspi.py +++ b/examples/temperatureDS18B20_raspi.py @@ -19,8 +19,9 @@ TEMPERATURE_INTERVAL = 60 config = homie.loadConfigFile("homie-python.json") -Homie = homie.Homie(config) -temperatureNode = Homie.Node("temperature", "temperature") +device = homie.Device(config) +temperatureNode = device.addNode("temperature", "temperature", "temperature") +temperatureProperty = temperatureNode.addProperty("temperature", "Temperature value", "ºC", "float") def read_temp_raw(): f = open(device_file, 'r') @@ -41,15 +42,14 @@ def read_temp(): return temp_c def main(): - Homie.setFirmware("raspi-temperatureDS18B20", "1.0.0") - temperatureNode.advertise("degrees") + device.setFirmware("raspi-temperatureDS18B20", "1.0.0") - Homie.setup() + device.setup() while True: temperature = read_temp() logger.info("Temperature: {:0.2f} °C".format(temperature)) - temperatureNode.setProperty("degrees").send(temperature) + temperatureProperty.update(temperature) time.sleep(TEMPERATURE_INTERVAL) if __name__ == '__main__': diff --git a/examples/temperature_raspi.py b/examples/temperature_raspi.py index c1db48e..0921ecc 100755 --- a/examples/temperature_raspi.py +++ b/examples/temperature_raspi.py @@ -9,8 +9,9 @@ TEMPERATURE_INTERVAL = 60 config = homie.loadConfigFile("homie-python.json") -Homie = homie.Homie(config) -temperatureNode = Homie.Node("temperature", "temperature") +device = homie.Device(config) +temperatureNode = device.addNode("temperature", "temperature", "temperature") +temperatureProperty = temperatureNode.addProperty("temperature", "Temperature value", "ºC", "float") def getCpuTemperature(): @@ -21,15 +22,14 @@ def getCpuTemperature(): def main(): - Homie.setFirmware("raspi-temperature", "1.0.0") - temperatureNode.advertise("degrees") + device.setFirmware("raspi-temperature", "1.0.0") - Homie.setup() + device.setup() while True: temperature = getCpuTemperature() logger.info("Temperature: {:0.2f} °C".format(temperature)) - temperatureNode.setProperty("degrees").send(temperature) + temperatureProperty.update(temperature) time.sleep(TEMPERATURE_INTERVAL) if __name__ == '__main__': diff --git a/examples/temperature_sensor.py b/examples/temperature_sensor.py index e5ccc27..e3d9840 100755 --- a/examples/temperature_sensor.py +++ b/examples/temperature_sensor.py @@ -6,30 +6,30 @@ logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) -TEMPERATURE_INTERVAL = 3 +TEMPERATURE_INTERVAL = 60 config = homie.loadConfigFile("homie-python.json") -Homie = homie.Homie(config) -temperatureNode = Homie.Node("temperature", "temperature") -humidityNode = Homie.Node("humidity", "humidity") +device = homie.Device(config) +temperatureNode = device.addNode("temperature", "temperature", "temperature") +humidityNode = device.addNode("humidity", "humidity", "humidity") +temperatureProperty = temperatureNode.addProperty("temperature", "Temperature value", "ºC", "float") +humidityProperty = humidityNode.addProperty("humidity", "Humidity value", "%", "float", "0.0:100.0") def main(): - Homie.setFirmware("awesome-temperature", "1.0.0") - temperatureNode.advertise("degrees") - humidityNode.advertise("humidity") + device.setFirmware("awesome-temperature", "1.0.0") - Homie.setup() + device.setup() while True: temperature = 22.0 humidity = 60.0 logger.info("Temperature: {:0.2f} °C".format(temperature)) - temperatureNode.setProperty("degrees").send(temperature) + temperatureProperty.update(temperature) logger.info("Humidity: {:0.2f} %".format(humidity)) - humidityNode.setProperty("humidity").send(humidity) + humidityProperty.update(humidity) time.sleep(TEMPERATURE_INTERVAL) From fcb9561810521de5501d813732a6367b703387fc Mon Sep 17 00:00:00 2001 From: Aitor Iturrioz Date: Thu, 13 Dec 2018 09:55:40 +0100 Subject: [PATCH 27/31] Update to Homie 3.0.1 and add more examples --- .gitignore | 1 + examples/remote_with_config.py | 50 +++++++++++++++++++++++++ examples/thermostat_with_config.py | 60 ++++++++++++++++++++++++++++++ homie/main.py | 2 +- homie/node.py | 8 ++-- homie/property.py | 28 +++++++++----- setup.py | 2 +- 7 files changed, 135 insertions(+), 16 deletions(-) create mode 100644 examples/remote_with_config.py create mode 100644 examples/thermostat_with_config.py diff --git a/.gitignore b/.gitignore index 663c1ed..dc80c9d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ *.pyc *.sublime-* __pycache__ +build/* dist/* homie.egg-info/* MANIFEST diff --git a/examples/remote_with_config.py b/examples/remote_with_config.py new file mode 100644 index 0000000..6260db4 --- /dev/null +++ b/examples/remote_with_config.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python +import time +import random +import homie +import logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +config = { + "HOST": "iot.eclipse.org", + "PORT": 1883, + "KEEPALIVE": 10, + "USERNAME": "", + "PASSWORD": "", + "CA_CERTS": "", + "DEVICE_ID": "remote-control", + "DEVICE_NAME": "xxxxxxxx", + "TOPIC": "homie" +} + +device = homie.Device(config) +remoteNode = device.addNode("remote", "Remote", "Buttons") +playButtonProperty = remoteNode.addProperty("play", "Play Button", datatype="enum", format="PRESSED,RELEASED", retained=False) +nextButtonProperty = remoteNode.addProperty("next", "Next Button", datatype="enum", format="PRESSED,RELEASED", retained=False) +prevButtonProperty = remoteNode.addProperty("prev", "Prev Button", datatype="enum", format="PRESSED,RELEASED", retained=False) + + +def main(): + device.setFirmware("remote-control", "1.0.0") + + device.setup() + + buttonPressFrecuency = 30 + lastButtonPressTime = 0 + + while True: + # We simulate that the button is pressed (and released) every 30 seconds + if (time.time() - lastButtonPressTime) > buttonPressFrecuency: + playButtonProperty.update("PRESSED") + time.sleep(0.3) + playButtonProperty.update("RELEASED") + logger.info("Play button pressed and released") + lastButtonPressTime = time.time() + time.sleep(1) + +if __name__ == '__main__': + try: + main() + except (KeyboardInterrupt, SystemExit): + logger.info("Quitting.") \ No newline at end of file diff --git a/examples/thermostat_with_config.py b/examples/thermostat_with_config.py new file mode 100644 index 0000000..e82b8be --- /dev/null +++ b/examples/thermostat_with_config.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python +import time +import random +import homie +import logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +config = { + "HOST": "iot.eclipse.org", + "PORT": 1883, + "KEEPALIVE": 10, + "USERNAME": "", + "PASSWORD": "", + "CA_CERTS": "", + "DEVICE_ID": "xxxxxxxx", + "DEVICE_NAME": "xxxxxxxx", + "TOPIC": "homie" +} + +device = homie.Device(config) +thermostatNode = device.addNode("thermostat", "Thermostat", "thermostat") +temperatureProperty = thermostatNode.addProperty("temperature", "Indoor Temperature", "ºC", "float", "0:50") +setpointProperty = thermostatNode.addProperty("setpoint", "Setpoint", "ºC", "float", "10:30") +modeProperty = thermostatNode.addProperty("mode", "Thermostat Mode", datatype="enum", format="NORMAL,COLD,HEAT") + +def modeHandler(property, value): + logger.info("Changing thermostat mode to {}".format(value)) + property.update(value) + +def setpointHandler(property, value): + logger.info("Changing thermostat setpoint to {}ºC".format(value)) + property.update(value) + + +def main(): + device.setFirmware("thermostat", "1.0.0") + modeProperty.settable(modeHandler) + setpointProperty.settable(setpointHandler) + + device.setup() + + reportFrecuency = 60 + lastTemperatureTime = 0 + + setpointProperty.update(21.5) + modeProperty.update("NORMAL") + while True: + if (time.time() - lastTemperatureTime) > reportFrecuency: + temperature = random.uniform(20,25) + temperatureProperty.update("{0:.2f}".format(temperature)) + logger.info("New temperature value: {0:.2f}ºC".format(temperature)) + lastTemperatureTime = time.time() + time.sleep(1) + +if __name__ == '__main__': + try: + main() + except (KeyboardInterrupt, SystemExit): + logger.info("Quitting.") diff --git a/homie/main.py b/homie/main.py index 2f3d2b9..2b551f7 100755 --- a/homie/main.py +++ b/homie/main.py @@ -14,7 +14,7 @@ from homie.helpers import isValidId, generateDeviceId logger = logging.getLogger(__name__) -HOMIE_VERSION = "3.0.0" +HOMIE_VERSION = "3.0.1" DEFAULT_PREFS = { "CA_CERTS": {"key": "ca_certs", "val": None}, "DEVICE_ID": {"key": "deviceId", "val": "xxxxxxxx"}, diff --git a/homie/node.py b/homie/node.py index 8cc4605..8308e4a 100755 --- a/homie/node.py +++ b/homie/node.py @@ -16,12 +16,12 @@ def __init__(self, homie, nodeId, nodeName, nodeType): self.nodeType = nodeType self.properties = {} - def addProperty(self, id=None, name=None, unit=None, datatype=None, format=None): + def addProperty(self, id=None, name=None, unit=None, datatype=None, format=None, retained=True): if not id: logger.error("'id' required for HomieNodeProperty") return if id not in self.properties: - homieNodeProperty = HomieNodeProperty(self, id, name, unit, datatype, format) + homieNodeProperty = HomieNodeProperty(self, id, name, unit, datatype, format, retained) homieNodeProperty.setSubscribe(self.homie.subscribeProperty) if homieNodeProperty: self.properties[id] = homieNodeProperty @@ -30,7 +30,7 @@ def addProperty(self, id=None, name=None, unit=None, datatype=None, format=None) logger.warning("Property '{}' already created.".format(id)) return self.properties[id] - def addPropertyRange(self, id=None, lower=None, upper=None, name=None, unit=None, datatype=None, format=None): + def addPropertyRange(self, id=None, lower=None, upper=None, name=None, unit=None, datatype=None, format=None, retained=True): if not id: logger.error("'id' value required for HomieNodePropertyRange") return @@ -38,7 +38,7 @@ def addPropertyRange(self, id=None, lower=None, upper=None, name=None, unit=None logger.error("'lower' and 'upper' values required for HomieNodePropertyRange") return if id not in self.properties: - homieNodePropertyRange = HomieNodePropertyRange(self, id, lower, upper, name, unit, datatype, format) + homieNodePropertyRange = HomieNodePropertyRange(self, id, lower, upper, name, unit, datatype, format, retained) homieNodePropertyRange.setSubscribe(self.homie.subscribeProperty) if homieNodePropertyRange: self.properties[id] = homieNodePropertyRange diff --git a/homie/property.py b/homie/property.py index 710caac..449b9d2 100644 --- a/homie/property.py +++ b/homie/property.py @@ -10,7 +10,7 @@ class HomieNodeProperty(object): def setSubscribe(self, func): self.subscribe = func - def __init__(self, node, id, name=None, unit=None, datatype=None, format=None): + def __init__(self, node, id, name=None, unit=None, datatype=None, format=None, retained=True): super(HomieNodeProperty, self).__init__() self.node = node # stores ref to node self._id = None @@ -18,11 +18,13 @@ def __init__(self, node, id, name=None, unit=None, datatype=None, format=None): self._propertyUnit = None self._propertyDatatype = None self._propertyFormat = None + self._retained = True self.id = id self.propertyName = name self.propertyUnit = unit self.propertyDatatype = datatype self.propertyFormat = format + self.retained = retained self.handler = None self._settable = False @@ -40,13 +42,11 @@ def update(self, value): self.id, ]), value, + self._retained ) def representation(self): - repr = self.id - if self._settable: - repr += ":settable" - return repr + return self.id def publishAttribute(self, name, value): self.node.homie.publish( @@ -64,13 +64,15 @@ def publishAttributes(self): if self._propertyName: self.publishAttribute("name", self._propertyName) if self._settable: - self.publishAttribute("settable", self._settable) + self.publishAttribute("settable", str(self._settable).lower()) if self._propertyUnit: self.publishAttribute("unit", self._propertyUnit) if self._propertyDatatype: self.publishAttribute("datatype", self._propertyDatatype) if self._propertyFormat: self.publishAttribute("format", self._propertyFormat) + if not self._retained: + self.publishAttribute("retained", str(self._retained).lower()) @property def id(self): @@ -127,11 +129,19 @@ def propertyFormat(self, format): else: logger.warning("'{}' is not a valid format for {}".format(format, self._propertyDatatype)) + @property + def retained(self): + return self._retained + + @retained.setter + def retained(self, retained): + self._retained = retained + class HomieNodePropertyRange(HomieNodeProperty): """docstring for HomieNodeRange""" - def __init__(self, node, id, lower, upper, name=None, unit=None, datatype=None, format=None): - super(HomieNodePropertyRange, self).__init__(node, id, name, unit, datatype, format) + def __init__(self, node, id, lower, upper, name=None, unit=None, datatype=None, format=None, retained=True): + super(HomieNodePropertyRange, self).__init__(node, id, name, unit, datatype, format, retained) self.node = node self._range = range(lower, upper + 1) self.range = None @@ -169,7 +179,5 @@ def send(self, value): def representation(self): repr = "{}[{}-{}]".format(self.id, self.lower, self.upper) - if self._settable: - repr += ":settable" return repr diff --git a/setup.py b/setup.py index ea4b276..b5d7b5d 100755 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ """Provide a setup routine.""" from setuptools import setup -version = "0.4.1" +version = "0.5.0" setup( name="homie", From b4c4b41b4130060ba30dadd276f603169718a7ba Mon Sep 17 00:00:00 2001 From: Aitor Iturrioz Date: Thu, 3 Jan 2019 13:02:01 +0100 Subject: [PATCH 28/31] Fix $stats mismatch with specification --- homie/main.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/homie/main.py b/homie/main.py index 2b551f7..dd36639 100755 --- a/homie/main.py +++ b/homie/main.py @@ -228,10 +228,13 @@ def setup(self): # 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() @@ -339,7 +342,7 @@ def publishLocalipAndMac(self): def publishStats(self): """Publish stats info""" payload = ",".join([ - "interval", "uptime", "signal" + "signal" ]) self.publish( self.mqtt_topic + "/$stats", From da3362ba8d37a0213500a0b0f16332513e6787bd Mon Sep 17 00:00:00 2001 From: Aitor Iturrioz Date: Thu, 21 Mar 2019 16:08:05 +0100 Subject: [PATCH 29/31] Remove '' topic usage Signed-off-by: Aitor Iturrioz --- homie/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homie/main.py b/homie/main.py index dd36639..6daaae0 100755 --- a/homie/main.py +++ b/homie/main.py @@ -144,7 +144,7 @@ def _initialize(self): self.mqtt.on_disconnect = self._disconnected self.mqtt.will_set( - self.mqtt_topic + "/$online", payload="lost", retain=True) + self.mqtt_topic + "/$state", payload="lost", retain=True) if self.username: self.mqtt.username_pw_set(self.username, password=self.password) @@ -462,7 +462,7 @@ def _exitus(self): """ Clean up before exit """ self.publish( - self.mqtt_topic + "/$online", + self.mqtt_topic + "/$state", payload="disconnected", retain=True) self.mqtt.loop_stop() From f63afa1b1f45e0540eee33677efc4b6f76f881a9 Mon Sep 17 00:00:00 2001 From: Aitor Iturrioz Date: Thu, 21 Mar 2019 16:19:29 +0100 Subject: [PATCH 30/31] Remove "self.deviceID = None" statement --- homie/main.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homie/main.py b/homie/main.py index 6daaae0..3b066ee 100755 --- a/homie/main.py +++ b/homie/main.py @@ -56,7 +56,6 @@ def __init__(self, config): super(Device, self).__init__() atexit.register(self._exitus) - self.deviceId = None self.deviceName = None self.host = None self.port = None From 5a8388dce13bf4e4e7811ab41e7a0c6e47d1b712 Mon Sep 17 00:00:00 2001 From: Aitor Iturrioz Date: Thu, 21 Mar 2019 16:34:18 +0100 Subject: [PATCH 31/31] Allow no format for certain datatypes --- homie/helpers.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homie/helpers.py b/homie/helpers.py index 7192621..af6c1e4 100644 --- a/homie/helpers.py +++ b/homie/helpers.py @@ -32,8 +32,9 @@ def isValidFormat(datatype, format): # False if there is a format without datatype if not datatype and format: return False - if not format: - format = "" + # True if there is no format but the datatype is "string", "integer", "float" or "boolean" + if not format and datatype in ("string", "integer", "float", "boolean"): + return True if datatype == "color": allowed_formats = ("rgb", "hsv") return True if format in allowed_formats else False