Home Assistant Unofficial Reference 2024.12.1
config_flow.py
Go to the documentation of this file.
1 """Config flow for 1-Wire component."""
2 
3 from __future__ import annotations
4 
5 from copy import deepcopy
6 from typing import Any
7 
8 import voluptuous as vol
9 
10 from homeassistant.config_entries import (
11  ConfigEntry,
12  ConfigFlow,
13  ConfigFlowResult,
14  OptionsFlow,
15 )
16 from homeassistant.const import CONF_HOST, CONF_PORT
17 from homeassistant.core import HomeAssistant, callback
18 from homeassistant.helpers import config_validation as cv, device_registry as dr
19 from homeassistant.helpers.device_registry import DeviceEntry
20 
21 from .const import (
22  DEFAULT_HOST,
23  DEFAULT_PORT,
24  DEVICE_SUPPORT_OPTIONS,
25  DOMAIN,
26  INPUT_ENTRY_CLEAR_OPTIONS,
27  INPUT_ENTRY_DEVICE_SELECTION,
28  OPTION_ENTRY_DEVICE_OPTIONS,
29  OPTION_ENTRY_SENSOR_PRECISION,
30  PRECISION_MAPPING_FAMILY_28,
31 )
32 from .onewirehub import CannotConnect, OneWireHub
33 
34 DATA_SCHEMA = vol.Schema(
35  {
36  vol.Required(CONF_HOST, default=DEFAULT_HOST): str,
37  vol.Required(CONF_PORT, default=DEFAULT_PORT): int,
38  }
39 )
40 
41 
42 async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, str]:
43  """Validate the user input allows us to connect.
44 
45  Data has the keys from DATA_SCHEMA with values provided by the user.
46  """
47 
48  hub = OneWireHub(hass)
49 
50  host = data[CONF_HOST]
51  port = data[CONF_PORT]
52  # Raises CannotConnect exception on failure
53  await hub.connect(host, port)
54 
55  # Return info that you want to store in the config entry.
56  return {"title": host}
57 
58 
59 class OneWireFlowHandler(ConfigFlow, domain=DOMAIN):
60  """Handle 1-Wire config flow."""
61 
62  VERSION = 1
63 
64  def __init__(self) -> None:
65  """Initialize 1-Wire config flow."""
66  self.onewire_config: dict[str, Any] = {}
67 
68  async def async_step_user(
69  self, user_input: dict[str, Any] | None = None
70  ) -> ConfigFlowResult:
71  """Handle 1-Wire config flow start.
72 
73  Let user manually input configuration.
74  """
75  errors: dict[str, str] = {}
76  if user_input:
77  # Prevent duplicate entries
78  self._async_abort_entries_match_async_abort_entries_match(
79  {
80  CONF_HOST: user_input[CONF_HOST],
81  CONF_PORT: user_input[CONF_PORT],
82  }
83  )
84 
85  self.onewire_config.update(user_input)
86 
87  try:
88  info = await validate_input(self.hass, user_input)
89  except CannotConnect:
90  errors["base"] = "cannot_connect"
91  else:
92  return self.async_create_entryasync_create_entryasync_create_entry(
93  title=info["title"], data=self.onewire_config
94  )
95 
96  return self.async_show_formasync_show_formasync_show_form(
97  step_id="user",
98  data_schema=DATA_SCHEMA,
99  errors=errors,
100  )
101 
102  @staticmethod
103  @callback
105  config_entry: ConfigEntry,
106  ) -> OnewireOptionsFlowHandler:
107  """Get the options flow for this handler."""
108  return OnewireOptionsFlowHandler(config_entry)
109 
110 
112  """Handle OneWire Config options."""
113 
114  configurable_devices: dict[str, str]
115  """Mapping of the configurable devices.
116 
117  `key`: friendly name
118  `value`: onewire id
119  """
120  devices_to_configure: dict[str, str]
121  """Mapping of the devices selected for configuration.
122 
123  `key`: friendly name
124  `value`: onewire id
125  """
126  current_device: str
127  """Friendly name of the currently selected device."""
128 
129  def __init__(self, config_entry: ConfigEntry) -> None:
130  """Initialize options flow."""
131  self.optionsoptions = deepcopy(dict(config_entry.options))
132 
133  async def async_step_init(
134  self, user_input: dict[str, Any] | None = None
135  ) -> ConfigFlowResult:
136  """Manage the options."""
137  device_registry = dr.async_get(self.hass)
138  self.configurable_devicesconfigurable_devices = {
139  self._get_device_friendly_name_get_device_friendly_name(device, device.name): device.name
140  for device in dr.async_entries_for_config_entry(
141  device_registry, self.config_entryconfig_entryconfig_entry.entry_id
142  )
143  if device.name and device.name[0:2] in DEVICE_SUPPORT_OPTIONS
144  }
145 
146  if not self.configurable_devicesconfigurable_devices:
147  return self.async_abortasync_abort(reason="no_configurable_devices")
148 
149  return await self.async_step_device_selectionasync_step_device_selection(user_input=None)
150 
152  self, user_input: dict[str, Any] | None = None
153  ) -> ConfigFlowResult:
154  """Select what devices to configure."""
155  errors = {}
156  if user_input is not None:
157  if user_input.get(INPUT_ENTRY_CLEAR_OPTIONS):
158  # Reset all options
159  return self.async_create_entryasync_create_entry(data={})
160 
161  selected_devices: list[str] = (
162  user_input.get(INPUT_ENTRY_DEVICE_SELECTION) or []
163  )
164  if selected_devices:
165  self.devices_to_configuredevices_to_configure = {
166  friendly_name: self.configurable_devicesconfigurable_devices[friendly_name]
167  for friendly_name in selected_devices
168  }
169 
170  return await self.async_step_configure_deviceasync_step_configure_device(user_input=None)
171  errors["base"] = "device_not_selected"
172 
173  return self.async_show_formasync_show_form(
174  step_id="device_selection",
175  data_schema=vol.Schema(
176  {
177  vol.Optional(
178  INPUT_ENTRY_CLEAR_OPTIONS,
179  default=False,
180  ): bool,
181  vol.Optional(
182  INPUT_ENTRY_DEVICE_SELECTION,
183  default=self._get_current_configured_sensors_get_current_configured_sensors(),
184  description="Multiselect with list of devices to choose from",
185  ): cv.multi_select(
186  {
187  friendly_name: False
188  for friendly_name in self.configurable_devicesconfigurable_devices
189  }
190  ),
191  }
192  ),
193  errors=errors,
194  )
195 
197  self, user_input: dict[str, Any] | None = None
198  ) -> ConfigFlowResult:
199  """Config precision option for device."""
200  if user_input is not None:
201  self._update_device_options_update_device_options(user_input)
202  if self.devices_to_configuredevices_to_configure:
203  return await self.async_step_configure_deviceasync_step_configure_device(user_input=None)
204  return self.async_create_entryasync_create_entry(data=self.optionsoptions)
205 
206  self.current_device, onewire_id = self.devices_to_configuredevices_to_configure.popitem()
207  data_schema = vol.Schema(
208  {
209  vol.Required(
210  OPTION_ENTRY_SENSOR_PRECISION,
211  default=self._get_current_setting_get_current_setting(
212  onewire_id, OPTION_ENTRY_SENSOR_PRECISION, "temperature"
213  ),
214  ): vol.In(PRECISION_MAPPING_FAMILY_28),
215  }
216  )
217 
218  return self.async_show_formasync_show_form(
219  step_id="configure_device",
220  data_schema=data_schema,
221  description_placeholders={"sensor_id": self.current_device},
222  )
223 
224  @staticmethod
225  def _get_device_friendly_name(entry: DeviceEntry, onewire_id: str) -> str:
226  if entry.name_by_user:
227  return f"{entry.name_by_user} ({onewire_id})"
228  return onewire_id
229 
230  def _get_current_configured_sensors(self) -> list[str]:
231  """Get current list of sensors that are configured."""
232  configured_sensors = self.optionsoptions.get(OPTION_ENTRY_DEVICE_OPTIONS)
233  if not configured_sensors:
234  return []
235  return [
236  friendly_name
237  for friendly_name, onewire_id in self.configurable_devicesconfigurable_devices.items()
238  if onewire_id in configured_sensors
239  ]
240 
241  def _get_current_setting(self, device_id: str, setting: str, default: Any) -> Any:
242  """Get current value for setting."""
243  if entry_device_options := self.optionsoptions.get(OPTION_ENTRY_DEVICE_OPTIONS):
244  if device_options := entry_device_options.get(device_id):
245  return device_options.get(setting)
246  return default
247 
248  def _update_device_options(self, user_input: dict[str, Any]) -> None:
249  """Update the global config with the new options for the current device."""
250  options: dict[str, dict[str, Any]] = self.optionsoptions.setdefault(
251  OPTION_ENTRY_DEVICE_OPTIONS, {}
252  )
253 
254  onewire_id = self.configurable_devicesconfigurable_devices[self.current_device]
255  device_options: dict[str, Any] = options.setdefault(onewire_id, {})
256  if onewire_id[0:2] == "28":
257  device_options[OPTION_ENTRY_SENSOR_PRECISION] = user_input[
258  OPTION_ENTRY_SENSOR_PRECISION
259  ]
260 
261  self.optionsoptions.update({OPTION_ENTRY_DEVICE_OPTIONS: options})
OnewireOptionsFlowHandler async_get_options_flow(ConfigEntry config_entry)
Definition: config_flow.py:106
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:70
ConfigFlowResult async_step_init(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:135
ConfigFlowResult async_step_device_selection(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:153
Any _get_current_setting(self, str device_id, str setting, Any default)
Definition: config_flow.py:241
ConfigFlowResult async_step_configure_device(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:198
str _get_device_friendly_name(DeviceEntry entry, str onewire_id)
Definition: config_flow.py:225
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)
None _async_abort_entries_match(self, dict[str, Any]|None match_dict=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)
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88
IssData update(pyiss.ISS iss)
Definition: __init__.py:33
dict[str, str] validate_input(HomeAssistant hass, dict[str, Any] data)
Definition: config_flow.py:42