Home Assistant Unofficial Reference 2024.12.1
config_flow.py
Go to the documentation of this file.
1 """Config flow for Samsung SyncThru."""
2 
3 import re
4 from typing import Any
5 from urllib.parse import urlparse
6 
7 from pysyncthru import ConnectionMode, SyncThru, SyncThruAPINotSupported
8 from url_normalize import url_normalize
9 import voluptuous as vol
10 
11 from homeassistant.components import ssdp
12 from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
13 from homeassistant.const import CONF_NAME, CONF_URL
14 from homeassistant.helpers import aiohttp_client
15 
16 from .const import DEFAULT_MODEL, DEFAULT_NAME_TEMPLATE, DOMAIN
17 
18 
19 class SyncThruConfigFlow(ConfigFlow, domain=DOMAIN):
20  """Samsung SyncThru config flow."""
21 
22  VERSION = 1
23 
24  url: str
25  name: str
26 
27  async def async_step_user(
28  self, user_input: dict[str, Any] | None = None
29  ) -> ConfigFlowResult:
30  """Handle user initiated flow."""
31  if user_input is None:
32  return await self._async_show_form_async_show_form(step_id="user")
33  return await self._async_check_and_create_async_check_and_create("user", user_input)
34 
35  async def async_step_ssdp(
36  self, discovery_info: ssdp.SsdpServiceInfo
37  ) -> ConfigFlowResult:
38  """Handle SSDP initiated flow."""
39  await self.async_set_unique_idasync_set_unique_id(discovery_info.upnp[ssdp.ATTR_UPNP_UDN])
40  self._abort_if_unique_id_configured_abort_if_unique_id_configured()
41 
42  self.urlurl = url_normalize(
43  discovery_info.upnp.get(
44  ssdp.ATTR_UPNP_PRESENTATION_URL,
45  f"http://{urlparse(discovery_info.ssdp_location or '').hostname}/",
46  )
47  )
48 
49  for existing_entry in (
50  x for x in self._async_current_entries_async_current_entries() if x.data[CONF_URL] == self.urlurl
51  ):
52  # Update unique id of entry with the same URL
53  if not existing_entry.unique_id:
54  self.hass.config_entries.async_update_entry(
55  existing_entry, unique_id=discovery_info.upnp[ssdp.ATTR_UPNP_UDN]
56  )
57  return self.async_abortasync_abortasync_abort(reason="already_configured")
58 
59  self.namename = discovery_info.upnp.get(ssdp.ATTR_UPNP_FRIENDLY_NAME, "")
60  if self.namename:
61  # Remove trailing " (ip)" if present for consistency with user driven config
62  self.namename = re.sub(r"\s+\‍([\d.]+\‍)\s*$", "", self.namename)
63 
64  self.context["title_placeholders"] = {CONF_NAME: self.namename}
65  return await self.async_step_confirmasync_step_confirm()
66 
67  async def async_step_confirm(
68  self, user_input: dict[str, str] | None = None
69  ) -> ConfigFlowResult:
70  """Handle discovery confirmation by user."""
71  if user_input is not None:
72  return await self._async_check_and_create_async_check_and_create("confirm", user_input)
73 
74  return await self._async_show_form_async_show_form(
75  step_id="confirm",
76  user_input={CONF_URL: self.urlurl, CONF_NAME: self.namename},
77  )
78 
79  async def _async_show_form(self, step_id, user_input=None, errors=None):
80  """Show our form."""
81  if user_input is None:
82  user_input = {}
83  return self.async_show_formasync_show_formasync_show_form(
84  step_id=step_id,
85  data_schema=vol.Schema(
86  {
87  vol.Required(CONF_URL, default=user_input.get(CONF_URL, "")): str,
88  vol.Optional(CONF_NAME, default=user_input.get(CONF_NAME, "")): str,
89  }
90  ),
91  errors=errors or {},
92  )
93 
94  async def _async_check_and_create(self, step_id, user_input):
95  """Validate input, proceed to create."""
96  user_input[CONF_URL] = url_normalize(
97  user_input[CONF_URL], default_scheme="http"
98  )
99  if "://" not in user_input[CONF_URL]:
100  return await self._async_show_form_async_show_form(
101  step_id=step_id, user_input=user_input, errors={CONF_URL: "invalid_url"}
102  )
103 
104  # If we don't have a unique id, copy one from existing entry with same URL
105  if not self.unique_idunique_id:
106  for existing_entry in (
107  x
108  for x in self._async_current_entries_async_current_entries()
109  if x.data[CONF_URL] == user_input[CONF_URL] and x.unique_id
110  ):
111  await self.async_set_unique_idasync_set_unique_id(existing_entry.unique_id)
112  break
113 
114  session = aiohttp_client.async_get_clientsession(self.hass)
115  printer = SyncThru(
116  user_input[CONF_URL], session, connection_mode=ConnectionMode.API
117  )
118  errors = {}
119  try:
120  await printer.update()
121  if not user_input.get(CONF_NAME):
122  user_input[CONF_NAME] = DEFAULT_NAME_TEMPLATE.format(
123  printer.model() or DEFAULT_MODEL
124  )
125  except SyncThruAPINotSupported:
126  errors[CONF_URL] = "syncthru_not_supported"
127  else:
128  if printer.is_unknown_state():
129  errors[CONF_URL] = "unknown_state"
130 
131  if errors:
132  return await self._async_show_form_async_show_form(
133  step_id=step_id, user_input=user_input, errors=errors
134  )
135 
136  return self.async_create_entryasync_create_entryasync_create_entry(
137  title=user_input.get(CONF_NAME),
138  data=user_input,
139  )
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:29
ConfigFlowResult async_step_ssdp(self, ssdp.SsdpServiceInfo discovery_info)
Definition: config_flow.py:37
ConfigFlowResult async_step_confirm(self, dict[str, str]|None user_input=None)
Definition: config_flow.py:69
def _async_show_form(self, step_id, user_input=None, errors=None)
Definition: config_flow.py:79
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)
_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)