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:
parent
8fcec03adf
commit
19d1d748d4
25 changed files with 314 additions and 149 deletions
|
@ -91,7 +91,7 @@ def request_configuration(hass, config, atv, credentials):
|
|||
hass.async_add_job(configurator.request_done, instance)
|
||||
|
||||
instance = configurator.request_config(
|
||||
hass, 'Apple TV Authentication', configuration_callback,
|
||||
'Apple TV Authentication', configuration_callback,
|
||||
description='Please enter PIN code shown on screen.',
|
||||
submit_caption='Confirm',
|
||||
fields=[{'id': 'pin', 'name': 'PIN Code', 'type': 'password'}]
|
||||
|
|
|
@ -110,7 +110,7 @@ def request_configuration(hass, name, host, serialnumber):
|
|||
|
||||
title = '{} ({})'.format(name, host)
|
||||
request_id = configurator.request_config(
|
||||
hass, title, configuration_callback,
|
||||
title, configuration_callback,
|
||||
description='Functionality: ' + str(AXIS_INCLUDE),
|
||||
entity_picture="/static/images/logo_axis.png",
|
||||
link_name='Axis platform documentation',
|
||||
|
|
|
@ -7,19 +7,21 @@ A callback has to be provided to `request_config` which will be called when
|
|||
the user has submitted configuration information.
|
||||
"""
|
||||
import asyncio
|
||||
import functools as ft
|
||||
import logging
|
||||
|
||||
from homeassistant.core import callback as async_callback
|
||||
from homeassistant.const import EVENT_TIME_CHANGED, ATTR_FRIENDLY_NAME, \
|
||||
ATTR_ENTITY_PICTURE
|
||||
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
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
_REQUESTS = {}
|
||||
_KEY_INSTANCE = 'configurator'
|
||||
|
||||
DATA_REQUESTS = 'configurator_requests'
|
||||
|
||||
ATTR_CONFIGURE_ID = 'configure_id'
|
||||
ATTR_DESCRIPTION = 'description'
|
||||
ATTR_DESCRIPTION_IMAGE = 'description_image'
|
||||
|
@ -39,63 +41,89 @@ STATE_CONFIGURED = 'configured'
|
|||
|
||||
|
||||
@bind_hass
|
||||
def request_config(
|
||||
hass, name, callback, description=None, description_image=None,
|
||||
@async_callback
|
||||
def async_request_config(
|
||||
hass, name, callback=None, description=None, description_image=None,
|
||||
submit_caption=None, fields=None, link_name=None, link_url=None,
|
||||
entity_picture=None):
|
||||
"""Create a new request for configuration.
|
||||
|
||||
Will return an ID to be used for sequent calls.
|
||||
"""
|
||||
instance = run_callback_threadsafe(hass.loop,
|
||||
_async_get_instance,
|
||||
hass).result()
|
||||
instance = hass.data.get(_KEY_INSTANCE)
|
||||
|
||||
request_id = instance.request_config(
|
||||
if instance is None:
|
||||
instance = hass.data[_KEY_INSTANCE] = Configurator(hass)
|
||||
|
||||
request_id = instance.async_request_config(
|
||||
name, callback,
|
||||
description, description_image, submit_caption,
|
||||
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
|
||||
|
||||
|
||||
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."""
|
||||
try:
|
||||
_REQUESTS[request_id].notify_errors(request_id, error)
|
||||
hass.data[DATA_REQUESTS][request_id].async_notify_errors(
|
||||
request_id, error)
|
||||
except KeyError:
|
||||
# If request_id does not exist
|
||||
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."""
|
||||
try:
|
||||
_REQUESTS.pop(request_id).request_done(request_id)
|
||||
hass.data[DATA_REQUESTS].pop(request_id).async_request_done(request_id)
|
||||
except KeyError:
|
||||
# If request_id does not exist
|
||||
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
|
||||
def async_setup(hass, config):
|
||||
"""Set up the configurator component."""
|
||||
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):
|
||||
"""The class to keep track of current configuration requests."""
|
||||
|
||||
|
@ -105,14 +133,16 @@ class Configurator(object):
|
|||
self._cur_id = 0
|
||||
self._requests = {}
|
||||
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,
|
||||
description, description_image, submit_caption,
|
||||
fields, link_name, link_url, entity_picture):
|
||||
"""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:
|
||||
fields = []
|
||||
|
@ -138,11 +168,12 @@ class Configurator(object):
|
|||
] 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
|
||||
|
||||
def notify_errors(self, request_id, error):
|
||||
@async_callback
|
||||
def async_notify_errors(self, request_id, error):
|
||||
"""Update the state with errors."""
|
||||
if not self._validate_request_id(request_id):
|
||||
return
|
||||
|
@ -154,9 +185,10 @@ class Configurator(object):
|
|||
new_data = dict(state.attributes)
|
||||
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."""
|
||||
if not self._validate_request_id(request_id):
|
||||
return
|
||||
|
@ -167,15 +199,16 @@ class Configurator(object):
|
|||
# the result fo the service call (current design limitation).
|
||||
# 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.
|
||||
self.hass.states.set(entity_id, STATE_CONFIGURED)
|
||||
self.hass.states.async_set(entity_id, STATE_CONFIGURED)
|
||||
|
||||
def deferred_remove(event):
|
||||
"""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."""
|
||||
request_id = call.data.get(ATTR_CONFIGURE_ID)
|
||||
|
||||
|
@ -186,8 +219,8 @@ class Configurator(object):
|
|||
entity_id, fields, callback = self._requests[request_id]
|
||||
|
||||
# field validation goes here?
|
||||
|
||||
self.hass.async_add_job(callback, call.data.get(ATTR_FIELDS, {}))
|
||||
if callback:
|
||||
self.hass.async_add_job(callback, call.data.get(ATTR_FIELDS, {}))
|
||||
|
||||
def _generate_unique_id(self):
|
||||
"""Generate a unique configurator ID."""
|
||||
|
|
|
@ -6,28 +6,33 @@ https://home-assistant.io/components/device_tracker.automatic/
|
|||
"""
|
||||
import asyncio
|
||||
from datetime import timedelta
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
|
||||
from aiohttp import web
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.device_tracker import (
|
||||
PLATFORM_SCHEMA, ATTR_ATTRIBUTES, ATTR_DEV_ID, ATTR_HOST_NAME, ATTR_MAC,
|
||||
ATTR_GPS, ATTR_GPS_ACCURACY)
|
||||
from homeassistant.components.http import HomeAssistantView
|
||||
from homeassistant.const import (
|
||||
CONF_USERNAME, CONF_PASSWORD, EVENT_HOMEASSISTANT_STOP,
|
||||
EVENT_HOMEASSISTANT_START)
|
||||
CONF_USERNAME, CONF_PASSWORD, EVENT_HOMEASSISTANT_STOP)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
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__)
|
||||
|
||||
CONF_CLIENT_ID = 'client_id'
|
||||
CONF_SECRET = 'secret'
|
||||
CONF_DEVICES = 'devices'
|
||||
CONF_CURRENT_LOCATION = 'current_location'
|
||||
|
||||
DEFAULT_TIMEOUT = 5
|
||||
|
||||
|
@ -38,38 +43,76 @@ ATTR_FUEL_LEVEL = 'fuel_level'
|
|||
|
||||
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({
|
||||
vol.Required(CONF_CLIENT_ID): cv.string,
|
||||
vol.Required(CONF_SECRET): cv.string,
|
||||
vol.Required(CONF_USERNAME): cv.string,
|
||||
vol.Required(CONF_PASSWORD): cv.string,
|
||||
vol.Inclusive(CONF_USERNAME, 'auth'): 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(
|
||||
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
|
||||
def async_setup_scanner(hass, config, async_see, discovery_info=None):
|
||||
"""Validate the configuration and return an Automatic scanner."""
|
||||
import aioautomatic
|
||||
|
||||
hass.http.register_view(AutomaticAuthCallbackView())
|
||||
|
||||
scope = FULL_SCOPE if config.get(CONF_CURRENT_LOCATION) else DEFAULT_SCOPE
|
||||
|
||||
client = aioautomatic.Client(
|
||||
client_id=config[CONF_CLIENT_ID],
|
||||
client_secret=config[CONF_SECRET],
|
||||
client_session=async_get_clientsession(hass),
|
||||
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(
|
||||
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()
|
||||
for vehicle in vehicles:
|
||||
hass.async_add_job(data.load_vehicle(vehicle))
|
||||
except aioautomatic.exceptions.AutomaticError as err:
|
||||
_LOGGER.error(str(err))
|
||||
return False
|
||||
|
||||
@callback
|
||||
def ws_connect(event):
|
||||
"""Open the websocket connection."""
|
||||
hass.async_add_job(data.ws_connect())
|
||||
# Create a task instead of adding a tracking job, since this task will
|
||||
# run until the websocket connection is closed.
|
||||
hass.loop.create_task(data.ws_connect())
|
||||
|
||||
@callback
|
||||
def ws_close(event):
|
||||
"""Close the websocket connection."""
|
||||
hass.async_add_job(data.ws_close())
|
||||
if refresh_token is not None:
|
||||
try:
|
||||
session = yield from client.create_session_from_refresh_token(
|
||||
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)
|
||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, ws_close)
|
||||
if CONF_USERNAME in config:
|
||||
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
|
||||
|
||||
|
||||
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):
|
||||
"""A class representing an Automatic cloud service connection."""
|
||||
|
||||
|
@ -115,6 +237,8 @@ class AutomaticData(object):
|
|||
lambda name, event: self.hass.async_add_job(
|
||||
self.handle_event(name, event)))
|
||||
|
||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.ws_close())
|
||||
|
||||
@asyncio.coroutine
|
||||
def handle_event(self, name, event):
|
||||
"""Coroutine to update state for a realtime event."""
|
||||
|
|
|
@ -19,7 +19,6 @@ import homeassistant.helpers.config_validation as cv
|
|||
from homeassistant.util import slugify
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.util.location import distance
|
||||
from homeassistant.loader import get_component
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -209,7 +208,7 @@ class Icloud(DeviceScanner):
|
|||
|
||||
if self.accountname in _CONFIGURING:
|
||||
request_id = _CONFIGURING.pop(self.accountname)
|
||||
configurator = get_component('configurator')
|
||||
configurator = self.hass.components.configurator
|
||||
configurator.request_done(request_id)
|
||||
|
||||
# Trigger the next step immediately
|
||||
|
@ -217,7 +216,7 @@ class Icloud(DeviceScanner):
|
|||
|
||||
def icloud_need_trusted_device(self):
|
||||
"""We need a trusted device."""
|
||||
configurator = get_component('configurator')
|
||||
configurator = self.hass.components.configurator
|
||||
if self.accountname in _CONFIGURING:
|
||||
return
|
||||
|
||||
|
@ -229,7 +228,7 @@ class Icloud(DeviceScanner):
|
|||
devicesstring += "{}: {};".format(i, devicename)
|
||||
|
||||
_CONFIGURING[self.accountname] = configurator.request_config(
|
||||
self.hass, 'iCloud {}'.format(self.accountname),
|
||||
'iCloud {}'.format(self.accountname),
|
||||
self.icloud_trusted_device_callback,
|
||||
description=(
|
||||
'Please choose your trusted device by entering'
|
||||
|
@ -259,17 +258,17 @@ class Icloud(DeviceScanner):
|
|||
|
||||
if self.accountname in _CONFIGURING:
|
||||
request_id = _CONFIGURING.pop(self.accountname)
|
||||
configurator = get_component('configurator')
|
||||
configurator = self.hass.components.configurator
|
||||
configurator.request_done(request_id)
|
||||
|
||||
def icloud_need_verification_code(self):
|
||||
"""Return the verification code."""
|
||||
configurator = get_component('configurator')
|
||||
configurator = self.hass.components.configurator
|
||||
if self.accountname in _CONFIGURING:
|
||||
return
|
||||
|
||||
_CONFIGURING[self.accountname] = configurator.request_config(
|
||||
self.hass, 'iCloud {}'.format(self.accountname),
|
||||
'iCloud {}'.format(self.accountname),
|
||||
self.icloud_verification_callback,
|
||||
description=('Please enter the validation code:'),
|
||||
entity_picture="/static/images/config_icloud.png",
|
||||
|
|
|
@ -13,7 +13,6 @@ import voluptuous as vol
|
|||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers import discovery
|
||||
from homeassistant.const import CONF_API_KEY
|
||||
from homeassistant.loader import get_component
|
||||
from homeassistant.util import Throttle
|
||||
|
||||
REQUIREMENTS = ['python-ecobee-api==0.0.7']
|
||||
|
@ -41,7 +40,7 @@ CONFIG_SCHEMA = vol.Schema({
|
|||
|
||||
def request_configuration(network, hass, config):
|
||||
"""Request configuration steps from the user."""
|
||||
configurator = get_component('configurator')
|
||||
configurator = hass.components.configurator
|
||||
if 'ecobee' in _CONFIGURING:
|
||||
configurator.notify_errors(
|
||||
_CONFIGURING['ecobee'], "Failed to register, please try again.")
|
||||
|
@ -56,7 +55,7 @@ def request_configuration(network, hass, config):
|
|||
setup_ecobee(hass, network, config)
|
||||
|
||||
_CONFIGURING['ecobee'] = configurator.request_config(
|
||||
hass, "Ecobee", ecobee_configuration_callback,
|
||||
"Ecobee", ecobee_configuration_callback,
|
||||
description=(
|
||||
'Please authorize this app at https://www.ecobee.com/consumer'
|
||||
'portal/index.html with pin code: ' + network.pin),
|
||||
|
@ -73,7 +72,7 @@ def setup_ecobee(hass, network, config):
|
|||
return
|
||||
|
||||
if 'ecobee' in _CONFIGURING:
|
||||
configurator = get_component('configurator')
|
||||
configurator = hass.components.configurator
|
||||
configurator.request_done(_CONFIGURING.pop('ecobee'))
|
||||
|
||||
hold_temp = config[DOMAIN].get(CONF_HOLD_TEMP)
|
||||
|
|
|
@ -13,7 +13,6 @@ from homeassistant.components.fan import (
|
|||
ATTR_SPEED, SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH,
|
||||
SUPPORT_SET_SPEED, FanEntity)
|
||||
from homeassistant.helpers.entity import ToggleEntity
|
||||
from homeassistant.loader import get_component
|
||||
import homeassistant.util as util
|
||||
|
||||
_CONFIGURING = {}
|
||||
|
@ -57,7 +56,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
def request_configuration(device_id, insteonhub, model, hass,
|
||||
add_devices_callback):
|
||||
"""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
|
||||
if device_id in _CONFIGURING:
|
||||
|
@ -72,7 +71,7 @@ def request_configuration(device_id, insteonhub, model, hass,
|
|||
add_devices_callback)
|
||||
|
||||
_CONFIGURING[device_id] = configurator.request_config(
|
||||
hass, 'Insteon ' + model + ' addr: ' + device_id,
|
||||
'Insteon ' + model + ' addr: ' + device_id,
|
||||
insteon_fan_config_callback,
|
||||
description=('Enter a name for ' + model + ' Fan addr: ' + device_id),
|
||||
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."""
|
||||
if device_id in _CONFIGURING:
|
||||
request_id = _CONFIGURING.pop(device_id)
|
||||
configurator = get_component('configurator')
|
||||
configurator = hass.components.configurator
|
||||
configurator.request_done(request_id)
|
||||
_LOGGER.info("Device configuration done!")
|
||||
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 6.1 KiB |
|
@ -23,7 +23,6 @@ from homeassistant.components.light import (
|
|||
SUPPORT_XY_COLOR, Light, PLATFORM_SCHEMA)
|
||||
from homeassistant.config import load_yaml_config_file
|
||||
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
|
||||
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 host in _CONFIGURING:
|
||||
request_id = _CONFIGURING.pop(host)
|
||||
|
||||
configurator = get_component('configurator')
|
||||
|
||||
configurator = hass.components.configurator
|
||||
configurator.request_done(request_id)
|
||||
|
||||
lights = {}
|
||||
|
@ -268,7 +265,7 @@ def request_configuration(host, hass, add_devices, filename,
|
|||
allow_unreachable, allow_in_emulated_hue,
|
||||
allow_hue_groups):
|
||||
"""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
|
||||
if host in _CONFIGURING:
|
||||
|
@ -284,7 +281,7 @@ def request_configuration(host, hass, add_devices, filename,
|
|||
allow_in_emulated_hue, allow_hue_groups)
|
||||
|
||||
_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 "
|
||||
"with Home Assistant."),
|
||||
entity_picture="/static/images/logo_philips_hue.png",
|
||||
|
|
|
@ -11,7 +11,6 @@ from datetime import timedelta
|
|||
|
||||
from homeassistant.components.light import (
|
||||
ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light)
|
||||
from homeassistant.loader import get_component
|
||||
import homeassistant.util as util
|
||||
|
||||
_CONFIGURING = {}
|
||||
|
@ -54,7 +53,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
def request_configuration(device_id, insteonhub, model, hass,
|
||||
add_devices_callback):
|
||||
"""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
|
||||
if device_id in _CONFIGURING:
|
||||
|
@ -69,7 +68,7 @@ def request_configuration(device_id, insteonhub, model, hass,
|
|||
add_devices_callback)
|
||||
|
||||
_CONFIGURING[device_id] = configurator.request_config(
|
||||
hass, 'Insteon ' + model + ' addr: ' + device_id,
|
||||
'Insteon ' + model + ' addr: ' + device_id,
|
||||
insteon_light_config_callback,
|
||||
description=('Enter a name for ' + model + ' addr: ' + device_id),
|
||||
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."""
|
||||
if device_id in _CONFIGURING:
|
||||
request_id = _CONFIGURING.pop(device_id)
|
||||
configurator = get_component('configurator')
|
||||
configurator = hass.components.configurator
|
||||
configurator.request_done(request_id)
|
||||
_LOGGER.debug("Device configuration done")
|
||||
|
||||
|
|
|
@ -11,7 +11,6 @@ import re
|
|||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.loader import get_component
|
||||
from homeassistant.components.media_player import (
|
||||
SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, SUPPORT_TURN_ON,
|
||||
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 host in _CONFIGURING:
|
||||
request_id = _CONFIGURING.pop(host)
|
||||
configurator = get_component('configurator')
|
||||
configurator = hass.components.configurator
|
||||
configurator.request_done(request_id)
|
||||
_LOGGER.info("Discovery configuration done")
|
||||
|
||||
|
@ -150,7 +149,7 @@ def request_configuration(config, hass, add_devices):
|
|||
host = config.get(CONF_HOST)
|
||||
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
|
||||
if host in _CONFIGURING:
|
||||
|
@ -171,7 +170,7 @@ def request_configuration(config, hass, add_devices):
|
|||
request_configuration(config, hass, add_devices)
|
||||
|
||||
_CONFIGURING[host] = configurator.request_config(
|
||||
hass, name, bravia_configuration_callback,
|
||||
name, bravia_configuration_callback,
|
||||
description='Enter the Pin shown on your Sony Bravia TV.' +
|
||||
'If no Pin is shown, enter 0000 to let TV show you a Pin.',
|
||||
description_image="/static/images/smart-tv.png",
|
||||
|
|
|
@ -18,7 +18,6 @@ from homeassistant.components.media_player import (
|
|||
MediaPlayerDevice, PLATFORM_SCHEMA)
|
||||
from homeassistant.const import (
|
||||
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
|
||||
|
||||
REQUIREMENTS = ['websocket-client==0.37.0']
|
||||
|
@ -48,7 +47,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||
|
||||
def request_configuration(hass, config, url, add_devices_callback):
|
||||
"""Request configuration steps from the user."""
|
||||
configurator = get_component('configurator')
|
||||
configurator = hass.components.configurator
|
||||
if 'gpmdp' in _CONFIGURING:
|
||||
configurator.notify_errors(
|
||||
_CONFIGURING['gpmdp'], "Failed to register, please try again.")
|
||||
|
@ -96,7 +95,7 @@ def request_configuration(hass, config, url, add_devices_callback):
|
|||
break
|
||||
|
||||
_CONFIGURING['gpmdp'] = configurator.request_config(
|
||||
hass, DEFAULT_NAME, gpmdp_configuration_callback,
|
||||
DEFAULT_NAME, gpmdp_configuration_callback,
|
||||
description=(
|
||||
'Enter the pin that is displayed in the '
|
||||
'Google Play Music Desktop Player.'),
|
||||
|
@ -117,7 +116,7 @@ def setup_gpmdp(hass, config, code, add_devices):
|
|||
return
|
||||
|
||||
if 'gpmdp' in _CONFIGURING:
|
||||
configurator = get_component('configurator')
|
||||
configurator = hass.components.configurator
|
||||
configurator.request_done(_CONFIGURING.pop('gpmdp'))
|
||||
|
||||
add_devices([GPMDP(name, url, code)], True)
|
||||
|
|
|
@ -23,7 +23,6 @@ from homeassistant.const import (
|
|||
DEVICE_DEFAULT_NAME, STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING)
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.event import track_utc_time_change
|
||||
from homeassistant.loader import get_component
|
||||
|
||||
REQUIREMENTS = ['plexapi==2.0.2']
|
||||
|
||||
|
@ -143,7 +142,7 @@ def setup_plexserver(
|
|||
# If we came here and configuring this host, mark as done
|
||||
if host in _CONFIGURING:
|
||||
request_id = _CONFIGURING.pop(host)
|
||||
configurator = get_component('configurator')
|
||||
configurator = hass.components.configurator
|
||||
configurator.request_done(request_id)
|
||||
_LOGGER.info("Discovery configuration done")
|
||||
|
||||
|
@ -236,7 +235,7 @@ def setup_plexserver(
|
|||
|
||||
def request_configuration(host, hass, config, add_devices_callback):
|
||||
"""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
|
||||
if host in _CONFIGURING:
|
||||
configurator.notify_errors(_CONFIGURING[host],
|
||||
|
@ -254,7 +253,6 @@ def request_configuration(host, hass, config, add_devices_callback):
|
|||
)
|
||||
|
||||
_CONFIGURING[host] = configurator.request_config(
|
||||
hass,
|
||||
'Plex Media Server',
|
||||
plex_configuration_callback,
|
||||
description=('Enter the X-Plex-Token'),
|
||||
|
|
|
@ -10,7 +10,6 @@ from datetime import timedelta
|
|||
import voluptuous as vol
|
||||
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.loader import get_component
|
||||
from homeassistant.components.http import HomeAssistantView
|
||||
from homeassistant.components.media_player import (
|
||||
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):
|
||||
"""Request Spotify authorization."""
|
||||
configurator = get_component('configurator')
|
||||
configurator = hass.components.configurator
|
||||
hass.data[DOMAIN] = configurator.request_config(
|
||||
hass, DEFAULT_NAME, lambda _: None,
|
||||
DEFAULT_NAME, lambda _: None,
|
||||
link_name=CONFIGURATOR_LINK_NAME,
|
||||
link_url=oauth.get_authorize_url(),
|
||||
description=CONFIGURATOR_DESCRIPTION,
|
||||
|
@ -88,7 +87,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
request_configuration(hass, config, add_devices, oauth)
|
||||
return
|
||||
if hass.data.get(DOMAIN):
|
||||
configurator = get_component('configurator')
|
||||
configurator = hass.components.configurator
|
||||
configurator.request_done(hass.data.get(DOMAIN))
|
||||
del hass.data[DOMAIN]
|
||||
player = SpotifyMediaPlayer(oauth, config.get(CONF_NAME, DEFAULT_NAME),
|
||||
|
|
|
@ -22,7 +22,6 @@ from homeassistant.const import (
|
|||
CONF_HOST, CONF_MAC, CONF_CUSTOMIZE, STATE_OFF,
|
||||
STATE_PLAYING, STATE_PAUSED,
|
||||
STATE_UNKNOWN, CONF_NAME, CONF_FILENAME)
|
||||
from homeassistant.loader import get_component
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
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 client.is_registered() and host in _CONFIGURING:
|
||||
request_id = _CONFIGURING.pop(host)
|
||||
configurator = get_component('configurator')
|
||||
configurator = hass.components.configurator
|
||||
configurator.request_done(request_id)
|
||||
|
||||
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(
|
||||
host, mac, name, customize, config, hass, add_devices):
|
||||
"""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
|
||||
if host in _CONFIGURING:
|
||||
|
@ -137,7 +136,7 @@ def request_configuration(
|
|||
setup_tv(host, mac, name, customize, config, hass, add_devices)
|
||||
|
||||
_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_image='/static/images/config_webos.png',
|
||||
submit_caption='Start pairing request'
|
||||
|
|
|
@ -14,7 +14,6 @@ from homeassistant.helpers import discovery
|
|||
from homeassistant.const import (
|
||||
CONF_STRUCTURE, CONF_FILENAME, CONF_BINARY_SENSORS, CONF_SENSORS,
|
||||
CONF_MONITORED_CONDITIONS)
|
||||
from homeassistant.loader import get_component
|
||||
|
||||
REQUIREMENTS = ['python-nest==3.1.0']
|
||||
|
||||
|
@ -54,7 +53,7 @@ CONFIG_SCHEMA = vol.Schema({
|
|||
|
||||
def request_configuration(nest, hass, config):
|
||||
"""Request configuration steps from the user."""
|
||||
configurator = get_component('configurator')
|
||||
configurator = hass.components.configurator
|
||||
if 'nest' in _CONFIGURING:
|
||||
_LOGGER.debug("configurator failed")
|
||||
configurator.notify_errors(
|
||||
|
@ -68,7 +67,7 @@ def request_configuration(nest, hass, config):
|
|||
setup_nest(hass, nest, config, pin=pin)
|
||||
|
||||
_CONFIGURING['nest'] = configurator.request_config(
|
||||
hass, "Nest", nest_configuration_callback,
|
||||
"Nest", nest_configuration_callback,
|
||||
description=('To configure Nest, click Request Authorization below, '
|
||||
'log into your Nest account, '
|
||||
'and then enter the resulting PIN'),
|
||||
|
@ -92,7 +91,7 @@ def setup_nest(hass, nest, config, pin=None):
|
|||
|
||||
if 'nest' in _CONFIGURING:
|
||||
_LOGGER.debug("configuration done")
|
||||
configurator = get_component('configurator')
|
||||
configurator = hass.components.configurator
|
||||
configurator.request_done(_CONFIGURING.pop('nest'))
|
||||
|
||||
_LOGGER.debug("proceeding with setup")
|
||||
|
|
|
@ -17,7 +17,6 @@ from homeassistant.components.http import HomeAssistantView
|
|||
from homeassistant.components.sensor import PLATFORM_SCHEMA
|
||||
from homeassistant.const import ATTR_ATTRIBUTION
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.loader import get_component
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
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,
|
||||
discovery_info=None):
|
||||
"""Assist user with configuring the Fitbit dev application."""
|
||||
configurator = get_component('configurator')
|
||||
configurator = hass.components.configurator
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
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:
|
||||
error_msg = ("You didn't correctly modify fitbit.conf",
|
||||
" please try again")
|
||||
configurator.notify_errors(_CONFIGURING['fitbit'], error_msg)
|
||||
configurator.notify_errors(_CONFIGURING['fitbit'],
|
||||
error_msg)
|
||||
else:
|
||||
setup_platform(hass, config, add_devices, discovery_info)
|
||||
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."
|
||||
|
||||
_CONFIGURING['fitbit'] = configurator.request_config(
|
||||
hass, 'Fitbit', fitbit_configuration_callback,
|
||||
'Fitbit', fitbit_configuration_callback,
|
||||
description=description, submit_caption=submit,
|
||||
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):
|
||||
"""Request user complete Fitbit OAuth2 flow."""
|
||||
configurator = get_component('configurator')
|
||||
configurator = hass.components.configurator
|
||||
if "fitbit" in _CONFIGURING:
|
||||
configurator.notify_errors(
|
||||
_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)
|
||||
|
||||
_CONFIGURING['fitbit'] = configurator.request_config(
|
||||
hass, 'Fitbit', fitbit_configuration_callback,
|
||||
'Fitbit', fitbit_configuration_callback,
|
||||
description=description,
|
||||
submit_caption="I have authorized Fitbit."
|
||||
)
|
||||
|
@ -233,7 +233,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
return False
|
||||
|
||||
if "fitbit" in _CONFIGURING:
|
||||
get_component('configurator').request_done(_CONFIGURING.pop("fitbit"))
|
||||
hass.components.configurator.request_done(_CONFIGURING.pop("fitbit"))
|
||||
|
||||
import fitbit
|
||||
|
||||
|
|
|
@ -18,7 +18,6 @@ from homeassistant.const import (
|
|||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.util import Throttle
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.loader import get_component
|
||||
|
||||
REQUIREMENTS = ['https://github.com/jamespcole/home-assistant-nzb-clients/'
|
||||
'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):
|
||||
"""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
|
||||
if host in _CONFIGURING:
|
||||
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)
|
||||
|
||||
_CONFIGURING[host] = configurator.request_config(
|
||||
hass,
|
||||
DEFAULT_NAME,
|
||||
sabnzbd_configuration_callback,
|
||||
description=('Enter the API Key'),
|
||||
|
|
|
@ -10,7 +10,6 @@ import os
|
|||
from datetime import timedelta
|
||||
|
||||
from homeassistant.components.switch import SwitchDevice
|
||||
from homeassistant.loader import get_component
|
||||
import homeassistant.util as util
|
||||
|
||||
_CONFIGURING = {}
|
||||
|
@ -51,7 +50,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
def request_configuration(
|
||||
device_id, insteonhub, model, hass, add_devices_callback):
|
||||
"""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
|
||||
if device_id in _CONFIGURING:
|
||||
|
@ -66,7 +65,7 @@ def request_configuration(
|
|||
add_devices_callback)
|
||||
|
||||
_CONFIGURING[device_id] = configurator.request_config(
|
||||
hass, 'Insteon Switch ' + model + ' addr: ' + device_id,
|
||||
'Insteon Switch ' + model + ' addr: ' + device_id,
|
||||
insteon_switch_config_callback,
|
||||
description=('Enter a name for ' + model + ' addr: ' + device_id),
|
||||
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."""
|
||||
if device_id in _CONFIGURING:
|
||||
request_id = _CONFIGURING.pop(device_id)
|
||||
configurator = get_component('configurator')
|
||||
configurator = hass.components.configurator
|
||||
configurator.request_done(request_id)
|
||||
_LOGGER.info("Device configuration done")
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@ import voluptuous as vol
|
|||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers import discovery
|
||||
from homeassistant.const import CONF_HOST, CONF_API_KEY
|
||||
from homeassistant.loader import get_component
|
||||
from homeassistant.components.discovery import SERVICE_IKEA_TRADFRI
|
||||
|
||||
REQUIREMENTS = ['pytradfri==1.1']
|
||||
|
@ -41,7 +40,7 @@ _LOGGER = logging.getLogger(__name__)
|
|||
|
||||
def request_configuration(hass, config, host):
|
||||
"""Request configuration steps from the user."""
|
||||
configurator = get_component('configurator')
|
||||
configurator = hass.components.configurator
|
||||
hass.data.setdefault(KEY_CONFIG, {})
|
||||
instance = hass.data[KEY_CONFIG].get(host)
|
||||
|
||||
|
@ -70,7 +69,7 @@ def request_configuration(hass, config, host):
|
|||
hass.async_add_job(success)
|
||||
|
||||
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 '
|
||||
'your IKEA Trådfri Gateway.',
|
||||
submit_caption="Confirm",
|
||||
|
|
|
@ -13,7 +13,6 @@ from datetime import timedelta
|
|||
import voluptuous as vol
|
||||
import requests
|
||||
|
||||
from homeassistant.loader import get_component
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.components.http import HomeAssistantView
|
||||
from homeassistant.helpers import discovery
|
||||
|
@ -103,7 +102,7 @@ def _read_config_file(file_path):
|
|||
def _request_app_setup(hass, config):
|
||||
"""Assist user with configuring the Wink dev application."""
|
||||
hass.data[DOMAIN]['configurator'] = True
|
||||
configurator = get_component('configurator')
|
||||
configurator = hass.components.configurator
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def wink_configuration_callback(callback_data):
|
||||
|
@ -138,7 +137,7 @@ def _request_app_setup(hass, config):
|
|||
""".format(start_url)
|
||||
|
||||
hass.data[DOMAIN]['configuring'][DOMAIN] = configurator.request_config(
|
||||
hass, DOMAIN, wink_configuration_callback,
|
||||
DOMAIN, wink_configuration_callback,
|
||||
description=description, submit_caption="submit",
|
||||
description_image="/static/images/config_wink.png",
|
||||
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):
|
||||
"""Request user complete Wink OAuth2 flow."""
|
||||
hass.data[DOMAIN]['configurator'] = True
|
||||
configurator = get_component('configurator')
|
||||
configurator = hass.components.configurator
|
||||
if DOMAIN in hass.data[DOMAIN]['configuring']:
|
||||
configurator.notify_errors(
|
||||
hass.data[DOMAIN]['configuring'][DOMAIN],
|
||||
|
@ -168,7 +167,7 @@ def _request_oauth_completion(hass, config):
|
|||
description = "Please authorize Wink by visiting {}".format(start_url)
|
||||
|
||||
hass.data[DOMAIN]['configuring'][DOMAIN] = configurator.request_config(
|
||||
hass, DOMAIN, wink_configuration_callback,
|
||||
DOMAIN, wink_configuration_callback,
|
||||
description=description
|
||||
)
|
||||
|
||||
|
@ -248,7 +247,7 @@ def setup(hass, config):
|
|||
|
||||
if DOMAIN in hass.data[DOMAIN]['configuring']:
|
||||
_configurator = hass.data[DOMAIN]['configuring']
|
||||
get_component('configurator').request_done(_configurator.pop(
|
||||
hass.components.configurator.request_done(_configurator.pop(
|
||||
DOMAIN))
|
||||
|
||||
# Using oauth
|
||||
|
|
|
@ -39,7 +39,7 @@ SoCo==0.12
|
|||
TwitterAPI==2.4.6
|
||||
|
||||
# homeassistant.components.device_tracker.automatic
|
||||
aioautomatic==0.4.0
|
||||
aioautomatic==0.5.0
|
||||
|
||||
# homeassistant.components.sensor.dnsip
|
||||
aiodns==1.1.1
|
||||
|
|
|
@ -27,7 +27,7 @@ PyJWT==1.5.2
|
|||
SoCo==0.12
|
||||
|
||||
# homeassistant.components.device_tracker.automatic
|
||||
aioautomatic==0.4.0
|
||||
aioautomatic==0.5.0
|
||||
|
||||
# homeassistant.components.emulated_hue
|
||||
# homeassistant.components.http
|
||||
|
|
|
@ -7,12 +7,16 @@ import aioautomatic
|
|||
from homeassistant.components.device_tracker.automatic import (
|
||||
async_setup_scanner)
|
||||
|
||||
from tests.common import mock_http_component
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@patch('aioautomatic.Client.create_session_from_password')
|
||||
def test_invalid_credentials(mock_create_session, hass):
|
||||
"""Test with invalid credentials."""
|
||||
mock_http_component(hass)
|
||||
|
||||
@asyncio.coroutine
|
||||
def get_session(*args, **kwargs):
|
||||
"""Return the test session."""
|
||||
|
@ -34,8 +38,15 @@ def test_invalid_credentials(mock_create_session, hass):
|
|||
|
||||
|
||||
@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."""
|
||||
mock_http_component(hass)
|
||||
|
||||
session = MagicMock()
|
||||
vehicle = MagicMock()
|
||||
trip = MagicMock()
|
||||
|
@ -66,13 +77,21 @@ def test_valid_credentials(mock_create_session, hass):
|
|||
return [trip]
|
||||
|
||||
mock_create_session.side_effect = get_session
|
||||
session.ws_connect = MagicMock()
|
||||
session.get_vehicles.side_effect = get_vehicles
|
||||
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 = {
|
||||
'platform': 'automatic',
|
||||
'username': 'bad_username',
|
||||
'password': 'bad_password',
|
||||
'username': 'good_username',
|
||||
'password': 'good_password',
|
||||
'client_id': 'client_id',
|
||||
'secret': 'client_secret',
|
||||
'devices': None,
|
||||
|
@ -80,6 +99,8 @@ def test_valid_credentials(mock_create_session, hass):
|
|||
result = hass.loop.run_until_complete(
|
||||
async_setup_scanner(hass, config, mock_see))
|
||||
|
||||
hass.async_block_till_done()
|
||||
|
||||
assert result
|
||||
assert mock_see.called
|
||||
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]['gps'] == (45.567, 34.345)
|
||||
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'
|
||||
}
|
||||
|
|
|
@ -90,20 +90,20 @@ class TestConfigurator(unittest.TestCase):
|
|||
request_id = configurator.request_config(
|
||||
self.hass, "Test Request", lambda _: None)
|
||||
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]
|
||||
self.assertEqual(error, state.attributes.get(configurator.ATTR_ERRORS))
|
||||
|
||||
def test_notify_errors_fail_silently_on_bad_request_id(self):
|
||||
"""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):
|
||||
"""Test if calling request done works."""
|
||||
request_id = configurator.request_config(
|
||||
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.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):
|
||||
"""Test that request_done fails silently with a bad request id."""
|
||||
configurator.request_done(2016)
|
||||
configurator.request_done(self.hass, 2016)
|
||||
|
|
Loading…
Add table
Reference in a new issue