diff --git a/homeassistant/components/frontend/www_static/images/config_wink.png b/homeassistant/components/frontend/www_static/images/config_wink.png
new file mode 100644
index 00000000000..6b91f8cb58e
Binary files /dev/null and b/homeassistant/components/frontend/www_static/images/config_wink.png differ
diff --git a/homeassistant/components/wink.py b/homeassistant/components/wink.py
index 7024291e7fe..58a6f51b67b 100644
--- a/homeassistant/components/wink.py
+++ b/homeassistant/components/wink.py
@@ -7,15 +7,21 @@ https://home-assistant.io/components/wink/
import logging
import time
import json
+import os
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
from homeassistant.helpers.event import track_time_interval
from homeassistant.const import (
- CONF_ACCESS_TOKEN, ATTR_BATTERY_LEVEL, CONF_EMAIL, CONF_PASSWORD,
- EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
+ ATTR_BATTERY_LEVEL, CONF_EMAIL, CONF_PASSWORD,
+ EVENT_HOMEASSISTANT_START,
+ EVENT_HOMEASSISTANT_STOP, __version__)
from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv
@@ -23,11 +29,10 @@ REQUIREMENTS = ['python-wink==1.3.1', 'pubnubsub-handler==1.0.2']
_LOGGER = logging.getLogger(__name__)
-CHANNELS = []
-
DOMAIN = 'wink'
SUBSCRIPTION_HANDLER = None
+
CONF_CLIENT_ID = 'client_id'
CONF_CLIENT_SECRET = 'client_secret'
CONF_USER_AGENT = 'user_agent'
@@ -37,8 +42,24 @@ CONF_DEFINED_BOTH_MSG = 'Remove access token to use oath2.'
CONF_MISSING_OATH_MSG = 'Missing oath2 credentials.'
CONF_TOKEN_URL = "https://winkbearertoken.appspot.com/token"
+ATTR_ACCESS_TOKEN = 'access_token'
+ATTR_REFRESH_TOKEN = 'refresh_token'
+ATTR_CLIENT_ID = 'client_id'
+ATTR_CLIENT_SECRET = 'client_secret'
+
+WINK_AUTH_CALLBACK_PATH = '/auth/wink/callback'
+WINK_AUTH_START = '/auth/wink'
+WINK_CONFIG_FILE = '.wink.conf'
+USER_AGENT = "Manufacturer/Home-Assistant%s python/3 Wink/3" % (__version__)
+
+DEFAULT_CONFIG = {
+ 'client_id': 'CLIENT_ID_HERE',
+ 'client_secret': 'CLIENT_SECRET_HERE'
+}
+
SERVICE_ADD_NEW_DEVICES = 'add_new_devices'
SERVICE_REFRESH_STATES = 'refresh_state_from_wink'
+SERVICE_KEEP_ALIVE = 'keep_pubnub_updates_flowing'
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
@@ -52,11 +73,6 @@ CONFIG_SCHEMA = vol.Schema({
msg=CONF_MISSING_OATH_MSG): cv.string,
vol.Exclusive(CONF_EMAIL, CONF_OATH,
msg=CONF_DEFINED_BOTH_MSG): cv.string,
- vol.Exclusive(CONF_ACCESS_TOKEN, CONF_OATH,
- msg=CONF_DEFINED_BOTH_MSG): cv.string,
- vol.Exclusive(CONF_ACCESS_TOKEN, CONF_APPSPOT,
- msg=CONF_DEFINED_BOTH_MSG): cv.string,
- vol.Optional(CONF_USER_AGENT, default=None): cv.string
})
}, extra=vol.ALLOW_EXTRA)
@@ -66,30 +82,118 @@ WINK_COMPONENTS = [
]
+def _write_config_file(file_path, config):
+ try:
+ with open(file_path, 'w') as conf_file:
+ conf_file.write(json.dumps(config, sort_keys=True, indent=4))
+ except IOError as error:
+ _LOGGER.error("Saving config file failed: %s", error)
+ raise IOError("Saving Wink config file failed")
+ return config
+
+
+def _read_config_file(file_path):
+ try:
+ with open(file_path, 'r') as conf_file:
+ return json.loads(conf_file.read())
+ except IOError as error:
+ _LOGGER.error("Reading config file failed: %s", error)
+ raise IOError("Reading Wink config file failed")
+
+
+def _request_app_setup(hass, config):
+ """Assist user with configuring the Wink dev application."""
+ hass.data['configurator'] = True
+ configurator = get_component('configurator')
+
+ # pylint: disable=unused-argument
+ def wink_configuration_callback(callback_data):
+ """Handle configuration updates."""
+ _config_path = hass.config.path(WINK_CONFIG_FILE)
+ if not os.path.isfile(_config_path):
+ setup(hass, config)
+ return
+
+ client_id = callback_data.get('client_id')
+ client_secret = callback_data.get('client_secret')
+ if None not in (client_id, client_secret):
+ _write_config_file(_config_path,
+ {ATTR_CLIENT_ID: client_id,
+ ATTR_CLIENT_SECRET: client_secret})
+ setup(hass, config)
+ return
+ else:
+ error_msg = ("Your input was invalid. Please try again.")
+ _configurator = hass.data[DOMAIN]['configuring'][DOMAIN]
+ configurator.notify_errors(_configurator, error_msg)
+
+ start_url = "{}{}".format(hass.config.api.base_url,
+ WINK_AUTH_CALLBACK_PATH)
+
+ description = """Please create a Wink developer app at
+ https://developer.wink.com.
+ Add a Redirect URI of {}.
+ They will provide you a Client ID and secret
+ after reviewing your request.
+ (This can take several days).
+ """.format(start_url)
+
+ hass.data[DOMAIN]['configuring'][DOMAIN] = configurator.request_config(
+ hass, 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'},
+ {'id': 'client_secret',
+ 'name': 'Client secret',
+ 'type': 'string'}]
+ )
+
+
+def _request_oauth_completion(hass, config):
+ """Request user complete Wink OAuth2 flow."""
+ hass.data['configurator'] = True
+ configurator = get_component('configurator')
+ if DOMAIN in hass.data[DOMAIN]['configuring']:
+ configurator.notify_errors(
+ hass.data[DOMAIN]['configuring'][DOMAIN],
+ "Failed to register, please try again.")
+ return
+
+ # pylint: disable=unused-argument
+ def wink_configuration_callback(callback_data):
+ """Call setup again."""
+ setup(hass, config)
+
+ start_url = '{}{}'.format(hass.config.api.base_url, WINK_AUTH_START)
+
+ description = "Please authorize Wink by visiting {}".format(start_url)
+
+ hass.data[DOMAIN]['configuring'][DOMAIN] = configurator.request_config(
+ hass, DOMAIN, wink_configuration_callback,
+ description=description
+ )
+
+
def setup(hass, config):
"""Set up the Wink component."""
import pywink
- import requests
from pubnubsubhandler import PubNubSubscriptionHandler
- hass.data[DOMAIN] = {}
- hass.data[DOMAIN]['entities'] = []
- hass.data[DOMAIN]['unique_ids'] = []
- hass.data[DOMAIN]['entities'] = {}
-
- user_agent = config[DOMAIN].get(CONF_USER_AGENT)
-
- if user_agent:
- pywink.set_user_agent(user_agent)
-
- access_token = config[DOMAIN].get(CONF_ACCESS_TOKEN)
- client_id = config[DOMAIN].get('client_id')
+ if hass.data.get(DOMAIN) is None:
+ hass.data[DOMAIN] = {
+ 'unique_ids': [],
+ 'entities': {},
+ 'oauth': {},
+ 'configuring': {},
+ 'pubnub': None,
+ 'configurator': False
+ }
def _get_wink_token_from_web():
- email = hass.data[DOMAIN]["oath"]["email"]
- password = hass.data[DOMAIN]["oath"]["password"]
+ _email = hass.data[DOMAIN]["oauth"]["email"]
+ _password = hass.data[DOMAIN]["oauth"]["password"]
- payload = {'username': email, 'password': password}
+ payload = {'username': _email, 'password': _password}
token_response = requests.post(CONF_TOKEN_URL, data=payload)
try:
token = token_response.text.split(':')[1].split()[0].rstrip('