mirror of
https://github.com/home-assistant/core.git
synced 2025-07-16 09:47:13 +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:
|
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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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
@ -26,7 +26,7 @@
|
|||||||
Polymer({
|
Polymer({
|
||||||
|
|
||||||
cardClicked: function() {
|
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">
|
<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.job('showDialogAfterRender', function() {
|
||||||
this.$.dialog.toggle();
|
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(
|
||||||
|
this.$.inputType.value,
|
||||||
|
this.$.inputData.value ? JSON.parse(this.$.inputData.value) : {});
|
||||||
|
this.$.dialog.close();
|
||||||
|
|
||||||
if(this.$.inputData.value !== "") {
|
} catch (err) {
|
||||||
data = JSON.parse(this.$.inputData.value);
|
alert("Error parsing JSON: " + err);
|
||||||
} else {
|
|
||||||
data = {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.api.fire_event(this.$.inputType.value, data);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -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>
|
||||||
|
@ -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.job('showDialogAfterRender', function() {
|
||||||
this.$.dialog.toggle();
|
this.$.dialog.toggle();
|
||||||
|
}.bind(this));
|
||||||
},
|
},
|
||||||
|
|
||||||
editClicked: function(ev) {
|
editClicked: function(ev) {
|
@ -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.job('showDialogAfterRender', function() {
|
||||||
this.$.dialog.toggle();
|
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>
|
||||||
|
@ -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.job('showDialogAfterRender', function() {
|
||||||
this.$.dialog.toggle();
|
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) {
|
||||||
|
try {
|
||||||
this.api.set_state(
|
this.api.set_state(
|
||||||
this.$.inputEntityID.value,
|
this.$.inputEntityID.value,
|
||||||
this.$.inputState.value,
|
this.$.inputState.value,
|
||||||
JSON.parse(this.$.inputData.value)
|
this.$.inputData.value ? JSON.parse(this.$.inputData.value) : {}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.$.dialog.close();
|
||||||
|
} catch (err) {
|
||||||
|
alert("Error parsing JSON: " + err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -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) {
|
||||||
|
@ -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);
|
||||||
@ -26,7 +32,13 @@ Polymer({
|
|||||||
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>
|
||||||
|
@ -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 center horizontal layout>
|
||||||
<div>Brightness</div>
|
<div>Brightness</div>
|
||||||
<paper-slider
|
<paper-slider
|
||||||
max="255" flex id='brightness'
|
max="255" flex id='brightness'
|
||||||
on-core-change="{{brightnessSliderChanged}}">
|
on-core-change="{{brightnessSliderChanged}}">
|
||||||
</paper-slider>
|
</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>
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
@ -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. """
|
||||||
|
brightness = kwargs.get(ATTR_BRIGHTNESS)
|
||||||
|
|
||||||
|
if brightness is not None:
|
||||||
|
self.wink.setState(True, brightness / 255)
|
||||||
|
|
||||||
|
else:
|
||||||
self.wink.setState(True)
|
self.wink.setState(True)
|
||||||
|
|
||||||
def turn_off(self):
|
@property
|
||||||
""" Turns the light off. """
|
def state_attributes(self):
|
||||||
self.wink.setState(False)
|
attr = super().state_attributes
|
||||||
|
|
||||||
def is_on(self):
|
if self.is_on:
|
||||||
""" True if light is on. """
|
brightness = self.wink.brightness()
|
||||||
return self.wink.state()
|
|
||||||
|
|
||||||
def get_state_attributes(self):
|
if brightness is not None:
|
||||||
""" Returns optional state attributes. """
|
attr[ATTR_BRIGHTNESS] = int(brightness * 255)
|
||||||
return self.state_attr
|
|
||||||
|
return attr
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
|
||||||
|
@ -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()
|
||||||
|
130
homeassistant/external/wink/pywink.py
vendored
130
homeassistant/external/wink/pywink.py
vendored
@ -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
|
||||||
@ -181,39 +214,38 @@ class wink_bulb(wink_binary_switch):
|
|||||||
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()
|
||||||
|
@ -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)),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user