Home Assistant Unofficial Reference 2024.12.1
fan.py
Go to the documentation of this file.
1 """Fan representation of a Snooz device."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Callable
6 from datetime import timedelta
7 from typing import Any
8 
9 from pysnooz.api import UnknownSnoozState
10 from pysnooz.commands import (
11  SnoozCommandData,
12  SnoozCommandResultStatus,
13  set_volume,
14  turn_off,
15  turn_on,
16 )
17 import voluptuous as vol
18 
19 from homeassistant.components.fan import ATTR_PERCENTAGE, FanEntity, FanEntityFeature
20 from homeassistant.config_entries import ConfigEntry
21 from homeassistant.const import STATE_OFF, STATE_ON
22 from homeassistant.core import HomeAssistant, callback
23 from homeassistant.exceptions import HomeAssistantError
24 from homeassistant.helpers import entity_platform
25 from homeassistant.helpers.device_registry import DeviceInfo
26 from homeassistant.helpers.entity_platform import AddEntitiesCallback
27 from homeassistant.helpers.restore_state import RestoreEntity
28 
29 from .const import (
30  ATTR_DURATION,
31  ATTR_VOLUME,
32  DEFAULT_TRANSITION_DURATION,
33  DOMAIN,
34  SERVICE_TRANSITION_OFF,
35  SERVICE_TRANSITION_ON,
36 )
37 from .models import SnoozConfigurationData
38 
39 
41  hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
42 ) -> None:
43  """Set up Snooz device from a config entry."""
44 
45  platform = entity_platform.async_get_current_platform()
46  platform.async_register_entity_service(
47  SERVICE_TRANSITION_ON,
48  {
49  vol.Optional(ATTR_VOLUME): vol.All(
50  vol.Coerce(int), vol.Range(min=0, max=100)
51  ),
52  vol.Optional(ATTR_DURATION, default=DEFAULT_TRANSITION_DURATION): vol.All(
53  vol.Coerce(int), vol.Range(min=1, max=300)
54  ),
55  },
56  "async_transition_on",
57  )
58  platform.async_register_entity_service(
59  SERVICE_TRANSITION_OFF,
60  {
61  vol.Optional(ATTR_DURATION, default=DEFAULT_TRANSITION_DURATION): vol.All(
62  vol.Coerce(int), vol.Range(min=1, max=300)
63  ),
64  },
65  "async_transition_off",
66  )
67 
68  data: SnoozConfigurationData = hass.data[DOMAIN][entry.entry_id]
69 
71 
72 
74  """Fan representation of a Snooz device."""
75 
76  _attr_has_entity_name = True
77  _attr_name = None
78  _attr_supported_features = (
79  FanEntityFeature.SET_SPEED
80  | FanEntityFeature.TURN_OFF
81  | FanEntityFeature.TURN_ON
82  )
83  _attr_should_poll = False
84  _is_on: bool | None = None
85  _percentage: int | None = None
86  _enable_turn_on_off_backwards_compatibility = False
87 
88  def __init__(self, data: SnoozConfigurationData) -> None:
89  """Initialize a Snooz fan entity."""
90  self._device_device = data.device
91  self._attr_unique_id_attr_unique_id = data.device.address
92  self._attr_device_info_attr_device_info = DeviceInfo(identifiers={(DOMAIN, data.device.address)})
93 
94  @callback
95  def _async_write_state_changed(self) -> None:
96  # cache state for restore entity
97  if not self.assumed_stateassumed_stateassumed_state:
98  self._is_on_is_on = self._device_device.state.on
99  self._percentage_percentage = self._device_device.state.volume
100 
101  self.async_write_ha_stateasync_write_ha_state()
102 
103  async def async_added_to_hass(self) -> None:
104  """Restore state and subscribe to device events."""
105  await super().async_added_to_hass()
106 
107  if last_state := await self.async_get_last_stateasync_get_last_state():
108  if last_state.state in (STATE_ON, STATE_OFF):
109  self._is_on_is_on = last_state.state == STATE_ON
110  else:
111  self._is_on_is_on = None
112  self._percentage_percentage = last_state.attributes.get(ATTR_PERCENTAGE)
113 
114  self.async_on_removeasync_on_remove(self._async_subscribe_to_device_change_async_subscribe_to_device_change())
115 
116  @callback
117  def _async_subscribe_to_device_change(self) -> Callable[[], None]:
118  return self._device_device.subscribe_to_state_change(self._async_write_state_changed_async_write_state_changed)
119 
120  @property
121  def percentage(self) -> int | None:
122  """Volume level of the device."""
123  return self._percentage_percentage if self.assumed_stateassumed_stateassumed_state else self._device_device.state.volume
124 
125  @property
126  def is_on(self) -> bool | None:
127  """Power state of the device."""
128  return self._is_on_is_on if self.assumed_stateassumed_stateassumed_state else self._device_device.state.on
129 
130  @property
131  def assumed_state(self) -> bool:
132  """Return True if unable to access real state of the entity."""
133  return not self._device_device.is_connected or self._device_device.state is UnknownSnoozState
134 
135  async def async_turn_on(
136  self,
137  percentage: int | None = None,
138  preset_mode: str | None = None,
139  **kwargs: Any,
140  ) -> None:
141  """Turn on the device."""
142  await self._async_execute_command_async_execute_command(turn_on(percentage))
143 
144  async def async_turn_off(self, **kwargs: Any) -> None:
145  """Turn off the device."""
146  await self._async_execute_command_async_execute_command(turn_off())
147 
148  async def async_set_percentage(self, percentage: int) -> None:
149  """Set the volume of the device. A value of 0 will turn off the device."""
150  await self._async_execute_command_async_execute_command(
151  set_volume(percentage) if percentage > 0 else turn_off()
152  )
153 
154  async def async_transition_on(self, duration: int, **kwargs: Any) -> None:
155  """Transition on the device."""
156  await self._async_execute_command_async_execute_command(
157  turn_on(volume=kwargs.get("volume"), duration=timedelta(seconds=duration))
158  )
159 
160  async def async_transition_off(self, duration: int, **kwargs: Any) -> None:
161  """Transition off the device."""
162  await self._async_execute_command_async_execute_command(
163  turn_off(duration=timedelta(seconds=duration))
164  )
165 
166  async def _async_execute_command(self, command: SnoozCommandData) -> None:
167  result = await self._device_device.async_execute_command(command)
168 
169  if result.status == SnoozCommandResultStatus.SUCCESSFUL:
170  self._async_write_state_changed_async_write_state_changed()
171  elif result.status != SnoozCommandResultStatus.CANCELLED:
172  raise HomeAssistantError(
173  f"Command {command} failed with status {result.status.name} after"
174  f" {result.duration}"
175  )
None turn_on(self, int|None percentage=None, str|None preset_mode=None, **Any kwargs)
Definition: __init__.py:416
None _async_execute_command(self, SnoozCommandData command)
Definition: fan.py:166
None async_transition_off(self, int duration, **Any kwargs)
Definition: fan.py:160
None async_turn_on(self, int|None percentage=None, str|None preset_mode=None, **Any kwargs)
Definition: fan.py:140
None async_transition_on(self, int duration, **Any kwargs)
Definition: fan.py:154
None async_turn_off(self, **Any kwargs)
Definition: fan.py:144
None __init__(self, SnoozConfigurationData data)
Definition: fan.py:88
None async_set_percentage(self, int percentage)
Definition: fan.py:148
Callable[[], None] _async_subscribe_to_device_change(self)
Definition: fan.py:117
None async_on_remove(self, CALLBACK_TYPE func)
Definition: entity.py:1331
None turn_off(self, **Any kwargs)
Definition: entity.py:1705
None async_setup_entry(HomeAssistant hass, ConfigEntry entry, AddEntitiesCallback async_add_entities)
Definition: fan.py:42