core/homeassistant/components/snooz/fan.py

176 lines
6.0 KiB
Python

"""Fan representation of a Snooz device."""
from __future__ import annotations
from collections.abc import Callable
from datetime import timedelta
from typing import Any
from pysnooz.api import UnknownSnoozState
from pysnooz.commands import (
SnoozCommandData,
SnoozCommandResultStatus,
set_volume,
turn_off,
turn_on,
)
import voluptuous as vol
from homeassistant.components.fan import ATTR_PERCENTAGE, FanEntity, FanEntityFeature
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import STATE_OFF, STATE_ON
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_platform
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.restore_state import RestoreEntity
from .const import (
ATTR_DURATION,
ATTR_VOLUME,
DEFAULT_TRANSITION_DURATION,
DOMAIN,
SERVICE_TRANSITION_OFF,
SERVICE_TRANSITION_ON,
)
from .models import SnoozConfigurationData
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up Snooz device from a config entry."""
platform = entity_platform.async_get_current_platform()
platform.async_register_entity_service(
SERVICE_TRANSITION_ON,
{
vol.Optional(ATTR_VOLUME): vol.All(
vol.Coerce(int), vol.Range(min=0, max=100)
),
vol.Optional(ATTR_DURATION, default=DEFAULT_TRANSITION_DURATION): vol.All(
vol.Coerce(int), vol.Range(min=1, max=300)
),
},
"async_transition_on",
)
platform.async_register_entity_service(
SERVICE_TRANSITION_OFF,
{
vol.Optional(ATTR_DURATION, default=DEFAULT_TRANSITION_DURATION): vol.All(
vol.Coerce(int), vol.Range(min=1, max=300)
),
},
"async_transition_off",
)
data: SnoozConfigurationData = hass.data[DOMAIN][entry.entry_id]
async_add_entities([SnoozFan(data)])
class SnoozFan(FanEntity, RestoreEntity):
"""Fan representation of a Snooz device."""
_attr_has_entity_name = True
_attr_name = None
_attr_supported_features = (
FanEntityFeature.SET_SPEED
| FanEntityFeature.TURN_OFF
| FanEntityFeature.TURN_ON
)
_attr_should_poll = False
_is_on: bool | None = None
_percentage: int | None = None
_enable_turn_on_off_backwards_compatibility = False
def __init__(self, data: SnoozConfigurationData) -> None:
"""Initialize a Snooz fan entity."""
self._device = data.device
self._attr_unique_id = data.device.address
self._attr_device_info = DeviceInfo(identifiers={(DOMAIN, data.device.address)})
@callback
def _async_write_state_changed(self) -> None:
# cache state for restore entity
if not self.assumed_state:
self._is_on = self._device.state.on
self._percentage = self._device.state.volume
self.async_write_ha_state()
async def async_added_to_hass(self) -> None:
"""Restore state and subscribe to device events."""
await super().async_added_to_hass()
if last_state := await self.async_get_last_state():
if last_state.state in (STATE_ON, STATE_OFF):
self._is_on = last_state.state == STATE_ON
else:
self._is_on = None
self._percentage = last_state.attributes.get(ATTR_PERCENTAGE)
self.async_on_remove(self._async_subscribe_to_device_change())
@callback
def _async_subscribe_to_device_change(self) -> Callable[[], None]:
return self._device.subscribe_to_state_change(self._async_write_state_changed)
@property
def percentage(self) -> int | None:
"""Volume level of the device."""
return self._percentage if self.assumed_state else self._device.state.volume
@property
def is_on(self) -> bool | None:
"""Power state of the device."""
return self._is_on if self.assumed_state else self._device.state.on
@property
def assumed_state(self) -> bool:
"""Return True if unable to access real state of the entity."""
return not self._device.is_connected or self._device.state is UnknownSnoozState
async def async_turn_on(
self,
percentage: int | None = None,
preset_mode: str | None = None,
**kwargs: Any,
) -> None:
"""Turn on the device."""
await self._async_execute_command(turn_on(percentage))
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn off the device."""
await self._async_execute_command(turn_off())
async def async_set_percentage(self, percentage: int) -> None:
"""Set the volume of the device. A value of 0 will turn off the device."""
await self._async_execute_command(
set_volume(percentage) if percentage > 0 else turn_off()
)
async def async_transition_on(self, duration: int, **kwargs: Any) -> None:
"""Transition on the device."""
await self._async_execute_command(
turn_on(volume=kwargs.get("volume"), duration=timedelta(seconds=duration))
)
async def async_transition_off(self, duration: int, **kwargs: Any) -> None:
"""Transition off the device."""
await self._async_execute_command(
turn_off(duration=timedelta(seconds=duration))
)
async def _async_execute_command(self, command: SnoozCommandData) -> None:
result = await self._device.async_execute_command(command)
if result.status == SnoozCommandResultStatus.SUCCESSFUL:
self._async_write_state_changed()
elif result.status != SnoozCommandResultStatus.CANCELLED:
raise HomeAssistantError(
f"Command {command} failed with status {result.status.name} after"
f" {result.duration}"
)