"""
Support for Wink hubs.
For more details about this component, please refer to the documentation at
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.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 (
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
REQUIREMENTS = ['python-wink==1.5.1', 'pubnubsub-handler==1.0.2']
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'wink'
SUBSCRIPTION_HANDLER = None
CONF_CLIENT_ID = 'client_id'
CONF_CLIENT_SECRET = 'client_secret'
CONF_USER_AGENT = 'user_agent'
CONF_OAUTH = 'oauth'
CONF_LOCAL_CONTROL = 'local_control'
CONF_APPSPOT = 'appspot'
CONF_MISSING_OAUTH_MSG = 'Missing oauth2 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({
vol.Inclusive(CONF_EMAIL, CONF_APPSPOT,
msg=CONF_MISSING_OAUTH_MSG): cv.string,
vol.Inclusive(CONF_PASSWORD, CONF_APPSPOT,
msg=CONF_MISSING_OAUTH_MSG): cv.string,
vol.Inclusive(CONF_CLIENT_ID, CONF_OAUTH,
msg=CONF_MISSING_OAUTH_MSG): cv.string,
vol.Inclusive(CONF_CLIENT_SECRET, CONF_OAUTH,
msg=CONF_MISSING_OAUTH_MSG): cv.string,
vol.Optional(CONF_LOCAL_CONTROL, default=False): cv.boolean
})
}, extra=vol.ALLOW_EXTRA)
WINK_COMPONENTS = [
'binary_sensor', 'sensor', 'light', 'switch', 'lock', 'cover', 'climate',
'fan', 'alarm_control_panel', 'scene'
]
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[DOMAIN]['configurator'] = True
configurator = hass.components.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(
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[DOMAIN]['configurator'] = True
configurator = hass.components.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(
DOMAIN, wink_configuration_callback,
description=description
)
def setup(hass, config):
"""Set up the Wink component."""
import pywink
from pubnubsubhandler import PubNubSubscriptionHandler
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]["oauth"]["email"]
_password = hass.data[DOMAIN]["oauth"]["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('