"""The Yandex Smart Home component.""" from __future__ import annotations import logging from typing import TYPE_CHECKING, Any, cast from homeassistant.config_entries import ConfigEntry, ConfigEntryState from homeassistant.const import CONF_ID, CONF_PLATFORM, CONF_TOKEN, SERVICE_RELOAD from homeassistant.core import HomeAssistant from homeassistant.helpers.entityfilter import FILTER_SCHEMA, EntityFilter from homeassistant.helpers.reload import async_integration_yaml_config from homeassistant.helpers.service import async_register_admin_service from homeassistant.helpers.typing import ConfigType import voluptuous as vol from .config_schema import YANDEX_SMART_HOME_SCHEMA from .const import ( CONF_CLOUD_INSTANCE, CONF_CONNECTION_TYPE, CONF_DEVICES_DISCOVERED, CONF_ENTITY_CONFIG, CONF_FILTER, CONF_FILTER_SOURCE, CONF_LINKED_PLATFORMS, CONF_NOTIFIER, CONF_NOTIFIER_OAUTH_TOKEN, CONF_NOTIFIER_SKILL_ID, CONF_NOTIFIER_USER_ID, CONF_SKILL, CONF_USER_ID, DOMAIN, ConnectionType, EntityFilterSource, ) from .entry_data import ConfigEntryData from .helpers import SmartHomePlatform from .http import async_register_http from .repairs import delete_unexposed_entity_found_issues if TYPE_CHECKING: from .cloud_stream import CloudStreamManager _LOGGER = logging.getLogger(__name__) CONFIG_SCHEMA = vol.Schema({DOMAIN: YANDEX_SMART_HOME_SCHEMA}, extra=vol.ALLOW_EXTRA) class YandexSmartHome: """Yandex Smart Home component main class.""" def __init__(self, hass: HomeAssistant, yaml_config: ConfigType): """Initialize the Yandex Smart Home from yaml configuration.""" self.cloud_streams: dict[str, CloudStreamManager] = {} self._hass = hass self._yaml_config = yaml_config self._entry_datas: dict[str, ConfigEntryData] = {} async_register_admin_service(hass, DOMAIN, SERVICE_RELOAD, self._handle_yaml_config_reload) async def _handle_yaml_config_reload(self, _: Any) -> None: """Handle yaml configuration reloading.""" if config := await async_integration_yaml_config(self._hass, DOMAIN): self._yaml_config = config.get(DOMAIN, {}) for entry in self._hass.config_entries.async_entries(DOMAIN): await _async_entry_update_listener(self._hass, entry) return None def get_entry_data(self, entry: ConfigEntry) -> ConfigEntryData: """Return a config entry data for a config entry.""" return self._entry_datas[entry.entry_id] def get_direct_connection_entry_data( self, platform: SmartHomePlatform, user_id: str | None ) -> ConfigEntryData | None: """Return a config entry data with direct connection config entry.""" for data in self._entry_datas.values(): if ( data.connection_type == ConnectionType.DIRECT and data.entry.state == ConfigEntryState.LOADED and data.platform == platform ): if user_id and data.skill and data.skill.user_id == user_id: return data if not user_id: return data return None def get_diagnostics(self) -> ConfigType: """Return diagnostics for the component.""" from homeassistant.components.diagnostics import async_redact_data return {"yaml_config": async_redact_data(self._yaml_config, [CONF_NOTIFIER])} def get_entity_filter_from_yaml(self) -> EntityFilter | None: """Return entity filter from yaml configuration.""" if entity_filter_config := self._yaml_config.get(CONF_FILTER): return cast(EntityFilter, FILTER_SCHEMA(entity_filter_config)) return None async def async_setup_entry(self, entry: ConfigEntry) -> bool: """Set up a config entry.""" entity_config = self._yaml_config.get(CONF_ENTITY_CONFIG) entity_filter: EntityFilter | None = None if entry.options.get(CONF_FILTER_SOURCE) == EntityFilterSource.YAML: entity_filter = self.get_entity_filter_from_yaml() else: entity_filter = FILTER_SCHEMA(entry.options.get(CONF_FILTER, {})) data = ConfigEntryData( hass=self._hass, entry=entry, yaml_config=self._yaml_config, entity_config=entity_config, entity_filter=entity_filter, ) self._entry_datas[entry.entry_id] = await data.async_setup() entry.async_on_unload(entry.add_update_listener(_async_entry_update_listener)) delete_unexposed_entity_found_issues(self._hass) return True async def async_unload_entry(self, entry: ConfigEntry) -> bool: """Unload a config entry.""" delete_unexposed_entity_found_issues(self._hass) data = self.get_entry_data(entry) await data.async_unload() return True async def async_remove_entry(self, entry: ConfigEntry) -> None: """Remove a config entry.""" try: del self._entry_datas[entry.entry_id] except KeyError: pass return None async def async_setup(hass: HomeAssistant, yaml_config: ConfigType) -> bool: """Activate Yandex Smart Home component.""" hass.data[DOMAIN] = component = YandexSmartHome(hass, yaml_config.get(DOMAIN, {})) async_register_http(hass, component) return True async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up a config entry.""" component: YandexSmartHome = hass.data[DOMAIN] return await component.async_setup_entry(entry) async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" component: YandexSmartHome = hass.data[DOMAIN] return await component.async_unload_entry(entry) async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Migrate the config entry upon new versions.""" version = entry.version component: YandexSmartHome = hass.data[DOMAIN] data: ConfigType = {**entry.data} options: ConfigType = {**entry.options} _LOGGER.debug(f"Migrating from version {version}") if version == 1: preserve_keys = [ CONF_CONNECTION_TYPE, CONF_CLOUD_INSTANCE, CONF_DEVICES_DISCOVERED, CONF_FILTER, CONF_USER_ID, ] for store in [data, options]: for key in list(store.keys()): if key not in preserve_keys: store.pop(key, None) data.setdefault(CONF_CONNECTION_TYPE, ConnectionType.DIRECT) data.setdefault(CONF_DEVICES_DISCOVERED, True) version = 2 hass.config_entries.async_update_entry(entry, data=data, options=options, version=version) _LOGGER.debug(f"Migration to version {version} successful") if version == 2: version = 3 _LOGGER.debug(f"Migration to version {version} successful") if version == 3: options[CONF_FILTER_SOURCE] = EntityFilterSource.CONFIG_ENTRY if CONF_FILTER in component._yaml_config: options[CONF_FILTER_SOURCE] = EntityFilterSource.YAML version = 4 hass.config_entries.async_update_entry(entry, data=data, options=options, version=version) _LOGGER.debug(f"Migration to version {version} successful") if version == 4: from .config_flow import DEFAULT_CONFIG_ENTRY_TITLE, PRE_V1_DIRECT_CONFIG_ENTRY_TITLE, async_config_entry_title title = entry.title data.setdefault(CONF_PLATFORM, SmartHomePlatform.YANDEX) if len(hass.config_entries.async_entries(DOMAIN)) == 1 and data[CONF_CONNECTION_TYPE] == ConnectionType.DIRECT: for notifier_config in component._yaml_config.get(CONF_NOTIFIER, []): options.setdefault( CONF_SKILL, { CONF_USER_ID: notifier_config[CONF_NOTIFIER_USER_ID], CONF_ID: notifier_config[CONF_NOTIFIER_SKILL_ID], CONF_TOKEN: notifier_config[CONF_NOTIFIER_OAUTH_TOKEN], }, ) break if entry.title in (DEFAULT_CONFIG_ENTRY_TITLE, PRE_V1_DIRECT_CONFIG_ENTRY_TITLE): title = await async_config_entry_title(hass, data, options) version = 5 hass.config_entries.async_update_entry( entry, title=title, data=data, options=options, version=version, ) _LOGGER.debug(f"Migration to version {version} successful") if version == 5: if data.get(CONF_DEVICES_DISCOVERED): data[CONF_LINKED_PLATFORMS] = [SmartHomePlatform.YANDEX] version = 6 hass.config_entries.async_update_entry(entry, data=data, version=version) _LOGGER.debug(f"Migration to version {version} successful") return True async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None: """Remove a config entry.""" component: YandexSmartHome | None = hass.data.get(DOMAIN) if component: await component.async_remove_entry(entry) return None async def _async_entry_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: """Handle config entry options update.""" await hass.config_entries.async_reload(entry.entry_id) return None