"""Config flow for Integration 101 Template integration.""" from __future__ import annotations import logging from typing import Any import voluptuous as vol from homeassistant.config_entries import ( ConfigEntry, ConfigFlow, ConfigFlowResult, OptionsFlow, ) from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_USERNAME, ) from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.aiohttp_client import async_get_clientsession from .api import (RouterAPI, RouterAPIAuthError, RouterAPIConnectionError) from .const import (DEFAULT_SCAN_INTERVAL, DOMAIN, MIN_SCAN_INTERVAL, DEFAULT_IP, DEFAULT_USER) _LOGGER = logging.getLogger(__name__) # TODO adjust the data schema to the data that you need STEP_USER_DATA_SCHEMA = vol.Schema( { vol.Required(CONF_HOST, description={"suggested_value": DEFAULT_IP}): str, vol.Required(CONF_USERNAME, description={"suggested_value": DEFAULT_USER}): str, vol.Required(CONF_PASSWORD): str, } ) async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, Any]: """Validate the user input allows us to connect. Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user. """ # TODO validate the data can be used to set up a connection. # If your PyPI package is not built with async, pass your methods # to the executor: # await hass.async_add_executor_job( # your_validate_func, data[CONF_USERNAME], data[CONF_PASSWORD] # ) session = async_get_clientsession( hass=hass, verify_ssl=False ) api = RouterAPI(host=data[CONF_HOST], user=data[CONF_USERNAME], pwd=data[CONF_PASSWORD], session=session) try: await api.async_login() except RouterAPIAuthError as err: raise InvalidAuth from err except RouterAPIConnectionError as err: raise CannotConnect from err return {"title": f"Odido Klik & Klaar router - {data[CONF_HOST]}"} class RouterConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Example Integration.""" VERSION = 1 _input_data: dict[str, Any] @staticmethod @callback def async_get_options_flow(config_entry): """Get the options flow for this handler.""" # Remove this method and the ExampleOptionsFlowHandler class # if you do not want any options for your integration. return RouterOptionsFlowHandler(config_entry) async def async_step_user( self, user_input: dict[str, Any] | None = None ) -> ConfigFlowResult: """Handle the initial step.""" # Called when you initiate adding an integration via the UI errors: dict[str, str] = {} if user_input is not None: # The form has been filled in and submitted, so process the data provided. try: # Validate that the setup data is valid and if not handle errors. # The errors["base"] values match the values in your strings.json and translation files. info = await validate_input(self.hass, user_input) except CannotConnect: errors["base"] = "cannot_connect" except InvalidAuth: errors["base"] = "invalid_auth" except Exception as e: # pylint: disable=broad-except _LOGGER.exception(f"Unexpected exception: {e}") errors["base"] = "unknown" #errors[CONF_PASSWORD] = str(e) if "base" not in errors: # Validation was successful, so create a unique id for this instance of your integration # and create the config entry. await self.async_set_unique_id(info.get("title")) self._abort_if_unique_id_configured() return self.async_create_entry(title=info["title"], data=user_input) # Show initial form. return self.async_show_form( step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors ) async def async_step_reconfigure( self, user_input: dict[str, Any] | None = None ) -> ConfigFlowResult: """Add reconfigure step to allow to reconfigure a config entry.""" # This methid displays a reconfigure option in the integration and is # different to options. # It can be used to reconfigure any of the data submitted when first installed. # This is optional and can be removed if you do not want to allow reconfiguration. errors: dict[str, str] = {} config_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] ) if user_input is not None: try: user_input[CONF_HOST] = config_entry.data[CONF_HOST] await validate_input(self.hass, user_input) except CannotConnect: errors["base"] = "cannot_connect" except InvalidAuth: errors["base"] = "invalid_auth" except Exception: # pylint: disable=broad-except _LOGGER.exception("Unexpected exception") errors["base"] = "unknown" else: return self.async_update_reload_and_abort( config_entry, unique_id=config_entry.unique_id, data={**config_entry.data, **user_input}, reason="reconfigure_successful", ) return self.async_show_form( step_id="reconfigure", data_schema=vol.Schema( { vol.Required( CONF_USERNAME, default=config_entry.data[CONF_USERNAME] ): str, vol.Required(CONF_PASSWORD): str, } ), errors=errors, ) class RouterOptionsFlowHandler(OptionsFlow): """Handles the options flow.""" def __init__(self, config_entry: ConfigEntry) -> None: """Initialize options flow.""" self.config_entry = config_entry self.options = dict(config_entry.options) async def async_step_init(self, user_input=None): """Handle options flow.""" if user_input is not None: options = self.config_entry.options | user_input return self.async_create_entry(title="", data=options) # It is recommended to prepopulate options fields with default values if available. # These will be the same default values you use on your coordinator for setting variable values # if the option has not been set. data_schema = vol.Schema( { vol.Required( CONF_SCAN_INTERVAL, default=self.options.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL), ): (vol.All(vol.Coerce(int), vol.Clamp(min=MIN_SCAN_INTERVAL))), } ) return self.async_show_form(step_id="init", data_schema=data_schema) class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" class InvalidAuth(HomeAssistantError): """Error to indicate there is invalid auth."""