2015-12-27 11:32:08 +00:00
|
|
|
"""
|
2016-03-07 17:49:31 +00:00
|
|
|
Support for Telldus Live.
|
2015-12-27 11:32:08 +00:00
|
|
|
|
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/
|
2015-12-27 11:32:08 +00:00
|
|
|
"""
|
2016-12-12 05:39:37 +00:00
|
|
|
from datetime import datetime, timedelta
|
2015-12-27 11:32:08 +00:00
|
|
|
import logging
|
2016-09-11 07:22:49 +00:00
|
|
|
|
2017-06-02 07:02:26 +00:00
|
|
|
from homeassistant.const import (
|
|
|
|
ATTR_BATTERY_LEVEL, DEVICE_DEFAULT_NAME, EVENT_HOMEASSISTANT_START)
|
2016-09-11 07:22:49 +00:00
|
|
|
from homeassistant.helpers import discovery
|
|
|
|
import homeassistant.helpers.config_validation as cv
|
2016-12-12 05:39:37 +00:00
|
|
|
from homeassistant.helpers.entity import Entity
|
|
|
|
from homeassistant.helpers.event import track_point_in_utc_time
|
|
|
|
from homeassistant.util.dt import utcnow
|
|
|
|
import voluptuous as vol
|
2015-12-27 11:32:08 +00:00
|
|
|
|
2016-09-11 07:22:49 +00:00
|
|
|
DOMAIN = 'tellduslive'
|
2016-02-03 21:31:28 +00:00
|
|
|
|
2017-02-23 11:37:25 +00:00
|
|
|
REQUIREMENTS = ['tellduslive==0.3.4']
|
2016-02-03 21:31:28 +00:00
|
|
|
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
2016-09-11 07:22:49 +00:00
|
|
|
CONF_PUBLIC_KEY = 'public_key'
|
|
|
|
CONF_PRIVATE_KEY = 'private_key'
|
|
|
|
CONF_TOKEN = 'token'
|
|
|
|
CONF_TOKEN_SECRET = 'token_secret'
|
2016-12-12 05:39:37 +00:00
|
|
|
CONF_UPDATE_INTERVAL = 'update_interval'
|
2015-12-27 11:32:08 +00:00
|
|
|
|
2016-12-12 05:39:37 +00:00
|
|
|
MIN_UPDATE_INTERVAL = timedelta(seconds=5)
|
|
|
|
DEFAULT_UPDATE_INTERVAL = timedelta(minutes=1)
|
2015-12-27 11:32:08 +00:00
|
|
|
|
2016-09-11 07:22:49 +00:00
|
|
|
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,
|
2016-12-12 05:39:37 +00:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
2016-12-12 05:39:37 +00:00
|
|
|
ATTR_LAST_UPDATED = 'time_last_updated'
|
|
|
|
|
|
|
|
|
2016-09-11 07:22:49 +00:00
|
|
|
def setup(hass, config):
|
2017-04-30 05:04:49 +00:00
|
|
|
"""Set up the Telldus Live component."""
|
2016-12-12 05:39:37 +00:00
|
|
|
client = TelldusLiveClient(hass, config)
|
2016-09-11 07:22:49 +00:00
|
|
|
|
2016-12-12 05:39:37 +00:00
|
|
|
if not client.validate_session():
|
2016-09-11 07:22:49 +00:00
|
|
|
_LOGGER.error(
|
2017-04-30 05:04:49 +00:00
|
|
|
"Authentication Error: Please make sure you have configured your "
|
|
|
|
"keys that can be aquired from https://api.telldus.com/keys/index")
|
2016-09-11 07:22:49 +00:00
|
|
|
return False
|
|
|
|
|
2016-12-12 05:39:37 +00:00
|
|
|
hass.data[DOMAIN] = client
|
2017-06-02 07:02:26 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
2016-12-12 05:39:37 +00:00
|
|
|
class TelldusLiveClient(object):
|
2016-03-08 16:55:57 +00:00
|
|
|
"""Get the latest data and update the states."""
|
|
|
|
|
2015-12-27 11:32:08 +00:00
|
|
|
def __init__(self, hass, config):
|
2016-03-08 16:55:57 +00:00
|
|
|
"""Initialize the Tellus data object."""
|
2016-12-12 05:39:37 +00:00
|
|
|
from tellduslive import Client
|
|
|
|
|
2015-12-27 11:32:08 +00:00
|
|
|
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)
|
|
|
|
|
2016-12-12 05:39:37 +00:00
|
|
|
self.entities = []
|
2016-02-03 21:31:28 +00:00
|
|
|
|
|
|
|
self._hass = hass
|
|
|
|
self._config = config
|
2015-12-27 11:32:08 +00:00
|
|
|
|
2016-12-12 05:39:37 +00:00
|
|
|
self._interval = config[DOMAIN].get(CONF_UPDATE_INTERVAL)
|
|
|
|
_LOGGER.debug('Update interval %s', self._interval)
|
|
|
|
|
|
|
|
self._client = Client(public_key,
|
|
|
|
private_key,
|
|
|
|
token,
|
|
|
|
token_secret)
|
2015-12-27 11:32:08 +00:00
|
|
|
|
2016-02-03 21:31:28 +00:00
|
|
|
def validate_session(self):
|
2016-12-12 05:39:37 +00:00
|
|
|
"""Make a request to see if the session is valid."""
|
|
|
|
response = self._client.request_user()
|
2016-02-16 16:36:09 +00:00
|
|
|
return response and 'email' in response
|
2016-02-03 21:31:28 +00:00
|
|
|
|
2017-06-02 07:02:26 +00:00
|
|
|
def update(self, *args):
|
2016-12-12 05:39:37 +00:00
|
|
|
"""Periodically poll the servers for current state."""
|
2017-04-30 05:04:49 +00:00
|
|
|
_LOGGER.debug("Updating")
|
2016-02-16 16:36:09 +00:00
|
|
|
try:
|
2016-12-12 05:39:37 +00:00
|
|
|
self._sync()
|
|
|
|
finally:
|
2017-03-01 15:37:48 +00:00
|
|
|
track_point_in_utc_time(
|
|
|
|
self._hass, self.update, utcnow() + self._interval)
|
2016-12-12 05:39:37 +00:00
|
|
|
|
|
|
|
def _sync(self):
|
|
|
|
"""Update local list of devices."""
|
2017-02-23 11:37:25 +00:00
|
|
|
if not self._client.update():
|
2017-04-30 05:04:49 +00:00
|
|
|
_LOGGER.warning("Failed request")
|
2016-12-12 05:39:37 +00:00
|
|
|
|
|
|
|
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'
|
|
|
|
elif device.methods & UP:
|
|
|
|
return 'cover'
|
|
|
|
elif device.methods & TURNON:
|
|
|
|
return 'switch'
|
|
|
|
else:
|
2017-04-30 05:04:49 +00:00
|
|
|
_LOGGER.warning(
|
|
|
|
"Unidentified device type (methods: %d)", device.methods)
|
2016-12-12 05:39:37 +00:00
|
|
|
return 'switch'
|
|
|
|
|
|
|
|
def discover(device_id, component):
|
|
|
|
"""Discover the component."""
|
2017-03-01 15:37:48 +00:00
|
|
|
discovery.load_platform(
|
|
|
|
self._hass, component, DOMAIN, [device_id], self._config)
|
2016-12-12 05:39:37 +00:00
|
|
|
|
|
|
|
known_ids = set([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),
|
2016-12-12 05:39:37 +00:00
|
|
|
'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)
|
2016-12-12 05:39:37 +00:00
|
|
|
|
|
|
|
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)
|
2017-02-23 11:37:25 +00:00
|
|
|
self._name = self.device.name
|
2017-04-30 05:04:49 +00:00
|
|
|
_LOGGER.debug("Created device %s", self)
|
2016-12-12 05:39:37 +00:00
|
|
|
|
|
|
|
def changed(self):
|
2017-04-30 05:04:49 +00:00
|
|
|
"""Return the property of the device might have changed."""
|
2017-02-23 11:37:25 +00:00
|
|
|
if self.device.name:
|
|
|
|
self._name = self.device.name
|
2016-12-12 05:39:37 +00:00
|
|
|
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 representaion 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):
|
2017-04-30 05:04:49 +00:00
|
|
|
"""Return the polling state."""
|
2016-12-12 05:39:37 +00:00
|
|
|
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."""
|
2017-02-23 11:37:25 +00:00
|
|
|
return self._name or DEVICE_DEFAULT_NAME
|
2016-12-12 05:39:37 +00:00
|
|
|
|
|
|
|
@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."""
|
|
|
|
return round(self.device.battery * 100 / 255) \
|
|
|
|
if self.device.battery else None
|
|
|
|
|
|
|
|
@property
|
|
|
|
def _last_updated(self):
|
|
|
|
"""Return the last update of a device."""
|
2017-01-31 19:08:11 +00:00
|
|
|
return str(datetime.fromtimestamp(self.device.lastUpdated)) \
|
|
|
|
if self.device.lastUpdated else None
|