Files
Victor Alexandrovich Tsyrenschikov 373ed28445 python
2026-03-30 20:25:42 +05:00

266 lines
9.1 KiB
Python

"""Implement the Yandex Smart Home toggle capabilities."""
from typing import Protocol
from homeassistant.components import cover, fan, light, media_player, vacuum
from homeassistant.components.cover import CoverEntityFeature
from homeassistant.components.fan import FanEntityFeature
from homeassistant.components.media_player.const import MediaPlayerEntityFeature
from homeassistant.components.vacuum import VacuumEntityFeature
from homeassistant.const import (
ATTR_ENTITY_ID,
SERVICE_MEDIA_PAUSE,
SERVICE_MEDIA_PLAY,
SERVICE_STOP_COVER,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
SERVICE_VOLUME_MUTE,
STATE_ON,
STATE_PLAYING,
)
from homeassistant.core import Context
from .backports import VacuumActivity
from .capability import STATE_CAPABILITIES_REGISTRY, ActionOnlyCapabilityMixin, Capability, StateCapability
from .color import SOLID_LIGHT_EFFECT, LightState
from .const import CONF_FEATURES, MediaPlayerFeature
from .schema import (
CapabilityType,
ToggleCapabilityInstance,
ToggleCapabilityInstanceActionState,
ToggleCapabilityParameters,
)
class ToggleCapability(Capability[ToggleCapabilityInstanceActionState], Protocol):
"""Base class for capabilities with toggle functions like mute or pause.
https://yandex.ru/dev/dialogs/alice/doc/smart-home/concepts/toggle-docpage/
"""
type: CapabilityType = CapabilityType.TOGGLE
instance: ToggleCapabilityInstance
@property
def parameters(self) -> ToggleCapabilityParameters:
"""Return parameters for a devices list request."""
return ToggleCapabilityParameters(instance=self.instance)
class StateToggleCapability(ToggleCapability, StateCapability[ToggleCapabilityInstanceActionState], Protocol):
"""Base class for a toggle capability based on the state."""
pass
class BacklightCapability(StateToggleCapability):
"""Capability to represent state as backlight toggle."""
instance = ToggleCapabilityInstance.BACKLIGHT
@property
def supported(self) -> bool:
"""Test if the capability is supported."""
return True
def get_value(self) -> bool:
"""Return the current capability value."""
return self.state.state == STATE_ON
async def set_instance_state(self, context: Context, state: ToggleCapabilityInstanceActionState) -> None:
"""Change the capability state."""
if state.value:
service = SERVICE_TURN_ON
else:
service = SERVICE_TURN_OFF
await self._hass.services.async_call(
self.state.domain,
service,
{ATTR_ENTITY_ID: self.state.entity_id},
blocking=self._wait_for_service_call,
context=context,
)
class MuteCapability(StateToggleCapability):
"""Capability to mute and unmute device."""
instance = ToggleCapabilityInstance.MUTE
@property
def supported(self) -> bool:
"""Test if the capability is supported."""
if self.state.domain == media_player.DOMAIN:
if self._state_features & MediaPlayerEntityFeature.VOLUME_MUTE:
return True
if MediaPlayerFeature.VOLUME_MUTE in self._entity_config.get(CONF_FEATURES, []):
return True
return False
@property
def retrievable(self) -> bool:
"""Test if the capability can return the current value."""
return media_player.ATTR_MEDIA_VOLUME_MUTED in self.state.attributes
def get_value(self) -> bool:
"""Return the current capability value."""
return bool(self.state.attributes.get(media_player.ATTR_MEDIA_VOLUME_MUTED))
async def set_instance_state(self, context: Context, state: ToggleCapabilityInstanceActionState) -> None:
"""Change the capability state."""
await self._hass.services.async_call(
media_player.DOMAIN,
SERVICE_VOLUME_MUTE,
{ATTR_ENTITY_ID: self.state.entity_id, media_player.ATTR_MEDIA_VOLUME_MUTED: state.value},
blocking=self._wait_for_service_call,
context=context,
)
class PauseCapabilityMediaPlayer(StateToggleCapability):
"""Capability to pause and resume media player playback."""
instance = ToggleCapabilityInstance.PAUSE
@property
def supported(self) -> bool:
"""Test if the capability is supported."""
if self.state.domain == media_player.DOMAIN:
if MediaPlayerFeature.PLAY_PAUSE in self._entity_config.get(CONF_FEATURES, []):
return True
if (
self._state_features & MediaPlayerEntityFeature.PAUSE
and self._state_features & MediaPlayerEntityFeature.PLAY
):
return True
return False
def get_value(self) -> bool:
"""Return the current capability value."""
return bool(self.state.state != STATE_PLAYING)
async def set_instance_state(self, context: Context, state: ToggleCapabilityInstanceActionState) -> None:
"""Change the capability state."""
if state.value:
service = SERVICE_MEDIA_PAUSE
else:
service = SERVICE_MEDIA_PLAY
await self._hass.services.async_call(
media_player.DOMAIN,
service,
{ATTR_ENTITY_ID: self.state.entity_id},
blocking=self._wait_for_service_call,
context=context,
)
class PauseCapabilityCover(ActionOnlyCapabilityMixin, StateToggleCapability):
"""Capability to stop a cover."""
instance = ToggleCapabilityInstance.PAUSE
@property
def supported(self) -> bool:
"""Test if the capability is supported."""
return self.state.domain == cover.DOMAIN and bool(self._state_features & CoverEntityFeature.STOP)
async def set_instance_state(self, context: Context, state: ToggleCapabilityInstanceActionState) -> None:
"""Change the capability state."""
await self._hass.services.async_call(
cover.DOMAIN,
SERVICE_STOP_COVER,
{ATTR_ENTITY_ID: self.state.entity_id},
blocking=self._wait_for_service_call,
context=context,
)
class PauseCapabilityLight(ActionOnlyCapabilityMixin, StateToggleCapability, LightState):
"""Capability to turn on solid light effect for a light device."""
instance = ToggleCapabilityInstance.PAUSE
@property
def supported(self) -> bool:
"""Test if the capability is supported."""
return self.state.domain == light.DOMAIN and self._solid_effect_supported
async def set_instance_state(self, context: Context, state: ToggleCapabilityInstanceActionState) -> None:
"""Change the capability state."""
await self._hass.services.async_call(
light.DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: self.state.entity_id, light.ATTR_EFFECT: SOLID_LIGHT_EFFECT},
blocking=self._wait_for_service_call,
context=context,
)
class PauseCapabilityVacuum(StateToggleCapability):
"""Capability to stop a vacuum."""
instance = ToggleCapabilityInstance.PAUSE
@property
def supported(self) -> bool:
"""Test if the capability is supported."""
return self.state.domain == vacuum.DOMAIN and bool(self._state_features & VacuumEntityFeature.PAUSE)
def get_value(self) -> bool:
"""Return the current capability value."""
return self.state.state == VacuumActivity.PAUSED
async def set_instance_state(self, context: Context, state: ToggleCapabilityInstanceActionState) -> None:
"""Change the capability state."""
if state.value:
service = vacuum.SERVICE_PAUSE
else:
service = vacuum.SERVICE_START
await self._hass.services.async_call(
vacuum.DOMAIN,
service,
{ATTR_ENTITY_ID: self.state.entity_id},
blocking=self._wait_for_service_call,
context=context,
)
class OscillationCapability(StateToggleCapability):
"""Capability to control fan oscillation."""
instance = ToggleCapabilityInstance.OSCILLATION
@property
def supported(self) -> bool:
"""Test if the capability is supported."""
return self.state.domain == fan.DOMAIN and bool(self._state_features & FanEntityFeature.OSCILLATE)
def get_value(self) -> bool:
"""Return the current capability value."""
return bool(self.state.attributes.get(fan.ATTR_OSCILLATING))
async def set_instance_state(self, context: Context, state: ToggleCapabilityInstanceActionState) -> None:
"""Change the capability state."""
await self._hass.services.async_call(
fan.DOMAIN,
fan.SERVICE_OSCILLATE,
{ATTR_ENTITY_ID: self.state.entity_id, fan.ATTR_OSCILLATING: state.value},
blocking=self._wait_for_service_call,
context=context,
)
STATE_CAPABILITIES_REGISTRY.register(MuteCapability)
STATE_CAPABILITIES_REGISTRY.register(PauseCapabilityMediaPlayer)
STATE_CAPABILITIES_REGISTRY.register(PauseCapabilityCover)
STATE_CAPABILITIES_REGISTRY.register(PauseCapabilityLight)
STATE_CAPABILITIES_REGISTRY.register(PauseCapabilityVacuum)
STATE_CAPABILITIES_REGISTRY.register(OscillationCapability)