Home Assistant Unofficial Reference 2024.12.1
config_flow.py
Go to the documentation of this file.
1 """Config flow for Z-Wave JS integration."""
2 
3 from __future__ import annotations
4 
5 from abc import ABC, abstractmethod
6 import asyncio
7 import logging
8 from typing import Any
9 
10 import aiohttp
11 from serial.tools import list_ports
12 import voluptuous as vol
13 from zwave_js_server.version import VersionInfo, get_server_version
14 
15 from homeassistant.components import usb
17  AddonError,
18  AddonInfo,
19  AddonManager,
20  AddonState,
21 )
22 from homeassistant.components.zeroconf import ZeroconfServiceInfo
23 from homeassistant.config_entries import (
24  SOURCE_USB,
25  ConfigEntriesFlowManager,
26  ConfigEntry,
27  ConfigEntryBaseFlow,
28  ConfigEntryState,
29  ConfigFlow,
30  ConfigFlowContext,
31  ConfigFlowResult,
32  OptionsFlow,
33  OptionsFlowManager,
34 )
35 from homeassistant.const import CONF_NAME, CONF_URL
36 from homeassistant.core import HomeAssistant, callback
37 from homeassistant.data_entry_flow import AbortFlow, FlowManager
38 from homeassistant.exceptions import HomeAssistantError
39 from homeassistant.helpers.aiohttp_client import async_get_clientsession
40 from homeassistant.helpers.hassio import is_hassio
41 from homeassistant.helpers.service_info.hassio import HassioServiceInfo
42 from homeassistant.helpers.typing import VolDictType
43 
44 from . import disconnect_client
45 from .addon import get_addon_manager
46 from .const import (
47  ADDON_SLUG,
48  CONF_ADDON_DEVICE,
49  CONF_ADDON_EMULATE_HARDWARE,
50  CONF_ADDON_LOG_LEVEL,
51  CONF_ADDON_LR_S2_ACCESS_CONTROL_KEY,
52  CONF_ADDON_LR_S2_AUTHENTICATED_KEY,
53  CONF_ADDON_NETWORK_KEY,
54  CONF_ADDON_S0_LEGACY_KEY,
55  CONF_ADDON_S2_ACCESS_CONTROL_KEY,
56  CONF_ADDON_S2_AUTHENTICATED_KEY,
57  CONF_ADDON_S2_UNAUTHENTICATED_KEY,
58  CONF_INTEGRATION_CREATED_ADDON,
59  CONF_LR_S2_ACCESS_CONTROL_KEY,
60  CONF_LR_S2_AUTHENTICATED_KEY,
61  CONF_S0_LEGACY_KEY,
62  CONF_S2_ACCESS_CONTROL_KEY,
63  CONF_S2_AUTHENTICATED_KEY,
64  CONF_S2_UNAUTHENTICATED_KEY,
65  CONF_USB_PATH,
66  CONF_USE_ADDON,
67  DOMAIN,
68 )
69 
70 _LOGGER = logging.getLogger(__name__)
71 
72 DEFAULT_URL = "ws://localhost:3000"
73 TITLE = "Z-Wave JS"
74 
75 ADDON_SETUP_TIMEOUT = 5
76 ADDON_SETUP_TIMEOUT_ROUNDS = 40
77 CONF_EMULATE_HARDWARE = "emulate_hardware"
78 CONF_LOG_LEVEL = "log_level"
79 SERVER_VERSION_TIMEOUT = 10
80 
81 ADDON_LOG_LEVELS = {
82  "error": "Error",
83  "warn": "Warn",
84  "info": "Info",
85  "verbose": "Verbose",
86  "debug": "Debug",
87  "silly": "Silly",
88 }
89 ADDON_USER_INPUT_MAP = {
90  CONF_ADDON_DEVICE: CONF_USB_PATH,
91  CONF_ADDON_S0_LEGACY_KEY: CONF_S0_LEGACY_KEY,
92  CONF_ADDON_S2_ACCESS_CONTROL_KEY: CONF_S2_ACCESS_CONTROL_KEY,
93  CONF_ADDON_S2_AUTHENTICATED_KEY: CONF_S2_AUTHENTICATED_KEY,
94  CONF_ADDON_S2_UNAUTHENTICATED_KEY: CONF_S2_UNAUTHENTICATED_KEY,
95  CONF_ADDON_LR_S2_ACCESS_CONTROL_KEY: CONF_LR_S2_ACCESS_CONTROL_KEY,
96  CONF_ADDON_LR_S2_AUTHENTICATED_KEY: CONF_LR_S2_AUTHENTICATED_KEY,
97  CONF_ADDON_LOG_LEVEL: CONF_LOG_LEVEL,
98  CONF_ADDON_EMULATE_HARDWARE: CONF_EMULATE_HARDWARE,
99 }
100 
101 ON_SUPERVISOR_SCHEMA = vol.Schema({vol.Optional(CONF_USE_ADDON, default=True): bool})
102 
103 
104 def get_manual_schema(user_input: dict[str, Any]) -> vol.Schema:
105  """Return a schema for the manual step."""
106  default_url = user_input.get(CONF_URL, DEFAULT_URL)
107  return vol.Schema({vol.Required(CONF_URL, default=default_url): str})
108 
109 
110 def get_on_supervisor_schema(user_input: dict[str, Any]) -> vol.Schema:
111  """Return a schema for the on Supervisor step."""
112  default_use_addon = user_input[CONF_USE_ADDON]
113  return vol.Schema({vol.Optional(CONF_USE_ADDON, default=default_use_addon): bool})
114 
115 
116 async def validate_input(hass: HomeAssistant, user_input: dict) -> VersionInfo:
117  """Validate if the user input allows us to connect."""
118  ws_address = user_input[CONF_URL]
119 
120  if not ws_address.startswith(("ws://", "wss://")):
121  raise InvalidInput("invalid_ws_url")
122 
123  try:
124  return await async_get_version_info(hass, ws_address)
125  except CannotConnect as err:
126  raise InvalidInput("cannot_connect") from err
127 
128 
129 async def async_get_version_info(hass: HomeAssistant, ws_address: str) -> VersionInfo:
130  """Return Z-Wave JS version info."""
131  try:
132  async with asyncio.timeout(SERVER_VERSION_TIMEOUT):
133  version_info: VersionInfo = await get_server_version(
134  ws_address, async_get_clientsession(hass)
135  )
136  except (TimeoutError, aiohttp.ClientError) as err:
137  # We don't want to spam the log if the add-on isn't started
138  # or takes a long time to start.
139  _LOGGER.debug("Failed to connect to Z-Wave JS server: %s", err)
140  raise CannotConnect from err
141 
142  return version_info
143 
144 
145 def get_usb_ports() -> dict[str, str]:
146  """Return a dict of USB ports and their friendly names."""
147  ports = list_ports.comports()
148  port_descriptions = {}
149  for port in ports:
150  vid: str | None = None
151  pid: str | None = None
152  if port.vid is not None and port.pid is not None:
153  usb_device = usb.usb_device_from_port(port)
154  vid = usb_device.vid
155  pid = usb_device.pid
156  dev_path = usb.get_serial_by_id(port.device)
157  human_name = usb.human_readable_device_name(
158  dev_path,
159  port.serial_number,
160  port.manufacturer,
161  port.description,
162  vid,
163  pid,
164  )
165  port_descriptions[dev_path] = human_name
166  return port_descriptions
167 
168 
169 async def async_get_usb_ports(hass: HomeAssistant) -> dict[str, str]:
170  """Return a dict of USB ports and their friendly names."""
171  return await hass.async_add_executor_job(get_usb_ports)
172 
173 
175  """Represent the base config flow for Z-Wave JS."""
176 
177  def __init__(self) -> None:
178  """Set up flow instance."""
179  self.s0_legacy_key: str | None = None
180  self.s2_access_control_key: str | None = None
181  self.s2_authenticated_key: str | None = None
182  self.s2_unauthenticated_key: str | None = None
183  self.lr_s2_access_control_key: str | None = None
184  self.lr_s2_authenticated_key: str | None = None
185  self.usb_path: str | None = None
186  self.ws_addressws_address: str | None = None
187  self.restart_addon: bool = False
188  # If we install the add-on we should uninstall it on entry remove.
189  self.integration_created_addonintegration_created_addon = False
190  self.install_taskinstall_task: asyncio.Task | None = None
191  self.start_taskstart_task: asyncio.Task | None = None
192  self.version_infoversion_info: VersionInfo | None = None
193 
194  @property
195  @abstractmethod
196  def flow_manager(self) -> FlowManager[ConfigFlowContext, ConfigFlowResult]:
197  """Return the flow manager of the flow."""
198 
200  self, user_input: dict[str, Any] | None = None
201  ) -> ConfigFlowResult:
202  """Install Z-Wave JS add-on."""
203  if not self.install_task:
204  self.install_taskinstall_task = self.hass.async_create_task(self._async_install_addon_async_install_addon())
205 
206  if not self.install_taskinstall_task.done():
207  return self.async_show_progressasync_show_progress(
208  step_id="install_addon",
209  progress_action="install_addon",
210  progress_task=self.install_taskinstall_task,
211  )
212 
213  try:
214  await self.install_taskinstall_task
215  except AddonError as err:
216  _LOGGER.error(err)
217  return self.async_show_progress_doneasync_show_progress_done(next_step_id="install_failed")
218  finally:
219  self.install_taskinstall_task = None
220 
221  self.integration_created_addonintegration_created_addon = True
222 
223  return self.async_show_progress_doneasync_show_progress_done(next_step_id="configure_addon")
224 
226  self, user_input: dict[str, Any] | None = None
227  ) -> ConfigFlowResult:
228  """Add-on installation failed."""
229  return self.async_abortasync_abort(reason="addon_install_failed")
230 
232  self, user_input: dict[str, Any] | None = None
233  ) -> ConfigFlowResult:
234  """Start Z-Wave JS add-on."""
235  if not self.start_taskstart_task:
236  self.start_taskstart_task = self.hass.async_create_task(self._async_start_addon_async_start_addon())
237 
238  if not self.start_taskstart_task.done():
239  return self.async_show_progressasync_show_progress(
240  step_id="start_addon",
241  progress_action="start_addon",
242  progress_task=self.start_taskstart_task,
243  )
244 
245  try:
246  await self.start_taskstart_task
247  except (CannotConnect, AddonError, AbortFlow) as err:
248  _LOGGER.error(err)
249  return self.async_show_progress_doneasync_show_progress_done(next_step_id="start_failed")
250  finally:
251  self.start_taskstart_task = None
252 
253  return self.async_show_progress_doneasync_show_progress_done(next_step_id="finish_addon_setup")
254 
256  self, user_input: dict[str, Any] | None = None
257  ) -> ConfigFlowResult:
258  """Add-on start failed."""
259  return self.async_abortasync_abort(reason="addon_start_failed")
260 
261  async def _async_start_addon(self) -> None:
262  """Start the Z-Wave JS add-on."""
263  addon_manager: AddonManager = get_addon_manager(self.hass)
264  self.version_infoversion_info = None
265  if self.restart_addon:
266  await addon_manager.async_schedule_restart_addon()
267  else:
268  await addon_manager.async_schedule_start_addon()
269  # Sleep some seconds to let the add-on start properly before connecting.
270  for _ in range(ADDON_SETUP_TIMEOUT_ROUNDS):
271  await asyncio.sleep(ADDON_SETUP_TIMEOUT)
272  try:
273  if not self.ws_addressws_address:
274  discovery_info = await self._async_get_addon_discovery_info_async_get_addon_discovery_info()
275  self.ws_addressws_address = (
276  f"ws://{discovery_info['host']}:{discovery_info['port']}"
277  )
278  self.version_infoversion_info = await async_get_version_info(
279  self.hass, self.ws_addressws_address
280  )
281  except (AbortFlow, CannotConnect) as err:
282  _LOGGER.debug(
283  "Add-on not ready yet, waiting %s seconds: %s",
284  ADDON_SETUP_TIMEOUT,
285  err,
286  )
287  else:
288  break
289  else:
290  raise CannotConnect("Failed to start Z-Wave JS add-on: timeout")
291 
292  @abstractmethod
294  self, user_input: dict[str, Any] | None = None
295  ) -> ConfigFlowResult:
296  """Ask for config for Z-Wave JS add-on."""
297 
298  @abstractmethod
300  self, user_input: dict[str, Any] | None = None
301  ) -> ConfigFlowResult:
302  """Prepare info needed to complete the config entry.
303 
304  Get add-on discovery info and server version info.
305  Set unique id and abort if already configured.
306  """
307 
308  async def _async_get_addon_info(self) -> AddonInfo:
309  """Return and cache Z-Wave JS add-on info."""
310  addon_manager: AddonManager = get_addon_manager(self.hass)
311  try:
312  addon_info: AddonInfo = await addon_manager.async_get_addon_info()
313  except AddonError as err:
314  _LOGGER.error(err)
315  raise AbortFlow("addon_info_failed") from err
316 
317  return addon_info
318 
319  async def _async_set_addon_config(self, config: dict) -> None:
320  """Set Z-Wave JS add-on config."""
321  addon_manager: AddonManager = get_addon_manager(self.hass)
322  try:
323  await addon_manager.async_set_addon_options(config)
324  except AddonError as err:
325  _LOGGER.error(err)
326  raise AbortFlow("addon_set_config_failed") from err
327 
328  async def _async_install_addon(self) -> None:
329  """Install the Z-Wave JS add-on."""
330  addon_manager: AddonManager = get_addon_manager(self.hass)
331  await addon_manager.async_schedule_install_addon()
332 
333  async def _async_get_addon_discovery_info(self) -> dict:
334  """Return add-on discovery info."""
335  addon_manager: AddonManager = get_addon_manager(self.hass)
336  try:
337  discovery_info_config = await addon_manager.async_get_addon_discovery_info()
338  except AddonError as err:
339  _LOGGER.error(err)
340  raise AbortFlow("addon_get_discovery_info_failed") from err
341 
342  return discovery_info_config
343 
344 
346  """Handle a config flow for Z-Wave JS."""
347 
348  VERSION = 1
349 
350  _title: str
351 
352  def __init__(self) -> None:
353  """Set up flow instance."""
354  super().__init__()
355  self.use_addonuse_addon = False
356  self._usb_discovery_usb_discovery = False
357 
358  @property
359  def flow_manager(self) -> ConfigEntriesFlowManager:
360  """Return the correct flow manager."""
361  return self.hass.config_entries.flow
362 
363  @staticmethod
364  @callback
366  config_entry: ConfigEntry,
367  ) -> OptionsFlowHandler:
368  """Return the options flow."""
369  return OptionsFlowHandler()
370 
371  async def async_step_user(
372  self, user_input: dict[str, Any] | None = None
373  ) -> ConfigFlowResult:
374  """Handle the initial step."""
375  if is_hassio(self.hass):
376  return await self.async_step_on_supervisorasync_step_on_supervisor()
377 
378  return await self.async_step_manualasync_step_manual()
379 
381  self, discovery_info: ZeroconfServiceInfo
382  ) -> ConfigFlowResult:
383  """Handle zeroconf discovery."""
384  home_id = str(discovery_info.properties["homeId"])
385  await self.async_set_unique_idasync_set_unique_id(home_id)
386  self._abort_if_unique_id_configured_abort_if_unique_id_configured()
387  self.ws_addressws_addressws_address = f"ws://{discovery_info.host}:{discovery_info.port}"
388  self.context.update({"title_placeholders": {CONF_NAME: home_id}})
389  return await self.async_step_zeroconf_confirmasync_step_zeroconf_confirm()
390 
392  self, user_input: dict | None = None
393  ) -> ConfigFlowResult:
394  """Confirm the setup."""
395  if user_input is not None:
396  return await self.async_step_manualasync_step_manual({CONF_URL: self.ws_addressws_addressws_address})
397 
398  assert self.ws_addressws_addressws_address
399  assert self.unique_idunique_id
400  return self.async_show_formasync_show_formasync_show_form(
401  step_id="zeroconf_confirm",
402  description_placeholders={
403  "home_id": self.unique_idunique_id,
404  CONF_URL: self.ws_addressws_addressws_address[5:],
405  },
406  )
407 
408  async def async_step_usb(
409  self, discovery_info: usb.UsbServiceInfo
410  ) -> ConfigFlowResult:
411  """Handle USB Discovery."""
412  if not is_hassio(self.hass):
413  return self.async_abortasync_abortasync_abort(reason="discovery_requires_supervisor")
414  if self._async_current_entries_async_current_entries():
415  return self.async_abortasync_abortasync_abort(reason="already_configured")
416  if self._async_in_progress_async_in_progress():
417  return self.async_abortasync_abortasync_abort(reason="already_in_progress")
418 
419  vid = discovery_info.vid
420  pid = discovery_info.pid
421  serial_number = discovery_info.serial_number
422  manufacturer = discovery_info.manufacturer
423  description = discovery_info.description
424  # Zooz uses this vid/pid, but so do 2652 sticks
425  if vid == "10C4" and pid == "EA60" and description and "2652" in description:
426  return self.async_abortasync_abortasync_abort(reason="not_zwave_device")
427 
428  addon_info = await self._async_get_addon_info_async_get_addon_info()
429  if addon_info.state not in (AddonState.NOT_INSTALLED, AddonState.NOT_RUNNING):
430  return self.async_abortasync_abortasync_abort(reason="already_configured")
431 
432  await self.async_set_unique_idasync_set_unique_id(
433  f"{vid}:{pid}_{serial_number}_{manufacturer}_{description}"
434  )
435  self._abort_if_unique_id_configured_abort_if_unique_id_configured()
436  dev_path = discovery_info.device
437  self.usb_pathusb_path = dev_path
438  self._title_title = usb.human_readable_device_name(
439  dev_path,
440  serial_number,
441  manufacturer,
442  description,
443  vid,
444  pid,
445  )
446  self.context["title_placeholders"] = {
447  CONF_NAME: self._title_title.split(" - ")[0].strip()
448  }
449  return await self.async_step_usb_confirmasync_step_usb_confirm()
450 
452  self, user_input: dict[str, Any] | None = None
453  ) -> ConfigFlowResult:
454  """Handle USB Discovery confirmation."""
455  if user_input is None:
456  return self.async_show_formasync_show_formasync_show_form(
457  step_id="usb_confirm",
458  description_placeholders={CONF_NAME: self._title_title},
459  )
460 
461  self._usb_discovery_usb_discovery = True
462 
463  return await self.async_step_on_supervisorasync_step_on_supervisor({CONF_USE_ADDON: True})
464 
465  async def async_step_manual(
466  self, user_input: dict[str, Any] | None = None
467  ) -> ConfigFlowResult:
468  """Handle a manual configuration."""
469  if user_input is None:
470  return self.async_show_formasync_show_formasync_show_form(
471  step_id="manual", data_schema=get_manual_schema({})
472  )
473 
474  errors = {}
475 
476  try:
477  version_info = await validate_input(self.hass, user_input)
478  except InvalidInput as err:
479  errors["base"] = err.error
480  except Exception:
481  _LOGGER.exception("Unexpected exception")
482  errors["base"] = "unknown"
483  else:
484  await self.async_set_unique_idasync_set_unique_id(
485  str(version_info.home_id), raise_on_progress=False
486  )
487  # Make sure we disable any add-on handling
488  # if the controller is reconfigured in a manual step.
489  self._abort_if_unique_id_configured_abort_if_unique_id_configured(
490  updates={
491  **user_input,
492  CONF_USE_ADDON: False,
493  CONF_INTEGRATION_CREATED_ADDON: False,
494  }
495  )
496  self.ws_addressws_addressws_address = user_input[CONF_URL]
497  return self._async_create_entry_from_vars_async_create_entry_from_vars()
498 
499  return self.async_show_formasync_show_formasync_show_form(
500  step_id="manual", data_schema=get_manual_schema(user_input), errors=errors
501  )
502 
503  async def async_step_hassio(
504  self, discovery_info: HassioServiceInfo
505  ) -> ConfigFlowResult:
506  """Receive configuration from add-on discovery info.
507 
508  This flow is triggered by the Z-Wave JS add-on.
509  """
510  if self._async_in_progress_async_in_progress():
511  return self.async_abortasync_abortasync_abort(reason="already_in_progress")
512 
513  if discovery_info.slug != ADDON_SLUG:
514  return self.async_abortasync_abortasync_abort(reason="not_zwave_js_addon")
515 
516  self.ws_addressws_addressws_address = (
517  f"ws://{discovery_info.config['host']}:{discovery_info.config['port']}"
518  )
519  try:
520  version_info = await async_get_version_info(self.hass, self.ws_addressws_addressws_address)
521  except CannotConnect:
522  return self.async_abortasync_abortasync_abort(reason="cannot_connect")
523 
524  await self.async_set_unique_idasync_set_unique_id(str(version_info.home_id))
525  self._abort_if_unique_id_configured_abort_if_unique_id_configured(updates={CONF_URL: self.ws_addressws_addressws_address})
526 
527  return await self.async_step_hassio_confirmasync_step_hassio_confirm()
528 
530  self, user_input: dict[str, Any] | None = None
531  ) -> ConfigFlowResult:
532  """Confirm the add-on discovery."""
533  if user_input is not None:
534  return await self.async_step_on_supervisorasync_step_on_supervisor(
535  user_input={CONF_USE_ADDON: True}
536  )
537 
538  return self.async_show_formasync_show_formasync_show_form(step_id="hassio_confirm")
539 
541  self, user_input: dict[str, Any] | None = None
542  ) -> ConfigFlowResult:
543  """Handle logic when on Supervisor host."""
544  if user_input is None:
545  return self.async_show_formasync_show_formasync_show_form(
546  step_id="on_supervisor", data_schema=ON_SUPERVISOR_SCHEMA
547  )
548  if not user_input[CONF_USE_ADDON]:
549  return await self.async_step_manualasync_step_manual()
550 
551  self.use_addonuse_addon = True
552 
553  addon_info = await self._async_get_addon_info_async_get_addon_info()
554 
555  if addon_info.state == AddonState.RUNNING:
556  addon_config = addon_info.options
557  self.usb_pathusb_path = addon_config[CONF_ADDON_DEVICE]
558  self.s0_legacy_keys0_legacy_key = addon_config.get(CONF_ADDON_S0_LEGACY_KEY, "")
559  self.s2_access_control_keys2_access_control_key = addon_config.get(
560  CONF_ADDON_S2_ACCESS_CONTROL_KEY, ""
561  )
562  self.s2_authenticated_keys2_authenticated_key = addon_config.get(
563  CONF_ADDON_S2_AUTHENTICATED_KEY, ""
564  )
565  self.s2_unauthenticated_keys2_unauthenticated_key = addon_config.get(
566  CONF_ADDON_S2_UNAUTHENTICATED_KEY, ""
567  )
568  self.lr_s2_access_control_keylr_s2_access_control_key = addon_config.get(
569  CONF_ADDON_LR_S2_ACCESS_CONTROL_KEY, ""
570  )
571  self.lr_s2_authenticated_keylr_s2_authenticated_key = addon_config.get(
572  CONF_ADDON_LR_S2_AUTHENTICATED_KEY, ""
573  )
574  return await self.async_step_finish_addon_setupasync_step_finish_addon_setupasync_step_finish_addon_setup()
575 
576  if addon_info.state == AddonState.NOT_RUNNING:
577  return await self.async_step_configure_addonasync_step_configure_addonasync_step_configure_addon()
578 
579  return await self.async_step_install_addonasync_step_install_addon()
580 
582  self, user_input: dict[str, Any] | None = None
583  ) -> ConfigFlowResult:
584  """Ask for config for Z-Wave JS add-on."""
585  addon_info = await self._async_get_addon_info_async_get_addon_info()
586  addon_config = addon_info.options
587 
588  if user_input is not None:
589  self.s0_legacy_keys0_legacy_key = user_input[CONF_S0_LEGACY_KEY]
590  self.s2_access_control_keys2_access_control_key = user_input[CONF_S2_ACCESS_CONTROL_KEY]
591  self.s2_authenticated_keys2_authenticated_key = user_input[CONF_S2_AUTHENTICATED_KEY]
592  self.s2_unauthenticated_keys2_unauthenticated_key = user_input[CONF_S2_UNAUTHENTICATED_KEY]
593  self.lr_s2_access_control_keylr_s2_access_control_key = user_input[CONF_LR_S2_ACCESS_CONTROL_KEY]
594  self.lr_s2_authenticated_keylr_s2_authenticated_key = user_input[CONF_LR_S2_AUTHENTICATED_KEY]
595  if not self._usb_discovery_usb_discovery:
596  self.usb_pathusb_path = user_input[CONF_USB_PATH]
597 
598  new_addon_config = {
599  **addon_config,
600  CONF_ADDON_DEVICE: self.usb_pathusb_path,
601  CONF_ADDON_S0_LEGACY_KEY: self.s0_legacy_keys0_legacy_key,
602  CONF_ADDON_S2_ACCESS_CONTROL_KEY: self.s2_access_control_keys2_access_control_key,
603  CONF_ADDON_S2_AUTHENTICATED_KEY: self.s2_authenticated_keys2_authenticated_key,
604  CONF_ADDON_S2_UNAUTHENTICATED_KEY: self.s2_unauthenticated_keys2_unauthenticated_key,
605  CONF_ADDON_LR_S2_ACCESS_CONTROL_KEY: self.lr_s2_access_control_keylr_s2_access_control_key,
606  CONF_ADDON_LR_S2_AUTHENTICATED_KEY: self.lr_s2_authenticated_keylr_s2_authenticated_key,
607  }
608 
609  if new_addon_config != addon_config:
610  await self._async_set_addon_config_async_set_addon_config(new_addon_config)
611 
612  return await self.async_step_start_addonasync_step_start_addon()
613 
614  usb_path = self.usb_pathusb_path or addon_config.get(CONF_ADDON_DEVICE) or ""
615  s0_legacy_key = addon_config.get(
616  CONF_ADDON_S0_LEGACY_KEY, self.s0_legacy_keys0_legacy_key or ""
617  )
618  s2_access_control_key = addon_config.get(
619  CONF_ADDON_S2_ACCESS_CONTROL_KEY, self.s2_access_control_keys2_access_control_key or ""
620  )
621  s2_authenticated_key = addon_config.get(
622  CONF_ADDON_S2_AUTHENTICATED_KEY, self.s2_authenticated_keys2_authenticated_key or ""
623  )
624  s2_unauthenticated_key = addon_config.get(
625  CONF_ADDON_S2_UNAUTHENTICATED_KEY, self.s2_unauthenticated_keys2_unauthenticated_key or ""
626  )
627  lr_s2_access_control_key = addon_config.get(
628  CONF_ADDON_LR_S2_ACCESS_CONTROL_KEY, self.lr_s2_access_control_keylr_s2_access_control_key or ""
629  )
630  lr_s2_authenticated_key = addon_config.get(
631  CONF_ADDON_LR_S2_AUTHENTICATED_KEY, self.lr_s2_authenticated_keylr_s2_authenticated_key or ""
632  )
633 
634  schema: VolDictType = {
635  vol.Optional(CONF_S0_LEGACY_KEY, default=s0_legacy_key): str,
636  vol.Optional(
637  CONF_S2_ACCESS_CONTROL_KEY, default=s2_access_control_key
638  ): str,
639  vol.Optional(CONF_S2_AUTHENTICATED_KEY, default=s2_authenticated_key): str,
640  vol.Optional(
641  CONF_S2_UNAUTHENTICATED_KEY, default=s2_unauthenticated_key
642  ): str,
643  vol.Optional(
644  CONF_LR_S2_ACCESS_CONTROL_KEY, default=lr_s2_access_control_key
645  ): str,
646  vol.Optional(
647  CONF_LR_S2_AUTHENTICATED_KEY, default=lr_s2_authenticated_key
648  ): str,
649  }
650 
651  if not self._usb_discovery_usb_discovery:
652  ports = await async_get_usb_ports(self.hass)
653  schema = {
654  vol.Required(CONF_USB_PATH, default=usb_path): vol.In(ports),
655  **schema,
656  }
657 
658  data_schema = vol.Schema(schema)
659 
660  return self.async_show_formasync_show_formasync_show_form(step_id="configure_addon", data_schema=data_schema)
661 
663  self, user_input: dict[str, Any] | None = None
664  ) -> ConfigFlowResult:
665  """Prepare info needed to complete the config entry.
666 
667  Get add-on discovery info and server version info.
668  Set unique id and abort if already configured.
669  """
670  if not self.ws_addressws_addressws_address:
671  discovery_info = await self._async_get_addon_discovery_info_async_get_addon_discovery_info()
672  self.ws_addressws_addressws_address = f"ws://{discovery_info['host']}:{discovery_info['port']}"
673 
674  if not self.unique_idunique_id or self.context["source"] == SOURCE_USB:
675  if not self.version_infoversion_infoversion_info:
676  try:
678  self.hass, self.ws_addressws_addressws_address
679  )
680  except CannotConnect as err:
681  raise AbortFlow("cannot_connect") from err
682 
683  await self.async_set_unique_idasync_set_unique_id(
684  str(self.version_infoversion_infoversion_info.home_id), raise_on_progress=False
685  )
686 
687  self._abort_if_unique_id_configured_abort_if_unique_id_configured(
688  updates={
689  CONF_URL: self.ws_addressws_addressws_address,
690  CONF_USB_PATH: self.usb_pathusb_path,
691  CONF_S0_LEGACY_KEY: self.s0_legacy_keys0_legacy_key,
692  CONF_S2_ACCESS_CONTROL_KEY: self.s2_access_control_keys2_access_control_key,
693  CONF_S2_AUTHENTICATED_KEY: self.s2_authenticated_keys2_authenticated_key,
694  CONF_S2_UNAUTHENTICATED_KEY: self.s2_unauthenticated_keys2_unauthenticated_key,
695  CONF_LR_S2_ACCESS_CONTROL_KEY: self.lr_s2_access_control_keylr_s2_access_control_key,
696  CONF_LR_S2_AUTHENTICATED_KEY: self.lr_s2_authenticated_keylr_s2_authenticated_key,
697  }
698  )
699  return self._async_create_entry_from_vars_async_create_entry_from_vars()
700 
701  @callback
702  def _async_create_entry_from_vars(self) -> ConfigFlowResult:
703  """Return a config entry for the flow."""
704  # Abort any other flows that may be in progress
705  for progress in self._async_in_progress_async_in_progress():
706  self.hass.config_entries.flow.async_abort(progress["flow_id"])
707 
708  return self.async_create_entryasync_create_entryasync_create_entry(
709  title=TITLE,
710  data={
711  CONF_URL: self.ws_addressws_addressws_address,
712  CONF_USB_PATH: self.usb_pathusb_path,
713  CONF_S0_LEGACY_KEY: self.s0_legacy_keys0_legacy_key,
714  CONF_S2_ACCESS_CONTROL_KEY: self.s2_access_control_keys2_access_control_key,
715  CONF_S2_AUTHENTICATED_KEY: self.s2_authenticated_keys2_authenticated_key,
716  CONF_S2_UNAUTHENTICATED_KEY: self.s2_unauthenticated_keys2_unauthenticated_key,
717  CONF_LR_S2_ACCESS_CONTROL_KEY: self.lr_s2_access_control_keylr_s2_access_control_key,
718  CONF_LR_S2_AUTHENTICATED_KEY: self.lr_s2_authenticated_keylr_s2_authenticated_key,
719  CONF_USE_ADDON: self.use_addonuse_addon,
720  CONF_INTEGRATION_CREATED_ADDON: self.integration_created_addonintegration_created_addon,
721  },
722  )
723 
724 
726  """Handle an options flow for Z-Wave JS."""
727 
728  def __init__(self) -> None:
729  """Set up the options flow."""
730  super().__init__()
731  self.original_addon_configoriginal_addon_config: dict[str, Any] | None = None
732  self.revert_reasonrevert_reason: str | None = None
733 
734  @property
735  def flow_manager(self) -> OptionsFlowManager:
736  """Return the correct flow manager."""
737  return self.hass.config_entries.options
738 
739  @callback
740  def _async_update_entry(self, data: dict[str, Any]) -> None:
741  """Update the config entry with new data."""
742  self.hass.config_entries.async_update_entry(self.config_entryconfig_entryconfig_entry, data=data)
743 
744  async def async_step_init(
745  self, user_input: dict[str, Any] | None = None
746  ) -> ConfigFlowResult:
747  """Manage the options."""
748  if is_hassio(self.hass):
749  return await self.async_step_on_supervisorasync_step_on_supervisor()
750 
751  return await self.async_step_manualasync_step_manual()
752 
753  async def async_step_manual(
754  self, user_input: dict[str, Any] | None = None
755  ) -> ConfigFlowResult:
756  """Handle a manual configuration."""
757  if user_input is None:
758  return self.async_show_formasync_show_form(
759  step_id="manual",
760  data_schema=get_manual_schema(
761  {CONF_URL: self.config_entryconfig_entryconfig_entry.data[CONF_URL]}
762  ),
763  )
764 
765  errors = {}
766 
767  try:
768  version_info = await validate_input(self.hass, user_input)
769  except InvalidInput as err:
770  errors["base"] = err.error
771  except Exception:
772  _LOGGER.exception("Unexpected exception")
773  errors["base"] = "unknown"
774  else:
775  if self.config_entryconfig_entryconfig_entry.unique_id != str(version_info.home_id):
776  return self.async_abortasync_abort(reason="different_device")
777 
778  # Make sure we disable any add-on handling
779  # if the controller is reconfigured in a manual step.
780  self._async_update_entry_async_update_entry(
781  {
782  **self.config_entryconfig_entryconfig_entry.data,
783  **user_input,
784  CONF_USE_ADDON: False,
785  CONF_INTEGRATION_CREATED_ADDON: False,
786  }
787  )
788 
789  self.hass.config_entries.async_schedule_reload(self.config_entryconfig_entryconfig_entry.entry_id)
790  return self.async_create_entryasync_create_entry(title=TITLE, data={})
791 
792  return self.async_show_formasync_show_form(
793  step_id="manual", data_schema=get_manual_schema(user_input), errors=errors
794  )
795 
797  self, user_input: dict[str, Any] | None = None
798  ) -> ConfigFlowResult:
799  """Handle logic when on Supervisor host."""
800  if user_input is None:
801  return self.async_show_formasync_show_form(
802  step_id="on_supervisor",
803  data_schema=get_on_supervisor_schema(
804  {CONF_USE_ADDON: self.config_entryconfig_entryconfig_entry.data.get(CONF_USE_ADDON, True)}
805  ),
806  )
807  if not user_input[CONF_USE_ADDON]:
808  return await self.async_step_manualasync_step_manual()
809 
810  addon_info = await self._async_get_addon_info_async_get_addon_info()
811 
812  if addon_info.state == AddonState.NOT_INSTALLED:
813  return await self.async_step_install_addonasync_step_install_addon()
814 
815  return await self.async_step_configure_addonasync_step_configure_addonasync_step_configure_addon()
816 
818  self, user_input: dict[str, Any] | None = None
819  ) -> ConfigFlowResult:
820  """Ask for config for Z-Wave JS add-on."""
821  addon_info = await self._async_get_addon_info_async_get_addon_info()
822  addon_config = addon_info.options
823 
824  if user_input is not None:
825  self.s0_legacy_keys0_legacy_key = user_input[CONF_S0_LEGACY_KEY]
826  self.s2_access_control_keys2_access_control_key = user_input[CONF_S2_ACCESS_CONTROL_KEY]
827  self.s2_authenticated_keys2_authenticated_key = user_input[CONF_S2_AUTHENTICATED_KEY]
828  self.s2_unauthenticated_keys2_unauthenticated_key = user_input[CONF_S2_UNAUTHENTICATED_KEY]
829  self.lr_s2_access_control_keylr_s2_access_control_key = user_input[CONF_LR_S2_ACCESS_CONTROL_KEY]
830  self.lr_s2_authenticated_keylr_s2_authenticated_key = user_input[CONF_LR_S2_AUTHENTICATED_KEY]
831  self.usb_pathusb_path = user_input[CONF_USB_PATH]
832 
833  new_addon_config = {
834  **addon_config,
835  CONF_ADDON_DEVICE: self.usb_pathusb_path,
836  CONF_ADDON_S0_LEGACY_KEY: self.s0_legacy_keys0_legacy_key,
837  CONF_ADDON_S2_ACCESS_CONTROL_KEY: self.s2_access_control_keys2_access_control_key,
838  CONF_ADDON_S2_AUTHENTICATED_KEY: self.s2_authenticated_keys2_authenticated_key,
839  CONF_ADDON_S2_UNAUTHENTICATED_KEY: self.s2_unauthenticated_keys2_unauthenticated_key,
840  CONF_ADDON_LR_S2_ACCESS_CONTROL_KEY: self.lr_s2_access_control_keylr_s2_access_control_key,
841  CONF_ADDON_LR_S2_AUTHENTICATED_KEY: self.lr_s2_authenticated_keylr_s2_authenticated_key,
842  CONF_ADDON_LOG_LEVEL: user_input[CONF_LOG_LEVEL],
843  CONF_ADDON_EMULATE_HARDWARE: user_input.get(
844  CONF_EMULATE_HARDWARE, False
845  ),
846  }
847 
848  if new_addon_config != addon_config:
849  if addon_info.state == AddonState.RUNNING:
850  self.restart_addonrestart_addon = True
851  # Copy the add-on config to keep the objects separate.
852  self.original_addon_configoriginal_addon_config = dict(addon_config)
853  # Remove legacy network_key
854  new_addon_config.pop(CONF_ADDON_NETWORK_KEY, None)
855  await self._async_set_addon_config_async_set_addon_config(new_addon_config)
856 
857  if addon_info.state == AddonState.RUNNING and not self.restart_addonrestart_addon:
858  return await self.async_step_finish_addon_setupasync_step_finish_addon_setupasync_step_finish_addon_setup()
859 
860  if (
861  self.config_entryconfig_entryconfig_entry.data.get(CONF_USE_ADDON)
862  and self.config_entryconfig_entryconfig_entry.state == ConfigEntryState.LOADED
863  ):
864  # Disconnect integration before restarting add-on.
865  await disconnect_client(self.hass, self.config_entryconfig_entryconfig_entry)
866 
867  return await self.async_step_start_addonasync_step_start_addon()
868 
869  usb_path = addon_config.get(CONF_ADDON_DEVICE, self.usb_pathusb_path or "")
870  s0_legacy_key = addon_config.get(
871  CONF_ADDON_S0_LEGACY_KEY, self.s0_legacy_keys0_legacy_key or ""
872  )
873  s2_access_control_key = addon_config.get(
874  CONF_ADDON_S2_ACCESS_CONTROL_KEY, self.s2_access_control_keys2_access_control_key or ""
875  )
876  s2_authenticated_key = addon_config.get(
877  CONF_ADDON_S2_AUTHENTICATED_KEY, self.s2_authenticated_keys2_authenticated_key or ""
878  )
879  s2_unauthenticated_key = addon_config.get(
880  CONF_ADDON_S2_UNAUTHENTICATED_KEY, self.s2_unauthenticated_keys2_unauthenticated_key or ""
881  )
882  lr_s2_access_control_key = addon_config.get(
883  CONF_ADDON_LR_S2_ACCESS_CONTROL_KEY, self.lr_s2_access_control_keylr_s2_access_control_key or ""
884  )
885  lr_s2_authenticated_key = addon_config.get(
886  CONF_ADDON_LR_S2_AUTHENTICATED_KEY, self.lr_s2_authenticated_keylr_s2_authenticated_key or ""
887  )
888  log_level = addon_config.get(CONF_ADDON_LOG_LEVEL, "info")
889  emulate_hardware = addon_config.get(CONF_ADDON_EMULATE_HARDWARE, False)
890 
891  ports = await async_get_usb_ports(self.hass)
892 
893  data_schema = vol.Schema(
894  {
895  vol.Required(CONF_USB_PATH, default=usb_path): vol.In(ports),
896  vol.Optional(CONF_S0_LEGACY_KEY, default=s0_legacy_key): str,
897  vol.Optional(
898  CONF_S2_ACCESS_CONTROL_KEY, default=s2_access_control_key
899  ): str,
900  vol.Optional(
901  CONF_S2_AUTHENTICATED_KEY, default=s2_authenticated_key
902  ): str,
903  vol.Optional(
904  CONF_S2_UNAUTHENTICATED_KEY, default=s2_unauthenticated_key
905  ): str,
906  vol.Optional(
907  CONF_LR_S2_ACCESS_CONTROL_KEY, default=lr_s2_access_control_key
908  ): str,
909  vol.Optional(
910  CONF_LR_S2_AUTHENTICATED_KEY, default=lr_s2_authenticated_key
911  ): str,
912  vol.Optional(CONF_LOG_LEVEL, default=log_level): vol.In(
913  ADDON_LOG_LEVELS
914  ),
915  vol.Optional(CONF_EMULATE_HARDWARE, default=emulate_hardware): bool,
916  }
917  )
918 
919  return self.async_show_formasync_show_form(step_id="configure_addon", data_schema=data_schema)
920 
922  self, user_input: dict[str, Any] | None = None
923  ) -> ConfigFlowResult:
924  """Add-on start failed."""
925  return await self.async_revert_addon_configasync_revert_addon_config(reason="addon_start_failed")
926 
928  self, user_input: dict[str, Any] | None = None
929  ) -> ConfigFlowResult:
930  """Prepare info needed to complete the config entry update.
931 
932  Get add-on discovery info and server version info.
933  Check for same unique id and abort if not the same unique id.
934  """
935  if self.revert_reasonrevert_reason:
936  self.original_addon_configoriginal_addon_config = None
937  reason = self.revert_reasonrevert_reason
938  self.revert_reasonrevert_reason = None
939  return await self.async_revert_addon_configasync_revert_addon_config(reason=reason)
940 
941  if not self.ws_addressws_addressws_address:
942  discovery_info = await self._async_get_addon_discovery_info_async_get_addon_discovery_info()
943  self.ws_addressws_addressws_address = f"ws://{discovery_info['host']}:{discovery_info['port']}"
944 
945  if not self.version_infoversion_infoversion_info:
946  try:
948  self.hass, self.ws_addressws_addressws_address
949  )
950  except CannotConnect:
951  return await self.async_revert_addon_configasync_revert_addon_config(reason="cannot_connect")
952 
953  if self.config_entryconfig_entryconfig_entry.unique_id != str(self.version_infoversion_infoversion_info.home_id):
954  return await self.async_revert_addon_configasync_revert_addon_config(reason="different_device")
955 
956  self._async_update_entry_async_update_entry(
957  {
958  **self.config_entryconfig_entryconfig_entry.data,
959  CONF_URL: self.ws_addressws_addressws_address,
960  CONF_USB_PATH: self.usb_pathusb_path,
961  CONF_S0_LEGACY_KEY: self.s0_legacy_keys0_legacy_key,
962  CONF_S2_ACCESS_CONTROL_KEY: self.s2_access_control_keys2_access_control_key,
963  CONF_S2_AUTHENTICATED_KEY: self.s2_authenticated_keys2_authenticated_key,
964  CONF_S2_UNAUTHENTICATED_KEY: self.s2_unauthenticated_keys2_unauthenticated_key,
965  CONF_LR_S2_ACCESS_CONTROL_KEY: self.lr_s2_access_control_keylr_s2_access_control_key,
966  CONF_LR_S2_AUTHENTICATED_KEY: self.lr_s2_authenticated_keylr_s2_authenticated_key,
967  CONF_USE_ADDON: True,
968  CONF_INTEGRATION_CREATED_ADDON: self.integration_created_addonintegration_created_addon,
969  }
970  )
971  # Always reload entry since we may have disconnected the client.
972  self.hass.config_entries.async_schedule_reload(self.config_entryconfig_entryconfig_entry.entry_id)
973  return self.async_create_entryasync_create_entry(title=TITLE, data={})
974 
975  async def async_revert_addon_config(self, reason: str) -> ConfigFlowResult:
976  """Abort the options flow.
977 
978  If the add-on options have been changed, revert those and restart add-on.
979  """
980  # If reverting the add-on options failed, abort immediately.
981  if self.revert_reasonrevert_reason:
982  _LOGGER.error(
983  "Failed to revert add-on options before aborting flow, reason: %s",
984  reason,
985  )
986 
987  if self.revert_reasonrevert_reason or not self.original_addon_configoriginal_addon_config:
988  self.hass.config_entries.async_schedule_reload(self.config_entryconfig_entryconfig_entry.entry_id)
989  return self.async_abortasync_abort(reason=reason)
990 
991  self.revert_reasonrevert_reason = reason
992  addon_config_input = {
993  ADDON_USER_INPUT_MAP[addon_key]: addon_val
994  for addon_key, addon_val in self.original_addon_configoriginal_addon_config.items()
995  if addon_key in ADDON_USER_INPUT_MAP
996  }
997  _LOGGER.debug("Reverting add-on options, reason: %s", reason)
998  return await self.async_step_configure_addonasync_step_configure_addonasync_step_configure_addon(addon_config_input)
999 
1000 
1002  """Indicate connection error."""
1003 
1004 
1005 class InvalidInput(HomeAssistantError):
1006  """Error to indicate input data is invalid."""
1007 
1008  def __init__(self, error: str) -> None:
1009  """Initialize error."""
1010  super().__init__()
1011  self.errorerror = error
FlowManager[ConfigFlowContext, ConfigFlowResult] flow_manager(self)
Definition: config_flow.py:196
ConfigFlowResult async_step_install_failed(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:227
ConfigFlowResult async_step_start_failed(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:257
ConfigFlowResult async_step_start_addon(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:233
ConfigFlowResult async_step_install_addon(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:201
ConfigFlowResult async_step_configure_addon(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:295
ConfigFlowResult async_step_finish_addon_setup(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:301
ConfigFlowResult async_step_init(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:746
ConfigFlowResult async_step_configure_addon(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:819
ConfigFlowResult async_step_manual(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:755
ConfigFlowResult async_step_start_failed(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:923
ConfigFlowResult async_step_on_supervisor(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:798
ConfigFlowResult async_step_finish_addon_setup(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:929
ConfigFlowResult async_revert_addon_config(self, str reason)
Definition: config_flow.py:975
OptionsFlowHandler async_get_options_flow(ConfigEntry config_entry)
Definition: config_flow.py:367
ConfigFlowResult async_step_manual(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:467
ConfigFlowResult async_step_finish_addon_setup(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:664
ConfigFlowResult async_step_usb_confirm(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:453
ConfigFlowResult async_step_hassio_confirm(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:531
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:373
ConfigFlowResult async_step_on_supervisor(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:542
ConfigFlowResult async_step_usb(self, usb.UsbServiceInfo discovery_info)
Definition: config_flow.py:410
ConfigFlowResult async_step_configure_addon(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:583
ConfigFlowResult async_step_zeroconf_confirm(self, dict|None user_input=None)
Definition: config_flow.py:393
ConfigFlowResult async_step_hassio(self, HassioServiceInfo discovery_info)
Definition: config_flow.py:505
ConfigFlowResult async_step_zeroconf(self, ZeroconfServiceInfo discovery_info)
Definition: config_flow.py:382
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)
list[ConfigFlowResult] _async_in_progress(self, bool include_uninitialized=False, dict[str, Any]|None match_context=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_show_progress(self, *str|None step_id=None, str progress_action, Mapping[str, str]|None description_placeholders=None, asyncio.Task[Any]|None progress_task=None)
_FlowResultT async_show_progress_done(self, *str next_step_id)
_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 is_hassio(HomeAssistant hass)
Definition: __init__.py:302
IssData update(pyiss.ISS iss)
Definition: __init__.py:33
AddonManager get_addon_manager(HomeAssistant hass, str slug)
Definition: config_flow.py:44
vol.Schema get_on_supervisor_schema(dict[str, Any] user_input)
Definition: config_flow.py:110
vol.Schema get_manual_schema(dict[str, Any] user_input)
Definition: config_flow.py:104
VersionInfo async_get_version_info(HomeAssistant hass, str ws_address)
Definition: config_flow.py:129
dict[str, str] async_get_usb_ports(HomeAssistant hass)
Definition: config_flow.py:169
VersionInfo validate_input(HomeAssistant hass, dict user_input)
Definition: config_flow.py:116
None disconnect_client(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:952
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)