1 """Helpers for components that manage entities."""
3 from __future__
import annotations
6 from collections.abc
import Callable, Iterable
7 from datetime
import timedelta
9 from types
import ModuleType
10 from typing
import Any, Generic
12 from typing_extensions
import TypeVar
14 from homeassistant
import config
as conf_util
17 CONF_ENTITY_NAMESPACE,
19 EVENT_HOMEASSISTANT_STOP,
35 from .
import config_validation
as cv, discovery, entity, service
36 from .entity_platform
import EntityPlatform
37 from .typing
import ConfigType, DiscoveryInfoType, VolDictType, VolSchemaType
40 DATA_INSTANCES =
"entity_components"
47 """Trigger an update for an entity."""
48 domain = entity_id.partition(
".")[0]
50 entity_comp = hass.data.get(DATA_INSTANCES, {}).
get(domain)
52 if entity_comp
is None:
53 logging.getLogger(__name__).warning(
54 "Forced update failed. Component for %s not loaded.", entity_id
58 if (entity_obj := entity_comp.get_entity(entity_id))
is None:
59 logging.getLogger(__name__).warning(
60 "Forced update failed. Entity %s not found.", entity_id
64 await entity_obj.async_update_ha_state(
True)
68 """The EntityComponent manages platforms that manage entities.
70 An example of an entity component is 'light', which manages platforms such
73 This class has the following responsibilities:
74 - Process the configuration and set up a platform based component, for example light.
75 - Manage the platforms and their entities.
76 - Help extract the entities from a service call.
77 - Listen for discovery events for platforms related to the domain.
82 logger: logging.Logger,
85 scan_interval: timedelta = DEFAULT_SCAN_INTERVAL,
87 """Initialize an entity component."""
93 self.
configconfig: ConfigType |
None =
None
97 str | tuple[str, timedelta |
None, str |
None], EntityPlatform
98 ] = {domain: domain_platform}
101 self._entities: dict[str,
entity.Entity] = domain_platform.domain_entities
102 hass.data.setdefault(DATA_INSTANCES, {})[domain] = self
106 """Return an iterable that returns all entities.
108 As the underlying dicts may change when async context is lost,
109 callers that iterate over this asynchronously should make a copy
110 using list() before iterating.
112 return self._entities.values()
116 return self._entities.
get(entity_id)
119 """Register shutdown on Home Assistant STOP event.
121 Note: this is only required if the integration never calls
122 `setup` or `async_setup`.
124 self.
hasshass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.
_async_shutdown_async_shutdown)
126 def setup(self, config: ConfigType) ->
None:
127 """Set up a full entity component.
129 This doesn't block the executor to protect from deadlocks.
131 self.
hasshass.create_task(
132 self.
async_setupasync_setup(config), f
"EntityComponent setup {self.domain}"
136 """Set up a full entity component.
138 Loads the platforms from the config and will listen for supported
139 discovered platforms.
141 This method must be run in the event loop.
148 for p_type, p_config
in conf_util.config_per_platform(config, self.
domaindomain):
149 if p_type
is not None:
150 self.
hasshass.async_create_task_internal(
152 f
"EntityComponent setup platform {p_type} {self.domain}",
158 discovery.async_listen_platform(
163 self, platform: str, info: dict[str, Any] |
None
165 """Handle the loading of a platform."""
169 """Set up a config entry."""
170 platform_type = config_entry.domain
183 key = config_entry.entry_id
187 f
"Config entry {config_entry.title} ({key}) for "
188 f
"{platform_type}.{self.domain} has already been setup!"
194 scan_interval=getattr(platform,
"SCAN_INTERVAL",
None),
200 """Unload a config entry."""
201 key = config_entry.entry_id
203 if (platform := self.
_platforms_platforms.pop(key,
None))
is None:
204 raise ValueError(
"Config entry was never loaded!")
206 await platform.async_reset()
210 self, service_call: ServiceCall, expand_group: bool =
True
212 """Extract all known and available entities from a service call.
214 Will return an empty list if entities specified but unknown.
216 This method must be run in the event loop.
218 return await service.async_extract_entities(
219 self.
hasshass, self.
entitiesentities, service_call, expand_group
226 schema: VolDictType | VolSchemaType,
227 func: str | Callable[..., Any],
228 required_features: list[int] |
None =
None,
229 supports_response: SupportsResponse = SupportsResponse.NONE,
231 """Register an entity service with a legacy response format."""
232 if isinstance(schema, dict):
233 schema = cv.make_entity_service_schema(schema)
235 service_func: str | HassJob[..., Any]
236 service_func = func
if isinstance(func, str)
else HassJob(func)
238 async
def handle_service(
240 ) -> ServiceResponse:
241 """Handle the service."""
243 result = await service.entity_service_call(
244 self.
hasshass, self._entities, service_func, call, required_features
250 "Deprecated service call matched more than one entity"
252 return result.popitem()[1]
255 self.
hasshass.services.async_register(
256 self.
domaindomain, name, handle_service, schema, supports_response
263 schema: VolDictType | VolSchemaType |
None,
264 func: str | Callable[..., Any],
265 required_features: list[int] |
None =
None,
266 supports_response: SupportsResponse = SupportsResponse.NONE,
268 """Register an entity service."""
269 service.async_register_entity_service(
273 entities=self._entities,
275 job_type=HassJobType.Coroutinefunction,
276 required_features=required_features,
278 supports_response=supports_response,
284 platform_config: ConfigType,
285 discovery_info: DiscoveryInfoType |
None =
None,
287 """Set up a platform for this component."""
288 if self.
configconfig
is None:
289 raise RuntimeError(
"async_setup needs to be called first")
299 scan_interval = platform_config.get(
300 CONF_SCAN_INTERVAL, getattr(platform,
"SCAN_INTERVAL",
None)
302 entity_namespace = platform_config.get(CONF_ENTITY_NAMESPACE)
304 key = (platform_type, scan_interval, entity_namespace)
308 platform_type, platform, scan_interval, entity_namespace
314 """Remove entities and reset the entity component to initial values.
316 This method must be run in the event loop.
320 for key, platform
in self.
_platforms_platforms.items():
321 if key == self.
domaindomain:
322 tasks.append(platform.async_reset())
324 tasks.append(platform.async_destroy())
327 await asyncio.gather(*tasks)
333 """Remove an entity managed by one of the platforms."""
336 for platform
in self.
_platforms_platforms.values():
337 if entity_id
in platform.entities:
342 await found.async_remove_entity(entity_id)
345 self, *, skip_reset: bool =
False
346 ) -> ConfigType |
None:
347 """Prepare reloading this entity component.
349 This method must be run in the event loop.
352 conf = await conf_util.async_hass_config_yaml(self.
hasshass)
353 except HomeAssistantError
as err:
354 self.
loggerlogger.error(err)
359 processed_conf = await conf_util.async_process_component_and_handle_errors(
360 self.
hasshass, conf, integration
363 if processed_conf
is None:
369 return processed_conf
375 platform: ModuleType |
None,
376 scan_interval: timedelta |
None =
None,
377 entity_namespace: str |
None =
None,
379 """Initialize an entity platform."""
380 if scan_interval
is None:
387 platform_name=platform_type,
389 scan_interval=scan_interval,
390 entity_namespace=entity_namespace,
392 entity_platform.async_prepare()
393 return entity_platform
397 """Call when Home Assistant is stopping."""
398 for platform
in self.
_platforms_platforms.values():
399 platform.async_shutdown()
None setup(self, ConfigType config)
list[_EntityT] async_extract_from_service(self, ServiceCall service_call, bool expand_group=True)
Iterable[_EntityT] entities(self)
EntityPlatform _async_init_entity_platform(self, str platform_type, ModuleType|None platform, timedelta|None scan_interval=None, str|None entity_namespace=None)
_EntityT|None get_entity(self, str entity_id)
None __init__(self, logging.Logger logger, str domain, HomeAssistant hass, timedelta scan_interval=DEFAULT_SCAN_INTERVAL)
None async_setup_platform(self, str platform_type, ConfigType platform_config, DiscoveryInfoType|None discovery_info=None)
None _async_shutdown(self, Event event)
None _async_component_platform_discovered(self, str platform, dict[str, Any]|None info)
None async_remove_entity(self, str entity_id)
bool async_unload_entry(self, ConfigEntry config_entry)
None async_setup(self, ConfigType config)
ConfigType|None async_prepare_reload(self, *bool skip_reset=False)
bool async_setup_entry(self, ConfigEntry config_entry)
None async_register_legacy_entity_service(self, str name, VolDictType|VolSchemaType schema, str|Callable[..., Any] func, list[int]|None required_features=None, SupportsResponse supports_response=SupportsResponse.NONE)
None register_shutdown(self)
None async_register_entity_service(self, str name, VolDictType|VolSchemaType|None schema, str|Callable[..., Any] func, list[int]|None required_features=None, SupportsResponse supports_response=SupportsResponse.NONE)
web.Response get(self, web.Request request, str config_key)
None async_update_entity(HomeAssistant hass, str entity_id)
Integration async_get_integration(HomeAssistant hass, str domain)
ModuleType|None async_prepare_setup_platform(core.HomeAssistant hass, ConfigType hass_config, str domain, str platform_name)