Skip to content

Commit

Permalink
Tellduslive update with support for auto config and Local api (home-a…
Browse files Browse the repository at this point in the history
…ssistant#10435)

* Add support for local api connection found in TellStick Znet Lite/Pro.
Added auto discovery support for all TelldusLive devices,
changed authentication method. Breaking change!
Upgraded tellduslive dependency
Update CODEOWNERS.

* Close any open configurator when configuration done

* Add support for Telldus Local API via config (#2)

* Updated dependency (addresses issue raised by @rasmusbe in home-assistant#10435 (comment))

* Fix requested changes
  • Loading branch information
fredrike authored and balloob committed Nov 28, 2017
1 parent 282e37e commit 6df5e71
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 39 deletions.
2 changes: 2 additions & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ homeassistant/components/tahoma.py @philklei
homeassistant/components/*/tahoma.py @philklei
homeassistant/components/tesla.py @zabuldon
homeassistant/components/*/tesla.py @zabuldon
homeassistant/components/tellduslive.py @molobrakos @fredrike
homeassistant/components/*/tellduslive.py @molobrakos @fredrike
homeassistant/components/*/tradfri.py @ggravlingen
homeassistant/components/*/xiaomi_aqara.py @danielhiversen @syssi
homeassistant/components/*/xiaomi_miio.py @rytilahti @syssi
2 changes: 2 additions & 0 deletions homeassistant/components/discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
SERVICE_APPLE_TV = 'apple_tv'
SERVICE_WINK = 'wink'
SERVICE_XIAOMI_GW = 'xiaomi_gw'
SERVICE_TELLDUSLIVE = 'tellstick'

SERVICE_HANDLERS = {
SERVICE_HASS_IOS_APP: ('ios', None),
Expand All @@ -46,6 +47,7 @@
SERVICE_APPLE_TV: ('apple_tv', None),
SERVICE_WINK: ('wink', None),
SERVICE_XIAOMI_GW: ('xiaomi_aqara', None),
SERVICE_TELLDUSLIVE: ('tellduslive', None),
'philips_hue': ('light', 'hue'),
'google_cast': ('media_player', 'cast'),
'panasonic_viera': ('media_player', 'panasonic_viera'),
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
202 changes: 164 additions & 38 deletions homeassistant/components/tellduslive.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,35 +8,41 @@
import logging

from homeassistant.const import (
ATTR_BATTERY_LEVEL, DEVICE_DEFAULT_NAME, EVENT_HOMEASSISTANT_START)
ATTR_BATTERY_LEVEL, DEVICE_DEFAULT_NAME,
CONF_TOKEN, CONF_HOST,
EVENT_HOMEASSISTANT_START)
from homeassistant.helpers import discovery
from homeassistant.components.discovery import SERVICE_TELLDUSLIVE
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import track_point_in_utc_time
from homeassistant.util.dt import utcnow
from homeassistant.util.json import load_json, save_json
import voluptuous as vol

APPLICATION_NAME = 'Home Assistant'

DOMAIN = 'tellduslive'

REQUIREMENTS = ['tellduslive==0.3.4']
REQUIREMENTS = ['tellduslive==0.10.3']

_LOGGER = logging.getLogger(__name__)

CONF_PUBLIC_KEY = 'public_key'
CONF_PRIVATE_KEY = 'private_key'
CONF_TOKEN = 'token'
TELLLDUS_CONFIG_FILE = 'tellduslive.conf'
KEY_CONFIG = 'tellduslive_config'

CONF_TOKEN_SECRET = 'token_secret'
CONF_UPDATE_INTERVAL = 'update_interval'

PUBLIC_KEY = 'THUPUNECH5YEQA3RE6UYUPRUZ2DUGUGA'
NOT_SO_PRIVATE_KEY = 'PHES7U2RADREWAFEBUSTUBAWRASWUTUS'

MIN_UPDATE_INTERVAL = timedelta(seconds=5)
DEFAULT_UPDATE_INTERVAL = timedelta(minutes=1)

CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_PUBLIC_KEY): cv.string,
vol.Required(CONF_PRIVATE_KEY): cv.string,
vol.Required(CONF_TOKEN): cv.string,
vol.Required(CONF_TOKEN_SECRET): cv.string,
vol.Optional(CONF_HOST): cv.string,
vol.Optional(CONF_UPDATE_INTERVAL, default=DEFAULT_UPDATE_INTERVAL): (
vol.All(cv.time_period, vol.Clamp(min=MIN_UPDATE_INTERVAL)))
}),
Expand All @@ -45,58 +51,178 @@

