Home Assistant Unofficial Reference 2024.12.1
config_flow.py
Go to the documentation of this file.
1 """Config flow to configure songpal component."""
2 
3 from __future__ import annotations
4 
5 import logging
6 from typing import TYPE_CHECKING, Any
7 from urllib.parse import urlparse
8 
9 from songpal import Device, SongpalException
10 import voluptuous as vol
11 
12 from homeassistant.components import ssdp
13 from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
14 from homeassistant.const import CONF_HOST, CONF_NAME
15 
16 from .const import CONF_ENDPOINT, DOMAIN
17 
18 _LOGGER = logging.getLogger(__name__)
19 
20 
22  """Device Configuration."""
23 
24  def __init__(self, name: str, host: str | None, endpoint: str) -> None:
25  """Initialize Configuration."""
26  self.namename = name
27  if TYPE_CHECKING:
28  assert host is not None
29  self.hosthost = host
30  self.endpointendpoint = endpoint
31 
32 
33 class SongpalConfigFlow(ConfigFlow, domain=DOMAIN):
34  """Songpal configuration flow."""
35 
36  VERSION = 1
37 
38  conf: SongpalConfig
39 
40  async def async_step_user(
41  self, user_input: dict[str, str] | None = None
42  ) -> ConfigFlowResult:
43  """Handle a flow initiated by the user."""
44  if user_input is None:
45  return self.async_show_formasync_show_formasync_show_form(
46  step_id="user",
47  data_schema=vol.Schema({vol.Required(CONF_ENDPOINT): str}),
48  )
49 
50  # Validate input
51  endpoint = user_input[CONF_ENDPOINT]
52  parsed_url = urlparse(endpoint)
53 
54  # Try to connect and get device name
55  try:
56  device = Device(endpoint)
57  await device.get_supported_methods()
58  interface_info = await device.get_interface_information()
59  name = interface_info.modelName
60  except SongpalException as ex:
61  _LOGGER.debug("Connection failed: %s", ex)
62  return self.async_show_formasync_show_formasync_show_form(
63  step_id="user",
64  data_schema=vol.Schema(
65  {
66  vol.Required(
67  CONF_ENDPOINT, default=user_input.get(CONF_ENDPOINT, "")
68  ): str,
69  }
70  ),
71  errors={"base": "cannot_connect"},
72  )
73 
74  self.confconf = SongpalConfig(name, parsed_url.hostname, endpoint)
75 
76  return await self.async_step_initasync_step_init(user_input)
77 
78  async def async_step_init(
79  self, user_input: dict[str, Any] | None = None
80  ) -> ConfigFlowResult:
81  """Handle a flow start."""
82  # Check if already configured
83  self._async_abort_entries_match_async_abort_entries_match({CONF_ENDPOINT: self.confconf.endpoint})
84  if user_input is None:
85  return self.async_show_formasync_show_formasync_show_form(
86  step_id="init",
87  description_placeholders={
88  CONF_NAME: self.confconf.name,
89  CONF_HOST: self.confconf.host,
90  },
91  )
92 
93  await self.async_set_unique_idasync_set_unique_id(self.confconf.endpoint)
94  self._abort_if_unique_id_configured_abort_if_unique_id_configured()
95 
96  return self.async_create_entryasync_create_entryasync_create_entry(
97  title=self.confconf.name,
98  data={CONF_NAME: self.confconf.name, CONF_ENDPOINT: self.confconf.endpoint},
99  )
100 
101  async def async_step_ssdp(
102  self, discovery_info: ssdp.SsdpServiceInfo
103  ) -> ConfigFlowResult:
104  """Handle a discovered Songpal device."""
105  await self.async_set_unique_idasync_set_unique_id(discovery_info.upnp[ssdp.ATTR_UPNP_UDN])
106  self._abort_if_unique_id_configured_abort_if_unique_id_configured()
107 
108  _LOGGER.debug("Discovered: %s", discovery_info)
109 
110  friendly_name = discovery_info.upnp[ssdp.ATTR_UPNP_FRIENDLY_NAME]
111  hostname = urlparse(discovery_info.ssdp_location).hostname
112  scalarweb_info = discovery_info.upnp["X_ScalarWebAPI_DeviceInfo"]
113  endpoint = scalarweb_info["X_ScalarWebAPI_BaseURL"]
114  service_types = scalarweb_info["X_ScalarWebAPI_ServiceList"][
115  "X_ScalarWebAPI_ServiceType"
116  ]
117 
118  # Ignore Bravia TVs
119  if "videoScreen" in service_types:
120  return self.async_abortasync_abortasync_abort(reason="not_songpal_device")
121 
122  if TYPE_CHECKING:
123  # the hostname must be str because the ssdp_location is not bytes and
124  # not a relative url
125  assert isinstance(hostname, str)
126 
127  self.context["title_placeholders"] = {
128  CONF_NAME: friendly_name,
129  CONF_HOST: hostname,
130  }
131 
132  self.confconf = SongpalConfig(friendly_name, hostname, endpoint)
133 
134  return await self.async_step_initasync_step_init()
135 
136  async def async_step_import(self, import_data: dict[str, str]) -> ConfigFlowResult:
137  """Import a config entry."""
138  name = import_data.get(CONF_NAME)
139  endpoint = import_data[CONF_ENDPOINT]
140  parsed_url = urlparse(endpoint)
141 
142  # Try to connect to test the endpoint
143  try:
144  device = Device(endpoint)
145  await device.get_supported_methods()
146  # Get name
147  if name is None:
148  interface_info = await device.get_interface_information()
149  name = interface_info.modelName
150  except SongpalException as ex:
151  _LOGGER.error("Import from yaml configuration failed: %s", ex)
152  return self.async_abortasync_abortasync_abort(reason="cannot_connect")
153 
154  self.confconf = SongpalConfig(name, parsed_url.hostname, endpoint)
155 
156  return await self.async_step_initasync_step_init(import_data)
ConfigFlowResult async_step_user(self, dict[str, str]|None user_input=None)
Definition: config_flow.py:42
ConfigFlowResult async_step_ssdp(self, ssdp.SsdpServiceInfo discovery_info)
Definition: config_flow.py:103
ConfigFlowResult async_step_import(self, dict[str, str] import_data)
Definition: config_flow.py:136
ConfigFlowResult async_step_init(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:80
None __init__(self, str name, str|None host, str endpoint)
Definition: config_flow.py:24
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)
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)
_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)