core/homeassistant/components/tellduslive.py

363 lines
12 KiB
Python
Raw Normal View History

"""
2016-03-07 17:49:31 +00:00
Support for Telldus Live.
2016-02-02 23:35:53 +00:00
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/tellduslive/
"""
from datetime import datetime, timedelta
import logging
2016-09-11 07:22:49 +00:00
import voluptuous as vol
from homeassistant.const import (
ATTR_BATTERY_LEVEL, DEVICE_DEFAULT_NAME,
CONF_TOKEN, CONF_HOST,
EVENT_HOMEASSISTANT_START)
2016-09-11 07:22:49 +00:00
from homeassistant.helpers import discovery
from homeassistant.components.discovery import SERVICE_TELLDUSLIVE
2016-09-11 07:22:49 +00:00
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
APPLICATION_NAME = 'Home Assistant'
2016-09-11 07:22:49 +00:00
DOMAIN = 'tellduslive'
2016-02-03 21:31:28 +00:00
REQUIREMENTS = ['tellduslive==0.10.4']
2016-02-03 21:31:28 +00:00
_LOGGER = logging.getLogger(__name__)
TELLLDUS_CONFIG_FILE = 'tellduslive.conf'
KEY_CONFIG = 'tellduslive_config'
2016-09-11 07:22:49 +00:00
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)
2016-09-11 07:22:49 +00:00
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
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)))
2016-09-11 07:22:49 +00:00
}),
}, extra=vol.ALLOW_EXTRA)
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})
"""
2016-09-11 07:22:49 +00:00
def setup(hass, config, session=None):
"""Set up the Telldus Live component."""
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))
return True
else:
_LOGGER.info('Tellstick discovered, awaiting discovery callback')
return True
if not session.is_authorized:
2016-09-11 07:22:49 +00:00
_LOGGER.error(
'Authentication Error')
2016-09-11 07:22:49 +00:00
return False
client = TelldusLiveClient(hass, config, session)
hass.data[DOMAIN] = client
if session:
client.update()
else:
hass.bus.listen(EVENT_HOMEASSISTANT_START, client.update)
2016-09-11 07:22:49 +00:00
return True
2016-02-03 21:31:28 +00:00
class TelldusLiveClient:
2016-03-08 16:55:57 +00:00
"""Get the latest data and update the states."""
def __init__(self, hass, config, session):
2016-03-08 16:55:57 +00:00
"""Initialize the Tellus data object."""
self.entities = []
2016-02-03 21:31:28 +00:00
self._hass = hass
self._config = config
self._interval = config.get(DOMAIN, {}).get(
CONF_UPDATE_INTERVAL, DEFAULT_UPDATE_INTERVAL)
_LOGGER.debug('Update interval %s', self._interval)
self._client = session
2016-02-03 21:31:28 +00:00
def update(self, *args):
"""Periodically poll the servers for current state."""
_LOGGER.debug('Updating')
2016-02-16 16:36:09 +00:00
try:
self._sync()
finally:
track_point_in_utc_time(
self._hass, self.update, utcnow() + self._interval)
def _sync(self):
"""Update local list of devices."""
if not self._client.update():
_LOGGER.warning('Failed request')
def identify_device(device):
"""Find out what type of HA component to create."""
from tellduslive import (DIM, UP, TURNON)
if device.methods & DIM:
return 'light'
if device.methods & UP:
return 'cover'
if device.methods & TURNON:
return 'switch'
if device.methods == 0:
return 'binary_sensor'
_LOGGER.warning(
"Unidentified device type (methods: %d)", device.methods)
return 'switch'
def discover(device_id, component):
"""Discover the component."""
discovery.load_platform(
self._hass, component, DOMAIN, [device_id], self._config)
known_ids = {entity.device_id for entity in self.entities}
for device in self._client.devices:
if device.device_id in known_ids:
continue
if device.is_sensor:
2017-01-31 19:08:11 +00:00
for item in device.items:
discover((device.device_id, item.name, item.scale),
'sensor')
else:
discover(device.device_id,
identify_device(device))
for entity in self.entities:
entity.changed()
def device(self, device_id):
"""Return device representation."""
2017-01-31 19:08:11 +00:00
return self._client.device(device_id)
def is_available(self, device_id):
"""Return device availability."""
return device_id in self._client.device_ids
class TelldusLiveEntity(Entity):
"""Base class for all Telldus Live entities."""
def __init__(self, hass, device_id):
"""Initialize the entity."""
self._id = device_id
self._client = hass.data[DOMAIN]
self._client.entities.append(self)
self._name = self.device.name
_LOGGER.debug('Created device %s', self)
def changed(self):
"""Return the property of the device might have changed."""
if self.device.name:
self._name = self.device.name
self.schedule_update_ha_state()
@property
def device_id(self):
"""Return the id of the device."""
return self._id
@property
def device(self):
"""Return the representation of the device."""
return self._client.device(self.device_id)
@property
def _state(self):
"""Return the state of the device."""
return self.device.state
@property
def should_poll(self):
"""Return the polling state."""
return False
@property
def assumed_state(self):
"""Return true if unable to access real state of entity."""
return True
@property
def name(self):
"""Return name of device."""
return self._name or DEVICE_DEFAULT_NAME
@property
def available(self):
"""Return true if device is not offline."""
return self._client.is_available(self.device_id)
@property
def device_state_attributes(self):
"""Return the state attributes."""
attrs = {}
if self._battery_level:
attrs[ATTR_BATTERY_LEVEL] = self._battery_level
if self._last_updated:
attrs[ATTR_LAST_UPDATED] = self._last_updated
return attrs
@property
def _battery_level(self):
"""Return the battery level of a device."""
from tellduslive import (BATTERY_LOW,
BATTERY_UNKNOWN,
BATTERY_OK)
if self.device.battery == BATTERY_LOW:
return 1
if self.device.battery == BATTERY_UNKNOWN:
return None
if self.device.battery == BATTERY_OK:
return 100
return self.device.battery # Percentage
@property
def _last_updated(self):
"""Return the last update of a device."""
return str(datetime.fromtimestamp(self.device.lastUpdated)) \
if self.device.lastUpdated else None