Compare commits
1 commit
dev
...
nest-accou
Author | SHA1 | Date | |
---|---|---|---|
![]() |
5efbe119f9 |
5 changed files with 166 additions and 11 deletions
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -129,6 +129,7 @@ HANDLERS = Registry()
|
|||
FLOWS = [
|
||||
'deconz',
|
||||
'hue',
|
||||
'nest',
|
||||
'zone',
|
||||
]
|
||||
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue