Files
homeassistant/custom_components/yandex_smart_home/capability.py
Victor Alexandrovich Tsyrenschikov 373ed28445 python
2026-03-30 20:25:42 +05:00

234 lines
6.8 KiB
Python

"""Implement the Yandex Smart Home base device capability."""
from __future__ import annotations
from abc import abstractmethod
from functools import cached_property
from typing import TYPE_CHECKING, Any, Protocol, Self, runtime_checkable
from homeassistant.const import ATTR_SUPPORTED_FEATURES
from homeassistant.core import Context, HomeAssistant, State
from homeassistant.helpers.typing import ConfigType
from .const import CONF_SLOW
from .helpers import CacheStore, ListRegistry
from .schema import (
CapabilityDescription,
CapabilityInstance,
CapabilityInstanceActionResultValue,
CapabilityInstanceActionState,
CapabilityInstanceState,
CapabilityInstanceStateValue,
CapabilityParameters,
CapabilityType,
)
if TYPE_CHECKING:
from .entry_data import ConfigEntryData
@runtime_checkable
class Capability(Protocol[CapabilityInstanceActionState]):
"""Base class for a device capability."""
device_id: str
type: CapabilityType
instance: CapabilityInstance
_hass: HomeAssistant
_entry_data: ConfigEntryData
@property
@abstractmethod
def supported(self) -> bool:
"""Test if the capability is supported."""
...
@property
def retrievable(self) -> bool:
"""Test if the capability can return the current value."""
return True
@property
def reportable(self) -> bool:
"""Test if the capability can report value changes."""
return self._entry_data.is_reporting_states
@property
def time_sensitive(self) -> bool:
"""Test if value changes should be reported immediately."""
return False
@property
@abstractmethod
def parameters(self) -> CapabilityParameters | None:
"""Return parameters for a devices list request."""
...
def get_description(self) -> CapabilityDescription | None:
"""Return a description for a device list request. Capability with an empty description isn't discoverable."""
return CapabilityDescription(
type=self.type, retrievable=self.retrievable, reportable=self.reportable, parameters=self.parameters
)
@abstractmethod
def get_value(self) -> Any:
"""Return the current capability value."""
...
def get_instance_state(self) -> CapabilityInstanceState | None:
"""Return a state for a state query request."""
if (value := self.get_value()) is not None:
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
) -> CapabilityInstanceActionResultValue:
"""Change the capability state."""
...
def check_value_change(self, other: Self | None) -> bool:
"""Test if the capability value differs from other capability."""
if other is None:
return True
value, other_value = self.get_value(), other.get_value()
if value is None:
return False
if other_value is None or value != other_value:
return True
return False
@cached_property
def _entity_config(self) -> ConfigType:
"""Return additional configuration for the device."""
return self._entry_data.get_entity_config(self.device_id)
@property
def _wait_for_service_call(self) -> bool:
"""Check if service should be run in blocking mode."""
if self._entity_config.get(CONF_SLOW) is True:
return False
return True
def __str__(self) -> str:
"""Return string representation."""
return f"instance {self.instance} of {self.type.short} capability of {self.device_id}"
def __repr__(self) -> str:
"""Return the representation."""
return (
f"<{self.__class__.__name__}"
f" device_id={self.device_id }"
f" type={self.type}"
f" instance={self.instance}"
f">"
)
def __eq__(self, other: Any) -> bool:
"""Compare capabilities."""
return bool(
isinstance(other, Capability)
and self.type == other.type
and self.instance == other.instance
and self.device_id == other.device_id
)
class ActionOnlyCapabilityMixin:
"""Represents a capability that can only execute an action."""
@property
def retrievable(self) -> bool:
"""Test if the capability can return the current value."""
return False
@property
def reportable(self) -> bool:
"""Test if the capability can report value changes."""
return False
def get_value(self) -> None:
"""Return the current capability value."""
return None
class DummyCapability(Capability[CapabilityInstanceActionState]):
"""Represents a capability that user has disabled."""
def __init__(
self,
hass: HomeAssistant,
entry_data: ConfigEntryData,
type: CapabilityType,
instance: CapabilityInstance,
device_id: str,
):
"""Initialize a dummy capability."""
self._hass = hass
self._entry_data = entry_data
self.type = type
self.instance = instance
self.device_id = device_id
@property
def supported(self) -> bool:
"""Test if the capability is supported."""
raise NotImplementedError
@property
def parameters(self) -> CapabilityParameters | None:
"""Return parameters for a devices list request."""
raise NotImplementedError
def get_value(self) -> Any:
"""Return the current capability value."""
raise NotImplementedError
async def set_instance_state(
self, context: Context, state: CapabilityInstanceActionState
) -> CapabilityInstanceActionResultValue:
"""Change the capability state."""
raise NotImplementedError
@runtime_checkable
class StateCapability(Capability[CapabilityInstanceActionState], Protocol):
"""Base class for a device capability based on the state."""
state: State
device_id: str
_hass: HomeAssistant
_entry_data: ConfigEntryData
def __init__(self, hass: HomeAssistant, entry_data: ConfigEntryData, device_id: str, state: State):
"""Initialize a capability for the state."""
self._hass = hass
self._entry_data = entry_data
self.device_id = device_id
self.state = state
@property
def _state_features(self) -> int:
"""Return supported features for the state."""
return int(self.state.attributes.get(ATTR_SUPPORTED_FEATURES, 0))
@property
def _cache(self) -> CacheStore:
"""Return cache storage."""
return self._entry_data.cache
STATE_CAPABILITIES_REGISTRY = ListRegistry[type[StateCapability[Any]]]()