1 """Config flow for TP-Link Omada integration."""
3 from __future__
import annotations
5 from collections.abc
import Mapping
8 from types
import MappingProxyType
9 from typing
import Any, NamedTuple
10 from urllib.parse
import urlsplit
12 from aiohttp
import CookieJar
13 from tplink_omada_client
import OmadaClient, OmadaSite
14 from tplink_omada_client.exceptions
import (
18 UnsupportedControllerVersion,
20 import voluptuous
as vol
27 async_create_clientsession,
28 async_get_clientsession,
31 from .const
import DOMAIN
33 _LOGGER = logging.getLogger(__name__)
37 STEP_USER_DATA_SCHEMA = vol.Schema(
39 vol.Required(CONF_HOST): str,
40 vol.Required(CONF_VERIFY_SSL, default=
True): bool,
41 vol.Required(CONF_USERNAME): str,
42 vol.Required(CONF_PASSWORD): str,
48 hass: HomeAssistant, data: MappingProxyType[str, Any]
50 """Create a TP-Link Omada client API for the given config entry."""
52 host: str = data[CONF_HOST]
53 verify_ssl = bool(data[CONF_VERIFY_SSL])
55 if not host.lower().startswith((
"http://",
"https://")):
56 host =
"https://" + host
57 host_parts = urlsplit(host)
60 and re.fullmatch(
r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}", host_parts.hostname)
65 hass, cookie_jar=CookieJar(unsafe=
True), verify_ssl=verify_ssl
70 username = data[CONF_USERNAME]
71 password = data[CONF_PASSWORD]
73 return OmadaClient(host, username, password, websession=websession)
77 """Discovered controller information."""
81 sites: list[OmadaSite]
85 """Validate the user input allows us to connect."""
88 controller_id = await client.login()
89 name = await client.get_controller_name()
90 sites = await client.get_sites()
92 return HubInfo(controller_id, name, sites)
96 """Handle a config flow for TP-Link Omada."""
101 """Create the config flow for a new integration."""
103 self.
_sites_sites: list[OmadaSite] = []
107 self, user_input: dict[str, Any] |
None =
None
108 ) -> ConfigFlowResult:
109 """Handle the initial step."""
111 errors: dict[str, str] = {}
113 if user_input
is not None:
114 info = await self.
_test_login_test_login(user_input, errors)
116 if info
is None or user_input
is None:
118 step_id=
"user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
127 if len(self.
_sites_sites) > 1:
132 self, user_input: dict[str, Any] |
None =
None
133 ) -> ConfigFlowResult:
134 """Handle step to select site to manage."""
136 if user_input
is None:
139 vol.Required(CONF_SITE,
"site"): selector.SelectSelector(
140 selector.SelectSelectorConfig(
142 selector.SelectOptionDict(value=s.id, label=s.name)
143 for s
in self.
_sites_sites
146 mode=selector.SelectSelectorMode.DROPDOWN,
156 site
for site
in self.
_sites_sites
if site.id == user_input[
"site"]
158 display_name = f
"{self._controller_name} ({site_name})"
163 self, entry_data: Mapping[str, Any]
164 ) -> ConfigFlowResult:
165 """Perform reauth upon an API authentication error."""
170 self, user_input: dict[str, Any] |
None =
None
171 ) -> ConfigFlowResult:
172 """Dialog that informs the user that reauth is required."""
174 errors: dict[str, str] = {}
176 if user_input
is not None:
187 step_id=
"reauth_confirm",
188 data_schema=vol.Schema(
190 vol.Required(CONF_USERNAME): str,
191 vol.Required(CONF_PASSWORD): str,
198 self, data: dict[str, Any], errors: dict[str, str]
202 if len(info.sites) > 0:
204 errors[
"base"] =
"no_sites_found"
206 except ConnectionFailed:
207 errors[
"base"] =
"cannot_connect"
209 errors[
"base"] =
"invalid_auth"
210 except UnsupportedControllerVersion:
211 errors[
"base"] =
"unsupported_controller"
212 except OmadaClientException
as ex:
213 _LOGGER.error(
"Unexpected API error: %s", ex)
214 errors[
"base"] =
"unknown"
216 _LOGGER.exception(
"Unexpected exception")
217 errors[
"base"] =
"unknown"
ConfigFlowResult async_step_site(self, dict[str, Any]|None user_input=None)
HubInfo|None _test_login(self, dict[str, Any] data, dict[str, str] errors)
ConfigFlowResult async_step_reauth_confirm(self, dict[str, Any]|None user_input=None)
ConfigFlowResult async_step_reauth(self, Mapping[str, Any] entry_data)
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
None _abort_if_unique_id_configured(self, dict[str, Any]|None updates=None, bool reload_on_update=True, *str error="already_configured")
ConfigEntry _get_reauth_entry(self)
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_update_reload_and_abort(self, ConfigEntry entry, *str|None|UndefinedType unique_id=UNDEFINED, str|UndefinedType title=UNDEFINED, Mapping[str, Any]|UndefinedType data=UNDEFINED, Mapping[str, Any]|UndefinedType data_updates=UNDEFINED, Mapping[str, Any]|UndefinedType options=UNDEFINED, str|UndefinedType reason=UNDEFINED, bool reload_even_if_entry_is_unchanged=True)
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)
IssData update(pyiss.ISS iss)
aiohttp.ClientSession async_create_clientsession()
HubInfo _validate_input(HomeAssistant hass, dict[str, Any] data)
OmadaClient create_omada_client(HomeAssistant hass, MappingProxyType[str, Any] data)
aiohttp.ClientSession async_get_clientsession(HomeAssistant hass, bool verify_ssl=True, socket.AddressFamily family=socket.AF_UNSPEC, ssl_util.SSLCipherList ssl_cipher=ssl_util.SSLCipherList.PYTHON_DEFAULT)