Home Assistant Unofficial Reference 2024.12.1
config_flow.py
Go to the documentation of this file.
1 """Config flow for elmax-cloud integration."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Mapping
6 import logging
7 from typing import Any
8 
9 from elmax_api.exceptions import ElmaxBadLoginError, ElmaxBadPinError, ElmaxNetworkError
10 from elmax_api.http import Elmax, ElmaxLocal, GenericElmax
11 from elmax_api.model.panel import PanelEntry, PanelStatus
12 import httpx
13 import voluptuous as vol
14 
15 from homeassistant.components.zeroconf import ZeroconfServiceInfo
16 from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
17 from homeassistant.exceptions import HomeAssistantError
18 
19 from .common import (
20  build_direct_ssl_context,
21  check_local_version_supported,
22  get_direct_api_url,
23 )
24 from .const import (
25  CONF_ELMAX_MODE,
26  CONF_ELMAX_MODE_CLOUD,
27  CONF_ELMAX_MODE_DIRECT,
28  CONF_ELMAX_MODE_DIRECT_HOST,
29  CONF_ELMAX_MODE_DIRECT_PORT,
30  CONF_ELMAX_MODE_DIRECT_SSL,
31  CONF_ELMAX_MODE_DIRECT_SSL_CERT,
32  CONF_ELMAX_PANEL_ID,
33  CONF_ELMAX_PANEL_NAME,
34  CONF_ELMAX_PANEL_PIN,
35  CONF_ELMAX_PASSWORD,
36  CONF_ELMAX_USERNAME,
37  DOMAIN,
38  ELMAX_MODE_DIRECT_DEFAULT_HTTP_PORT,
39  ELMAX_MODE_DIRECT_DEFAULT_HTTPS_PORT,
40 )
41 
42 _LOGGER = logging.getLogger(__name__)
43 
44 LOGIN_FORM_SCHEMA = vol.Schema(
45  {
46  vol.Required(CONF_ELMAX_USERNAME): str,
47  vol.Required(CONF_ELMAX_PASSWORD): str,
48  }
49 )
50 
51 REAUTH_FORM_SCHEMA = vol.Schema(
52  {
53  vol.Required(CONF_ELMAX_USERNAME): str,
54  vol.Required(CONF_ELMAX_PASSWORD): str,
55  vol.Required(CONF_ELMAX_PANEL_PIN): str,
56  }
57 )
58 
59 DIRECT_SETUP_SCHEMA = vol.Schema(
60  {
61  vol.Required(CONF_ELMAX_MODE_DIRECT_HOST): str,
62  vol.Required(CONF_ELMAX_MODE_DIRECT_PORT, default=443): int,
63  vol.Required(CONF_ELMAX_MODE_DIRECT_SSL, default=True): bool,
64  vol.Required(CONF_ELMAX_PANEL_PIN): str,
65  }
66 )
67 
68 ZEROCONF_SETUP_SCHEMA = vol.Schema(
69  {
70  vol.Required(CONF_ELMAX_PANEL_PIN): str,
71  vol.Required(CONF_ELMAX_MODE_DIRECT_SSL, default=True): bool,
72  }
73 )
74 
75 
77  panel: PanelEntry, username: str, panel_names: dict[str, str]
78 ) -> None:
79  original_panel_name = panel.get_name_by_user(username=username)
80  panel_id = panel.hash
81  collisions_count = 0
82  panel_name = original_panel_name
83  while panel_name in panel_names:
84  # Handle same-name collision.
85  collisions_count += 1
86  panel_name = f"{original_panel_name} ({collisions_count})"
87  panel_names[panel_name] = panel_id
88 
89 
90 class ElmaxConfigFlow(ConfigFlow, domain=DOMAIN):
91  """Handle a config flow for elmax-cloud."""
92 
93  VERSION = 1
94  _client: Elmax
95  _selected_mode: str
96  _panel_pin: str
97  _panel_id: str
98 
99  # Direct API variables
100  _panel_direct_use_ssl: bool
101  _panel_direct_hostname: str
102  _panel_direct_port: int
103  _panel_direct_follow_mdns: bool
104  _panel_direct_ssl_cert: str | None
105  _panel_direct_http_port: int
106  _panel_direct_https_port: int
107 
108  # Cloud API variables
109  _cloud_username: str
110  _cloud_password: str
111  _reauth_cloud_username: str | None
112  _reauth_cloud_panelid: str | None
113 
114  # Panel selection variables
115  _panels_schema: vol.Schema
116  _panel_names: dict
117 
118  async def async_step_user(
119  self, user_input: dict[str, Any] | None = None
120  ) -> ConfigFlowResult:
121  """Handle the flow initiated by the user."""
122  return await self.async_step_choose_modeasync_step_choose_mode(user_input=user_input)
123 
125  self, user_input: dict[str, Any] | None = None
126  ) -> ConfigFlowResult:
127  """Handle local vs cloud mode selection step."""
128  return self.async_show_menuasync_show_menu(
129  step_id="choose_mode",
130  menu_options={
131  CONF_ELMAX_MODE_CLOUD: "Connect to Elmax Panel via Elmax Cloud APIs",
132  CONF_ELMAX_MODE_DIRECT: "Connect to Elmax Panel via local/direct IP",
133  },
134  )
135 
137  self, fallback_step_id: str, schema: vol.Schema
138  ) -> ConfigFlowResult:
139  return await self._test_direct_and_create_entry_test_direct_and_create_entry()
140 
142  """Test the direct connection to the Elmax panel and create and entry if successful."""
143  ssl_context = None
144  self._panel_direct_ssl_cert_panel_direct_ssl_cert = None
145  if self._panel_direct_use_ssl_panel_direct_use_ssl:
146  # Fetch the remote certificate.
147  # Local API is exposed via a self-signed SSL that we must add to our trust store.
148  self._panel_direct_ssl_cert_panel_direct_ssl_cert = (
149  await GenericElmax.retrieve_server_certificate(
150  hostname=self._panel_direct_hostname_panel_direct_hostname,
151  port=self._panel_direct_port_panel_direct_port,
152  )
153  )
154  ssl_context = build_direct_ssl_context(cadata=self._panel_direct_ssl_cert_panel_direct_ssl_cert)
155 
156  # Attempt the connection to make sure the pin works. Also, take the chance to retrieve the panel ID via APIs.
157  client_api_url = get_direct_api_url(
158  host=self._panel_direct_hostname_panel_direct_hostname,
159  port=self._panel_direct_port_panel_direct_port,
160  use_ssl=self._panel_direct_use_ssl_panel_direct_use_ssl,
161  )
162  client = ElmaxLocal(
163  panel_api_url=client_api_url,
164  panel_code=self._panel_pin_panel_pin,
165  ssl_context=ssl_context,
166  )
167  try:
168  await client.login()
169  except (ElmaxNetworkError, httpx.ConnectError, httpx.ConnectTimeout):
170  return self.async_show_formasync_show_formasync_show_form(
171  step_id=CONF_ELMAX_MODE_DIRECT,
172  data_schema=DIRECT_SETUP_SCHEMA,
173  errors={"base": "network_error"},
174  )
175  except ElmaxBadLoginError:
176  return self.async_show_formasync_show_formasync_show_form(
177  step_id=CONF_ELMAX_MODE_DIRECT,
178  data_schema=DIRECT_SETUP_SCHEMA,
179  errors={"base": "invalid_auth"},
180  )
181 
182  # Retrieve the current panel status. If this succeeds, it means the
183  # setup did complete successfully.
184  panel_status: PanelStatus = await client.get_current_panel_status()
185 
186  # Make sure this is the only Elmax integration for this specific panel id.
187  await self.async_set_unique_idasync_set_unique_id(panel_status.panel_id)
188  self._abort_if_unique_id_configured_abort_if_unique_id_configured()
189 
190  return await self._check_unique_and_create_entry_check_unique_and_create_entry(
191  unique_id=panel_status.panel_id,
192  title=f"Elmax Direct {panel_status.panel_id}",
193  data={
194  CONF_ELMAX_MODE: self._selected_mode_selected_mode,
195  CONF_ELMAX_MODE_DIRECT_HOST: self._panel_direct_hostname_panel_direct_hostname,
196  CONF_ELMAX_MODE_DIRECT_PORT: self._panel_direct_port_panel_direct_port,
197  CONF_ELMAX_MODE_DIRECT_SSL: self._panel_direct_use_ssl_panel_direct_use_ssl,
198  CONF_ELMAX_PANEL_PIN: self._panel_pin_panel_pin,
199  CONF_ELMAX_PANEL_ID: panel_status.panel_id,
200  CONF_ELMAX_MODE_DIRECT_SSL_CERT: self._panel_direct_ssl_cert_panel_direct_ssl_cert,
201  },
202  )
203 
204  async def async_step_direct(self, user_input: dict[str, Any]) -> ConfigFlowResult:
205  """Handle the direct setup step."""
206  self._selected_mode_selected_mode = CONF_ELMAX_MODE_DIRECT
207  if user_input is None:
208  return self.async_show_formasync_show_formasync_show_form(
209  step_id=CONF_ELMAX_MODE_DIRECT,
210  data_schema=DIRECT_SETUP_SCHEMA,
211  errors=None,
212  )
213 
214  self._panel_direct_hostname_panel_direct_hostname = user_input[CONF_ELMAX_MODE_DIRECT_HOST]
215  self._panel_direct_port_panel_direct_port = user_input[CONF_ELMAX_MODE_DIRECT_PORT]
216  self._panel_direct_use_ssl_panel_direct_use_ssl = user_input[CONF_ELMAX_MODE_DIRECT_SSL]
217  self._panel_pin_panel_pin = user_input[CONF_ELMAX_PANEL_PIN]
218  self._panel_direct_follow_mdns_panel_direct_follow_mdns = True
219 
220  tmp_schema = vol.Schema(
221  {
222  vol.Required(
223  CONF_ELMAX_MODE_DIRECT_HOST, default=self._panel_direct_hostname_panel_direct_hostname
224  ): str,
225  vol.Required(
226  CONF_ELMAX_MODE_DIRECT_PORT, default=self._panel_direct_port_panel_direct_port
227  ): int,
228  vol.Required(
229  CONF_ELMAX_MODE_DIRECT_SSL, default=self._panel_direct_use_ssl_panel_direct_use_ssl
230  ): bool,
231  vol.Required(CONF_ELMAX_PANEL_PIN, default=self._panel_pin_panel_pin): str,
232  }
233  )
234  return await self._handle_direct_and_create_entry_handle_direct_and_create_entry(
235  fallback_step_id=CONF_ELMAX_MODE_DIRECT, schema=tmp_schema
236  )
237 
239  self, user_input: dict[str, Any]
240  ) -> ConfigFlowResult:
241  """Handle the direct setup step triggered via zeroconf."""
242  if user_input is None:
243  return self.async_show_formasync_show_formasync_show_form(
244  step_id="zeroconf_setup",
245  data_schema=ZEROCONF_SETUP_SCHEMA,
246  errors=None,
247  )
248  self._panel_direct_use_ssl_panel_direct_use_ssl = user_input[CONF_ELMAX_MODE_DIRECT_SSL]
249  self._panel_direct_port_panel_direct_port = (
250  self._panel_direct_https_port_panel_direct_https_port
251  if self._panel_direct_use_ssl_panel_direct_use_ssl
252  else self._panel_direct_http_port_panel_direct_http_port
253  )
254  self._panel_pin_panel_pin = user_input[CONF_ELMAX_PANEL_PIN]
255  tmp_schema = vol.Schema(
256  {
257  vol.Required(CONF_ELMAX_PANEL_PIN, default=self._panel_pin_panel_pin): str,
258  vol.Required(
259  CONF_ELMAX_MODE_DIRECT_SSL, default=self._panel_direct_use_ssl_panel_direct_use_ssl
260  ): bool,
261  }
262  )
263  return await self._handle_direct_and_create_entry_handle_direct_and_create_entry(
264  fallback_step_id="zeroconf_setup", schema=tmp_schema
265  )
266 
268  self, unique_id: str, title: str, data: Mapping[str, Any]
269  ) -> ConfigFlowResult:
270  # Make sure this is the only Elmax integration for this specific panel id.
271  await self.async_set_unique_idasync_set_unique_id(unique_id)
272  self._abort_if_unique_id_configured_abort_if_unique_id_configured()
273 
274  return self.async_create_entryasync_create_entryasync_create_entry(
275  title=title,
276  data=data,
277  )
278 
279  async def async_step_cloud(self, user_input: dict[str, Any]) -> ConfigFlowResult:
280  """Handle the cloud setup flow."""
281  self._selected_mode_selected_mode = CONF_ELMAX_MODE_CLOUD
282 
283  # When invokes without parameters, show the login form.
284  if user_input is None:
285  return self.async_show_formasync_show_formasync_show_form(
286  step_id=CONF_ELMAX_MODE_CLOUD, data_schema=LOGIN_FORM_SCHEMA, errors={}
287  )
288 
289  # Otherwise, it means we are handling now the "submission" of the user form.
290  # In this case, let's try to log in to the Elmax cloud and retrieve the available panels.
291  username = user_input[CONF_ELMAX_USERNAME]
292  password = user_input[CONF_ELMAX_PASSWORD]
293  try:
294  client = await self._async_login_async_login(username=username, password=password)
295 
296  except ElmaxBadLoginError:
297  return self.async_show_formasync_show_formasync_show_form(
298  step_id=CONF_ELMAX_MODE_CLOUD,
299  data_schema=LOGIN_FORM_SCHEMA,
300  errors={"base": "invalid_auth"},
301  )
302  except ElmaxNetworkError:
303  _LOGGER.exception("A network error occurred")
304  return self.async_show_formasync_show_formasync_show_form(
305  step_id=CONF_ELMAX_MODE_CLOUD,
306  data_schema=LOGIN_FORM_SCHEMA,
307  errors={"base": "network_error"},
308  )
309 
310  # If the login succeeded, retrieve the list of available panels and filter the online ones
311  online_panels = [x for x in await client.list_control_panels() if x.online]
312 
313  # If no online panel was found, we display an error in the next UI.
314  if not online_panels:
315  return self.async_show_formasync_show_formasync_show_form(
316  step_id=CONF_ELMAX_MODE_CLOUD,
317  data_schema=LOGIN_FORM_SCHEMA,
318  errors={"base": "no_panel_online"},
319  )
320 
321  # Show the panel selection.
322  # We want the user to choose the panel using the associated name, we set up a mapping
323  # dictionary to handle that case.
324  panel_names: dict[str, str] = {}
325  username = client.get_authenticated_username()
326  for panel in online_panels:
328  panel=panel, username=username, panel_names=panel_names
329  )
330 
331  self._client_client = client
332  self._panel_names_panel_names = panel_names
333  schema = vol.Schema(
334  {
335  vol.Required(CONF_ELMAX_PANEL_NAME): vol.In(self._panel_names_panel_names.keys()),
336  vol.Required(CONF_ELMAX_PANEL_PIN, default="000000"): str,
337  }
338  )
339  self._panels_schema_panels_schema = schema
340  self._cloud_username_cloud_username = username
341  self._cloud_password_cloud_password = password
342  # If everything went OK, proceed to panel selection.
343  return await self.async_step_panelsasync_step_panels(user_input=None)
344 
345  async def async_step_panels(
346  self, user_input: dict[str, Any] | None = None
347  ) -> ConfigFlowResult:
348  """Handle Panel selection step."""
349  errors: dict[str, Any] = {}
350  if user_input is None:
351  return self.async_show_formasync_show_formasync_show_form(
352  step_id="panels", data_schema=self._panels_schema_panels_schema, errors=errors
353  )
354 
355  panel_name = user_input[CONF_ELMAX_PANEL_NAME]
356  panel_pin = user_input[CONF_ELMAX_PANEL_PIN]
357 
358  # Lookup the panel id from the panel name.
359  panel_id = self._panel_names_panel_names[panel_name]
360 
361  # Make sure this is the only elmax integration for this specific panel id.
362  await self.async_set_unique_idasync_set_unique_id(panel_id)
363  self._abort_if_unique_id_configured_abort_if_unique_id_configured()
364 
365  # Try to list all the devices using the given PIN.
366  try:
367  await self._client_client.get_panel_status(
368  control_panel_id=panel_id, pin=panel_pin
369  )
370  except ElmaxBadPinError:
371  errors["base"] = "invalid_pin"
372  except Exception:
373  _LOGGER.exception("Error occurred")
374  errors["base"] = "unknown"
375 
376  if errors:
377  return self.async_show_formasync_show_formasync_show_form(
378  step_id="panels", data_schema=self._panels_schema_panels_schema, errors=errors
379  )
380 
381  return await self._check_unique_and_create_entry_check_unique_and_create_entry(
382  unique_id=panel_id,
383  title=f"Elmax cloud {panel_name}",
384  data={
385  CONF_ELMAX_MODE: CONF_ELMAX_MODE_CLOUD,
386  CONF_ELMAX_PANEL_ID: panel_id,
387  CONF_ELMAX_PANEL_PIN: panel_pin,
388  CONF_ELMAX_USERNAME: self._cloud_username_cloud_username,
389  CONF_ELMAX_PASSWORD: self._cloud_password_cloud_password,
390  },
391  )
392 
393  async def async_step_reauth(
394  self, entry_data: Mapping[str, Any]
395  ) -> ConfigFlowResult:
396  """Perform reauth upon an API authentication error."""
397  self._reauth_cloud_username_reauth_cloud_username = entry_data.get(CONF_ELMAX_USERNAME)
398  self._reauth_cloud_panelid_reauth_cloud_panelid = entry_data.get(CONF_ELMAX_PANEL_ID)
399  return await self.async_step_reauth_confirmasync_step_reauth_confirm()
400 
402  self, user_input: dict[str, Any] | None = None
403  ) -> ConfigFlowResult:
404  """Handle reauthorization flow."""
405  errors = {}
406  if user_input is not None:
407  username = user_input[CONF_ELMAX_USERNAME]
408  password = user_input[CONF_ELMAX_PASSWORD]
409  panel_pin = user_input[CONF_ELMAX_PANEL_PIN]
410  await self.async_set_unique_idasync_set_unique_id(self._reauth_cloud_panelid_reauth_cloud_panelid)
411 
412  # Handle authentication, make sure the panel we are re-authenticating against is listed among results
413  # and verify its pin is correct.
414  reauth_entry = self._get_reauth_entry_get_reauth_entry()
415  try:
416  # Test login.
417  client = await self._async_login_async_login(username=username, password=password)
418  # Make sure the panel we are authenticating to is still available.
419  panels = [
420  p
421  for p in await client.list_control_panels()
422  if p.hash == reauth_entry.data[CONF_ELMAX_PANEL_ID]
423  ]
424  if len(panels) < 1:
425  raise NoOnlinePanelsError # noqa: TRY301
426 
427  # Verify the pin is still valid.
428  await client.get_panel_status(
429  control_panel_id=reauth_entry.data[CONF_ELMAX_PANEL_ID],
430  pin=panel_pin,
431  )
432 
433  except ElmaxBadLoginError:
434  errors["base"] = "invalid_auth"
435  except NoOnlinePanelsError:
436  errors["base"] = "reauth_panel_disappeared"
437  except ElmaxBadPinError:
438  errors["base"] = "invalid_pin"
439 
440  # If all went right, update the config entry
441  else:
442  return self.async_update_reload_and_abortasync_update_reload_and_abort(
443  reauth_entry,
444  data={
445  CONF_ELMAX_PANEL_ID: reauth_entry.data[CONF_ELMAX_PANEL_ID],
446  CONF_ELMAX_PANEL_PIN: panel_pin,
447  CONF_ELMAX_USERNAME: username,
448  CONF_ELMAX_PASSWORD: password,
449  },
450  )
451 
452  # Otherwise start over and show the relative error message
453  return self.async_show_formasync_show_formasync_show_form(
454  step_id="reauth_confirm", data_schema=REAUTH_FORM_SCHEMA, errors=errors
455  )
456 
458  self,
459  local_id: str,
460  remote_id: str | None,
461  host: str,
462  https_port: int,
463  http_port: int,
464  ) -> ConfigFlowResult | None:
465  # Look for another entry with the same PANEL_ID (local or remote).
466  # If there already is a matching panel, take the change to notify the Coordinator
467  # so that it uses the newly discovered IP address. This mitigates the issues
468  # arising with DHCP and IP changes of the panels.
469  for entry in self._async_current_entries_async_current_entries(include_ignore=False):
470  if entry.data[CONF_ELMAX_PANEL_ID] in (local_id, remote_id):
471  # If the discovery finds another entry with the same ID, skip the notification.
472  # However, if the discovery finds a new host for a panel that was already registered
473  # for a given host (leave PORT comparison aside as we don't want to get notified twice
474  # for HTTP and HTTPS), update the entry so that the integration "follows" the DHCP IP.
475  if (
476  entry.data.get(CONF_ELMAX_MODE, CONF_ELMAX_MODE_CLOUD)
477  == CONF_ELMAX_MODE_DIRECT
478  and entry.data[CONF_ELMAX_MODE_DIRECT_HOST] != host
479  ):
480  new_data: dict[str, Any] = {}
481  new_data.update(entry.data)
482  new_data[CONF_ELMAX_MODE_DIRECT_HOST] = host
483  new_data[CONF_ELMAX_MODE_DIRECT_PORT] = (
484  https_port
485  if entry.data[CONF_ELMAX_MODE_DIRECT_SSL]
486  else http_port
487  )
488  self.hass.config_entries.async_update_entry(
489  entry, unique_id=entry.unique_id, data=new_data
490  )
491  # Abort the configuration, as there already is an entry for this PANEL-ID.
492  return self.async_abortasync_abortasync_abort(reason="already_configured")
493  return None
494 
496  self, discovery_info: ZeroconfServiceInfo
497  ) -> ConfigFlowResult:
498  """Handle device found via zeroconf."""
499  host = discovery_info.host
500  https_port = (
501  int(discovery_info.port)
502  if discovery_info.port is not None
503  else ELMAX_MODE_DIRECT_DEFAULT_HTTPS_PORT
504  )
505  plain_http_port = discovery_info.properties.get(
506  "http_port", ELMAX_MODE_DIRECT_DEFAULT_HTTP_PORT
507  )
508  plain_http_port = int(plain_http_port)
509  local_id = discovery_info.properties.get("idl")
510  remote_id = discovery_info.properties.get("idr")
511  v2api_version = discovery_info.properties.get("v2")
512 
513  # Only deal with panels exposing v2 version
514  if not check_local_version_supported(v2api_version):
515  return self.async_abortasync_abortasync_abort(reason="not_supported")
516 
517  # Handle the discovered panel info. This is useful especially if the panel
518  # changes its IP address while remaining perfectly configured.
519  if (
520  local_id is not None
521  and (
522  abort_result := await self._async_handle_entry_match_async_handle_entry_match(
523  local_id, remote_id, host, https_port, plain_http_port
524  )
525  )
526  is not None
527  ):
528  return abort_result
529 
530  self._selected_mode_selected_mode = CONF_ELMAX_MODE_DIRECT
531  self._panel_direct_hostname_panel_direct_hostname = host
532  self._panel_direct_https_port_panel_direct_https_port = https_port
533  self._panel_direct_http_port_panel_direct_http_port = plain_http_port
534  self._panel_direct_follow_mdns_panel_direct_follow_mdns = True
535 
536  return self.async_show_formasync_show_formasync_show_form(
537  step_id="zeroconf_setup", data_schema=ZEROCONF_SETUP_SCHEMA
538  )
539 
540  @staticmethod
541  async def _async_login(username: str, password: str) -> Elmax:
542  """Log in to the Elmax cloud and return the http client."""
543  client = Elmax(username=username, password=password)
544  await client.login()
545  return client
546 
547 
549  """Error occurring when no online panel was found."""
ConfigFlowResult async_step_zeroconf_setup(self, dict[str, Any] user_input)
Definition: config_flow.py:240
ConfigFlowResult async_step_direct(self, dict[str, Any] user_input)
Definition: config_flow.py:204
ConfigFlowResult async_step_panels(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:347
ConfigFlowResult async_step_choose_mode(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:126
ConfigFlowResult async_step_cloud(self, dict[str, Any] user_input)
Definition: config_flow.py:279
ConfigFlowResult async_step_reauth(self, Mapping[str, Any] entry_data)
Definition: config_flow.py:395
ConfigFlowResult _check_unique_and_create_entry(self, str unique_id, str title, Mapping[str, Any] data)
Definition: config_flow.py:269
ConfigFlowResult async_step_reauth_confirm(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:403
ConfigFlowResult _handle_direct_and_create_entry(self, str fallback_step_id, vol.Schema schema)
Definition: config_flow.py:138
ConfigFlowResult|None _async_handle_entry_match(self, str local_id, str|None remote_id, str host, int https_port, int http_port)
Definition: config_flow.py:464
ConfigFlowResult async_step_zeroconf(self, ZeroconfServiceInfo discovery_info)
Definition: config_flow.py:497
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:120
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_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_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_show_menu(self, *str|None step_id=None, Container[str] menu_options, Mapping[str, str]|None description_placeholders=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)
bool check_local_version_supported(str|None api_version)
Definition: common.py:28
ssl.SSLContext build_direct_ssl_context(str cadata)
Definition: common.py:19
str get_direct_api_url(str host, int port, bool use_ssl)
Definition: common.py:13
None _store_panel_by_name(PanelEntry panel, str username, dict[str, str] panel_names)
Definition: config_flow.py:78