diff --git a/run/conf_testing/rules/openhab/test_item_funcs.py b/run/conf_testing/rules/openhab/test_item_funcs.py index 16d906f3..4b3fbba8 100644 --- a/run/conf_testing/rules/openhab/test_item_funcs.py +++ b/run/conf_testing/rules/openhab/test_item_funcs.py @@ -117,7 +117,8 @@ class TestOpenhabItemConvenience(TestBaseRule): def __init__(self) -> None: super().__init__() - for name in ('oh_post_update', 'oh_send_command'): + # oh_send_command and command_value is the same for openhab items + for name in ('oh_post_update', 'oh_send_command', 'command_value'): for k in get_openhab_test_types(): if name == 'oh_send_command' and k == 'Contact': continue diff --git a/src/HABApp/core/items/base_valueitem.py b/src/HABApp/core/items/base_valueitem.py index 4e0064b7..492ac996 100644 --- a/src/HABApp/core/items/base_valueitem.py +++ b/src/HABApp/core/items/base_valueitem.py @@ -1,18 +1,18 @@ import logging -import typing from datetime import datetime from math import ceil, floor +from typing import TYPE_CHECKING, Any from whenever import Instant from HABApp.core.const import MISSING -from HABApp.core.events import ValueChangeEvent, ValueUpdateEvent +from HABApp.core.events import ValueChangeEvent, ValueCommandEvent, ValueUpdateEvent from HABApp.core.internals import uses_post_event from HABApp.core.items.base_item import BaseItem from HABApp.core.lib.funcs import compare as _compare -if typing.TYPE_CHECKING: +if TYPE_CHECKING: datetime = datetime @@ -33,9 +33,9 @@ class BaseValueItem(BaseItem): def __init__(self, name: str, initial_value=None) -> None: super().__init__(name) - self.value: typing.Any = initial_value + self.value: Any = initial_value - def set_value(self, new_value) -> bool: + def set_value(self, new_value: Any) -> bool: """Set a new value without creating events on the event bus :param new_value: new value of the item @@ -51,7 +51,7 @@ def set_value(self, new_value) -> bool: self.value = new_value return state_changed - def post_value(self, new_value) -> bool: + def post_value(self, new_value: Any) -> bool: """Set a new value and post appropriate events on the HABApp event bus (``ValueUpdateEvent``, ``ValueChangeEvent``) @@ -69,7 +69,16 @@ def post_value(self, new_value) -> bool: ) return state_changed - def post_value_if(self, new_value, *, equal=MISSING, eq=MISSING, not_equal=MISSING, ne=MISSING, + def command_value(self, value: Any) -> None: + """Send a ``ValueCommandEvent`` for the item to the HABApp event bus. A ``ValueCommandEvent`` is typically + used to indicate that the item should change the value. + E.g. a command "ON" to a dimmer might result in a brightness value of 100%. + + :param value: the commanded value + """ + post_event(self._name, ValueCommandEvent(self._name, value)) + + def post_value_if(self, new_value: Any, *, equal=MISSING, eq=MISSING, not_equal=MISSING, ne=MISSING, lower_than=MISSING, lt=MISSING, lower_equal=MISSING, le=MISSING, greater_than=MISSING, gt=MISSING, greater_equal=MISSING, ge=MISSING, is_=MISSING, is_not=MISSING) -> bool: @@ -103,7 +112,7 @@ def post_value_if(self, new_value, *, equal=MISSING, eq=MISSING, not_equal=MISSI return True return False - def get_value(self, default_value=None) -> typing.Any: + def get_value(self, default_value=None) -> Any: """Return the value of the item. This is a helper function that returns a default in case the item value is None. @@ -122,23 +131,23 @@ def __repr__(self) -> str: # only support == and != operators by default # __ne__ delegates to __eq__ and inverts the result so this is not overloaded separately - def __eq__(self, other): + def __eq__(self, other: Any) -> bool: return self.value == other def __bool__(self) -> bool: return bool(self.value) # rich comparisons - def __lt__(self, other): + def __lt__(self, other: Any) -> bool: return self.value < other - def __le__(self, other): + def __le__(self, other: Any) -> bool: return self.value <= other - def __ge__(self, other): + def __ge__(self, other: Any) -> bool: return self.value >= other - def __gt__(self, other): + def __gt__(self, other: Any) -> bool: return self.value > other # https://docs.python.org/3/reference/datamodel.html#emulating-numeric-types diff --git a/src/HABApp/openhab/items/base_item.py b/src/HABApp/openhab/items/base_item.py index 56dd33b9..58d43613 100644 --- a/src/HABApp/openhab/items/base_item.py +++ b/src/HABApp/openhab/items/base_item.py @@ -3,6 +3,7 @@ from typing import Any, NamedTuple from immutables import Map +from typing_extensions import override from HABApp.core.const import MISSING from HABApp.core.items import BaseValueItem @@ -55,6 +56,16 @@ def oh_send_command(self, value: Any = MISSING) -> None: """ send_command(self.name, self.value if value is MISSING else value) + # For the openhab items HABApp internal commands make not much sense + # so we send the commands to openHAB + @override + def command_value(self, value: Any) -> None: + """Send a command to the openHAB item, the same as oh_send_command + + :param value: (optional) value to be sent. If not specified the current item value will be used. + """ + send_command(self.name, value) + def oh_post_update(self, value: Any = MISSING) -> None: """Post an update to the openHAB item diff --git a/tests/test_core/test_items/item_tests.py b/tests/test_core/test_items/item_tests.py index 4c72abe5..95320aa1 100644 --- a/tests/test_core/test_items/item_tests.py +++ b/tests/test_core/test_items/item_tests.py @@ -1,10 +1,12 @@ import time +from unittest.mock import MagicMock from whenever import Instant, patch_current_time +from HABApp.core.events import NoEventFilter, ValueCommandEvent from HABApp.core.internals import ItemRegistry from HABApp.core.items import Item -from HABApp.core.items.base_valueitem import datetime +from tests.helpers import TestEventBus class ItemTests: @@ -99,3 +101,19 @@ def test_post_if(self) -> None: assert i.post_value_if(0, is_=None) assert i.post_value_if(1, eq=0) assert not i.post_value_if(1, eq=0) + + def test_post_command(self, sync_worker, eb: TestEventBus) -> None: + i = self.get_item() + + mock = MagicMock() + eb.listen_events(i.name, mock, NoEventFilter()) + mock.assert_not_called() + + value = self.ITEM_VALUES[0] + i.command_value(value) + mock.assert_called() + + update = mock.call_args_list[0][0][0] + assert isinstance(update, ValueCommandEvent) + assert update.name == i.name + assert update.value == value diff --git a/tests/test_core/test_items/test_item.py b/tests/test_core/test_items/test_item.py index 0cc9fa27..84b3b4a8 100644 --- a/tests/test_core/test_items/test_item.py +++ b/tests/test_core/test_items/test_item.py @@ -1,6 +1,5 @@ from HABApp.core.items import Item - -from . import ItemTests +from tests.test_core.test_items import ItemTests class TestItem(ItemTests):