diff --git a/homeassistant/components/cover/tellduslive.py b/homeassistant/components/cover/tellduslive.py new file mode 100644 index 00000000000..c48a14e9133 --- /dev/null +++ b/homeassistant/components/cover/tellduslive.py @@ -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() diff --git a/homeassistant/components/light/tellduslive.py b/homeassistant/components/light/tellduslive.py new file mode 100644 index 00000000000..f919268c380 --- /dev/null +++ b/homeassistant/components/light/tellduslive.py @@ -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() diff --git a/homeassistant/components/sensor/tellduslive.py b/homeassistant/components/sensor/tellduslive.py index 915bbac429f..abc5843ad91 100644 --- a/homeassistant/components/sensor/tellduslive.py +++ b/homeassistant/components/sensor/tellduslive.py @@ -6,37 +6,32 @@ https://home-assistant.io/components/sensor.tellduslive/ """ import logging -from datetime import datetime -from homeassistant.components import tellduslive -from homeassistant.const import ( - ATTR_BATTERY_LEVEL, DEVICE_DEFAULT_NAME, TEMP_CELSIUS) -from homeassistant.helpers.entity import Entity - -ATTR_LAST_UPDATED = "time_last_updated" +from homeassistant.components.tellduslive import TelldusLiveEntity +from homeassistant.const import TEMP_CELSIUS _LOGGER = logging.getLogger(__name__) -SENSOR_TYPE_TEMP = "temp" -SENSOR_TYPE_HUMIDITY = "humidity" -SENSOR_TYPE_RAINRATE = "rrate" -SENSOR_TYPE_RAINTOTAL = "rtot" -SENSOR_TYPE_WINDDIRECTION = "wdir" -SENSOR_TYPE_WINDAVERAGE = "wavg" -SENSOR_TYPE_WINDGUST = "wgust" -SENSOR_TYPE_WATT = "watt" -SENSOR_TYPE_LUMINANCE = "lum" +SENSOR_TYPE_TEMP = 'temp' +SENSOR_TYPE_HUMIDITY = 'humidity' +SENSOR_TYPE_RAINRATE = 'rrate' +SENSOR_TYPE_RAINTOTAL = 'rtot' +SENSOR_TYPE_WINDDIRECTION = 'wdir' +SENSOR_TYPE_WINDAVERAGE = 'wavg' +SENSOR_TYPE_WINDGUST = 'wgust' +SENSOR_TYPE_WATT = 'watt' +SENSOR_TYPE_LUMINANCE = 'lum' SENSOR_TYPES = { - SENSOR_TYPE_TEMP: ['Temperature', TEMP_CELSIUS, "mdi:thermometer"], - SENSOR_TYPE_HUMIDITY: ['Humidity', '%', "mdi:water"], - SENSOR_TYPE_RAINRATE: ['Rain rate', 'mm', "mdi:water"], - SENSOR_TYPE_RAINTOTAL: ['Rain total', 'mm', "mdi:water"], - SENSOR_TYPE_WINDDIRECTION: ['Wind direction', '', ""], - SENSOR_TYPE_WINDAVERAGE: ['Wind average', 'm/s', ""], - SENSOR_TYPE_WINDGUST: ['Wind gust', 'm/s', ""], - SENSOR_TYPE_WATT: ['Watt', 'W', ""], - SENSOR_TYPE_LUMINANCE: ['Luminance', 'lx', ""], + SENSOR_TYPE_TEMP: ['Temperature', TEMP_CELSIUS, 'mdi:thermometer'], + SENSOR_TYPE_HUMIDITY: ['Humidity', '%', 'mdi:water'], + SENSOR_TYPE_RAINRATE: ['Rain rate', 'mm', 'mdi:water'], + SENSOR_TYPE_RAINTOTAL: ['Rain total', 'mm', 'mdi:water'], + SENSOR_TYPE_WINDDIRECTION: ['Wind direction', '', ''], + SENSOR_TYPE_WINDAVERAGE: ['Wind average', 'm/s', ''], + SENSOR_TYPE_WINDGUST: ['Wind gust', 'm/s', ''], + SENSOR_TYPE_WATT: ['Watt', 'W', ''], + SENSOR_TYPE_LUMINANCE: ['Luminance', 'lx', ''], } @@ -44,114 +39,75 @@ def setup_platform(hass, config, add_devices, discovery_info=None): """Setup Tellstick sensors.""" if discovery_info is None: 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.""" - def __init__(self, sensor_id): - """Initialize the sensor.""" - self._id = sensor_id - self.update() - _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 + def device_id(self): + """Return id of the device.""" + return self._id[0] @property - def _sensor_name(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): + def _type(self): """Return the type of the sensor.""" - return self._sensor["data"]["name"] + return self._id[1] @property - def _battery_level(self): - """Return the battery level of a sensor.""" - sensor_battery_level = self._sensor.get("battery") - 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 + def _value(self): + """Return value of the sensor.""" + return self.device.value(self._id[1:]) @property def _value_as_temperature(self): """Return the value as temperature.""" - return round(float(self._sensor_value), 1) + return round(float(self._value), 1) @property def _value_as_luminance(self): """Return the value as luminance.""" - return round(float(self._sensor_value), 1) + return round(float(self._value), 1) @property def _value_as_humidity(self): """Return the value as humidity.""" - return int(round(float(self._sensor_value))) + return int(round(float(self._value))) @property def name(self): """Return the name of the sensor.""" - return "{} {}".format(self._sensor_name or DEVICE_DEFAULT_NAME, - self.quantity_name or "") - - @property - def available(self): - """Return true if the sensor is available.""" - return not self._sensor.get("offline", False) + return '{} {}'.format( + super().name, + self.quantity_name or '') @property def state(self): """Return the state of the sensor.""" - if self._sensor_type == SENSOR_TYPE_TEMP: + if self._type == SENSOR_TYPE_TEMP: return self._value_as_temperature - elif self._sensor_type == SENSOR_TYPE_HUMIDITY: + elif self._type == SENSOR_TYPE_HUMIDITY: return self._value_as_humidity - elif self._sensor_type == SENSOR_TYPE_LUMINANCE: + elif self._type == SENSOR_TYPE_LUMINANCE: return self._value_as_luminance else: - return self._sensor_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 + return self._value @property def quantity_name(self): """Name of quantity.""" - return SENSOR_TYPES[self._sensor_type][0] \ - if self._sensor_type in SENSOR_TYPES else None + return SENSOR_TYPES[self._type][0] \ + if self._type in SENSOR_TYPES else None @property def unit_of_measurement(self): """Return the unit of measurement.""" - return SENSOR_TYPES[self._sensor_type][1] \ - if self._sensor_type in SENSOR_TYPES else None + return SENSOR_TYPES[self._type][1] \ + if self._type in SENSOR_TYPES else None @property def icon(self): """Return the icon.""" - return SENSOR_TYPES[self._sensor_type][2] \ - if self._sensor_type in SENSOR_TYPES else None + return SENSOR_TYPES[self._type][2] \ + if self._type in SENSOR_TYPES else None diff --git a/homeassistant/components/switch/tellduslive.py b/homeassistant/components/switch/tellduslive.py index eaa78412c27..b1450de6c5e 100644 --- a/homeassistant/components/switch/tellduslive.py +++ b/homeassistant/components/switch/tellduslive.py @@ -9,7 +9,7 @@ https://home-assistant.io/components/switch.tellduslive/ """ import logging -from homeassistant.components import tellduslive +from homeassistant.components.tellduslive import TelldusLiveEntity from homeassistant.helpers.entity import ToggleEntity _LOGGER = logging.getLogger(__name__) @@ -19,53 +19,23 @@ def setup_platform(hass, config, add_devices, discovery_info=None): """Setup Tellstick switches.""" if discovery_info is None: 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.""" - 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 def is_on(self): """Return true if switch is on.""" - from tellive.live import const - return self._switch["state"] == const.TELLSTICK_TURNON + return self.device.is_on() def turn_on(self, **kwargs): """Turn the switch on.""" - tellduslive.NETWORK.turn_switch_on(self._id) + self.device.turn_on() + self.changed() def turn_off(self, **kwargs): """Turn the switch off.""" - tellduslive.NETWORK.turn_switch_off(self._id) + self.device.turn_off() + self.changed() diff --git a/homeassistant/components/tellduslive.py b/homeassistant/components/tellduslive.py index 36e9b01d511..cf62a28b552 100644 --- a/homeassistant/components/tellduslive.py +++ b/homeassistant/components/tellduslive.py @@ -4,18 +4,20 @@ Support for Telldus Live. 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 -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.util import Throttle 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' -REQUIREMENTS = ['tellive-py==0.5.2'] +REQUIREMENTS = ['tellduslive==0.1.9'] _LOGGER = logging.getLogger(__name__) @@ -23,11 +25,10 @@ CONF_PUBLIC_KEY = 'public_key' CONF_PRIVATE_KEY = 'private_key' CONF_TOKEN = 'token' CONF_TOKEN_SECRET = 'token_secret' +CONF_UPDATE_INTERVAL = 'update_interval' -MIN_TIME_BETWEEN_SWITCH_UPDATES = timedelta(minutes=1) -MIN_TIME_BETWEEN_SENSOR_UPDATES = timedelta(minutes=5) - -NETWORK = None +MIN_UPDATE_INTERVAL = timedelta(seconds=5) +DEFAULT_UPDATE_INTERVAL = timedelta(minutes=1) CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ @@ -35,183 +36,190 @@ CONFIG_SCHEMA = vol.Schema({ 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.All(cv.time_period, vol.Clamp(min=MIN_UPDATE_INTERVAL))) }), }, extra=vol.ALLOW_EXTRA) +ATTR_LAST_UPDATED = 'time_last_updated' + + def setup(hass, config): """Setup the Telldus Live component.""" - # fixme: aquire app key and provide authentication using username+password + client = TelldusLiveClient(hass, config) - global NETWORK - NETWORK = TelldusLiveData(hass, config) - - if not NETWORK.validate_session(): + if not client.validate_session(): _LOGGER.error( - "Authentication Error: " - "Please make sure you have configured your keys " - "that can be aquired from https://api.telldus.com/keys/index") + 'Authentication Error: ' + 'Please make sure you have configured your keys ' + 'that can be aquired from https://api.telldus.com/keys/index') return False - NETWORK.discover() + hass.data[DOMAIN] = client + client.update(utcnow()) return True -@Throttle(MIN_TIME_BETWEEN_SWITCH_UPDATES) -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): +class TelldusLiveClient(object): """Get the latest data and update the states.""" def __init__(self, hass, config): """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) - from tellive.client import LiveClient - - self._switches = {} - self._sensors = {} + self.entities = [] self._hass = hass self._config = config - self._client = LiveClient( - public_key=public_key, private_key=private_key, access_token=token, - access_secret=token_secret) + 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) def validate_session(self): - """Make a dummy request to see if the session is valid.""" - response = self.request("user/profile") + """Make a request to see if the session is valid.""" + response = self._client.request_user() return response and 'email' in response - def discover(self): - """Update states, will trigger discover.""" - self.update_sensors() - 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 - + def update(self, now): + """Periodically poll the servers for current state.""" + _LOGGER.debug('Updating') try: - response = self._client.request(what, params) - _LOGGER.debug("got response %s", response) - return response - except OSError as error: - _LOGGER.error("failed to make request to Tellduslive servers: %s", - error) - return None + self._sync() + finally: + track_point_in_utc_time(self._hass, + self.update, + now + self._interval) - def update_devices(self, local_devices, remote_devices, component_name): - """Update local device list and discover new devices.""" - if remote_devices is None: - return local_devices + def _sync(self): + """Update local list of devices.""" + self._client.update() - remote_ids = remote_devices.keys() - local_ids = local_devices.keys() + 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: + _LOGGER.warning('Unidentified device type (methods: %d)', + device.methods) + return 'switch' - added_devices = list(remote_ids - local_ids) - self._discover(added_devices, - component_name) + def discover(device_id, component): + """Discover the component.""" + discovery.load_platform(self._hass, + component, + DOMAIN, + [device_id], + self._config) - removed_devices = list(local_ids - remote_ids) - remote_devices.update({id: dict(local_devices[id], offline=True) - for id in removed_devices}) + 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: + 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): - """Update local list of sensors.""" - self._sensors = self.update_devices( - self._sensors, request_sensors(), 'sensor') + def device(self, device_id): + """Return device representation.""" + import tellduslive + return tellduslive.Device(self._client, device_id) - def update_switches(self): - """Update local list of switches.""" - self._switches = self.update_devices( - self._switches, request_switches(), 'switch') + def is_available(self, device_id): + """Return device availability.""" + return device_id in self._client.device_ids - 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): - """Return the switch representation.""" - return self._switches[switch_id] +class TelldusLiveEntity(Entity): + """Base class for all Telldus Live entities.""" - def get_sensor(self, sensor_id): - """Return the sensor representation.""" - return self._sensors[sensor_id] + def __init__(self, hass, device_id): + """Initialize the entity.""" + 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): - """Turn switch off.""" - if self._check_request('device/turnOn', id=switch_id): - from tellive.live import const - self.get_switch(switch_id)['state'] = const.TELLSTICK_TURNON + def changed(self): + """A property of the device might have changed.""" + self.schedule_update_ha_state() - def turn_switch_off(self, switch_id): - """Turn switch on.""" - if self._check_request('device/turnOff', id=switch_id): - from tellive.live import const - self.get_switch(switch_id)['state'] = const.TELLSTICK_TURNOFF + @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): + """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 diff --git a/requirements_all.txt b/requirements_all.txt index 4b2763f26bb..fe37b085b2c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -560,7 +560,7 @@ steamodd==4.21 tellcore-py==1.1.2 # homeassistant.components.tellduslive -tellive-py==0.5.2 +tellduslive==0.1.9 # homeassistant.components.sensor.temper temperusb==1.5.1