Tellduslive update with support for auto config and Local api (#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 https://github.com/home-assistant/home-assistant/pull/10435#issuecomment-344719714)

* Fix requested changes
pull/10788/head
Fredrik Erlandsson 2017-11-28 08:13:30 +01:00 committed by Paulus Schoutsen
parent 282e37ef14
commit 6df5e712f7
5 changed files with 169 additions and 39 deletions

View File

@ -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

View File

@ -35,6 +35,7 @@ SERVICE_AXIS = 'axis'
SERVICE_APPLE_TV = 'apple_tv'
SERVICE_WINK = 'wink'
SERVICE_XIAOMI_GW = 'xiaomi_gw'
SERVICE_TELLDUSLIVE = 'tellstick'
SERVICE_HANDLERS = {
SERVICE_HASS_IOS_APP: ('ios', None),
@ -46,6 +47,7 @@ SERVICE_HANDLERS = {
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'),

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -8,35 +8,41 @@ from datetime import datetime, timedelta
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)))
}),
@ -45,21 +51,156 @@ CONFIG_SCHEMA = vol.Schema({
ATTR_LAST_UPDATED = 'time_last_updated'
CONFIG_INSTRUCTIONS = """
To link your TelldusLive account:
def setup(hass, config):
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, 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)
if not client.validate_session():
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))
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
@ -67,36 +208,21 @@ def setup(hass, config):
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:
@ -106,7 +232,7 @@ class TelldusLiveClient(object):
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."""
@ -161,7 +287,7 @@ class TelldusLiveEntity(Entity):
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."""

View File

@ -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