Compare commits

...
Sign in to create a new pull request.

1 commit

Author SHA1 Message Date
Paulus Schoutsen
5efbe119f9 WIP 2018-06-10 13:09:15 -04:00
5 changed files with 166 additions and 11 deletions

View file

@ -106,6 +106,34 @@ def async_setup(hass, config):
cloud = hass.data[DOMAIN] = Cloud(hass, **kwargs)
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, cloud.async_start)
yield from http_api.async_setup(hass)
# TEMP, see iot.py for real TODO
async def async_generate_url(flow_id):
# TODO extract this into helper
websession = hass.helpers.aiohttp_client.async_get_clientsession()
with async_timeout.timeout(10, loop=hass.loop):
await hass.async_add_job(auth_api.check_token, cloud)
with async_timeout.timeout(10, loop=hass.loop):
req = await websession.post(
'https://wijyl1dxe5.execute-api.us-east-1.amazonaws.com'
'/prod/{service}/generate_authorize_url',
headers={
'authorization': cloud.id_token
},
data={
'config_flow_id': flow_id,
}
)
data = await req.json()
return data['url']
hass.components.nest.async_register_flow_handler('Home Assistant Cloud', async_generate_url, False)
return True

View file

@ -5,6 +5,7 @@ import pprint
from aiohttp import hdrs, client_exceptions, WSMsgType
from homeassistant import data_entry_flow
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.components.alexa import smart_home as alexa
from homeassistant.components.google_assistant import smart_home as ga
@ -48,6 +49,9 @@ class CloudIoT:
if self.state != STATE_DISCONNECTED:
raise RuntimeError('Connect called while not disconnected')
# TODO add easy way to listen to cloud connect/disconnect events
# That's how we'll subscribe to auth
hass = self.cloud.hass
self.close_requested = False
self.state = STATE_CONNECTING
@ -255,3 +259,20 @@ def async_handle_cloud(hass, cloud, payload):
_LOGGER.warning("Received unknown cloud action: %s", action)
return None
@HANDLERS.register('account_link')
async def async_handle_account_link(hass, cloud, payload):
"""Handle an incoming IoT message for linking accounts."""
try:
await hass.config_entries.flow.async_configure(
payload['config_flow_id'], payload['tokens'])
except data_entry_flow.UnknownFlow:
return {
'success': False,
'reason': 'unknown_config_flow',
}
return {
'success': True
}

View file