ATTR_LAST_UPDATED = 'time_last_updated'

CONFIG_INSTRUCTIONS = """
To link your TelldusLive account:
1. Click the link below
2. Login to Telldus Live
3. Authorize {app_name}.
4. Click the Confirm button.
[Link TelldusLive account]({auth_url})
"""


def setup(hass, config):
def setup(hass, config, session=None):
"""Set up the Telldus Live component."""
client = TelldusLiveClient(hass, config)
from tellduslive import Session, supports_local_api
config_filename = hass.config.path(TELLLDUS_CONFIG_FILE)
conf = load_json(config_filename)

def request_configuration(host=None):
"""Request TelldusLive authorization."""
configurator = hass.components.configurator
hass.data.setdefault(KEY_CONFIG, {})
data_key = host or DOMAIN

# Configuration already in progress
if hass.data[KEY_CONFIG].get(data_key):
return

_LOGGER.info('Configuring TelldusLive %s',
'local client: {}'.format(host) if host else
'cloud service')

session = Session(public_key=PUBLIC_KEY,
private_key=NOT_SO_PRIVATE_KEY,
host=host,
application=APPLICATION_NAME)

auth_url = session.authorize_url
if not auth_url:
_LOGGER.warning('Failed to retrieve authorization URL')
return

_LOGGER.debug('Got authorization URL %s', auth_url)

def configuration_callback(callback_data):
"""Handle the submitted configuration."""
session.authorize()
res = setup(hass, config, session)
if not res:
configurator.notify_errors(
hass.data[KEY_CONFIG].get(data_key),
'Unable to connect.')
return

conf.update(
{host: {CONF_HOST: host,
CONF_TOKEN: session.access_token}} if host else
{DOMAIN: {CONF_TOKEN: session.access_token,
CONF_TOKEN_SECRET: session.access_token_secret}})
save_json(config_filename, conf)
# Close all open configurators: for now, we only support one
# tellstick device, and configuration via either cloud service
# or via local API, not both at the same time
for instance in hass.data[KEY_CONFIG].values():
configurator.request_done(instance)

hass.data[KEY_CONFIG][data_key] = \
configurator.request_config(
'TelldusLive ({})'.format(
'LocalAPI' if host
else 'Cloud service'),
configuration_callback,
description=CONFIG_INSTRUCTIONS.format(
app_name=APPLICATION_NAME,
auth_url=auth_url),
submit_caption='Confirm',
entity_picture='/static/images/logo_tellduslive.png',
)

def tellstick_discovered(service, info):
"""Run when a Tellstick is discovered."""
_LOGGER.info('Discovered tellstick device')

if DOMAIN in hass.data:
_LOGGER.debug('Tellstick already configured')
return

host, device = info[:2]

if not supports_local_api(device):
_LOGGER.debug('Tellstick does not support local API')
# Configure the cloud service
hass.async_add_job(request_configuration)
return

_LOGGER.debug('Tellstick does support local API')

# Ignore any known devices
if conf and host in conf:
_LOGGER.debug('Discovered already known device: %s', host)
return

# Offer configuration of both live and local API
request_configuration()
request_configuration(host)

discovery.listen(hass, SERVICE_TELLDUSLIVE, tellstick_discovered)

