Skip to content

Commit

Permalink
Added toggle for SwitchItem
Browse files Browse the repository at this point in the history
  • Loading branch information
spacemanspiff2007 committed Dec 20, 2023
1 parent 0d299ea commit 223d7b4
Show file tree
Hide file tree
Showing 18 changed files with 229 additions and 56 deletions.
2 changes: 2 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@ MyOpenhabRule()
- Added CompressedMidnightRotatingFileHandler
- Updated dependencies
- Small improvement for RGB and HSB types
- Small improvements for openHAB items
- Added toggle for SwitchItem

#### 23.11.0 (2023-11-23)
- Fix for very small float values (#425)
Expand Down
4 changes: 2 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
# -----------------------------------------------------------------------------
# Packages for source formatting
# -----------------------------------------------------------------------------
pre-commit >= 3.6, < 4
ruff >= 0.1.8, < 0.2
pre-commit == 3.5.0
ruff == 0.1.8

# -----------------------------------------------------------------------------
# Packages for other developement tasks
Expand Down
8 changes: 8 additions & 0 deletions src/HABApp/core/const/hints.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from typing import Any as __Any
from typing import Awaitable as __Awaitable
from typing import Callable as __Callable
from typing import Protocol as __Protocol
from typing import Type as __Type

from .const import PYTHON_310 as __IS_GE_PYTHON_310
Expand All @@ -16,3 +17,10 @@
TYPE_FUNC_ASYNC: TypeAlias = __Callable[..., __Awaitable[__Any]]

TYPE_EVENT_CALLBACK: TypeAlias = __Callable[[__Any], __Any]


# noinspection PyPropertyDefinition
class HasNameAttr(__Protocol):
@property
def name(self) -> str:
...
22 changes: 22 additions & 0 deletions src/HABApp/core/errors.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from HABApp.core.const.hints import HasNameAttr as _HasNameAttr


class HABAppException(Exception):
pass

Expand Down Expand Up @@ -33,3 +36,22 @@ class ContextBoundObjectIsAlreadyLinkedError(HABAppException):

class ContextBoundObjectIsAlreadyUnlinkedError(HABAppException):
pass


# ----------------------------------------------------------------------------------------------------------------------
# Value errors
# ----------------------------------------------------------------------------------------------------------------------
class HABAppValueError(ValueError, HABAppException):
pass


class ItemValueIsNoneError(HABAppValueError):
@classmethod
def from_item(cls, item: _HasNameAttr):
return cls(f'Item value is None (item "{item.name:s}")')


class InvalidItemValue(HABAppValueError):
@classmethod
def from_item(cls, item: _HasNameAttr, value):
return cls(f'Invalid value for {item.__class__.__name__} {item.name:s}: {value}')
5 changes: 2 additions & 3 deletions src/HABApp/openhab/items/base_item.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import datetime
from typing import Any, FrozenSet, Mapping, NamedTuple, Optional, TypeVar, Type
from typing import Any, FrozenSet, Mapping, NamedTuple, Optional, Type

from immutables import Map

Expand Down Expand Up @@ -110,5 +110,4 @@ def get_persistence_data(self, persistence: Optional[str] = None,
)


HINT_OPENHAB_ITEM = TypeVar('HINT_OPENHAB_ITEM', bound=OpenhabItem)
HINT_TYPE_OPENHAB_ITEM = Type[HINT_OPENHAB_ITEM]
HINT_TYPE_OPENHAB_ITEM = Type[OpenhabItem]
29 changes: 15 additions & 14 deletions src/HABApp/openhab/items/commands.py
Original file line number Diff line number Diff line change
@@ -1,46 +1,47 @@
from HABApp.core.const.hints import HasNameAttr as _HasNameAttr
from HABApp.openhab.definitions import OnOffValue, UpDownValue
from HABApp.openhab.interface_sync import send_command


class OnOffCommand:

def is_on(self) -> bool:
def is_on(self: _HasNameAttr) -> bool:
"""Test value against on-value"""
raise NotImplementedError()

def is_off(self) -> bool:
def is_off(self: _HasNameAttr) -> bool:
"""Test value against off-value"""
raise NotImplementedError()

def on(self):
def on(self: _HasNameAttr):
"""Command item on"""
send_command(self, OnOffValue.ON)
send_command(self.name, OnOffValue.ON)

def off(self):
def off(self: _HasNameAttr):
"""Command item off"""
send_command(self, OnOffValue.OFF)
send_command(self.name, OnOffValue.OFF)


class PercentCommand:
def percent(self, value: float):
def percent(self: _HasNameAttr, value: float):
"""Command to value (in percent)"""
assert 0 <= value <= 100, value
send_command(self, str(value))
send_command(self.name, str(value))


class UpDownCommand:
def up(self):
def up(self: _HasNameAttr):
"""Command up"""
send_command(self, UpDownValue.UP)
send_command(self.name, UpDownValue.UP)

def down(self):
def down(self: _HasNameAttr):
"""Command down"""
send_command(self, UpDownValue.DOWN)
send_command(self.name, UpDownValue.DOWN)

def is_up(self) -> bool:
def is_up(self: _HasNameAttr) -> bool:
"""Test value against on-value"""
raise NotImplementedError()

def is_down(self) -> bool:
def is_down(self: _HasNameAttr) -> bool:
"""Test value against off-value"""
raise NotImplementedError()
6 changes: 4 additions & 2 deletions src/HABApp/openhab/items/contact_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from ...core.const import MISSING
from ..errors import SendCommandNotSupported
from HABApp.openhab.interface_sync import post_update
from ...core.errors import InvalidItemValue

if TYPE_CHECKING:
Optional = Optional
Expand Down Expand Up @@ -40,8 +41,9 @@ def set_value(self, new_value) -> bool:
if isinstance(new_value, OpenClosedValue):
new_value = new_value.value

if new_value is not None and new_value != OPEN and new_value != CLOSED:
raise ValueError(f'Invalid value for ContactItem: {new_value}')
if new_value not in (OPEN, CLOSED, None):
raise InvalidItemValue.from_item(self, new_value)

return super().set_value(new_value)

def is_open(self) -> bool:
Expand Down
29 changes: 19 additions & 10 deletions src/HABApp/openhab/items/dimmer_item.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
from typing import Union, TYPE_CHECKING, Optional, FrozenSet, Mapping
from typing import TYPE_CHECKING, FrozenSet, Mapping, Optional, Union

from HABApp.openhab.items.base_item import OpenhabItem, MetaData
from HABApp.openhab.items.base_item import MetaData, OpenhabItem
from HABApp.openhab.items.commands import OnOffCommand, PercentCommand

from ...core.errors import InvalidItemValue, ItemValueIsNoneError
from ..definitions import OnOffValue, PercentValue


if TYPE_CHECKING:
Union = Union
Optional = Optional
Expand Down Expand Up @@ -39,20 +42,26 @@ def set_value(self, new_value) -> bool:
new_value = new_value.value

# Percent is 0 ... 100
if isinstance(new_value, (int, float)):
assert 0 <= new_value <= 100, new_value
else:
assert new_value is None, new_value
if isinstance(new_value, (int, float)) and (0 <= new_value <= 100):
return super().set_value(new_value)

return super().set_value(new_value)
if new_value is None:
return super().set_value(new_value)

def __str__(self):
return self.value
raise InvalidItemValue.from_item(self, new_value)

def is_on(self) -> bool:
"""Test value against on-value"""
return bool(self.value)

def is_off(self) -> bool:
"""Test value against off-value"""
return not bool(self.value)
return self.value is not None and not self.value

def __str__(self):
return self.value

def __bool__(self):
if self.value is None:
raise ItemValueIsNoneError.from_item(self)
return self.is_on()
14 changes: 13 additions & 1 deletion src/HABApp/openhab/items/number_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from HABApp.openhab.items.base_item import OpenhabItem, MetaData
from ..definitions import QuantityValue
from ...core.errors import ItemValueIsNoneError, InvalidItemValue

if TYPE_CHECKING:
Union = Union
Expand Down Expand Up @@ -41,4 +42,15 @@ 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)
if isinstance(new_value, (int, float)):
return super().set_value(new_value)

