Home Assistant Unofficial Reference 2024.12.1
config_flow.py
Go to the documentation of this file.
1 """Config flow to configure webostv component."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Mapping
6 import logging
7 from typing import Any, Self
8 from urllib.parse import urlparse
9 
10 from aiowebostv import WebOsTvPairError
11 import voluptuous as vol
12 
13 from homeassistant.components import ssdp
14 from homeassistant.config_entries import (
15  ConfigEntry,
16  ConfigFlow,
17  ConfigFlowResult,
18  OptionsFlow,
19 )
20 from homeassistant.const import CONF_CLIENT_SECRET, CONF_HOST, CONF_NAME
21 from homeassistant.core import callback
22 from homeassistant.data_entry_flow import AbortFlow
23 from homeassistant.helpers import config_validation as cv
24 
25 from . import async_control_connect, update_client_key
26 from .const import CONF_SOURCES, DEFAULT_NAME, DOMAIN, WEBOSTV_EXCEPTIONS
27 from .helpers import async_get_sources
28 
29 DATA_SCHEMA = vol.Schema(
30  {
31  vol.Required(CONF_HOST): cv.string,
32  vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
33  },
34  extra=vol.ALLOW_EXTRA,
35 )
36 
37 _LOGGER = logging.getLogger(__name__)
38 
39 
40 class FlowHandler(ConfigFlow, domain=DOMAIN):
41  """WebosTV configuration flow."""
42 
43  VERSION = 1
44 
45  def __init__(self) -> None:
46  """Initialize workflow."""
47  self._host_host: str = ""
48  self._name_name: str = ""
49  self._uuid_uuid: str | None = None
50 
51  @staticmethod
52  @callback
53  def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
54  """Get the options flow for this handler."""
55  return OptionsFlowHandler(config_entry)
56 
57  async def async_step_user(
58  self, user_input: dict[str, Any] | None = None
59  ) -> ConfigFlowResult:
60  """Handle a flow initialized by the user."""
61  errors: dict[str, str] = {}
62  if user_input is not None:
63  self._host_host = user_input[CONF_HOST]
64  self._name_name = user_input[CONF_NAME]
65  return await self.async_step_pairingasync_step_pairing()
66 
67  return self.async_show_formasync_show_formasync_show_form(
68  step_id="user", data_schema=DATA_SCHEMA, errors=errors
69  )
70 
71  @callback
72  def _async_check_configured_entry(self) -> None:
73  """Check if entry is configured, update unique_id if needed."""
74  for entry in self._async_current_entries_async_current_entries(include_ignore=False):
75  if entry.data[CONF_HOST] != self._host_host:
76  continue
77 
78  if self._uuid_uuid and not entry.unique_id:
79  _LOGGER.debug(
80  "Updating unique_id for host %s, unique_id: %s",
81  self._host_host,
82  self._uuid_uuid,
83  )
84  self.hass.config_entries.async_update_entry(entry, unique_id=self._uuid_uuid)
85 
86  raise AbortFlow("already_configured")
87 
88  async def async_step_pairing(
89  self, user_input: dict[str, Any] | None = None
90  ) -> ConfigFlowResult:
91  """Display pairing form."""
92  self._async_check_configured_entry_async_check_configured_entry()
93 
94  self.context["title_placeholders"] = {"name": self._name_name}
95  errors = {}
96 
97  if user_input is not None:
98  try:
99  client = await async_control_connect(self._host_host, None)
100  except WebOsTvPairError:
101  return self.async_abortasync_abortasync_abort(reason="error_pairing")
102  except WEBOSTV_EXCEPTIONS:
103  errors["base"] = "cannot_connect"
104  else:
105  await self.async_set_unique_idasync_set_unique_id(
106  client.hello_info["deviceUUID"], raise_on_progress=False
107  )
108  self._abort_if_unique_id_configured_abort_if_unique_id_configured({CONF_HOST: self._host_host})
109  data = {CONF_HOST: self._host_host, CONF_CLIENT_SECRET: client.client_key}
110  return self.async_create_entryasync_create_entryasync_create_entry(title=self._name_name, data=data)
111 
112  return self.async_show_formasync_show_formasync_show_form(step_id="pairing", errors=errors)
113 
114  async def async_step_ssdp(
115  self, discovery_info: ssdp.SsdpServiceInfo
116  ) -> ConfigFlowResult:
117  """Handle a flow initialized by discovery."""
118  assert discovery_info.ssdp_location
119  host = urlparse(discovery_info.ssdp_location).hostname
120  assert host
121  self._host_host = host
122  self._name_name = discovery_info.upnp.get(ssdp.ATTR_UPNP_FRIENDLY_NAME, DEFAULT_NAME)
123 
124  uuid = discovery_info.upnp[ssdp.ATTR_UPNP_UDN]
125  assert uuid
126  if uuid.startswith("uuid:"):
127  uuid = uuid[5:]
128  await self.async_set_unique_idasync_set_unique_id(uuid)
129  self._abort_if_unique_id_configured_abort_if_unique_id_configured({CONF_HOST: self._host_host})
130 
131  if self.hass.config_entries.flow.async_has_matching_flow(self):
132  return self.async_abortasync_abortasync_abort(reason="already_in_progress")
133 
134  self._uuid_uuid = uuid
135  return await self.async_step_pairingasync_step_pairing()
136 
137  def is_matching(self, other_flow: Self) -> bool:
138  """Return True if other_flow is matching this flow."""
139  return other_flow._host == self._host_host # noqa: SLF001
140 
141  async def async_step_reauth(
142  self, entry_data: Mapping[str, Any]
143  ) -> ConfigFlowResult:
144  """Perform reauth upon an WebOsTvPairError."""
145  self._host_host = entry_data[CONF_HOST]
146  return await self.async_step_reauth_confirmasync_step_reauth_confirm()
147 
149  self, user_input: dict[str, Any] | None = None
150  ) -> ConfigFlowResult:
151  """Dialog that informs the user that reauth is required."""
152  if user_input is not None:
153  try:
154  client = await async_control_connect(self._host_host, None)
155  except WebOsTvPairError:
156  return self.async_abortasync_abortasync_abort(reason="error_pairing")
157  except WEBOSTV_EXCEPTIONS:
158  return self.async_abortasync_abortasync_abort(reason="reauth_unsuccessful")
159 
160  reauth_entry = self._get_reauth_entry_get_reauth_entry()
161  update_client_key(self.hass, reauth_entry, client)
162  await self.hass.config_entries.async_reload(reauth_entry.entry_id)
163  return self.async_abortasync_abortasync_abort(reason="reauth_successful")
164 
165  return self.async_show_formasync_show_formasync_show_form(step_id="reauth_confirm")
166 
167 
169  """Handle options."""
170 
171  def __init__(self, config_entry: ConfigEntry) -> None:
172  """Initialize options flow."""
173  self.hosthost = config_entry.data[CONF_HOST]
174  self.keykey = config_entry.data[CONF_CLIENT_SECRET]
175 
176  async def async_step_init(
177  self, user_input: dict[str, Any] | None = None
178  ) -> ConfigFlowResult:
179  """Manage the options."""
180  errors = {}
181  if user_input is not None:
182  options_input = {CONF_SOURCES: user_input[CONF_SOURCES]}
183  return self.async_create_entryasync_create_entry(title="", data=options_input)
184  # Get sources
185  sources_list = await async_get_sources(self.hosthost, self.keykey)
186  if not sources_list:
187  errors["base"] = "cannot_retrieve"
188 
189  option_sources = self.config_entryconfig_entryconfig_entry.options.get(CONF_SOURCES, [])
190  sources = [s for s in option_sources if s in sources_list]
191  if not sources:
192  sources = sources_list
193 
194  options_schema = vol.Schema(
195  {
196  vol.Optional(
197  CONF_SOURCES,
198  description={"suggested_value": sources},
199  ): cv.multi_select({source: source for source in sources_list}),
200  }
201  )
202 
203  return self.async_show_formasync_show_form(
204  step_id="init", data_schema=options_schema, errors=errors
205  )
ConfigFlowResult async_step_pairing(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:90
OptionsFlow async_get_options_flow(ConfigEntry config_entry)
Definition: config_flow.py:53
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:59
ConfigFlowResult async_step_ssdp(self, ssdp.SsdpServiceInfo discovery_info)
Definition: config_flow.py:116
ConfigFlowResult async_step_reauth_confirm(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:150
ConfigFlowResult async_step_reauth(self, Mapping[str, Any] entry_data)
Definition: config_flow.py:143
ConfigFlowResult async_step_init(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:178
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_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[str] async_get_sources(str host, str key)
Definition: helpers.py:69
WebOsClient async_control_connect(str host, str|None key)
Definition: __init__.py:151
None update_client_key(HomeAssistant hass, ConfigEntry entry, WebOsClient client)
Definition: __init__.py:165