core/homeassistant/helpers/discovery.py

185 lines
5.3 KiB
Python
Raw Permalink Normal View History

"""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
2016-11-20 00:05:33 +00:00
components to allow discovery of their platforms.
"""
2021-03-17 17:34:19 +00:00
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 homeassistant.util.signal_type import SignalTypeFormat
from .dispatcher import async_dispatcher_connect, async_dispatcher_send_internal
from .typing import ConfigType, DiscoveryInfoType
SIGNAL_PLATFORM_DISCOVERED: SignalTypeFormat[DiscoveryDict] = SignalTypeFormat(
"discovery.platform_discovered_{}"
)
2019-07-31 19:25:30 +00:00
EVENT_LOAD_PLATFORM = "load_platform.{}"
ATTR_PLATFORM = "platform"
ATTR_DISCOVERED = "discovered"
class DiscoveryDict(TypedDict):
"""Discovery data."""
service: str
2021-03-17 17:34:19 +00:00
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."""
Run coroutines as eager tasks in async_run_hass_job (#111683) * Run coroutines as eager tasks in async_run_hass_job Note that this does not change async_add_hass_job Do not merge this. For test run only * Phase out periodic tasks * false by default or some tests will block forever, will need to fix each one manually * kwarg works * kwarg works * kwarg works * fixes * fix more tests * fix more tests * fix lifx * opensky * pvpc_hourly_pricing * adjust more * adjust more * smarttub * adjust more * adjust more * adjust more * adjust more * adjust * no eager executor * zha * qnap_qsw * fix more * fix fix * docs * its a wrapper now * add more coverage * coverage * cover all combos * more fixes * more fixes * more fixes * remaining issues are legit bugs in tests * make tplink test more predictable * more fixes * feedreader * grind out some more * make test race safe * limit first scope to triggers * one more * Start tasks eagerly in for async_at_start(ed) A few of these can avoid being scheduled on the loop during startup * fix cloud * Revert "fix cloud" This reverts commit 5eb3ce695da788bcae649f82c9527c0f9307139c. * fix test to do what start does * flip flag * flip flag * Fix here_travel_time creating many refresh requests at startup - Each entity would try to refresh the coordinator which created many tasks. Move the refresh to a single async_at_started - The tests fired the EVENT_HOMEASSISTANT_START event but the code used async_at_started which only worked because the tests did not set CoreState to not_running * fix azure * remove kw * remove kw * rip * cover * more rips * more rips * more rips
2024-03-12 00:05:08 +00:00
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
2020-04-17 18:33:58 +00:00
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
2020-04-17 18:33:58 +00:00
async def async_discover(
hass: core.HomeAssistant,
service: str,
2021-03-17 17:34:19 +00:00
discovered: DiscoveryInfoType | None,
component: str | None,
2020-04-17 18:33:58 +00:00
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:
2019-07-31 19:25:30 +00:00
await setup.async_setup_component(hass, component, hass_config)
data: DiscoveryDict = {
"service": service,
"platform": None,
"discovered": discovered,
}
async_dispatcher_send_internal(
hass, SIGNAL_PLATFORM_DISCOVERED.format(service), data
)
@bind_hass
def async_listen_platform(
hass: core.HomeAssistant,
component: str,
2021-03-17 17:34:19 +00:00
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."""
2021-10-17 18:08:11 +00:00
if not (platform := discovered["platform"]):
return
Run coroutines as eager tasks in async_run_hass_job (#111683) * Run coroutines as eager tasks in async_run_hass_job Note that this does not change async_add_hass_job Do not merge this. For test run only * Phase out periodic tasks * false by default or some tests will block forever, will need to fix each one manually * kwarg works * kwarg works * kwarg works * fixes * fix more tests * fix more tests * fix lifx * opensky * pvpc_hourly_pricing * adjust more * adjust more * smarttub * adjust more * adjust more * adjust more * adjust more * adjust * no eager executor * zha * qnap_qsw * fix more * fix fix * docs * its a wrapper now * add more coverage * coverage * cover all combos * more fixes * more fixes * more fixes * remaining issues are legit bugs in tests * make tplink test more predictable * more fixes * feedreader * grind out some more * make test race safe * limit first scope to triggers * one more * Start tasks eagerly in for async_at_start(ed) A few of these can avoid being scheduled on the loop during startup * fix cloud * Revert "fix cloud" This reverts commit 5eb3ce695da788bcae649f82c9527c0f9307139c. * fix test to do what start does * flip flag * flip flag * Fix here_travel_time creating many refresh requests at startup - Each entity would try to refresh the coordinator which created many tasks. Move the refresh to a single async_at_started - The tests fired the EVENT_HOMEASSISTANT_START event but the code used async_at_started which only worked because the tests did not set CoreState to not_running * fix azure * remove kw * remove kw * rip * cover * more rips * more rips * more rips
2024-03-12 00:05:08 +00:00
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
2020-04-17 18:33:58 +00:00
def load_platform(
hass: core.HomeAssistant,
component: Platform | str,
2020-04-17 18:33:58 +00:00
platform: str,
discovered: DiscoveryInfoType | None,
2020-04-17 18:33:58 +00:00
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}",
2019-07-31 19:25:30 +00:00
)
@bind_hass
2020-04-17 18:33:58 +00:00
async def async_load_platform(
hass: core.HomeAssistant,
component: Platform | str,
2020-04-17 18:33:58 +00:00
platform: str,
discovered: DiscoveryInfoType | None,
2020-04-17 18:33:58 +00:00
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:
2019-07-31 19:25:30 +00:00
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_internal(
hass, SIGNAL_PLATFORM_DISCOVERED.format(service), data
)