diff --git a/src/mash/bridges/hue/hue_bridge.py b/src/mash/bridges/hue/hue_bridge.py index 3755b55..fcad9cc 100644 --- a/src/mash/bridges/hue/hue_bridge.py +++ b/src/mash/bridges/hue/hue_bridge.py @@ -27,7 +27,8 @@ class HueBridge(Bridge): for _ in range(self._retry_limit): try: self._hue.connect() - break + logging.info(f"Connected to Hue Bridge [{self.id}].") + return except Exception as e: logging.exception( f"Failed to connect to Hue bridge [ip {self._hue.ip}]. Retrying in [{self._retry_timeout_seconds}] seconds.", @@ -35,7 +36,9 @@ class HueBridge(Bridge): ) sleep(self._retry_timeout_seconds) - logging.info(f"Connected to Hue Bridge [{self.id}].") + logging.error( + f"Unable to connect to Hue bridge [{self.id}]. Retry count exceeded [{self._retry_limit}]." + ) @property def is_connected(self) -> bool | None: @@ -54,6 +57,18 @@ class HueBridge(Bridge): return scene return None + def set_light(self, lights, command): + return self._hue.set_light(lights, command) + + def get_light(self, id, command=None): + return self._hue.get_light(id, command) + + def set_group(self, groups, command): + return self._hue.set_group(groups, command) + + def get_group(self, id, command=None): + return self._hue.get_group(id, command) + def in_room_activate_scene(self, room_name: str, scene_name: str): """Activate a scene in a room. diff --git a/src/mash/bridges/hue/hue_light.py b/src/mash/bridges/hue/hue_light.py new file mode 100644 index 0000000..132dcda --- /dev/null +++ b/src/mash/bridges/hue/hue_light.py @@ -0,0 +1,13 @@ +from mash.core.entities.light import Light +from mash.core.utilities.glow import Glow + + +class HueLight(Light): + def __init__( + self, *, id: str, name: str, room: str, groups: list[str] = ... + ) -> None: + super().__init__(id=id, name=name, room=room, groups=groups) + + def __on_change__(self, current_on: bool, current_glow: Glow): + pass + # TODO: Requires reference to bridge diff --git a/src/mash/core/__init__.py b/src/mash/core/__init__.py index ed333cb..3c07599 100644 --- a/src/mash/core/__init__.py +++ b/src/mash/core/__init__.py @@ -1,5 +1,3 @@ +from .entities import * from .bridge import Bridge, BridgeException -from .entity import Entity -from .group import Group -from .home import Home from .feature import Feature diff --git a/src/mash/core/entities/__init__.py b/src/mash/core/entities/__init__.py new file mode 100644 index 0000000..2aef817 --- /dev/null +++ b/src/mash/core/entities/__init__.py @@ -0,0 +1,6 @@ +from .entity import Entity +from .group import Group +from .home import Home +from .contact_sensor import ContactSensor +from .light import Light +from .device_type import DeviceTypes diff --git a/src/mash/core/entities/contact_sensor.py b/src/mash/core/entities/contact_sensor.py new file mode 100644 index 0000000..b8710aa --- /dev/null +++ b/src/mash/core/entities/contact_sensor.py @@ -0,0 +1,21 @@ +from mash.core.entities.device_type import DeviceType +from mash.core.entities.entity import Entity + + +class ContactSensor(Entity): + def __init__( + self, *, id: str, name: str, room: str, groups: list[str] = ... + ) -> None: + super().__init__( + id=id, + name=name, + room=room, + device_type=DeviceType.CONTACT_SENSOR, + groups=groups, + ) + self._has_contact: bool = False + + def is_closed(self) -> bool: + return self._has_contact + + # TODO: Update state diff --git a/src/mash/core/entities/device_type.py b/src/mash/core/entities/device_type.py new file mode 100644 index 0000000..ea992d2 --- /dev/null +++ b/src/mash/core/entities/device_type.py @@ -0,0 +1,6 @@ +from enum import Enum + + +class DeviceType(Enum): + LIGHT = "light" + CONTACT_SENSOR = "contact_sensor" diff --git a/src/mash/core/entity.py b/src/mash/core/entities/entity.py similarity index 73% rename from src/mash/core/entity.py rename to src/mash/core/entities/entity.py index 317980b..a60252a 100644 --- a/src/mash/core/entity.py +++ b/src/mash/core/entities/entity.py @@ -1,6 +1,15 @@ +from mash.core.entities.device_type import DeviceType + + class Entity: def __init__( - self, *, id: str, name: str, room: str, device_type: str, groups: list[str] = [] + self, + *, + id: str, + name: str, + room: str, + device_type: DeviceType, + groups: list[str] = [], ) -> None: self._id = id self._name = name @@ -21,7 +30,7 @@ class Entity: return self._room @property - def device_type(self) -> str: + def device_type(self) -> DeviceType: return self._device_type @property diff --git a/src/mash/core/group.py b/src/mash/core/entities/group.py similarity index 97% rename from src/mash/core/group.py rename to src/mash/core/entities/group.py index e270749..dc71146 100644 --- a/src/mash/core/group.py +++ b/src/mash/core/entities/group.py @@ -1,4 +1,4 @@ -from mash.core.entity import Entity +from mash.core.entities.entity import Entity from fnmatch import fnmatch diff --git a/src/mash/core/home.py b/src/mash/core/entities/home.py similarity index 59% rename from src/mash/core/home.py rename to src/mash/core/entities/home.py index 6dda77d..de86912 100644 --- a/src/mash/core/home.py +++ b/src/mash/core/entities/home.py @@ -1,5 +1,5 @@ -from mash.core.entity import Entity -from mash.core.group import Group +from mash.core.entities.entity import Entity +from mash.core.entities.group import Group class Home(Group): diff --git a/src/mash/core/entities/light.py b/src/mash/core/entities/light.py new file mode 100644 index 0000000..cf9190a --- /dev/null +++ b/src/mash/core/entities/light.py @@ -0,0 +1,66 @@ +from mash.core.entities.device_type import DeviceType +from mash.core.entities.entity import Entity +from mash.core.utilities.glow import Glow +from mash.core.utilities.validation import clip_int + + +class Light(Entity): + def __init__( + self, *, id: str, name: str, room: str, groups: list[str] = ... + ) -> None: + super().__init__( + id=id, name=name, room=room, device_type=DeviceType.LIGHT, groups=groups + ) + self._glow: Glow = Glow() + self._on: bool = False + + def __on_change__(self, current_on: bool, current_glow: Glow): + pass + + def __check_for_change__(self, obj, prop, new_value): + old_value = getattr(obj, prop) + if old_value == new_value: + return + + setattr(obj, prop, new_value) + self.__on_change__(current_on=self.on, current_glow=self._glow) + + @property + def on(self) -> bool: + """True, if light is emitting glow. False, if light is off.""" + return self._on + + @on.setter + def on(self, value: bool): + """True, if light is emitting glow. False, if light is off.""" + self.__check_for_change__(self, "_on", value) + + @property + def brightness(self) -> int: + """Brightness in the range [0, 254].""" + return self._glow.brightness + + @brightness.setter + def brightness(self, value: int): + """Brightness in the range [0, 254]. Value will be clipped.""" + self.__check_for_change__(self._glow, "brightness", clip_int(value, 0, 254)) + + @property + def saturation(self) -> int: + """Saturation in the range [0, 254].""" + return self._glow.saturation + + @saturation.setter + def saturation(self, value: int): + """Saturation in the range [0, 254]. Value will be clipped.""" + self.__check_for_change__(self._glow, "saturation", clip_int(value, 0, 254)) + + @property + def hue(self) -> int: + """Hue in the range [0, 65535].""" + return self._glow.hue + + @hue.setter + def hue(self, value: int): + """Hue in the range [0, 65535]. Value will be clipped.""" + self.__check_for_change__(self._glow, "hue", clip_int(value, 0, 65535)) diff --git a/src/mash/core/utilities/__init__.py b/src/mash/core/utilities/__init__.py new file mode 100644 index 0000000..bb8ee04 --- /dev/null +++ b/src/mash/core/utilities/__init__.py @@ -0,0 +1,2 @@ +from .glow import Glow +from .validation import clip_int diff --git a/src/mash/core/utilities/glow.py b/src/mash/core/utilities/glow.py new file mode 100644 index 0000000..b0ff3b7 --- /dev/null +++ b/src/mash/core/utilities/glow.py @@ -0,0 +1,39 @@ +class Glow: + """Concept of colored light-rays.""" + + def __init__( + self, *, brightness: int = 0, saturation: int = 0, hue: int = 0 + ) -> None: + self._brightness: int = brightness + self._saturation: int = saturation + self._hue: int = hue + + @property + def brightness(self) -> int: + """Brightness in the range [0, 254].""" + return self._brightness + + @brightness.setter + def brightness(self, value: int): + """Brightness in the range [0, 254].""" + self._brightness = value + + @property + def saturation(self) -> int: + """Saturation in the range [0, 254].""" + return self._saturation + + @saturation.setter + def saturation(self, value: int): + """Saturation in the range [0, 254].""" + self._saturation = value + + @property + def hue(self) -> int: + """Hue in the range [0, 65535].""" + return self._hue + + @hue.setter + def hue(self, value: int): + """Hue in the range [0, 65535].""" + self._hue = value diff --git a/src/mash/core/utilities/validation.py b/src/mash/core/utilities/validation.py new file mode 100644 index 0000000..7b85365 --- /dev/null +++ b/src/mash/core/utilities/validation.py @@ -0,0 +1,2 @@ +def clip_int(value, min_val: int, max_val: int) -> int: + return min([max([int(value), min_val]), max_val])