1 """Adds config flow for Vulcan."""
3 from collections.abc
import Mapping
5 from typing
import TYPE_CHECKING, Any
7 from aiohttp
import ClientConnectionError
8 import voluptuous
as vol
11 ExpiredTokenException,
13 InvalidSymbolException,
14 InvalidTokenException,
16 UnauthorizedCertificateException,
19 from vulcan.model
import Student
26 from .register
import register
28 _LOGGER = logging.getLogger(__name__)
31 vol.Required(CONF_TOKEN): str,
32 vol.Required(CONF_REGION): str,
33 vol.Required(CONF_PIN): str,
38 """Handle a Uonet+ Vulcan config flow."""
46 """Initialize config flow."""
47 self.
studentsstudents: list[Student] |
None =
None
50 self, user_input: dict[str, Any] |
None =
None
51 ) -> ConfigFlowResult:
52 """Handle config flow."""
60 user_input: dict[str, str] |
None =
None,
61 errors: dict[str, str] |
None =
None,
62 ) -> ConfigFlowResult:
63 """Authorize integration."""
65 if user_input
is not None:
68 user_input[CONF_TOKEN],
69 user_input[CONF_REGION],
72 except InvalidSymbolException:
73 errors = {
"base":
"invalid_symbol"}
74 except InvalidTokenException:
75 errors = {
"base":
"invalid_token"}
76 except InvalidPINException:
77 errors = {
"base":
"invalid_pin"}
78 except ExpiredTokenException:
79 errors = {
"base":
"expired_token"}
80 except ClientConnectionError
as err:
81 errors = {
"base":
"cannot_connect"}
82 _LOGGER.error(
"Connection error: %s", err)
84 _LOGGER.exception(
"Unexpected exception")
85 errors = {
"base":
"unknown"}
87 account = credentials[
"account"]
88 keystore = credentials[
"keystore"]
90 students = await client.get_students()
101 title=f
"{student.pupil.first_name} {student.pupil.last_name}",
103 "student_id":
str(student.pupil.id),
104 "keystore": keystore.as_dict,
105 "account": account.as_dict,
111 data_schema=vol.Schema(LOGIN_SCHEMA),
116 self, user_input: dict[str, str] |
None =
None
117 ) -> ConfigFlowResult:
118 """Allow user to select student."""
119 errors: dict[str, str] = {}
120 students: dict[str, str] = {}
121 if self.
studentsstudents
is not None:
122 for student
in self.
studentsstudents:
123 students[
str(student.pupil.id)] = (
124 f
"{student.pupil.first_name} {student.pupil.last_name}"
126 if user_input
is not None:
128 assert self.
keystorekeystore
is not None
129 student_id = user_input[
"student"]
133 title=students[student_id],
135 "student_id":
str(student_id),
136 "keystore": self.
keystorekeystore.as_dict,
137 "account": self.
accountaccount.as_dict,
142 step_id=
"select_student",
143 data_schema=vol.Schema({vol.Required(
"student"): vol.In(students)}),
149 user_input: dict[str, str] |
None =
None,
150 errors: dict[str, str] |
None =
None,
151 ) -> ConfigFlowResult:
152 """Allow user to select saved credentials."""
154 credentials: dict[str, Any] = {}
155 for entry
in self.hass.config_entries.async_entries(DOMAIN):
156 credentials[entry.entry_id] = entry.data[
"account"][
"UserName"]
158 if user_input
is not None:
159 existing_entry = self.hass.config_entries.async_get_entry(
160 user_input[
"credentials"]
163 assert existing_entry
is not None
164 keystore = Keystore.load(existing_entry.data[
"keystore"])
165 account = Account.load(existing_entry.data[
"account"])
168 students = await client.get_students()
169 except UnauthorizedCertificateException:
171 errors={
"base":
"expired_credentials"}
173 except ClientConnectionError
as err:
174 _LOGGER.error(
"Connection error: %s", err)
176 errors={
"base":
"cannot_connect"}
179 _LOGGER.exception(
"Unexpected exception")
180 return await self.
async_step_authasync_step_auth(errors={
"base":
"unknown"})
181 if len(students) == 1:
182 student = students[0]
186 title=f
"{student.pupil.first_name} {student.pupil.last_name}",
188 "student_id":
str(student.pupil.id),
189 "keystore": keystore.as_dict,
190 "account": account.as_dict,
201 ): vol.In(credentials),
204 step_id=
"select_saved_credentials",
205 data_schema=vol.Schema(data_schema),
210 self, user_input: dict[str, bool] |
None =
None
211 ) -> ConfigFlowResult:
212 """Flow initialized when user is adding next entry of that integration."""
214 existing_entries = self.hass.config_entries.async_entries(DOMAIN)
216 errors: dict[str, str] = {}
218 if user_input
is not None:
219 if not user_input[
"use_saved_credentials"]:
221 if len(existing_entries) > 1:
223 keystore = Keystore.load(existing_entries[0].data[
"keystore"])
224 account = Account.load(existing_entries[0].data[
"account"])
226 students = await client.get_students()
227 existing_entry_ids = [
228 entry.data[
"student_id"]
for entry
in existing_entries
232 for student
in students
233 if str(student.pupil.id)
not in existing_entry_ids
237 if len(new_students) == 1:
242 f
"{new_students[0].pupil.first_name} {new_students[0].pupil.last_name}"
245 "student_id":
str(new_students[0].pupil.id),
246 "keystore": keystore.as_dict,
247 "account": account.as_dict,
252 self.
studentsstudents = new_students
256 vol.Required(
"use_saved_credentials", default=
True): bool,
259 step_id=
"add_next_config_entry",
260 data_schema=vol.Schema(data_schema),
265 self, entry_data: Mapping[str, Any]
266 ) -> ConfigFlowResult:
267 """Perform reauth upon an API authentication error."""
271 self, user_input: dict[str, str] |
None =
None
272 ) -> ConfigFlowResult:
273 """Reauthorize integration."""
275 if user_input
is not None:
278 user_input[CONF_TOKEN],
279 user_input[CONF_REGION],
280 user_input[CONF_PIN],
282 except InvalidSymbolException:
283 errors = {
"base":
"invalid_symbol"}
284 except InvalidTokenException:
285 errors = {
"base":
"invalid_token"}
286 except InvalidPINException:
287 errors = {
"base":
"invalid_pin"}
288 except ExpiredTokenException:
289 errors = {
"base":
"expired_token"}
290 except ClientConnectionError
as err:
291 errors[
"base"] =
"cannot_connect"
292 _LOGGER.error(
"Connection error: %s", err)
294 _LOGGER.exception(
"Unexpected exception")
295 errors[
"base"] =
"unknown"
297 account = credentials[
"account"]
298 keystore = credentials[
"keystore"]
300 students = await client.get_students()
301 existing_entries = self.hass.config_entries.async_entries(DOMAIN)
302 matching_entries =
False
303 for student
in students:
304 for entry
in existing_entries:
305 if str(student.pupil.id) ==
str(entry.data[
"student_id"]):
306 self.hass.config_entries.async_update_entry(
309 f
"{student.pupil.first_name} {student.pupil.last_name}"
312 "student_id":
str(student.pupil.id),
313 "keystore": keystore.as_dict,
314 "account": account.as_dict,
317 await self.hass.config_entries.async_reload(entry.entry_id)
318 matching_entries =
True
319 if not matching_entries:
324 step_id=
"reauth_confirm",
325 data_schema=vol.Schema(LOGIN_SCHEMA),
ConfigFlowResult async_step_select_saved_credentials(self, dict[str, str]|None user_input=None, dict[str, str]|None errors=None)
ConfigFlowResult async_step_reauth(self, Mapping[str, Any] entry_data)
ConfigFlowResult async_step_select_student(self, dict[str, str]|None user_input=None)
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
ConfigFlowResult async_step_reauth_confirm(self, dict[str, str]|None user_input=None)
ConfigFlowResult async_step_add_next_config_entry(self, dict[str, bool]|None user_input=None)
ConfigFlowResult async_step_auth(self, dict[str, str]|None user_input=None, dict[str, str]|None errors=None)
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)
list[ConfigEntry] _async_current_entries(self, bool|None include_ignore=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)
_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)
def register(HomeAssistant hass, Heos controller)
aiohttp.ClientSession async_get_clientsession(HomeAssistant hass, bool verify_ssl=True, socket.AddressFamily family=socket.AF_UNSPEC, ssl_util.SSLCipherList ssl_cipher=ssl_util.SSLCipherList.PYTHON_DEFAULT)