Merge branch 'dev'

* dev:
  Exclude external libraries from code coverage
  Bug fixes for Wink
  Better positioning of dialogs
  Added error handling in frontend debug forms
  Integrate add worker to bootstrap.setup_component
  Conditionally show widgets on light more info
  Rename the edit state button in more info to debug
  Expect devices to have no name
This commit is contained in:
Paulus Schoutsen 2015-01-15 21:39:43 -08:00
commit 93f93aff93
23 changed files with 356 additions and 223 deletions

View File

@ -7,6 +7,6 @@ install:
script: script:
- flake8 homeassistant --exclude bower_components,external - flake8 homeassistant --exclude bower_components,external
- pylint homeassistant - pylint homeassistant
- coverage run --source=homeassistant -m unittest discover tests - coverage run --source=homeassistant --omit "homeassistant/external/*" -m unittest discover tests
after_success: after_success:
- coveralls - coveralls

View File

@ -17,7 +17,7 @@ from collections import defaultdict
import homeassistant import homeassistant
import homeassistant.loader as loader import homeassistant.loader as loader
import homeassistant.components as core_components import homeassistant.components as core_components
import homeassistant.components.group as group
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -35,6 +35,11 @@ def setup_component(hass, domain, config=None):
_LOGGER.info("component %s initialized", domain) _LOGGER.info("component %s initialized", domain)
# Assumption: if a component does not depend on groups
# it communicates with devices
if group.DOMAIN not in component.DEPENDENCIES:
hass.pool.add_worker()
return True return True
else: else:
@ -75,18 +80,8 @@ def from_config_dict(config, hass=None):
_LOGGER.info("Home Assistant core initialized") _LOGGER.info("Home Assistant core initialized")
# Setup the components # Setup the components
# We assume that all components that load before the group component loads
# are components that poll devices. As their tasks are IO based, we will
# add an extra worker for each of them.
add_worker = True
for domain in loader.load_order_components(components): for domain in loader.load_order_components(components):
if setup_component(hass, domain, config): setup_component(hass, domain, config)
add_worker = add_worker and domain != "group"
if add_worker:
hass.pool.add_worker()
return hass return hass

View File

@ -68,8 +68,7 @@ def setup(hass, config):
logger.info("Found new service: %s %s", service, info) logger.info("Found new service: %s %s", service, info)
if component and component not in hass.components: if component and component not in hass.components:
if bootstrap.setup_component(hass, component, config): bootstrap.setup_component(hass, component, config)
hass.pool.add_worker()
hass.bus.fire(EVENT_PLATFORM_DISCOVERED, { hass.bus.fire(EVENT_PLATFORM_DISCOVERED, {
ATTR_SERVICE: service, ATTR_SERVICE: service,

View File

@ -1,2 +1,2 @@
""" DO NOT MODIFY. Auto-generated by build_frontend script """ """ DO NOT MODIFY. Auto-generated by build_frontend script """
VERSION = "b32ea78a1157b29555410384fe251dc9" VERSION = "a82c6e4bf6b91042a6e891d46464275d"

File diff suppressed because one or more lines are too long

View File

@ -26,7 +26,7 @@
Polymer({ Polymer({
cardClicked: function() { cardClicked: function() {
this.api.showStateCardDialog(this.stateObj.entity_id); this.api.showmoreInfoDialog(this.stateObj.entity_id);
}, },
}); });

View File

@ -10,7 +10,11 @@
<polymer-element name="event-fire-dialog" attributes="api"> <polymer-element name="event-fire-dialog" attributes="api">
<template> <template>
<ha-action-dialog id="dialog" heading="Fire Event" class='two-column'> <ha-action-dialog
id="dialog"
heading="Fire Event"
class='two-column'
closeSelector='[dismissive]'>
<div layout horizontal> <div layout horizontal>
<div class='ha-form'> <div class='ha-form'>
@ -51,7 +55,9 @@ Polymer({
this.setEventType(eventType); this.setEventType(eventType);
this.setEventData(eventData); this.setEventData(eventData);
this.$.dialog.toggle(); this.job('showDialogAfterRender', function() {
this.$.dialog.toggle();
}.bind(this));
}, },
setEventType: function(eventType) { setEventType: function(eventType) {
@ -68,15 +74,15 @@ Polymer({
}, },
clickFireEvent: function() { clickFireEvent: function() {
var data; try {
this.api.fire_event(
if(this.$.inputData.value !== "") { this.$.inputType.value,
data = JSON.parse(this.$.inputData.value); this.$.inputData.value ? JSON.parse(this.$.inputData.value) : {});
} else { this.$.dialog.close();
data = {};
} catch (err) {
alert("Error parsing JSON: " + err);
} }
this.api.fire_event(this.$.inputType.value, data);
} }
}); });
</script> </script>

View File

@ -13,12 +13,6 @@
layered: true, layered: true,
backdrop: true, backdrop: true,
transition: 'core-transition-bottom', transition: 'core-transition-bottom',
domReady: function() {
this.addEventListener('core-overlay-open-completed', function() {
this.resizeHandler();
}.bind(this));
},
}); });
</script> </script>
</polymer-element> </polymer-element>

