Bug fixes for Wink

This commit is contained in:
Paulus Schoutsen 2015-01-15 21:25:24 -08:00
parent c116cb095d
commit 702498ca09
7 changed files with 193 additions and 140 deletions

View File

@ -178,7 +178,7 @@ def setup(hass, config):
_LOGGER.info("Updating light states") _LOGGER.info("Updating light states")
for light in lights.values(): for light in lights.values():
light.update_ha_state(hass) light.update_ha_state(hass, True)
update_lights_state(None) update_lights_state(None)
@ -196,7 +196,7 @@ def setup(hass, config):
for light in discovered: for light in discovered:
if light is not None and light not in lights.values(): if light is not None and light not in lights.values():
light.entity_id = util.ensure_unique_string( 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.keys())
lights[light.entity_id] = light lights[light.entity_id] = light

View File

@ -4,8 +4,9 @@ import logging
# pylint: disable=no-name-in-module, import-error # pylint: disable=no-name-in-module, import-error
import homeassistant.external.wink.pywink as pywink import homeassistant.external.wink.pywink as pywink
from homeassistant.helpers import ToggleDevice from homeassistant.components.light import ATTR_BRIGHTNESS
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 # pylint: disable=unused-argument
@ -35,29 +36,28 @@ def get_lights():
return [WinkLight(light) for light in pywink.get_bulbs()] return [WinkLight(light) for light in pywink.get_bulbs()]
class WinkLight(ToggleDevice): class WinkLight(WinkToggleDevice):
""" Represents a Wink light """ """ Represents a Wink light """
def __init__(self, wink): # pylint: disable=too-few-public-methods
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()
def turn_on(self, **kwargs): def turn_on(self, **kwargs):
""" Turns the light on. """ """ Turns the switch on. """
self.wink.setState(True) brightness = kwargs.get(ATTR_BRIGHTNESS)
def turn_off(self): if brightness is not None:
""" Turns the light off. """ self.wink.setState(True, brightness / 255)
self.wink.setState(False)
def is_on(self): else:
""" True if light is on. """ self.wink.setState(True)
return self.wink.state()
def get_state_attributes(self): @property
""" Returns optional state attributes. """ def state_attributes(self):
return self.state_attr 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

View File

@ -73,7 +73,7 @@ def setup(hass, config):
logger.info("Updating switch states") logger.info("Updating switch states")
for switch in switches.values(): for switch in switches.values():
switch.update_ha_state(hass) switch.update_ha_state(hass, True)
update_states(None) update_states(None)

View File

@ -86,7 +86,7 @@ class WemoSwitch(ToggleDevice):
@property @property
def is_on(self): def is_on(self):
""" True if switch is on. """ """ True if switch is on. """
return self.wemo.get_state(True) return self.wemo.get_state()
def turn_on(self, **kwargs): def turn_on(self, **kwargs):
""" Turns the switch on. """ """ Turns the switch on. """
@ -95,3 +95,7 @@ class WemoSwitch(ToggleDevice):
def turn_off(self): def turn_off(self):
""" Turns the switch off. """ """ Turns the switch off. """
self.wemo.off() self.wemo.off()
def update(self):
""" Update Wemo state. """
self.wemo.get_state(True)

View File

@ -4,8 +4,8 @@ import logging
# pylint: disable=no-name-in-module, import-error # pylint: disable=no-name-in-module, import-error
import homeassistant.external.wink.pywink as pywink import homeassistant.external.wink.pywink as pywink
from homeassistant.helpers import ToggleDevice from homeassistant.components.wink import WinkToggleDevice
from homeassistant.const import ATTR_FRIENDLY_NAME, CONF_ACCESS_TOKEN from homeassistant.const import CONF_ACCESS_TOKEN
# pylint: disable=unused-argument # pylint: disable=unused-argument
@ -32,32 +32,4 @@ def devices_discovered(hass, config, info):
def get_switches(): def get_switches():
""" Returns the Wink switches. """ """ Returns the Wink switches. """
return [WinkSwitch(switch) for switch in pywink.get_switches()] return [WinkToggleDevice(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

View File

@ -8,10 +8,10 @@ import homeassistant.external.wink.pywink as pywink
from homeassistant import bootstrap from homeassistant import bootstrap
from homeassistant.loader import get_component from homeassistant.loader import get_component
from homeassistant.helpers import validate_config from homeassistant.helpers import validate_config, ToggleDevice
from homeassistant.const import ( from homeassistant.const import (
EVENT_PLATFORM_DISCOVERED, ATTR_SERVICE, ATTR_DISCOVERED, EVENT_PLATFORM_DISCOVERED, CONF_ACCESS_TOKEN,
CONF_ACCESS_TOKEN) ATTR_SERVICE, ATTR_DISCOVERED, ATTR_FRIENDLY_NAME)
DOMAIN = "wink" DOMAIN = "wink"
DEPENDENCIES = [] DEPENDENCIES = []
@ -48,3 +48,44 @@ def setup(hass, config):
}) })
return True 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()

