1 """Config flow for BMW ConnectedDrive integration."""
3 from __future__
import annotations
5 from collections.abc
import Mapping
8 from bimmer_connected.api.authentication
import MyBMWAuthentication
9 from bimmer_connected.api.regions
import get_region_from_name
10 from bimmer_connected.models
import (
13 MyBMWCaptchaMissingError,
15 from httpx
import RequestError
16 import voluptuous
as vol
43 DATA_SCHEMA = vol.Schema(
45 vol.Required(CONF_USERNAME): str,
46 vol.Required(CONF_PASSWORD): str,
49 options=CONF_ALLOWED_REGIONS,
50 translation_key=
"regions",
54 extra=vol.REMOVE_EXTRA,
56 CAPTCHA_SCHEMA = vol.Schema(
58 vol.Required(CONF_CAPTCHA_TOKEN): str,
60 extra=vol.REMOVE_EXTRA,
64 async
def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, str]:
65 """Validate the user input allows us to connect.
67 Data has the keys from DATA_SCHEMA with values provided by the user.
69 auth = MyBMWAuthentication(
72 get_region_from_name(data[CONF_REGION]),
73 hcaptcha_token=data.get(CONF_CAPTCHA_TOKEN),
79 except MyBMWCaptchaMissingError
as ex:
80 raise MissingCaptcha
from ex
81 except MyBMWAuthError
as ex:
82 raise InvalidAuth
from ex
83 except (MyBMWAPIError, RequestError)
as ex:
84 raise CannotConnect
from ex
87 retval = {
"title": f
"{data[CONF_USERNAME]}{data.get(CONF_SOURCE, '')}"}
88 if auth.refresh_token:
89 retval[CONF_REFRESH_TOKEN] = auth.refresh_token
91 retval[CONF_GCID] = auth.gcid
96 """Handle a config flow for MyBMW."""
100 data: dict[str, Any] = {}
102 _existing_entry_data: Mapping[str, Any] |
None =
None
105 self, user_input: dict[str, Any] |
None =
None
106 ) -> ConfigFlowResult:
107 """Handle the initial step."""
108 errors: dict[str, str] = self.data.pop(
"errors", {})
110 if user_input
is not None and not errors:
111 unique_id = f
"{user_input[CONF_REGION]}-{user_input[CONF_USERNAME]}"
120 self.data.
update(user_input)
124 self.data.
get(CONF_REGION)
in CONF_CAPTCHA_REGIONS
125 and CONF_CAPTCHA_TOKEN
not in self.data
127 return await self.async_step_captcha()
132 except MissingCaptcha:
133 errors[
"base"] =
"missing_captcha"
134 except CannotConnect:
135 errors[
"base"] =
"cannot_connect"
137 errors[
"base"] =
"invalid_auth"
139 self.data.pop(CONF_CAPTCHA_TOKEN,
None)
144 CONF_REFRESH_TOKEN: info.get(CONF_REFRESH_TOKEN),
145 CONF_GCID: info.get(CONF_GCID),
169 async
def async_step_reauth(
170 self, entry_data: Mapping[str, Any]
171 ) -> ConfigFlowResult:
172 """Handle configuration by re-auth."""
176 async
def async_step_reconfigure(
177 self, user_input: dict[str, Any] |
None =
None
178 ) -> ConfigFlowResult:
179 """Handle a reconfiguration flow initialized by the user."""
183 async
def async_step_captcha(
184 self, user_input: dict[str, Any] |
None =
None
185 ) -> ConfigFlowResult:
186 """Show captcha form."""
187 if user_input
and user_input.get(CONF_CAPTCHA_TOKEN):
188 self.data[CONF_CAPTCHA_TOKEN] = user_input[CONF_CAPTCHA_TOKEN].strip()
193 data_schema=CAPTCHA_SCHEMA,
194 description_placeholders={
195 "captcha_url": CONF_CAPTCHA_URL.format(region=self.data[CONF_REGION])
202 config_entry: ConfigEntry,
204 """Return a MyBMW option flow."""
209 """Handle a option flow for MyBMW."""
212 self, user_input: dict[str, Any] |
None =
None
213 ) -> ConfigFlowResult:
214 """Manage the options."""
215 return await self.async_step_account_options()
217 async
def async_step_account_options(
218 self, user_input: dict[str, Any] |
None =
None
219 ) -> ConfigFlowResult:
220 """Handle the initial step."""
221 if user_input
is not None:
226 changed = self.hass.config_entries.async_update_entry(
234 step_id=
"account_options",
235 data_schema=vol.Schema(
247 """Error to indicate we cannot connect."""
250 class InvalidAuth(HomeAssistantError):
251 """Error to indicate there is invalid auth."""
255 """Error to indicate the captcha token is missing."""
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
ConfigFlowResult async_step_init(self, dict[str, Any]|None user_input=None)
None _abort_if_unique_id_configured(self, dict[str, Any]|None updates=None, bool reload_on_update=True, *str error="already_configured")
ConfigEntry _get_reauth_entry(self)
ConfigEntry|None async_set_unique_id(self, str|None unique_id=None, *bool raise_on_progress=True)
ConfigFlowResult async_create_entry(self, *str title, Mapping[str, Any] data, str|None description=None, Mapping[str, str]|None description_placeholders=None, Mapping[str, Any]|None options=None)
ConfigFlowResult async_update_reload_and_abort(self, ConfigEntry entry, *str|None|UndefinedType unique_id=UNDEFINED, str|UndefinedType title=UNDEFINED, Mapping[str, Any]|UndefinedType data=UNDEFINED, Mapping[str, Any]|UndefinedType data_updates=UNDEFINED, Mapping[str, Any]|UndefinedType options=UNDEFINED, str|UndefinedType reason=UNDEFINED, bool reload_even_if_entry_is_unchanged=True)
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
ConfigEntry _get_reconfigure_entry(self)
None _abort_if_unique_id_mismatch(self, *str reason="unique_id_mismatch", Mapping[str, str]|None description_placeholders=None)
ConfigFlowResult async_show_form(self, *str|None step_id=None, vol.Schema|None data_schema=None, dict[str, str]|None errors=None, Mapping[str, str]|None description_placeholders=None, bool|None last_step=None, str|None preview=None)
OptionsFlow async_get_options_flow(ConfigEntry config_entry)
ConfigEntry config_entry(self)
None config_entry(self, ConfigEntry value)
vol.Schema add_suggested_values_to_schema(self, vol.Schema data_schema, Mapping[str, Any]|None suggested_values)
_FlowResultT async_show_form(self, *str|None step_id=None, vol.Schema|None data_schema=None, dict[str, str]|None errors=None, Mapping[str, str]|None description_placeholders=None, bool|None last_step=None, str|None preview=None)
_FlowResultT async_create_entry(self, *str|None title=None, Mapping[str, Any] data, str|None description=None, Mapping[str, str]|None description_placeholders=None)
dict[str, str] validate_input(HomeAssistant hass, dict[str, Any] data)
web.Response get(self, web.Request request, str config_key)
IssData update(pyiss.ISS iss)
ssl.SSLContext get_default_context()