Add support for Automatic OAuth2 authentication (#8962)

* Add support for Automatic OAuth2 authentication

* Fix async conversion of configurator

* Rename method for async

* Use hass.components to get configurator component

* Fix typo

* Move session data to hidden directory

* Make configurator callback optional
This commit is contained in:
Adam Mills 2017-08-14 01:37:50 -04:00 committed by Paulus Schoutsen
parent 8fcec03adf
commit 19d1d748d4
25 changed files with 314 additions and 149 deletions

View file

@ -91,7 +91,7 @@ def request_configuration(hass, config, atv, credentials):
hass.async_add_job(configurator.request_done, instance) hass.async_add_job(configurator.request_done, instance)
instance = configurator.request_config( instance = configurator.request_config(
hass, 'Apple TV Authentication', configuration_callback, 'Apple TV Authentication', configuration_callback,
description='Please enter PIN code shown on screen.', description='Please enter PIN code shown on screen.',
submit_caption='Confirm', submit_caption='Confirm',
fields=[{'id': 'pin', 'name': 'PIN Code', 'type': 'password'}] fields=[{'id': 'pin', 'name': 'PIN Code', 'type': 'password'}]

View file

@ -110,7 +110,7 @@ def request_configuration(hass, name, host, serialnumber):
title = '{} ({})'.format(name, host) title = '{} ({})'.format(name, host)
request_id = configurator.request_config( request_id = configurator.request_config(
hass, title, configuration_callback, title, configuration_callback,
description='Functionality: ' + str(AXIS_INCLUDE), description='Functionality: ' + str(AXIS_INCLUDE),
entity_picture="/static/images/logo_axis.png", entity_picture="/static/images/logo_axis.png",
link_name='Axis platform documentation', link_name='Axis platform documentation',

View file

@ -7,19 +7,21 @@ A callback has to be provided to `request_config` which will be called when
the user has submitted configuration information. the user has submitted configuration information.
""" """
import asyncio import asyncio
import functools as ft
import logging import logging
from homeassistant.core import callback as async_callback from homeassistant.core import callback as async_callback
from homeassistant.const import EVENT_TIME_CHANGED, ATTR_FRIENDLY_NAME, \ from homeassistant.const import EVENT_TIME_CHANGED, ATTR_FRIENDLY_NAME, \
ATTR_ENTITY_PICTURE ATTR_ENTITY_PICTURE
from homeassistant.loader import bind_hass from homeassistant.loader import bind_hass
from homeassistant.helpers.entity import generate_entity_id from homeassistant.helpers.entity import async_generate_entity_id
from homeassistant.util.async import run_callback_threadsafe from homeassistant.util.async import run_callback_threadsafe
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
_REQUESTS = {}
_KEY_INSTANCE = 'configurator' _KEY_INSTANCE = 'configurator'
DATA_REQUESTS = 'configurator_requests'
ATTR_CONFIGURE_ID = 'configure_id' ATTR_CONFIGURE_ID = 'configure_id'
ATTR_DESCRIPTION = 'description' ATTR_DESCRIPTION = 'description'
ATTR_DESCRIPTION_IMAGE = 'description_image' ATTR_DESCRIPTION_IMAGE = 'description_image'
@ -39,63 +41,89 @@ STATE_CONFIGURED = 'configured'
@bind_hass @bind_hass
def request_config( @async_callback
hass, name, callback, description=None, description_image=None, def async_request_config(
hass, name, callback=None, description=None, description_image=None,
submit_caption=None, fields=None, link_name=None, link_url=None, submit_caption=None, fields=None, link_name=None, link_url=None,
entity_picture=None): entity_picture=None):
"""Create a new request for configuration. """Create a new request for configuration.
Will return an ID to be used for sequent calls. Will return an ID to be used for sequent calls.
""" """
instance = run_callback_threadsafe(hass.loop, instance = hass.data.get(_KEY_INSTANCE)
_async_get_instance,
hass).result()
request_id = instance.request_config( if instance is None:
instance = hass.data[_KEY_INSTANCE] = Configurator(hass)
request_id = instance.async_request_config(
name, callback, name, callback,
description, description_image, submit_caption, description, description_image, submit_caption,
fields, link_name, link_url, entity_picture) fields, link_name, link_url, entity_picture)
_REQUESTS[request_id] = instance if DATA_REQUESTS not in hass.data:
hass.data[DATA_REQUESTS] = {}
hass.data[DATA_REQUESTS][request_id] = instance
return request_id return request_id
def notify_errors(request_id, error): @bind_hass
def request_config(hass, *args, **kwargs):
"""Create a new request for configuration.
Will return an ID to be used for sequent calls.
"""
return run_callback_threadsafe(
hass.loop, ft.partial(async_request_config, hass, *args, **kwargs)
).result()
@bind_hass
@async_callback
def async_notify_errors(hass, request_id, error):
"""Add errors to a config request.""" """Add errors to a config request."""
try: try:
_REQUESTS[request_id].notify_errors(request_id, error) hass.data[DATA_REQUESTS][request_id].async_notify_errors(
request_id, error)
except KeyError: except KeyError:
# If request_id does not exist # If request_id does not exist
pass pass
def request_done(request_id): @bind_hass
def notify_errors(hass, request_id, error):
"""Add errors to a config request."""
return run_callback_threadsafe(
hass.loop, async_notify_errors, hass, request_id, error
).result()
@bind_hass
@async_callback
def async_request_done(hass, request_id):
"""Mark a configuration request as done.""" """Mark a configuration request as done."""
try: try:
_REQUESTS.pop(request_id).request_done(request_id) hass.data[DATA_REQUESTS].pop(request_id).async_request_done(request_id)
except KeyError: except KeyError:
# If request_id does not exist # If request_id does not exist
pass pass
@bind_hass
def request_done(hass, request_id):
"""Mark a configuration request as done."""
return run_callback_threadsafe(
hass.loop, async_request_done, hass, request_id
).result()
@asyncio.coroutine @asyncio.coroutine
def async_setup(hass, config): def async_setup(hass, config):
"""Set up the configurator component.""" """Set up the configurator component."""
return True return True
@async_callback
def _async_get_instance(hass):
"""Get an instance per hass object."""
instance = hass.data.get(_KEY_INSTANCE)
if instance is None:
instance = hass.data[_KEY_INSTANCE] = Configurator(hass)
return instance
class Configurator(object): class Configurator(object):
"""The class to keep track of current configuration requests.""" """The class to keep track of current configuration requests."""
@ -105,14 +133,16 @@ class Configurator(object):
self._cur_id = 0 self._cur_id = 0
self._requests = {} self._requests = {}
hass.services.async_register( hass.services.async_register(
DOMAIN, SERVICE_CONFIGURE, self.handle_service_call) DOMAIN, SERVICE_CONFIGURE, self.async_handle_service_call)
def request_config( @async_callback
def async_request_config(
self, name, callback, self, name, callback,
description, description_image, submit_caption, description, description_image, submit_caption,
fields, link_name, link_url, entity_picture): fields, link_name, link_url, entity_picture):
"""Set up a request for configuration.""" """Set up a request for configuration."""
entity_id = generate_entity_id(ENTITY_ID_FORMAT, name, hass=self.hass) entity_id = async_generate_entity_id(
ENTITY_ID_FORMAT, name, hass=self.hass)
if fields is None: if fields is None:
fields = [] fields = []
@ -138,11 +168,12 @@ class Configurator(object):
] if value is not None ] if value is not None
}) })
self.hass.states.set(entity_id, STATE_CONFIGURE, data) self.hass.states.async_set(entity_id, STATE_CONFIGURE, data)
return request_id return request_id
def notify_errors(self, request_id, error): @async_callback
def async_notify_errors(self, request_id, error):
"""Update the state with errors.""" """Update the state with errors."""
if not self._validate_request_id(request_id): if not self._validate_request_id(request_id):
return return
@ -154,9 +185,10 @@ class Configurator(object):
new_data = dict(state.attributes) new_data = dict(state.attributes)
new_data[ATTR_ERRORS] = error new_data[ATTR_ERRORS] = error
self.hass.states.set(entity_id, STATE_CONFIGURE, new_data) self.hass.states.async_set(entity_id, STATE_CONFIGURE, new_data)
def request_done(self, request_id): @async_callback
def async_request_done(self, request_id):
"""Remove the configuration request.""" """Remove the configuration request."""
if not self._validate_request_id(request_id): if not self._validate_request_id(request_id):
return return
@ -167,15 +199,16 @@ class Configurator(object):
# the result fo the service call (current design limitation). # the result fo the service call (current design limitation).
# Instead, we will set it to configured to give as feedback but delete # Instead, we will set it to configured to give as feedback but delete
# it shortly after so that it is deleted when the client updates. # it shortly after so that it is deleted when the client updates.
self.hass.states.set(entity_id, STATE_CONFIGURED) self.hass.states.async_set(entity_id, STATE_CONFIGURED)
def deferred_remove(event): def deferred_remove(event):
"""Remove the request state.""" """Remove the request state."""
self.hass.states.remove(entity_id) self.hass.states.async_remove(entity_id)
self.hass.bus.listen_once(EVENT_TIME_CHANGED, deferred_remove) self.hass.bus.async_listen_once(EVENT_TIME_CHANGED, deferred_remove)
def handle_service_call(self, call): @async_callback
def async_handle_service_call(self, call):
"""Handle a configure service call.""" """Handle a configure service call."""
request_id = call.data.get(ATTR_CONFIGURE_ID) request_id = call.data.get(ATTR_CONFIGURE_ID)
@ -186,8 +219,8 @@ class Configurator(object):
entity_id, fields, callback = self._requests[request_id] entity_id, fields, callback = self._requests[request_id]
# field validation goes here? # field validation goes here?
if callback:
self.hass.async_add_job(callback, call.data.get(ATTR_FIELDS, {})) self.hass.async_add_job(callback, call.data.get(ATTR_FIELDS, {}))
def _generate_unique_id(self): def _generate_unique_id(self):
"""Generate a unique configurator ID.""" """Generate a unique configurator ID."""

