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 changespull/10788/head
parent
282e37ef14
commit
6df5e712f7
|
@ -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
|
||||
|
|
|
@ -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 |
|
@ -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."""
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue