"""Config flow for MyPermobil integration.""" from __future__ import annotations from collections.abc import Mapping import logging from typing import Any from mypermobil import ( MyPermobil, MyPermobilAPIException, MyPermobilClientException, MyPermobilEulaException, ) import voluptuous as vol from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_CODE, CONF_EMAIL, CONF_REGION, CONF_TOKEN, CONF_TTL from homeassistant.core import HomeAssistant, async_get_hass from homeassistant.helpers import selector from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv from homeassistant.helpers.selector import ( TextSelector, TextSelectorConfig, TextSelectorType, ) from .const import APPLICATION, DOMAIN _LOGGER = logging.getLogger(__name__) GET_EMAIL_SCHEMA = vol.Schema( { vol.Required(CONF_EMAIL): TextSelector( TextSelectorConfig(type=TextSelectorType.EMAIL) ), } ) GET_TOKEN_SCHEMA = vol.Schema({vol.Required(CONF_CODE): cv.string}) class PermobilConfigFlow(ConfigFlow, domain=DOMAIN): """Permobil config flow.""" VERSION = 1 region_names: dict[str, str] = {} data: dict[str, str] = {} def __init__(self) -> None: """Initialize flow.""" hass: HomeAssistant = async_get_hass() session = async_get_clientsession(hass) self.p_api = MyPermobil(APPLICATION, session=session) async def async_step_user( self, user_input: dict[str, Any] | None = None ) -> ConfigFlowResult: """Invoke when a user initiates a flow via the user interface.""" errors: dict[str, str] = {} if user_input: try: self.p_api.set_email(user_input[CONF_EMAIL]) except MyPermobilClientException: _LOGGER.exception("Error validating email") errors["base"] = "invalid_email" self.data.update(user_input) await self.async_set_unique_id(self.data[CONF_EMAIL]) self._abort_if_unique_id_configured() if errors or not user_input: return self.async_show_form( step_id="user", data_schema=GET_EMAIL_SCHEMA, errors=errors ) return await self.async_step_region() async def async_step_region( self, user_input: dict[str, Any] | None = None ) -> ConfigFlowResult: """Invoke when a user initiates a flow via the user interface.""" errors: dict[str, str] = {} if not user_input: # fetch the list of regions names and urls from the api # for the user to select from. try: self.region_names = await self.p_api.request_region_names() _LOGGER.debug( "region names %s", ",".join(list(self.region_names.keys())), ) except MyPermobilAPIException: _LOGGER.exception("Error requesting regions") errors["base"] = "region_fetch_error" else: region_url = self.region_names[user_input[CONF_REGION]] self.data[CONF_REGION] = region_url self.p_api.set_region(region_url) _LOGGER.debug("region %s", self.p_api.region) try: # tell backend to send code to the users email await self.p_api.request_application_code() except MyPermobilAPIException: _LOGGER.exception("Error requesting code") errors["base"] = "code_request_error" if errors or not user_input: # the error could either be that the fetch region did not pass # or that the request application code failed schema = vol.Schema( { vol.Required(CONF_REGION): selector.SelectSelector( selector.SelectSelectorConfig( options=list(self.region_names.keys()), mode=selector.SelectSelectorMode.DROPDOWN, ) ), } ) return self.async_show_form( step_id="region", data_schema=schema, errors=errors ) return await self.async_step_email_code() async def async_step_email_code( self, user_input: dict[str, Any] | None = None ) -> ConfigFlowResult: """Second step in config flow to enter the email code.""" errors: dict[str, str] = {} if user_input: try: self.p_api.set_code(user_input[CONF_CODE]) self.data.update(user_input) token, ttl = await self.p_api.request_application_token() self.data[CONF_TOKEN] = token self.data[CONF_TTL] = ttl except (MyPermobilAPIException, MyPermobilClientException): # the code did not pass validation by the api client # or the backend returned an error when trying to validate the code _LOGGER.exception("Error verifying code") errors["base"] = "invalid_code" except MyPermobilEulaException: # The user has not accepted the EULA errors["base"] = "unsigned_eula" if errors or not user_input: return self.async_show_form( step_id="email_code", data_schema=GET_TOKEN_SCHEMA, errors=errors, description_placeholders={"app_name": "MyPermobil"}, ) return self.async_create_entry(title=self.data[CONF_EMAIL], data=self.data) async def async_step_reauth( self, user_input: Mapping[str, Any] ) -> ConfigFlowResult: """Perform reauth upon an API authentication error.""" reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] ) assert reauth_entry try: email: str = reauth_entry.data[CONF_EMAIL] region: str = reauth_entry.data[CONF_REGION] self.p_api.set_email(email) self.p_api.set_region(region) self.data = { CONF_EMAIL: email, CONF_REGION: region, } await self.p_api.request_application_code() except MyPermobilAPIException: _LOGGER.exception("Error requesting code for reauth") return self.async_abort(reason="unknown") return await self.async_step_email_code()