View file

@ -6,28 +6,33 @@ https://home-assistant.io/components/device_tracker.automatic/
""" """
import asyncio import asyncio
from datetime import timedelta from datetime import timedelta
import json
import logging import logging
import os
from aiohttp import web
import voluptuous as vol import voluptuous as vol
from homeassistant.components.device_tracker import ( from homeassistant.components.device_tracker import (
PLATFORM_SCHEMA, ATTR_ATTRIBUTES, ATTR_DEV_ID, ATTR_HOST_NAME, ATTR_MAC, PLATFORM_SCHEMA, ATTR_ATTRIBUTES, ATTR_DEV_ID, ATTR_HOST_NAME, ATTR_MAC,
ATTR_GPS, ATTR_GPS_ACCURACY) ATTR_GPS, ATTR_GPS_ACCURACY)
from homeassistant.components.http import HomeAssistantView
from homeassistant.const import ( from homeassistant.const import (
CONF_USERNAME, CONF_PASSWORD, EVENT_HOMEASSISTANT_STOP, CONF_USERNAME, CONF_PASSWORD, EVENT_HOMEASSISTANT_STOP)
EVENT_HOMEASSISTANT_START)
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.event import async_track_time_interval
REQUIREMENTS = ['aioautomatic==0.4.0'] REQUIREMENTS = ['aioautomatic==0.5.0']
DEPENDENCIES = ['http']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
CONF_CLIENT_ID = 'client_id' CONF_CLIENT_ID = 'client_id'
CONF_SECRET = 'secret' CONF_SECRET = 'secret'
CONF_DEVICES = 'devices' CONF_DEVICES = 'devices'
CONF_CURRENT_LOCATION = 'current_location'
DEFAULT_TIMEOUT = 5 DEFAULT_TIMEOUT = 5
@ -38,38 +43,76 @@ ATTR_FUEL_LEVEL = 'fuel_level'
EVENT_AUTOMATIC_UPDATE = 'automatic_update' EVENT_AUTOMATIC_UPDATE = 'automatic_update'
AUTOMATIC_CONFIG_FILE = '.automatic/session-{}.json'
DATA_CONFIGURING = 'automatic_configurator_clients'
DATA_REFRESH_TOKEN = 'refresh_token'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_CLIENT_ID): cv.string, vol.Required(CONF_CLIENT_ID): cv.string,
vol.Required(CONF_SECRET): cv.string, vol.Required(CONF_SECRET): cv.string,
vol.Required(CONF_USERNAME): cv.string, vol.Inclusive(CONF_USERNAME, 'auth'): cv.string,
vol.Required(CONF_PASSWORD): cv.string, vol.Inclusive(CONF_PASSWORD, 'auth'): cv.string,
vol.Optional(CONF_CURRENT_LOCATION, default=False): cv.boolean,
vol.Optional(CONF_DEVICES, default=None): vol.All( vol.Optional(CONF_DEVICES, default=None): vol.All(
cv.ensure_list, [cv.string]) cv.ensure_list, [cv.string])
}) })
def _get_refresh_token_from_file(hass, filename):
"""Attempt to load session data from file."""
path = hass.config.path(filename)
if not os.path.isfile(path):
return None
try:
with open(path) as data_file:
data = json.load(data_file)
if data is None:
return None
return data.get(DATA_REFRESH_TOKEN)
except ValueError:
return None
def _write_refresh_token_to_file(hass, filename, refresh_token):
"""Attempt to store session data to file."""
path = hass.config.path(filename)
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, 'w+') as data_file:
json.dump({
DATA_REFRESH_TOKEN: refresh_token
}, data_file)
@asyncio.coroutine @asyncio.coroutine
def async_setup_scanner(hass, config, async_see, discovery_info=None): def async_setup_scanner(hass, config, async_see, discovery_info=None):
"""Validate the configuration and return an Automatic scanner.""" """Validate the configuration and return an Automatic scanner."""
import aioautomatic import aioautomatic
hass.http.register_view(AutomaticAuthCallbackView())
scope = FULL_SCOPE if config.get(CONF_CURRENT_LOCATION) else DEFAULT_SCOPE
client = aioautomatic.Client( client = aioautomatic.Client(
client_id=config[CONF_CLIENT_ID], client_id=config[CONF_CLIENT_ID],
client_secret=config[CONF_SECRET], client_secret=config[CONF_SECRET],
client_session=async_get_clientsession(hass), client_session=async_get_clientsession(hass),
request_kwargs={'timeout': DEFAULT_TIMEOUT}) request_kwargs={'timeout': DEFAULT_TIMEOUT})
try:
try:
session = yield from client.create_session_from_password(
FULL_SCOPE, config[CONF_USERNAME], config[CONF_PASSWORD])
except aioautomatic.exceptions.ForbiddenError as exc:
if not str(exc).startswith("invalid_scope"):
raise exc
_LOGGER.info("Client not authorized for current_location scope. "
"location:updated events will not be received.")
session = yield from client.create_session_from_password(
DEFAULT_SCOPE, config[CONF_USERNAME], config[CONF_PASSWORD])
filename = AUTOMATIC_CONFIG_FILE.format(config[CONF_CLIENT_ID])
refresh_token = yield from hass.async_add_job(
_get_refresh_token_from_file, hass, filename)
@asyncio.coroutine
def initialize_data(session):
"""Initialize the AutomaticData object from the created session."""
hass.async_add_job(
_write_refresh_token_to_file, hass, filename,
session.refresh_token)
data = AutomaticData( data = AutomaticData(
hass, client, session, config[CONF_DEVICES], async_see) hass, client, session, config[CONF_DEVICES], async_see)
@ -77,26 +120,105 @@ def async_setup_scanner(hass, config, async_see, discovery_info=None):
vehicles = yield from session.get_vehicles() vehicles = yield from session.get_vehicles()
for vehicle in vehicles: for vehicle in vehicles:
hass.async_add_job(data.load_vehicle(vehicle)) hass.async_add_job(data.load_vehicle(vehicle))
except aioautomatic.exceptions.AutomaticError as err:
_LOGGER.error(str(err))
return False
@callback # Create a task instead of adding a tracking job, since this task will
def ws_connect(event): # run until the websocket connection is closed.
"""Open the websocket connection.""" hass.loop.create_task(data.ws_connect())
hass.async_add_job(data.ws_connect())
@callback if refresh_token is not None:
def ws_close(event): try:
"""Close the websocket connection.""" session = yield from client.create_session_from_refresh_token(
hass.async_add_job(data.ws_close()) refresh_token)
yield from initialize_data(session)
return True
except aioautomatic.exceptions.BadRequestError as err:
if str(err) == 'err_invalid_refresh_token':
_LOGGER.error("Stored refresh token was invalid.")
yield from hass.async_add_job(
_write_refresh_token_to_file, hass, filename, None)
else:
_LOGGER.error(str(err))
return False
except aioautomatic.exceptions.AutomaticError as err:
_LOGGER.error(str(err))
return False
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, ws_connect) if CONF_USERNAME in config:
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, ws_close) try:
session = yield from client.create_session_from_password(
scope, config[CONF_USERNAME], config[CONF_PASSWORD])
yield from initialize_data(session)
return True
except aioautomatic.exceptions.AutomaticError as err:
_LOGGER.error(str(err))
return False
configurator = hass.components.configurator
request_id = configurator.async_request_config(
"Automatic", description=(
"Authorization required for Automatic device tracker."),
link_name="Click here to authorize Home Assistant.",
link_url=client.generate_oauth_url(scope),
entity_picture="/static/images/logo_automatic.png",
)
@asyncio.coroutine
def initialize_callback(code, state):
"""Callback after OAuth2 response is returned."""
try:
session = yield from client.create_session_from_oauth_code(
code, state)
yield from initialize_data(session)
configurator.async_request_done(request_id)
except aioautomatic.exceptions.AutomaticError as err:
_LOGGER.error(str(err))
configurator.async_notify_errors(request_id, str(err))
return False
if DATA_CONFIGURING not in hass.data:
hass.data[DATA_CONFIGURING] = {}
hass.data[DATA_CONFIGURING][client.state] = initialize_callback
return True return True
class AutomaticAuthCallbackView(HomeAssistantView):
"""Handle OAuth finish callback requests."""
requires_auth = False
url = '/api/automatic/callback'
name = 'api:automatic:callback'
@callback
def get(self, request): # pylint: disable=no-self-use
"""Finish OAuth callback request."""
hass = request.app['hass']
params = request.query
response = web.HTTPFound('/states')
if 'state' not in params or 'code' not in params:
if 'error' in params:
_LOGGER.error(
"Error authorizing Automatic: %s", params['error'])
return response
else:
_LOGGER.error(
"Error authorizing Automatic. Invalid response returned.")
return response
if DATA_CONFIGURING not in hass.data or \
params['state'] not in hass.data[DATA_CONFIGURING]:
_LOGGER.error("Automatic configuration request not found.")
return response
code = params['code']
state = params['state']
initialize_callback = hass.data[DATA_CONFIGURING][state]
hass.async_add_job(initialize_callback(code, state))
return response
class AutomaticData(object): class AutomaticData(object):
"""A class representing an Automatic cloud service connection.""" """A class representing an Automatic cloud service connection."""
@ -115,6 +237,8 @@ class AutomaticData(object):
lambda name, event: self.hass.async_add_job( lambda name, event: self.hass.async_add_job(
self.handle_event(name, event))) self.handle_event(name, event)))
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.ws_close())
@asyncio.coroutine @asyncio.coroutine
def handle_event(self, name, event): def handle_event(self, name, event):
"""Coroutine to update state for a realtime event.""" """Coroutine to update state for a realtime event."""

