Skip to content

Commit

Permalink
Improve type hinting
Browse files Browse the repository at this point in the history
  • Loading branch information
dext0r committed Aug 18, 2023
1 parent 37e7780 commit af810eb
Show file tree
Hide file tree
Showing 20 changed files with 188 additions and 95 deletions.
13 changes: 8 additions & 5 deletions custom_components/yandex_smart_home/capability.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from abc import ABC, abstractmethod
import logging
from typing import Any, TypeVar
from typing import Any, Generic, TypeVar

from homeassistant.const import ATTR_SUPPORTED_FEATURES
from homeassistant.core import Context, HomeAssistant, State
Expand All @@ -22,6 +22,7 @@

_LOGGER = logging.getLogger(__name__)
_CapabilityT = TypeVar("_CapabilityT", bound="AbstractCapability")

CAPABILITIES: list[type[AbstractCapability]] = []


Expand All @@ -31,11 +32,11 @@ def register_capability(capability: type[_CapabilityT]) -> type[_CapabilityT]:
return capability


class AbstractCapability(ABC):
class AbstractCapability(Generic[CapabilityInstanceActionState], ABC):
"""Represents a device base capability."""

type: CapabilityType
instance: CapabilityInstance | None = None
instance: CapabilityInstance

def __init__(self, hass: HomeAssistant, config: Config, state: State):
"""Initialize a capability for a state."""
Expand Down Expand Up @@ -81,11 +82,13 @@ def get_value(self) -> Any:

def get_instance_state(self) -> CapabilityInstanceState | None:
"""Return a state for a device query request."""
if (value := self.get_value()) is not None:
if (value := self.get_value()) is not None and self.instance:
return CapabilityInstanceState(
type=self.type, state=CapabilityInstanceStateValue(instance=self.instance, value=value)
)

return None

