* Config flow for elkm1 * As entity ids can now be changed, the “alarm_control_panel” attribute “changed_by_entity_id” is now “changed_by_keypad” and will show the name of the Elk keypad instead of the entity id. * An auto configure mode has been introduced which avoids the need to setup the complex include and exclude filters. This functionality still exists when configuring from yaml for power users who want more control over which entities elkm1 generates. * restore _has_all_unique_prefixes * preserve legacy behavior of creating alarm_control_panels that have no linked keypads when auto_configure is False * unroll loop
164 lines
5 KiB
Python
164 lines
5 KiB
Python
"""Config flow for Elk-M1 Control integration."""
|
|
import logging
|
|
from urllib.parse import urlparse
|
|
|
|
import elkm1_lib as elkm1
|
|
import voluptuous as vol
|
|
|
|
from homeassistant import config_entries, exceptions
|
|
from homeassistant.const import (
|
|
CONF_ADDRESS,
|
|
CONF_HOST,
|
|
CONF_PASSWORD,
|
|
CONF_PROTOCOL,
|
|
CONF_TEMPERATURE_UNIT,
|
|
CONF_USERNAME,
|
|
)
|
|
from homeassistant.util import slugify
|
|
|
|
from . import async_wait_for_elk_to_sync
|
|
from .const import CONF_AUTO_CONFIGURE, CONF_PREFIX
|
|
from .const import DOMAIN # pylint:disable=unused-import
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
PROTOCOL_MAP = {"secure": "elks://", "non-secure": "elk://", "serial": "serial://"}
|
|
|
|
DATA_SCHEMA = vol.Schema(
|
|
{
|
|
vol.Required(CONF_PROTOCOL, default="secure"): vol.In(
|
|
["secure", "non-secure", "serial"]
|
|
),
|
|
vol.Required(CONF_ADDRESS): str,
|
|
vol.Optional(CONF_USERNAME, default=""): str,
|
|
vol.Optional(CONF_PASSWORD, default=""): str,
|
|
vol.Optional(CONF_PREFIX, default=""): str,
|
|
vol.Optional(CONF_TEMPERATURE_UNIT, default="F"): vol.In(["F", "C"]),
|
|
}
|
|
)
|
|
|
|
VALIDATE_TIMEOUT = 35
|
|
|
|
|
|
async def validate_input(data):
|
|
"""Validate the user input allows us to connect.
|
|
|
|
Data has the keys from DATA_SCHEMA with values provided by the user.
|
|
"""
|
|
|
|
userid = data.get(CONF_USERNAME)
|
|
password = data.get(CONF_PASSWORD)
|
|
|
|
prefix = data[CONF_PREFIX]
|
|
url = _make_url_from_data(data)
|
|
requires_password = url.startswith("elks://")
|
|
|
|
if requires_password and (not userid or not password):
|
|
raise InvalidAuth
|
|
|
|
elk = elkm1.Elk(
|
|
{"url": url, "userid": userid, "password": password, "element_list": ["panel"]}
|
|
)
|
|
elk.connect()
|
|
|
|
timed_out = False
|
|
if not await async_wait_for_elk_to_sync(elk, VALIDATE_TIMEOUT):
|
|
_LOGGER.error(
|
|
"Timed out after %d seconds while trying to sync with elkm1",
|
|
VALIDATE_TIMEOUT,
|
|
)
|
|
timed_out = True
|
|
|
|
elk.disconnect()
|
|
|
|
if timed_out:
|
|
raise CannotConnect
|
|
if elk.invalid_auth:
|
|
raise InvalidAuth
|
|
|
|
device_name = data[CONF_PREFIX] if data[CONF_PREFIX] else "ElkM1"
|
|
# Return info that you want to store in the config entry.
|
|
return {"title": device_name, CONF_HOST: url, CONF_PREFIX: slugify(prefix)}
|
|
|
|
|
|
def _make_url_from_data(data):
|
|
host = data.get(CONF_HOST)
|
|
if host:
|
|
return host
|
|
|
|
protocol = PROTOCOL_MAP[data[CONF_PROTOCOL]]
|
|
address = data[CONF_ADDRESS]
|
|
return f"{protocol}{address}"
|
|
|
|
|
|
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|
"""Handle a config flow for Elk-M1 Control."""
|
|
|
|
VERSION = 1
|
|
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH
|
|
|
|
def __init__(self):
|
|
"""Initialize the elkm1 config flow."""
|
|
self.importing = False
|
|
|
|
async def async_step_user(self, user_input=None):
|
|
"""Handle the initial step."""
|
|
errors = {}
|
|
if user_input is not None:
|
|
if self._url_already_configured(_make_url_from_data(user_input)):
|
|
return self.async_abort(reason="address_already_configured")
|
|
|
|
try:
|
|
info = await validate_input(user_input)
|
|
|
|
except CannotConnect:
|
|
errors["base"] = "cannot_connect"
|
|
except InvalidAuth:
|
|
errors["base"] = "invalid_auth"
|
|
except Exception: # pylint: disable=broad-except
|
|
_LOGGER.exception("Unexpected exception")
|
|
errors["base"] = "unknown"
|
|
|
|
if "base" not in errors:
|
|
await self.async_set_unique_id(user_input[CONF_PREFIX])
|
|
self._abort_if_unique_id_configured()
|
|
|
|
if self.importing:
|
|
return self.async_create_entry(title=info["title"], data=user_input)
|
|
|
|
return self.async_create_entry(
|
|
title=info["title"],
|
|
data={
|
|
CONF_HOST: info[CONF_HOST],
|
|
CONF_USERNAME: user_input[CONF_USERNAME],
|
|
CONF_PASSWORD: user_input[CONF_PASSWORD],
|
|
CONF_AUTO_CONFIGURE: True,
|
|
CONF_TEMPERATURE_UNIT: user_input[CONF_TEMPERATURE_UNIT],
|
|
CONF_PREFIX: info[CONF_PREFIX],
|
|
},
|
|
)
|
|
|
|
return self.async_show_form(
|
|
step_id="user", data_schema=DATA_SCHEMA, errors=errors
|
|
)
|
|
|
|
async def async_step_import(self, user_input):
|
|
"""Handle import."""
|
|
self.importing = True
|
|
return await self.async_step_user(user_input)
|
|
|
|
def _url_already_configured(self, url):
|
|
"""See if we already have a elkm1 matching user input configured."""
|
|
existing_hosts = {
|
|
urlparse(entry.data[CONF_HOST]).hostname
|
|
for entry in self._async_current_entries()
|
|
}
|
|
return urlparse(url).hostname in existing_hosts
|
|
|
|
|
|
class CannotConnect(exceptions.HomeAssistantError):
|
|
"""Error to indicate we cannot connect."""
|
|
|
|
|
|
class InvalidAuth(exceptions.HomeAssistantError):
|
|
"""Error to indicate there is invalid auth."""
|