ESPHome load platforms lazily (#24594)

pull/24607/head
Otto Winter 2019-06-18 17:41:45 +02:00 committed by GitHub
parent e669e1e2bf
commit ee5540f351
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 57 additions and 31 deletions

View File

@ -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

View File

@ -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)