View File

@ -1,21 +1,16 @@
__author__ = 'JOHNMCL' __author__ = 'JOHNMCL'
import json import json
import time
import requests import requests
baseUrl = "https://winkapi.quirky.com" baseUrl = "https://winkapi.quirky.com"
object_type = "light_bulb"
object_type_plural = "light_bulbs"
bearer_token=""
headers = {} headers = {}
class wink_binary_switch(): class wink_binary_switch(object):
""" represents a wink.py switch """ represents a wink.py switch
json_obj holds the json stat at init (and if there is a refresh it's updated 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 it's the native format for this objects methods
@ -85,14 +80,11 @@ class wink_binary_switch():
} }
""" """
jsonState = {} def __init__(self, aJSonObj, objectprefix="binary_switches"):
def __init__(self, aJSonObj):
self.jsonState = aJSonObj self.jsonState = aJSonObj
self.objectprefix = "binary_switches" self.objectprefix = objectprefix
# Tuple (desired state, time)
self._last_call = (0, None)
def __str__(self): def __str__(self):
return "%s %s %s" % (self.name(), self.deviceId(), self.state()) return "%s %s %s" % (self.name(), self.deviceId(), self.state())
@ -100,13 +92,23 @@ class wink_binary_switch():
def __repr__(self): def __repr__(self):
return "<Wink switch %s %s %s>" % (self.name(), self.deviceId(), self.state()) return "<Wink switch %s %s %s>" % (self.name(), self.deviceId(), self.state())
@property
def _last_reading(self):
return self.jsonState.get('last_reading') or {}
def name(self): def name(self):
name = self.jsonState.get('name') return self.jsonState.get('name', "Unknown Name")
return name or "Unknown Name"
def state(self): def state(self):
state = self.jsonState.get('desired_state').get('powered') # Optimistic approach to setState:
return state # 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): def setState(self, state):
""" """
@ -115,20 +117,47 @@ class wink_binary_switch():
""" """
urlString = baseUrl + "/%s/%s" % (self.objectprefix, self.deviceId()) urlString = baseUrl + "/%s/%s" % (self.objectprefix, self.deviceId())
values = {"desired_state": {"powered": state}} values = {"desired_state": {"powered": state}}
urlString = baseUrl + "/%s/%s" % (self.objectprefix, self.deviceId())
arequest = requests.put(urlString, data=json.dumps(values), headers=headers) arequest = requests.put(urlString, data=json.dumps(values), headers=headers)
self._updateStateFromResponse(arequest.json()) self._updateStateFromResponse(arequest.json())
self._last_call = (time.time(), state)
def deviceId(self): def refresh_state_at_hub(self):
deviceId = self.jsonState.get('binary_switch_id') """
return deviceId or "Unknown Device ID" 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): def updateState(self):
""" Update state with latest info from Wink API. """
urlString = baseUrl + "/%s/%s" % (self.objectprefix, self.deviceId()) urlString = baseUrl + "/%s/%s" % (self.objectprefix, self.deviceId())
arequest = requests.get(urlString, headers=headers) arequest = requests.get(urlString, headers=headers)
self._updateStateFromResponse(arequest.json()) 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): def _updateStateFromResponse(self, response_json):
""" """
:param response_json: the json obj returned from query :param response_json: the json obj returned from query
@ -136,6 +165,10 @@ class wink_binary_switch():
""" """
self.jsonState = response_json.get('data') 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): class wink_bulb(wink_binary_switch):
""" represents a wink.py bulb """ represents a wink.py bulb
json_obj holds the json stat at init (and if there is a refresh it's updated 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: and looks like so:
"light_bulb_id": "33990", "light_bulb_id": "33990",
"name": "downstaurs lamp", "name": "downstaurs lamp",
"locale": "en_us", "locale": "en_us",
"units":{}, "units":{},
"created_at": 1410925804, "created_at": 1410925804,
"hidden_at": null, "hidden_at": null,
"capabilities":{}, "capabilities":{},
"subscription":{}, "subscription":{},
"triggers":[], "triggers":[],
"desired_state":{"powered": true, "brightness": 1}, "desired_state":{"powered": true, "brightness": 1},
"manufacturer_device_model": "lutron_p_pkg1_w_wh_d", "manufacturer_device_model": "lutron_p_pkg1_w_wh_d",
"manufacturer_device_id": null, "manufacturer_device_id": null,
"device_manufacturer": "lutron", "device_manufacturer": "lutron",
"model_name": "Caseta Wireless Dimmer & Pico", "model_name": "Caseta Wireless Dimmer & Pico",
"upc_id": "3", "upc_id": "3",
"hub_id": "11780", "hub_id": "11780",
"local_id": "8", "local_id": "8",
"radio_type": "lutron", "radio_type": "lutron",
"linked_service_id": null, "linked_service_id": null,
"last_reading":{ "last_reading":{
"brightness": 1, "brightness": 1,
"brightness_updated_at": 1417823487.490747, "brightness_updated_at": 1417823487.490747,
"connection": true, "connection": true,
"connection_updated_at": 1417823487.4907365, "connection_updated_at": 1417823487.4907365,
"powered": true, "powered": true,
"powered_updated_at": 1417823487.4907532, "powered_updated_at": 1417823487.4907532,
"desired_powered": true, "desired_powered": true,
"desired_powered_updated_at": 1417823485.054675, "desired_powered_updated_at": 1417823485.054675,
"desired_brightness": 1, "desired_brightness": 1,
"desired_brightness_updated_at": 1417409293.2591703 "desired_brightness_updated_at": 1417409293.2591703
}, },
"lat_lng":[38.429962, -122.653715], "lat_lng":[38.429962, -122.653715],
"location": "", "location": "",
"order": 0 "order": 0
""" """
jsonState = {} jsonState = {}
def __init__(self, ajsonobj): def __init__(self, ajsonobj):
self.jsonState = ajsonobj super().__init__(ajsonobj, "light_bulbs")
self.objectprefix = "light_bulbs"
def __str__(self): def deviceId(self):
return "%s %s %s" % (self.name(), self.deviceId(), self.state()) return self.jsonState.get('light_bulb_id', self.name())
def __repr__(self): def brightness(self):
return "<Wink Bulb %s %s %s>" % (self.name(), self.deviceId(), self.state()) return self._last_reading.get('brightness')
def name(self): def setState(self, state, brightness=None):
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):
""" """
:param state: a boolean of true (on) or false ('off') :param state: a boolean of true (on) or false ('off')
:return: nothing :return: nothing
""" """
urlString = baseUrl + "/light_bulbs/%s" % self.deviceId() 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() urlString = baseUrl + "/light_bulbs/%s" % self.deviceId()
arequest = requests.put(urlString, data=json.dumps(values), headers=headers) arequest = requests.put(urlString, data=json.dumps(values), headers=headers)
self._updateStateFromResponse(arequest.json())
self.updateState() self._last_call = (time.time(), state)
def __repr__(self):
def deviceId(self): return "<Wink Bulb %s %s %s>" % (
deviceId = self.jsonState.get('light_bulb_id') self.name(), self.deviceId(), self.state())
return deviceId or "Unknown Device ID"
def get_bulbs_and_switches(): def get_bulbs_and_switches():
@ -243,7 +275,7 @@ def get_bulbs():
switches = [] switches = []
for item in items: for item in items:
id = item.get('light_bulb_id') id = item.get('light_bulb_id')
if id != None: if id is not None:
switches.append(wink_bulb(item)) switches.append(wink_bulb(item))
return switches return switches
@ -258,15 +290,19 @@ def get_switches():
switches = [] switches = []
for item in items: for item in items:
id = item.get('binary_switch_id') id = item.get('binary_switch_id')
if id != None: if id is not None:
switches.append(wink_binary_switch(item)) switches.append(wink_binary_switch(item))
return switches return switches
def set_bearer_token(token): def set_bearer_token(token):
global headers 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__": if __name__ == "__main__":
sw = get_bulbs() sw = get_bulbs()