@abstractmethod
async def set_instance_state(
self, context: Context, state: CapabilityInstanceActionState
Expand All @@ -96,7 +99,7 @@ async def set_instance_state(
@property
def _state_features(self) -> int:
"""Return features attribute for the state."""
return self.state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
return int(self.state.attributes.get(ATTR_SUPPORTED_FEATURES, 0))


class ActionOnlyCapability(AbstractCapability, ABC):
Expand Down
35 changes: 26 additions & 9 deletions custom_components/yandex_smart_home/capability_color.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Implement the Yandex Smart Home color_setting capability."""
from functools import cached_property
import logging
from typing import Any

from homeassistant.components import light
from homeassistant.const import ATTR_ENTITY_ID
Expand Down Expand Up @@ -30,13 +31,14 @@


@register_capability
class ColorSettingCapability(AbstractCapability):
class ColorSettingCapability(AbstractCapability[ColorSettingCapabilityInstanceActionState]):
"""Root capability to discover another light device capabilities.
https://yandex.ru/dev/dialogs/smart-home/doc/concepts/color_setting.html
"""

type = CapabilityType.COLOR_SETTING
instance = ColorSettingCapabilityInstance.BASE

def __init__(self, hass: HomeAssistant, config: Config, state: State):
"""Initialize a capability for a state."""
Expand All @@ -60,7 +62,7 @@ def parameters(self) -> ColorSettingCapabilityParameters | None:
"""Return parameters for a devices list request."""
return ColorSettingCapabilityParameters(
color_model=self._color.parameters.color_model if self._color.supported else None,
temperature_k=self._temperature.parameters.temperature_k if self._temperature.supported else None,
temperature_k=self._temperature.parameters.temperature_k if self._temperature.parameters else None,
color_scene=self._color_scene.parameters.color_scene if self._color_scene.supported else None,
)

Expand All @@ -79,7 +81,7 @@ def _capabilities(self) -> list[AbstractCapability]:


@register_capability
class RGBColorCapability(AbstractCapability):
class RGBColorCapability(AbstractCapability[RGBInstanceActionState]):
"""Capability to control color of a light device."""

type = CapabilityType.COLOR_SETTING
Expand Down Expand Up @@ -120,7 +122,7 @@ def get_value(self) -> int | None:
if self.state.attributes.get(light.ATTR_COLOR_MODE) == light.COLOR_MODE_COLOR_TEMP:
return None

rgb_color: tuple[int, int, int] = self.state.attributes.get(light.ATTR_RGB_COLOR)
rgb_color = self.state.attributes.get(light.ATTR_RGB_COLOR)
if rgb_color is None:
hs_color = self.state.attributes.get(light.ATTR_HS_COLOR)
if hs_color is not None:
Expand All @@ -136,6 +138,8 @@ def get_value(self) -> int | None:

return self._converter.get_yandex_color(RGBColor(*rgb_color))

return None

async def set_instance_state(self, context: Context, state: RGBInstanceActionState) -> None:
"""Change the capability state."""
await self._hass.services.async_call(
Expand Down Expand Up @@ -166,7 +170,7 @@ def _converter(self) -> ColorConverter:


@register_capability
class ColorTemperatureCapability(AbstractCapability):
class ColorTemperatureCapability(AbstractCapability[TemperatureKInstanceActionState]):
"""Capability to control color temperature of a light device."""

type = CapabilityType.COLOR_SETTING
Expand All @@ -191,8 +195,11 @@ def supported(self) -> bool:
return False

@property
def parameters(self) -> ColorSettingCapabilityParameters:
def parameters(self) -> ColorSettingCapabilityParameters | None:
"""Return parameters for a devices list request."""
if not self.supported:
return None

supported_color_modes = set(self.state.attributes.get(light.ATTR_SUPPORTED_COLOR_MODES, []))

if self._state_features & light.SUPPORT_COLOR_TEMP or light.color_temp_supported(supported_color_modes):
Expand All @@ -211,6 +218,8 @@ def parameters(self) -> ColorSettingCapabilityParameters:
temperature_k=CapabilityParameterTemperatureK(min=min_temp, max=max_temp)
)

return None # pragma: no cover

def get_description(self) -> None:
"""Return a description for a device list request. Capability with an empty description isn't discoverable."""
return None
Expand Down Expand Up @@ -247,10 +256,12 @@ def get_value(self) -> int | None:

return None

return None

async def set_instance_state(self, context: Context, state: TemperatureKInstanceActionState) -> None:
"""Change the capability state."""
supported_color_modes = set(self.state.attributes.get(light.ATTR_SUPPORTED_COLOR_MODES, []))
service_data = {}
service_data: dict[str, Any] = {}

if self._state_features & light.SUPPORT_COLOR_TEMP or light.color_temp_supported(supported_color_modes):
service_data[light.ATTR_KELVIN] = self._converter.get_ha_color_temperature(state.value)
Expand Down Expand Up @@ -296,7 +307,7 @@ def _converter(self) -> ColorTemperatureConverter:


@register_capability
class ColorSceneCapability(AbstractCapability):
class ColorSceneCapability(AbstractCapability[SceneInstanceActionState]):
"""Capability to control effect of a light device."""

type = CapabilityType.COLOR_SETTING
Expand Down Expand Up @@ -347,6 +358,8 @@ def get_value(self) -> ColorScene | None:
if effect := self.state.attributes.get(light.ATTR_EFFECT):
return self.get_scene_by_effect(effect)

return None

async def set_instance_state(self, context: Context, state: SceneInstanceActionState) -> None:
"""Change the capability state."""
await self._hass.services.async_call(
Expand Down Expand Up @@ -393,9 +406,13 @@ def get_scene_by_effect(self, effect: str) -> ColorScene | None:
if effect.lower() in effects:
return scene

return None

def get_effect_by_scene(self, scene: ColorScene) -> str | None:
"""Return HA light effect for Yandex scene."""
for effect in self.scenes_map.get(scene, {}):
for supported_effect in self.state.attributes.get(light.ATTR_EFFECT_LIST, []):
if str(supported_effect).lower() == effect:
return supported_effect
return str(supported_effect)

return None
10 changes: 8 additions & 2 deletions custom_components/yandex_smart_home/capability_custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
CONF_ENTITY_RANGE_MIN,
CONF_ENTITY_RANGE_PRECISION,
ERR_DEVICE_UNREACHABLE,
ERR_INTERNAL_ERROR,
ERR_NOT_SUPPORTED_IN_CURRENT_MODE,
)
from .error import SmartHomeError
Expand Down Expand Up @@ -68,14 +69,16 @@ def get_value(self) -> Any:
entity_state = self.state

if self._state_entity_id:
entity_state = self._hass.states.get(self._state_entity_id)
if not entity_state:
state_by_entity_id = self._hass.states.get(self._state_entity_id)
if not state_by_entity_id:
raise SmartHomeError(
ERR_DEVICE_UNREACHABLE,
f"Entity {self._state_entity_id} not found for "
f"{self.instance} instance of {self.state.entity_id}",
)

entity_state = state_by_entity_id

if self._state_value_attribute:
value = entity_state.attributes.get(self._state_value_attribute)
else:
Expand Down Expand Up @@ -195,6 +198,9 @@ async def set_instance_state(self, context: Context, state: RangeCapabilityInsta

value = self._get_absolute_value(state.value)

if not config:
raise SmartHomeError(ERR_INTERNAL_ERROR, "Missing capability service")

await async_call_from_config(
self._hass,
config,
Expand Down
Loading

0 comments on commit af810eb

Please sign in to comment.