ESPHome load platforms lazily (#24594)
parent
e669e1e2bf
commit
ee5540f351
|
@ -39,18 +39,6 @@ _LOGGER = logging.getLogger(__name__)
|
||||||
STORAGE_KEY = 'esphome.{}'
|
STORAGE_KEY = 'esphome.{}'
|
||||||
STORAGE_VERSION = 1
|
STORAGE_VERSION = 1
|
||||||
|
|
||||||
# The HA component types this integration supports
|
|
||||||
HA_COMPONENTS = [
|
|
||||||
'binary_sensor',
|
|
||||||
'camera',
|
|
||||||
'climate',
|
|
||||||
'cover',
|
|
||||||
'fan',
|
|
||||||
'light',
|
|
||||||
'sensor',
|
|
||||||
'switch',
|
|
||||||
]
|
|
||||||
|
|
||||||
# No config schema - only configuration entry
|
# No config schema - only configuration entry
|
||||||
CONFIG_SCHEMA = vol.Schema({}, extra=vol.ALLOW_EXTRA)
|
CONFIG_SCHEMA = vol.Schema({}, extra=vol.ALLOW_EXTRA)
|
||||||
|
|
||||||
|
@ -144,7 +132,8 @@ async def async_setup_entry(hass: HomeAssistantType,
|
||||||
entry_data.async_update_device_state(hass)
|
entry_data.async_update_device_state(hass)
|
||||||
|
|
||||||
entity_infos, services = await cli.list_entities_services()
|
entity_infos, services = await cli.list_entities_services()
|
||||||
entry_data.async_update_static_infos(hass, entity_infos)
|
await entry_data.async_update_static_infos(
|
||||||
|
hass, entry, entity_infos)
|
||||||
await _setup_services(hass, entry_data, services)
|
await _setup_services(hass, entry_data, services)
|
||||||
await cli.subscribe_states(async_on_state)
|
await cli.subscribe_states(async_on_state)
|
||||||
await cli.subscribe_service_calls(async_on_service_call)
|
await cli.subscribe_service_calls(async_on_service_call)
|
||||||
|
@ -162,14 +151,8 @@ async def async_setup_entry(hass: HomeAssistantType,
|
||||||
|
|
||||||
async def complete_setup() -> None:
|
async def complete_setup() -> None:
|
||||||
"""Complete the config entry setup."""
|
"""Complete the config entry setup."""
|
||||||
tasks = []
|
|
||||||
for component in HA_COMPONENTS:
|
|
||||||
tasks.append(hass.config_entries.async_forward_entry_setup(
|
|
||||||
entry, component))
|
|
||||||
await asyncio.wait(tasks)
|
|
||||||
|
|
||||||
infos, services = await entry_data.async_load_from_store()
|
infos, services = await entry_data.async_load_from_store()
|
||||||
entry_data.async_update_static_infos(hass, infos)
|
await entry_data.async_update_static_infos(hass, entry, infos)
|
||||||
await _setup_services(hass, entry_data, services)
|
await _setup_services(hass, entry_data, services)
|
||||||
|
|
||||||
# Create connection attempt outside of HA's tracked task in order
|
# Create connection attempt outside of HA's tracked task in order
|
||||||
|
@ -308,7 +291,7 @@ async def _setup_services(hass: HomeAssistantType,
|
||||||
|
|
||||||
|
|
||||||
async def _cleanup_instance(hass: HomeAssistantType,
|
async def _cleanup_instance(hass: HomeAssistantType,
|
||||||
entry: ConfigEntry) -> None:
|
entry: ConfigEntry) -> RuntimeEntryData:
|
||||||
"""Cleanup the esphome client if it exists."""
|
"""Cleanup the esphome client if it exists."""
|
||||||
data = hass.data[DATA_KEY].pop(entry.entry_id) # type: RuntimeEntryData
|
data = hass.data[DATA_KEY].pop(entry.entry_id) # type: RuntimeEntryData
|
||||||
if data.reconnect_task is not None:
|
if data.reconnect_task is not None:
|
||||||
|
@ -318,19 +301,19 @@ async def _cleanup_instance(hass: HomeAssistantType,
|
||||||
for cleanup_callback in data.cleanup_callbacks:
|
for cleanup_callback in data.cleanup_callbacks:
|
||||||
cleanup_callback()
|
cleanup_callback()
|
||||||
await data.client.disconnect()
|
await data.client.disconnect()
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
async def async_unload_entry(hass: HomeAssistantType,
|
async def async_unload_entry(hass: HomeAssistantType,
|
||||||
entry: ConfigEntry) -> bool:
|
entry: ConfigEntry) -> bool:
|
||||||
"""Unload an esphome config entry."""
|
"""Unload an esphome config entry."""
|
||||||
await _cleanup_instance(hass, entry)
|
entry_data = await _cleanup_instance(hass, entry)
|
||||||
|
|
||||||
tasks = []
|
tasks = []
|
||||||
for component in HA_COMPONENTS:
|
for platform in entry_data.loaded_platforms:
|
||||||
tasks.append(hass.config_entries.async_forward_entry_unload(
|
tasks.append(hass.config_entries.async_forward_entry_unload(
|
||||||
entry, component))
|
entry, platform))
|
||||||
await asyncio.wait(tasks)
|
if tasks:
|
||||||
|
await asyncio.wait(tasks)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
"""Runtime entry data for ESPHome stored in hass.data."""
|
"""Runtime entry data for ESPHome stored in hass.data."""
|
||||||
import asyncio
|
import asyncio
|
||||||
from typing import Any, Callable, Dict, List, Optional, Tuple
|
from typing import Any, Callable, Dict, List, Optional, Tuple, Set
|
||||||
|
|
||||||
from aioesphomeapi import (
|
from aioesphomeapi import (
|
||||||
COMPONENT_TYPE_TO_INFO, DeviceInfo, EntityInfo, EntityState, UserService)
|
COMPONENT_TYPE_TO_INFO, DeviceInfo, EntityInfo, EntityState, UserService,
|
||||||
|
BinarySensorInfo,
|
||||||
|
CameraInfo, ClimateInfo, CoverInfo, FanInfo, LightInfo, SensorInfo,
|
||||||
|
SwitchInfo, TextSensorInfo)
|
||||||
import attr
|
import attr
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||||
from homeassistant.helpers.storage import Store
|
from homeassistant.helpers.storage import Store
|
||||||
from homeassistant.helpers.typing import HomeAssistantType
|
from homeassistant.helpers.typing import HomeAssistantType
|
||||||
|
@ -17,6 +21,19 @@ DISPATCHER_ON_LIST = 'esphome_{entry_id}_on_list'
|
||||||
DISPATCHER_ON_DEVICE_UPDATE = 'esphome_{entry_id}_on_device_update'
|
DISPATCHER_ON_DEVICE_UPDATE = 'esphome_{entry_id}_on_device_update'
|
||||||
DISPATCHER_ON_STATE = 'esphome_{entry_id}_on_state'
|
DISPATCHER_ON_STATE = 'esphome_{entry_id}_on_state'
|
||||||
|
|
||||||
|
# Mapping from ESPHome info type to HA platform
|
||||||
|
INFO_TYPE_TO_PLATFORM = {
|
||||||
|
BinarySensorInfo: 'binary_sensor',
|
||||||
|
CameraInfo: 'camera',
|
||||||
|
ClimateInfo: 'climate',
|
||||||
|
CoverInfo: 'cover',
|
||||||
|
FanInfo: 'fan',
|
||||||
|
LightInfo: 'light',
|
||||||
|
SensorInfo: 'sensor',
|
||||||
|
SwitchInfo: 'switch',
|
||||||
|
TextSensorInfo: 'sensor',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@attr.s
|
@attr.s
|
||||||
class RuntimeEntryData:
|
class RuntimeEntryData:
|
||||||
|
@ -33,6 +50,8 @@ class RuntimeEntryData:
|
||||||
device_info = attr.ib(type=DeviceInfo, default=None)
|
device_info = attr.ib(type=DeviceInfo, default=None)
|
||||||
cleanup_callbacks = attr.ib(type=List[Callable[[], None]], factory=list)
|
cleanup_callbacks = attr.ib(type=List[Callable[[], None]], factory=list)
|
||||||
disconnect_callbacks = attr.ib(type=List[Callable[[], None]], factory=list)
|
disconnect_callbacks = attr.ib(type=List[Callable[[], None]], factory=list)
|
||||||
|
loaded_platforms = attr.ib(type=Set[str], factory=set)
|
||||||
|
platform_load_lock = attr.ib(type=asyncio.Lock, factory=asyncio.Lock)
|
||||||
|
|
||||||
def async_update_entity(self, hass: HomeAssistantType, component_key: str,
|
def async_update_entity(self, hass: HomeAssistantType, component_key: str,
|
||||||
key: int) -> None:
|
key: int) -> None:
|
||||||
|
@ -48,9 +67,33 @@ class RuntimeEntryData:
|
||||||
entry_id=self.entry_id, component_key=component_key, key=key)
|
entry_id=self.entry_id, component_key=component_key, key=key)
|
||||||
async_dispatcher_send(hass, signal)
|
async_dispatcher_send(hass, signal)
|
||||||
|
|
||||||
def async_update_static_infos(self, hass: HomeAssistantType,
|
async def _ensure_platforms_loaded(self, hass: HomeAssistantType,
|
||||||
infos: List[EntityInfo]) -> None:
|
entry: ConfigEntry,
|
||||||
|
platforms: Set[str]):
|
||||||
|
async with self.platform_load_lock:
|
||||||
|
needed = platforms - self.loaded_platforms
|
||||||
|
tasks = []
|
||||||
|
for platform in needed:
|
||||||
|
tasks.append(hass.config_entries.async_forward_entry_setup(
|
||||||
|
entry, platform))
|
||||||
|
if tasks:
|
||||||
|
await asyncio.wait(tasks)
|
||||||
|
self.loaded_platforms |= needed
|
||||||
|
|
||||||
|
async def async_update_static_infos(
|
||||||
|
self, hass: HomeAssistantType, entry: ConfigEntry,
|
||||||
|
infos: List[EntityInfo]) -> None:
|
||||||
"""Distribute an update of static infos to all platforms."""
|
"""Distribute an update of static infos to all platforms."""
|
||||||
|
# First, load all platforms
|
||||||
|
needed_platforms = set()
|
||||||
|
for info in infos:
|
||||||
|
for info_type, platform in INFO_TYPE_TO_PLATFORM.items():
|
||||||
|
if isinstance(info, info_type):
|
||||||
|
needed_platforms.add(platform)
|
||||||
|
break
|
||||||
|
await self._ensure_platforms_loaded(hass, entry, needed_platforms)
|
||||||
|
|
||||||
|
# Then send dispatcher event
|
||||||
signal = DISPATCHER_ON_LIST.format(entry_id=self.entry_id)
|
signal = DISPATCHER_ON_LIST.format(entry_id=self.entry_id)
|
||||||
async_dispatcher_send(hass, signal, infos)
|
async_dispatcher_send(hass, signal, infos)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue