1 """Config flow for UniFi Network integration.
3 Provides user initiated configuration flow.
4 Discovery of UniFi Network instances hosted on UDM and UDM Pro devices
5 through SSDP. Reauthentication when issue with credentials are reported.
6 Configuration of options through options flow.
9 from __future__
import annotations
11 from collections.abc
import Mapping
14 from types
import MappingProxyType
15 from typing
import Any
16 from urllib.parse
import urlparse
18 from aiounifi.interfaces.sites
import Sites
19 import voluptuous
as vol
40 from .
import UnifiConfigEntry
42 CONF_ALLOW_BANDWIDTH_SENSORS,
43 CONF_ALLOW_UPTIME_SENSORS,
47 CONF_DPI_RESTRICTIONS,
48 CONF_IGNORE_WIRED_BUG,
53 CONF_TRACK_WIRED_CLIENTS,
54 DEFAULT_DPI_RESTRICTIONS,
55 DOMAIN
as UNIFI_DOMAIN,
57 from .errors
import AuthenticationRequired, CannotConnect
58 from .hub
import UnifiHub, get_unifi_api
61 DEFAULT_SITE_ID =
"default"
62 DEFAULT_VERIFY_SSL =
False
66 "UniFi Dream Machine": 443,
67 "UniFi Dream Machine Pro": 443,
72 """Handle a UniFi Network config flow."""
81 config_entry: UnifiConfigEntry,
82 ) -> UnifiOptionsFlowHandler:
83 """Get the options flow for this handler."""
87 """Initialize the UniFi Network flow."""
88 self.
configconfig: dict[str, Any] = {}
92 self, user_input: dict[str, Any] |
None =
None
93 ) -> ConfigFlowResult:
94 """Handle a flow initialized by the user."""
97 if user_input
is not None:
99 CONF_HOST: user_input[CONF_HOST],
100 CONF_USERNAME: user_input[CONF_USERNAME],
101 CONF_PASSWORD: user_input[CONF_PASSWORD],
102 CONF_PORT: user_input.get(CONF_PORT),
103 CONF_VERIFY_SSL: user_input.get(CONF_VERIFY_SSL),
104 CONF_SITE_ID: DEFAULT_SITE_ID,
109 await hub.sites.update()
112 except AuthenticationRequired:
113 errors[
"base"] =
"faulty_credentials"
115 except CannotConnect:
116 errors[
"base"] =
"service_unavailable"
125 and reauth_unique_id
in self.
sitessites
127 return await self.
async_step_siteasync_step_site({CONF_SITE_ID: reauth_unique_id})
137 vol.Required(CONF_HOST, default=host): str,
138 vol.Required(CONF_USERNAME): str,
139 vol.Required(CONF_PASSWORD): str,
141 CONF_PORT, default=self.
configconfig.
get(CONF_PORT, DEFAULT_PORT)
143 vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): bool,
148 data_schema=vol.Schema(data),
153 self, user_input: dict[str, Any] |
None =
None
154 ) -> ConfigFlowResult:
155 """Select site to control."""
156 if user_input
is not None:
157 unique_id = user_input[CONF_SITE_ID]
158 self.
configconfig[CONF_SITE_ID] = self.
sitessites[unique_id].name
161 abort_reason =
"configuration_updated"
165 abort_reason =
"reauth_successful"
169 config_entry.state
is ConfigEntryState.LOADED
170 and (hub := config_entry.runtime_data)
176 config_entry, data=self.
configconfig, reason=abort_reason
179 site_nice_name = self.
sitessites[unique_id].description
182 if len(self.
sitessites.values()) == 1:
185 site_names = {site.site_id: site.description
for site
in self.
sitessites.values()}
188 data_schema=vol.Schema({vol.Required(CONF_SITE_ID): vol.In(site_names)}),
192 self, entry_data: Mapping[str, Any]
193 ) -> ConfigFlowResult:
194 """Trigger a reauthentication flow."""
197 self.context[
"title_placeholders"] = {
198 CONF_HOST: reauth_entry.data[CONF_HOST],
199 CONF_SITE_ID: reauth_entry.title,
203 vol.Required(CONF_HOST, default=reauth_entry.data[CONF_HOST]): str,
204 vol.Required(CONF_USERNAME, default=reauth_entry.data[CONF_USERNAME]): str,
205 vol.Required(CONF_PASSWORD): str,
206 vol.Required(CONF_PORT, default=reauth_entry.data[CONF_PORT]): int,
208 CONF_VERIFY_SSL, default=reauth_entry.data[CONF_VERIFY_SSL]
215 self, discovery_info: ssdp.SsdpServiceInfo
216 ) -> ConfigFlowResult:
217 """Handle a discovered UniFi device."""
218 parsed_url = urlparse(discovery_info.ssdp_location)
219 model_description = discovery_info.upnp[ssdp.ATTR_UPNP_MODEL_DESCRIPTION]
220 mac_address =
format_mac(discovery_info.upnp[ssdp.ATTR_UPNP_SERIAL])
223 CONF_HOST: parsed_url.hostname,
231 self.context[
"title_placeholders"] = {
232 CONF_HOST: self.
configconfig[CONF_HOST],
233 CONF_SITE_ID: DEFAULT_SITE_ID,
236 if (port := MODEL_PORTS.get(model_description))
is not None:
237 self.
configconfig[CONF_PORT] = port
238 self.context[
"configuration_url"] = (
239 f
"https://{self.config[CONF_HOST]}:{port}"
246 """Handle Unifi Network options."""
250 def __init__(self, config_entry: UnifiConfigEntry) ->
None:
251 """Initialize UniFi Network options flow."""
255 self, user_input: dict[str, Any] |
None =
None
256 ) -> ConfigFlowResult:
257 """Manage the UniFi Network options."""
259 self.
optionsoptions[CONF_BLOCK_CLIENT] = self.
hubhub.config.option_block_clients
267 self, user_input: dict[str, Any] |
None =
None
268 ) -> ConfigFlowResult:
269 """For users without advanced settings enabled."""
270 if user_input
is not None:
274 clients_to_block = {}
276 for client
in self.
hubhub.api.clients.values():
277 clients_to_block[client.mac] = (
278 f
"{client.name or client.hostname} ({client.mac})"
282 step_id=
"simple_options",
283 data_schema=vol.Schema(
287 default=self.
hubhub.config.option_track_clients,
291 default=self.
hubhub.config.option_track_devices,
294 CONF_BLOCK_CLIENT, default=self.
optionsoptions[CONF_BLOCK_CLIENT]
295 ): cv.multi_select(clients_to_block),
302 self, user_input: dict[str, Any] |
None =
None
303 ) -> ConfigFlowResult:
304 """Select sources for entities."""
305 if user_input
is not None:
310 client.mac: f
"{client.name or client.hostname} ({client.mac})"
311 for client
in self.
hubhub.api.clients.values()
314 mac: f
"Unknown ({mac})"
315 for mac
in self.
optionsoptions.
get(CONF_CLIENT_SOURCE, [])
316 if mac
not in clients
320 step_id=
"configure_entity_sources",
321 data_schema=vol.Schema(
325 default=self.
optionsoptions.
get(CONF_CLIENT_SOURCE, []),
327 dict(sorted(clients.items(), key=operator.itemgetter(1)))
335 self, user_input: dict[str, Any] |
None =
None
336 ) -> ConfigFlowResult:
337 """Manage the device tracker options."""
338 if user_input
is not None:
343 {wlan.name
for wlan
in self.
hubhub.api.wlans.values()}
345 f
"{wlan.name}{wlan.name_combine_suffix}"
346 for wlan
in self.
hubhub.api.wlans.values()
347 if not wlan.name_combine_enabled
348 and wlan.name_combine_suffix
is not None
352 for ap
in self.
hubhub.api.devices.values()
353 for wlan
in ap.wlan_overrides
357 ssid_filter = {ssid: ssid
for ssid
in sorted(ssids)}
359 selected_ssids_to_filter = [
360 ssid
for ssid
in self.
hubhub.config.option_ssid_filter
if ssid
in ssid_filter
364 step_id=
"device_tracker",
365 data_schema=vol.Schema(
369 default=self.
hubhub.config.option_track_clients,
372 CONF_TRACK_WIRED_CLIENTS,
373 default=self.
hubhub.config.option_track_wired_clients,
377 default=self.
hubhub.config.option_track_devices,
380 CONF_SSID_FILTER, default=selected_ssids_to_filter
381 ): cv.multi_select(ssid_filter),
385 self.
hubhub.config.option_detection_time.total_seconds()
389 CONF_IGNORE_WIRED_BUG,
390 default=self.
hubhub.config.option_ignore_wired_bug,
398 self, user_input: dict[str, Any] |
None =
None
399 ) -> ConfigFlowResult:
400 """Manage configuration of network access controlled clients."""
401 if user_input
is not None:
405 clients_to_block = {}
407 for client
in self.
hubhub.api.clients.values():
408 clients_to_block[client.mac] = (
409 f
"{client.name or client.hostname} ({client.mac})"
412 selected_clients_to_block = [
414 for client
in self.
optionsoptions.
get(CONF_BLOCK_CLIENT, [])
415 if client
in clients_to_block
419 step_id=
"client_control",
420 data_schema=vol.Schema(
423 CONF_BLOCK_CLIENT, default=selected_clients_to_block
424 ): cv.multi_select(clients_to_block),
426 CONF_DPI_RESTRICTIONS,
428 CONF_DPI_RESTRICTIONS, DEFAULT_DPI_RESTRICTIONS
437 self, user_input: dict[str, Any] |
None =
None
438 ) -> ConfigFlowResult:
439 """Manage the statistics sensors options."""
440 if user_input
is not None:
445 step_id=
"statistics_sensors",
446 data_schema=vol.Schema(
449 CONF_ALLOW_BANDWIDTH_SENSORS,
450 default=self.
hubhub.config.option_allow_bandwidth_sensors,
453 CONF_ALLOW_UPTIME_SENSORS,
454 default=self.
hubhub.config.option_allow_uptime_sensors,
462 """Update config entry options."""
467 """Discover UniFi Network address."""
469 return await hass.async_add_executor_job(socket.gethostbyname,
"unifi")
470 except socket.gaierror:
ConfigFlowResult async_step_site(self, dict[str, Any]|None user_input=None)
ConfigFlowResult async_step_ssdp(self, ssdp.SsdpServiceInfo discovery_info)
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
ConfigFlowResult async_step_reauth(self, Mapping[str, Any] entry_data)
UnifiOptionsFlowHandler async_get_options_flow(UnifiConfigEntry config_entry)
ConfigFlowResult async_step_device_tracker(self, dict[str, Any]|None user_input=None)
None __init__(self, UnifiConfigEntry config_entry)
ConfigFlowResult async_step_simple_options(self, dict[str, Any]|None user_input=None)
ConfigFlowResult async_step_client_control(self, dict[str, Any]|None user_input=None)
ConfigFlowResult _update_options(self)
ConfigFlowResult async_step_configure_entity_sources(self, dict[str, Any]|None user_input=None)
ConfigFlowResult async_step_statistics_sensors(self, dict[str, Any]|None user_input=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")
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_step_user(self, dict[str, Any]|None user_input=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)
ConfigEntry config_entry(self)
None config_entry(self, ConfigEntry value)
bool show_advanced_options(self)
_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)
IssData update(pyiss.ISS iss)
str|None _async_discover_unifi(HomeAssistant hass)
aiounifi.Controller get_unifi_api(HomeAssistant hass, MappingProxyType[str, Any] config)