diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index b169c0d44d5..75a5f3767da 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -178,7 +178,7 @@ def setup(hass, config): _LOGGER.info("Updating light states") for light in lights.values(): - light.update_ha_state(hass) + light.update_ha_state(hass, True) update_lights_state(None) @@ -196,7 +196,7 @@ def setup(hass, config): for light in discovered: if light is not None and light not in lights.values(): light.entity_id = util.ensure_unique_string( - ENTITY_ID_FORMAT.format(util.slugify(light.get_name())), + ENTITY_ID_FORMAT.format(util.slugify(light.name)), lights.keys()) lights[light.entity_id] = light diff --git a/homeassistant/components/light/wink.py b/homeassistant/components/light/wink.py index 061ff06b7f8..ebfdc44fff1 100644 --- a/homeassistant/components/light/wink.py +++ b/homeassistant/components/light/wink.py @@ -4,8 +4,9 @@ import logging # pylint: disable=no-name-in-module, import-error import homeassistant.external.wink.pywink as pywink -from homeassistant.helpers import ToggleDevice -from homeassistant.const import ATTR_FRIENDLY_NAME, CONF_ACCESS_TOKEN +from homeassistant.components.light import ATTR_BRIGHTNESS +from homeassistant.components.wink import WinkToggleDevice +from homeassistant.const import CONF_ACCESS_TOKEN # pylint: disable=unused-argument @@ -35,29 +36,28 @@ def get_lights(): return [WinkLight(light) for light in pywink.get_bulbs()] -class WinkLight(ToggleDevice): +class WinkLight(WinkToggleDevice): """ Represents a Wink light """ - def __init__(self, wink): - self.wink = wink - self.state_attr = {ATTR_FRIENDLY_NAME: wink.name()} - - def get_name(self): - """ Returns the name of the light if any. """ - return self.wink.name() - + # pylint: disable=too-few-public-methods def turn_on(self, **kwargs): - """ Turns the light on. """ - self.wink.setState(True) + """ Turns the switch on. """ + brightness = kwargs.get(ATTR_BRIGHTNESS) - def turn_off(self): - """ Turns the light off. """ - self.wink.setState(False) + if brightness is not None: + self.wink.setState(True, brightness / 255) - def is_on(self): - """ True if light is on. """ - return self.wink.state() + else: + self.wink.setState(True) - def get_state_attributes(self): - """ Returns optional state attributes. """ - return self.state_attr + @property + def state_attributes(self): + attr = super().state_attributes + + if self.is_on: + brightness = self.wink.brightness() + + if brightness is not None: + attr[ATTR_BRIGHTNESS] = int(brightness * 255) + + return attr diff --git a/homeassistant/components/switch/__init__.py b/homeassistant/components/switch/__init__.py index 13bfa075932..5d772f9444a 100644 --- a/homeassistant/components/switch/__init__.py +++ b/homeassistant/components/switch/__init__.py @@ -73,7 +73,7 @@ def setup(hass, config): logger.info("Updating switch states") for switch in switches.values(): - switch.update_ha_state(hass) + switch.update_ha_state(hass, True) update_states(None) diff --git a/homeassistant/components/switch/wemo.py b/homeassistant/components/switch/wemo.py index bb6290526f7..e6dce192e22 100644 --- a/homeassistant/components/switch/wemo.py +++ b/homeassistant/components/switch/wemo.py @@ -86,7 +86,7 @@ class WemoSwitch(ToggleDevice): @property def is_on(self): """ True if switch is on. """ - return self.wemo.get_state(True) + return self.wemo.get_state() def turn_on(self, **kwargs): """ Turns the switch on. """ @@ -95,3 +95,7 @@ class WemoSwitch(ToggleDevice): def turn_off(self): """ Turns the switch off. """ self.wemo.off() + + def update(self): + """ Update Wemo state. """ + self.wemo.get_state(True) diff --git a/homeassistant/components/switch/wink.py b/homeassistant/components/switch/wink.py index edc404781fe..58faed9a2a5 100644 --- a/homeassistant/components/switch/wink.py +++ b/homeassistant/components/switch/wink.py @@ -4,8 +4,8 @@ import logging # pylint: disable=no-name-in-module, import-error import homeassistant.external.wink.pywink as pywink -from homeassistant.helpers import ToggleDevice -from homeassistant.const import ATTR_FRIENDLY_NAME, CONF_ACCESS_TOKEN +from homeassistant.components.wink import WinkToggleDevice +from homeassistant.const import CONF_ACCESS_TOKEN # pylint: disable=unused-argument @@ -32,32 +32,4 @@ def devices_discovered(hass, config, info): def get_switches(): """ Returns the Wink switches. """ - return [WinkSwitch(switch) for switch in pywink.get_switches()] - - -class WinkSwitch(ToggleDevice): - """ represents a WeMo switch within home assistant. """ - - def __init__(self, wink): - self.wink = wink - self.state_attr = {ATTR_FRIENDLY_NAME: wink.name()} - - def get_name(self): - """ Returns the name of the switch if any. """ - return self.wink.name() - - def turn_on(self, **kwargs): - """ Turns the switch on. """ - self.wink.setState(True) - - def turn_off(self): - """ Turns the switch off. """ - self.wink.setState(False) - - def is_on(self): - """ True if switch is on. """ - return self.wink.state() - - def get_state_attributes(self): - """ Returns optional state attributes. """ - return self.state_attr + return [WinkToggleDevice(switch) for switch in pywink.get_switches()] diff --git a/homeassistant/components/wink.py b/homeassistant/components/wink.py index 0ccabd27f73..d049d1ecc26 100644 --- a/homeassistant/components/wink.py +++ b/homeassistant/components/wink.py @@ -8,10 +8,10 @@ import homeassistant.external.wink.pywink as pywink from homeassistant import bootstrap from homeassistant.loader import get_component -from homeassistant.helpers import validate_config +from homeassistant.helpers import validate_config, ToggleDevice from homeassistant.const import ( - EVENT_PLATFORM_DISCOVERED, ATTR_SERVICE, ATTR_DISCOVERED, - CONF_ACCESS_TOKEN) + EVENT_PLATFORM_DISCOVERED, CONF_ACCESS_TOKEN, + ATTR_SERVICE, ATTR_DISCOVERED, ATTR_FRIENDLY_NAME) DOMAIN = "wink" DEPENDENCIES = [] @@ -48,3 +48,44 @@ def setup(hass, config): }) return True + + +class WinkToggleDevice(ToggleDevice): + """ represents a WeMo switch within home assistant. """ + + def __init__(self, wink): + self.wink = wink + + @property + def unique_id(self): + """ Returns the id of this WeMo switch """ + return "{}.{}".format(self.__class__, self.wink.deviceId()) + + @property + def name(self): + """ Returns the name of the light if any. """ + return self.wink.name() + + @property + def is_on(self): + """ True if light is on. """ + return self.wink.state() + + @property + def state_attributes(self): + """ Returns optional state attributes. """ + return { + ATTR_FRIENDLY_NAME: self.wink.name() + } + + def turn_on(self, **kwargs): + """ Turns the switch on. """ + self.wink.setState(True) + + def turn_off(self): + """ Turns the switch off. """ + self.wink.setState(False) + + def update(self): + """ Update state of the light. """ + self.wink.wait_till_desired_reached() diff --git a/homeassistant/external/wink/pywink.py b/homeassistant/external/wink/pywink.py index 3131873a9e2..7d44050f55c 100644 --- a/homeassistant/external/wink/pywink.py +++ b/homeassistant/external/wink/pywink.py @@ -1,21 +1,16 @@ __author__ = 'JOHNMCL' import json +import time import requests - baseUrl = "https://winkapi.quirky.com" -object_type = "light_bulb" -object_type_plural = "light_bulbs" - -bearer_token="" - headers = {} -class wink_binary_switch(): +class wink_binary_switch(object): """ represents a wink.py switch json_obj holds the json stat at init (and if there is a refresh it's updated it's the native format for this objects methods @@ -85,14 +80,11 @@ class wink_binary_switch(): } """ - jsonState = {} - - - - def __init__(self, aJSonObj): + def __init__(self, aJSonObj, objectprefix="binary_switches"): self.jsonState = aJSonObj - self.objectprefix = "binary_switches" - + self.objectprefix = objectprefix + # Tuple (desired state, time) + self._last_call = (0, None) def __str__(self): return "%s %s %s" % (self.name(), self.deviceId(), self.state()) @@ -100,13 +92,23 @@ class wink_binary_switch(): def __repr__(self): return "" % (self.name(), self.deviceId(), self.state()) + @property + def _last_reading(self): + return self.jsonState.get('last_reading') or {} + def name(self): - name = self.jsonState.get('name') - return name or "Unknown Name" + return self.jsonState.get('name', "Unknown Name") def state(self): - state = self.jsonState.get('desired_state').get('powered') - return state + # Optimistic approach to setState: + # Within 15 seconds of a call to setState we assume it worked. + if self._recent_state_set(): + return self._last_call[1] + + return self._last_reading.get('powered', False) + + def deviceId(self): + return self.jsonState.get('binary_switch_id', self.name()) def setState(self, state): """ @@ -115,20 +117,47 @@ class wink_binary_switch(): """ urlString = baseUrl + "/%s/%s" % (self.objectprefix, self.deviceId()) values = {"desired_state": {"powered": state}} - urlString = baseUrl + "/%s/%s" % (self.objectprefix, self.deviceId()) arequest = requests.put(urlString, data=json.dumps(values), headers=headers) self._updateStateFromResponse(arequest.json()) + self._last_call = (time.time(), state) - def deviceId(self): - deviceId = self.jsonState.get('binary_switch_id') - return deviceId or "Unknown Device ID" + def refresh_state_at_hub(self): + """ + Tell hub to query latest status from device and upload to Wink. + PS: Not sure if this even works.. + """ + urlString = baseUrl + "/%s/%s/refresh" % (self.objectprefix, self.deviceId()) + requests.get(urlString, headers=headers) def updateState(self): + """ Update state with latest info from Wink API. """ urlString = baseUrl + "/%s/%s" % (self.objectprefix, self.deviceId()) arequest = requests.get(urlString, headers=headers) self._updateStateFromResponse(arequest.json()) + def wait_till_desired_reached(self): + """ Wait till desired state reached. Max 10s. """ + if self._recent_state_set(): + return + + # self.refresh_state_at_hub() + tries = 1 + + while True: + self.updateState() + last_read = self._last_reading + + if last_read.get('desired_powered') == last_read.get('powered') \ + or tries == 5: + break + + time.sleep(2) + + tries += 1 + self.updateState() + last_read = self._last_reading + def _updateStateFromResponse(self, response_json): """ :param response_json: the json obj returned from query @@ -136,6 +165,10 @@ class wink_binary_switch(): """ self.jsonState = response_json.get('data') + def _recent_state_set(self): + return time.time() - self._last_call[0] < 15 + + class wink_bulb(wink_binary_switch): """ represents a wink.py bulb json_obj holds the json stat at init (and if there is a refresh it's updated @@ -143,77 +176,76 @@ class wink_bulb(wink_binary_switch): and looks like so: "light_bulb_id": "33990", - "name": "downstaurs lamp", - "locale": "en_us", - "units":{}, - "created_at": 1410925804, - "hidden_at": null, - "capabilities":{}, - "subscription":{}, - "triggers":[], - "desired_state":{"powered": true, "brightness": 1}, - "manufacturer_device_model": "lutron_p_pkg1_w_wh_d", - "manufacturer_device_id": null, - "device_manufacturer": "lutron", - "model_name": "Caseta Wireless Dimmer & Pico", - "upc_id": "3", - "hub_id": "11780", - "local_id": "8", - "radio_type": "lutron", - "linked_service_id": null, - "last_reading":{ - "brightness": 1, - "brightness_updated_at": 1417823487.490747, - "connection": true, - "connection_updated_at": 1417823487.4907365, - "powered": true, - "powered_updated_at": 1417823487.4907532, - "desired_powered": true, - "desired_powered_updated_at": 1417823485.054675, - "desired_brightness": 1, - "desired_brightness_updated_at": 1417409293.2591703 - }, - "lat_lng":[38.429962, -122.653715], - "location": "", - "order": 0 + "name": "downstaurs lamp", + "locale": "en_us", + "units":{}, + "created_at": 1410925804, + "hidden_at": null, + "capabilities":{}, + "subscription":{}, + "triggers":[], + "desired_state":{"powered": true, "brightness": 1}, + "manufacturer_device_model": "lutron_p_pkg1_w_wh_d", + "manufacturer_device_id": null, + "device_manufacturer": "lutron", + "model_name": "Caseta Wireless Dimmer & Pico", + "upc_id": "3", + "hub_id": "11780", + "local_id": "8", + "radio_type": "lutron", + "linked_service_id": null, + "last_reading":{ + "brightness": 1, + "brightness_updated_at": 1417823487.490747, + "connection": true, + "connection_updated_at": 1417823487.4907365, + "powered": true, + "powered_updated_at": 1417823487.4907532, + "desired_powered": true, + "desired_powered_updated_at": 1417823485.054675, + "desired_brightness": 1, + "desired_brightness_updated_at": 1417409293.2591703 + }, + "lat_lng":[38.429962, -122.653715], + "location": "", + "order": 0 """ jsonState = {} def __init__(self, ajsonobj): - self.jsonState = ajsonobj - self.objectprefix = "light_bulbs" + super().__init__(ajsonobj, "light_bulbs") - def __str__(self): - return "%s %s %s" % (self.name(), self.deviceId(), self.state()) + def deviceId(self): + return self.jsonState.get('light_bulb_id', self.name()) - def __repr__(self): - return "" % (self.name(), self.deviceId(), self.state()) + def brightness(self): + return self._last_reading.get('brightness') - def name(self): - name = self.jsonState.get('name') - return name or "Unknown Name" - - def state(self): - state = self.jsonState.get('desired_state').get('powered') - return state - - def setState(self, state): + def setState(self, state, brightness=None): """ :param state: a boolean of true (on) or false ('off') :return: nothing """ urlString = baseUrl + "/light_bulbs/%s" % self.deviceId() - values = {"desired_state": {"desired_powered": state, "powered": state}} + values = { + "desired_state": { + "powered": state + } + } + + if brightness is not None: + values["desired_state"]["brightness"] = brightness + urlString = baseUrl + "/light_bulbs/%s" % self.deviceId() arequest = requests.put(urlString, data=json.dumps(values), headers=headers) + self._updateStateFromResponse(arequest.json()) - self.updateState() + self._last_call = (time.time(), state) - - def deviceId(self): - deviceId = self.jsonState.get('light_bulb_id') - return deviceId or "Unknown Device ID" + def __repr__(self): + return "" % ( + self.name(), self.deviceId(), self.state()) def get_bulbs_and_switches(): @@ -243,7 +275,7 @@ def get_bulbs(): switches = [] for item in items: id = item.get('light_bulb_id') - if id != None: + if id is not None: switches.append(wink_bulb(item)) return switches @@ -258,15 +290,19 @@ def get_switches(): switches = [] for item in items: id = item.get('binary_switch_id') - if id != None: + if id is not None: switches.append(wink_binary_switch(item)) return switches + def set_bearer_token(token): global headers - bearer_token=token - headers={"Content-Type": "application/json", "Authorization": "Bearer {}".format(token)} + + headers = { + "Content-Type": "application/json", + "Authorization": "Bearer {}".format(token) + } if __name__ == "__main__": sw = get_bulbs()