mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 16:57:53 +00:00
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:
commit
93f93aff93
@ -7,6 +7,6 @@ install:
|
||||
script:
|
||||
- flake8 homeassistant --exclude bower_components,external
|
||||
- pylint homeassistant
|
||||
- coverage run --source=homeassistant -m unittest discover tests
|
||||
- coverage run --source=homeassistant --omit "homeassistant/external/*" -m unittest discover tests
|
||||
after_success:
|
||||
- coveralls
|
||||
|
@ -17,7 +17,7 @@ from collections import defaultdict
|
||||
import homeassistant
|
||||
import homeassistant.loader as loader
|
||||
import homeassistant.components as core_components
|
||||
|
||||
import homeassistant.components.group as group
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -35,6 +35,11 @@ def setup_component(hass, domain, config=None):
|
||||
|
||||
_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
|
||||
|
||||
else:
|
||||
@ -75,18 +80,8 @@ def from_config_dict(config, hass=None):
|
||||
_LOGGER.info("Home Assistant core initialized")
|
||||
|
||||
# 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):
|
||||
if setup_component(hass, domain, config):
|
||||
add_worker = add_worker and domain != "group"
|
||||
|
||||
if add_worker:
|
||||
hass.pool.add_worker()
|
||||
setup_component(hass, domain, config)
|
||||
|
||||
return hass
|
||||
|
||||
|
@ -68,8 +68,7 @@ def setup(hass, config):
|
||||
logger.info("Found new service: %s %s", service, info)
|
||||
|
||||
if component and component not in hass.components:
|
||||
if bootstrap.setup_component(hass, component, config):
|
||||
hass.pool.add_worker()
|
||||
bootstrap.setup_component(hass, component, config)
|
||||
|
||||
hass.bus.fire(EVENT_PLATFORM_DISCOVERED, {
|
||||
ATTR_SERVICE: service,
|
||||
|
@ -1,2 +1,2 @@
|
||||
""" DO NOT MODIFY. Auto-generated by build_frontend script """
|
||||
VERSION = "b32ea78a1157b29555410384fe251dc9"
|
||||
VERSION = "a82c6e4bf6b91042a6e891d46464275d"
|
||||
|
File diff suppressed because one or more lines are too long
@ -26,7 +26,7 @@
|
||||
Polymer({
|
||||
|
||||
cardClicked: function() {
|
||||
this.api.showStateCardDialog(this.stateObj.entity_id);
|
||||
this.api.showmoreInfoDialog(this.stateObj.entity_id);
|
||||
},
|
||||
|
||||
});
|
||||
|
@ -10,7 +10,11 @@
|
||||
<polymer-element name="event-fire-dialog" attributes="api">
|
||||
<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 class='ha-form'>
|
||||
@ -51,7 +55,9 @@ Polymer({
|
||||
this.setEventType(eventType);
|
||||
this.setEventData(eventData);
|
||||
|
||||
this.$.dialog.toggle();
|
||||
this.job('showDialogAfterRender', function() {
|
||||
this.$.dialog.toggle();
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
setEventType: function(eventType) {
|
||||
@ -68,15 +74,15 @@ Polymer({
|
||||
},
|
||||
|
||||
clickFireEvent: function() {
|
||||
var data;
|
||||
|
||||
if(this.$.inputData.value !== "") {
|
||||
data = JSON.parse(this.$.inputData.value);
|
||||
} else {
|
||||
data = {};
|
||||
try {
|
||||
this.api.fire_event(
|
||||
this.$.inputType.value,
|
||||
this.$.inputData.value ? JSON.parse(this.$.inputData.value) : {});
|
||||
this.$.dialog.close();
|
||||
|
||||
} catch (err) {
|
||||
alert("Error parsing JSON: " + err);
|
||||
}
|
||||
|
||||
this.api.fire_event(this.$.inputType.value, data);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -13,12 +13,6 @@
|
||||
layered: true,
|
||||
backdrop: true,
|
||||
transition: 'core-transition-bottom',
|
||||
|
||||
domReady: function() {
|
||||
this.addEventListener('core-overlay-open-completed', function() {
|
||||
this.resizeHandler();
|
||||
}.bind(this));
|
||||
},
|
||||
});
|
||||
</script>
|
||||
</polymer-element>
|
||||
|
@ -4,7 +4,7 @@
|
||||
<link rel="import" href="../cards/state-card-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>
|
||||
<ha-action-dialog id="dialog">
|
||||
|
||||
@ -20,8 +20,8 @@
|
||||
<more-info-content stateObj="{{stateObj}}" api="{{api}}"></more-info-content>
|
||||
</div>
|
||||
|
||||
<paper-button dismissive>Dismiss</paper-button>
|
||||
<paper-button affirmative on-click={{editClicked}}>Edit State</paper-button>
|
||||
<paper-button dismissive on-click={{editClicked}}>Debug</paper-button>
|
||||
<paper-button affirmative>Dismiss</paper-button>
|
||||
</ha-action-dialog>
|
||||
|
||||
</template>
|
||||
@ -29,15 +29,29 @@
|
||||
Polymer({
|
||||
stateObj: {},
|
||||
|
||||
// domReady: function() {
|
||||
// this.$.dialog.addEventListener('core-overlay-open-completed', function() {
|
||||
// this.$.dialog.resizeHandler();
|
||||
// }.bind(this));
|
||||
// },
|
||||
observe: {
|
||||
'stateObj.attributes': 'reposition'
|
||||
},
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
this.stateObj = stateObj;
|
||||
this.$.dialog.toggle();
|
||||
this.job('showDialogAfterRender', function() {
|
||||
this.$.dialog.toggle();
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
editClicked: function(ev) {
|
@ -10,7 +10,10 @@
|
||||
<polymer-element name="service-call-dialog" attributes="api">
|
||||
<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>
|
||||
|
||||
@ -45,14 +48,16 @@
|
||||
Polymer({
|
||||
ready: function() {
|
||||
// to ensure callback methods work..
|
||||
this.serviceSelected = this.serviceSelected.bind(this)
|
||||
this.serviceSelected = this.serviceSelected.bind(this);
|
||||
},
|
||||
|
||||
show: function(domain, service, serviceData) {
|
||||
this.setService(domain, service);
|
||||
this.$.inputData.value = serviceData;
|
||||
// this.$.inputDataWrapper.update();
|
||||
this.$.dialog.toggle();
|
||||
this.job('showDialogAfterRender', function() {
|
||||
this.$.dialog.toggle();
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
setService: function(domain, service) {
|
||||
@ -65,16 +70,17 @@ Polymer({
|
||||
},
|
||||
|
||||
clickCallService: function() {
|
||||
var data;
|
||||
|
||||
if(this.$.inputData.value != "") {
|
||||
data = JSON.parse(this.$.inputData.value);
|
||||
}
|
||||
|
||||
try {
|
||||
this.api.call_service(
|
||||
this.$.inputDomain.value,
|
||||
this.$.inputService.value,
|
||||
data);
|
||||
this.$.inputData.value ? JSON.parse(this.$.inputData.value) : {});
|
||||
|
||||
this.$.dialog.close();
|
||||
|
||||
} catch (err) {
|
||||
alert("Error parsing JSON: " + err);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -9,10 +9,18 @@
|
||||
|
||||
<polymer-element name="state-set-dialog" attributes="api">
|
||||
<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>
|
||||
|
||||
<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 class='ha-form'>
|
||||
<paper-input id="inputEntityID" label="Entity ID" floatingLabel="true" autofocus required></paper-input>
|
||||
@ -52,7 +60,9 @@ Polymer({
|
||||
this.setState(state);
|
||||
this.setStateData(stateData);
|
||||
|
||||
this.$.dialog.toggle();
|
||||
this.job('showDialogAfterRender', function() {
|
||||
this.$.dialog.toggle();
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
setEntityId: function(entityId) {
|
||||
@ -77,12 +87,18 @@ Polymer({
|
||||
this.setStateData(state.attributes);
|
||||
},
|
||||
|
||||
clickSetState: function() {
|
||||
this.api.set_state(
|
||||
this.$.inputEntityID.value,
|
||||
this.$.inputState.value,
|
||||
JSON.parse(this.$.inputData.value)
|
||||
clickSetState: function(ev) {
|
||||
try {
|
||||
this.api.set_state(
|
||||
this.$.inputEntityID.value,
|
||||
this.$.inputState.value,
|
||||
this.$.inputData.value ? JSON.parse(this.$.inputData.value) : {}
|
||||
);
|
||||
|
||||
this.$.dialog.close();
|
||||
} catch (err) {
|
||||
alert("Error parsing JSON: " + err);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -6,7 +6,7 @@
|
||||
<link rel="import" href="dialogs/event-fire-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-card-dialog.html">
|
||||
<link rel="import" href="dialogs/more-info-dialog.html">
|
||||
|
||||
<script>
|
||||
var ha = {};
|
||||
@ -36,7 +36,7 @@
|
||||
<event-fire-dialog id="eventDialog" api={{api}}></event-fire-dialog>
|
||||
<service-call-dialog id="serviceDialog" api={{api}}></service-call-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>
|
||||
<script>
|
||||
var domainsWithCard = ['thermostat'];
|
||||
@ -395,8 +395,8 @@
|
||||
},
|
||||
|
||||
// show dialogs
|
||||
showStateCardDialog: function(entityId) {
|
||||
this.$.stateCardDialog.show(this.getState(entityId));
|
||||
showmoreInfoDialog: function(entityId) {
|
||||
this.$.moreInfoDialog.show(this.getState(entityId));
|
||||
},
|
||||
|
||||
showEditStateDialog: function(entityId) {
|
||||
|
@ -13,10 +13,16 @@
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id='moreInfo'></div>
|
||||
<div id='moreInfo' class='{{classNames}}'></div>
|
||||
</template>
|
||||
<script>
|
||||
Polymer({
|
||||
classNames: '',
|
||||
|
||||
observe: {
|
||||
'stateObj.attributes': 'stateAttributesChanged',
|
||||
},
|
||||
|
||||
stateObjChanged: function() {
|
||||
while (this.$.moreInfo.lastChild) {
|
||||
this.$.moreInfo.removeChild(this.$.moreInfo.lastChild);
|
||||
@ -25,8 +31,14 @@ Polymer({
|
||||
var moreInfo = document.createElement("more-info-" + this.stateObj.moreInfoType);
|
||||
moreInfo.api = this.api;
|
||||
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>
|
||||
</polymer-element>
|
||||
|
@ -8,6 +8,10 @@
|
||||
<style>
|
||||
.brightness {
|
||||
margin-bottom: 8px;
|
||||
|
||||
max-height: 0px;
|
||||
overflow: hidden;
|
||||
transition: max-height .5s ease-in;
|
||||
}
|
||||
|
||||
.brightness paper-slider::shadow #sliderKnobInner,
|
||||
@ -19,16 +23,32 @@
|
||||
display: block;
|
||||
width: 350px;
|
||||
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>
|
||||
<div>
|
||||
<div center horizontal layout class='brightness'>
|
||||
<div>Brightness</div>
|
||||
<paper-slider
|
||||
max="255" flex id='brightness'
|
||||
on-core-change="{{brightnessSliderChanged}}">
|
||||
</paper-slider>
|
||||
<div class='brightness'>
|
||||
<div center horizontal layout>
|
||||
<div>Brightness</div>
|
||||
<paper-slider
|
||||
max="255" flex id='brightness'
|
||||
on-core-change="{{brightnessSliderChanged}}">
|
||||
</paper-slider>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<color-picker id="colorpicker" width="350" height="200">
|
||||
</color-picker>
|
||||
</div>
|
||||
@ -48,7 +68,7 @@ Polymer({
|
||||
brightnessChanged: function(oldVal, newVal) {
|
||||
this.ignoreNextBrightnessEvent = true;
|
||||
|
||||
this.$.brightness.value = newVal;
|
||||
this.$.brightness.value = newVal;
|
||||
},
|
||||
|
||||
domReady: function() {
|
||||
|
@ -8,6 +8,14 @@
|
||||
font-family: RobotoDraft, 'Helvetica Neue', Helvetica, Arial;
|
||||
|
||||
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 {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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()]
|
||||
|
@ -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 = []
|
||||
@ -39,9 +39,7 @@ def setup(hass, config):
|
||||
|
||||
# Ensure component is loaded
|
||||
if component.DOMAIN not in hass.components:
|
||||
# Add a worker on succesfull setup
|
||||
if bootstrap.setup_component(hass, component.DOMAIN, config):
|
||||
hass.pool.add_worker()
|
||||
bootstrap.setup_component(hass, component.DOMAIN, config)
|
||||
|
||||
# Fire discovery event
|
||||
hass.bus.fire(EVENT_PLATFORM_DISCOVERED, {
|
||||
@ -50,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()
|
||||
|
196
homeassistant/external/wink/pywink.py
vendored
196
homeassistant/external/wink/pywink.py
vendored
@ -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 "<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):
|
||||
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 "<Wink Bulb %s %s %s>" % (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 "<Wink Bulb %s %s %s>" % (
|
||||
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()
|
||||
|
@ -153,11 +153,12 @@ def platform_devices_from_config(config, domain, hass,
|
||||
no_name_count = 0
|
||||
|
||||
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:
|
||||
no_name_count += 1
|
||||
name = "{} #{}".format(domain, no_name_count)
|
||||
name = "{} {}".format(domain, no_name_count)
|
||||
|
||||
entity_id = ensure_unique_string(
|
||||
entity_id_format.format(slugify(name)),
|
||||
|
Loading…
x
Reference in New Issue
Block a user