core/homeassistant/components/smartthings/config_flow.py

180 lines
6.7 KiB
Python

"""Config flow to configure SmartThings."""
import logging
from aiohttp.client_exceptions import ClientResponseError
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.const import CONF_ACCESS_TOKEN
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import (
CONF_APP_ID, CONF_INSTALLED_APP_ID, CONF_LOCATION_ID, DOMAIN,
VAL_UID_MATCHER)
from .smartapp import (
create_app, find_app, setup_smartapp, setup_smartapp_endpoint, update_app)
_LOGGER = logging.getLogger(__name__)
@config_entries.HANDLERS.register(DOMAIN)
class SmartThingsFlowHandler(config_entries.ConfigFlow):
"""
Handle configuration of SmartThings integrations.
Any number of integrations are supported. The high level flow follows:
1) Flow initiated
a) User initiates through the UI
b) Re-configuration of a failed entry setup
2) Enter access token
a) Check not already setup
b) Validate format
c) Setup SmartApp
3) Wait for Installation
a) Check user installed into one or more locations
b) Config entries setup for all installations
"""
VERSION = 1
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_PUSH
def __init__(self):
"""Create a new instance of the flow handler."""
self.access_token = None
self.app_id = None
self.api = None
async def async_step_import(self, user_input=None):
"""Occurs when a previously entry setup fails and is re-initiated."""
return await self.async_step_user(user_input)
async def async_step_user(self, user_input=None):
"""Get access token and validate it."""
from pysmartthings import SmartThings
errors = {}
if not self.hass.config.api.base_url.lower().startswith('https://'):
errors['base'] = "base_url_not_https"
return self._show_step_user(errors)
if user_input is None or CONF_ACCESS_TOKEN not in user_input:
return self._show_step_user(errors)
self.access_token = user_input.get(CONF_ACCESS_TOKEN, '')
self.api = SmartThings(async_get_clientsession(self.hass),
self.access_token)
# Ensure token is a UUID
if not VAL_UID_MATCHER.match(self.access_token):
errors[CONF_ACCESS_TOKEN] = "token_invalid_format"
return self._show_step_user(errors)
# Check not already setup in another entry
if any(entry.data.get(CONF_ACCESS_TOKEN) == self.access_token
for entry
in self.hass.config_entries.async_entries(DOMAIN)):
errors[CONF_ACCESS_TOKEN] = "token_already_setup"
return self._show_step_user(errors)
# Setup end-point
await setup_smartapp_endpoint(self.hass)
try:
app = await find_app(self.hass, self.api)
if app:
await app.refresh() # load all attributes
await update_app(self.hass, app)
else:
app = await create_app(self.hass, self.api)
setup_smartapp(self.hass, app)
self.app_id = app.app_id
except ClientResponseError as ex:
if ex.status == 401:
errors[CONF_ACCESS_TOKEN] = "token_unauthorized"
elif ex.status == 403:
errors[CONF_ACCESS_TOKEN] = "token_forbidden"
else:
errors['base'] = "app_setup_error"
return self._show_step_user(errors)
except Exception: # pylint:disable=broad-except
errors['base'] = "app_setup_error"
_LOGGER.exception("Unexpected error setting up the SmartApp")
return self._show_step_user(errors)
return await self.async_step_wait_install()
async def async_step_wait_install(self, user_input=None):
"""Wait for SmartApp installation."""
from pysmartthings import InstalledAppStatus
errors = {}
if user_input is None:
return self._show_step_wait_install(errors)
# Find installed apps that were authorized
installed_apps = [app for app in await self.api.installed_apps(
installed_app_status=InstalledAppStatus.AUTHORIZED)
if app.app_id == self.app_id]
if not installed_apps:
errors['base'] = 'app_not_installed'
return self._show_step_wait_install(errors)
# User may have installed the SmartApp in more than one SmartThings
# location. Config flows are created for the additional installations
for installed_app in installed_apps[1:]:
self.hass.async_create_task(
self.hass.config_entries.flow.async_init(
DOMAIN, context={'source': 'install'},
data={
CONF_APP_ID: installed_app.app_id,
CONF_INSTALLED_APP_ID: installed_app.installed_app_id,
CONF_LOCATION_ID: installed_app.location_id,
CONF_ACCESS_TOKEN: self.access_token
}))
# return entity for the first one.
installed_app = installed_apps[0]
return await self.async_step_install({
CONF_APP_ID: installed_app.app_id,
CONF_INSTALLED_APP_ID: installed_app.installed_app_id,
CONF_LOCATION_ID: installed_app.location_id,
CONF_ACCESS_TOKEN: self.access_token
})
def _show_step_user(self, errors):
return self.async_show_form(
step_id='user',
data_schema=vol.Schema({
vol.Required(CONF_ACCESS_TOKEN,
default=self.access_token): str
}),
errors=errors,
description_placeholders={
'token_url': 'https://account.smartthings.com/tokens',
'component_url':
'https://www.home-assistant.io/components/smartthings/'
}
)
def _show_step_wait_install(self, errors):
return self.async_show_form(
step_id='wait_install',
errors=errors
)
async def async_step_install(self, data=None):
"""
Create a config entry at completion of a flow.
Launched when the user completes the flow or when the SmartApp
is installed into an additional location.
"""
from pysmartthings import SmartThings
if not self.api:
# Launched from the SmartApp install event handler
self.api = SmartThings(
async_get_clientsession(self.hass), data[CONF_ACCESS_TOKEN])
location = await self.api.location(data[CONF_LOCATION_ID])
return self.async_create_entry(title=location.name, data=data)