if new_value is None:
return super().set_value(new_value)

raise InvalidItemValue.from_item(self, new_value)

def __bool__(self):
if self.value is None:
raise ItemValueIsNoneError.from_item(self)
return bool(self.value)
20 changes: 14 additions & 6 deletions src/HABApp/openhab/items/rollershutter_item.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from typing import TYPE_CHECKING, Optional, FrozenSet, Mapping, Union
from typing import TYPE_CHECKING, FrozenSet, Mapping, Optional, Union

from HABApp.core.errors import InvalidItemValue
from HABApp.openhab.definitions import PercentValue, UpDownValue
from HABApp.openhab.items.base_item import MetaData, OpenhabItem
from HABApp.openhab.items.commands import PercentCommand, UpDownCommand

from HABApp.openhab.items.base_item import OpenhabItem, MetaData
from HABApp.openhab.items.commands import UpDownCommand, PercentCommand
from ..definitions import UpDownValue, PercentValue

if TYPE_CHECKING:
Union = Union
Expand Down Expand Up @@ -38,8 +40,14 @@ def set_value(self, new_value) -> bool:
elif isinstance(new_value, PercentValue):
new_value = new_value.value

assert isinstance(new_value, (int, float)) or new_value is None, new_value
return super().set_value(new_value)
# Position is 0 ... 100
if isinstance(new_value, (int, float)) and (0 <= new_value <= 100):
return super().set_value(new_value)

