Home Assistant Unofficial Reference 2024.12.1
config_flow.py
Go to the documentation of this file.
1 """Config flow for Vera."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Mapping
6 import logging
7 import re
8 from typing import Any
9 
10 import pyvera as pv
11 from requests.exceptions import RequestException
12 import voluptuous as vol
13 
14 from homeassistant.config_entries import (
15  SOURCE_IMPORT,
16  SOURCE_USER,
17  ConfigEntry,
18  ConfigFlow,
19  ConfigFlowResult,
20  OptionsFlow,
21 )
22 from homeassistant.const import CONF_EXCLUDE, CONF_LIGHTS, CONF_SOURCE
23 from homeassistant.core import callback
24 from homeassistant.helpers import entity_registry as er
25 from homeassistant.helpers.typing import VolDictType
26 
27 from .const import CONF_CONTROLLER, CONF_LEGACY_UNIQUE_ID, DOMAIN
28 
29 LIST_REGEX = re.compile("[^0-9]+")
30 _LOGGER = logging.getLogger(__name__)
31 
32 
33 def fix_device_id_list(data: list[Any]) -> list[int]:
34  """Fix the id list by converting it to a supported int list."""
35  return str_to_int_list(list_to_str(data))
36 
37 
38 def str_to_int_list(data: str) -> list[int]:
39  """Convert a string to an int list."""
40  return [int(s) for s in LIST_REGEX.split(data) if len(s) > 0]
41 
42 
43 def list_to_str(data: list[Any]) -> str:
44  """Convert an int list to a string."""
45  return " ".join([str(i) for i in data])
46 
47 
48 def new_options(lights: list[int], exclude: list[int]) -> dict[str, list[int]]:
49  """Create a standard options object."""
50  return {CONF_LIGHTS: lights, CONF_EXCLUDE: exclude}
51 
52 
53 def options_schema(options: Mapping[str, Any] | None = None) -> VolDictType:
54  """Return options schema."""
55  options = options or {}
56  return {
57  vol.Optional(
58  CONF_LIGHTS,
59  default=list_to_str(options.get(CONF_LIGHTS, [])),
60  ): str,
61  vol.Optional(
62  CONF_EXCLUDE,
63  default=list_to_str(options.get(CONF_EXCLUDE, [])),
64  ): str,
65  }
66 
67 
68 def options_data(user_input: dict[str, str]) -> dict[str, list[int]]:
69  """Return options dict."""
70  return new_options(
71  str_to_int_list(user_input.get(CONF_LIGHTS, "")),
72  str_to_int_list(user_input.get(CONF_EXCLUDE, "")),
73  )
74 
75 
77  """Options for the component."""
78 
79  async def async_step_init(
80  self,
81  user_input: dict[str, str] | None = None,
82  ) -> ConfigFlowResult:
83  """Manage the options."""
84  if user_input is not None:
85  return self.async_create_entryasync_create_entry(
86  title="",
87  data=options_data(user_input),
88  )
89 
90  return self.async_show_formasync_show_form(
91  step_id="init",
92  data_schema=vol.Schema(options_schema(self.config_entryconfig_entryconfig_entry.options)),
93  )
94 
95 
96 class VeraFlowHandler(ConfigFlow, domain=DOMAIN):
97  """Vera config flow."""
98 
99  @staticmethod
100  @callback
101  def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlowHandler:
102  """Get the options flow."""
103  return OptionsFlowHandler()
104 
105  async def async_step_user(
106  self, user_input: dict[str, Any] | None = None
107  ) -> ConfigFlowResult:
108  """Handle user initiated flow."""
109  if user_input is not None:
110  return await self.async_step_finishasync_step_finish(
111  {
112  **user_input,
113  **options_data(user_input),
114  CONF_SOURCE: SOURCE_USER,
115  CONF_LEGACY_UNIQUE_ID: False,
116  }
117  )
118 
119  return self.async_show_formasync_show_formasync_show_form(
120  step_id="user",
121  data_schema=vol.Schema(
122  {vol.Required(CONF_CONTROLLER): str, **options_schema()}
123  ),
124  )
125 
126  async def async_step_import(self, import_data: dict[str, Any]) -> ConfigFlowResult:
127  """Handle a flow initialized by import."""
128 
129  # If there are entities with the legacy unique_id, then this imported config
130  # should also use the legacy unique_id for entity creation.
131  entity_registry = er.async_get(self.hass)
132  use_legacy_unique_id = (
133  len(
134  [
135  entry
136  for entry in entity_registry.entities.values()
137  if entry.platform == DOMAIN and entry.unique_id.isdigit()
138  ]
139  )
140  > 0
141  )
142 
143  return await self.async_step_finishasync_step_finish(
144  {
145  **import_data,
146  CONF_SOURCE: SOURCE_IMPORT,
147  CONF_LEGACY_UNIQUE_ID: use_legacy_unique_id,
148  }
149  )
150 
151  async def async_step_finish(self, config: dict[str, Any]) -> ConfigFlowResult:
152  """Validate and create config entry."""
153  base_url = config[CONF_CONTROLLER] = config[CONF_CONTROLLER].rstrip("/")
154  controller = pv.VeraController(base_url)
155 
156  # Verify the controller is online and get the serial number.
157  try:
158  await self.hass.async_add_executor_job(controller.refresh_data)
159  except RequestException:
160  _LOGGER.error("Failed to connect to vera controller %s", base_url)
161  return self.async_abortasync_abortasync_abort(
162  reason="cannot_connect", description_placeholders={"base_url": base_url}
163  )
164 
165  await self.async_set_unique_idasync_set_unique_id(controller.serial_number)
166  self._abort_if_unique_id_configured_abort_if_unique_id_configured(config)
167 
168  return self.async_create_entryasync_create_entryasync_create_entry(title=base_url, data=config)
ConfigFlowResult async_step_init(self, dict[str, str]|None user_input=None)
Definition: config_flow.py:82
ConfigFlowResult async_step_import(self, dict[str, Any] import_data)
Definition: config_flow.py:126
ConfigFlowResult async_step_finish(self, dict[str, Any] config)
Definition: config_flow.py:151
OptionsFlowHandler async_get_options_flow(ConfigEntry config_entry)
Definition: config_flow.py:101
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:107
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)
_FlowResultT async_abort(self, *str reason, Mapping[str, str]|None description_placeholders=None)
list[int] fix_device_id_list(list[Any] data)
Definition: config_flow.py:33
dict[str, list[int]] options_data(dict[str, str] user_input)
Definition: config_flow.py:68
dict[str, list[int]] new_options(list[int] lights, list[int] exclude)
Definition: config_flow.py:48
VolDictType options_schema(Mapping[str, Any]|None options=None)
Definition: config_flow.py:53