diff --git a/HABApp/core/items/item.py b/HABApp/core/items/item.py index 8b277eda..333871cc 100644 --- a/HABApp/core/items/item.py +++ b/HABApp/core/items/item.py @@ -5,6 +5,13 @@ class Item: + """Simple item + + :ivar str ~.name: Name of the item + :ivar ~.value: Value of the item, can be anything + :ivar ~.datetime.datetime last_change: Timestamp of the last time when the item has changed the value + :ivar ~.datetime.datetime last_update: Timestamp of the last time when the item has updated the value + """ @classmethod def get_item(cls, name: str): @@ -64,7 +71,8 @@ def set_value(self, new_value) -> bool: return state_changed def post_value(self, new_value): - """Set a new value and post appropriate events on the event bus (``ValueUpdateEvent``, ``ValueChangeEvent``) + """Set a new value and post appropriate events on the HABApp event bus + (``ValueUpdateEvent``, ``ValueChangeEvent``) :param new_value: new value of the item """ @@ -72,18 +80,18 @@ def post_value(self, new_value): self.set_value(new_value) # create events - HABApp.core.EventBus.post_event(self.name, HABApp.core.events.ValueUpdateEvent(self.name, new_value)) - if old_value != new_value: + HABApp.core.EventBus.post_event(self.name, HABApp.core.events.ValueUpdateEvent(self.name, self.value)) + if old_value != self.value: HABApp.core.EventBus.post_event( - self.name, HABApp.core.events.ValueChangeEvent(self.name, value=new_value, old_value=old_value) + self.name, HABApp.core.events.ValueChangeEvent(self.name, value=self.value, old_value=old_value) ) return None def get_value(self, default_value=None) -> typing.Any: - """Return the state of the item. + """Return the value of the item. - :param default_value: Return this value if the item state is None - :return: State of the item + :param default_value: Return this value if the item value is None + :return: value of the item """ if self.value is None: return default_value diff --git a/HABApp/core/items/item_color.py b/HABApp/core/items/item_color.py index e6a68884..32112f98 100644 --- a/HABApp/core/items/item_color.py +++ b/HABApp/core/items/item_color.py @@ -17,11 +17,10 @@ def __init__(self, name: str, h=0.0, s=0.0, b=0.0): def set_value(self, hue=0.0, saturation=0.0, brightness=0.0): """Set the color value - + :param hue: hue (in °) :param saturation: saturation (in %) :param brightness: brightness (in %) - :return: """ # map tuples to variables @@ -47,7 +46,7 @@ def post_value(self, hue=0.0, saturation=0.0, brightness=0.0): def get_rgb(self, max_rgb_value=255) -> typing.Tuple[int, int, int]: """Return a rgb equivalent of the color - + :param max_rgb_value: the max value for rgb, typically 255 (default) or 65.536 :return: rgb tuple """ @@ -60,7 +59,7 @@ def get_rgb(self, max_rgb_value=255) -> typing.Tuple[int, int, int]: def set_rgb(self, r, g, b, max_rgb_value=255) -> 'ColorItem': """Set a rgb value - + :param r: red value :param g: green value :param b: blue value diff --git a/HABApp/mqtt/items/mqtt_item.py b/HABApp/mqtt/items/mqtt_item.py index 557fc744..a78b0bc6 100644 --- a/HABApp/mqtt/items/mqtt_item.py +++ b/HABApp/mqtt/items/mqtt_item.py @@ -4,12 +4,12 @@ class MqttItem(Item): - def publish(self, payload, qos=None, retain=None): + def publish(self, payload, qos: int = None, retain: bool = None): """ Publish the payload under the topic from the item. :param payload: MQTT Payload - :param qos: QoS, can be 0, 1 or 2. If not specified value from configuration file will be used. + :param qos: QoS, can be ``0``, ``1`` or ``2``. If not specified value from configuration file will be used. :param retain: retain message. If not specified value from configuration file will be used. :return: 0 if successful """ diff --git a/HABApp/openhab/definitions/__init__.py b/HABApp/openhab/definitions/__init__.py index fdff833e..d013600a 100644 --- a/HABApp/openhab/definitions/__init__.py +++ b/HABApp/openhab/definitions/__init__.py @@ -4,4 +4,4 @@ GROUP_FUNCTIONS = ['AND', 'OR', 'NAND', 'NOR', 'AVG', 'MAX', 'MIN', 'SUM'] -from .values import OnOffValue, PercentValue, UpDownValue, HSBValue \ No newline at end of file +from .values import OnOffValue, PercentValue, UpDownValue, HSBValue, QuantityValue \ No newline at end of file diff --git a/HABApp/openhab/definitions/values.py b/HABApp/openhab/definitions/values.py index 0bfeec76..5290a463 100644 --- a/HABApp/openhab/definitions/values.py +++ b/HABApp/openhab/definitions/values.py @@ -43,3 +43,18 @@ def __init__(self, value: str): def __str__(self): return f'{self.value[0]}°,{self.value[1]}%,{self.value[2]}%' + + +class QuantityValue(ComplexEventValue): + def __init__(self, value: str): + val, unit = value.split(' ') + try: + val = int(val) + except ValueError: + val = float(val) + + super().__init__(val) + self.unit = unit + + def __str__(self): + return f'{self.value} {self.unit}' diff --git a/HABApp/openhab/items/__init__.py b/HABApp/openhab/items/__init__.py index f58a3e4e..b5cfb22e 100644 --- a/HABApp/openhab/items/__init__.py +++ b/HABApp/openhab/items/__init__.py @@ -2,4 +2,5 @@ from .dimmer_item import DimmerItem from .rollershutter_item import RollershutterItem from .switch_item import SwitchItem -from .color_item import ColorItem \ No newline at end of file +from .color_item import ColorItem +from .number_item import NumberItem \ No newline at end of file diff --git a/HABApp/openhab/items/map_items.py b/HABApp/openhab/items/map_items.py index 4d6f3209..30101425 100644 --- a/HABApp/openhab/items/map_items.py +++ b/HABApp/openhab/items/map_items.py @@ -1,7 +1,7 @@ import datetime from HABApp.core.items import Item -from . import SwitchItem, ContactItem, RollershutterItem, DimmerItem, ColorItem +from . import SwitchItem, ContactItem, RollershutterItem, DimmerItem, ColorItem, NumberItem def map_items(name, openhab_type : str, openhab_value : str): @@ -29,13 +29,13 @@ def map_items(name, openhab_type : str, openhab_value : str): if openhab_type == "Number": if value is None: - return Item(name, value) + return NumberItem(name, value) # Number items can be int or float try: - return Item(name, int(value)) + return NumberItem(name, int(value)) except ValueError: - return Item(name, float(value)) + return NumberItem(name, float(value)) if openhab_type == "DateTime": if value is None: diff --git a/HABApp/openhab/items/number_item.py b/HABApp/openhab/items/number_item.py new file mode 100644 index 00000000..66fadcf4 --- /dev/null +++ b/HABApp/openhab/items/number_item.py @@ -0,0 +1,12 @@ +from HABApp.core.items import Item +from ..definitions import QuantityValue + + +class NumberItem(Item): + + def set_value(self, new_value) -> bool: + + if isinstance(new_value, QuantityValue): + return super().set_value(new_value.value) + + return super().set_value(new_value) diff --git a/HABApp/util/statistics.py b/HABApp/util/statistics.py index eba30f16..95e8749b 100644 --- a/HABApp/util/statistics.py +++ b/HABApp/util/statistics.py @@ -4,19 +4,20 @@ class Statistics: + """Calculate mathematical statistics of numerical values. + + :ivar sum: sum of all values + :ivar min: minimum of all values + :ivar max: maximum of all values + :ivar mean: mean of all values + :ivar median: median of all values + :ivar last_value: last added value + :ivar last_change: timestamp the last time a value was added + """ def __init__(self, max_age=None, max_samples=None): - """Calculate mathematical statistics of numerical values. - + """ :param max_age: Maximum age of values in seconds :param max_samples: Maximum amount of samples which will be kept - - :ivar sum: sum of all values - :ivar min: minimum of all values - :ivar max: maximum of all values - :ivar mean: mean of all values - :ivar median: median of all values - :ivar last_value: last added value - :ivar last_change: timestamp the last time a value was added """ if max_age is None and max_samples is None: diff --git a/_doc/conf.py b/_doc/conf.py index a0962931..272ec550 100644 --- a/_doc/conf.py +++ b/_doc/conf.py @@ -220,6 +220,7 @@ 'ColorItem', ) + def skip_member(app, what, name, obj, skip, options): # Debug print @@ -228,7 +229,7 @@ def skip_member(app, what, name, obj, skip, options): # don't change if we skip anyway if skip: return skip - + for regex in RE_SKIP: m = regex.search(str(obj)) if m: diff --git a/_doc/interface_mqtt.rst b/_doc/interface_mqtt.rst index 191b85b7..8914d3a6 100644 --- a/_doc/interface_mqtt.rst +++ b/_doc/interface_mqtt.rst @@ -3,15 +3,17 @@ MQTT ================================== -Interaction with a MQTT broker ------------------------------- -Interaction with the MQTT broker is done through the ``self.mqtt`` object in the rule. +Interaction with the MQTT broker +--------------------------------- +Interaction with the MQTT broker is done through the ``self.mqtt`` object in the rule or through +the :class:`~HABApp.mqtt.items.MqttItem`. When receiving a topic for the first time a new :class:`~HABApp.mqtt.items.MqttItem` +will automatically be created. .. image:: /gifs/mqtt.gif -Function parameters +Rule Interface ------------------------------ .. py:class:: mqtt @@ -42,27 +44,38 @@ Function parameters :return: 0 if successful -MQTT item types +MqttItem ------------------------------ -Mqtt items have a publish method which make interaction with the mqtt broker easier. +Mqtt items have an additional publish method which make interaction with the mqtt broker easier. + +.. execute_code:: + :hide_output: + + # hide + import HABApp + from unittest.mock import MagicMock + HABApp.mqtt.mqtt_interface.MQTT_INTERFACE = MagicMock() + # hide -Example:: + from HABApp.mqtt.items import MqttItem - # items can be created manually or will be automatically created when the first mqtt message is received - my_mqtt_item = self.create_item('test/topic', HABApp.mqtt.items.MqttItem) - assert isinstance(my_mqtt_item, HABApp.mqtt.items.MqttItem) + # items can be created manually or will be automatically + # created when the first mqtt message is received + my_mqtt_item = MqttItem.get_create_item('test/topic') - # easy publish + # easy to publish values my_mqtt_item.publish('new_value') # comparing the item to get the state works, too if my_mqtt_item == 'test': - # do something + pass # do something + .. autoclass:: HABApp.mqtt.items.MqttItem :members: - + :inherited-members: + :member-order: groupwise Example MQTT rule diff --git a/_doc/interface_openhab.rst b/_doc/interface_openhab.rst index c7f4c380..ab9f94c0 100644 --- a/_doc/interface_openhab.rst +++ b/_doc/interface_openhab.rst @@ -21,34 +21,69 @@ Function parameters Openhab item types ------------------------------ -Openhab items are mapped to special classes and provide convenience functions +Openhab items are mapped to special classes and provide convenience functions. -Examples:: +Example: - my_contact = self.get_item('MyContact') +.. execute_code:: + + # hide + import HABApp + from HABApp.openhab.items import ContactItem, SwitchItem + ContactItem.get_create_item('MyContact', initial_value='OPEN') + SwitchItem.get_create_item('MySwitch', initial_value='OFF') + # hide + + my_contact = ContactItem.get_item('MyContact') if my_contact.is_open(): - pass + print('Contact is open!') - my_switch = self.get_item('MySwitch') + my_switch = SwitchItem.get_item('MySwitch') if my_switch.is_on(): my_switch.off() - +.. list-table:: + :widths: auto + :header-rows: 1 + + * - Openhab type + - HABApp class + * - ``Contact`` + - :class:`~HABApp.openhab.items.ContactItem` + * - ``Switch`` + - :class:`~HABApp.openhab.items.SwitchItem` + * - ``Dimmer`` + - :class:`~HABApp.openhab.items.DimmerItem` + * - ``Rollershutter`` + - :class:`~HABApp.openhab.items.RollershutterItem` + * - ``Color`` + - :class:`~HABApp.openhab.items.ColorItem` .. autoclass:: HABApp.openhab.items.ContactItem :members: + :inherited-members: + :member-order: groupwise + .. autoclass:: HABApp.openhab.items.SwitchItem - :members: is_on, is_off, on, off + :members: + :inherited-members: + :member-order: groupwise .. autoclass:: HABApp.openhab.items.DimmerItem - :members: is_on, is_off, on, off, percent + :members: + :inherited-members: + :member-order: groupwise .. autoclass:: HABApp.openhab.items.RollershutterItem - :members: up, down, percent + :members: + :inherited-members: + :member-order: groupwise .. autoclass:: HABApp.openhab.items.ColorItem - :members: is_on, is_off, on, off, percent, post_value, get_rgb, set_rgb, set_value + :members: + :inherited-members: + :member-order: groupwise diff --git a/_doc/rule.rst b/_doc/rule.rst index e77e9838..72f73c8a 100644 --- a/_doc/rule.rst +++ b/_doc/rule.rst @@ -21,10 +21,18 @@ Items from MQTT use the topic as item name and get created as soon as a message Some item types provide convenience functions, so it is advised to always set the correct item type. -The preferred way to interact with items is through the class factory `get_create_item` since this provides type hints:: +The preferred way to get and create items is through the class factories :class:`~HABApp.core.items.Item.get_item` +and :class:`~HABApp.core.items.Item.get_create_item` since this ensures the proper item class and provides type hints when +using an IDE: + +.. execute_code:: + :hide_output: from HABApp.core.items import Item - my_item = Item.get_create_item('MyItem') + my_item = Item.get_create_item('MyItem', initial_value=5) + my_item = Item.get_item('MyItem') + print(my_item) + If an item value gets set there will be a :class:`~HABApp.core.ValueUpdateEvent` on the event bus. If it changes there will be additionally a :class:`~HABApp.core.ValueChangeEvent`, too. @@ -59,17 +67,27 @@ If it changes there will be additionally a :class:`~HABApp.core.ValueChangeEvent * - :meth:`~HABApp.Rule.item_watch_and_listen` - Convenience function which combines :class:`~HABApp.Rule.item_watch` and :class:`~HABApp.Rule.listen_event` -It is possible to check the item value by comparing it:: +It is possible to check the item value by comparing it - my_item = self.get_item('MyItem') +.. execute_code:: + :hide_output: + + # hide + from HABApp.core.items import Item + Item.get_create_item('MyItem', initial_value=5) + # hide + + from HABApp.core.items import Item + my_item = Item.get_item('MyItem') # this works if my_item == 5: - # do sth + pass # do something + + # and is the same as this + if my_item.value == 5: + pass # do something - # and is the same as - if my_item.state == 5: - # do sth .. autoclass:: HABApp.core.items.Item :members: diff --git a/conf/rules/mqtt_rule.py b/conf/rules/mqtt_rule.py index d372f67c..0ac95022 100644 --- a/conf/rules/mqtt_rule.py +++ b/conf/rules/mqtt_rule.py @@ -15,9 +15,6 @@ def __init__(self): callback=self.publish_rand_value ) - # these two methods have the same effect. - # If item_factory is omitted self.get_item will raise an error if the item does not exist instead of creating it - self.my_mqtt_item: MqttItem = self.get_item('test/test', item_factory=MqttItem) self.my_mqtt_item = MqttItem.get_create_item('test/test') self.listen_event('test/test', self.topic_updated, ValueUpdateEvent)