if new_value is None:
return super().set_value(new_value)

raise InvalidItemValue.from_item(self, new_value)

def is_up(self) -> bool:
return self.value <= 0
Expand Down
35 changes: 25 additions & 10 deletions src/HABApp/openhab/items/switch_item.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from typing import TYPE_CHECKING, Tuple, Optional, FrozenSet, Mapping
from typing import TYPE_CHECKING, Final, FrozenSet, Mapping, Optional, Tuple

from HABApp.core.errors import ItemValueIsNoneError, InvalidItemValue
from HABApp.openhab.definitions import OnOffValue
from HABApp.openhab.items.base_item import OpenhabItem, MetaData
from HABApp.openhab.items.base_item import MetaData, OpenhabItem
from HABApp.openhab.items.commands import OnOffCommand


if TYPE_CHECKING:
Tuple = Tuple
Optional = Optional
Expand All @@ -12,8 +14,8 @@
MetaData = MetaData


ON = OnOffValue.ON
OFF = OnOffValue.OFF
ON: Final = OnOffValue.ON
OFF: Final = OnOffValue.OFF


class SwitchItem(OpenhabItem, OnOffCommand):
Expand All @@ -28,7 +30,6 @@ class SwitchItem(OpenhabItem, OnOffCommand):
:ivar Mapping[str, MetaData] metadata: |oh_item_desc_metadata|
"""


@staticmethod
def _state_from_oh_str(state: str):
if state != ON and state != OFF:
Expand All @@ -40,8 +41,9 @@ def set_value(self, new_value) -> bool:
if isinstance(new_value, OnOffValue):
new_value = new_value.value

if new_value is not None and new_value != ON and new_value != OFF:
raise ValueError(f'Invalid value for SwitchItem {self.name}: {new_value}')
if new_value not in (ON, OFF, None):
raise InvalidItemValue.from_item(self, new_value)

return super().set_value(new_value)

def is_on(self) -> bool:
Expand All @@ -52,15 +54,26 @@ def is_off(self) -> bool:
"""Test value against off-value"""
return self.value == OFF

def toggle(self):
"""Toggle the switch. Turns the switch on when off or off when currently on."""
if self.value == ON:
self.off()
elif self.value == OFF:
self.on()
elif self.value is None:
raise ItemValueIsNoneError.from_item(self)
else:
raise InvalidItemValue.from_item(self, self.value)

def __str__(self):
return self.value

def __eq__(self, other):
if isinstance(other, SwitchItem):
return self.value == other.value
elif isinstance(other, str):
if isinstance(other, str):
return self.value == other
elif isinstance(other, int):
if isinstance(other, int):
if other and self.is_on():
return True
if not other and self.is_off():
Expand All @@ -70,4 +83,6 @@ def __eq__(self, other):
return NotImplemented

def __bool__(self):
return self.is_on()
if self.value is None:
raise ItemValueIsNoneError.from_item(self)
return self.value == ON
13 changes: 7 additions & 6 deletions src/HABApp/openhab/map_items.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@

from immutables import Map

import HABApp
from HABApp.core.wrapper import process_exception
from HABApp.openhab.definitions.values import QuantityValue
from HABApp.openhab.items import ColorItem, ContactItem, DatetimeItem, DimmerItem, GroupItem, ImageItem, LocationItem, \
NumberItem, PlayerItem, RollershutterItem, StringItem, SwitchItem, CallItem
from HABApp.openhab.items.base_item import HINT_TYPE_OPENHAB_ITEM
from HABApp.openhab.items.base_item import MetaData
from HABApp.openhab.items import (
CallItem, ColorItem, ContactItem, DatetimeItem, DimmerItem, GroupItem, ImageItem,
LocationItem, NumberItem, PlayerItem, RollershutterItem, StringItem, SwitchItem,
)
from HABApp.openhab.items.base_item import HINT_TYPE_OPENHAB_ITEM, MetaData, OpenhabItem


log = logging.getLogger('HABApp.openhab.items')

Expand All @@ -34,7 +35,7 @@
def map_item(name: str, type: str, value: Optional[str],
label: Optional[str], tags: FrozenSet[str],
groups: FrozenSet[str], metadata: Optional[Dict[str, Dict[str, Any]]]) -> \
Optional['HABApp.openhab.items.OpenhabItem']:
Optional[OpenhabItem]:
try:
assert isinstance(type, str)
assert value is None or isinstance(value, str)
Expand Down
Loading

0 comments on commit 223d7b4

Please sign in to comment.