mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
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
This commit is contained in:
parent
282e37ef14
commit
6df5e712f7
@ -74,6 +74,8 @@ homeassistant/components/tahoma.py @philklei
|
|||||||
homeassistant/components/*/tahoma.py @philklei
|
homeassistant/components/*/tahoma.py @philklei
|
||||||
homeassistant/components/tesla.py @zabuldon
|
homeassistant/components/tesla.py @zabuldon
|
||||||
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/*/tradfri.py @ggravlingen
|
||||||
homeassistant/components/*/xiaomi_aqara.py @danielhiversen @syssi
|
homeassistant/components/*/xiaomi_aqara.py @danielhiversen @syssi
|
||||||
homeassistant/components/*/xiaomi_miio.py @rytilahti @syssi
|
homeassistant/components/*/xiaomi_miio.py @rytilahti @syssi
|
||||||
|
@ -35,6 +35,7 @@ SERVICE_AXIS = 'axis'
|
|||||||
SERVICE_APPLE_TV = 'apple_tv'
|
SERVICE_APPLE_TV = 'apple_tv'
|
||||||
SERVICE_WINK = 'wink'
|
SERVICE_WINK = 'wink'
|
||||||
SERVICE_XIAOMI_GW = 'xiaomi_gw'
|
SERVICE_XIAOMI_GW = 'xiaomi_gw'
|
||||||
|
SERVICE_TELLDUSLIVE = 'tellstick'
|
||||||
|
|
||||||
SERVICE_HANDLERS = {
|
SERVICE_HANDLERS = {
|
||||||
SERVICE_HASS_IOS_APP: ('ios', None),
|
SERVICE_HASS_IOS_APP: ('ios', None),
|
||||||
@ -46,6 +47,7 @@ SERVICE_HANDLERS = {
|
|||||||
SERVICE_APPLE_TV: ('apple_tv', None),
|
SERVICE_APPLE_TV: ('apple_tv', None),
|
||||||
SERVICE_WINK: ('wink', None),
|
SERVICE_WINK: ('wink', None),
|
||||||
SERVICE_XIAOMI_GW: ('xiaomi_aqara', None),
|
SERVICE_XIAOMI_GW: ('xiaomi_aqara', None),
|
||||||
|
SERVICE_TELLDUSLIVE: ('tellduslive', None),
|
||||||
'philips_hue': ('light', 'hue'),
|
'philips_hue': ('light', 'hue'),
|
||||||
'google_cast': ('media_player', 'cast'),
|
'google_cast': ('media_player', 'cast'),
|
||||||
'panasonic_viera': ('media_player', 'panasonic_viera'),
|
'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
|
import logging
|
||||||
|
|
||||||
from homeassistant.const import (
|
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.helpers import discovery
|
||||||
|
from homeassistant.components.discovery import SERVICE_TELLDUSLIVE
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.helpers.event import track_point_in_utc_time
|
from homeassistant.helpers.event import track_point_in_utc_time
|
||||||
from homeassistant.util.dt import utcnow
|
from homeassistant.util.dt import utcnow
|
||||||
|
from homeassistant.util.json import load_json, save_json
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
|
APPLICATION_NAME = 'Home Assistant'
|
||||||
|
|
||||||
DOMAIN = 'tellduslive'
|
DOMAIN = 'tellduslive'
|
||||||
|
|
||||||
REQUIREMENTS = ['tellduslive==0.3.4']
|
REQUIREMENTS = ['tellduslive==0.10.3']
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
CONF_PUBLIC_KEY = 'public_key'
|
TELLLDUS_CONFIG_FILE = 'tellduslive.conf'
|
||||||
CONF_PRIVATE_KEY = 'private_key'
|
KEY_CONFIG = 'tellduslive_config'
|
||||||
CONF_TOKEN = 'token'
|
|
||||||
CONF_TOKEN_SECRET = 'token_secret'
|
CONF_TOKEN_SECRET = 'token_secret'
|
||||||
CONF_UPDATE_INTERVAL = 'update_interval'
|
CONF_UPDATE_INTERVAL = 'update_interval'
|
||||||
|
|
||||||
|
PUBLIC_KEY = 'THUPUNECH5YEQA3RE6UYUPRUZ2DUGUGA'
|
||||||
|
NOT_SO_PRIVATE_KEY = 'PHES7U2RADREWAFEBUSTUBAWRASWUTUS'
|
||||||
|
|
||||||
MIN_UPDATE_INTERVAL = timedelta(seconds=5)
|
MIN_UPDATE_INTERVAL = timedelta(seconds=5)
|
||||||
DEFAULT_UPDATE_INTERVAL = timedelta(minutes=1)
|
DEFAULT_UPDATE_INTERVAL = timedelta(minutes=1)
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema({
|
CONFIG_SCHEMA = vol.Schema({
|
||||||
DOMAIN: vol.Schema({
|
DOMAIN: vol.Schema({
|
||||||
vol.Required(CONF_PUBLIC_KEY): cv.string,
|
vol.Optional(CONF_HOST): 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_UPDATE_INTERVAL, default=DEFAULT_UPDATE_INTERVAL): (
|
vol.Optional(CONF_UPDATE_INTERVAL, default=DEFAULT_UPDATE_INTERVAL): (
|
||||||
vol.All(cv.time_period, vol.Clamp(min=MIN_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'
|
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."""
|
"""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(
|
_LOGGER.error(
|
||||||
"Authentication Error: Please make sure you have configured your "
|
'Authentication Error')
|
||||||
"keys that can be acquired from "
|
|
||||||
"https://api.telldus.com/keys/index")
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
client = TelldusLiveClient(hass, config, session)
|
||||||
|
|
||||||
hass.data[DOMAIN] = client
|
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
|
return True
|
||||||
|
|
||||||
@ -67,36 +208,21 @@ def setup(hass, config):
|
|||||||
class TelldusLiveClient(object):
|
class TelldusLiveClient(object):
|
||||||
"""Get the latest data and update the states."""
|
"""Get the latest data and update the states."""
|
||||||
|
|
||||||
def __init__(self, hass, config):
|
def __init__(self, hass, config, session):
|
||||||
"""Initialize the Tellus data object."""
|
"""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.entities = []
|
||||||
|
|
||||||
self._hass = hass
|
self._hass = hass
|
||||||
self._config = config
|
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)
|
_LOGGER.debug('Update interval %s', self._interval)
|
||||||
|
self._client = session
|
||||||
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
|
|
||||||
|
|
||||||
def update(self, *args):
|
def update(self, *args):
|
||||||
"""Periodically poll the servers for current state."""
|
"""Periodically poll the servers for current state."""
|
||||||
_LOGGER.debug("Updating")
|
_LOGGER.debug('Updating')
|
||||||
try:
|
try:
|
||||||
self._sync()
|
self._sync()
|
||||||
finally:
|
finally:
|
||||||
@ -106,7 +232,7 @@ class TelldusLiveClient(object):
|
|||||||
def _sync(self):
|
def _sync(self):
|
||||||
"""Update local list of devices."""
|
"""Update local list of devices."""
|
||||||
if not self._client.update():
|
if not self._client.update():
|
||||||
_LOGGER.warning("Failed request")
|
_LOGGER.warning('Failed request')
|
||||||
|
|
||||||
def identify_device(device):
|
def identify_device(device):
|
||||||
"""Find out what type of HA component to create."""
|
"""Find out what type of HA component to create."""
|
||||||
@ -161,7 +287,7 @@ class TelldusLiveEntity(Entity):
|
|||||||
self._client = hass.data[DOMAIN]
|
self._client = hass.data[DOMAIN]
|
||||||
self._client.entities.append(self)
|
self._client.entities.append(self)
|
||||||
self._name = self.device.name
|
self._name = self.device.name
|
||||||
_LOGGER.debug("Created device %s", self)
|
_LOGGER.debug('Created device %s', self)
|
||||||
|
|
||||||
def changed(self):
|
def changed(self):
|
||||||
"""Return the property of the device might have changed."""
|
"""Return the property of the device might have changed."""
|
||||||
|
@ -1064,7 +1064,7 @@ tellcore-net==0.1
|
|||||||
tellcore-py==1.1.2
|
tellcore-py==1.1.2
|
||||||
|
|
||||||
# homeassistant.components.tellduslive
|
# homeassistant.components.tellduslive
|
||||||
tellduslive==0.3.4
|
tellduslive==0.10.3
|
||||||
|
|
||||||
# homeassistant.components.sensor.temper
|
# homeassistant.components.sensor.temper
|
||||||
temperusb==1.5.3
|
temperusb==1.5.3
|
||||||
|
Loading…
x
Reference in New Issue
Block a user