Telldus Live: (#4645)

- Implemented support for covers and dimmable lights.
- Removed global object, use hass.data.
- Disabled polling via update.
- Inherit from common TelldusLiveEntity device.
- Configurable polling interval
- Use https API endpoint
- Use tellduslive package
This commit is contained in:
Erik Eriksson 2016-12-12 06:39:37 +01:00 committed by Paulus Schoutsen
parent 04aa4e898a
commit 2a7fa5afc3
6 changed files with 314 additions and 275 deletions

View File

@ -0,0 +1,46 @@
"""
Support for Tellstick covers using Tellstick Net.
This platform uses the Telldus Live online service.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover.tellduslive/
"""
import logging
from homeassistant.components.cover import CoverDevice
from homeassistant.components.tellduslive import TelldusLiveEntity
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup covers."""
if discovery_info is None:
return
add_devices(TelldusLiveCover(hass, cover) for cover in discovery_info)
class TelldusLiveCover(TelldusLiveEntity, CoverDevice):
"""Representation of a cover."""
@property
def is_closed(self):
"""Return the current position of the cover."""
return self.device.is_down
def close_cover(self, **kwargs):
"""Close the cover."""
self.device.down()
self.changed()
def open_cover(self, **kwargs):
"""Open the cover."""
self.device.up()
self.changed()
def stop_cover(self, **kwargs):
"""Stop the cover."""
self.device.stop()
self.changed()

View File

@ -0,0 +1,59 @@
"""
Support for Tellstick switches using Tellstick Net.
This platform uses the Telldus Live online service.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.tellduslive/
"""
import logging
from homeassistant.components.light import (
ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light)
from homeassistant.components.tellduslive import TelldusLiveEntity
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup lights."""
if discovery_info is None:
return
add_devices(TelldusLiveLight(hass, light) for light in discovery_info)
class TelldusLiveLight(TelldusLiveEntity, Light):
"""Representation of a light."""
def changed(self):
"""A property of the device might have changed."""
# pylint: disable=attribute-defined-outside-init
self._last_brightness = self.brightness
super().changed()
@property
def brightness(self):
"""Return the brightness of this light between 0..255."""
return self.device.dim_level
@property
def supported_features(self):
"""Flag supported features."""
return SUPPORT_BRIGHTNESS
@property
def is_on(self):
"""Return true if light is on."""
return self.device.is_on
def turn_on(self, **kwargs):
"""Turn the light on."""
brightness = kwargs.get(ATTR_BRIGHTNESS, self._last_brightness)
self.device.dim(level=brightness)
self.changed()
def turn_off(self, **kwargs):
"""Turn the light off."""
self.device.turn_off()
self.changed()

View File

@ -6,37 +6,32 @@ https://home-assistant.io/components/sensor.tellduslive/
""" """
import logging import logging
from datetime import datetime
from homeassistant.components import tellduslive from homeassistant.components.tellduslive import TelldusLiveEntity
from homeassistant.const import ( from homeassistant.const import TEMP_CELSIUS
ATTR_BATTERY_LEVEL, DEVICE_DEFAULT_NAME, TEMP_CELSIUS)
from homeassistant.helpers.entity import Entity
ATTR_LAST_UPDATED = "time_last_updated"
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
SENSOR_TYPE_TEMP = "temp" SENSOR_TYPE_TEMP = 'temp'
SENSOR_TYPE_HUMIDITY = "humidity" SENSOR_TYPE_HUMIDITY = 'humidity'
SENSOR_TYPE_RAINRATE = "rrate" SENSOR_TYPE_RAINRATE = 'rrate'
SENSOR_TYPE_RAINTOTAL = "rtot" SENSOR_TYPE_RAINTOTAL = 'rtot'
SENSOR_TYPE_WINDDIRECTION = "wdir" SENSOR_TYPE_WINDDIRECTION = 'wdir'
SENSOR_TYPE_WINDAVERAGE = "wavg" SENSOR_TYPE_WINDAVERAGE = 'wavg'
SENSOR_TYPE_WINDGUST = "wgust" SENSOR_TYPE_WINDGUST = 'wgust'
SENSOR_TYPE_WATT = "watt" SENSOR_TYPE_WATT = 'watt'
SENSOR_TYPE_LUMINANCE = "lum" SENSOR_TYPE_LUMINANCE = 'lum'
SENSOR_TYPES = { SENSOR_TYPES = {
SENSOR_TYPE_TEMP: ['Temperature', TEMP_CELSIUS, "mdi:thermometer"], SENSOR_TYPE_TEMP: ['Temperature', TEMP_CELSIUS, 'mdi:thermometer'],
SENSOR_TYPE_HUMIDITY: ['Humidity', '%', "mdi:water"], SENSOR_TYPE_HUMIDITY: ['Humidity', '%', 'mdi:water'],
SENSOR_TYPE_RAINRATE: ['Rain rate', 'mm', "mdi:water"], SENSOR_TYPE_RAINRATE: ['Rain rate', 'mm', 'mdi:water'],
SENSOR_TYPE_RAINTOTAL: ['Rain total', 'mm', "mdi:water"], SENSOR_TYPE_RAINTOTAL: ['Rain total', 'mm', 'mdi:water'],
SENSOR_TYPE_WINDDIRECTION: ['Wind direction', '', ""], SENSOR_TYPE_WINDDIRECTION: ['Wind direction', '', ''],
SENSOR_TYPE_WINDAVERAGE: ['Wind average', 'm/s', ""], SENSOR_TYPE_WINDAVERAGE: ['Wind average', 'm/s', ''],
SENSOR_TYPE_WINDGUST: ['Wind gust', 'm/s', ""], SENSOR_TYPE_WINDGUST: ['Wind gust', 'm/s', ''],
SENSOR_TYPE_WATT: ['Watt', 'W', ""], SENSOR_TYPE_WATT: ['Watt', 'W', ''],
SENSOR_TYPE_LUMINANCE: ['Luminance', 'lx', ""], SENSOR_TYPE_LUMINANCE: ['Luminance', 'lx', ''],
} }
@ -44,114 +39,75 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup Tellstick sensors.""" """Setup Tellstick sensors."""
if discovery_info is None: if discovery_info is None:
return return
add_devices(TelldusLiveSensor(sensor) for sensor in discovery_info) add_devices(TelldusLiveSensor(hass, sensor) for sensor in discovery_info)
class TelldusLiveSensor(Entity): class TelldusLiveSensor(TelldusLiveEntity):
"""Representation of a Telldus Live sensor.""" """Representation of a Telldus Live sensor."""
def __init__(self, sensor_id): @property
"""Initialize the sensor.""" def device_id(self):
self._id = sensor_id """Return id of the device."""
self.update() return self._id[0]
_LOGGER.debug("created sensor %s", self)
def update(self):
"""Update sensor values."""
tellduslive.NETWORK.update_sensors()
self._sensor = tellduslive.NETWORK.get_sensor(self._id)
@property @property
def _sensor_name(self): def _type(self):
"""Return the name of the sensor."""
return self._sensor["name"]
@property
def _sensor_value(self):
"""Return the value the sensor."""
return self._sensor["data"]["value"]
@property
def _sensor_type(self):
"""Return the type of the sensor.""" """Return the type of the sensor."""
return self._sensor["data"]["name"] return self._id[1]
@property @property
def _battery_level(self): def _value(self):
"""Return the battery level of a sensor.""" """Return value of the sensor."""
sensor_battery_level = self._sensor.get("battery") return self.device.value(self._id[1:])
return round(sensor_battery_level * 100 / 255) \
if sensor_battery_level else None
@property
def _last_updated(self):
"""Return the last update."""
sensor_last_updated = self._sensor.get("lastUpdated")
return str(datetime.fromtimestamp(sensor_last_updated)) \
if sensor_last_updated else None
@property @property
def _value_as_temperature(self): def _value_as_temperature(self):
"""Return the value as temperature.""" """Return the value as temperature."""
return round(float(self._sensor_value), 1) return round(float(self._value), 1)
@property @property
def _value_as_luminance(self): def _value_as_luminance(self):
"""Return the value as luminance.""" """Return the value as luminance."""
return round(float(self._sensor_value), 1) return round(float(self._value), 1)
@property @property
def _value_as_humidity(self): def _value_as_humidity(self):
"""Return the value as humidity.""" """Return the value as humidity."""
return int(round(float(self._sensor_value))) return int(round(float(self._value)))
@property @property
def name(self): def name(self):
"""Return the name of the sensor.""" """Return the name of the sensor."""
return "{} {}".format(self._sensor_name or DEVICE_DEFAULT_NAME, return '{} {}'.format(
self.quantity_name or "") super().name,
self.quantity_name or '')
@property
def available(self):
"""Return true if the sensor is available."""
return not self._sensor.get("offline", False)
@property @property
def state(self): def state(self):
"""Return the state of the sensor.""" """Return the state of the sensor."""
if self._sensor_type == SENSOR_TYPE_TEMP: if self._type == SENSOR_TYPE_TEMP:
return self._value_as_temperature return self._value_as_temperature
elif self._sensor_type == SENSOR_TYPE_HUMIDITY: elif self._type == SENSOR_TYPE_HUMIDITY:
return self._value_as_humidity return self._value_as_humidity
elif self._sensor_type == SENSOR_TYPE_LUMINANCE: elif self._type == SENSOR_TYPE_LUMINANCE:
return self._value_as_luminance return self._value_as_luminance
else: else:
return self._sensor_value return self._value
@property
def device_state_attributes(self):
"""Return the state attributes."""
attrs = {}
if self._battery_level is not None:
attrs[ATTR_BATTERY_LEVEL] = self._battery_level
if self._last_updated is not None:
attrs[ATTR_LAST_UPDATED] = self._last_updated
return attrs
@property @property
def quantity_name(self): def quantity_name(self):
"""Name of quantity.""" """Name of quantity."""
return SENSOR_TYPES[self._sensor_type][0] \ return SENSOR_TYPES[self._type][0] \
if self._sensor_type in SENSOR_TYPES else None if self._type in SENSOR_TYPES else None
@property @property
def unit_of_measurement(self): def unit_of_measurement(self):
"""Return the unit of measurement.""" """Return the unit of measurement."""
return SENSOR_TYPES[self._sensor_type][1] \ return SENSOR_TYPES[self._type][1] \
if self._sensor_type in SENSOR_TYPES else None if self._type in SENSOR_TYPES else None
@property @property
def icon(self): def icon(self):
"""Return the icon.""" """Return the icon."""
return SENSOR_TYPES[self._sensor_type][2] \ return SENSOR_TYPES[self._type][2] \
if self._sensor_type in SENSOR_TYPES else None if self._type in SENSOR_TYPES else None

View File

@ -9,7 +9,7 @@ https://home-assistant.io/components/switch.tellduslive/
""" """
import logging import logging
from homeassistant.components import tellduslive from homeassistant.components.tellduslive import TelldusLiveEntity
from homeassistant.helpers.entity import ToggleEntity from homeassistant.helpers.entity import ToggleEntity
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -19,53 +19,23 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup Tellstick switches.""" """Setup Tellstick switches."""
if discovery_info is None: if discovery_info is None:
return return
add_devices(TelldusLiveSwitch(switch) for switch in discovery_info) add_devices(TelldusLiveSwitch(hass, switch) for switch in discovery_info)
class TelldusLiveSwitch(ToggleEntity): class TelldusLiveSwitch(TelldusLiveEntity, ToggleEntity):
"""Representation of a Tellstick switch.""" """Representation of a Tellstick switch."""
def __init__(self, switch_id):
"""Initialize the switch."""
self._id = switch_id
self.update()
_LOGGER.debug("created switch %s", self)
def update(self):
"""Get the latest date and update the state."""
tellduslive.NETWORK.update_switches()
self._switch = tellduslive.NETWORK.get_switch(self._id)
@property
def should_poll(self):
"""Polling is needed."""
return True
@property
def assumed_state(self):
"""Return true if unable to access real state of entity."""
return True
@property
def name(self):
"""Return the name of the switch if any."""
return self._switch["name"]
@property
def available(self):
"""Return the state of the switch."""
return not self._switch.get("offline", False)
@property @property
def is_on(self): def is_on(self):
"""Return true if switch is on.""" """Return true if switch is on."""
from tellive.live import const return self.device.is_on()
return self._switch["state"] == const.TELLSTICK_TURNON
def turn_on(self, **kwargs): def turn_on(self, **kwargs):
"""Turn the switch on.""" """Turn the switch on."""
tellduslive.NETWORK.turn_switch_on(self._id) self.device.turn_on()
self.changed()
def turn_off(self, **kwargs): def turn_off(self, **kwargs):
"""Turn the switch off.""" """Turn the switch off."""
tellduslive.NETWORK.turn_switch_off(self._id) self.device.turn_off()
self.changed()

View File

@ -4,18 +4,20 @@ Support for Telldus Live.
For more details about this component, please refer to the documentation at For more details about this component, please refer to the documentation at
https://home-assistant.io/components/tellduslive/ https://home-assistant.io/components/tellduslive/
""" """
from datetime import datetime, timedelta
import logging import logging
from datetime import timedelta
import voluptuous as vol
from homeassistant.const import ATTR_BATTERY_LEVEL, DEVICE_DEFAULT_NAME
from homeassistant.helpers import discovery from homeassistant.helpers import discovery
from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv 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
import voluptuous as vol
DOMAIN = 'tellduslive' DOMAIN = 'tellduslive'
REQUIREMENTS = ['tellive-py==0.5.2'] REQUIREMENTS = ['tellduslive==0.1.9']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -23,11 +25,10 @@ CONF_PUBLIC_KEY = 'public_key'
CONF_PRIVATE_KEY = 'private_key' CONF_PRIVATE_KEY = 'private_key'
CONF_TOKEN = 'token' CONF_TOKEN = 'token'
CONF_TOKEN_SECRET = 'token_secret' CONF_TOKEN_SECRET = 'token_secret'
CONF_UPDATE_INTERVAL = 'update_interval'
MIN_TIME_BETWEEN_SWITCH_UPDATES = timedelta(minutes=1) MIN_UPDATE_INTERVAL = timedelta(seconds=5)
MIN_TIME_BETWEEN_SENSOR_UPDATES = timedelta(minutes=5) DEFAULT_UPDATE_INTERVAL = timedelta(minutes=1)
NETWORK = None
CONFIG_SCHEMA = vol.Schema({ CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({ DOMAIN: vol.Schema({
@ -35,183 +36,190 @@ CONFIG_SCHEMA = vol.Schema({
vol.Required(CONF_PRIVATE_KEY): cv.string, vol.Required(CONF_PRIVATE_KEY): cv.string,
vol.Required(CONF_TOKEN): cv.string, vol.Required(CONF_TOKEN): cv.string,
vol.Required(CONF_TOKEN_SECRET): cv.string, vol.Required(CONF_TOKEN_SECRET): cv.string,
vol.Optional(CONF_UPDATE_INTERVAL, default=DEFAULT_UPDATE_INTERVAL): (
vol.All(cv.time_period, vol.Clamp(min=MIN_UPDATE_INTERVAL)))
}), }),
}, extra=vol.ALLOW_EXTRA) }, extra=vol.ALLOW_EXTRA)
ATTR_LAST_UPDATED = 'time_last_updated'
def setup(hass, config): def setup(hass, config):
"""Setup the Telldus Live component.""" """Setup the Telldus Live component."""
# fixme: aquire app key and provide authentication using username+password client = TelldusLiveClient(hass, config)
global NETWORK if not client.validate_session():
NETWORK = TelldusLiveData(hass, config)
if not NETWORK.validate_session():
_LOGGER.error( _LOGGER.error(
"Authentication Error: " 'Authentication Error: '
"Please make sure you have configured your keys " 'Please make sure you have configured your keys '
"that can be aquired from https://api.telldus.com/keys/index") 'that can be aquired from https://api.telldus.com/keys/index')
return False return False
NETWORK.discover() hass.data[DOMAIN] = client
client.update(utcnow())
return True return True
@Throttle(MIN_TIME_BETWEEN_SWITCH_UPDATES) class TelldusLiveClient(object):
def request_switches():
"""Make request to online service."""
_LOGGER.debug("Updating switches from Telldus Live")
switches = NETWORK.request('devices/list')
# Filter out any group of switches.
if switches and 'device' in switches:
return {switch["id"]: switch for switch in switches['device']
if switch["type"] == "device"}
return None
@Throttle(MIN_TIME_BETWEEN_SENSOR_UPDATES)
def request_sensors():
"""Make request to online service."""
_LOGGER.debug("Updating sensors from Telldus Live")
units = NETWORK.request('sensors/list')
# One unit can contain many sensors.
if units and 'sensor' in units:
return {(unit['id'], sensor['name'], sensor['scale']):
dict(unit, data=sensor)
for unit in units['sensor']
for sensor in unit['data']}
return None
class TelldusLiveData(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):
"""Initialize the Tellus data object.""" """Initialize the Tellus data object."""
from tellduslive import Client
public_key = config[DOMAIN].get(CONF_PUBLIC_KEY) public_key = config[DOMAIN].get(CONF_PUBLIC_KEY)
private_key = config[DOMAIN].get(CONF_PRIVATE_KEY) private_key = config[DOMAIN].get(CONF_PRIVATE_KEY)
token = config[DOMAIN].get(CONF_TOKEN) token = config[DOMAIN].get(CONF_TOKEN)
token_secret = config[DOMAIN].get(CONF_TOKEN_SECRET) token_secret = config[DOMAIN].get(CONF_TOKEN_SECRET)
from tellive.client import LiveClient self.entities = []
self._switches = {}
self._sensors = {}
self._hass = hass self._hass = hass
self._config = config self._config = config
self._client = LiveClient( self._interval = config[DOMAIN].get(CONF_UPDATE_INTERVAL)
public_key=public_key, private_key=private_key, access_token=token, _LOGGER.debug('Update interval %s', self._interval)
access_secret=token_secret)
self._client = Client(public_key,
private_key,
token,
token_secret)
def validate_session(self): def validate_session(self):
"""Make a dummy request to see if the session is valid.""" """Make a request to see if the session is valid."""
response = self.request("user/profile") response = self._client.request_user()
return response and 'email' in response return response and 'email' in response
def discover(self): def update(self, now):
"""Update states, will trigger discover.""" """Periodically poll the servers for current state."""
self.update_sensors() _LOGGER.debug('Updating')
self.update_switches()
def _discover(self, found_devices, component_name):
"""Send discovery event if component not yet discovered."""
if not found_devices:
return
_LOGGER.info("discovered %d new %s devices",
len(found_devices), component_name)
discovery.load_platform(self._hass, component_name, DOMAIN,
found_devices, self._config)
def request(self, what, **params):
"""Send a request to the Tellstick Live API."""
from tellive.live import const
supported_methods = const.TELLSTICK_TURNON \
| const.TELLSTICK_TURNOFF \
| const.TELLSTICK_TOGGLE \
# Tellstick device methods not yet supported
# | const.TELLSTICK_BELL \
# | const.TELLSTICK_DIM \
# | const.TELLSTICK_LEARN \
# | const.TELLSTICK_EXECUTE \
# | const.TELLSTICK_UP \
# | const.TELLSTICK_DOWN \
# | const.TELLSTICK_STOP
default_params = {'supportedMethods': supported_methods,
'includeValues': 1,
'includeScale': 1,
'includeIgnored': 0}
params.update(default_params)
# room for improvement: the telllive library doesn't seem to
# re-use sessions, instead it opens a new session for each request
# this needs to be fixed
try: try:
response = self._client.request(what, params) self._sync()
_LOGGER.debug("got response %s", response) finally:
return response track_point_in_utc_time(self._hass,
except OSError as error: self.update,
_LOGGER.error("failed to make request to Tellduslive servers: %s", now + self._interval)
error)
return None
def update_devices(self, local_devices, remote_devices, component_name): def _sync(self):
"""Update local device list and discover new devices.""" """Update local list of devices."""
if remote_devices is None: self._client.update()
return local_devices
remote_ids = remote_devices.keys() def identify_device(device):
local_ids = local_devices.keys() """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:
_LOGGER.warning('Unidentified device type (methods: %d)',
device.methods)
return 'switch'
added_devices = list(remote_ids - local_ids) def discover(device_id, component):
self._discover(added_devices, """Discover the component."""
component_name) discovery.load_platform(self._hass,
component,
DOMAIN,
[device_id],
self._config)
removed_devices = list(local_ids - remote_ids) known_ids = set([entity.device_id for entity in self.entities])
remote_devices.update({id: dict(local_devices[id], offline=True) for device in self._client.devices:
for id in removed_devices}) if device.device_id in known_ids:
continue
if device.is_sensor:
for item_id in device.items:
discover((device.device_id,) + item_id,
'sensor')
else:
discover(device.device_id,
identify_device(device))
return remote_devices for entity in self.entities:
entity.changed()
def update_sensors(self): def device(self, device_id):
"""Update local list of sensors.""" """Return device representation."""
self._sensors = self.update_devices( import tellduslive
self._sensors, request_sensors(), 'sensor') return tellduslive.Device(self._client, device_id)
def update_switches(self): def is_available(self, device_id):
"""Update local list of switches.""" """Return device availability."""
self._switches = self.update_devices( return device_id in self._client.device_ids
self._switches, request_switches(), 'switch')
def _check_request(self, what, **params):
"""Make request, check result if successful."""
response = self.request(what, **params)
return response and response.get('status') == 'success'
def get_switch(self, switch_id): class TelldusLiveEntity(Entity):
"""Return the switch representation.""" """Base class for all Telldus Live entities."""
return self._switches[switch_id]
def get_sensor(self, sensor_id): def __init__(self, hass, device_id):
"""Return the sensor representation.""" """Initialize the entity."""
return self._sensors[sensor_id] self._id = device_id
self._client = hass.data[DOMAIN]
self._client.entities.append(self)
_LOGGER.debug('Created device %s', self)
def turn_switch_on(self, switch_id): def changed(self):
"""Turn switch off.""" """A property of the device might have changed."""
if self._check_request('device/turnOn', id=switch_id): self.schedule_update_ha_state()
from tellive.live import const
self.get_switch(switch_id)['state'] = const.TELLSTICK_TURNON
def turn_switch_off(self, switch_id): @property
"""Turn switch on.""" def device_id(self):
if self._check_request('device/turnOff', id=switch_id): """Return the id of the device."""
from tellive.live import const return self._id
self.get_switch(switch_id)['state'] = const.TELLSTICK_TURNOFF
@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):
"""Polling is not needed."""
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.device.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."""
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."""
return str(datetime.fromtimestamp(self.device.last_updated)) \
if self.device.last_updated else None

View File

@ -560,7 +560,7 @@ steamodd==4.21
tellcore-py==1.1.2 tellcore-py==1.1.2
# homeassistant.components.tellduslive # homeassistant.components.tellduslive
tellive-py==0.5.2 tellduslive==0.1.9
# homeassistant.components.sensor.temper # homeassistant.components.sensor.temper
temperusb==1.5.1 temperusb==1.5.1