Home Assistant Unofficial Reference 2024.12.1
config_flow.py
Go to the documentation of this file.
1 """Config flow to configure Heos."""
2 
3 from typing import TYPE_CHECKING, Any
4 from urllib.parse import urlparse
5 
6 from pyheos import Heos, HeosError
7 import voluptuous as vol
8 
9 from homeassistant.components import ssdp
10 from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
11 from homeassistant.const import CONF_HOST
12 
13 from .const import DATA_DISCOVERED_HOSTS, DOMAIN
14 
15 
16 def format_title(host: str) -> str:
17  """Format the title for config entries."""
18  return f"Controller ({host})"
19 
20 
21 class HeosFlowHandler(ConfigFlow, domain=DOMAIN):
22  """Define a flow for HEOS."""
23 
24  VERSION = 1
25 
26  async def async_step_ssdp(
27  self, discovery_info: ssdp.SsdpServiceInfo
28  ) -> ConfigFlowResult:
29  """Handle a discovered Heos device."""
30  # Store discovered host
31  if TYPE_CHECKING:
32  assert discovery_info.ssdp_location
33  hostname = urlparse(discovery_info.ssdp_location).hostname
34  friendly_name = (
35  f"{discovery_info.upnp[ssdp.ATTR_UPNP_FRIENDLY_NAME]} ({hostname})"
36  )
37  self.hass.data.setdefault(DATA_DISCOVERED_HOSTS, {})
38  self.hass.data[DATA_DISCOVERED_HOSTS][friendly_name] = hostname
39  # Abort if other flows in progress or an entry already exists
40  if self._async_in_progress_async_in_progress() or self._async_current_entries_async_current_entries():
41  return self.async_abortasync_abortasync_abort(reason="single_instance_allowed")
42  await self.async_set_unique_idasync_set_unique_id(DOMAIN)
43  # Show selection form
44  return self.async_show_formasync_show_formasync_show_form(step_id="user")
45 
46  async def async_step_import(self, import_data: dict[str, Any]) -> ConfigFlowResult:
47  """Occurs when an entry is setup through config."""
48  host = import_data[CONF_HOST]
49  # raise_on_progress is False here in case ssdp discovers
50  # heos first which would block the import
51  await self.async_set_unique_idasync_set_unique_id(DOMAIN, raise_on_progress=False)
52  return self.async_create_entryasync_create_entryasync_create_entry(title=format_title(host), data={CONF_HOST: host})
53 
54  async def async_step_user(
55  self, user_input: dict[str, Any] | None = None
56  ) -> ConfigFlowResult:
57  """Obtain host and validate connection."""
58  self.hass.data.setdefault(DATA_DISCOVERED_HOSTS, {})
59  # Only a single entry is needed for all devices
60  if self._async_current_entries_async_current_entries():
61  return self.async_abortasync_abortasync_abort(reason="single_instance_allowed")
62  # Try connecting to host if provided
63  errors = {}
64  host = None
65  if user_input is not None:
66  host = user_input[CONF_HOST]
67  # Map host from friendly name if in discovered hosts
68  host = self.hass.data[DATA_DISCOVERED_HOSTS].get(host, host)
69  heos = Heos(host)
70  try:
71  await heos.connect()
72  self.hass.data.pop(DATA_DISCOVERED_HOSTS)
73  return await self.async_step_importasync_step_import({CONF_HOST: host})
74  except HeosError:
75  errors[CONF_HOST] = "cannot_connect"
76  finally:
77  await heos.disconnect()
78 
79  # Return form
80  host_type = (
81  str
82  if not self.hass.data[DATA_DISCOVERED_HOSTS]
83  else vol.In(list(self.hass.data[DATA_DISCOVERED_HOSTS]))
84  )
85  return self.async_show_formasync_show_formasync_show_form(
86  step_id="user",
87  data_schema=vol.Schema({vol.Required(CONF_HOST, default=host): host_type}),
88  errors=errors,
89  )
ConfigFlowResult async_step_import(self, dict[str, Any] import_data)
Definition: config_flow.py:46
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:56
ConfigFlowResult async_step_ssdp(self, ssdp.SsdpServiceInfo discovery_info)
Definition: config_flow.py:28
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)
_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