Home Assistant Unofficial Reference 2024.12.1
config_flow.py
Go to the documentation of this file.
1 """Config flow for ScreenLogic."""
2 
3 from __future__ import annotations
4 
5 import logging
6 from typing import Any
7 
8 from screenlogicpy import ScreenLogicError, discovery
9 from screenlogicpy.const.common import SL_GATEWAY_IP, SL_GATEWAY_NAME, SL_GATEWAY_PORT
10 from screenlogicpy.requests import login
11 import voluptuous as vol
12 
13 from homeassistant.components import dhcp
14 from homeassistant.config_entries import (
15  ConfigEntry,
16  ConfigFlow,
17  ConfigFlowResult,
18  OptionsFlow,
19 )
20 from homeassistant.const import CONF_IP_ADDRESS, CONF_PORT, CONF_SCAN_INTERVAL
21 from homeassistant.core import callback
23 from homeassistant.helpers.device_registry import format_mac
24 
25 from .const import DEFAULT_SCAN_INTERVAL, DOMAIN, MIN_SCAN_INTERVAL
26 
27 _LOGGER = logging.getLogger(__name__)
28 
29 GATEWAY_SELECT_KEY = "selected_gateway"
30 GATEWAY_MANUAL_ENTRY = "manual"
31 
32 PENTAIR_OUI = "00-C0-33"
33 
34 
35 async def async_discover_gateways_by_unique_id() -> dict[str, dict[str, Any]]:
36  """Discover gateways and return a dict of them by unique id."""
37  discovered_gateways: dict[str, dict[str, Any]] = {}
38  try:
39  hosts = await discovery.async_discover()
40  _LOGGER.debug("Discovered hosts: %s", hosts)
41  except ScreenLogicError as ex:
42  _LOGGER.debug(ex)
43  return discovered_gateways
44 
45  for host in hosts:
46  if (name := host[SL_GATEWAY_NAME]).startswith("Pentair:"):
47  mac = _extract_mac_from_name(name)
48  discovered_gateways[mac] = host
49 
50  _LOGGER.debug("Discovered gateways: %s", discovered_gateways)
51  return discovered_gateways
52 
53 
54 def _extract_mac_from_name(name: str) -> str:
55  return format_mac(f"{PENTAIR_OUI}-{name.split(':')[1].strip()}")
56 
57 
58 def short_mac(mac: str) -> str:
59  """Short version of the mac as seen in the app."""
60  return "-".join(mac.split(":")[3:]).upper()
61 
62 
63 def name_for_mac(mac: str) -> str:
64  """Derive the gateway name from the mac."""
65  return f"Pentair: {short_mac(mac)}"
66 
67 
68 class ScreenlogicConfigFlow(ConfigFlow, domain=DOMAIN):
69  """Config flow to setup screen logic devices."""
70 
71  VERSION = 1
72 
73  def __init__(self) -> None:
74  """Initialize ScreenLogic ConfigFlow."""
75  self.discovered_gatewaysdiscovered_gateways: dict[str, dict[str, Any]] = {}
76  self.discovered_ipdiscovered_ip: str | None = None
77 
78  @staticmethod
79  @callback
81  config_entry: ConfigEntry,
82  ) -> ScreenLogicOptionsFlowHandler:
83  """Get the options flow for ScreenLogic."""
85 
86  async def async_step_user(
87  self, user_input: dict[str, Any] | None = None
88  ) -> ConfigFlowResult:
89  """Handle the start of the config flow."""
91  return await self.async_step_gateway_selectasync_step_gateway_select()
92 
93  async def async_step_dhcp(
94  self, discovery_info: dhcp.DhcpServiceInfo
95  ) -> ConfigFlowResult:
96  """Handle dhcp discovery."""
97  mac = format_mac(discovery_info.macaddress)
98  await self.async_set_unique_idasync_set_unique_id(mac)
99  self._abort_if_unique_id_configured_abort_if_unique_id_configured(
100  updates={CONF_IP_ADDRESS: discovery_info.ip}
101  )
102  self.discovered_ipdiscovered_ip = discovery_info.ip
103  self.context["title_placeholders"] = {"name": discovery_info.hostname}
104  return await self.async_step_gateway_entryasync_step_gateway_entry()
105 
106  async def async_step_gateway_select(self, user_input=None) -> ConfigFlowResult:
107  """Handle the selection of a discovered ScreenLogic gateway."""
108  existing = self._async_current_ids_async_current_ids()
109  unconfigured_gateways = {
110  mac: gateway[SL_GATEWAY_NAME]
111  for mac, gateway in self.discovered_gatewaysdiscovered_gateways.items()
112  if mac not in existing
113  }
114 
115  if not unconfigured_gateways:
116  return await self.async_step_gateway_entryasync_step_gateway_entry()
117 
118  errors: dict[str, str] = {}
119  if user_input is not None:
120  if user_input[GATEWAY_SELECT_KEY] == GATEWAY_MANUAL_ENTRY:
121  return await self.async_step_gateway_entryasync_step_gateway_entry()
122 
123  mac = user_input[GATEWAY_SELECT_KEY]
124  selected_gateway = self.discovered_gatewaysdiscovered_gateways[mac]
125  await self.async_set_unique_idasync_set_unique_id(mac, raise_on_progress=False)
126  self._abort_if_unique_id_configured_abort_if_unique_id_configured()
127  return self.async_create_entryasync_create_entryasync_create_entry(
128  title=name_for_mac(mac),
129  data={
130  CONF_IP_ADDRESS: selected_gateway[SL_GATEWAY_IP],
131  CONF_PORT: selected_gateway[SL_GATEWAY_PORT],
132  },
133  )
134 
135  return self.async_show_formasync_show_formasync_show_form(
136  step_id="gateway_select",
137  data_schema=vol.Schema(
138  {
139  vol.Required(GATEWAY_SELECT_KEY): vol.In(
140  {
141  **unconfigured_gateways,
142  GATEWAY_MANUAL_ENTRY: (
143  "Manually configure a ScreenLogic gateway"
144  ),
145  }
146  )
147  }
148  ),
149  errors=errors,
150  description_placeholders={},
151  )
152 
153  async def async_step_gateway_entry(self, user_input=None) -> ConfigFlowResult:
154  """Handle the manual entry of a ScreenLogic gateway."""
155  errors: dict[str, str] = {}
156  ip_address = self.discovered_ipdiscovered_ip
157  port = 80
158 
159  if user_input is not None:
160  ip_address = user_input[CONF_IP_ADDRESS]
161  port = user_input[CONF_PORT]
162  try:
163  mac = format_mac(await login.async_get_mac_address(ip_address, port))
164  except ScreenLogicError as ex:
165  _LOGGER.debug(ex)
166  errors[CONF_IP_ADDRESS] = "cannot_connect"
167 
168  if not errors:
169  await self.async_set_unique_idasync_set_unique_id(mac, raise_on_progress=False)
170  self._abort_if_unique_id_configured_abort_if_unique_id_configured()
171  return self.async_create_entryasync_create_entryasync_create_entry(
172  title=name_for_mac(mac),
173  data={
174  CONF_IP_ADDRESS: ip_address,
175  CONF_PORT: port,
176  },
177  )
178 
179  return self.async_show_formasync_show_formasync_show_form(
180  step_id="gateway_entry",
181  data_schema=vol.Schema(
182  {
183  vol.Required(CONF_IP_ADDRESS, default=ip_address): str,
184  vol.Required(CONF_PORT, default=port): int,
185  }
186  ),
187  errors=errors,
188  description_placeholders={},
189  )
190 
191 
193  """Handles the options for the ScreenLogic integration."""
194 
195  async def async_step_init(self, user_input=None) -> ConfigFlowResult:
196  """Manage the options."""
197  if user_input is not None:
198  return self.async_create_entryasync_create_entry(
199  title=self.config_entryconfig_entryconfig_entry.title, data=user_input
200  )
201 
202  current_interval = self.config_entryconfig_entryconfig_entry.options.get(
203  CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL
204  )
205  return self.async_show_formasync_show_form(
206  step_id="init",
207  data_schema=vol.Schema(
208  {
209  vol.Required(
210  CONF_SCAN_INTERVAL,
211  default=current_interval,
212  ): vol.All(cv.positive_int, vol.Clamp(min=MIN_SCAN_INTERVAL))
213  }
214  ),
215  description_placeholders={"gateway_name": self.config_entryconfig_entryconfig_entry.title},
216  )
ConfigFlowResult async_step_gateway_select(self, user_input=None)
Definition: config_flow.py:106
ScreenLogicOptionsFlowHandler async_get_options_flow(ConfigEntry config_entry)
Definition: config_flow.py:82
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:88
ConfigFlowResult async_step_dhcp(self, dhcp.DhcpServiceInfo discovery_info)
Definition: config_flow.py:95
ConfigFlowResult async_step_gateway_entry(self, user_input=None)
Definition: config_flow.py:153
None _abort_if_unique_id_configured(self, dict[str, Any]|None updates=None, bool reload_on_update=True, *str error="already_configured")
set[str|None] _async_current_ids(self, bool include_ignore=True)
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_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)
dict[str, dict[str, Any]] async_discover_gateways_by_unique_id()
Definition: config_flow.py:35