View file

@ -19,7 +19,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.util import slugify from homeassistant.util import slugify
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
from homeassistant.util.location import distance from homeassistant.util.location import distance
from homeassistant.loader import get_component
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -209,7 +208,7 @@ class Icloud(DeviceScanner):
if self.accountname in _CONFIGURING: if self.accountname in _CONFIGURING:
request_id = _CONFIGURING.pop(self.accountname) request_id = _CONFIGURING.pop(self.accountname)
configurator = get_component('configurator') configurator = self.hass.components.configurator
configurator.request_done(request_id) configurator.request_done(request_id)
# Trigger the next step immediately # Trigger the next step immediately
@ -217,7 +216,7 @@ class Icloud(DeviceScanner):
def icloud_need_trusted_device(self): def icloud_need_trusted_device(self):
"""We need a trusted device.""" """We need a trusted device."""
configurator = get_component('configurator') configurator = self.hass.components.configurator
if self.accountname in _CONFIGURING: if self.accountname in _CONFIGURING:
return return
@ -229,7 +228,7 @@ class Icloud(DeviceScanner):
devicesstring += "{}: {};".format(i, devicename) devicesstring += "{}: {};".format(i, devicename)
_CONFIGURING[self.accountname] = configurator.request_config( _CONFIGURING[self.accountname] = configurator.request_config(
self.hass, 'iCloud {}'.format(self.accountname), 'iCloud {}'.format(self.accountname),
self.icloud_trusted_device_callback, self.icloud_trusted_device_callback,
description=( description=(
'Please choose your trusted device by entering' 'Please choose your trusted device by entering'
@ -259,17 +258,17 @@ class Icloud(DeviceScanner):
if self.accountname in _CONFIGURING: if self.accountname in _CONFIGURING:
request_id = _CONFIGURING.pop(self.accountname) request_id = _CONFIGURING.pop(self.accountname)
configurator = get_component('configurator') configurator = self.hass.components.configurator
configurator.request_done(request_id) configurator.request_done(request_id)
def icloud_need_verification_code(self): def icloud_need_verification_code(self):
"""Return the verification code.""" """Return the verification code."""
configurator = get_component('configurator') configurator = self.hass.components.configurator
if self.accountname in _CONFIGURING: if self.accountname in _CONFIGURING:
return return
_CONFIGURING[self.accountname] = configurator.request_config( _CONFIGURING[self.accountname] = configurator.request_config(
self.hass, 'iCloud {}'.format(self.accountname), 'iCloud {}'.format(self.accountname),
self.icloud_verification_callback, self.icloud_verification_callback,
description=('Please enter the validation code:'), description=('Please enter the validation code:'),
entity_picture="/static/images/config_icloud.png", entity_picture="/static/images/config_icloud.png",

View file

@ -13,7 +13,6 @@ import voluptuous as vol
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers import discovery from homeassistant.helpers import discovery
from homeassistant.const import CONF_API_KEY from homeassistant.const import CONF_API_KEY
from homeassistant.loader import get_component
from homeassistant.util import Throttle from homeassistant.util import Throttle
REQUIREMENTS = ['python-ecobee-api==0.0.7'] REQUIREMENTS = ['python-ecobee-api==0.0.7']
@ -41,7 +40,7 @@ CONFIG_SCHEMA = vol.Schema({
def request_configuration(network, hass, config): def request_configuration(network, hass, config):
"""Request configuration steps from the user.""" """Request configuration steps from the user."""
configurator = get_component('configurator') configurator = hass.components.configurator
if 'ecobee' in _CONFIGURING: if 'ecobee' in _CONFIGURING:
configurator.notify_errors( configurator.notify_errors(
_CONFIGURING['ecobee'], "Failed to register, please try again.") _CONFIGURING['ecobee'], "Failed to register, please try again.")
@ -56,7 +55,7 @@ def request_configuration(network, hass, config):
setup_ecobee(hass, network, config) setup_ecobee(hass, network, config)
_CONFIGURING['ecobee'] = configurator.request_config( _CONFIGURING['ecobee'] = configurator.request_config(
hass, "Ecobee", ecobee_configuration_callback, "Ecobee", ecobee_configuration_callback,
description=( description=(
'Please authorize this app at https://www.ecobee.com/consumer' 'Please authorize this app at https://www.ecobee.com/consumer'
'portal/index.html with pin code: ' + network.pin), 'portal/index.html with pin code: ' + network.pin),
@ -73,7 +72,7 @@ def setup_ecobee(hass, network, config):
return return
if 'ecobee' in _CONFIGURING: if 'ecobee' in _CONFIGURING:
configurator = get_component('configurator') configurator = hass.components.configurator
configurator.request_done(_CONFIGURING.pop('ecobee')) configurator.request_done(_CONFIGURING.pop('ecobee'))
hold_temp = config[DOMAIN].get(CONF_HOLD_TEMP) hold_temp = config[DOMAIN].get(CONF_HOLD_TEMP)

View file

@ -13,7 +13,6 @@ from homeassistant.components.fan import (
ATTR_SPEED, SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH, ATTR_SPEED, SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH,
SUPPORT_SET_SPEED, FanEntity) SUPPORT_SET_SPEED, FanEntity)
from homeassistant.helpers.entity import ToggleEntity from homeassistant.helpers.entity import ToggleEntity
from homeassistant.loader import get_component
import homeassistant.util as util import homeassistant.util as util
_CONFIGURING = {} _CONFIGURING = {}
@ -57,7 +56,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
def request_configuration(device_id, insteonhub, model, hass, def request_configuration(device_id, insteonhub, model, hass,
add_devices_callback): add_devices_callback):
"""Request configuration steps from the user.""" """Request configuration steps from the user."""
configurator = get_component('configurator') configurator = hass.components.configurator
# We got an error if this method is called while we are configuring # We got an error if this method is called while we are configuring
if device_id in _CONFIGURING: if device_id in _CONFIGURING:
@ -72,7 +71,7 @@ def request_configuration(device_id, insteonhub, model, hass,
add_devices_callback) add_devices_callback)
_CONFIGURING[device_id] = configurator.request_config( _CONFIGURING[device_id] = configurator.request_config(
hass, 'Insteon ' + model + ' addr: ' + device_id, 'Insteon ' + model + ' addr: ' + device_id,
insteon_fan_config_callback, insteon_fan_config_callback,
description=('Enter a name for ' + model + ' Fan addr: ' + device_id), description=('Enter a name for ' + model + ' Fan addr: ' + device_id),
entity_picture='/static/images/config_insteon.png', entity_picture='/static/images/config_insteon.png',
@ -85,7 +84,7 @@ def setup_fan(device_id, name, insteonhub, hass, add_devices_callback):
"""Set up the fan.""" """Set up the fan."""
if device_id in _CONFIGURING: if device_id in _CONFIGURING:
request_id = _CONFIGURING.pop(device_id) request_id = _CONFIGURING.pop(device_id)
configurator = get_component('configurator') configurator = hass.components.configurator
configurator.request_done(request_id) configurator.request_done(request_id)
_LOGGER.info("Device configuration done!") _LOGGER.info("Device configuration done!")

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

View file

@ -23,7 +23,6 @@ from homeassistant.components.light import (
SUPPORT_XY_COLOR, Light, PLATFORM_SCHEMA) SUPPORT_XY_COLOR, Light, PLATFORM_SCHEMA)
from homeassistant.config import load_yaml_config_file from homeassistant.config import load_yaml_config_file
from homeassistant.const import (CONF_FILENAME, CONF_HOST, DEVICE_DEFAULT_NAME) from homeassistant.const import (CONF_FILENAME, CONF_HOST, DEVICE_DEFAULT_NAME)
from homeassistant.loader import get_component
from homeassistant.components.emulated_hue import ATTR_EMULATED_HUE from homeassistant.components.emulated_hue import ATTR_EMULATED_HUE
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
@ -164,9 +163,7 @@ def setup_bridge(host, hass, add_devices, filename, allow_unreachable,
# If we came here and configuring this host, mark as done # If we came here and configuring this host, mark as done
if host in _CONFIGURING: if host in _CONFIGURING:
request_id = _CONFIGURING.pop(host) request_id = _CONFIGURING.pop(host)
configurator = hass.components.configurator
configurator = get_component('configurator')
configurator.request_done(request_id) configurator.request_done(request_id)
lights = {} lights = {}
@ -268,7 +265,7 @@ def request_configuration(host, hass, add_devices, filename,
allow_unreachable, allow_in_emulated_hue, allow_unreachable, allow_in_emulated_hue,
allow_hue_groups): allow_hue_groups):
"""Request configuration steps from the user.""" """Request configuration steps from the user."""
configurator = get_component('configurator') configurator = hass.components.configurator
# We got an error if this method is called while we are configuring # We got an error if this method is called while we are configuring
if host in _CONFIGURING: if host in _CONFIGURING:
@ -284,7 +281,7 @@ def request_configuration(host, hass, add_devices, filename,
allow_in_emulated_hue, allow_hue_groups) allow_in_emulated_hue, allow_hue_groups)
_CONFIGURING[host] = configurator.request_config( _CONFIGURING[host] = configurator.request_config(
hass, "Philips Hue", hue_configuration_callback, "Philips Hue", hue_configuration_callback,
description=("Press the button on the bridge to register Philips Hue " description=("Press the button on the bridge to register Philips Hue "
"with Home Assistant."), "with Home Assistant."),
entity_picture="/static/images/logo_philips_hue.png", entity_picture="/static/images/logo_philips_hue.png",

View file

@ -11,7 +11,6 @@ from datetime import timedelta
from homeassistant.components.light import ( from homeassistant.components.light import (
ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light) ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light)
from homeassistant.loader import get_component
import homeassistant.util as util import homeassistant.util as util
_CONFIGURING = {} _CONFIGURING = {}
@ -54,7 +53,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
def request_configuration(device_id, insteonhub, model, hass, def request_configuration(device_id, insteonhub, model, hass,
add_devices_callback): add_devices_callback):
"""Request configuration steps from the user.""" """Request configuration steps from the user."""
configurator = get_component('configurator') configurator = hass.components.configurator
# We got an error if this method is called while we are configuring # We got an error if this method is called while we are configuring
if device_id in _CONFIGURING: if device_id in _CONFIGURING:
@ -69,7 +68,7 @@ def request_configuration(device_id, insteonhub, model, hass,
add_devices_callback) add_devices_callback)
_CONFIGURING[device_id] = configurator.request_config( _CONFIGURING[device_id] = configurator.request_config(
hass, 'Insteon ' + model + ' addr: ' + device_id, 'Insteon ' + model + ' addr: ' + device_id,
insteon_light_config_callback, insteon_light_config_callback,
description=('Enter a name for ' + model + ' addr: ' + device_id), description=('Enter a name for ' + model + ' addr: ' + device_id),
entity_picture='/static/images/config_insteon.png', entity_picture='/static/images/config_insteon.png',
@ -82,7 +81,7 @@ def setup_light(device_id, name, insteonhub, hass, add_devices_callback):
"""Set up the light.""" """Set up the light."""
if device_id in _CONFIGURING: if device_id in _CONFIGURING:
request_id = _CONFIGURING.pop(device_id) request_id = _CONFIGURING.pop(device_id)
configurator = get_component('configurator') configurator = hass.components.configurator
configurator.request_done(request_id) configurator.request_done(request_id)
_LOGGER.debug("Device configuration done") _LOGGER.debug("Device configuration done")

View file

@ -11,7 +11,6 @@ import re
import voluptuous as vol import voluptuous as vol
from homeassistant.loader import get_component
from homeassistant.components.media_player import ( from homeassistant.components.media_player import (
SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, SUPPORT_TURN_ON, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, SUPPORT_TURN_ON,
SUPPORT_TURN_OFF, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP, SUPPORT_PLAY, SUPPORT_TURN_OFF, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP, SUPPORT_PLAY,
@ -132,7 +131,7 @@ def setup_bravia(config, pin, hass, add_devices):
# If we came here and configuring this host, mark as done # If we came here and configuring this host, mark as done
if host in _CONFIGURING: if host in _CONFIGURING:
request_id = _CONFIGURING.pop(host) request_id = _CONFIGURING.pop(host)
configurator = get_component('configurator') configurator = hass.components.configurator
configurator.request_done(request_id) configurator.request_done(request_id)
_LOGGER.info("Discovery configuration done") _LOGGER.info("Discovery configuration done")
@ -150,7 +149,7 @@ def request_configuration(config, hass, add_devices):
host = config.get(CONF_HOST) host = config.get(CONF_HOST)
name = config.get(CONF_NAME) name = config.get(CONF_NAME)
configurator = get_component('configurator') configurator = hass.components.configurator
# We got an error if this method is called while we are configuring # We got an error if this method is called while we are configuring
if host in _CONFIGURING: if host in _CONFIGURING:
@ -171,7 +170,7 @@ def request_configuration(config, hass, add_devices):
request_configuration(config, hass, add_devices) request_configuration(config, hass, add_devices)
_CONFIGURING[host] = configurator.request_config( _CONFIGURING[host] = configurator.request_config(
hass, name, bravia_configuration_callback, name, bravia_configuration_callback,
description='Enter the Pin shown on your Sony Bravia TV.' + description='Enter the Pin shown on your Sony Bravia TV.' +
'If no Pin is shown, enter 0000 to let TV show you a Pin.', 'If no Pin is shown, enter 0000 to let TV show you a Pin.',
description_image="/static/images/smart-tv.png", description_image="/static/images/smart-tv.png",

View file

@ -18,7 +18,6 @@ from homeassistant.components.media_player import (
MediaPlayerDevice, PLATFORM_SCHEMA) MediaPlayerDevice, PLATFORM_SCHEMA)
from homeassistant.const import ( from homeassistant.const import (
STATE_PLAYING, STATE_PAUSED, STATE_OFF, CONF_HOST, CONF_PORT, CONF_NAME) STATE_PLAYING, STATE_PAUSED, STATE_OFF, CONF_HOST, CONF_PORT, CONF_NAME)
from homeassistant.loader import get_component
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['websocket-client==0.37.0'] REQUIREMENTS = ['websocket-client==0.37.0']
@ -48,7 +47,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def request_configuration(hass, config, url, add_devices_callback): def request_configuration(hass, config, url, add_devices_callback):
"""Request configuration steps from the user.""" """Request configuration steps from the user."""
configurator = get_component('configurator') configurator = hass.components.configurator
if 'gpmdp' in _CONFIGURING: if 'gpmdp' in _CONFIGURING:
configurator.notify_errors( configurator.notify_errors(
_CONFIGURING['gpmdp'], "Failed to register, please try again.") _CONFIGURING['gpmdp'], "Failed to register, please try again.")
@ -96,7 +95,7 @@ def request_configuration(hass, config, url, add_devices_callback):
break break
_CONFIGURING['gpmdp'] = configurator.request_config( _CONFIGURING['gpmdp'] = configurator.request_config(
hass, DEFAULT_NAME, gpmdp_configuration_callback, DEFAULT_NAME, gpmdp_configuration_callback,
description=( description=(
'Enter the pin that is displayed in the ' 'Enter the pin that is displayed in the '
'Google Play Music Desktop Player.'), 'Google Play Music Desktop Player.'),
@ -117,7 +116,7 @@ def setup_gpmdp(hass, config, code, add_devices):
return return
if 'gpmdp' in _CONFIGURING: if 'gpmdp' in _CONFIGURING:
configurator = get_component('configurator') configurator = hass.components.configurator
configurator.request_done(_CONFIGURING.pop('gpmdp')) configurator.request_done(_CONFIGURING.pop('gpmdp'))
add_devices([GPMDP(name, url, code)], True) add_devices([GPMDP(name, url, code)], True)

View file

@ -23,7 +23,6 @@ from homeassistant.const import (
DEVICE_DEFAULT_NAME, STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING) DEVICE_DEFAULT_NAME, STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING)
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.event import track_utc_time_change from homeassistant.helpers.event import track_utc_time_change
from homeassistant.loader import get_component
REQUIREMENTS = ['plexapi==2.0.2'] REQUIREMENTS = ['plexapi==2.0.2']
@ -143,7 +142,7 @@ def setup_plexserver(
# If we came here and configuring this host, mark as done # If we came here and configuring this host, mark as done
if host in _CONFIGURING: if host in _CONFIGURING:
request_id = _CONFIGURING.pop(host) request_id = _CONFIGURING.pop(host)
configurator = get_component('configurator') configurator = hass.components.configurator
configurator.request_done(request_id) configurator.request_done(request_id)
_LOGGER.info("Discovery configuration done") _LOGGER.info("Discovery configuration done")
@ -236,7 +235,7 @@ def setup_plexserver(
def request_configuration(host, hass, config, add_devices_callback): def request_configuration(host, hass, config, add_devices_callback):
"""Request configuration steps from the user.""" """Request configuration steps from the user."""
configurator = get_component('configurator') configurator = hass.components.configurator
# We got an error if this method is called while we are configuring # We got an error if this method is called while we are configuring
if host in _CONFIGURING: if host in _CONFIGURING:
configurator.notify_errors(_CONFIGURING[host], configurator.notify_errors(_CONFIGURING[host],
@ -254,7 +253,6 @@ def request_configuration(host, hass, config, add_devices_callback):
) )
_CONFIGURING[host] = configurator.request_config( _CONFIGURING[host] = configurator.request_config(
hass,
'Plex Media Server', 'Plex Media Server',
plex_configuration_callback, plex_configuration_callback,
description=('Enter the X-Plex-Token'), description=('Enter the X-Plex-Token'),

View file

@ -10,7 +10,6 @@ from datetime import timedelta
import voluptuous as vol import voluptuous as vol
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.loader import get_component
from homeassistant.components.http import HomeAssistantView from homeassistant.components.http import HomeAssistantView
from homeassistant.components.media_player import ( from homeassistant.components.media_player import (
MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, SUPPORT_VOLUME_SET, MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, SUPPORT_VOLUME_SET,
@ -62,9 +61,9 @@ SCAN_INTERVAL = timedelta(seconds=30)
def request_configuration(hass, config, add_devices, oauth): def request_configuration(hass, config, add_devices, oauth):
"""Request Spotify authorization.""" """Request Spotify authorization."""
configurator = get_component('configurator') configurator = hass.components.configurator
hass.data[DOMAIN] = configurator.request_config( hass.data[DOMAIN] = configurator.request_config(
hass, DEFAULT_NAME, lambda _: None, DEFAULT_NAME, lambda _: None,
link_name=CONFIGURATOR_LINK_NAME, link_name=CONFIGURATOR_LINK_NAME,
link_url=oauth.get_authorize_url(), link_url=oauth.get_authorize_url(),
description=CONFIGURATOR_DESCRIPTION, description=CONFIGURATOR_DESCRIPTION,
@ -88,7 +87,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
request_configuration(hass, config, add_devices, oauth) request_configuration(hass, config, add_devices, oauth)
return return
if hass.data.get(DOMAIN): if hass.data.get(DOMAIN):
configurator = get_component('configurator') configurator = hass.components.configurator
configurator.request_done(hass.data.get(DOMAIN)) configurator.request_done(hass.data.get(DOMAIN))
del hass.data[DOMAIN] del hass.data[DOMAIN]
player = SpotifyMediaPlayer(oauth, config.get(CONF_NAME, DEFAULT_NAME), player = SpotifyMediaPlayer(oauth, config.get(CONF_NAME, DEFAULT_NAME),

View file

@ -22,7 +22,6 @@ from homeassistant.const import (
CONF_HOST, CONF_MAC, CONF_CUSTOMIZE, STATE_OFF, CONF_HOST, CONF_MAC, CONF_CUSTOMIZE, STATE_OFF,
STATE_PLAYING, STATE_PAUSED, STATE_PLAYING, STATE_PAUSED,
STATE_UNKNOWN, CONF_NAME, CONF_FILENAME) STATE_UNKNOWN, CONF_NAME, CONF_FILENAME)
from homeassistant.loader import get_component
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pylgtv==0.1.7', REQUIREMENTS = ['pylgtv==0.1.7',
@ -114,7 +113,7 @@ def setup_tv(host, mac, name, customize, config, hass, add_devices):
# If we came here and configuring this host, mark as done. # If we came here and configuring this host, mark as done.
if client.is_registered() and host in _CONFIGURING: if client.is_registered() and host in _CONFIGURING:
request_id = _CONFIGURING.pop(host) request_id = _CONFIGURING.pop(host)
configurator = get_component('configurator') configurator = hass.components.configurator
configurator.request_done(request_id) configurator.request_done(request_id)
add_devices([LgWebOSDevice(host, mac, name, customize, config)], True) add_devices([LgWebOSDevice(host, mac, name, customize, config)], True)
@ -123,7 +122,7 @@ def setup_tv(host, mac, name, customize, config, hass, add_devices):
def request_configuration( def request_configuration(
host, mac, name, customize, config, hass, add_devices): host, mac, name, customize, config, hass, add_devices):
"""Request configuration steps from the user.""" """Request configuration steps from the user."""
configurator = get_component('configurator') configurator = hass.components.configurator
# We got an error if this method is called while we are configuring # We got an error if this method is called while we are configuring
if host in _CONFIGURING: if host in _CONFIGURING:
@ -137,7 +136,7 @@ def request_configuration(
setup_tv(host, mac, name, customize, config, hass, add_devices) setup_tv(host, mac, name, customize, config, hass, add_devices)
_CONFIGURING[host] = configurator.request_config( _CONFIGURING[host] = configurator.request_config(
hass, name, lgtv_configuration_callback, name, lgtv_configuration_callback,
description='Click start and accept the pairing request on your TV.', description='Click start and accept the pairing request on your TV.',
description_image='/static/images/config_webos.png', description_image='/static/images/config_webos.png',
submit_caption='Start pairing request' submit_caption='Start pairing request'

View file

@ -14,7 +14,6 @@ from homeassistant.helpers import discovery
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)
from homeassistant.loader import get_component
REQUIREMENTS = ['python-nest==3.1.0'] REQUIREMENTS = ['python-nest==3.1.0']
@ -54,7 +53,7 @@ CONFIG_SCHEMA = vol.Schema({
def request_configuration(nest, hass, config): def request_configuration(nest, hass, config):
"""Request configuration steps from the user.""" """Request configuration steps from the user."""
configurator = get_component('configurator') configurator = hass.components.configurator
if 'nest' in _CONFIGURING: if 'nest' in _CONFIGURING:
_LOGGER.debug("configurator failed") _LOGGER.debug("configurator failed")
configurator.notify_errors( configurator.notify_errors(
@ -68,7 +67,7 @@ def request_configuration(nest, hass, config):
setup_nest(hass, nest, config, pin=pin) setup_nest(hass, nest, config, pin=pin)
_CONFIGURING['nest'] = configurator.request_config( _CONFIGURING['nest'] = configurator.request_config(
hass, "Nest", nest_configuration_callback, "Nest", nest_configuration_callback,
description=('To configure Nest, click Request Authorization below, ' description=('To configure Nest, click Request Authorization below, '
'log into your Nest account, ' 'log into your Nest account, '
'and then enter the resulting PIN'), 'and then enter the resulting PIN'),
@ -92,7 +91,7 @@ def setup_nest(hass, nest, config, pin=None):
if 'nest' in _CONFIGURING: if 'nest' in _CONFIGURING:
_LOGGER.debug("configuration done") _LOGGER.debug("configuration done")
configurator = get_component('configurator') configurator = hass.components.configurator
configurator.request_done(_CONFIGURING.pop('nest')) configurator.request_done(_CONFIGURING.pop('nest'))
_LOGGER.debug("proceeding with setup") _LOGGER.debug("proceeding with setup")

View file

@ -17,7 +17,6 @@ from homeassistant.components.http import HomeAssistantView
from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import ATTR_ATTRIBUTION from homeassistant.const import ATTR_ATTRIBUTION
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.loader import get_component
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['fitbit==0.2.3'] REQUIREMENTS = ['fitbit==0.2.3']
@ -155,7 +154,7 @@ def config_from_file(filename, config=None):
def request_app_setup(hass, config, add_devices, config_path, def request_app_setup(hass, config, add_devices, config_path,
discovery_info=None): discovery_info=None):
"""Assist user with configuring the Fitbit dev application.""" """Assist user with configuring the Fitbit dev application."""
configurator = get_component('configurator') configurator = hass.components.configurator
# pylint: disable=unused-argument # pylint: disable=unused-argument
def fitbit_configuration_callback(callback_data): def fitbit_configuration_callback(callback_data):
@ -166,7 +165,8 @@ def request_app_setup(hass, config, add_devices, config_path,
if config_file == DEFAULT_CONFIG: if config_file == DEFAULT_CONFIG:
error_msg = ("You didn't correctly modify fitbit.conf", error_msg = ("You didn't correctly modify fitbit.conf",
" please try again") " please try again")
configurator.notify_errors(_CONFIGURING['fitbit'], error_msg) configurator.notify_errors(_CONFIGURING['fitbit'],
error_msg)
else: else:
setup_platform(hass, config, add_devices, discovery_info) setup_platform(hass, config, add_devices, discovery_info)
else: else:
@ -187,7 +187,7 @@ def request_app_setup(hass, config, add_devices, config_path,
submit = "I have saved my Client ID and Client Secret into fitbit.conf." submit = "I have saved my Client ID and Client Secret into fitbit.conf."
_CONFIGURING['fitbit'] = configurator.request_config( _CONFIGURING['fitbit'] = configurator.request_config(
hass, 'Fitbit', fitbit_configuration_callback, 'Fitbit', fitbit_configuration_callback,
description=description, submit_caption=submit, description=description, submit_caption=submit,
description_image="/static/images/config_fitbit_app.png" description_image="/static/images/config_fitbit_app.png"
) )
@ -195,7 +195,7 @@ def request_app_setup(hass, config, add_devices, config_path,
def request_oauth_completion(hass): def request_oauth_completion(hass):
"""Request user complete Fitbit OAuth2 flow.""" """Request user complete Fitbit OAuth2 flow."""
configurator = get_component('configurator') configurator = hass.components.configurator
if "fitbit" in _CONFIGURING: if "fitbit" in _CONFIGURING:
configurator.notify_errors( configurator.notify_errors(
_CONFIGURING['fitbit'], "Failed to register, please try again.") _CONFIGURING['fitbit'], "Failed to register, please try again.")
@ -211,7 +211,7 @@ def request_oauth_completion(hass):
description = "Please authorize Fitbit by visiting {}".format(start_url) description = "Please authorize Fitbit by visiting {}".format(start_url)
_CONFIGURING['fitbit'] = configurator.request_config( _CONFIGURING['fitbit'] = configurator.request_config(
hass, 'Fitbit', fitbit_configuration_callback, 'Fitbit', fitbit_configuration_callback,
description=description, description=description,
submit_caption="I have authorized Fitbit." submit_caption="I have authorized Fitbit."
) )
@ -233,7 +233,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
return False return False
if "fitbit" in _CONFIGURING: if "fitbit" in _CONFIGURING:
get_component('configurator').request_done(_CONFIGURING.pop("fitbit")) hass.components.configurator.request_done(_CONFIGURING.pop("fitbit"))
import fitbit import fitbit

View file

@ -18,7 +18,6 @@ from homeassistant.const import (
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.loader import get_component
REQUIREMENTS = ['https://github.com/jamespcole/home-assistant-nzb-clients/' REQUIREMENTS = ['https://github.com/jamespcole/home-assistant-nzb-clients/'
'archive/616cad59154092599278661af17e2a9f2cf5e2a9.zip' 'archive/616cad59154092599278661af17e2a9f2cf5e2a9.zip'
@ -88,7 +87,7 @@ def setup_sabnzbd(base_url, apikey, name, hass, config, add_devices, sab_api):
def request_configuration(host, name, hass, config, add_devices, sab_api): def request_configuration(host, name, hass, config, add_devices, sab_api):
"""Request configuration steps from the user.""" """Request configuration steps from the user."""
configurator = get_component('configurator') configurator = hass.components.configurator
# We got an error if this method is called while we are configuring # We got an error if this method is called while we are configuring
if host in _CONFIGURING: if host in _CONFIGURING:
configurator.notify_errors(_CONFIGURING[host], configurator.notify_errors(_CONFIGURING[host],
@ -114,7 +113,6 @@ def request_configuration(host, name, hass, config, add_devices, sab_api):
hass.async_add_job(success) hass.async_add_job(success)
_CONFIGURING[host] = configurator.request_config( _CONFIGURING[host] = configurator.request_config(
hass,
DEFAULT_NAME, DEFAULT_NAME,
sabnzbd_configuration_callback, sabnzbd_configuration_callback,
description=('Enter the API Key'), description=('Enter the API Key'),

View file

@ -10,7 +10,6 @@ import os
from datetime import timedelta from datetime import timedelta
from homeassistant.components.switch import SwitchDevice from homeassistant.components.switch import SwitchDevice
from homeassistant.loader import get_component
import homeassistant.util as util import homeassistant.util as util
_CONFIGURING = {} _CONFIGURING = {}
@ -51,7 +50,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
def request_configuration( def request_configuration(
device_id, insteonhub, model, hass, add_devices_callback): device_id, insteonhub, model, hass, add_devices_callback):
"""Request configuration steps from the user.""" """Request configuration steps from the user."""
configurator = get_component('configurator') configurator = hass.components.configurator
# We got an error if this method is called while we are configuring # We got an error if this method is called while we are configuring
if device_id in _CONFIGURING: if device_id in _CONFIGURING:
@ -66,7 +65,7 @@ def request_configuration(
add_devices_callback) add_devices_callback)
_CONFIGURING[device_id] = configurator.request_config( _CONFIGURING[device_id] = configurator.request_config(
hass, 'Insteon Switch ' + model + ' addr: ' + device_id, 'Insteon Switch ' + model + ' addr: ' + device_id,
insteon_switch_config_callback, insteon_switch_config_callback,
description=('Enter a name for ' + model + ' addr: ' + device_id), description=('Enter a name for ' + model + ' addr: ' + device_id),
entity_picture='/static/images/config_insteon.png', entity_picture='/static/images/config_insteon.png',
@ -79,7 +78,7 @@ def setup_switch(device_id, name, insteonhub, hass, add_devices_callback):
"""Set up the switch.""" """Set up the switch."""
if device_id in _CONFIGURING: if device_id in _CONFIGURING:
request_id = _CONFIGURING.pop(device_id) request_id = _CONFIGURING.pop(device_id)
configurator = get_component('configurator') configurator = hass.components.configurator
configurator.request_done(request_id) configurator.request_done(request_id)
_LOGGER.info("Device configuration done") _LOGGER.info("Device configuration done")

View file

@ -14,7 +14,6 @@ import voluptuous as vol
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers import discovery from homeassistant.helpers import discovery
from homeassistant.const import CONF_HOST, CONF_API_KEY from homeassistant.const import CONF_HOST, CONF_API_KEY
from homeassistant.loader import get_component
from homeassistant.components.discovery import SERVICE_IKEA_TRADFRI from homeassistant.components.discovery import SERVICE_IKEA_TRADFRI
REQUIREMENTS = ['pytradfri==1.1'] REQUIREMENTS = ['pytradfri==1.1']
@ -41,7 +40,7 @@ _LOGGER = logging.getLogger(__name__)
def request_configuration(hass, config, host): def request_configuration(hass, config, host):
"""Request configuration steps from the user.""" """Request configuration steps from the user."""
configurator = get_component('configurator') configurator = hass.components.configurator
hass.data.setdefault(KEY_CONFIG, {}) hass.data.setdefault(KEY_CONFIG, {})
instance = hass.data[KEY_CONFIG].get(host) instance = hass.data[KEY_CONFIG].get(host)
@ -70,7 +69,7 @@ def request_configuration(hass, config, host):
hass.async_add_job(success) hass.async_add_job(success)
instance = configurator.request_config( instance = configurator.request_config(
hass, "IKEA Trådfri", configuration_callback, "IKEA Trådfri", configuration_callback,
description='Please enter the security code written at the bottom of ' description='Please enter the security code written at the bottom of '
'your IKEA Trådfri Gateway.', 'your IKEA Trådfri Gateway.',
submit_caption="Confirm", submit_caption="Confirm",

View file

@ -13,7 +13,6 @@ from datetime import timedelta
import voluptuous as vol import voluptuous as vol
import requests import requests
from homeassistant.loader import get_component
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.components.http import HomeAssistantView from homeassistant.components.http import HomeAssistantView
from homeassistant.helpers import discovery from homeassistant.helpers import discovery
@ -103,7 +102,7 @@ def _read_config_file(file_path):
def _request_app_setup(hass, config): def _request_app_setup(hass, config):
"""Assist user with configuring the Wink dev application.""" """Assist user with configuring the Wink dev application."""
hass.data[DOMAIN]['configurator'] = True hass.data[DOMAIN]['configurator'] = True
configurator = get_component('configurator') configurator = hass.components.configurator
# pylint: disable=unused-argument # pylint: disable=unused-argument
def wink_configuration_callback(callback_data): def wink_configuration_callback(callback_data):
@ -138,7 +137,7 @@ def _request_app_setup(hass, config):
""".format(start_url) """.format(start_url)
hass.data[DOMAIN]['configuring'][DOMAIN] = configurator.request_config( hass.data[DOMAIN]['configuring'][DOMAIN] = configurator.request_config(
hass, DOMAIN, wink_configuration_callback, DOMAIN, wink_configuration_callback,
description=description, submit_caption="submit", description=description, submit_caption="submit",
description_image="/static/images/config_wink.png", description_image="/static/images/config_wink.png",
fields=[{'id': 'client_id', 'name': 'Client ID', 'type': 'string'}, fields=[{'id': 'client_id', 'name': 'Client ID', 'type': 'string'},
@ -151,7 +150,7 @@ def _request_app_setup(hass, config):
def _request_oauth_completion(hass, config): def _request_oauth_completion(hass, config):
"""Request user complete Wink OAuth2 flow.""" """Request user complete Wink OAuth2 flow."""
hass.data[DOMAIN]['configurator'] = True hass.data[DOMAIN]['configurator'] = True
configurator = get_component('configurator') configurator = hass.components.configurator
if DOMAIN in hass.data[DOMAIN]['configuring']: if DOMAIN in hass.data[DOMAIN]['configuring']:
configurator.notify_errors( configurator.notify_errors(
hass.data[DOMAIN]['configuring'][DOMAIN], hass.data[DOMAIN]['configuring'][DOMAIN],
@ -168,7 +167,7 @@ def _request_oauth_completion(hass, config):
description = "Please authorize Wink by visiting {}".format(start_url) description = "Please authorize Wink by visiting {}".format(start_url)
hass.data[DOMAIN]['configuring'][DOMAIN] = configurator.request_config( hass.data[DOMAIN]['configuring'][DOMAIN] = configurator.request_config(
hass, DOMAIN, wink_configuration_callback, DOMAIN, wink_configuration_callback,
description=description description=description
) )
@ -248,7 +247,7 @@ def setup(hass, config):
if DOMAIN in hass.data[DOMAIN]['configuring']: if DOMAIN in hass.data[DOMAIN]['configuring']:
_configurator = hass.data[DOMAIN]['configuring'] _configurator = hass.data[DOMAIN]['configuring']
get_component('configurator').request_done(_configurator.pop( hass.components.configurator.request_done(_configurator.pop(
DOMAIN)) DOMAIN))
# Using oauth # Using oauth

View file

@ -39,7 +39,7 @@ SoCo==0.12
TwitterAPI==2.4.6 TwitterAPI==2.4.6
# homeassistant.components.device_tracker.automatic # homeassistant.components.device_tracker.automatic
aioautomatic==0.4.0 aioautomatic==0.5.0
# homeassistant.components.sensor.dnsip # homeassistant.components.sensor.dnsip
aiodns==1.1.1 aiodns==1.1.1

View file

@ -27,7 +27,7 @@ PyJWT==1.5.2
SoCo==0.12 SoCo==0.12
# homeassistant.components.device_tracker.automatic # homeassistant.components.device_tracker.automatic
aioautomatic==0.4.0 aioautomatic==0.5.0
# homeassistant.components.emulated_hue # homeassistant.components.emulated_hue
# homeassistant.components.http # homeassistant.components.http

View file

@ -7,12 +7,16 @@ import aioautomatic
from homeassistant.components.device_tracker.automatic import ( from homeassistant.components.device_tracker.automatic import (
async_setup_scanner) async_setup_scanner)
from tests.common import mock_http_component
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@patch('aioautomatic.Client.create_session_from_password') @patch('aioautomatic.Client.create_session_from_password')
def test_invalid_credentials(mock_create_session, hass): def test_invalid_credentials(mock_create_session, hass):
"""Test with invalid credentials.""" """Test with invalid credentials."""
mock_http_component(hass)
@asyncio.coroutine @asyncio.coroutine
def get_session(*args, **kwargs): def get_session(*args, **kwargs):
"""Return the test session.""" """Return the test session."""
@ -34,8 +38,15 @@ def test_invalid_credentials(mock_create_session, hass):
@patch('aioautomatic.Client.create_session_from_password') @patch('aioautomatic.Client.create_session_from_password')
def test_valid_credentials(mock_create_session, hass): @patch('aioautomatic.Client.ws_connect')
@patch('json.dump')
@patch('os.makedirs')
@patch('homeassistant.components.device_tracker.automatic.open', create=True)
def test_valid_credentials(mock_open, mock_os_makedirs, mock_json_dump,
mock_ws_connect, mock_create_session, hass):
"""Test with valid credentials.""" """Test with valid credentials."""
mock_http_component(hass)
session = MagicMock() session = MagicMock()
vehicle = MagicMock() vehicle = MagicMock()
trip = MagicMock() trip = MagicMock()
@ -66,13 +77,21 @@ def test_valid_credentials(mock_create_session, hass):
return [trip] return [trip]
mock_create_session.side_effect = get_session mock_create_session.side_effect = get_session
session.ws_connect = MagicMock()
session.get_vehicles.side_effect = get_vehicles session.get_vehicles.side_effect = get_vehicles
session.get_trips.side_effect = get_trips session.get_trips.side_effect = get_trips
session.refresh_token = 'mock_refresh_token'
@asyncio.coroutine
def ws_connect():
return asyncio.Future(loop=hass.loop)
mock_ws_connect.side_effect = ws_connect
config = { config = {
'platform': 'automatic', 'platform': 'automatic',
'username': 'bad_username', 'username': 'good_username',
'password': 'bad_password', 'password': 'good_password',
'client_id': 'client_id', 'client_id': 'client_id',
'secret': 'client_secret', 'secret': 'client_secret',
'devices': None, 'devices': None,
@ -80,6 +99,8 @@ def test_valid_credentials(mock_create_session, hass):
result = hass.loop.run_until_complete( result = hass.loop.run_until_complete(
async_setup_scanner(hass, config, mock_see)) async_setup_scanner(hass, config, mock_see))
hass.async_block_till_done()
assert result assert result
assert mock_see.called assert mock_see.called
assert len(mock_see.mock_calls) == 2 assert len(mock_see.mock_calls) == 2
@ -89,3 +110,9 @@ def test_valid_credentials(mock_create_session, hass):
assert mock_see.mock_calls[0][2]['attributes'] == {'fuel_level': 45.6} assert mock_see.mock_calls[0][2]['attributes'] == {'fuel_level': 45.6}
assert mock_see.mock_calls[0][2]['gps'] == (45.567, 34.345) assert mock_see.mock_calls[0][2]['gps'] == (45.567, 34.345)
assert mock_see.mock_calls[0][2]['gps_accuracy'] == 5.6 assert mock_see.mock_calls[0][2]['gps_accuracy'] == 5.6
assert mock_json_dump.called
assert len(mock_json_dump.mock_calls) == 1
assert mock_json_dump.mock_calls[0][1][0] == {
'refresh_token': 'mock_refresh_token'
}

View file

@ -90,20 +90,20 @@ class TestConfigurator(unittest.TestCase):
request_id = configurator.request_config( request_id = configurator.request_config(
self.hass, "Test Request", lambda _: None) self.hass, "Test Request", lambda _: None)
error = "Oh no bad bad bad" error = "Oh no bad bad bad"
configurator.notify_errors(request_id, error) configurator.notify_errors(self.hass, request_id, error)
state = self.hass.states.all()[0] state = self.hass.states.all()[0]
self.assertEqual(error, state.attributes.get(configurator.ATTR_ERRORS)) self.assertEqual(error, state.attributes.get(configurator.ATTR_ERRORS))
def test_notify_errors_fail_silently_on_bad_request_id(self): def test_notify_errors_fail_silently_on_bad_request_id(self):
"""Test if notify errors fails silently with a bad request id.""" """Test if notify errors fails silently with a bad request id."""
configurator.notify_errors(2015, "Try this error") configurator.notify_errors(self.hass, 2015, "Try this error")
def test_request_done_works(self): def test_request_done_works(self):
"""Test if calling request done works.""" """Test if calling request done works."""
request_id = configurator.request_config( request_id = configurator.request_config(
self.hass, "Test Request", lambda _: None) self.hass, "Test Request", lambda _: None)
configurator.request_done(request_id) configurator.request_done(self.hass, request_id)
self.assertEqual(1, len(self.hass.states.all())) self.assertEqual(1, len(self.hass.states.all()))
self.hass.bus.fire(EVENT_TIME_CHANGED) self.hass.bus.fire(EVENT_TIME_CHANGED)
@ -112,4 +112,4 @@ class TestConfigurator(unittest.TestCase):
def test_request_done_fail_silently_on_bad_request_id(self): def test_request_done_fail_silently_on_bad_request_id(self):
"""Test that request_done fails silently with a bad request id.""" """Test that request_done fails silently with a bad request id."""
configurator.request_done(2016) configurator.request_done(self.hass, 2016)