Home Assistant Unofficial Reference 2024.12.1
config_flow.py
Go to the documentation of this file.
1 """Config flow for the Total Connect component."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Mapping
6 from typing import TYPE_CHECKING, Any
7 
8 from total_connect_client.client import TotalConnectClient
9 from total_connect_client.exceptions import AuthenticationError
10 import voluptuous as vol
11 
12 from homeassistant.config_entries import (
13  ConfigEntry,
14  ConfigFlow,
15  ConfigFlowResult,
16  OptionsFlow,
17 )
18 from homeassistant.const import CONF_LOCATION, CONF_PASSWORD, CONF_USERNAME
19 from homeassistant.core import callback
20 from homeassistant.helpers.typing import VolDictType
21 
22 from .const import AUTO_BYPASS, CODE_REQUIRED, CONF_USERCODES, DOMAIN
23 
24 PASSWORD_DATA_SCHEMA = vol.Schema({vol.Required(CONF_PASSWORD): str})
25 
26 
27 class TotalConnectConfigFlow(ConfigFlow, domain=DOMAIN):
28  """Total Connect config flow."""
29 
30  VERSION = 1
31 
32  client: TotalConnectClient
33 
34  def __init__(self) -> None:
35  """Initialize the config flow."""
36  self.usernameusername: str | None = None
37  self.passwordpassword: str | None = None
38  self.usercodesusercodes: dict[int, str | None] = {}
39 
40  async def async_step_user(
41  self, user_input: dict[str, str] | None = None
42  ) -> ConfigFlowResult:
43  """Handle a flow initiated by the user."""
44  errors = {}
45 
46  if user_input is not None:
47  # Validate user input
48  username = user_input[CONF_USERNAME]
49  password = user_input[CONF_PASSWORD]
50 
51  await self.async_set_unique_idasync_set_unique_id(username)
52  self._abort_if_unique_id_configured_abort_if_unique_id_configured()
53 
54  try:
55  client = await self.hass.async_add_executor_job(
56  TotalConnectClient, username, password, None
57  )
58  except AuthenticationError:
59  errors["base"] = "invalid_auth"
60  else:
61  # username/password valid so show user locations
62  self.usernameusername = username
63  self.passwordpassword = password
64  self.clientclient = client
65  return await self.async_step_locationsasync_step_locations()
66 
67  data_schema = vol.Schema(
68  {vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str}
69  )
70 
71  return self.async_show_formasync_show_formasync_show_form(
72  step_id="user", data_schema=data_schema, errors=errors
73  )
74 
76  self, user_input: dict[str, str] | None = None
77  ) -> ConfigFlowResult:
78  """Handle the user locations and associated usercodes."""
79  errors = {}
80  if user_input is not None:
81  for location_id in self.usercodesusercodes:
82  if self.usercodesusercodes[location_id] is None:
83  valid = await self.hass.async_add_executor_job(
84  self.clientclient.locations[location_id].set_usercode,
85  user_input[CONF_USERCODES],
86  )
87  if valid:
88  self.usercodesusercodes[location_id] = user_input[CONF_USERCODES]
89  else:
90  errors[CONF_LOCATION] = "usercode"
91  break
92 
93  complete = True
94  for location_id in self.usercodesusercodes:
95  if self.usercodesusercodes[location_id] is None:
96  complete = False
97 
98  if not errors and complete:
99  return self.async_create_entryasync_create_entryasync_create_entry(
100  title="Total Connect",
101  data={
102  CONF_USERNAME: self.usernameusername,
103  CONF_PASSWORD: self.passwordpassword,
104  CONF_USERCODES: self.usercodesusercodes,
105  },
106  )
107  else:
108  # Force the loading of locations using I/O
109  number_locations = await self.hass.async_add_executor_job(
110  self.clientclient.get_number_locations,
111  )
112  if number_locations < 1:
113  return self.async_abortasync_abortasync_abort(reason="no_locations")
114  for location_id in self.clientclient.locations:
115  self.usercodesusercodes[location_id] = None
116 
117  # show the next location that needs a usercode
118  location_codes: VolDictType = {}
119  location_for_user = ""
120  for location_id in self.usercodesusercodes:
121  if self.usercodesusercodes[location_id] is None:
122  location_for_user = str(location_id)
123  location_codes[
124  vol.Required(
125  CONF_USERCODES,
126  default="0000",
127  )
128  ] = str
129  break
130 
131  data_schema = vol.Schema(location_codes)
132  return self.async_show_formasync_show_formasync_show_form(
133  step_id="locations",
134  data_schema=data_schema,
135  errors=errors,
136  description_placeholders={"location_id": location_for_user},
137  )
138 
139  async def async_step_reauth(
140  self, entry_data: Mapping[str, Any]
141  ) -> ConfigFlowResult:
142  """Perform reauth upon an authentication error or no usercode."""
143  self.usernameusername = entry_data[CONF_USERNAME]
144  self.usercodesusercodes = entry_data[CONF_USERCODES]
145 
146  return await self.async_step_reauth_confirmasync_step_reauth_confirm()
147 
149  self, user_input: dict[str, str] | None = None
150  ) -> ConfigFlowResult:
151  """Dialog that informs the user that reauth is required."""
152  errors = {}
153  if user_input is None:
154  return self.async_show_formasync_show_formasync_show_form(
155  step_id="reauth_confirm",
156  data_schema=PASSWORD_DATA_SCHEMA,
157  )
158 
159  try:
160  await self.hass.async_add_executor_job(
161  TotalConnectClient,
162  self.usernameusername,
163  user_input[CONF_PASSWORD],
164  self.usercodesusercodes,
165  )
166  except AuthenticationError:
167  errors["base"] = "invalid_auth"
168  return self.async_show_formasync_show_formasync_show_form(
169  step_id="reauth_confirm",
170  errors=errors,
171  data_schema=PASSWORD_DATA_SCHEMA,
172  )
173 
174  existing_entry = await self.async_set_unique_idasync_set_unique_id(self.usernameusername)
175  if TYPE_CHECKING:
176  assert existing_entry is not None
177  new_entry = {
178  CONF_USERNAME: self.usernameusername,
179  CONF_PASSWORD: user_input[CONF_PASSWORD],
180  CONF_USERCODES: self.usercodesusercodes,
181  }
182  self.hass.config_entries.async_update_entry(existing_entry, data=new_entry)
183 
184  self.hass.async_create_task(
185  self.hass.config_entries.async_reload(existing_entry.entry_id)
186  )
187 
188  return self.async_abortasync_abortasync_abort(reason="reauth_successful")
189 
190  @staticmethod
191  @callback
193  config_entry: ConfigEntry,
194  ) -> TotalConnectOptionsFlowHandler:
195  """Get options flow."""
197 
198 
200  """TotalConnect options flow handler."""
201 
202  async def async_step_init(
203  self, user_input: dict[str, bool] | None = None
204  ) -> ConfigFlowResult:
205  """Manage the options."""
206  if user_input is not None:
207  return self.async_create_entryasync_create_entry(title="", data=user_input)
208 
209  return self.async_show_formasync_show_form(
210  step_id="init",
211  data_schema=vol.Schema(
212  {
213  vol.Required(
214  AUTO_BYPASS,
215  default=self.config_entryconfig_entryconfig_entry.options.get(AUTO_BYPASS, False),
216  ): bool,
217  vol.Required(
218  CODE_REQUIRED,
219  default=self.config_entryconfig_entryconfig_entry.options.get(CODE_REQUIRED, False),
220  ): bool,
221  }
222  ),
223  )
ConfigFlowResult async_step_user(self, dict[str, str]|None user_input=None)
Definition: config_flow.py:42
ConfigFlowResult async_step_locations(self, dict[str, str]|None user_input=None)
Definition: config_flow.py:77
ConfigFlowResult async_step_reauth_confirm(self, dict[str, str]|None user_input=None)
Definition: config_flow.py:150
ConfigFlowResult async_step_reauth(self, Mapping[str, Any] entry_data)
Definition: config_flow.py:141
TotalConnectOptionsFlowHandler async_get_options_flow(ConfigEntry config_entry)
Definition: config_flow.py:194
ConfigFlowResult async_step_init(self, dict[str, bool]|None user_input=None)
Definition: config_flow.py:204
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)
str
_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)
_FlowResultT async_abort(self, *str reason, Mapping[str, str]|None description_placeholders=None)