2016-11-19 16:18:33 +00:00
|
|
|
"""Helper methods to help with platform discovery.
|
|
|
|
|
|
|
|
There are two different types of discoveries that can be fired/listened for.
|
2017-06-08 13:53:12 +00:00
|
|
|
- listen/discover is for services. These are targeted at a component.
|
2016-11-19 16:18:33 +00:00
|
|
|
- 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.
|
2016-11-19 16:18:33 +00:00
|
|
|
"""
|
2024-03-08 15:36:11 +00:00
|
|
|
|
2021-03-17 17:34:19 +00:00
|
|
|
from __future__ import annotations
|
|
|
|
|
2022-08-09 20:12:33 +00:00
|
|
|
from collections.abc import Callable, Coroutine
|
2021-09-29 14:32:11 +00:00
|
|
|
from typing import Any, TypedDict
|
2019-12-21 07:23:48 +00:00
|
|
|
|
2019-12-09 15:42:10 +00:00
|
|
|
from homeassistant import core, setup
|
2022-01-09 05:06:56 +00:00
|
|
|
from homeassistant.const import Platform
|
2020-06-10 06:27:47 +00:00
|
|
|
from homeassistant.loader import bind_hass
|
2024-09-20 10:25:51 +00:00
|
|
|
from homeassistant.util.signal_type import SignalTypeFormat
|
2016-06-12 00:43:13 +00:00
|
|
|
|
2024-05-05 20:29:43 +00:00
|
|
|
from .dispatcher import async_dispatcher_connect, async_dispatcher_send_internal
|
2021-02-24 21:37:31 +00:00
|
|
|
from .typing import ConfigType, DiscoveryInfoType
|
|
|
|
|
2024-03-26 09:38:29 +00:00
|
|
|
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"
|
2021-02-24 21:37:31 +00:00
|
|
|
ATTR_DISCOVERED = "discovered"
|
2016-06-12 00:43:13 +00:00
|
|
|
|
|
|
|
|
2021-02-24 21:37:31 +00:00
|
|
|
class DiscoveryDict(TypedDict):
|
|
|
|
"""Discovery data."""
|
2016-06-12 00:43:13 +00:00
|
|
|
|
2021-02-24 21:37:31 +00:00
|
|
|
service: str
|
2021-03-17 17:34:19 +00:00
|
|
|
platform: str | None
|
|
|
|
discovered: DiscoveryInfoType | None
|
2016-11-18 22:35:08 +00:00
|
|
|
|
|
|
|
|
|
|
|
@core.callback
|
2017-10-08 15:17:54 +00:00
|
|
|
@bind_hass
|
2019-12-21 07:23:48 +00:00
|
|
|
def async_listen(
|
2021-02-08 10:59:46 +00:00
|
|
|
hass: core.HomeAssistant,
|
2021-02-24 21:37:31 +00:00
|
|
|
service: str,
|
2022-08-09 20:12:33 +00:00
|
|
|
callback: Callable[
|
|
|
|
[str, DiscoveryInfoType | None], Coroutine[Any, Any, None] | None
|
|
|
|
],
|
2019-12-21 07:23:48 +00:00
|
|
|
) -> None:
|
2017-05-02 16:18:47 +00:00
|
|
|
"""Set up listener for discovery of specific service.
|
2016-11-18 22:35:08 +00:00
|
|
|
|
2016-06-12 00:43:13 +00:00
|
|
|
Service can be a string or a list/tuple.
|
|
|
|
"""
|
2023-03-05 11:46:02 +00:00
|
|
|
job = core.HassJob(callback, f"discovery listener {service}")
|
2020-10-09 07:35:28 +00:00
|
|
|
|
2023-03-31 07:10:55 +00:00
|
|
|
@core.callback
|
|
|
|
def _async_discovery_event_listener(discovered: DiscoveryDict) -> None:
|
2016-06-12 00:43:13 +00:00
|
|
|
"""Listen for discovery events."""
|
2024-03-12 00:05:08 +00:00
|
|
|
hass.async_run_hass_job(job, discovered["service"], discovered["discovered"])
|
2016-06-12 00:43:13 +00:00
|
|
|
|
2021-02-24 21:37:31 +00:00
|
|
|
async_dispatcher_connect(
|
2023-03-31 07:10:55 +00:00
|
|
|
hass,
|
|
|
|
SIGNAL_PLATFORM_DISCOVERED.format(service),
|
|
|
|
_async_discovery_event_listener,
|
2021-02-24 21:37:31 +00:00
|
|
|
)
|
2016-06-12 00:43:13 +00:00
|
|
|
|
|
|
|
|
2017-10-08 15:17:54 +00:00
|
|
|
@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:
|
2016-06-12 00:43:13 +00:00
|
|
|
"""Fire discovery event. Can ensure a component is loaded."""
|
2023-04-05 06:41:15 +00:00
|
|
|
hass.create_task(
|
|
|
|
async_discover(hass, service, discovered, component, hass_config),
|
|
|
|
f"discover {service} {component} {discovered}",
|
|
|
|
)
|
2016-11-19 16:18:33 +00:00
|
|
|
|
|
|
|
|
2017-10-08 15:17:54 +00:00
|
|
|
@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:
|
2016-11-19 16:18:33 +00:00
|
|
|
"""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)
|
2016-06-12 00:43:13 +00:00
|
|
|
|
2021-02-24 21:37:31 +00:00
|
|
|
data: DiscoveryDict = {
|
|
|
|
"service": service,
|
|
|
|
"platform": None,
|
|
|
|
"discovered": discovered,
|
|
|
|
}
|
2016-06-12 00:43:13 +00:00
|
|
|
|
2024-05-05 20:29:43 +00:00
|
|
|
async_dispatcher_send_internal(
|
|
|
|
hass, SIGNAL_PLATFORM_DISCOVERED.format(service), data
|
|
|
|
)
|
2016-10-16 16:35:46 +00:00
|
|
|
|
|
|
|
|
2017-10-08 15:17:54 +00:00
|
|
|
@bind_hass
|
2019-12-21 07:23:48 +00:00
|
|
|
def async_listen_platform(
|
2020-03-18 17:27:25 +00:00
|
|
|
hass: core.HomeAssistant,
|
|
|
|
component: str,
|
2021-03-17 17:34:19 +00:00
|
|
|
callback: Callable[[str, dict[str, Any] | None], Any],
|
2022-03-28 06:53:30 +00:00
|
|
|
) -> Callable[[], None]:
|
2016-10-16 16:35:46 +00:00
|
|
|
"""Register a platform loader listener.
|
|
|
|
|
|
|
|
This method must be run in the event loop.
|
|
|
|
"""
|
2016-06-12 00:43:13 +00:00
|
|
|
service = EVENT_LOAD_PLATFORM.format(component)
|
2023-03-05 11:46:02 +00:00
|
|
|
job = core.HassJob(callback, f"platform loaded {component}")
|
2016-06-12 00:43:13 +00:00
|
|
|
|
2023-03-31 07:10:55 +00:00
|
|
|
@core.callback
|
|
|
|
def _async_discovery_platform_listener(discovered: DiscoveryDict) -> None:
|
2016-06-12 00:43:13 +00:00
|
|
|
"""Listen for platform discovery events."""
|
2021-10-17 18:08:11 +00:00
|
|
|
if not (platform := discovered["platform"]):
|
2016-06-12 00:43:13 +00:00
|
|
|
return
|
2024-03-12 00:05:08 +00:00
|
|
|
hass.async_run_hass_job(job, platform, discovered.get("discovered"))
|
2016-06-12 00:43:13 +00:00
|
|
|
|
2022-03-28 06:53:30 +00:00
|
|
|
return async_dispatcher_connect(
|
2023-03-31 07:10:55 +00:00
|
|
|
hass,
|
|
|
|
SIGNAL_PLATFORM_DISCOVERED.format(service),
|
|
|
|
_async_discovery_platform_listener,
|
2021-02-24 21:37:31 +00:00
|
|
|
)
|
2016-06-12 00:43:13 +00:00
|
|
|
|
|
|
|
|
2017-10-08 15:17:54 +00:00
|
|
|
@bind_hass
|
2020-04-17 18:33:58 +00:00
|
|
|
def load_platform(
|
|
|
|
hass: core.HomeAssistant,
|
2022-01-09 05:06:56 +00:00
|
|
|
component: Platform | str,
|
2020-04-17 18:33:58 +00:00
|
|
|
platform: str,
|
2022-01-10 10:14:31 +00:00
|
|
|
discovered: DiscoveryInfoType | None,
|
2020-04-17 18:33:58 +00:00
|
|
|
hass_config: ConfigType,
|
|
|
|
) -> None:
|
2021-02-24 21:37:31 +00:00
|
|
|
"""Load a component and platform dynamically."""
|
2023-04-05 06:41:15 +00:00
|
|
|
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
|
|
|
)
|
2016-10-29 19:54:47 +00:00
|
|
|
|
|
|
|
|
2017-10-08 15:17:54 +00:00
|
|
|
@bind_hass
|
2020-04-17 18:33:58 +00:00
|
|
|
async def async_load_platform(
|
|
|
|
hass: core.HomeAssistant,
|
2022-01-09 05:06:56 +00:00
|
|
|
component: Platform | str,
|
2020-04-17 18:33:58 +00:00
|
|
|
platform: str,
|
2022-01-10 10:14:31 +00:00
|
|
|
discovered: DiscoveryInfoType | None,
|
2020-04-17 18:33:58 +00:00
|
|
|
hass_config: ConfigType,
|
|
|
|
) -> None:
|
2016-10-29 19:54:47 +00:00
|
|
|
"""Load a component and platform dynamically.
|
|
|
|
|
2021-02-24 21:37:31 +00:00
|
|
|
Use `async_listen_platform` to register a callback for these events.
|
2016-10-29 19:54:47 +00:00
|
|
|
|
2024-04-04 02:53:31 +00:00
|
|
|
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.
|
2016-10-29 19:54:47 +00:00
|
|
|
"""
|
2022-04-07 21:24:47 +00:00
|
|
|
assert hass_config is not None, "You need to pass in the real hass config"
|
2018-10-29 18:21:21 +00:00
|
|
|
|
2016-11-19 16:18:33 +00:00
|
|
|
setup_success = True
|
2016-10-29 19:54:47 +00:00
|
|
|
|
2017-03-01 04:33:19 +00:00
|
|
|
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)
|
2016-06-12 00:43:13 +00:00
|
|
|
|
2021-02-24 21:37:31 +00:00
|
|
|
# No need to send signal if we could not set up component
|
2016-11-19 16:18:33 +00:00
|
|
|
if not setup_success:
|
2016-10-29 19:54:47 +00:00
|
|
|
return
|
2016-06-12 00:43:13 +00:00
|
|
|
|
2021-02-24 21:37:31 +00:00
|
|
|
service = EVENT_LOAD_PLATFORM.format(component)
|
2016-06-12 00:43:13 +00:00
|
|
|
|
2021-02-24 21:37:31 +00:00
|
|
|
data: DiscoveryDict = {
|
|
|
|
"service": service,
|
|
|
|
"platform": platform,
|
|
|
|
"discovered": discovered,
|
|
|
|
}
|
2016-07-26 05:49:10 +00:00
|
|
|
|
2024-05-05 20:29:43 +00:00
|
|
|
async_dispatcher_send_internal(
|
|
|
|
hass, SIGNAL_PLATFORM_DISCOVERED.format(service), data
|
|
|
|
)
|