"""Config flow for Vera.""" from __future__ import annotations from collections.abc import Mapping import logging import re from typing import Any import pyvera as pv from requests.exceptions import RequestException import voluptuous as vol from homeassistant import config_entries from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_EXCLUDE, CONF_LIGHTS, CONF_SOURCE from homeassistant.core import callback from homeassistant.helpers.entity_registry import EntityRegistry from .const import CONF_CONTROLLER, CONF_LEGACY_UNIQUE_ID, DOMAIN LIST_REGEX = re.compile("[^0-9]+") _LOGGER = logging.getLogger(__name__) def fix_device_id_list(data: list[Any]) -> list[int]: """Fix the id list by converting it to a supported int list.""" return str_to_int_list(list_to_str(data)) def str_to_int_list(data: str) -> list[int]: """Convert a string to an int list.""" return [int(s) for s in LIST_REGEX.split(data) if len(s) > 0] def list_to_str(data: list[Any]) -> str: """Convert an int list to a string.""" return " ".join([str(i) for i in data]) def new_options(lights: list[int], exclude: list[int]) -> dict: """Create a standard options object.""" return {CONF_LIGHTS: lights, CONF_EXCLUDE: exclude} def options_schema(options: Mapping[str, Any] = None) -> dict: """Return options schema.""" options = options or {} return { vol.Optional( CONF_LIGHTS, default=list_to_str(options.get(CONF_LIGHTS, [])), ): str, vol.Optional( CONF_EXCLUDE, default=list_to_str(options.get(CONF_EXCLUDE, [])), ): str, } def options_data(user_input: dict) -> dict: """Return options dict.""" return new_options( str_to_int_list(user_input.get(CONF_LIGHTS, "")), str_to_int_list(user_input.get(CONF_EXCLUDE, "")), ) class OptionsFlowHandler(config_entries.OptionsFlow): """Options for the component.""" def __init__(self, config_entry: ConfigEntry) -> None: """Init object.""" self.config_entry = config_entry async def async_step_init(self, user_input: dict = None): """Manage the options.""" if user_input is not None: return self.async_create_entry( title="", data=options_data(user_input), ) return self.async_show_form( step_id="init", data_schema=vol.Schema(options_schema(self.config_entry.options)), ) class VeraFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Vera config flow.""" @staticmethod @callback def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlowHandler: """Get the options flow.""" return OptionsFlowHandler(config_entry) async def async_step_user(self, user_input: dict = None): """Handle user initiated flow.""" if user_input is not None: return await self.async_step_finish( { **user_input, **options_data(user_input), **{CONF_SOURCE: config_entries.SOURCE_USER}, **{CONF_LEGACY_UNIQUE_ID: False}, } ) return self.async_show_form( step_id="user", data_schema=vol.Schema( {**{vol.Required(CONF_CONTROLLER): str}, **options_schema()} ), ) async def async_step_import(self, config: dict): """Handle a flow initialized by import.""" # If there are entities with the legacy unique_id, then this imported config # should also use the legacy unique_id for entity creation. entity_registry: EntityRegistry = ( await self.hass.helpers.entity_registry.async_get_registry() ) use_legacy_unique_id = ( len( [ entry for entry in entity_registry.entities.values() if entry.platform == DOMAIN and entry.unique_id.isdigit() ] ) > 0 ) return await self.async_step_finish( { **config, **{CONF_SOURCE: config_entries.SOURCE_IMPORT}, **{CONF_LEGACY_UNIQUE_ID: use_legacy_unique_id}, } ) async def async_step_finish(self, config: dict): """Validate and create config entry.""" base_url = config[CONF_CONTROLLER] = config[CONF_CONTROLLER].rstrip("/") controller = pv.VeraController(base_url) # Verify the controller is online and get the serial number. try: await self.hass.async_add_executor_job(controller.refresh_data) except RequestException: _LOGGER.error("Failed to connect to vera controller %s", base_url) return self.async_abort( reason="cannot_connect", description_placeholders={"base_url": base_url} ) await self.async_set_unique_id(controller.serial_number) self._abort_if_unique_id_configured(config) return self.async_create_entry(title=base_url, data=config)