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)
|
cloud = hass.data[DOMAIN] = Cloud(hass, **kwargs)
|
||||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, cloud.async_start)
|
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, cloud.async_start)
|
||||||
yield from http_api.async_setup(hass)
|
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
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import pprint
|
||||||
|
|
||||||
from aiohttp import hdrs, client_exceptions, WSMsgType
|
from aiohttp import hdrs, client_exceptions, WSMsgType
|
||||||
|
|
||||||
|
from homeassistant import data_entry_flow
|
||||||
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
||||||
from homeassistant.components.alexa import smart_home as alexa
|
from homeassistant.components.alexa import smart_home as alexa
|
||||||
from homeassistant.components.google_assistant import smart_home as ga
|
from homeassistant.components.google_assistant import smart_home as ga
|
||||||
|
@ -48,6 +49,9 @@ class CloudIoT:
|
||||||
if self.state != STATE_DISCONNECTED:
|
if self.state != STATE_DISCONNECTED:
|
||||||
raise RuntimeError('Connect called while not 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
|
hass = self.cloud.hass
|
||||||
self.close_requested = False
|
self.close_requested = False
|
||||||
self.state = STATE_CONNECTING
|
self.state = STATE_CONNECTING
|
||||||
|
@ -255,3 +259,20 @@ def async_handle_cloud(hass, cloud, payload):
|
||||||
_LOGGER.warning("Received unknown cloud action: %s", action)
|
_LOGGER.warning("Received unknown cloud action: %s", action)
|
||||||
|
|
||||||
return None
|
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
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
import logging
|
import logging
|
||||||
|
import uuid
|
||||||
import socket
|
import socket
|
||||||
|
|
||||||
import voluptuous as vol
|
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 (
|
from homeassistant.const import (
|
||||||
CONF_STRUCTURE, CONF_FILENAME, CONF_BINARY_SENSORS, CONF_SENSORS,
|
CONF_STRUCTURE, CONF_FILENAME, CONF_BINARY_SENSORS, CONF_SENSORS,
|
||||||
CONF_MONITORED_CONDITIONS,
|
CONF_MONITORED_CONDITIONS,
|
||||||
|
@ -27,6 +31,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||||
DOMAIN = 'nest'
|
DOMAIN = 'nest'
|
||||||
|
|
||||||
DATA_NEST = 'nest'
|
DATA_NEST = 'nest'
|
||||||
|
DATA_NEST_FLOW = 'nest_config_flow'
|
||||||
|
|
||||||
SIGNAL_NEST_UPDATE = 'nest_update'
|
SIGNAL_NEST_UPDATE = 'nest_update'
|
||||||
|
|
||||||
|
@ -187,8 +192,9 @@ async def async_setup(hass, config):
|
||||||
"""Set up Nest components."""
|
"""Set up Nest components."""
|
||||||
from nest import Nest
|
from nest import Nest
|
||||||
|
|
||||||
if 'nest' in _CONFIGURING:
|
# When we are doing config entries.
|
||||||
return
|
if DOMAIN not in config:
|
||||||
|
return True
|
||||||
|
|
||||||
conf = config[DOMAIN]
|
conf = config[DOMAIN]
|
||||||
client_id = conf[CONF_CLIENT_ID]
|
client_id = conf[CONF_CLIENT_ID]
|
||||||
|
@ -325,3 +331,67 @@ class NestSensorDevice(Entity):
|
||||||
|
|
||||||
async_dispatcher_connect(self.hass, SIGNAL_NEST_UPDATE,
|
async_dispatcher_connect(self.hass, SIGNAL_NEST_UPDATE,
|
||||||
async_update_state)
|
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 = [
|
FLOWS = [
|
||||||
'deconz',
|
'deconz',
|
||||||
'hue',
|
'hue',
|
||||||
|
'nest',
|
||||||
'zone',
|
'zone',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,10 @@ SOURCE_DISCOVERY = 'discovery'
|
||||||
RESULT_TYPE_FORM = 'form'
|
RESULT_TYPE_FORM = 'form'
|
||||||
RESULT_TYPE_CREATE_ENTRY = 'create_entry'
|
RESULT_TYPE_CREATE_ENTRY = 'create_entry'
|
||||||
RESULT_TYPE_ABORT = 'abort'
|
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):
|
class FlowError(HomeAssistantError):
|
||||||
|
@ -73,13 +77,33 @@ class FlowManager:
|
||||||
if flow is None:
|
if flow is None:
|
||||||
raise UnknownFlow
|
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:
|
if cur_step.get('data_schema') is not None and user_input is not None:
|
||||||
user_input = data_schema(user_input)
|
user_input = cur_step['data_schema'](user_input)
|
||||||
|
|
||||||
return await self._async_handle_step(
|
result = await self._async_handle_step(
|
||||||
flow, step_id, user_input)
|
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
|
@callback
|
||||||
def async_abort(self, flow_id):
|
def async_abort(self, flow_id):
|
||||||
|
@ -98,13 +122,13 @@ class FlowManager:
|
||||||
|
|
||||||
result = await getattr(flow, method)(user_input)
|
result = await getattr(flow, method)(user_input)
|
||||||
|
|
||||||
if result['type'] not in (RESULT_TYPE_FORM, RESULT_TYPE_CREATE_ENTRY,
|
if result['type'] not in (RESULT_TYPE_FORM, RESULT_TYPE_EXTERNAL_STEP,
|
||||||
RESULT_TYPE_ABORT):
|
RESULT_TYPE_CREATE_ENTRY, RESULT_TYPE_ABORT):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
'Handler returned incorrect type: {}'.format(result['type']))
|
'Handler returned incorrect type: {}'.format(result['type']))
|
||||||
|
|
||||||
if result['type'] == RESULT_TYPE_FORM:
|
if result['type'] in (RESULT_TYPE_FORM, RESULT_TYPE_EXTERNAL_STEP):
|
||||||
flow.cur_step = (result['step_id'], result['data_schema'])
|
flow.cur_step = result
|
||||||
return result
|
return result
|
||||||
|
|
||||||
# Abort and Success results both finish the flow
|
# Abort and Success results both finish the flow
|
||||||
|
@ -165,3 +189,14 @@ class FlowHandler:
|
||||||
'handler': self.handler,
|
'handler': self.handler,
|
||||||
'reason': reason
|
'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