View File

@ -4,7 +4,7 @@
<link rel="import" href="../cards/state-card-content.html"> <link rel="import" href="../cards/state-card-content.html">
<link rel="import" href="../more-infos/more-info-content.html"> <link rel="import" href="../more-infos/more-info-content.html">
<polymer-element name="state-card-dialog" attributes="api"> <polymer-element name="more-info-dialog" attributes="api">
<template> <template>
<ha-action-dialog id="dialog"> <ha-action-dialog id="dialog">
@ -20,8 +20,8 @@
<more-info-content stateObj="{{stateObj}}" api="{{api}}"></more-info-content> <more-info-content stateObj="{{stateObj}}" api="{{api}}"></more-info-content>
</div> </div>
<paper-button dismissive>Dismiss</paper-button> <paper-button dismissive on-click={{editClicked}}>Debug</paper-button>
<paper-button affirmative on-click={{editClicked}}>Edit State</paper-button> <paper-button affirmative>Dismiss</paper-button>
</ha-action-dialog> </ha-action-dialog>
</template> </template>
@ -29,15 +29,29 @@
Polymer({ Polymer({
stateObj: {}, stateObj: {},
// domReady: function() { observe: {
// this.$.dialog.addEventListener('core-overlay-open-completed', function() { 'stateObj.attributes': 'reposition'
// this.$.dialog.resizeHandler(); },
// }.bind(this));
// }, /**
* Whenever the attributes change, the more info component can
* hide or show elements. We will reposition the dialog.
* DISABLED FOR NOW - BAD UX
*/
reposition: function(oldVal, newVal) {
// Only resize if already open
if(this.$.dialog.opened) {
this.job('resizeAfterLayoutChange', function() {
this.$.dialog.resizeHandler();
}.bind(this), 1000);
}
},
show: function(stateObj) { show: function(stateObj) {
this.stateObj = stateObj; this.stateObj = stateObj;
this.$.dialog.toggle(); this.job('showDialogAfterRender', function() {
this.$.dialog.toggle();
}.bind(this));
}, },
editClicked: function(ev) { editClicked: function(ev) {

View File

@ -10,7 +10,10 @@
<polymer-element name="service-call-dialog" attributes="api"> <polymer-element name="service-call-dialog" attributes="api">
<template> <template>
<ha-action-dialog id="dialog" heading="Call Service"> <ha-action-dialog
id="dialog"
heading="Call Service"
closeSelector='[dismissive]'>
<core-style ref='ha-dialog'></core-style> <core-style ref='ha-dialog'></core-style>
@ -45,14 +48,16 @@
Polymer({ Polymer({
ready: function() { ready: function() {
// to ensure callback methods work.. // to ensure callback methods work..
this.serviceSelected = this.serviceSelected.bind(this) this.serviceSelected = this.serviceSelected.bind(this);
}, },
show: function(domain, service, serviceData) { show: function(domain, service, serviceData) {
this.setService(domain, service); this.setService(domain, service);
this.$.inputData.value = serviceData; this.$.inputData.value = serviceData;
// this.$.inputDataWrapper.update(); // this.$.inputDataWrapper.update();
this.$.dialog.toggle(); this.job('showDialogAfterRender', function() {
this.$.dialog.toggle();
}.bind(this));
}, },
setService: function(domain, service) { setService: function(domain, service) {
@ -65,16 +70,17 @@ Polymer({
}, },
clickCallService: function() { clickCallService: function() {
var data; try {
if(this.$.inputData.value != "") {
data = JSON.parse(this.$.inputData.value);
}
this.api.call_service( this.api.call_service(
this.$.inputDomain.value, this.$.inputDomain.value,
this.$.inputService.value, this.$.inputService.value,
data); this.$.inputData.value ? JSON.parse(this.$.inputData.value) : {});
this.$.dialog.close();
} catch (err) {
alert("Error parsing JSON: " + err);
}
} }
}); });
</script> </script>

View File

@ -9,10 +9,18 @@
<polymer-element name="state-set-dialog" attributes="api"> <polymer-element name="state-set-dialog" attributes="api">
<template> <template>
<ha-action-dialog id="dialog" heading="Set State"> <ha-action-dialog
id="dialog"
heading="Set State"
closeSelector='[dismissive]'>
<core-style ref='ha-dialog'></core-style> <core-style ref='ha-dialog'></core-style>
<p>
This dialog will update the representation of the device within Home Assistant.<br />
This will not communicate with the actual device.
</p>
<div layout horizontal> <div layout horizontal>
<div class='ha-form'> <div class='ha-form'>
<paper-input id="inputEntityID" label="Entity ID" floatingLabel="true" autofocus required></paper-input> <paper-input id="inputEntityID" label="Entity ID" floatingLabel="true" autofocus required></paper-input>
@ -52,7 +60,9 @@ Polymer({
this.setState(state); this.setState(state);
this.setStateData(stateData); this.setStateData(stateData);
this.$.dialog.toggle(); this.job('showDialogAfterRender', function() {
this.$.dialog.toggle();
}.bind(this));
}, },
setEntityId: function(entityId) { setEntityId: function(entityId) {
@ -77,12 +87,18 @@ Polymer({
this.setStateData(state.attributes); this.setStateData(state.attributes);
}, },
clickSetState: function() { clickSetState: function(ev) {
this.api.set_state( try {
this.$.inputEntityID.value, this.api.set_state(
this.$.inputState.value, this.$.inputEntityID.value,
JSON.parse(this.$.inputData.value) this.$.inputState.value,
this.$.inputData.value ? JSON.parse(this.$.inputData.value) : {}
); );
this.$.dialog.close();
} catch (err) {
alert("Error parsing JSON: " + err);
}
} }
}); });
</script> </script>

View File

@ -6,7 +6,7 @@
<link rel="import" href="dialogs/event-fire-dialog.html"> <link rel="import" href="dialogs/event-fire-dialog.html">
<link rel="import" href="dialogs/service-call-dialog.html"> <link rel="import" href="dialogs/service-call-dialog.html">
<link rel="import" href="dialogs/state-set-dialog.html"> <link rel="import" href="dialogs/state-set-dialog.html">
<link rel="import" href="dialogs/state-card-dialog.html"> <link rel="import" href="dialogs/more-info-dialog.html">
<script> <script>
var ha = {}; var ha = {};
@ -36,7 +36,7 @@
<event-fire-dialog id="eventDialog" api={{api}}></event-fire-dialog> <event-fire-dialog id="eventDialog" api={{api}}></event-fire-dialog>
<service-call-dialog id="serviceDialog" api={{api}}></service-call-dialog> <service-call-dialog id="serviceDialog" api={{api}}></service-call-dialog>
<state-set-dialog id="stateSetDialog" api={{api}}></state-set-dialog> <state-set-dialog id="stateSetDialog" api={{api}}></state-set-dialog>
<state-card-dialog id="stateCardDialog" api={{api}}></state-card-dialog> <more-info-dialog id="moreInfoDialog" api={{api}}></more-info-dialog>
</template> </template>
<script> <script>
var domainsWithCard = ['thermostat']; var domainsWithCard = ['thermostat'];
@ -395,8 +395,8 @@
}, },
// show dialogs // show dialogs
showStateCardDialog: function(entityId) { showmoreInfoDialog: function(entityId) {
this.$.stateCardDialog.show(this.getState(entityId)); this.$.moreInfoDialog.show(this.getState(entityId));
}, },
showEditStateDialog: function(entityId) { showEditStateDialog: function(entityId) {

View File

@ -13,10 +13,16 @@
} }
</style> </style>
<div id='moreInfo'></div> <div id='moreInfo' class='{{classNames}}'></div>
</template> </template>
<script> <script>
Polymer({ Polymer({
classNames: '',
observe: {
'stateObj.attributes': 'stateAttributesChanged',
},
stateObjChanged: function() { stateObjChanged: function() {
while (this.$.moreInfo.lastChild) { while (this.$.moreInfo.lastChild) {
this.$.moreInfo.removeChild(this.$.moreInfo.lastChild); this.$.moreInfo.removeChild(this.$.moreInfo.lastChild);
@ -25,8 +31,14 @@ Polymer({
var moreInfo = document.createElement("more-info-" + this.stateObj.moreInfoType); var moreInfo = document.createElement("more-info-" + this.stateObj.moreInfoType);
moreInfo.api = this.api; moreInfo.api = this.api;
moreInfo.stateObj = this.stateObj; moreInfo.stateObj = this.stateObj;
this.$.moreInfo.appendChild(moreInfo); this.$.moreInfo.appendChild(moreInfo);
} },
stateAttributesChanged: function(oldVal, newVal) {
this.classNames = Object.keys(newVal).map(
function(key) { return "has-" + key; }).join(' ');
},
}); });
</script> </script>
</polymer-element> </polymer-element>

View File

@ -8,6 +8,10 @@
<style> <style>
.brightness { .brightness {
margin-bottom: 8px; margin-bottom: 8px;
max-height: 0px;
overflow: hidden;
transition: max-height .5s ease-in;
} }
.brightness paper-slider::shadow #sliderKnobInner, .brightness paper-slider::shadow #sliderKnobInner,
@ -19,16 +23,32 @@
display: block; display: block;
width: 350px; width: 350px;
margin: 0 auto; margin: 0 auto;
max-height: 0px;
overflow: hidden;
transition: max-height .5s ease-in .3s;
} }
:host-context(.has-brightness) .brightness {
max-height: 500px;
}
:host-context(.has-xy_color) color-picker {
max-height: 500px;
}
</style> </style>
<div> <div>
<div center horizontal layout class='brightness'> <div class='brightness'>
<div>Brightness</div> <div center horizontal layout>
<paper-slider <div>Brightness</div>
max="255" flex id='brightness' <paper-slider
on-core-change="{{brightnessSliderChanged}}"> max="255" flex id='brightness'
</paper-slider> on-core-change="{{brightnessSliderChanged}}">
</paper-slider>
</div>
</div> </div>
<color-picker id="colorpicker" width="350" height="200"> <color-picker id="colorpicker" width="350" height="200">
</color-picker> </color-picker>
</div> </div>
@ -48,7 +68,7 @@ Polymer({
brightnessChanged: function(oldVal, newVal) { brightnessChanged: function(oldVal, newVal) {
this.ignoreNextBrightnessEvent = true; this.ignoreNextBrightnessEvent = true;
this.$.brightness.value = newVal; this.$.brightness.value = newVal;
}, },
domReady: function() { domReady: function() {

View File

@ -8,6 +8,14 @@
font-family: RobotoDraft, 'Helvetica Neue', Helvetica, Arial; font-family: RobotoDraft, 'Helvetica Neue', Helvetica, Arial;
min-width: 350px; min-width: 350px;
max-width: 700px;
/* First two are from core-transition-bottom */
transition:
transform 0.2s ease-in-out,
opacity 0.2s ease-in,
top .3s,
left .3s !important;
} }
:host .sidebar { :host .sidebar {

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 = []
@ -39,9 +39,7 @@ def setup(hass, config):
# Ensure component is loaded # Ensure component is loaded
if component.DOMAIN not in hass.components: if component.DOMAIN not in hass.components:
# Add a worker on succesfull setup bootstrap.setup_component(hass, component.DOMAIN, config)
if bootstrap.setup_component(hass, component.DOMAIN, config):
hass.pool.add_worker()
# Fire discovery event # Fire discovery event
hass.bus.fire(EVENT_PLATFORM_DISCOVERED, { hass.bus.fire(EVENT_PLATFORM_DISCOVERED, {
@ -50,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()

View File

@ -153,11 +153,12 @@ def platform_devices_from_config(config, domain, hass,
no_name_count = 0 no_name_count = 0
for device in devices: for device in devices:
name = device.name # Get the name or set to default if none given
name = device.name or DEVICE_DEFAULT_NAME
if name == DEVICE_DEFAULT_NAME: if name == DEVICE_DEFAULT_NAME:
no_name_count += 1 no_name_count += 1
name = "{} #{}".format(domain, no_name_count) name = "{} {}".format(domain, no_name_count)
entity_id = ensure_unique_string( entity_id = ensure_unique_string(
entity_id_format.format(slugify(name)), entity_id_format.format(slugify(name)),