if session:
_LOGGER.debug('Continuing setup configured by configurator')
elif conf and CONF_HOST in next(iter(conf.values())):
# For now, only one local device is supported
_LOGGER.debug('Using Local API pre-configured by configurator')
session = Session(**next(iter(conf.values())))
elif DOMAIN in conf:
_LOGGER.debug('Using TelldusLive cloud service '
'pre-configured by configurator')
session = Session(PUBLIC_KEY, NOT_SO_PRIVATE_KEY,
application=APPLICATION_NAME, **conf[DOMAIN])
elif config.get(DOMAIN):
_LOGGER.info('Found entry in configuration.yaml. '
'Requesting TelldusLive cloud service configuration')
request_configuration()

if CONF_HOST in config.get(DOMAIN, {}):
_LOGGER.info('Found TelldusLive host entry in configuration.yaml. '
'Requesting Telldus Local API configuration')
request_configuration(config.get(DOMAIN).get(CONF_HOST))

if not client.validate_session():
return True
else:
_LOGGER.info('Tellstick discovered, awaiting discovery callback')
return True

if not session.is_authorized:
_LOGGER.error(
"Authentication Error: Please make sure you have configured your "
"keys that can be acquired from "
"https://api.telldus.com/keys/index")
'Authentication Error')
return False

client = TelldusLiveClient(hass, config, session)

hass.data[DOMAIN] = client

hass.bus.listen(EVENT_HOMEASSISTANT_START, client.update)
if session:
client.update()
else:
hass.bus.listen(EVENT_HOMEASSISTANT_START, client.update)

return True


class TelldusLiveClient(object):
"""Get the latest data and update the states."""

def __init__(self, hass, config):
def __init__(self, hass, config, session):
"""Initialize the Tellus data object."""
from tellduslive import Client

public_key = config[DOMAIN].get(CONF_PUBLIC_KEY)
private_key = config[DOMAIN].get(CONF_PRIVATE_KEY)
token = config[DOMAIN].get(CONF_TOKEN)
token_secret = config[DOMAIN].get(CONF_TOKEN_SECRET)

self.entities = []

self._hass = hass
self._config = config

self._interval = config[DOMAIN].get(CONF_UPDATE_INTERVAL)
self._interval = config.get(DOMAIN, {}).get(
CONF_UPDATE_INTERVAL, DEFAULT_UPDATE_INTERVAL)
_LOGGER.debug('Update interval %s', self._interval)

self._client = Client(public_key,
private_key,
token,
token_secret)

def validate_session(self):
"""Make a request to see if the session is valid."""
response = self._client.request_user()
return response and 'email' in response
self._client = session

def update(self, *args):
"""Periodically poll the servers for current state."""
_LOGGER.debug("Updating")
_LOGGER.debug('Updating')
try:
self._sync()
finally:
Expand All @@ -106,7 +232,7 @@ def update(self, *args):
def _sync(self):
"""Update local list of devices."""
if not self._client.update():
_LOGGER.warning("Failed request")
_LOGGER.warning('Failed request')

def identify_device(device):
"""Find out what type of HA component to create."""
Expand Down Expand Up @@ -161,7 +287,7 @@ def __init__(self, hass, device_id):
self._client = hass.data[DOMAIN]
self._client.entities.append(self)
self._name = self.device.name
_LOGGER.debug("Created device %s", self)
_LOGGER.debug('Created device %s', self)

def changed(self):
"""Return the property of the device might have changed."""
Expand Down
2 changes: 1 addition & 1 deletion requirements_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1064,7 +1064,7 @@ tellcore-net==0.1
tellcore-py==1.1.2

# homeassistant.components.tellduslive
tellduslive==0.3.4
tellduslive==0.10.3

# homeassistant.components.sensor.temper
temperusb==1.5.3
Expand Down

0 comments on commit 6df5e71

Please sign in to comment.