1 """Config flow for the Huawei LTE platform."""
3 from __future__
import annotations
5 from collections.abc
import Mapping
7 from typing
import TYPE_CHECKING, Any
8 from urllib.parse
import urlparse
10 from huawei_lte_api.Client
import Client
11 from huawei_lte_api.Connection
import Connection
12 from huawei_lte_api.exceptions
import (
13 LoginErrorPasswordWrongException,
14 LoginErrorUsernamePasswordOverrunException,
15 LoginErrorUsernamePasswordWrongException,
16 LoginErrorUsernameWrongException,
17 ResponseErrorException,
19 from huawei_lte_api.Session
import GetResponseType
20 from requests.exceptions
import SSLError, Timeout
21 from url_normalize
import url_normalize
22 import voluptuous
as vol
44 CONF_TRACK_WIRED_CLIENTS,
45 CONF_UNAUTHENTICATED_MODE,
48 DEFAULT_NOTIFY_SERVICE_NAME,
49 DEFAULT_TRACK_WIRED_CLIENTS,
50 DEFAULT_UNAUTHENTICATED_MODE,
53 from .utils
import get_device_macs, non_verifying_requests_session
55 _LOGGER = logging.getLogger(__name__)
59 """Handle Huawei LTE config flow."""
63 manufacturer: str |
None =
None
64 url: str |
None =
None
69 config_entry: ConfigEntry,
70 ) -> OptionsFlowHandler:
71 """Get options flow."""
76 user_input: dict[str, Any] |
None =
None,
77 errors: dict[str, str] |
None =
None,
78 ) -> ConfigFlowResult:
79 if user_input
is None:
83 data_schema=vol.Schema(
87 default=user_input.get(CONF_URL, self.
urlurl
or ""),
91 default=user_input.get(
97 CONF_USERNAME, default=user_input.get(CONF_USERNAME)
or ""
100 CONF_PASSWORD, default=user_input.get(CONF_PASSWORD)
or ""
109 user_input: dict[str, Any],
110 errors: dict[str, str] |
None =
None,
111 ) -> ConfigFlowResult:
113 step_id=
"reauth_confirm",
114 data_schema=vol.Schema(
117 CONF_USERNAME, default=user_input.get(CONF_USERNAME)
or ""
120 CONF_PASSWORD, default=user_input.get(CONF_PASSWORD)
or ""
128 self, user_input: dict[str, Any], errors: dict[str, str]
129 ) -> Connection |
None:
130 """Try connecting with given data."""
131 username = user_input.get(CONF_USERNAME)
or ""
132 password = user_input.get(CONF_PASSWORD)
or ""
134 def _get_connection() -> Connection:
136 user_input[CONF_URL].startswith(
"https://")
137 and not user_input[CONF_VERIFY_SSL]
141 requests_session =
None
144 url=user_input[CONF_URL],
147 timeout=CONNECTION_TIMEOUT,
148 requests_session=requests_session,
153 conn = await self.hass.async_add_executor_job(_get_connection)
154 except LoginErrorUsernameWrongException:
155 errors[CONF_USERNAME] =
"incorrect_username"
156 except LoginErrorPasswordWrongException:
157 errors[CONF_PASSWORD] =
"incorrect_password"
158 except LoginErrorUsernamePasswordWrongException:
159 errors[CONF_USERNAME] =
"invalid_auth"
160 except LoginErrorUsernamePasswordOverrunException:
161 errors[
"base"] =
"login_attempts_exceeded"
162 except ResponseErrorException:
163 _LOGGER.warning(
"Response error", exc_info=
True)
164 errors[
"base"] =
"response_error"
166 _LOGGER.warning(
"SSL error", exc_info=
True)
167 if user_input[CONF_VERIFY_SSL]:
168 errors[CONF_URL] =
"ssl_error_try_unverified"
170 errors[CONF_URL] =
"ssl_error_try_plain"
172 _LOGGER.warning(
"Connection timeout", exc_info=
True)
173 errors[CONF_URL] =
"connection_timeout"
175 _LOGGER.warning(
"Unknown error connecting to device", exc_info=
True)
176 errors[CONF_URL] =
"unknown"
183 conn.requests_session.close()
185 _LOGGER.debug(
"Disconnect error", exc_info=
True)
188 self, user_input: dict[str, Any] |
None =
None
189 ) -> ConfigFlowResult:
190 """Handle user initiated config flow."""
191 if user_input
is None:
197 user_input[CONF_URL] = url_normalize(
198 user_input[CONF_URL], default_scheme=
"http"
200 if "://" not in user_input[CONF_URL]:
201 errors[CONF_URL] =
"invalid_url"
203 user_input=user_input, errors=errors
208 ) -> tuple[GetResponseType, GetResponseType]:
209 """Get router info."""
210 client = Client(conn)
212 device_info = client.device.information()
214 _LOGGER.debug(
"Could not get device.information", exc_info=
True)
216 device_info = client.device.basic_information()
219 "Could not get device.basic_information", exc_info=
True
223 wlan_settings = client.wlan.multi_basic_settings()
225 _LOGGER.debug(
"Could not get wlan.multi_basic_settings", exc_info=
True)
227 return device_info, wlan_settings
229 conn = await self.
_connect_connect(user_input, errors)
232 user_input=user_input, errors=errors
236 info, wlan_settings = await self.hass.async_add_executor_job(
237 get_device_info, conn
239 await self.hass.async_add_executor_job(self.
_disconnect_disconnect, conn)
249 if serial_number := info.get(
"SerialNumber"):
256 self.context.
get(
"title_placeholders", {}).
get(CONF_NAME)
257 or info.get(
"DeviceName")
258 or info.get(
"devicename")
259 or DEFAULT_DEVICE_NAME
265 self, discovery_info: ssdp.SsdpServiceInfo
266 ) -> ConfigFlowResult:
267 """Handle SSDP initiated config flow."""
270 assert discovery_info.ssdp_location
272 discovery_info.upnp.get(
273 ssdp.ATTR_UPNP_PRESENTATION_URL,
274 f
"http://{urlparse(discovery_info.ssdp_location).hostname}/",
278 unique_id = discovery_info.upnp.get(
279 ssdp.ATTR_UPNP_SERIAL, discovery_info.upnp[ssdp.ATTR_UPNP_UDN]
284 def _is_supported_device() -> bool:
285 """See if we are looking at a possibly supported device.
287 Matching solely on SSDP data does not yield reliable enough results.
290 with Connection(url=url, timeout=CONNECTION_TIMEOUT)
as conn:
291 basic_info = Client(conn).device.basic_information()
292 except ResponseErrorException:
296 return isinstance(basic_info, dict)
298 if not await self.hass.async_add_executor_job(_is_supported_device):
303 "title_placeholders": {
304 CONF_NAME: discovery_info.upnp.get(ssdp.ATTR_UPNP_FRIENDLY_NAME)
309 self.
manufacturermanufacturer = discovery_info.upnp.get(ssdp.ATTR_UPNP_MANUFACTURER)
314 self, entry_data: Mapping[str, Any]
315 ) -> ConfigFlowResult:
316 """Perform reauth upon an API authentication error."""
320 self, user_input: dict[str, Any] |
None =
None
321 ) -> ConfigFlowResult:
322 """Dialog that informs the user that reauth is required."""
327 CONF_USERNAME: entry.data[CONF_USERNAME],
328 CONF_PASSWORD: entry.data[CONF_PASSWORD],
332 new_data = {**entry.data, **user_input}
333 errors: dict[str, str] = {}
334 conn = await self.
_connect_connect(new_data, errors)
336 await self.hass.async_add_executor_job(self.
_disconnect_disconnect, conn)
339 user_input=user_input, errors=errors
346 """Huawei LTE options flow."""
349 self, user_input: dict[str, Any] |
None =
None
350 ) -> ConfigFlowResult:
351 """Handle options flow."""
355 if user_input
is not None:
358 if not isinstance(data[CONF_RECIPIENT], list):
359 data[CONF_RECIPIENT] = [
360 x.strip()
for x
in data[CONF_RECIPIENT].split(
",")
364 data_schema = vol.Schema(
369 CONF_NAME, DEFAULT_NOTIFY_SERVICE_NAME
379 CONF_TRACK_WIRED_CLIENTS,
381 CONF_TRACK_WIRED_CLIENTS, DEFAULT_TRACK_WIRED_CLIENTS
385 CONF_UNAUTHENTICATED_MODE,
387 CONF_UNAUTHENTICATED_MODE, DEFAULT_UNAUTHENTICATED_MODE
392 return self.
async_show_formasync_show_form(step_id=
"init", data_schema=data_schema)
Connection|None _connect(self, dict[str, Any] user_input, dict[str, str] errors)
ConfigFlowResult async_step_reauth(self, Mapping[str, Any] entry_data)
OptionsFlowHandler async_get_options_flow(ConfigEntry config_entry)
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
ConfigFlowResult _async_show_user_form(self, dict[str, Any]|None user_input=None, dict[str, str]|None errors=None)
ConfigFlowResult async_step_reauth_confirm(self, dict[str, Any]|None user_input=None)
None _disconnect(Connection conn)
ConfigFlowResult async_step_ssdp(self, ssdp.SsdpServiceInfo discovery_info)
ConfigFlowResult _async_show_reauth_form(self, dict[str, Any] user_input, dict[str, str]|None errors=None)
ConfigFlowResult async_step_init(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")
None _async_handle_discovery_without_unique_id(self)
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_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)
ConfigEntry config_entry(self)
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)
web.Response get(self, web.Request request, str config_key)
requests.Session non_verifying_requests_session(str url)
list[str] get_device_macs(GetResponseType device_info, GetResponseType wlan_settings)
IssData update(pyiss.ISS iss)
DeviceInfo get_device_info(str coordinates, str name)