Home Assistant Unofficial Reference 2024.12.1
config_flow.py
Go to the documentation of this file.
1 """Config flow for Roborock."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Mapping
6 from copy import deepcopy
7 import logging
8 from typing import Any
9 
10 from roborock.containers import UserData
11 from roborock.exceptions import (
12  RoborockAccountDoesNotExist,
13  RoborockException,
14  RoborockInvalidCode,
15  RoborockInvalidEmail,
16  RoborockTooFrequentCodeRequests,
17  RoborockUrlException,
18 )
19 from roborock.web_api import RoborockApiClient
20 import voluptuous as vol
21 
22 from homeassistant.config_entries import (
23  SOURCE_REAUTH,
24  ConfigEntry,
25  ConfigFlow,
26  ConfigFlowResult,
27  OptionsFlow,
28 )
29 from homeassistant.const import CONF_USERNAME
30 from homeassistant.core import callback
31 
32 from .const import (
33  CONF_BASE_URL,
34  CONF_ENTRY_CODE,
35  CONF_USER_DATA,
36  DEFAULT_DRAWABLES,
37  DOMAIN,
38  DRAWABLES,
39 )
40 
41 _LOGGER = logging.getLogger(__name__)
42 
43 
44 class RoborockFlowHandler(ConfigFlow, domain=DOMAIN):
45  """Handle a config flow for Roborock."""
46 
47  VERSION = 1
48 
49  def __init__(self) -> None:
50  """Initialize the config flow."""
51  self._username_username: str | None = None
52  self._client_client: RoborockApiClient | None = None
53 
54  async def async_step_user(
55  self, user_input: dict[str, Any] | None = None
56  ) -> ConfigFlowResult:
57  """Handle a flow initialized by the user."""
58  errors: dict[str, str] = {}
59 
60  if user_input is not None:
61  username = user_input[CONF_USERNAME]
62  await self.async_set_unique_idasync_set_unique_id(username.lower())
63  self._abort_if_unique_id_configured_abort_if_unique_id_configured()
64  self._username_username = username
65  _LOGGER.debug("Requesting code for Roborock account")
66  self._client_client = RoborockApiClient(username)
67  errors = await self._request_code_request_code()
68  if not errors:
69  return await self.async_step_codeasync_step_code()
70  return self.async_show_formasync_show_formasync_show_form(
71  step_id="user",
72  data_schema=vol.Schema({vol.Required(CONF_USERNAME): str}),
73  errors=errors,
74  )
75 
76  async def _request_code(self) -> dict:
77  assert self._client_client
78  errors: dict[str, str] = {}
79  try:
80  await self._client_client.request_code()
81  except RoborockAccountDoesNotExist:
82  errors["base"] = "invalid_email"
83  except RoborockUrlException:
84  errors["base"] = "unknown_url"
85  except RoborockInvalidEmail:
86  errors["base"] = "invalid_email_format"
87  except RoborockTooFrequentCodeRequests:
88  errors["base"] = "too_frequent_code_requests"
89  except RoborockException:
90  _LOGGER.exception("Unexpected exception")
91  errors["base"] = "unknown_roborock"
92  except Exception:
93  _LOGGER.exception("Unexpected exception")
94  errors["base"] = "unknown"
95  return errors
96 
97  async def async_step_code(
98  self,
99  user_input: dict[str, Any] | None = None,
100  ) -> ConfigFlowResult:
101  """Handle a flow initialized by the user."""
102  errors: dict[str, str] = {}
103  assert self._client_client
104  assert self._username_username
105  if user_input is not None:
106  code = user_input[CONF_ENTRY_CODE]
107  _LOGGER.debug("Logging into Roborock account using email provided code")
108  try:
109  login_data = await self._client_client.code_login(code)
110  except RoborockInvalidCode:
111  errors["base"] = "invalid_code"
112  except RoborockException:
113  _LOGGER.exception("Unexpected exception")
114  errors["base"] = "unknown_roborock"
115  except Exception:
116  _LOGGER.exception("Unexpected exception")
117  errors["base"] = "unknown"
118  else:
119  if self.sourcesourcesourcesource == SOURCE_REAUTH:
120  reauth_entry = self._get_reauth_entry_get_reauth_entry()
121  self.hass.config_entries.async_update_entry(
122  reauth_entry,
123  data={
124  **reauth_entry.data,
125  CONF_USER_DATA: login_data.as_dict(),
126  },
127  )
128  return self.async_abortasync_abortasync_abort(reason="reauth_successful")
129  return self._create_entry_create_entry(self._client_client, self._username_username, login_data)
130 
131  return self.async_show_formasync_show_formasync_show_form(
132  step_id="code",
133  data_schema=vol.Schema({vol.Required(CONF_ENTRY_CODE): str}),
134  errors=errors,
135  )
136 
137  async def async_step_reauth(
138  self, entry_data: Mapping[str, Any]
139  ) -> ConfigFlowResult:
140  """Perform reauth upon an API authentication error."""
141  self._username_username = entry_data[CONF_USERNAME]
142  assert self._username_username
143  self._client_client = RoborockApiClient(self._username_username)
144  return await self.async_step_reauth_confirmasync_step_reauth_confirm()
145 
147  self, user_input: dict[str, Any] | None = None
148  ) -> ConfigFlowResult:
149  """Confirm reauth dialog."""
150  errors: dict[str, str] = {}
151  if user_input is not None:
152  errors = await self._request_code_request_code()
153  if not errors:
154  return await self.async_step_codeasync_step_code()
155  return self.async_show_formasync_show_formasync_show_form(step_id="reauth_confirm", errors=errors)
156 
158  self, client: RoborockApiClient, username: str, user_data: UserData
159  ) -> ConfigFlowResult:
160  """Finished config flow and create entry."""
161  return self.async_create_entryasync_create_entryasync_create_entry(
162  title=username,
163  data={
164  CONF_USERNAME: username,
165  CONF_USER_DATA: user_data.as_dict(),
166  CONF_BASE_URL: client.base_url,
167  },
168  )
169 
170  @staticmethod
171  @callback
173  config_entry: ConfigEntry,
174  ) -> RoborockOptionsFlowHandler:
175  """Create the options flow."""
176  return RoborockOptionsFlowHandler(config_entry)
177 
178 
180  """Handle an option flow for Roborock."""
181 
182  def __init__(self, config_entry: ConfigEntry) -> None:
183  """Initialize options flow."""
184  self.optionsoptions = deepcopy(dict(config_entry.options))
185 
186  async def async_step_init(
187  self, user_input: dict[str, Any] | None = None
188  ) -> ConfigFlowResult:
189  """Manage the options."""
190  return await self.async_step_drawablesasync_step_drawables()
191 
193  self, user_input: dict[str, Any] | None = None
194  ) -> ConfigFlowResult:
195  """Manage the map object drawable options."""
196  if user_input is not None:
197  self.optionsoptions.setdefault(DRAWABLES, {}).update(user_input)
198  return self.async_create_entryasync_create_entry(title="", data=self.optionsoptions)
199  data_schema = {}
200  for drawable, default_value in DEFAULT_DRAWABLES.items():
201  data_schema[
202  vol.Required(
203  drawable.value,
204  default=self.config_entryconfig_entryconfig_entry.options.get(DRAWABLES, {}).get(
205  drawable, default_value
206  ),
207  )
208  ] = bool
209  return self.async_show_formasync_show_form(
210  step_id=DRAWABLES,
211  data_schema=vol.Schema(data_schema),
212  )
ConfigFlowResult async_step_reauth(self, Mapping[str, Any] entry_data)
Definition: config_flow.py:139
ConfigFlowResult _create_entry(self, RoborockApiClient client, str username, UserData user_data)
Definition: config_flow.py:159
ConfigFlowResult async_step_reauth_confirm(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:148
ConfigFlowResult async_step_code(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:100
RoborockOptionsFlowHandler async_get_options_flow(ConfigEntry config_entry)
Definition: config_flow.py:174
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:56
ConfigFlowResult async_step_drawables(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:194
ConfigFlowResult async_step_init(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:188
None _abort_if_unique_id_configured(self, dict[str, Any]|None updates=None, bool reload_on_update=True, *str error="already_configured")
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_abort(self, *str reason, 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)
None config_entry(self, ConfigEntry value)
_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)
str|None source(self)
_FlowResultT async_abort(self, *str reason, Mapping[str, str]|None description_placeholders=None)
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88
IssData update(pyiss.ISS iss)
Definition: __init__.py:33