1 """Support for esphome entities."""
3 from __future__
import annotations
5 from collections.abc
import Awaitable, Callable, Coroutine
8 from typing
import TYPE_CHECKING, Any, Concatenate, Generic, TypeVar, cast
10 from aioesphomeapi
import (
12 EntityCategory
as EsphomeEntityCategory,
17 import voluptuous
as vol
30 from .entry_data
import ESPHomeConfigEntry, RuntimeEntryData
31 from .enum_mapper
import EsphomeEnumMapper
33 _InfoT = TypeVar(
"_InfoT", bound=EntityInfo)
34 _EntityT = TypeVar(
"_EntityT", bound=
"EsphomeEntity[Any,Any]")
35 _StateT = TypeVar(
"_StateT", bound=EntityState)
41 entry_data: RuntimeEntryData,
42 platform: entity_platform.EntityPlatform,
43 async_add_entities: AddEntitiesCallback,
44 info_type: type[_InfoT],
45 entity_type: type[_EntityT],
46 state_type: type[_StateT],
47 infos: list[EntityInfo],
49 """Update entities of this platform when entities are listed."""
50 current_infos = entry_data.info[info_type]
51 new_infos: dict[int, EntityInfo] = {}
52 add_entities: list[_EntityT] = []
55 if not current_infos.pop(info.key,
None):
57 entity = entity_type(entry_data, platform.domain, info, state_type)
58 add_entities.append(entity)
59 new_infos[info.key] = info
63 device_info = entry_data.device_info
65 assert device_info
is not None
66 entry_data.async_remove_entities(
67 hass, current_infos.values(), device_info.mac_address
71 entry_data.info[info_type] = new_infos
74 entry_data.async_update_entity_infos(new_infos.values())
83 entry: ESPHomeConfigEntry,
84 async_add_entities: AddEntitiesCallback,
86 info_type: type[_InfoT],
87 entity_type: type[_EntityT],
88 state_type: type[_StateT],
90 """Set up an esphome platform.
92 This method is in charge of receiving, distributing and storing
93 info and state updates.
95 entry_data = entry.runtime_data
96 entry_data.info[info_type] = {}
97 platform = entity_platform.async_get_current_platform()
98 on_static_info_update = functools.partial(
99 async_static_info_updated,
108 entry_data.cleanup_callbacks.append(
109 entry_data.async_register_static_info_callback(
111 on_static_info_update,
116 def esphome_state_property[_R, _EntityT: EsphomeEntity[Any, Any]](
117 func: Callable[[_EntityT], _R],
118 ) -> Callable[[_EntityT], _R |
None]:
119 """Wrap a state property of an esphome entity.
121 This checks if the state object in the entity is set
122 and returns None if it is not set.
125 @functools.wraps(func)
126 def _wrapper(self: _EntityT) -> _R |
None:
127 return func(self)
if self._has_state
else None
132 def esphome_float_state_property[_EntityT: EsphomeEntity[Any, Any]](
133 func: Callable[[_EntityT], float |
None],
134 ) -> Callable[[_EntityT], float |
None]:
135 """Wrap a state property of an esphome entity that returns a float.
137 This checks if the state object in the entity is set, and returns
138 None if its not set. If also prevents writing NAN values to the
139 Home Assistant state machine.
142 @functools.wraps(func)
143 def _wrapper(self: _EntityT) -> float |
None:
144 if not self._has_state:
149 return None if val
is None or not math.isfinite(val)
else val
154 def convert_api_error_ha_error[**_P, _R, _EntityT: EsphomeEntity[Any, Any]](
155 func: Callable[Concatenate[_EntityT, _P], Awaitable[
None]],
156 ) -> Callable[Concatenate[_EntityT, _P], Coroutine[Any, Any,
None]]:
157 """Decorate ESPHome command calls that send commands/make changes to the device.
159 A decorator that wraps the passed in function, catches APIConnectionError errors,
160 and raises a HomeAssistant error instead.
163 async
def handler(self: _EntityT, *args: _P.args, **kwargs: _P.kwargs) ->
None:
165 return await func(self, *args, **kwargs)
166 except APIConnectionError
as error:
168 f
"Error communicating with device: {error}"
174 ICON_SCHEMA = vol.Schema(cv.icon)
177 ENTITY_CATEGORIES: EsphomeEnumMapper[EsphomeEntityCategory, EntityCategory |
None] = (
180 EsphomeEntityCategory.NONE:
None,
181 EsphomeEntityCategory.CONFIG: EntityCategory.CONFIG,
182 EsphomeEntityCategory.DIAGNOSTIC: EntityCategory.DIAGNOSTIC,
189 """Define a base esphome entity."""
191 _attr_should_poll =
False
198 entry_data: RuntimeEntryData,
200 entity_info: EntityInfo,
201 state_type: type[_StateT],
205 self.
_states_states = cast(dict[int, _StateT], entry_data.state[state_type])
206 assert entry_data.device_info
is not None
207 device_info = entry_data.device_info
210 self.
_key_key = entity_info.key
214 connections={(dr.CONNECTION_NETWORK_MAC, device_info.mac_address)}
231 if not device_info.friendly_name:
237 """Register callbacks."""
240 entry_data.async_subscribe_device_updated(
245 entry_data.async_subscribe_state_update(
250 entry_data.async_register_key_static_info_updated_callback(
258 """Save the static info for this entity when it changes.
260 This method can be overridden in child classes to know
261 when the static info changes.
263 device_info = self.
_entry_data_entry_data.device_info
265 static_info = cast(_InfoT, static_info)
268 self.
_attr_unique_id_attr_unique_id = build_unique_id(device_info.mac_address, static_info)
271 if entity_category := static_info.entity_category:
275 if icon := static_info.icon:
282 """Update state from entry data."""
284 if has_state := key
in self.
_states_states:
290 """Call when state changed.
292 Behavior can be changed in child classes
311 """Call when device updates or entry data changes."""
322 """Define a base entity for Assist Pipeline entities."""
324 _attr_has_entity_name =
True
325 _attr_should_poll =
False
327 def __init__(self, entry_data: RuntimeEntryData) ->
None:
328 """Initialize the binary sensor."""
329 self._entry_data: RuntimeEntryData = entry_data
330 assert entry_data.device_info
is not None
331 device_info = entry_data.device_info
334 f
"{device_info.mac_address}-{self.entity_description.key}"
337 connections={(dr.CONNECTION_NETWORK_MAC, device_info.mac_address)}
341 """Register update callback."""
344 self._entry_data.async_subscribe_assist_pipeline_update(
None __init__(self, RuntimeEntryData entry_data)
None async_added_to_hass(self)
None _on_static_info_update(self, EntityInfo static_info)
None __init__(self, RuntimeEntryData entry_data, str domain, EntityInfo entity_info, type[_StateT] state_type)
None _update_state_from_entry_data(self)
None async_added_to_hass(self)
None _on_entry_data_changed(self)
_attr_entity_registry_enabled_default
None _on_state_update(self)
None _on_device_update(self)
None async_write_ha_state(self)
None async_on_remove(self, CALLBACK_TYPE func)
None platform_async_setup_entry(HomeAssistant hass, ESPHomeConfigEntry entry, AddEntitiesCallback async_add_entities, *type[_InfoT] info_type, type[_EntityT] entity_type, type[_StateT] state_type)
None async_static_info_updated(HomeAssistant hass, RuntimeEntryData entry_data, entity_platform.EntityPlatform platform, AddEntitiesCallback async_add_entities, type[_InfoT] info_type, type[_EntityT] entity_type, type[_StateT] state_type, list[EntityInfo] infos)