@ -6,10 +6,14 @@ https://home-assistant.io/components/nest/
"""
from concurrent.futures import ThreadPoolExecutor
import logging
import uuid
import socket
import voluptuous as vol
from homeassistant import config_entries, data_entry_flow
from homeassistant.core import callback
from homeassistant.loader import bind_hass
from homeassistant.const import (
CONF_STRUCTURE, CONF_FILENAME, CONF_BINARY_SENSORS, CONF_SENSORS,
CONF_MONITORED_CONDITIONS,
@ -27,6 +31,7 @@ _LOGGER = logging.getLogger(__name__)
DOMAIN = 'nest'
DATA_NEST = 'nest'
DATA_NEST_FLOW = 'nest_config_flow'
SIGNAL_NEST_UPDATE = 'nest_update'
@ -187,8 +192,9 @@ async def async_setup(hass, config):
"""Set up Nest components."""
from nest import Nest
if 'nest' in _CONFIGURING:
return
# When we are doing config entries.
if DOMAIN not in config:
return True
conf = config[DOMAIN]
client_id = conf[CONF_CLIENT_ID]
@ -325,3 +331,67 @@ class NestSensorDevice(Entity):
async_dispatcher_connect(self.hass, SIGNAL_NEST_UPDATE,
async_update_state)
@bind_hass
def async_register_flow_handler(hass, name, generate_oauth_auth_url, use_pin):
"""Register an auth flow for the config entry flow."""
flows = hass.data.get(DATA_NEST_FLOW)
if flows is None:
flows = hass.data[DATA_NEST_FLOW] = {}
handler_id = uuid.uuid4().hex
data = {
'name': name,
'generate_oauth_auth_url': generate_oauth_auth_url,
'use_pin': use_pin,
}
flows[handler_id] = data
@callback
def async_unregister_handler():
"""Removes the handler."""
flows.pop(handler_id, None)
return async_unregister_handler
@config_entries.HANDLERS.register(DOMAIN)
class NestFlowHandler(data_entry_flow.FlowHandler):
"""Handle a Nest config flow."""
VERSION = 1
def __init__(self):
"""Initialize Nest config flow."""
self._flow_handler = None
async def async_step_init(self, user_input=None):
"""Handle a flow start."""
flows = self.hass.data.get(DATA_NEST_FLOW, [])
if not flows:
return self.async_abort(
reason='no_flow'
)
elif len(flows) == 1:
self._flow_handler = list(flows.keys())[0]
return await self.async_step_flow()
# Handle if we have multiple flows we show a picker.
async def async_step_flow(self, user_input=None):
"""Handle the flow authentication."""
handler = self.hass.data[DATA_NEST_FLOW][self._flow_handler]
if user_input:
return self.async_create_entry(
title='Nest temp',
data=user_input,
)
url = await handler['generate_oauth_auth_url'](self.flow_id)
return self.async_external_step(
step_id='flow',
url=url,
)

View file

@ -129,6 +129,7 @@ HANDLERS = Registry()
FLOWS = [
'deconz',
'hue',
'nest',
'zone',
]

View file

@ -13,6 +13,10 @@ SOURCE_DISCOVERY = 'discovery'
RESULT_TYPE_FORM = 'form'
RESULT_TYPE_CREATE_ENTRY = 'create_entry'
RESULT_TYPE_ABORT = 'abort'
RESULT_TYPE_EXTERNAL_STEP = 'external'
# Event that is fired when a flow is progressed via external source.
EVENT_DATA_ENTRY_FLOW_PROGRESSED = 'data_entry_flow_progressed'
class FlowError(HomeAssistantError):
@ -73,13 +77,33 @@ class FlowManager:
if flow is None:
raise UnknownFlow
step_id, data_schema = flow.cur_step
cur_step = flow.cur_step
if data_schema is not None and user_input is not None:
user_input = data_schema(user_input)
if cur_step.get('data_schema') is not None and user_input is not None:
user_input = cur_step['data_schema'](user_input)
return await self._async_handle_step(
flow, step_id, user_input)
result = await self._async_handle_step(
flow, cur_step['step_id'], user_input)
# If we just got data from an external step which caused us to make
# progress, fire an event to update the frontend.
if (cur_step['type'] == RESULT_TYPE_EXTERNAL_STEP and
cur_step['step_id'] != result.get('step_id')):
# These results will end the flow, making a refresh impossible.
# So we embed the results.
if result['type'] in (RESULT_TYPE_ABORT, RESULT_TYPE_CREATE_ENTRY):
event_data = result
else:
# Tell frontend to reload the flow state.
event_data = {
'handler': flow.handler,
'flow_id': flow_id,
'refresh': True
}
self.hass.bus.async_fire(EVENT_DATA_ENTRY_FLOW_PROGRESSED,
event_data)
return result
@callback
def async_abort(self, flow_id):
@ -98,13 +122,13 @@ class FlowManager:
result = await getattr(flow, method)(user_input)
if result['type'] not in (RESULT_TYPE_FORM, RESULT_TYPE_CREATE_ENTRY,
RESULT_TYPE_ABORT):
if result['type'] not in (RESULT_TYPE_FORM, RESULT_TYPE_EXTERNAL_STEP,
RESULT_TYPE_CREATE_ENTRY, RESULT_TYPE_ABORT):
raise ValueError(
'Handler returned incorrect type: {}'.format(result['type']))
if result['type'] == RESULT_TYPE_FORM:
flow.cur_step = (result['step_id'], result['data_schema'])
if result['type'] in (RESULT_TYPE_FORM, RESULT_TYPE_EXTERNAL_STEP):
flow.cur_step = result
return result
# Abort and Success results both finish the flow
@ -165,3 +189,14 @@ class FlowHandler:
'handler': self.handler,
'reason': reason
}
@callback
def async_external_step(self, *, step_id, url):
"""Return the definition of an external step for the user to take."""
return {
'type': RESULT_TYPE_EXTERNAL_STEP,
'flow_id': self.flow_id,
'handler': self.handler,
'step_id': step_id,
'url': url,
}