Home Assistant Unofficial Reference 2024.12.1
entity.py
Go to the documentation of this file.
1 """An abstract class for entities."""
2 
3 from __future__ import annotations
4 
5 from abc import ABCMeta
6 import asyncio
7 from collections import deque
8 from collections.abc import Callable, Coroutine, Iterable, Mapping
9 import dataclasses
10 from enum import Enum, IntFlag, auto
11 import functools as ft
12 import logging
13 import math
14 from operator import attrgetter
15 import sys
16 import threading
17 import time
18 from types import FunctionType
19 from typing import TYPE_CHECKING, Any, Final, Literal, NotRequired, TypedDict, final
20 
21 from propcache import cached_property
22 import voluptuous as vol
23 
24 from homeassistant.const import (
25  ATTR_ASSUMED_STATE,
26  ATTR_ATTRIBUTION,
27  ATTR_DEVICE_CLASS,
28  ATTR_ENTITY_PICTURE,
29  ATTR_FRIENDLY_NAME,
30  ATTR_ICON,
31  ATTR_SUPPORTED_FEATURES,
32  ATTR_UNIT_OF_MEASUREMENT,
33  DEVICE_DEFAULT_NAME,
34  STATE_OFF,
35  STATE_ON,
36  STATE_UNAVAILABLE,
37  STATE_UNKNOWN,
38  EntityCategory,
39 )
40 from homeassistant.core import (
41  CALLBACK_TYPE,
42  Context,
43  Event,
44  HassJobType,
45  HomeAssistant,
46  ReleaseChannel,
47  callback,
48  get_hassjob_callable_job_type,
49  get_release_channel,
50 )
51 from homeassistant.core_config import DATA_CUSTOMIZE
52 from homeassistant.exceptions import (
53  HomeAssistantError,
54  InvalidStateError,
55  NoEntitySpecifiedError,
56 )
57 from homeassistant.loader import async_suggest_report_issue, bind_hass
58 from homeassistant.util import ensure_unique_string, slugify
59 from homeassistant.util.frozen_dataclass_compat import FrozenOrThawed
60 
61 from . import device_registry as dr, entity_registry as er, singleton
62 from .device_registry import DeviceInfo, EventDeviceRegistryUpdatedData
63 from .event import (
64  async_track_device_registry_updated_event,
65  async_track_entity_registry_updated_event,
66 )
67 from .frame import report_non_thread_safe_operation
68 from .typing import UNDEFINED, StateType, UndefinedType
69 
70 timer = time.time
71 
72 if TYPE_CHECKING:
73  from .entity_platform import EntityPlatform
74 
75 _LOGGER = logging.getLogger(__name__)
76 SLOW_UPDATE_WARNING = 10
77 DATA_ENTITY_SOURCE = "entity_info"
78 
79 # Used when converting float states to string: limit precision according to machine
80 # epsilon to make the string representation readable
81 FLOAT_PRECISION = abs(int(math.floor(math.log10(abs(sys.float_info.epsilon))))) - 1
82 
83 # How many times per hour we allow capabilities to be updated before logging a warning
84 CAPABILITIES_UPDATE_LIMIT = 100
85 
86 CONTEXT_RECENT_TIME_SECONDS = 5 # Time that a context is considered recent
87 
88 
89 @callback
90 def async_setup(hass: HomeAssistant) -> None:
91  """Set up entity sources."""
92  entity_sources(hass)
93 
94 
95 @callback
96 @bind_hass
97 @singleton.singleton(DATA_ENTITY_SOURCE)
98 def entity_sources(hass: HomeAssistant) -> dict[str, EntityInfo]:
99  """Get the entity sources."""
100  return {}
101 
102 
104  entity_id_format: str,
105  name: str | None,
106  current_ids: list[str] | None = None,
107  hass: HomeAssistant | None = None,
108 ) -> str:
109  """Generate a unique entity ID based on given entity IDs or used IDs."""
110  return async_generate_entity_id(entity_id_format, name, current_ids, hass)
111 
112 
113 @callback
115  entity_id_format: str,
116  name: str | None,
117  current_ids: Iterable[str] | None = None,
118  hass: HomeAssistant | None = None,
119 ) -> str:
120  """Generate a unique entity ID based on given entity IDs or used IDs."""
121  name = (name or DEVICE_DEFAULT_NAME).lower()
122  preferred_string = entity_id_format.format(slugify(name))
123 
124  if current_ids is not None:
125  return ensure_unique_string(preferred_string, current_ids)
126 
127  if hass is None:
128  raise ValueError("Missing required parameter current_ids or hass")
129 
130  test_string = preferred_string
131  tries = 1
132  while not hass.states.async_available(test_string):
133  tries += 1
134  test_string = f"{preferred_string}_{tries}"
135 
136  return test_string
137 
138 
139 def get_capability(hass: HomeAssistant, entity_id: str, capability: str) -> Any | None:
140  """Get a capability attribute of an entity.
141 
142  First try the statemachine, then entity registry.
143  """
144  if state := hass.states.get(entity_id):
145  return state.attributes.get(capability)
146 
147  entity_registry = er.async_get(hass)
148  if not (entry := entity_registry.async_get(entity_id)):
149  raise HomeAssistantError(f"Unknown entity {entity_id}")
150 
151  return entry.capabilities.get(capability) if entry.capabilities else None
152 
153 
154 def get_device_class(hass: HomeAssistant, entity_id: str) -> str | None:
155  """Get device class of an entity.
156 
157  First try the statemachine, then entity registry.
158  """
159  if state := hass.states.get(entity_id):
160  return state.attributes.get(ATTR_DEVICE_CLASS)
161 
162  entity_registry = er.async_get(hass)
163  if not (entry := entity_registry.async_get(entity_id)):
164  raise HomeAssistantError(f"Unknown entity {entity_id}")
165 
166  return entry.device_class or entry.original_device_class
167 
168 
169 def get_supported_features(hass: HomeAssistant, entity_id: str) -> int:
170  """Get supported features for an entity.
171 
172  First try the statemachine, then entity registry.
173  """
174  if state := hass.states.get(entity_id):
175  return state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) # type: ignore[no-any-return]
176 
177  entity_registry = er.async_get(hass)
178  if not (entry := entity_registry.async_get(entity_id)):
179  raise HomeAssistantError(f"Unknown entity {entity_id}")
180 
181  return entry.supported_features or 0
182 
183 
184 def get_unit_of_measurement(hass: HomeAssistant, entity_id: str) -> str | None:
185  """Get unit of measurement of an entity.
186 
187  First try the statemachine, then entity registry.
188  """
189  if state := hass.states.get(entity_id):
190  return state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
191 
192  entity_registry = er.async_get(hass)
193  if not (entry := entity_registry.async_get(entity_id)):
194  raise HomeAssistantError(f"Unknown entity {entity_id}")
195 
196  return entry.unit_of_measurement
197 
198 
199 ENTITY_CATEGORIES_SCHEMA: Final = vol.Coerce(EntityCategory)
200 
201 
202 class EntityInfo(TypedDict):
203  """Entity info."""
204 
205  domain: str
206  custom_component: bool
207  config_entry: NotRequired[str]
208 
209 
210 class StateInfo(TypedDict):
211  """State info."""
212 
213  unrecorded_attributes: frozenset[str]
214 
215 
217  """The platform state of an entity."""
218 
219  # Not Added: Not yet added to a platform, polling updates
220  # are written to the state machine.
221  NOT_ADDED = auto()
222 
223  # Added: Added to a platform, polling updates
224  # are written to the state machine.
225  ADDED = auto()
226 
227  # Removed: Removed from a platform, polling updates
228  # are not written to the state machine.
229  REMOVED = auto()
230 
231 
232 _SENTINEL = object()
233 
234 
235 class EntityDescription(metaclass=FrozenOrThawed, frozen_or_thawed=True):
236  """A class that describes Home Assistant entities."""
237 
238  # This is the key identifier for this entity
239  key: str
240 
241  device_class: str | None = None
242  entity_category: EntityCategory | None = None
243  entity_registry_enabled_default: bool = True
244  entity_registry_visible_default: bool = True
245  force_update: bool = False
246  icon: str | None = None
247  has_entity_name: bool = False
248  name: str | UndefinedType | None = UNDEFINED
249  translation_key: str | None = None
250  translation_placeholders: Mapping[str, str] | None = None
251  unit_of_measurement: str | None = None
252 
253 
254 @dataclasses.dataclass(frozen=True, slots=True)
256  """Container with state and attributes.
257 
258  Returned by Entity._async_calculate_state.
259  """
260 
261  state: str
262  # The union of all attributes, after overriding with entity registry settings
263  attributes: dict[str, Any]
264  # Capability attributes returned by the capability_attributes property
265  capability_attributes: Mapping[str, Any] | None
266 
267 
268 class CachedProperties(type):
269  """Metaclass which invalidates cached entity properties on write to _attr_.
270 
271  A class which has CachedProperties can optionally have a list of cached
272  properties, passed as cached_properties, which must be a set of strings.
273  - Each item in the cached_property set must be the name of a method decorated
274  with @cached_property
275  - For each item in the cached_property set, a property function with the
276  same name, prefixed with _attr_, will be created
277  - The property _attr_-property functions allow setting, getting and deleting
278  data, which will be stored in an attribute prefixed with __attr_
279  - The _attr_-property setter will invalidate the @cached_property by calling
280  delattr on it
281  """
282 
283  def __new__(
284  mcs, # noqa: N804 ruff bug, ruff does not understand this is a metaclass
285  name: str,
286  bases: tuple[type, ...],
287  namespace: dict[Any, Any],
288  cached_properties: set[str] | None = None,
289  **kwargs: Any,
290  ) -> Any:
291  """Start creating a new CachedProperties.
292 
293  Pop cached_properties and store it in the namespace.
294  """
295  namespace["_CachedProperties__cached_properties"] = cached_properties or set()
296  return super().__new__(mcs, name, bases, namespace, **kwargs)
297 
298  def __init__(
299  cls,
300  name: str,
301  bases: tuple[type, ...],
302  namespace: dict[Any, Any],
303  **kwargs: Any,
304  ) -> None:
305  """Finish creating a new CachedProperties.
306 
307  Wrap _attr_ for cached properties in property objects.
308  """
309 
310  def deleter(name: str) -> Callable[[Any], None]:
311  """Create a deleter for an _attr_ property."""
312  private_attr_name = f"__attr_{name}"
313 
314  def _deleter(o: Any) -> None:
315  """Delete an _attr_ property.
316 
317  Does two things:
318  - Delete the __attr_ attribute
319  - Invalidate the cache of the cached property
320 
321  Raises AttributeError if the __attr_ attribute does not exist
322  """
323  # Invalidate the cache of the cached property
324  o.__dict__.pop(name, None)
325  # Delete the __attr_ attribute
326  delattr(o, private_attr_name)
327 
328  return _deleter
329 
330  def setter(name: str) -> Callable[[Any, Any], None]:
331  """Create a setter for an _attr_ property."""
332  private_attr_name = f"__attr_{name}"
333 
334  def _setter(o: Any, val: Any) -> None:
335  """Set an _attr_ property to the backing __attr attribute.
336 
337  Also invalidates the corresponding cached_property by calling
338  delattr on it.
339  """
340  if (
341  old_val := getattr(o, private_attr_name, _SENTINEL)
342  ) == val and type(old_val) is type(val):
343  return
344  setattr(o, private_attr_name, val)
345  # Invalidate the cache of the cached property
346  o.__dict__.pop(name, None)
347 
348  return _setter
349 
350  def make_property(name: str) -> property:
351  """Help create a property object."""
352  return property(
353  fget=attrgetter(f"__attr_{name}"), fset=setter(name), fdel=deleter(name)
354  )
355 
356  def wrap_attr(cls: CachedProperties, property_name: str) -> None:
357  """Wrap a cached property's corresponding _attr in a property.
358 
359  If the class being created has an _attr class attribute, move it, and its
360  annotations, to the __attr attribute.
361  """
362  attr_name = f"_attr_{property_name}"
363  private_attr_name = f"__attr_{property_name}"
364  # Check if an _attr_ class attribute exits and move it to __attr_. We check
365  # __dict__ here because we don't care about _attr_ class attributes in parents.
366  if attr_name in cls.__dict__:
367  attr = getattr(cls, attr_name)
368  if isinstance(attr, (FunctionType, property)):
369  raise TypeError(f"Can't override {attr_name} in subclass")
370  setattr(cls, private_attr_name, attr)
371  annotations = cls.__annotations__
372  if attr_name in annotations:
373  annotations[private_attr_name] = annotations.pop(attr_name)
374  # Create the _attr_ property
375  setattr(cls, attr_name, make_property(property_name))
376 
377  cached_properties: set[str] = namespace["_CachedProperties__cached_properties"]
378  seen_props: set[str] = set() # Keep track of properties which have been handled
379  for property_name in cached_properties:
380  wrap_attr(cls, property_name)
381  seen_props.add(property_name)
382 
383  # Look for cached properties of parent classes where this class has
384  # corresponding _attr_ class attributes and re-wrap them.
385  for parent in cls.__mro__[:0:-1]:
386  if "_CachedProperties__cached_properties" not in parent.__dict__:
387  continue
388  cached_properties = getattr(parent, "_CachedProperties__cached_properties")
389  for property_name in cached_properties:
390  if property_name in seen_props:
391  continue
392  attr_name = f"_attr_{property_name}"
393  # Check if an _attr_ class attribute exits. We check __dict__ here because
394  # we don't care about _attr_ class attributes in parents.
395  if (attr_name) not in cls.__dict__:
396  continue
397  wrap_attr(cls, property_name)
398  seen_props.add(property_name)
399 
400 
402  """Add ABCMeta to CachedProperties."""
403 
404 
405 CACHED_PROPERTIES_WITH_ATTR_ = {
406  "assumed_state",
407  "attribution",
408  "available",
409  "capability_attributes",
410  "device_class",
411  "device_info",
412  "entity_category",
413  "has_entity_name",
414  "entity_picture",
415  "entity_registry_enabled_default",
416  "entity_registry_visible_default",
417  "extra_state_attributes",
418  "force_update",
419  "icon",
420  "name",
421  "should_poll",
422  "state",
423  "supported_features",
424  "translation_key",
425  "translation_placeholders",
426  "unique_id",
427  "unit_of_measurement",
428 }
429 
430 
431 class Entity(
432  metaclass=ABCCachedProperties, cached_properties=CACHED_PROPERTIES_WITH_ATTR_
433 ):
434  """An abstract class for Home Assistant entities."""
435 
436  # SAFE TO OVERWRITE
437  # The properties and methods here are safe to overwrite when inheriting
438  # this class. These may be used to customize the behavior of the entity.
439  entity_id: str = None # type: ignore[assignment]
440 
441  # Owning hass instance. Set by EntityPlatform by calling add_to_platform_start
442  # While not purely typed, it makes typehinting more useful for us
443  # and removes the need for constant None checks or asserts.
444  hass: HomeAssistant = None # type: ignore[assignment]
445 
446  # Owning platform instance. Set by EntityPlatform by calling add_to_platform_start
447  # While not purely typed, it makes typehinting more useful for us
448  # and removes the need for constant None checks or asserts.
449  platform: EntityPlatform = None # type: ignore[assignment]
450 
451  # Entity description instance for this Entity
452  entity_description: EntityDescription
453 
454  # If we reported if this entity was slow
455  _slow_reported = False
456 
457  # If we reported deprecated supported features constants
458  _deprecated_supported_features_reported = False
459 
460  # If we reported this entity is updated while disabled
461  _disabled_reported = False
462 
463  # If we reported this entity is using async_update_ha_state, while
464  # it should be using async_write_ha_state.
465  _async_update_ha_state_reported = False
466 
467  # If we reported this entity was added without its platform set
468  _no_platform_reported = False
469 
470  # If we reported the name translation placeholders do not match the name
471  _name_translation_placeholders_reported = False
472 
473  # Protect for multiple updates
474  _update_staged = False
475 
476  # _verified_state_writable is set to True if the entity has been verified
477  # to be writable. This is used to avoid repeated checks.
478  _verified_state_writable = False
479 
480  # Process updates in parallel
481  parallel_updates: asyncio.Semaphore | None = None
482 
483  # Entry in the entity registry
484  registry_entry: er.RegistryEntry | None = None
485 
486  # If the entity is removed from the entity registry
487  _removed_from_registry: bool = False
488 
489  # The device entry for this entity
490  device_entry: dr.DeviceEntry | None = None
491 
492  # Hold list for functions to call on remove.
493  _on_remove: list[CALLBACK_TYPE] | None = None
494 
495  _unsub_device_updates: CALLBACK_TYPE | None = None
496 
497  # Context
498  _context: Context | None = None
499  _context_set: float | None = None
500 
501  # If entity is added to an entity platform
502  _platform_state = EntityPlatformState.NOT_ADDED
503 
504  # Attributes to exclude from recording, only set by base components, e.g. light
505  _entity_component_unrecorded_attributes: frozenset[str] = frozenset()
506  # Additional integration specific attributes to exclude from recording, set by
507  # platforms, e.g. a derived class in hue.light
508  _unrecorded_attributes: frozenset[str] = frozenset()
509  # Union of _entity_component_unrecorded_attributes and _unrecorded_attributes,
510  # set automatically by __init_subclass__
511  __combined_unrecorded_attributes: frozenset[str] = (
512  _entity_component_unrecorded_attributes | _unrecorded_attributes
513  )
514  # Job type cache
515  _job_types: dict[str, HassJobType] | None = None
516 
517  # StateInfo. Set by EntityPlatform by calling async_internal_added_to_hass
518  # While not purely typed, it makes typehinting more useful for us
519  # and removes the need for constant None checks or asserts.
520  _state_info: StateInfo = None # type: ignore[assignment]
521 
522  __capabilities_updated_at: deque[float]
523  __capabilities_updated_at_reported: bool = False
524  __remove_future: asyncio.Future[None] | None = None
525 
526  # Entity Properties
527  _attr_assumed_state: bool = False
528  _attr_attribution: str | None = None
529  _attr_available: bool = True
530  _attr_capability_attributes: dict[str, Any] | None = None
531  _attr_device_class: str | None
532  _attr_device_info: DeviceInfo | None = None
533  _attr_entity_category: EntityCategory | None
534  _attr_has_entity_name: bool
535  _attr_entity_picture: str | None = None
536  _attr_entity_registry_enabled_default: bool
537  _attr_entity_registry_visible_default: bool
538  _attr_extra_state_attributes: dict[str, Any]
539  _attr_force_update: bool
540  _attr_icon: str | None
541  _attr_name: str | None
542  _attr_should_poll: bool = True
543  _attr_state: StateType = STATE_UNKNOWN
544  _attr_supported_features: int | None = None
545  _attr_translation_key: str | None
546  _attr_translation_placeholders: Mapping[str, str]
547  _attr_unique_id: str | None = None
548  _attr_unit_of_measurement: str | None
549 
550  def __init_subclass__(cls, **kwargs: Any) -> None:
551  """Initialize an Entity subclass."""
552  super().__init_subclass__(**kwargs)
553  cls.__combined_unrecorded_attributes__combined_unrecorded_attributes = (
554  cls._entity_component_unrecorded_attributes | cls._unrecorded_attributes
555  )
556 
557  def get_hassjob_type(self, function_name: str) -> HassJobType:
558  """Get the job type function for the given name.
559 
560  This is used for entity service calls to avoid
561  figuring out the job type each time.
562  """
563  if not self._job_types_job_types:
564  self._job_types_job_types = {}
565  if function_name not in self._job_types_job_types:
566  self._job_types_job_types[function_name] = get_hassjob_callable_job_type(
567  getattr(self, function_name)
568  )
569  return self._job_types_job_types[function_name]
570 
571  @cached_property
572  def should_poll(self) -> bool:
573  """Return True if entity has to be polled for state.
574 
575  False if entity pushes its state to HA.
576  """
577  return self._attr_should_poll
578 
579  @cached_property
580  def unique_id(self) -> str | None:
581  """Return a unique ID."""
582  return self._attr_unique_id
583 
584  @cached_property
585  def use_device_name(self) -> bool:
586  """Return if this entity does not have its own name.
587 
588  Should be True if the entity represents the single main feature of a device.
589  """
590  if hasattr(self, "_attr_name"):
591  return not self._attr_name
592  if (
593  name_translation_key := self._name_translation_key_name_translation_key
594  ) and name_translation_key in self.platformplatform.platform_translations:
595  return False
596  if hasattr(self, "entity_description"):
597  return not self.entity_description.name
598  return not self.namename
599 
600  @cached_property
601  def has_entity_name(self) -> bool:
602  """Return if the name of the entity is describing only the entity itself."""
603  if hasattr(self, "_attr_has_entity_name"):
604  return self._attr_has_entity_name
605  if hasattr(self, "entity_description"):
606  return self.entity_description.has_entity_name
607  return False
608 
610  self,
611  component_translations: dict[str, str],
612  ) -> str | None:
613  """Return a translated name of the entity based on its device class."""
614  if not self.has_entity_namehas_entity_name:
615  return None
616  device_class_key = self.device_classdevice_class or "_"
617  platform = self.platformplatform
618  name_translation_key = (
619  f"component.{platform.domain}.entity_component.{device_class_key}.name"
620  )
621  return component_translations.get(name_translation_key)
622 
623  @cached_property
624  def _object_id_device_class_name(self) -> str | None:
625  """Return a translated name of the entity based on its device class."""
626  return self._device_class_name_helper_device_class_name_helper(
627  self.platformplatform.object_id_component_translations
628  )
629 
630  @cached_property
631  def _device_class_name(self) -> str | None:
632  """Return a translated name of the entity based on its device class."""
633  return self._device_class_name_helper_device_class_name_helper(self.platformplatform.component_translations)
634 
635  def _default_to_device_class_name(self) -> bool:
636  """Return True if an unnamed entity should be named by its device class."""
637  return False
638 
639  @cached_property
640  def _name_translation_key(self) -> str | None:
641  """Return translation key for entity name."""
642  if self.translation_keytranslation_key is None:
643  return None
644  platform = self.platformplatform
645  return (
646  f"component.{platform.platform_name}.entity.{platform.domain}"
647  f".{self.translation_key}.name"
648  )
649 
650  @cached_property
651  def _unit_of_measurement_translation_key(self) -> str | None:
652  """Return translation key for unit of measurement."""
653  if self.translation_keytranslation_key is None:
654  return None
655  if self.platformplatform is None:
656  raise ValueError(
657  f"Entity {type(self)} cannot have a translation key for "
658  "unit of measurement before being added to the entity platform"
659  )
660  platform = self.platformplatform
661  return (
662  f"component.{platform.platform_name}.entity.{platform.domain}"
663  f".{self.translation_key}.unit_of_measurement"
664  )
665 
666  def _substitute_name_placeholders(self, name: str) -> str:
667  """Substitute placeholders in entity name."""
668  try:
669  return name.format(**self.translation_placeholderstranslation_placeholders)
670  except KeyError as err:
671  if not self._name_translation_placeholders_reported_name_translation_placeholders_reported_name_translation_placeholders_reported:
672  if get_release_channel() is not ReleaseChannel.STABLE:
673  raise HomeAssistantError(f"Missing placeholder {err}") from err
674  report_issue = self._suggest_report_issue_suggest_report_issue()
675  _LOGGER.warning(
676  (
677  "Entity %s (%s) has translation placeholders '%s' which do not "
678  "match the name '%s', please %s"
679  ),
680  self.entity_identity_id,
681  type(self),
682  self.translation_placeholderstranslation_placeholders,
683  name,
684  report_issue,
685  )
687  return name
688 
690  self,
691  device_class_name: str | None,
692  platform_translations: dict[str, str],
693  ) -> str | UndefinedType | None:
694  """Return the name of the entity."""
695  if hasattr(self, "_attr_name"):
696  return self._attr_name
697  if (
698  self.has_entity_namehas_entity_name
699  and (name_translation_key := self._name_translation_key_name_translation_key)
700  and (name := platform_translations.get(name_translation_key))
701  ):
702  return self._substitute_name_placeholders_substitute_name_placeholders(name)
703  if hasattr(self, "entity_description"):
704  description_name = self.entity_description.name
705  if description_name is UNDEFINED and self._default_to_device_class_name_default_to_device_class_name():
706  return device_class_name
707  return description_name
708 
709  # The entity has no name set by _attr_name, translation_key or entity_description
710  # Check if the entity should be named by its device class
711  if self._default_to_device_class_name_default_to_device_class_name():
712  return device_class_name
713  return UNDEFINED
714 
715  @property
716  def suggested_object_id(self) -> str | None:
717  """Return input for object id."""
718  if (
719  # Check our class has overridden the name property from Entity
720  # We need to use type.__getattribute__ to retrieve the underlying
721  # property or cached_property object instead of the property's
722  # value.
723  type.__getattribute__(self.__class__, "name")
724  is type.__getattribute__(Entity, "name")
725  # The check for self.platform guards against integrations not using an
726  # EntityComponent and can be removed in HA Core 2024.1
727  and self.platformplatform
728  ):
729  name = self._name_internal_name_internal(
730  self._object_id_device_class_name_object_id_device_class_name,
731  self.platformplatform.object_id_platform_translations,
732  )
733  else:
734  name = self.namename
735  return None if name is UNDEFINED else name
736 
737  @cached_property
738  def name(self) -> str | UndefinedType | None:
739  """Return the name of the entity."""
740  # The check for self.platform guards against integrations not using an
741  # EntityComponent and can be removed in HA Core 2024.1
742  if not self.platformplatform:
743  return self._name_internal_name_internal(None, {})
744  return self._name_internal_name_internal(
745  self._device_class_name_device_class_name,
746  self.platformplatform.platform_translations,
747  )
748 
749  @cached_property
750  def state(self) -> StateType:
751  """Return the state of the entity."""
752  return self._attr_state
753 
754  @cached_property
755  def capability_attributes(self) -> dict[str, Any] | None:
756  """Return the capability attributes.
757 
758  Attributes that explain the capabilities of an entity.
759 
760  Implemented by component base class. Convention for attribute names
761  is lowercase snake_case.
762  """
763  return self._attr_capability_attributes
764 
765  def get_initial_entity_options(self) -> er.EntityOptionsType | None:
766  """Return initial entity options.
767 
768  These will be stored in the entity registry the first time the entity is seen,
769  and then never updated.
770 
771  Implemented by component base class, should not be extended by integrations.
772 
773  Note: Not a property to avoid calculating unless needed.
774  """
775  return None
776 
777  @cached_property
778  def state_attributes(self) -> dict[str, Any] | None:
779  """Return the state attributes.
780 
781  Implemented by component base class, should not be extended by integrations.
782  Convention for attribute names is lowercase snake_case.
783  """
784  return None
785 
786  @cached_property
787  def extra_state_attributes(self) -> Mapping[str, Any] | None:
788  """Return entity specific state attributes.
789 
790  Implemented by platform classes. Convention for attribute names
791  is lowercase snake_case.
792  """
793  if hasattr(self, "_attr_extra_state_attributes"):
794  return self._attr_extra_state_attributes
795  return None
796 
797  @cached_property
798  def device_info(self) -> DeviceInfo | None:
799  """Return device specific attributes.
800 
801  Implemented by platform classes.
802  """
803  return self._attr_device_info
804 
805  @cached_property
806  def device_class(self) -> str | None:
807  """Return the class of this device, from component DEVICE_CLASSES."""
808  if hasattr(self, "_attr_device_class"):
809  return self._attr_device_class
810  if hasattr(self, "entity_description"):
811  return self.entity_description.device_class
812  return None
813 
814  @cached_property
815  def unit_of_measurement(self) -> str | None:
816  """Return the unit of measurement of this entity, if any."""
817  if hasattr(self, "_attr_unit_of_measurement"):
818  return self._attr_unit_of_measurement
819  if hasattr(self, "entity_description"):
820  return self.entity_description.unit_of_measurement
821  return None
822 
823  @cached_property
824  def icon(self) -> str | None:
825  """Return the icon to use in the frontend, if any."""
826  if hasattr(self, "_attr_icon"):
827  return self._attr_icon
828  if hasattr(self, "entity_description"):
829  return self.entity_description.icon
830  return None
831 
832  @cached_property
833  def entity_picture(self) -> str | None:
834  """Return the entity picture to use in the frontend, if any."""
835  return self._attr_entity_picture
836 
837  @cached_property
838  def available(self) -> bool:
839  """Return True if entity is available."""
840  return self._attr_available
841 
842  @cached_property
843  def assumed_state(self) -> bool:
844  """Return True if unable to access real state of the entity."""
845  return self._attr_assumed_state
846 
847  @cached_property
848  def force_update(self) -> bool:
849  """Return True if state updates should be forced.
850 
851  If True, a state change will be triggered anytime the state property is
852  updated, not just when the value changes.
853  """
854  if hasattr(self, "_attr_force_update"):
855  return self._attr_force_update
856  if hasattr(self, "entity_description"):
857  return self.entity_description.force_update
858  return False
859 
860  @cached_property
861  def supported_features(self) -> int | None:
862  """Flag supported features."""
863  return self._attr_supported_features
864 
865  @cached_property
867  """Return if the entity should be enabled when first added.
868 
869  This only applies when fist added to the entity registry.
870  """
871  if hasattr(self, "_attr_entity_registry_enabled_default"):
872  return self._attr_entity_registry_enabled_default
873  if hasattr(self, "entity_description"):
874  return self.entity_description.entity_registry_enabled_default
875  return True
876 
877  @cached_property
879  """Return if the entity should be visible when first added.
880 
881  This only applies when fist added to the entity registry.
882  """
883  if hasattr(self, "_attr_entity_registry_visible_default"):
884  return self._attr_entity_registry_visible_default
885  if hasattr(self, "entity_description"):
886  return self.entity_description.entity_registry_visible_default
887  return True
888 
889  @cached_property
890  def attribution(self) -> str | None:
891  """Return the attribution."""
892  return self._attr_attribution
893 
894  @cached_property
895  def entity_category(self) -> EntityCategory | None:
896  """Return the category of the entity, if any."""
897  if hasattr(self, "_attr_entity_category"):
898  return self._attr_entity_category
899  if hasattr(self, "entity_description"):
900  return self.entity_description.entity_category
901  return None
902 
903  @cached_property
904  def translation_key(self) -> str | None:
905  """Return the translation key to translate the entity's states."""
906  if hasattr(self, "_attr_translation_key"):
907  return self._attr_translation_key
908  if hasattr(self, "entity_description"):
909  return self.entity_description.translation_key
910  return None
911 
912  @final
913  @cached_property
914  def translation_placeholders(self) -> Mapping[str, str]:
915  """Return the translation placeholders for translated entity's name."""
916  if hasattr(self, "_attr_translation_placeholders"):
917  return self._attr_translation_placeholders
918  if hasattr(self, "entity_description"):
919  return self.entity_description.translation_placeholders or {}
920  return {}
921 
922  # DO NOT OVERWRITE
923  # These properties and methods are either managed by Home Assistant or they
924  # are used to perform a very specific function. Overwriting these may
925  # produce undesirable effects in the entity's operation.
926 
927  @property
928  def enabled(self) -> bool:
929  """Return if the entity is enabled in the entity registry.
930 
931  If an entity is not part of the registry, it cannot be disabled
932  and will therefore always be enabled.
933  """
934  return self.registry_entryregistry_entry is None or not self.registry_entryregistry_entry.disabled
935 
936  @callback
937  def async_set_context(self, context: Context) -> None:
938  """Set the context the entity currently operates under."""
939  self._context_context = context
940  self._context_set_context_set = time.time()
941 
942  async def async_update_ha_state(self, force_refresh: bool = False) -> None:
943  """Update Home Assistant with current state of entity.
944 
945  If force_refresh == True will update entity before setting state.
946 
947  This method must be run in the event loop.
948  """
949  if self.hasshass is None:
950  raise RuntimeError(f"Attribute hass is None for {self}")
951 
952  if self.entity_identity_id is None:
954  f"No entity id specified for entity {self.name}"
955  )
956 
957  # update entity data
958  if force_refresh:
959  try:
960  await self.async_device_updateasync_device_update()
961  except Exception:
962  _LOGGER.exception("Update for %s fails", self.entity_identity_id)
963  return
964  elif not self._async_update_ha_state_reported_async_update_ha_state_reported_async_update_ha_state_reported:
965  report_issue = self._suggest_report_issue_suggest_report_issue()
966  _LOGGER.warning(
967  (
968  "Entity %s (%s) is using self.async_update_ha_state(), without"
969  " enabling force_refresh. Instead it should use"
970  " self.async_write_ha_state(), please %s"
971  ),
972  self.entity_identity_id,
973  type(self),
974  report_issue,
975  )
976  self._async_update_ha_state_reported_async_update_ha_state_reported_async_update_ha_state_reported = True
977 
978  self._async_write_ha_state_async_write_ha_state()
979 
980  @callback
981  def _async_verify_state_writable(self) -> None:
982  """Verify the entity is in a writable state."""
983  if self.hasshass is None:
984  raise RuntimeError(f"Attribute hass is None for {self}")
985 
986  # The check for self.platform guards against integrations not using an
987  # EntityComponent and can be removed in HA Core 2024.1
988  if self.platformplatform is None and not self._no_platform_reported_no_platform_reported_no_platform_reported: # type: ignore[unreachable]
989  report_issue = self._suggest_report_issue_suggest_report_issue() # type: ignore[unreachable]
990  _LOGGER.warning(
991  (
992  "Entity %s (%s) does not have a platform, this may be caused by "
993  "adding it manually instead of with an EntityComponent helper"
994  ", please %s"
995  ),
996  self.entity_identity_id,
997  type(self),
998  report_issue,
999  )
1000  self._no_platform_reported_no_platform_reported_no_platform_reported = True
1001 
1002  if self.entity_identity_id is None:
1003  raise NoEntitySpecifiedError(
1004  f"No entity id specified for entity {self.name}"
1005  )
1006 
1007  self._verified_state_writable_verified_state_writable_verified_state_writable = True
1008 
1009  @callback
1011  """Write the state to the state machine from the event loop thread."""
1012  if not self.hasshass or not self._verified_state_writable_verified_state_writable_verified_state_writable:
1013  self._async_verify_state_writable_async_verify_state_writable()
1014  self._async_write_ha_state_async_write_ha_state()
1015 
1016  @callback
1017  def async_write_ha_state(self) -> None:
1018  """Write the state to the state machine."""
1019  if not self.hasshass or not self._verified_state_writable_verified_state_writable_verified_state_writable:
1020  self._async_verify_state_writable_async_verify_state_writable()
1021  if self.hasshass.loop_thread_id != threading.get_ident():
1022  report_non_thread_safe_operation("async_write_ha_state")
1023  self._async_write_ha_state_async_write_ha_state()
1024 
1025  def _stringify_state(self, available: bool) -> str:
1026  """Convert state to string."""
1027  if not available:
1028  return STATE_UNAVAILABLE
1029  if (state := self.statestate) is None:
1030  return STATE_UNKNOWN
1031  if type(state) is str: # noqa: E721
1032  # fast path for strings
1033  return state
1034  if isinstance(state, float):
1035  # If the entity's state is a float, limit precision according to machine
1036  # epsilon to make the string representation readable
1037  return f"{state:.{FLOAT_PRECISION}}"
1038  return str(state)
1039 
1040  def _friendly_name_internal(self) -> str | None:
1041  """Return the friendly name.
1042 
1043  If has_entity_name is False, this returns self.name
1044  If has_entity_name is True, this returns device.name + self.name
1045  """
1046  name = self.namename
1047  if name is UNDEFINED:
1048  name = None
1049 
1050  if not self.has_entity_namehas_entity_name or not (device_entry := self.device_entrydevice_entry):
1051  return name
1052 
1053  device_name = device_entry.name_by_user or device_entry.name
1054  if name is None and self.use_device_nameuse_device_name:
1055  return device_name
1056  return f"{device_name} {name}" if device_name else name
1057 
1058  @callback
1059  def _async_calculate_state(self) -> CalculatedState:
1060  """Calculate state string and attribute mapping."""
1061  state, attr, capabilities, _, _ = self.__async_calculate_state__async_calculate_state()
1062  return CalculatedState(state, attr, capabilities)
1063 
1065  self,
1066  ) -> tuple[str, dict[str, Any], Mapping[str, Any] | None, str | None, int | None]:
1067  """Calculate state string and attribute mapping.
1068 
1069  Returns a tuple:
1070  state - the stringified state
1071  attr - the attribute dictionary
1072  capability_attr - a mapping with capability attributes
1073  original_device_class - the device class which may be overridden
1074  supported_features - the supported features
1075 
1076  This method is called when writing the state to avoid the overhead of creating
1077  a dataclass object.
1078  """
1079  entry = self.registry_entryregistry_entry
1080 
1081  capability_attr = self.capability_attributescapability_attributes
1082  attr = capability_attr.copy() if capability_attr else {}
1083 
1084  available = self.availableavailable # only call self.available once per update cycle
1085  state = self._stringify_state_stringify_state(available)
1086  if available:
1087  if state_attributes := self.state_attributesstate_attributes:
1088  attr.update(state_attributes)
1089  if extra_state_attributes := self.extra_state_attributesextra_state_attributes:
1090  attr.update(extra_state_attributes)
1091 
1092  if (unit_of_measurement := self.unit_of_measurementunit_of_measurement) is not None:
1093  attr[ATTR_UNIT_OF_MEASUREMENT] = unit_of_measurement
1094 
1095  if assumed_state := self.assumed_stateassumed_state:
1096  attr[ATTR_ASSUMED_STATE] = assumed_state
1097 
1098  if (attribution := self.attributionattribution) is not None:
1099  attr[ATTR_ATTRIBUTION] = attribution
1100 
1101  original_device_class = self.device_classdevice_class
1102  if (
1103  device_class := (entry and entry.device_class) or original_device_class
1104  ) is not None:
1105  attr[ATTR_DEVICE_CLASS] = str(device_class)
1106 
1107  if (entity_picture := self.entity_pictureentity_picture) is not None:
1108  attr[ATTR_ENTITY_PICTURE] = entity_picture
1109 
1110  if (icon := (entry and entry.icon) or self.iconicon) is not None:
1111  attr[ATTR_ICON] = icon
1112 
1113  if (
1114  name := (entry and entry.name) or self._friendly_name_internal_friendly_name_internal()
1115  ) is not None:
1116  attr[ATTR_FRIENDLY_NAME] = name
1117 
1118  if (supported_features := self.supported_featuressupported_features) is not None:
1119  attr[ATTR_SUPPORTED_FEATURES] = supported_features
1120 
1121  return (state, attr, capability_attr, original_device_class, supported_features)
1122 
1123  @callback
1124  def _async_write_ha_state(self) -> None:
1125  """Write the state to the state machine."""
1126  if self._platform_state_platform_state is EntityPlatformState.REMOVED:
1127  # Polling returned after the entity has already been removed
1128  return
1129 
1130  hass = self.hasshass
1131  entity_id = self.entity_identity_id
1132 
1133  if (entry := self.registry_entryregistry_entry) and entry.disabled_by:
1134  if not self._disabled_reported_disabled_reported_disabled_reported:
1135  self._disabled_reported_disabled_reported_disabled_reported = True
1136  _LOGGER.warning(
1137  (
1138  "Entity %s is incorrectly being triggered for updates while it"
1139  " is disabled. This is a bug in the %s integration"
1140  ),
1141  entity_id,
1142  self.platformplatform.platform_name,
1143  )
1144  return
1145 
1146  state_calculate_start = timer()
1147  state, attr, capabilities, original_device_class, supported_features = (
1148  self.__async_calculate_state__async_calculate_state()
1149  )
1150  time_now = timer()
1151 
1152  if entry:
1153  # Make sure capabilities in the entity registry are up to date. Capabilities
1154  # include capability attributes, device class and supported features
1155  supported_features = supported_features or 0
1156  if (
1157  capabilities != entry.capabilities
1158  or original_device_class != entry.original_device_class
1159  or supported_features != entry.supported_features
1160  ):
1161  if not self.__capabilities_updated_at_reported__capabilities_updated_at_reported:
1162  # _Entity__capabilities_updated_at is because of name mangling
1163  if not (
1164  capabilities_updated_at := getattr(
1165  self, "_Entity__capabilities_updated_at", None
1166  )
1167  ):
1168  self.__capabilities_updated_at__capabilities_updated_at = deque(
1169  maxlen=CAPABILITIES_UPDATE_LIMIT + 1
1170  )
1171  capabilities_updated_at = self.__capabilities_updated_at__capabilities_updated_at
1172  capabilities_updated_at.append(time_now)
1173  while time_now - capabilities_updated_at[0] > 3600:
1174  capabilities_updated_at.popleft()
1175  if len(capabilities_updated_at) > CAPABILITIES_UPDATE_LIMIT:
1176  self.__capabilities_updated_at_reported__capabilities_updated_at_reported = True
1177  report_issue = self._suggest_report_issue_suggest_report_issue()
1178  _LOGGER.warning(
1179  (
1180  "Entity %s (%s) is updating its capabilities too often,"
1181  " please %s"
1182  ),
1183  entity_id,
1184  type(self),
1185  report_issue,
1186  )
1187  entity_registry = er.async_get(self.hasshass)
1188  self.registry_entryregistry_entry = entity_registry.async_update_entity(
1189  self.entity_identity_id,
1190  capabilities=capabilities,
1191  original_device_class=original_device_class,
1192  supported_features=supported_features,
1193  )
1194 
1195  if time_now - state_calculate_start > 0.4 and not self._slow_reported_slow_reported_slow_reported:
1196  self._slow_reported_slow_reported_slow_reported = True
1197  report_issue = self._suggest_report_issue_suggest_report_issue()
1198  _LOGGER.warning(
1199  "Updating state for %s (%s) took %.3f seconds. Please %s",
1200  entity_id,
1201  type(self),
1202  time_now - state_calculate_start,
1203  report_issue,
1204  )
1205 
1206  try:
1207  # Most of the time this will already be
1208  # set and since try is near zero cost
1209  # on py3.11+ its faster to assume it is
1210  # set and catch the exception if it is not.
1211  customize = hass.data[DATA_CUSTOMIZE]
1212  except KeyError:
1213  pass
1214  else:
1215  # Overwrite properties that have been set in the config file.
1216  if custom := customize.get(entity_id):
1217  attr.update(custom)
1218 
1219  if (
1220  self._context_set_context_set is not None
1221  and time_now - self._context_set_context_set > CONTEXT_RECENT_TIME_SECONDS
1222  ):
1223  self._context_context = None
1224  self._context_set_context_set = None
1225 
1226  try:
1227  hass.states.async_set_internal(
1228  entity_id,
1229  state,
1230  attr,
1231  self.force_updateforce_update,
1232  self._context_context,
1233  self._state_info_state_info,
1234  time_now,
1235  )
1236  except InvalidStateError:
1237  _LOGGER.exception(
1238  "Failed to set state for %s, fall back to %s", entity_id, STATE_UNKNOWN
1239  )
1240  hass.states.async_set(
1241  entity_id, STATE_UNKNOWN, {}, self.force_updateforce_update, self._context_context
1242  )
1243 
1244  def schedule_update_ha_state(self, force_refresh: bool = False) -> None:
1245  """Schedule an update ha state change task.
1246 
1247  Scheduling the update avoids executor deadlocks.
1248 
1249  Entity state and attributes are read when the update ha state change
1250  task is executed.
1251  If state is changed more than once before the ha state change task has
1252  been executed, the intermediate state transitions will be missed.
1253  """
1254  if force_refresh:
1255  self.hasshass.create_task(
1256  self.async_update_ha_stateasync_update_ha_state(force_refresh),
1257  f"Entity {self.entity_id} schedule update ha state",
1258  )
1259  else:
1260  self.hasshass.loop.call_soon_threadsafe(
1261  self._async_write_ha_state_from_call_soon_threadsafe_async_write_ha_state_from_call_soon_threadsafe
1262  )
1263 
1264  @callback
1265  def async_schedule_update_ha_state(self, force_refresh: bool = False) -> None:
1266  """Schedule an update ha state change task.
1267 
1268  This method must be run in the event loop.
1269  Scheduling the update avoids executor deadlocks.
1270 
1271  Entity state and attributes are read when the update ha state change
1272  task is executed.
1273  If state is changed more than once before the ha state change task has
1274  been executed, the intermediate state transitions will be missed.
1275  """
1276  if force_refresh:
1277  self.hasshass.async_create_task(
1278  self.async_update_ha_stateasync_update_ha_state(force_refresh),
1279  f"Entity schedule update ha state {self.entity_id}",
1280  eager_start=True,
1281  )
1282  else:
1283  self.async_write_ha_stateasync_write_ha_state()
1284 
1285  @callback
1286  def _async_slow_update_warning(self) -> None:
1287  """Log a warning if update is taking too long."""
1288  _LOGGER.warning(
1289  "Update of %s is taking over %s seconds",
1290  self.entity_identity_id,
1291  SLOW_UPDATE_WARNING,
1292  )
1293 
1294  async def async_device_update(self, warning: bool = True) -> None:
1295  """Process 'update' or 'async_update' from entity.
1296 
1297  This method is a coroutine.
1298  """
1299  if self._update_staged_update_staged_update_staged:
1300  return
1301 
1302  hass = self.hasshass
1303  assert hass is not None
1304 
1305  self._update_staged_update_staged_update_staged = True
1306 
1307  # Process update sequential
1308  if self.parallel_updatesparallel_updates:
1309  await self.parallel_updatesparallel_updates.acquire()
1310 
1311  if warning:
1312  update_warn = hass.loop.call_at(
1313  hass.loop.time() + SLOW_UPDATE_WARNING, self._async_slow_update_warning_async_slow_update_warning
1314  )
1315 
1316  try:
1317  if hasattr(self, "async_update"):
1318  await self.async_update()
1319  elif hasattr(self, "update"):
1320  await hass.async_add_executor_job(self.update)
1321  else:
1322  return
1323  finally:
1324  self._update_staged_update_staged_update_staged = False
1325  if warning:
1326  update_warn.cancel()
1327  if self.parallel_updatesparallel_updates:
1328  self.parallel_updatesparallel_updates.release()
1329 
1330  @callback
1331  def async_on_remove(self, func: CALLBACK_TYPE) -> None:
1332  """Add a function to call when entity is removed or not added."""
1333  if self._on_remove_on_remove is None:
1334  self._on_remove_on_remove = []
1335  self._on_remove_on_remove.append(func)
1336 
1337  async def async_removed_from_registry(self) -> None:
1338  """Run when entity has been removed from entity registry.
1339 
1340  To be extended by integrations.
1341  """
1342 
1343  @callback
1345  self,
1346  hass: HomeAssistant,
1347  platform: EntityPlatform,
1348  parallel_updates: asyncio.Semaphore | None,
1349  ) -> None:
1350  """Start adding an entity to a platform."""
1351  if self._platform_state_platform_state is not EntityPlatformState.NOT_ADDED:
1352  raise HomeAssistantError(
1353  f"Entity '{self.entity_id}' cannot be added a second time to an entity"
1354  " platform"
1355  )
1356 
1357  self.hasshass = hass
1358  self.platformplatform = platform
1359  self.parallel_updatesparallel_updates = parallel_updates
1360  self._platform_state_platform_state = EntityPlatformState.ADDED
1361 
1362  def _call_on_remove_callbacks(self) -> None:
1363  """Call callbacks registered by async_on_remove."""
1364  if self._on_remove_on_remove is None:
1365  return
1366  while self._on_remove_on_remove:
1367  self._on_remove_on_remove.pop()()
1368 
1369  @callback
1370  def add_to_platform_abort(self) -> None:
1371  """Abort adding an entity to a platform."""
1372 
1373  self._platform_state_platform_state = EntityPlatformState.REMOVED
1374  self._call_on_remove_callbacks_call_on_remove_callbacks()
1375 
1376  self.hasshass = None # type: ignore[assignment]
1377  self.platformplatform = None # type: ignore[assignment]
1378  self.parallel_updatesparallel_updates = None
1379 
1380  async def add_to_platform_finish(self) -> None:
1381  """Finish adding an entity to a platform."""
1382  await self.async_internal_added_to_hassasync_internal_added_to_hass()
1383  await self.async_added_to_hassasync_added_to_hass()
1384  self.async_write_ha_stateasync_write_ha_state()
1385 
1386  @final
1387  async def async_remove(self, *, force_remove: bool = False) -> None:
1388  """Remove entity from Home Assistant.
1389 
1390  If the entity has a non disabled entry in the entity registry,
1391  the entity's state will be set to unavailable, in the same way
1392  as when the entity registry is loaded.
1393 
1394  If the entity doesn't have a non disabled entry in the entity registry,
1395  or if force_remove=True, its state will be removed.
1396  """
1397  if self.__remove_future__remove_future is not None:
1398  await self.__remove_future__remove_future
1399  return
1400 
1401  self.__remove_future__remove_future = self.hasshass.loop.create_future()
1402  try:
1403  await self.__async_remove_impl__async_remove_impl(force_remove)
1404  except BaseException as ex:
1405  self.__remove_future__remove_future.set_exception(ex)
1406  raise
1407  finally:
1408  self.__remove_future__remove_future.set_result(None)
1409 
1410  @final
1411  async def __async_remove_impl(self, force_remove: bool) -> None:
1412  """Remove entity from Home Assistant."""
1413 
1414  self._platform_state_platform_state = EntityPlatformState.REMOVED
1415 
1416  self._call_on_remove_callbacks_call_on_remove_callbacks()
1417 
1418  await self.async_internal_will_remove_from_hassasync_internal_will_remove_from_hass()
1419  await self.async_will_remove_from_hassasync_will_remove_from_hass()
1420 
1421  # Check if entry still exists in entity registry (e.g. unloading config entry)
1422  if (
1423  not force_remove
1424  and self.registry_entryregistry_entry
1425  and not self.registry_entryregistry_entry.disabled
1426  # Check if entity is still in the entity registry
1427  # by checking self._removed_from_registry
1428  #
1429  # Because self.registry_entry is unset in a task,
1430  # its possible that the entity has been removed but
1431  # the task has not yet been executed.
1432  #
1433  # self._removed_from_registry is set to True in a
1434  # callback which does not have the same issue.
1435  #
1436  and not self._removed_from_registry_removed_from_registry
1437  ):
1438  # Set the entity's state will to unavailable + ATTR_RESTORED: True
1439  self.registry_entryregistry_entry.write_unavailable_state(self.hasshass)
1440  else:
1441  self.hasshass.states.async_remove(self.entity_identity_id, context=self._context_context)
1442 
1443  async def async_added_to_hass(self) -> None:
1444  """Run when entity about to be added to hass.
1445 
1446  To be extended by integrations.
1447  """
1448 
1449  async def async_will_remove_from_hass(self) -> None:
1450  """Run when entity will be removed from hass.
1451 
1452  To be extended by integrations.
1453  """
1454 
1455  @callback
1456  def async_registry_entry_updated(self) -> None:
1457  """Run when the entity registry entry has been updated.
1458 
1459  To be extended by integrations.
1460  """
1461 
1462  async def async_internal_added_to_hass(self) -> None:
1463  """Run when entity about to be added to hass.
1464 
1465  Not to be extended by integrations.
1466  """
1467  is_custom_component = "custom_components" in type(self).__module__
1468  entity_info: EntityInfo = {
1469  "domain": self.platform.platform_name,
1470  "custom_component": is_custom_component,
1471  }
1472  if self.platform.config_entry:
1473  entity_info["config_entry"] = self.platform.config_entry.entry_id
1474 
1475  entity_sources(self.hass)[self.entity_id] = entity_info
1476 
1477  self._state_info_state_info = {
1478  "unrecorded_attributes": self.__combined_unrecorded_attributes__combined_unrecorded_attributes
1479  }
1480 
1481  if self.registry_entryregistry_entry is not None:
1482  # This is an assert as it should never happen, but helps in tests
1483  assert (
1484  not self.registry_entryregistry_entry.disabled_by
1485  ), f"Entity '{self.entity_id}' is being added while it's disabled"
1486 
1487  self.async_on_removeasync_on_remove(
1489  self.hasshass,
1490  self.entity_identity_id,
1491  self._async_registry_updated_async_registry_updated,
1492  job_type=HassJobType.Callback,
1493  )
1494  )
1495  self._async_subscribe_device_updates_async_subscribe_device_updates()
1496 
1497  async def async_internal_will_remove_from_hass(self) -> None:
1498  """Run when entity will be removed from hass.
1499 
1500  Not to be extended by integrations.
1501  """
1502  # The check for self.platform guards against integrations not using an
1503  # EntityComponent and can be removed in HA Core 2024.1
1504  if self.platformplatform:
1505  del entity_sources(self.hasshass)[self.entity_identity_id]
1506 
1507  @callback
1509  self, event: Event[er.EventEntityRegistryUpdatedData]
1510  ) -> None:
1511  """Handle entity registry update."""
1512  action = event.data["action"]
1513  is_remove = action == "remove"
1514  self._removed_from_registry_removed_from_registry = is_remove
1515  if action == "update" or is_remove:
1516  self.hasshass.async_create_task_internal(
1517  self._async_process_registry_update_or_remove_async_process_registry_update_or_remove(event), eager_start=True
1518  )
1519 
1521  self, event: Event[er.EventEntityRegistryUpdatedData]
1522  ) -> None:
1523  """Handle entity registry update or remove."""
1524  data = event.data
1525  if data["action"] == "remove":
1526  await self.async_removed_from_registryasync_removed_from_registry()
1527  self.registry_entryregistry_entry = None
1528  await self.async_removeasync_remove()
1529 
1530  if data["action"] != "update":
1531  return
1532 
1533  if "device_id" in data["changes"]:
1534  self._async_subscribe_device_updates_async_subscribe_device_updates()
1535 
1536  ent_reg = er.async_get(self.hasshass)
1537  old = self.registry_entryregistry_entry
1538  registry_entry = ent_reg.async_get(data["entity_id"])
1539  assert registry_entry is not None
1540  self.registry_entryregistry_entry = registry_entry
1541 
1542  if device_id := registry_entry.device_id:
1543  self.device_entrydevice_entry = dr.async_get(self.hasshass).async_get(device_id)
1544 
1545  if registry_entry.disabled:
1546  await self.async_removeasync_remove()
1547  return
1548 
1549  assert old is not None
1550  if registry_entry.entity_id == old.entity_id:
1551  self.async_registry_entry_updatedasync_registry_entry_updated()
1552  self.async_write_ha_stateasync_write_ha_state()
1553  return
1554 
1555  await self.async_removeasync_remove(force_remove=True)
1556 
1557  self.entity_identity_id = registry_entry.entity_id
1558 
1559  # Clear the remove future to handle entity added again after entity id change
1560  self.__remove_future__remove_future = None
1561  self._platform_state_platform_state = EntityPlatformState.NOT_ADDED
1562  await self.platformplatform.async_add_entities([self])
1563 
1564  @callback
1566  """Unsubscribe from device registry updates."""
1567  if not self._unsub_device_updates_unsub_device_updates:
1568  return
1569  self._unsub_device_updates_unsub_device_updates()
1570  self._unsub_device_updates_unsub_device_updates = None
1571 
1572  @callback
1574  self, event: Event[EventDeviceRegistryUpdatedData]
1575  ) -> None:
1576  """Handle device registry update."""
1577  data = event.data
1578 
1579  if data["action"] != "update":
1580  return
1581 
1582  if "name" not in data["changes"] and "name_by_user" not in data["changes"]:
1583  return
1584 
1585  self.device_entrydevice_entry = dr.async_get(self.hasshass).async_get(data["device_id"])
1586  self.async_write_ha_stateasync_write_ha_state()
1587 
1588  @callback
1590  """Subscribe to device registry updates."""
1591  assert self.registry_entryregistry_entry
1592 
1593  self._async_unsubscribe_device_updates_async_unsubscribe_device_updates()
1594 
1595  if (device_id := self.registry_entryregistry_entry.device_id) is None:
1596  return
1597 
1598  if not self.has_entity_namehas_entity_name:
1599  return
1600 
1602  self.hasshass,
1603  device_id,
1604  self._async_device_registry_updated_async_device_registry_updated,
1605  job_type=HassJobType.Callback,
1606  )
1607  if (
1608  not self._on_remove_on_remove
1609  or self._async_unsubscribe_device_updates_async_unsubscribe_device_updates not in self._on_remove_on_remove
1610  ):
1611  self.async_on_removeasync_on_remove(self._async_unsubscribe_device_updates_async_unsubscribe_device_updates)
1612 
1613  def __repr__(self) -> str:
1614  """Return the representation.
1615 
1616  If the entity is not added to a platform it's not safe to call _stringify_state.
1617  """
1618  if self._platform_state_platform_state is not EntityPlatformState.ADDED:
1619  return f"<entity unknown.unknown={STATE_UNKNOWN}>"
1620  return f"<entity {self.entity_id}={self._stringify_state(self.available)}>"
1621 
1622  async def async_request_call[_T](self, coro: Coroutine[Any, Any, _T]) -> _T:
1623  """Process request batched."""
1624  if self.parallel_updatesparallel_updates:
1625  await self.parallel_updatesparallel_updates.acquire()
1626 
1627  try:
1628  return await coro
1629  finally:
1630  if self.parallel_updatesparallel_updates:
1631  self.parallel_updatesparallel_updates.release()
1632 
1633  def _suggest_report_issue(self) -> str:
1634  """Suggest to report an issue."""
1635  # The check for self.platform guards against integrations not using an
1636  # EntityComponent and can be removed in HA Core 2024.1
1637  platform_name = self.platformplatform.platform_name if self.platformplatform else None
1639  self.hasshass, integration_domain=platform_name, module=type(self).__module__
1640  )
1641 
1642  @callback
1644  self, replacement: IntFlag
1645  ) -> None:
1646  """Report deprecated supported features values."""
1647  if self._deprecated_supported_features_reported_deprecated_supported_features_reported_deprecated_supported_features_reported is True:
1648  return
1650  report_issue = self._suggest_report_issue_suggest_report_issue()
1651  report_issue += (
1652  " and reference "
1653  "https://developers.home-assistant.io/blog/2023/12/28/support-feature-magic-numbers-deprecation"
1654  )
1655  _LOGGER.warning(
1656  (
1657  "Entity %s (%s) is using deprecated supported features"
1658  " values which will be removed in HA Core 2025.1. Instead it should use"
1659  " %s, please %s"
1660  ),
1661  self.entity_identity_id,
1662  type(self),
1663  repr(replacement),
1664  report_issue,
1665  )
1666 
1667 
1668 class ToggleEntityDescription(EntityDescription, frozen_or_thawed=True):
1669  """A class that describes toggle entities."""
1670 
1671 
1672 TOGGLE_ENTITY_CACHED_PROPERTIES_WITH_ATTR_ = {"is_on"}
1673 
1674 
1676  Entity, cached_properties=TOGGLE_ENTITY_CACHED_PROPERTIES_WITH_ATTR_
1677 ):
1678  """An abstract class for entities that can be turned on and off."""
1679 
1680  entity_description: ToggleEntityDescription
1681  _attr_is_on: bool | None = None
1682  _attr_state: None = None
1683 
1684  @property
1685  @final
1686  def state(self) -> Literal["on", "off"] | None:
1687  """Return the state."""
1688  if (is_on := self.is_onis_on) is None:
1689  return None
1690  return STATE_ON if is_on else STATE_OFF
1691 
1692  @cached_property
1693  def is_on(self) -> bool | None:
1694  """Return True if entity is on."""
1695  return self._attr_is_on
1696 
1697  def turn_on(self, **kwargs: Any) -> None:
1698  """Turn the entity on."""
1699  raise NotImplementedError
1700 
1701  async def async_turn_on(self, **kwargs: Any) -> None:
1702  """Turn the entity on."""
1703  await self.hasshass.async_add_executor_job(ft.partial(self.turn_onturn_on, **kwargs))
1704 
1705  def turn_off(self, **kwargs: Any) -> None:
1706  """Turn the entity off."""
1707  raise NotImplementedError
1708 
1709  async def async_turn_off(self, **kwargs: Any) -> None:
1710  """Turn the entity off."""
1711  await self.hasshass.async_add_executor_job(ft.partial(self.turn_offturn_off, **kwargs))
1712 
1713  @final
1714  def toggle(self, **kwargs: Any) -> None:
1715  """Toggle the entity.
1716 
1717  This method will never be called by Home Assistant and should not be implemented
1718  by integrations.
1719  """
1720 
1721  async def async_toggle(self, **kwargs: Any) -> None:
1722  """Toggle the entity.
1723 
1724  This method should typically not be implemented by integrations, it's enough to
1725  implement async_turn_on + async_turn_off or turn_on + turn_off.
1726  """
1727  if self.is_onis_on:
1728  await self.async_turn_offasync_turn_off(**kwargs)
1729  else:
1730  await self.async_turn_onasync_turn_on(**kwargs)
Any __new__(mcs, str name, tuple[type,...] bases, dict[Any, Any] namespace, set[str]|None cached_properties=None, **Any kwargs)
Definition: entity.py:290
None __init__(cls, str name, tuple[type,...] bases, dict[Any, Any] namespace, **Any kwargs)
Definition: entity.py:304
dict[str, Any]|None capability_attributes(self)
Definition: entity.py:755
str|None _device_class_name_helper(self, dict[str, str] component_translations)
Definition: entity.py:612
None _report_deprecated_supported_features_values(self, IntFlag replacement)
Definition: entity.py:1645
None async_schedule_update_ha_state(self, bool force_refresh=False)
Definition: entity.py:1265
None _async_unsubscribe_device_updates(self)
Definition: entity.py:1565
str _stringify_state(self, bool available)
Definition: entity.py:1025
None __init_subclass__(cls, **Any kwargs)
Definition: entity.py:550
dict[str, Any]|None state_attributes(self)
Definition: entity.py:778
EntityCategory|None entity_category(self)
Definition: entity.py:895
None async_update_ha_state(self, bool force_refresh=False)
Definition: entity.py:942
er.EntityOptionsType|None get_initial_entity_options(self)
Definition: entity.py:765
Mapping[str, str] translation_placeholders(self)
Definition: entity.py:914
str|UndefinedType|None _name_internal(self, str|None device_class_name, dict[str, str] platform_translations)
Definition: entity.py:693
str|None _object_id_device_class_name(self)
Definition: entity.py:624
None _async_subscribe_device_updates(self)
Definition: entity.py:1589
HassJobType get_hassjob_type(self, str function_name)
Definition: entity.py:557
str _substitute_name_placeholders(self, str name)
Definition: entity.py:666
bool entity_registry_visible_default(self)
Definition: entity.py:878
None _async_write_ha_state_from_call_soon_threadsafe(self)
Definition: entity.py:1010
CalculatedState _async_calculate_state(self)
Definition: entity.py:1059
bool _default_to_device_class_name(self)
Definition: entity.py:635
tuple[str, dict[str, Any], Mapping[str, Any]|None, str|None, int|None] __async_calculate_state(self)
Definition: entity.py:1066
str|None _name_translation_key(self)
Definition: entity.py:640
int|None supported_features(self)
Definition: entity.py:861
None add_to_platform_start(self, HomeAssistant hass, EntityPlatform platform, asyncio.Semaphore|None parallel_updates)
Definition: entity.py:1349
Mapping[str, Any]|None extra_state_attributes(self)
Definition: entity.py:787
None async_on_remove(self, CALLBACK_TYPE func)
Definition: entity.py:1331
None async_internal_will_remove_from_hass(self)
Definition: entity.py:1497
None schedule_update_ha_state(self, bool force_refresh=False)
Definition: entity.py:1244
None _async_registry_updated(self, Event[er.EventEntityRegistryUpdatedData] event)
Definition: entity.py:1510
None async_device_update(self, bool warning=True)
Definition: entity.py:1294
str|None _unit_of_measurement_translation_key(self)
Definition: entity.py:651
str|None unit_of_measurement(self)
Definition: entity.py:815
None async_remove(self, *bool force_remove=False)
Definition: entity.py:1387
None __async_remove_impl(self, bool force_remove)
Definition: entity.py:1411
DeviceInfo|None device_info(self)
Definition: entity.py:798
bool entity_registry_enabled_default(self)
Definition: entity.py:866
str|None _device_class_name(self)
Definition: entity.py:631
None _async_device_registry_updated(self, Event[EventDeviceRegistryUpdatedData] event)
Definition: entity.py:1575
str|None suggested_object_id(self)
Definition: entity.py:716
str|UndefinedType|None name(self)
Definition: entity.py:738
None async_set_context(self, Context context)
Definition: entity.py:937
str|None _friendly_name_internal(self)
Definition: entity.py:1040
None _async_process_registry_update_or_remove(self, Event[er.EventEntityRegistryUpdatedData] event)
Definition: entity.py:1522
None async_turn_off(self, **Any kwargs)
Definition: entity.py:1709
None turn_off(self, **Any kwargs)
Definition: entity.py:1705
None async_turn_on(self, **Any kwargs)
Definition: entity.py:1701
None async_toggle(self, **Any kwargs)
Definition: entity.py:1721
None turn_on(self, **Any kwargs)
Definition: entity.py:1697
Literal["on", "off"]|None state(self)
Definition: entity.py:1686
None toggle(self, **Any kwargs)
Definition: entity.py:1714
HassJobType get_hassjob_callable_job_type(Callable[..., Any] target)
Definition: core.py:387
ReleaseChannel get_release_channel()
Definition: core.py:315
AreaRegistry async_get(HomeAssistant hass)
None async_setup(HomeAssistant hass)
Definition: entity.py:90
str|None get_device_class(HomeAssistant hass, str entity_id)
Definition: entity.py:154
dict[str, EntityInfo] entity_sources(HomeAssistant hass)
Definition: entity.py:98
int get_supported_features(HomeAssistant hass, str entity_id)
Definition: entity.py:169
Any|None get_capability(HomeAssistant hass, str entity_id, str capability)
Definition: entity.py:139
str generate_entity_id(str entity_id_format, str|None name, list[str]|None current_ids=None, HomeAssistant|None hass=None)
Definition: entity.py:108
str|None get_unit_of_measurement(HomeAssistant hass, str entity_id)
Definition: entity.py:184
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
CALLBACK_TYPE async_track_device_registry_updated_event(HomeAssistant hass, str|Iterable[str] device_ids, Callable[[Event[EventDeviceRegistryUpdatedData]], Any] action, HassJobType|None job_type=None)
Definition: event.py:599
CALLBACK_TYPE async_track_entity_registry_updated_event(HomeAssistant hass, str|Iterable[str] entity_ids, Callable[[Event[EventEntityRegistryUpdatedData]], Any] action, HassJobType|None job_type=None)
Definition: event.py:543
None report_non_thread_safe_operation(str what)
Definition: frame.py:366
str async_suggest_report_issue(HomeAssistant|None hass, *Integration|None integration=None, str|None integration_domain=None, str|None module=None)
Definition: loader.py:1752
str ensure_unique_string(str preferred_string, Iterable[str]|KeysView[str] current_strings)
Definition: __init__.py:74