hass-core/homeassistant/components/elkm1/config_flow.py
J. Nick Koston 18a4829314
Config flow for elkm1 (#33297)
* 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
2020-03-27 15:38:35 -05:00

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."""