mirror of
https://github.com/home-assistant/core.git
synced 2025-06-26 16:07:08 +00:00

* Adjust async_load_platform comment Its likely the deadlock here has been fixed for a long time, however we should still do these in a task because it has to wait for base components if they are not loaded yet. * Adjust async_load_platform comment Its likely the deadlock here has been fixed for a long time, however we should still do these in a task because it has to wait for base components if they are not loaded yet.
181 lines
5.3 KiB
Python
181 lines
5.3 KiB
Python
"""Helper methods to help with platform discovery.
|
|
|
|
There are two different types of discoveries that can be fired/listened for.
|
|
- listen/discover is for services. These are targeted at a component.
|
|
- listen_platform/discover_platform is for platforms. These are used by
|
|
components to allow discovery of their platforms.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Callable, Coroutine
|
|
from typing import Any, TypedDict
|
|
|
|
from homeassistant import core, setup
|
|
from homeassistant.const import Platform
|
|
from homeassistant.loader import bind_hass
|
|
|
|
from ..util.signal_type import SignalTypeFormat
|
|
from .dispatcher import async_dispatcher_connect, async_dispatcher_send
|
|
from .typing import ConfigType, DiscoveryInfoType
|
|
|
|
SIGNAL_PLATFORM_DISCOVERED: SignalTypeFormat[DiscoveryDict] = SignalTypeFormat(
|
|
"discovery.platform_discovered_{}"
|
|
)
|
|
EVENT_LOAD_PLATFORM = "load_platform.{}"
|
|
ATTR_PLATFORM = "platform"
|
|
ATTR_DISCOVERED = "discovered"
|
|
|
|
|
|
class DiscoveryDict(TypedDict):
|
|
"""Discovery data."""
|
|
|
|
service: str
|
|
platform: str | None
|
|
discovered: DiscoveryInfoType | None
|
|
|
|
|
|
@core.callback
|
|
@bind_hass
|
|
def async_listen(
|
|
hass: core.HomeAssistant,
|
|
service: str,
|
|
callback: Callable[
|
|
[str, DiscoveryInfoType | None], Coroutine[Any, Any, None] | None
|
|
],
|
|
) -> None:
|
|
"""Set up listener for discovery of specific service.
|
|
|
|
Service can be a string or a list/tuple.
|
|
"""
|
|
job = core.HassJob(callback, f"discovery listener {service}")
|
|
|
|
@core.callback
|
|
def _async_discovery_event_listener(discovered: DiscoveryDict) -> None:
|
|
"""Listen for discovery events."""
|
|
hass.async_run_hass_job(job, discovered["service"], discovered["discovered"])
|
|
|
|
async_dispatcher_connect(
|
|
hass,
|
|
SIGNAL_PLATFORM_DISCOVERED.format(service),
|
|
_async_discovery_event_listener,
|
|
)
|
|
|
|
|
|
@bind_hass
|
|
def discover(
|
|
hass: core.HomeAssistant,
|
|
service: str,
|
|
discovered: DiscoveryInfoType,
|
|
component: str,
|
|
hass_config: ConfigType,
|
|
) -> None:
|
|
"""Fire discovery event. Can ensure a component is loaded."""
|
|
hass.create_task(
|
|
async_discover(hass, service, discovered, component, hass_config),
|
|
f"discover {service} {component} {discovered}",
|
|
)
|
|
|
|
|
|
@bind_hass
|
|
async def async_discover(
|
|
hass: core.HomeAssistant,
|
|
service: str,
|
|
discovered: DiscoveryInfoType | None,
|
|
component: str | None,
|
|
hass_config: ConfigType,
|
|
) -> None:
|
|
"""Fire discovery event. Can ensure a component is loaded."""
|
|
if component is not None and component not in hass.config.components:
|
|
await setup.async_setup_component(hass, component, hass_config)
|
|
|
|
data: DiscoveryDict = {
|
|
"service": service,
|
|
"platform": None,
|
|
"discovered": discovered,
|
|
}
|
|
|
|
async_dispatcher_send(hass, SIGNAL_PLATFORM_DISCOVERED.format(service), data)
|
|
|
|
|
|
@bind_hass
|
|
def async_listen_platform(
|
|
hass: core.HomeAssistant,
|
|
component: str,
|
|
callback: Callable[[str, dict[str, Any] | None], Any],
|
|
) -> Callable[[], None]:
|
|
"""Register a platform loader listener.
|
|
|
|
This method must be run in the event loop.
|
|
"""
|
|
service = EVENT_LOAD_PLATFORM.format(component)
|
|
job = core.HassJob(callback, f"platform loaded {component}")
|
|
|
|
@core.callback
|
|
def _async_discovery_platform_listener(discovered: DiscoveryDict) -> None:
|
|
"""Listen for platform discovery events."""
|
|
if not (platform := discovered["platform"]):
|
|
return
|
|
hass.async_run_hass_job(job, platform, discovered.get("discovered"))
|
|
|
|
return async_dispatcher_connect(
|
|
hass,
|
|
SIGNAL_PLATFORM_DISCOVERED.format(service),
|
|
_async_discovery_platform_listener,
|
|
)
|
|
|
|
|
|
@bind_hass
|
|
def load_platform(
|
|
hass: core.HomeAssistant,
|
|
component: Platform | str,
|
|
platform: str,
|
|
discovered: DiscoveryInfoType | None,
|
|
hass_config: ConfigType,
|
|
) -> None:
|
|
"""Load a component and platform dynamically."""
|
|
hass.create_task(
|
|
async_load_platform(hass, component, platform, discovered, hass_config),
|
|
f"discovery load_platform {component} {platform}",
|
|
)
|
|
|
|
|
|
@bind_hass
|
|
async def async_load_platform(
|
|
hass: core.HomeAssistant,
|
|
component: Platform | str,
|
|
platform: str,
|
|
discovered: DiscoveryInfoType | None,
|
|
hass_config: ConfigType,
|
|
) -> None:
|
|
"""Load a component and platform dynamically.
|
|
|
|
Use `async_listen_platform` to register a callback for these events.
|
|
|
|
Warning: This method can load a base component if its not loaded which
|
|
can take a long time since base components currently have to import
|
|
every platform integration listed under it to do config validation.
|
|
To avoid waiting for this, use
|
|
`hass.async_create_task(async_load_platform(..))` instead.
|
|
"""
|
|
assert hass_config is not None, "You need to pass in the real hass config"
|
|
|
|
setup_success = True
|
|
|
|
if component not in hass.config.components:
|
|
setup_success = await setup.async_setup_component(hass, component, hass_config)
|
|
|
|
# No need to send signal if we could not set up component
|
|
if not setup_success:
|
|
return
|
|
|
|
service = EVENT_LOAD_PLATFORM.format(component)
|
|
|
|
data: DiscoveryDict = {
|
|
"service": service,
|
|
"platform": platform,
|
|
"discovered": discovered,
|
|
}
|
|
|
|
async_dispatcher_send(hass, SIGNAL_PLATFORM_DISCOVERED.format(service), data)
|