From 50a9b3a7c0bf65df2565cc403079404209d1e483 Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 3 Feb 2016 22:31:28 +0100 Subject: [PATCH] reworked telldus live support --- .../components/sensor/tellduslive.py | 96 +++++--- .../components/switch/tellduslive.py | 42 ++-- homeassistant/components/tellduslive.py | 216 +++++++++++------- 3 files changed, 212 insertions(+), 142 deletions(-) diff --git a/homeassistant/components/sensor/tellduslive.py b/homeassistant/components/sensor/tellduslive.py index c9afbf24eae..1b1edee34c8 100644 --- a/homeassistant/components/sensor/tellduslive.py +++ b/homeassistant/components/sensor/tellduslive.py @@ -46,51 +46,86 @@ def setup_platform(hass, config, add_devices, discovery_info=None): """ Sets up Tellstick sensors. """ if discovery_info is None: return - sensors = tellduslive.NETWORK.get_sensors() - devices = [] - - for component in sensors: - for sensor in component["data"]: - # one component can have more than one sensor - # (e.g. both humidity and temperature) - devices.append(TelldusLiveSensor(component["id"], - component["name"], - sensor["name"])) - add_devices(devices) + add_devices(TelldusLiveSensor(sensor) for sensor in discovery_info) class TelldusLiveSensor(Entity): """ Represents a Telldus Live sensor. """ - def __init__(self, sensor_id, sensor_name, sensor_type): - self._sensor_id = sensor_id - self._sensor_type = sensor_type - self._state = None - self._name = "{} {}".format(sensor_name or DEVICE_DEFAULT_NAME, - SENSOR_TYPES[sensor_type][0]) - self._last_update = None - self._battery_level = None + def __init__(self, sensor_id): + 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 _sensor_name(self): + return self._sensor["name"] + + @property + def _sensor_value(self): + return self._sensor["data"]["value"] + + @property + def _sensor_type(self): + return self._sensor["data"]["name"] + + @property + def _battery_level(self): + 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): + sensor_last_updated = self._sensor.get("lastUpdated") + return str(datetime.fromtimestamp(sensor_last_updated)) \ + if sensor_last_updated else None + + @property + def _value_as_temperature(self): + return round(float(self._sensor_value), 1) + + @property + def _value_as_humidity(self): + return int(round(float(self._sensor_value))) @property def name(self): """ Returns the name of the device. """ - return self._name + return "{} {}".format(self._sensor_name or DEVICE_DEFAULT_NAME, + self.quantity_name) + + @property + def available(self): + return not self._sensor.get("offline", False) @property def state(self): """ Returns the state of the device. """ - return self._state + if self._sensor_type == SENSOR_TYPE_TEMP: + return self._value_as_temperature + elif self._sensor_type == SENSOR_TYPE_HUMIDITY: + return self._value_as_humidity @property def state_attributes(self): attrs = dict() if self._battery_level is not None: attrs[ATTR_BATTERY_LEVEL] = self._battery_level - if self._last_update is not None: - attrs[ATTR_LAST_UPDATED] = self._last_update + if self._last_updated is not None: + attrs[ATTR_LAST_UPDATED] = self._last_updated return attrs + @property + def quantity_name(self): + """ name of quantity """ + return SENSOR_TYPES[self._sensor_type][0] + @property def unit_of_measurement(self): return SENSOR_TYPES[self._sensor_type][1] @@ -98,18 +133,3 @@ class TelldusLiveSensor(Entity): @property def icon(self): return SENSOR_TYPES[self._sensor_type][2] - - def update(self): - values = tellduslive.NETWORK.get_sensor_value(self._sensor_id, - self._sensor_type) - self._state, self._battery_level, self._last_update = values - - self._state = float(self._state) - if self._sensor_type == SENSOR_TYPE_TEMP: - self._state = round(self._state, 1) - elif self._sensor_type == SENSOR_TYPE_HUMIDITY: - self._state = int(round(self._state)) - - self._battery_level = round(self._battery_level * 100 / 255) # percent - - self._last_update = str(datetime.fromtimestamp(self._last_update)) diff --git a/homeassistant/components/switch/tellduslive.py b/homeassistant/components/switch/tellduslive.py index b6c7af3ce12..7edab40054f 100644 --- a/homeassistant/components/switch/tellduslive.py +++ b/homeassistant/components/switch/tellduslive.py @@ -10,7 +10,6 @@ https://home-assistant.io/components/switch.tellduslive/ """ import logging -from homeassistant.const import (STATE_ON, STATE_OFF, STATE_UNKNOWN) from homeassistant.components import tellduslive from homeassistant.helpers.entity import ToggleEntity @@ -21,54 +20,45 @@ def setup_platform(hass, config, add_devices, discovery_info=None): """ Find and return Tellstick switches. """ if discovery_info is None: return - switches = tellduslive.NETWORK.get_switches() - add_devices([TelldusLiveSwitch(switch["name"], - switch["id"]) - for switch in switches if switch["type"] == "device"]) + add_devices(TelldusLiveSwitch(switch) for switch in discovery_info) class TelldusLiveSwitch(ToggleEntity): """ Represents a Tellstick switch. """ - def __init__(self, name, switch_id): - self._name = name + def __init__(self, switch_id): self._id = switch_id - self._state = STATE_UNKNOWN self.update() + _LOGGER.debug("created switch %s", self) + + def update(self): + tellduslive.NETWORK.update_switches() + self._switch = tellduslive.NETWORK.get_switch(self._id) @property def should_poll(self): """ Tells Home Assistant to poll this entity. """ - return False + return True @property def name(self): """ Returns the name of the switch if any. """ - return self._name + return self._switch["name"] - def update(self): - from tellive.live import const - state = tellduslive.NETWORK.get_switch_state(self._id) - if state == const.TELLSTICK_TURNON: - self._state = STATE_ON - elif state == const.TELLSTICK_TURNOFF: - self._state = STATE_OFF - else: - self._state = STATE_UNKNOWN + @property + def available(self): + return not self._switch.get("offline", False) @property def is_on(self): """ True if switch is on. """ - return self._state == STATE_ON + from tellive.live import const + return self._switch["state"] == const.TELLSTICK_TURNON def turn_on(self, **kwargs): """ Turns the switch on. """ - if tellduslive.NETWORK.turn_switch_on(self._id): - self._state = STATE_ON - self.update_ha_state() + tellduslive.NETWORK.turn_switch_on(self._id) def turn_off(self, **kwargs): """ Turns the switch off. """ - if tellduslive.NETWORK.turn_switch_off(self._id): - self._state = STATE_OFF - self.update_ha_state() + tellduslive.NETWORK.turn_switch_off(self._id) diff --git a/homeassistant/components/tellduslive.py b/homeassistant/components/tellduslive.py index 191819ee1b2..18e2af54890 100644 --- a/homeassistant/components/tellduslive.py +++ b/homeassistant/components/tellduslive.py @@ -17,141 +17,201 @@ from homeassistant.const import ( EVENT_PLATFORM_DISCOVERED, ATTR_SERVICE, ATTR_DISCOVERED) DOMAIN = "tellduslive" -DISCOVER_SWITCHES = "tellduslive.switches" + +REQUIREMENTS = ['tellive-py==0.5.2'] + +_LOGGER = logging.getLogger(__name__) + DISCOVER_SENSORS = "tellduslive.sensors" +DISCOVER_SWITCHES = "tellduslive.switches" +DISCOVERY_TYPES = {"sensor": DISCOVER_SENSORS, + "switch": DISCOVER_SWITCHES} + CONF_PUBLIC_KEY = "public_key" CONF_PRIVATE_KEY = "private_key" CONF_TOKEN = "token" CONF_TOKEN_SECRET = "token_secret" -REQUIREMENTS = ['tellive-py==0.5.2'] -_LOGGER = logging.getLogger(__name__) +MIN_TIME_BETWEEN_SWITCH_UPDATES = timedelta(minutes=1) +MIN_TIME_BETWEEN_SENSOR_UPDATES = timedelta(minutes=5) NETWORK = None -# Return cached results if last scan was less then this time ago -MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=600) + +@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")["device"] + # filter out any group of switches + switches = {switch["id"]: switch for switch in switches + if switch["type"] == "device"} + return switches + + +@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")["sensor"] + # one unit can contain many sensors + sensors = {unit["id"]+sensor["name"]: dict(unit, data=sensor) + for unit in units + for sensor in unit["data"]} + return sensors class TelldusLiveData(object): """ Gets the latest data and update the states. """ def __init__(self, hass, config): - 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 - from tellive.live import TelldusLive - self._sensors = [] - self._switches = [] + self._switches = {} + self._sensors = {} + + 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._api = TelldusLive(self._client) - def update(self, hass, config): - """ Send discovery event if component not yet discovered. """ - self._update_sensors() - self._update_switches() - for component_name, found_devices, discovery_type in \ - (('sensor', self._sensors, DISCOVER_SENSORS), - ('switch', self._switches, DISCOVER_SWITCHES)): - if len(found_devices): - component = get_component(component_name) - bootstrap.setup_component(hass, component.DOMAIN, config) - hass.bus.fire(EVENT_PLATFORM_DISCOVERED, - {ATTR_SERVICE: discovery_type, - ATTR_DISCOVERED: {}}) + def validate_session(self): + """ Make a dummy request to see if the session is valid """ + try: + return 'email' in self.request("user/profile") + except RuntimeError: + return False - def _request(self, what, **params): - """ Sends a request to the Tellstick Live API. """ + 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 len(found_devices): + return + + _LOGGER.info("discovered %d new %s devices", + len(found_devices), component_name) + + component = get_component(component_name) + bootstrap.setup_component(self._hass, + component.DOMAIN, + self._config) + + discovery_type = DISCOVERY_TYPES[component_name] + + self._hass.bus.fire(EVENT_PLATFORM_DISCOVERED, + {ATTR_SERVICE: discovery_type, + ATTR_DISCOVERED: found_devices}) + + def request(self, what, **params): + """ Sends a request to the tellstick live API """ from tellive.live import const supported_methods = const.TELLSTICK_TURNON \ | const.TELLSTICK_TURNOFF \ - | const.TELLSTICK_TOGGLE + | 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} - + "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 + response = self._client.request(what, params) + + _LOGGER.debug("got response %s", response) return response - def check_request(self, what, **params): - """ Make request, check result if successful. """ - response = self._request(what, **params) - return response['status'] == "success" + def update_devices(self, + local_devices, + remote_devices, + component_name): + """ update local device list and discover new devices """ - def validate_session(self): - """ Make a dummy request to see if the session is valid. """ + if remote_devices is None: + return local_devices + + remote_ids = remote_devices.keys() + local_ids = local_devices.keys() + + added_devices = list(remote_ids - local_ids) + self._discover(added_devices, + component_name) + + removed_devices = list(local_ids - remote_ids) + remote_devices.update({id: dict(local_devices[id], offline=True) + for id in removed_devices}) + + return remote_devices + + def update_sensors(self): + """ update local list of sensors """ try: - response = self._request("user/profile") - return 'email' in response - except RuntimeError: - return False + self._sensors = self.update_devices(self._sensors, + request_sensors(), + "sensor") + except OSError: + _LOGGER.warning("could not update sensors") - @Throttle(MIN_TIME_BETWEEN_UPDATES) - def _update_sensors(self): - """ Get the latest sensor data from Telldus Live. """ - _LOGGER.info("Updating sensors from Telldus Live") - self._sensors = self._request("sensors/list")["sensor"] + def update_switches(self): + """ update local list of switches """ + try: + self._switches = self.update_devices(self._switches, + request_switches(), + "switch") + except OSError: + _LOGGER.warning("could not update switches") - def _update_switches(self): - """ Get the configured switches from Telldus Live. """ - _LOGGER.info("Updating switches from Telldus Live") - self._switches = self._request("devices/list")["device"] - # filter out any group of switches - self._switches = [switch for switch in self._switches - if switch["type"] == "device"] + 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_sensors(self): - """ Get the configured sensors. """ - self._update_sensors() - return self._sensors + def get_switch(self, switch_id): + """ return switch representation """ + return self._switches[switch_id] - def get_switches(self): - """ Get the configured switches. """ - self._update_switches() - return self._switches - - def get_sensor_value(self, sensor_id, sensor_name): - """ Get the latest (possibly cached) sensor value. """ - self._update_sensors() - for component in self._sensors: - if component["id"] == sensor_id: - for sensor in component["data"]: - if sensor["name"] == sensor_name: - return (sensor["value"], - component["battery"], - component["lastUpdated"]) - - def get_switch_state(self, switch_id): - """ Returns the state of an switch. """ - _LOGGER.info("Updating switch state from Telldus Live") - response = self._request("device/info", id=switch_id)["state"] - return int(response) + def get_sensor(self, sensor_id): + """ return sensor representation """ + return self._sensors[sensor_id] def turn_switch_on(self, switch_id): """ Turn switch off. """ - return self.check_request("device/turnOn", id=switch_id) + if self._check_request("device/turnOn", id=switch_id): + from tellive.live import const + self.get_switch(switch_id)["state"] = const.TELLSTICK_TURNON def turn_switch_off(self, switch_id): """ Turn switch on. """ - return self.check_request("device/turnOff", id=switch_id) + if self._check_request("device/turnOff", id=switch_id): + from tellive.live import const + self.get_switch(switch_id)["state"] = const.TELLSTICK_TURNOFF def setup(hass, config): @@ -181,6 +241,6 @@ def setup(hass, config): "that can be aquired from https://api.telldus.com/keys/index") return False - NETWORK.update(hass, config) + NETWORK.discover() return True