Home Assistant Unofficial Reference 2024.12.1
vacuum.py
Go to the documentation of this file.
1 """Support for Template vacuums."""
2 
3 from __future__ import annotations
4 
5 import logging
6 from typing import Any
7 
8 import voluptuous as vol
9 
11  ATTR_FAN_SPEED,
12  DOMAIN as VACUUM_DOMAIN,
13  SERVICE_CLEAN_SPOT,
14  SERVICE_LOCATE,
15  SERVICE_PAUSE,
16  SERVICE_RETURN_TO_BASE,
17  SERVICE_SET_FAN_SPEED,
18  SERVICE_START,
19  SERVICE_STOP,
20  STATE_CLEANING,
21  STATE_DOCKED,
22  STATE_ERROR,
23  STATE_IDLE,
24  STATE_PAUSED,
25  STATE_RETURNING,
26  StateVacuumEntity,
27  VacuumEntityFeature,
28 )
29 from homeassistant.const import (
30  CONF_ENTITY_ID,
31  CONF_FRIENDLY_NAME,
32  CONF_UNIQUE_ID,
33  CONF_VALUE_TEMPLATE,
34  STATE_UNKNOWN,
35 )
36 from homeassistant.core import HomeAssistant, callback
37 from homeassistant.exceptions import TemplateError
39 from homeassistant.helpers.entity import async_generate_entity_id
40 from homeassistant.helpers.entity_platform import AddEntitiesCallback
41 from homeassistant.helpers.script import Script
42 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
43 
44 from .const import DOMAIN
45 from .template_entity import (
46  TEMPLATE_ENTITY_ATTRIBUTES_SCHEMA_LEGACY,
47  TEMPLATE_ENTITY_AVAILABILITY_SCHEMA_LEGACY,
48  TemplateEntity,
49  rewrite_common_legacy_to_modern_conf,
50 )
51 
52 _LOGGER = logging.getLogger(__name__)
53 
54 CONF_VACUUMS = "vacuums"
55 CONF_BATTERY_LEVEL_TEMPLATE = "battery_level_template"
56 CONF_FAN_SPEED_LIST = "fan_speeds"
57 CONF_FAN_SPEED_TEMPLATE = "fan_speed_template"
58 
59 ENTITY_ID_FORMAT = VACUUM_DOMAIN + ".{}"
60 _VALID_STATES = [
61  STATE_CLEANING,
62  STATE_DOCKED,
63  STATE_PAUSED,
64  STATE_IDLE,
65  STATE_RETURNING,
66  STATE_ERROR,
67 ]
68 
69 VACUUM_SCHEMA = vol.All(
70  cv.deprecated(CONF_ENTITY_ID),
71  vol.Schema(
72  {
73  vol.Optional(CONF_FRIENDLY_NAME): cv.string,
74  vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
75  vol.Optional(CONF_BATTERY_LEVEL_TEMPLATE): cv.template,
76  vol.Optional(CONF_FAN_SPEED_TEMPLATE): cv.template,
77  vol.Required(SERVICE_START): cv.SCRIPT_SCHEMA,
78  vol.Optional(SERVICE_PAUSE): cv.SCRIPT_SCHEMA,
79  vol.Optional(SERVICE_STOP): cv.SCRIPT_SCHEMA,
80  vol.Optional(SERVICE_RETURN_TO_BASE): cv.SCRIPT_SCHEMA,
81  vol.Optional(SERVICE_CLEAN_SPOT): cv.SCRIPT_SCHEMA,
82  vol.Optional(SERVICE_LOCATE): cv.SCRIPT_SCHEMA,
83  vol.Optional(SERVICE_SET_FAN_SPEED): cv.SCRIPT_SCHEMA,
84  vol.Optional(CONF_FAN_SPEED_LIST, default=[]): cv.ensure_list,
85  vol.Optional(CONF_ENTITY_ID): cv.entity_ids,
86  vol.Optional(CONF_UNIQUE_ID): cv.string,
87  }
88  )
89  .extend(TEMPLATE_ENTITY_ATTRIBUTES_SCHEMA_LEGACY.schema)
90  .extend(TEMPLATE_ENTITY_AVAILABILITY_SCHEMA_LEGACY.schema),
91 )
92 
93 PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend(
94  {vol.Required(CONF_VACUUMS): vol.Schema({cv.slug: VACUUM_SCHEMA})}
95 )
96 
97 
98 async def _async_create_entities(hass, config):
99  """Create the Template Vacuums."""
100  vacuums = []
101 
102  for object_id, entity_config in config[CONF_VACUUMS].items():
103  entity_config = rewrite_common_legacy_to_modern_conf(hass, entity_config)
104  unique_id = entity_config.get(CONF_UNIQUE_ID)
105 
106  vacuums.append(
108  hass,
109  object_id,
110  entity_config,
111  unique_id,
112  )
113  )
114 
115  return vacuums
116 
117 
119  hass: HomeAssistant,
120  config: ConfigType,
121  async_add_entities: AddEntitiesCallback,
122  discovery_info: DiscoveryInfoType | None = None,
123 ) -> None:
124  """Set up the template vacuums."""
125  async_add_entities(await _async_create_entities(hass, config))
126 
127 
129  """A template vacuum component."""
130 
131  _attr_should_poll = False
132 
133  def __init__(
134  self,
135  hass,
136  object_id,
137  config,
138  unique_id,
139  ):
140  """Initialize the vacuum."""
141  super().__init__(
142  hass, config=config, fallback_name=object_id, unique_id=unique_id
143  )
145  ENTITY_ID_FORMAT, object_id, hass=hass
146  )
147  friendly_name = self._attr_name_attr_name
148 
149  self._template_template = config.get(CONF_VALUE_TEMPLATE)
150  self._battery_level_template_battery_level_template = config.get(CONF_BATTERY_LEVEL_TEMPLATE)
151  self._fan_speed_template_fan_speed_template = config.get(CONF_FAN_SPEED_TEMPLATE)
152  self._attr_supported_features_attr_supported_features = (
153  VacuumEntityFeature.START | VacuumEntityFeature.STATE
154  )
155 
156  self._start_script_start_script = Script(hass, config[SERVICE_START], friendly_name, DOMAIN)
157 
158  self._pause_script_pause_script = None
159  if pause_action := config.get(SERVICE_PAUSE):
160  self._pause_script_pause_script = Script(hass, pause_action, friendly_name, DOMAIN)
161  self._attr_supported_features_attr_supported_features |= VacuumEntityFeature.PAUSE
162 
163  self._stop_script_stop_script = None
164  if stop_action := config.get(SERVICE_STOP):
165  self._stop_script_stop_script = Script(hass, stop_action, friendly_name, DOMAIN)
166  self._attr_supported_features_attr_supported_features |= VacuumEntityFeature.STOP
167 
168  self._return_to_base_script_return_to_base_script = None
169  if return_to_base_action := config.get(SERVICE_RETURN_TO_BASE):
170  self._return_to_base_script_return_to_base_script = Script(
171  hass, return_to_base_action, friendly_name, DOMAIN
172  )
173  self._attr_supported_features_attr_supported_features |= VacuumEntityFeature.RETURN_HOME
174 
175  self._clean_spot_script_clean_spot_script = None
176  if clean_spot_action := config.get(SERVICE_CLEAN_SPOT):
177  self._clean_spot_script_clean_spot_script = Script(
178  hass, clean_spot_action, friendly_name, DOMAIN
179  )
180  self._attr_supported_features_attr_supported_features |= VacuumEntityFeature.CLEAN_SPOT
181 
182  self._locate_script_locate_script = None
183  if locate_action := config.get(SERVICE_LOCATE):
184  self._locate_script_locate_script = Script(hass, locate_action, friendly_name, DOMAIN)
185  self._attr_supported_features_attr_supported_features |= VacuumEntityFeature.LOCATE
186 
187  self._set_fan_speed_script_set_fan_speed_script = None
188  if set_fan_speed_action := config.get(SERVICE_SET_FAN_SPEED):
189  self._set_fan_speed_script_set_fan_speed_script = Script(
190  hass, set_fan_speed_action, friendly_name, DOMAIN
191  )
192  self._attr_supported_features_attr_supported_features |= VacuumEntityFeature.FAN_SPEED
193 
194  self._state_state = None
195  self._battery_level_battery_level = None
196  self._attr_fan_speed_attr_fan_speed = None
197 
198  if self._battery_level_template_battery_level_template:
199  self._attr_supported_features_attr_supported_features |= VacuumEntityFeature.BATTERY
200 
201  # List of valid fan speeds
202  self._attr_fan_speed_list_attr_fan_speed_list = config[CONF_FAN_SPEED_LIST]
203 
204  @property
205  def state(self) -> str | None:
206  """Return the status of the vacuum cleaner."""
207  return self._state_state
208 
209  async def async_start(self) -> None:
210  """Start or resume the cleaning task."""
211  await self.async_run_scriptasync_run_script(self._start_script_start_script, context=self._context_context)
212 
213  async def async_pause(self) -> None:
214  """Pause the cleaning task."""
215  if self._pause_script_pause_script is None:
216  return
217 
218  await self.async_run_scriptasync_run_script(self._pause_script_pause_script, context=self._context_context)
219 
220  async def async_stop(self, **kwargs: Any) -> None:
221  """Stop the cleaning task."""
222  if self._stop_script_stop_script is None:
223  return
224 
225  await self.async_run_scriptasync_run_script(self._stop_script_stop_script, context=self._context_context)
226 
227  async def async_return_to_base(self, **kwargs: Any) -> None:
228  """Set the vacuum cleaner to return to the dock."""
229  if self._return_to_base_script_return_to_base_script is None:
230  return
231 
232  await self.async_run_scriptasync_run_script(self._return_to_base_script_return_to_base_script, context=self._context_context)
233 
234  async def async_clean_spot(self, **kwargs: Any) -> None:
235  """Perform a spot clean-up."""
236  if self._clean_spot_script_clean_spot_script is None:
237  return
238 
239  await self.async_run_scriptasync_run_script(self._clean_spot_script_clean_spot_script, context=self._context_context)
240 
241  async def async_locate(self, **kwargs: Any) -> None:
242  """Locate the vacuum cleaner."""
243  if self._locate_script_locate_script is None:
244  return
245 
246  await self.async_run_scriptasync_run_script(self._locate_script_locate_script, context=self._context_context)
247 
248  async def async_set_fan_speed(self, fan_speed: str, **kwargs: Any) -> None:
249  """Set fan speed."""
250  if self._set_fan_speed_script_set_fan_speed_script is None:
251  return
252 
253  if fan_speed in self._attr_fan_speed_list_attr_fan_speed_list:
254  self._attr_fan_speed_attr_fan_speed = fan_speed
255  await self.async_run_scriptasync_run_script(
256  self._set_fan_speed_script_set_fan_speed_script,
257  run_variables={ATTR_FAN_SPEED: fan_speed},
258  context=self._context_context,
259  )
260  else:
261  _LOGGER.error(
262  "Received invalid fan speed: %s for entity %s. Expected: %s",
263  fan_speed,
264  self.entity_identity_identity_identity_id,
265  self._attr_fan_speed_list_attr_fan_speed_list,
266  )
267 
268  @callback
269  def _async_setup_templates(self) -> None:
270  """Set up templates."""
271  if self._template_template is not None:
272  self.add_template_attributeadd_template_attribute(
273  "_state", self._template_template, None, self._update_state_update_state_update_state
274  )
275  if self._fan_speed_template_fan_speed_template is not None:
276  self.add_template_attributeadd_template_attribute(
277  "_fan_speed",
278  self._fan_speed_template_fan_speed_template,
279  None,
280  self._update_fan_speed_update_fan_speed,
281  )
282  if self._battery_level_template_battery_level_template is not None:
283  self.add_template_attributeadd_template_attribute(
284  "_battery_level",
285  self._battery_level_template_battery_level_template,
286  None,
287  self._update_battery_level_update_battery_level,
288  none_on_template_error=True,
289  )
290  super()._async_setup_templates()
291 
292  @callback
293  def _update_state(self, result):
294  super()._update_state(result)
295  if isinstance(result, TemplateError):
296  # This is legacy behavior
297  self._state_state = STATE_UNKNOWN
298  if not self._availability_template_availability_template:
300  return
301 
302  # Validate state
303  if result in _VALID_STATES:
304  self._state_state = result
305  elif result == STATE_UNKNOWN:
306  self._state_state = None
307  else:
308  _LOGGER.error(
309  "Received invalid vacuum state: %s for entity %s. Expected: %s",
310  result,
311  self.entity_identity_identity_identity_id,
312  ", ".join(_VALID_STATES),
313  )
314  self._state_state = None
315 
316  @callback
317  def _update_battery_level(self, battery_level):
318  try:
319  battery_level_int = int(battery_level)
320  if not 0 <= battery_level_int <= 100:
321  raise ValueError # noqa: TRY301
322  except ValueError:
323  _LOGGER.error(
324  "Received invalid battery level: %s for entity %s. Expected: 0-100",
325  battery_level,
326  self.entity_identity_identity_identity_id,
327  )
328  self._attr_battery_level_attr_battery_level = None
329  return
330 
331  self._attr_battery_level_attr_battery_level = battery_level_int
332 
333  @callback
334  def _update_fan_speed(self, fan_speed):
335  if isinstance(fan_speed, TemplateError):
336  # This is legacy behavior
337  self._attr_fan_speed_attr_fan_speed = None
338  self._state_state = None
339  return
340 
341  if fan_speed in self._attr_fan_speed_list_attr_fan_speed_list:
342  self._attr_fan_speed_attr_fan_speed = fan_speed
343  elif fan_speed == STATE_UNKNOWN:
344  self._attr_fan_speed_attr_fan_speed = None
345  else:
346  _LOGGER.error(
347  "Received invalid fan speed: %s for entity %s. Expected: %s",
348  fan_speed,
349  self.entity_identity_identity_identity_id,
350  self._attr_fan_speed_list_attr_fan_speed_list,
351  )
352  self._attr_fan_speed_attr_fan_speed = None
None async_run_script(self, Script script, *_VarsType|None run_variables=None, Context|None context=None)
None add_template_attribute(self, str attribute, Template template, Callable[[Any], Any]|None validator=None, Callable[[Any], None]|None on_update=None, bool none_on_template_error=False)
None async_set_fan_speed(self, str fan_speed, **Any kwargs)
Definition: vacuum.py:248
def __init__(self, hass, object_id, config, unique_id)
Definition: vacuum.py:139
dict[str, Any] rewrite_common_legacy_to_modern_conf(HomeAssistant hass, dict[str, Any] entity_cfg, dict[str, str]|None extra_legacy_fields=None)
def _async_create_entities(hass, config)
Definition: vacuum.py:98
None async_setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback async_add_entities, DiscoveryInfoType|None discovery_info=None)
Definition: vacuum.py:123
str async_generate_entity_id(str entity_id_format, str|None name, Iterable[str]|None current_ids=None, HomeAssistant|None hass=None)
Definition: entity.py:119