core/homeassistant/components/deconz/config_flow.py

279 lines
9.4 KiB
Python
Raw Normal View History

"""Config flow to configure deCONZ component."""
import asyncio
from urllib.parse import urlparse
import async_timeout
from pydeconz.errors import RequestError, ResponseError
from pydeconz.utils import (
async_discovery,
async_get_api_key,
async_get_bridge_id,
normalize_bridge_id,
)
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.components import ssdp
from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT
from homeassistant.core import callback
from homeassistant.helpers import aiohttp_client
from .const import (
CONF_ALLOW_CLIP_SENSOR,
CONF_ALLOW_DECONZ_GROUPS,
CONF_BRIDGEID,
DEFAULT_ALLOW_CLIP_SENSOR,
DEFAULT_ALLOW_DECONZ_GROUPS,
DEFAULT_PORT,
DOMAIN,
)
2019-07-31 19:25:30 +00:00
DECONZ_MANUFACTURERURL = "http://www.dresden-elektronik.de"
CONF_SERIAL = "serial"
@callback
def get_master_gateway(hass):
"""Return the gateway which is marked as master."""
for gateway in hass.data[DOMAIN].values():
if gateway.master:
return gateway
class DeconzFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a deCONZ config flow."""
VERSION = 1
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH
_hassio_discovery = None
@staticmethod
@callback
def async_get_options_flow(config_entry):
"""Get the options flow for this handler."""
return DeconzOptionsFlowHandler(config_entry)
def __init__(self):
"""Initialize the deCONZ config flow."""
self.bridge_id = None
self.bridges = []
self.deconz_config = {}
async def async_step_init(self, user_input=None):
"""Needed in order to not require re-translation of strings."""
return await self.async_step_user(user_input)
async def async_step_user(self, user_input=None):
"""Handle a deCONZ config flow start.
If only one bridge is found go to link step.
If more than one bridge is found let user choose bridge to link.
If no bridge is found allow user to manually input configuration.
"""
if user_input is not None:
for bridge in self.bridges:
if bridge[CONF_HOST] == user_input[CONF_HOST]:
self.bridge_id = bridge[CONF_BRIDGEID]
self.deconz_config = {
CONF_HOST: bridge[CONF_HOST],
CONF_PORT: bridge[CONF_PORT],
}
return await self.async_step_link()
self.deconz_config = user_input
return await self.async_step_link()
session = aiohttp_client.async_get_clientsession(self.hass)
try:
with async_timeout.timeout(10):
self.bridges = await async_discovery(session)
except (asyncio.TimeoutError, ResponseError):
self.bridges = []
if len(self.bridges) == 1:
return await self.async_step_user(self.bridges[0])
if len(self.bridges) > 1:
hosts = []
for bridge in self.bridges:
hosts.append(bridge[CONF_HOST])
return self.async_show_form(
2019-07-31 19:25:30 +00:00
step_id="init",
data_schema=vol.Schema({vol.Required(CONF_HOST): vol.In(hosts)}),
)
return self.async_show_form(
2019-07-31 19:25:30 +00:00
step_id="init",
data_schema=vol.Schema(
{
vol.Required(CONF_HOST): str,
vol.Required(CONF_PORT, default=DEFAULT_PORT): int,
}
),
)
async def async_step_link(self, user_input=None):
"""Attempt to link with the deCONZ bridge."""
errors = {}
if user_input is not None:
session = aiohttp_client.async_get_clientsession(self.hass)
try:
with async_timeout.timeout(10):
2019-07-31 19:25:30 +00:00
api_key = await async_get_api_key(session, **self.deconz_config)
except (ResponseError, RequestError, asyncio.TimeoutError):
2019-07-31 19:25:30 +00:00
errors["base"] = "no_key"
else:
self.deconz_config[CONF_API_KEY] = api_key
return await self._create_entry()
2019-07-31 19:25:30 +00:00
return self.async_show_form(step_id="link", errors=errors)
async def _create_entry(self):
"""Create entry for gateway."""
if not self.bridge_id:
session = aiohttp_client.async_get_clientsession(self.hass)
try:
with async_timeout.timeout(10):
self.bridge_id = await async_get_bridge_id(
2019-07-31 19:25:30 +00:00
session, **self.deconz_config
)
await self.async_set_unique_id(self.bridge_id)
self._abort_if_unique_id_configured()
except asyncio.TimeoutError:
2019-07-31 19:25:30 +00:00
return self.async_abort(reason="no_bridges")
return self.async_create_entry(title=self.bridge_id, data=self.deconz_config)
def _update_entry(self, entry, host, port, api_key=None):
"""Update existing entry."""
if (
entry.data[CONF_HOST] == host
and entry.data[CONF_PORT] == port
and (api_key is None or entry.data[CONF_API_KEY] == api_key)
):
return self.async_abort(reason="already_configured")
entry.data[CONF_HOST] = host
entry.data[CONF_PORT] = port
if api_key is not None:
entry.data[CONF_API_KEY] = api_key
self.hass.config_entries.async_update_entry(entry)
return self.async_abort(reason="updated_instance")
async def async_step_ssdp(self, discovery_info):
"""Handle a discovered deCONZ bridge."""
if discovery_info[ssdp.ATTR_UPNP_MANUFACTURER_URL] != DECONZ_MANUFACTURERURL:
2019-07-31 19:25:30 +00:00
return self.async_abort(reason="not_deconz_bridge")
self.bridge_id = normalize_bridge_id(discovery_info[ssdp.ATTR_UPNP_SERIAL])
parsed_url = urlparse(discovery_info[ssdp.ATTR_SSDP_LOCATION])
for entry in self.hass.config_entries.async_entries(DOMAIN):
if self.bridge_id == entry.unique_id:
if entry.source == "hassio":
return self.async_abort(reason="already_configured")
return self._update_entry(entry, parsed_url.hostname, parsed_url.port)
await self.async_set_unique_id(self.bridge_id)
# pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167
self.context["title_placeholders"] = {"host": parsed_url.hostname}
self.deconz_config = {
CONF_HOST: parsed_url.hostname,
CONF_PORT: parsed_url.port,
}
return await self.async_step_link()
async def async_step_hassio(self, user_input=None):
"""Prepare configuration for a Hass.io deCONZ bridge.
This flow is triggered by the discovery component.
"""
self.bridge_id = normalize_bridge_id(user_input[CONF_SERIAL])
gateway = self.hass.data.get(DOMAIN, {}).get(self.bridge_id)
if gateway:
return self._update_entry(
gateway.config_entry,
user_input[CONF_HOST],
user_input[CONF_PORT],
user_input[CONF_API_KEY],
)
await self.async_set_unique_id(self.bridge_id)
self._hassio_discovery = user_input
return await self.async_step_hassio_confirm()
async def async_step_hassio_confirm(self, user_input=None):
"""Confirm a Hass.io discovery."""
if user_input is not None:
self.deconz_config = {
CONF_HOST: self._hassio_discovery[CONF_HOST],
CONF_PORT: self._hassio_discovery[CONF_PORT],
2019-07-31 19:25:30 +00:00
CONF_API_KEY: self._hassio_discovery[CONF_API_KEY],
}
return await self._create_entry()
return self.async_show_form(
2019-07-31 19:25:30 +00:00
step_id="hassio_confirm",
description_placeholders={"addon": self._hassio_discovery["addon"]},
)
class DeconzOptionsFlowHandler(config_entries.OptionsFlow):
"""Handle deCONZ options."""
def __init__(self, config_entry):
"""Initialize deCONZ options flow."""
self.config_entry = config_entry
self.options = dict(config_entry.options)
async def async_step_init(self, user_input=None):
"""Manage the deCONZ options."""
return await self.async_step_deconz_devices()
async def async_step_deconz_devices(self, user_input=None):
"""Manage the deconz devices options."""
if user_input is not None:
self.options[CONF_ALLOW_CLIP_SENSOR] = user_input[CONF_ALLOW_CLIP_SENSOR]
self.options[CONF_ALLOW_DECONZ_GROUPS] = user_input[
CONF_ALLOW_DECONZ_GROUPS
]
return self.async_create_entry(title="", data=self.options)
return self.async_show_form(
step_id="deconz_devices",
data_schema=vol.Schema(
{
vol.Optional(
CONF_ALLOW_CLIP_SENSOR,
default=self.config_entry.options.get(
CONF_ALLOW_CLIP_SENSOR, DEFAULT_ALLOW_CLIP_SENSOR
),
): bool,
vol.Optional(
CONF_ALLOW_DECONZ_GROUPS,
default=self.config_entry.options.get(
CONF_ALLOW_DECONZ_GROUPS, DEFAULT_ALLOW_DECONZ_GROUPS
),
): bool,
}
),
)