234 lines
6.8 KiB
Python
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]]]()
|