hass-core/homeassistant/helpers/discovery.py

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

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-19 16:05:33 -08:00
components to allow discovery of their platforms.
"""
2021-03-17 18:34:19 +01: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_{}"
)
EVENT_LOAD_PLATFORM = "load_platform.{}"
ATTR_PLATFORM = "platform"
ATTR_DISCOVERED = "discovered"
class DiscoveryDict(TypedDict):
"""Discovery data."""
service: str
2021-03-17 18:34:19 +01: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-11 14:05:08 -10: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 21:33:58 +03: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 21:33:58 +03:00
async def async_discover(
hass: core.HomeAssistant,
service: str,
2021-03-17 18:34:19 +01:00
discovered: DiscoveryInfoType | None,
component: str | None,
2020-04-17 21:33:58 +03: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:
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 18:34:19 +01: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 20:08:11 +02: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-11 14:05:08 -10: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 21:33:58 +03:00
def load_platform(
hass: core.HomeAssistant,
component: Platform | str,
2020-04-17 21:33:58 +03:00
platform: str,
discovered: DiscoveryInfoType | None,
2020-04-17 21:33:58 +03: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}",
)
@bind_hass
2020-04-17 21:33:58 +03:00
async def async_load_platform(
hass: core.HomeAssistant,
component: Platform | str,
2020-04-17 21:33:58 +03:00
platform: str,
discovered: DiscoveryInfoType | None,
2020-04-17 21:33:58 +03: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:
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
)