1 """Config flow for Elk-M1 Control integration."""
3 from __future__
import annotations
6 from typing
import Any, Self
8 from elkm1_lib.discovery
import ElkSystem
9 from elkm1_lib.elk
import Elk
10 import voluptuous
as vol
28 from .
import async_wait_for_elk_to_sync, hostname_from_url
29 from .const
import CONF_AUTO_CONFIGURE, DISCOVER_SCAN_TIMEOUT, DOMAIN, LOGIN_TIMEOUT
30 from .discovery
import (
32 async_discover_device,
33 async_discover_devices,
34 async_update_entry_from_discovery,
37 CONF_DEVICE =
"device"
39 NON_SECURE_PORT = 2101
41 STANDARD_PORTS = {NON_SECURE_PORT, SECURE_PORT}
43 _LOGGER = logging.getLogger(__name__)
47 "TLS 1.2":
"elksv1_2://",
48 "non-secure":
"elk://",
49 "serial":
"serial://",
55 BASE_SCHEMA: VolDictType = {
56 vol.Optional(CONF_USERNAME, default=
""): str,
57 vol.Optional(CONF_PASSWORD, default=
""): str,
60 SECURE_PROTOCOLS = [
"secure",
"TLS 1.2"]
61 ALL_PROTOCOLS = [*SECURE_PROTOCOLS,
"non-secure",
"serial"]
62 DEFAULT_SECURE_PROTOCOL =
"secure"
63 DEFAULT_NON_SECURE_PROTOCOL =
"non-secure"
66 NON_SECURE_PORT: DEFAULT_NON_SECURE_PROTOCOL,
67 SECURE_PORT: DEFAULT_SECURE_PROTOCOL,
71 async
def validate_input(data: dict[str, str], mac: str |
None) -> dict[str, str]:
72 """Validate the user input allows us to connect.
74 Data has the keys from DATA_SCHEMA with values provided by the user.
76 userid = data.get(CONF_USERNAME)
77 password = data.get(CONF_PASSWORD)
79 prefix = data[CONF_PREFIX]
81 requires_password = url.startswith((
"elks://",
"elksv1_2"))
83 if requires_password
and (
not userid
or not password):
87 {
"url": url,
"userid": userid,
"password": password,
"element_list": [
"panel"]}
98 if prefix
and prefix != short_mac:
101 device_name = f
"ElkM1 {short_mac}"
103 device_name =
"ElkM1"
104 return {
"title": device_name, CONF_HOST: url, CONF_PREFIX:
slugify(prefix)}
108 """Append the port only if its non-standard."""
109 if device.port
in STANDARD_PORTS:
110 return device.ip_address
111 return f
"{device.ip_address}:{device.port}"
115 if host := data.get(CONF_HOST):
118 protocol = PROTOCOL_MAP[data[CONF_PROTOCOL]]
119 address = data[CONF_ADDRESS]
120 return f
"{protocol}{address}"
125 "mac_address":
_short_mac(device.mac_address),
131 """Handle a config flow for Elk-M1 Control."""
135 host: str |
None =
None
138 """Initialize the elkm1 config flow."""
143 self, discovery_info: dhcp.DhcpServiceInfo
144 ) -> ConfigFlowResult:
145 """Handle discovery via dhcp."""
147 discovery_info.macaddress, discovery_info.ip, 0
149 _LOGGER.debug(
"Elk discovered from dhcp: %s", self.
_discovered_device_discovered_device)
153 self, discovery_info: DiscoveryInfoType
154 ) -> ConfigFlowResult:
155 """Handle integration discovery."""
157 discovery_info[
"mac_address"],
158 discovery_info[
"ip_address"],
159 discovery_info[
"port"],
162 "Elk discovered from integration discovery: %s", self.
_discovered_device_discovered_device
167 """Handle any discovery."""
169 assert device
is not None
170 mac = dr.format_mac(device.mac_address)
171 host = device.ip_address
175 entry.unique_id == mac
179 self.hass.config_entries.async_schedule_reload(entry.entry_id)
182 if self.hass.config_entries.flow.async_has_matching_flow(self):
195 """Return True if other_flow is matching this flow."""
196 return other_flow.host == self.
hosthost
199 self, user_input: dict[str, Any] |
None =
None
200 ) -> ConfigFlowResult:
201 """Confirm discovery."""
209 self, user_input: dict[str, Any] |
None =
None
210 ) -> ConfigFlowResult:
211 """Handle the initial step."""
212 if user_input
is not None:
213 if mac := user_input[CONF_DEVICE]:
225 self.hass, DISCOVER_SCAN_TIMEOUT
228 dr.format_mac(device.mac_address): device
for device
in discovered_devices
230 devices_name: dict[str |
None, str] = {
231 mac: f
"{_short_mac(device.mac_address)} ({device.ip_address})"
233 if mac
not in current_unique_ids
and device.ip_address
not in current_hosts
237 devices_name[
None] =
"Manual Entry"
240 data_schema=vol.Schema({vol.Required(CONF_DEVICE): vol.In(devices_name)}),
244 self, user_input: dict[str, Any], importing: bool
245 ) -> tuple[dict[str, str] |
None, ConfigFlowResult |
None]:
246 """Try to connect and create the entry or error."""
253 return {
"base":
"cannot_connect"},
None
255 return {CONF_PASSWORD:
"invalid_auth"},
None
257 _LOGGER.exception(
"Unexpected exception")
258 return {
"base":
"unknown"},
None
266 CONF_HOST: info[CONF_HOST],
267 CONF_USERNAME: user_input[CONF_USERNAME],
268 CONF_PASSWORD: user_input[CONF_PASSWORD],
269 CONF_AUTO_CONFIGURE:
True,
270 CONF_PREFIX: info[CONF_PREFIX],
275 self, user_input: dict[str, Any] |
None =
None
276 ) -> ConfigFlowResult:
277 """Handle connecting the device when we have a discovery."""
278 errors: dict[str, str] |
None = {}
280 assert device
is not None
281 if user_input
is not None:
284 user_input[CONF_PREFIX] =
_short_mac(device.mac_address)
286 user_input[CONF_PREFIX] =
""
288 if result
is not None:
291 default_proto = PORT_PROTOCOL_MAP.get(device.port, DEFAULT_SECURE_PROTOCOL)
293 step_id=
"discovered_connection",
294 data_schema=vol.Schema(
297 vol.Required(CONF_PROTOCOL, default=default_proto): vol.In(
307 self, user_input: dict[str, Any] |
None =
None
308 ) -> ConfigFlowResult:
309 """Handle connecting the device when we need manual entry."""
310 errors: dict[str, str] |
None = {}
311 if user_input
is not None:
315 self.hass, user_input[CONF_ADDRESS]
318 dr.format_mac(device.mac_address), raise_on_progress=
False
323 user_input[CONF_ADDRESS] = device.ip_address
325 if result
is not None:
329 step_id=
"manual_connection",
330 data_schema=vol.Schema(
333 vol.Required(CONF_ADDRESS): str,
334 vol.Optional(CONF_PREFIX, default=
""): str,
336 CONF_PROTOCOL, default=DEFAULT_SECURE_PROTOCOL
337 ): vol.In(ALL_PROTOCOLS),
345 _LOGGER.debug(
"Elk is importing from yaml")
353 "Importing is trying to fill unique id from discovery for %s", host
361 dr.format_mac(device.mac_address), raise_on_progress=
False
368 assert result
is not None
372 """See if we already have a elkm1 matching user input configured."""
381 """Error to indicate there is invalid auth."""
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
ConfigFlowResult async_step_integration_discovery(self, DiscoveryInfoType discovery_info)
ConfigFlowResult _async_handle_discovery(self)
bool _url_already_configured(self, str url)
ConfigFlowResult async_step_dhcp(self, dhcp.DhcpServiceInfo discovery_info)
ConfigFlowResult async_step_manual_connection(self, dict[str, Any]|None user_input=None)
ConfigFlowResult async_step_discovered_connection(self, dict[str, Any]|None user_input=None)
ConfigFlowResult async_step_discovery_confirm(self, dict[str, Any]|None user_input=None)
ConfigFlowResult async_step_import(self, dict[str, Any] import_data)
tuple[dict[str, str]|None, ConfigFlowResult|None] _async_create_or_error(self, dict[str, Any] user_input, bool importing)
bool is_matching(self, Self other_flow)
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)
list[ConfigEntry] _async_current_entries(self, bool|None include_ignore=None)
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)
_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)
str _make_url_from_data(dict[str, str] data)
dict[str, str] _placeholders_from_device(ElkSystem device)
str _address_from_discovery(ElkSystem device)
dict[str, str] validate_input(dict[str, str] data, str|None mac)
ElkSystem|None async_discover_device(HomeAssistant hass, str host)
str _short_mac(str mac_address)
list[ElkSystem] async_discover_devices(HomeAssistant hass, int timeout, str|None address=None)
bool async_update_entry_from_discovery(HomeAssistant hass, config_entries.ConfigEntry entry, ElkSystem device)
bool async_wait_for_elk_to_sync(Elk elk, int login_timeout, int sync_timeout)
str hostname_from_url(str url)
bool is_ip_address(str address)