mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 11:17:21 +00:00
Add initial version of configurator component
This commit is contained in:
parent
0c5f1234da
commit
980ecdaacb
155
homeassistant/components/configurator.py
Normal file
155
homeassistant/components/configurator.py
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
from homeassistant.helpers import generate_entity_id
|
||||||
|
from homeassistant.const import EVENT_TIME_CHANGED
|
||||||
|
|
||||||
|
DOMAIN = "configurator"
|
||||||
|
DEPENDENCIES = []
|
||||||
|
ENTITY_ID_FORMAT = DOMAIN + ".{}"
|
||||||
|
|
||||||
|
SERVICE_CONFIGURE = "configure"
|
||||||
|
|
||||||
|
STATE_CONFIGURE = "configure"
|
||||||
|
STATE_CONFIGURED = "configured"
|
||||||
|
|
||||||
|
ATTR_CONFIGURE_ID = "configure_id"
|
||||||
|
ATTR_DESCRIPTION = "description"
|
||||||
|
ATTR_DESCRIPTION_IMAGE = "description_image"
|
||||||
|
ATTR_SUBMIT_CAPTION = "submit_caption"
|
||||||
|
ATTR_FIELDS = "fields"
|
||||||
|
ATTR_ERRORS = "errors"
|
||||||
|
|
||||||
|
_INSTANCES = {}
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def request_config(
|
||||||
|
hass, name, callback, description=None, description_image=None,
|
||||||
|
submit_caption=None, fields=None):
|
||||||
|
""" Create a new request for config.
|
||||||
|
Will return an ID to be used for sequent calls. """
|
||||||
|
|
||||||
|
return _get_instance(hass).request_config(
|
||||||
|
name, callback,
|
||||||
|
description, description_image, submit_caption, fields)
|
||||||
|
|
||||||
|
|
||||||
|
def notify_errors(hass, request_id, error):
|
||||||
|
_get_instance(hass).notify_errors(request_id, error)
|
||||||
|
|
||||||
|
|
||||||
|
def request_done(hass, request_id):
|
||||||
|
_get_instance(hass).request_done(request_id)
|
||||||
|
|
||||||
|
|
||||||
|
def setup(hass, config):
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def _get_instance(hass):
|
||||||
|
""" Get an instance per hass object. """
|
||||||
|
try:
|
||||||
|
return _INSTANCES[hass]
|
||||||
|
except KeyError:
|
||||||
|
print("Creating instance")
|
||||||
|
_INSTANCES[hass] = Configurator(hass)
|
||||||
|
|
||||||
|
if DOMAIN not in hass.components:
|
||||||
|
hass.components.append(DOMAIN)
|
||||||
|
|
||||||
|
return _INSTANCES[hass]
|
||||||
|
|
||||||
|
|
||||||
|
class Configurator(object):
|
||||||
|
def __init__(self, hass):
|
||||||
|
self.hass = hass
|
||||||
|
self._cur_id = 0
|
||||||
|
self._requests = {}
|
||||||
|
hass.services.register(
|
||||||
|
DOMAIN, SERVICE_CONFIGURE, self.handle_service_call)
|
||||||
|
|
||||||
|
def request_config(
|
||||||
|
self, name, callback,
|
||||||
|
description, description_image, submit_caption, fields):
|
||||||
|
""" Setup a request for configuration. """
|
||||||
|
|
||||||
|
entity_id = generate_entity_id(ENTITY_ID_FORMAT, name, hass=self.hass)
|
||||||
|
|
||||||
|
if fields is None:
|
||||||
|
fields = []
|
||||||
|
|
||||||
|
request_id = self._generate_unique_id()
|
||||||
|
|
||||||
|
self._requests[request_id] = (entity_id, fields, callback)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
ATTR_CONFIGURE_ID: request_id,
|
||||||
|
ATTR_FIELDS: fields,
|
||||||
|
}
|
||||||
|
|
||||||
|
data.update({
|
||||||
|
key: value for key, value in [
|
||||||
|
(ATTR_DESCRIPTION, description),
|
||||||
|
(ATTR_DESCRIPTION_IMAGE, description_image),
|
||||||
|
(ATTR_SUBMIT_CAPTION, submit_caption),
|
||||||
|
] if value is not None
|
||||||
|
})
|
||||||
|
|
||||||
|
self.hass.states.set(entity_id, STATE_CONFIGURE, data)
|
||||||
|
|
||||||
|
return request_id
|
||||||
|
|
||||||
|
def notify_errors(self, request_id, error):
|
||||||
|
""" Update the state with errors. """
|
||||||
|
if not self._validate_request_id(request_id):
|
||||||
|
return
|
||||||
|
|
||||||
|
entity_id = self._requests[request_id][0]
|
||||||
|
|
||||||
|
state = self.hass.states.get(entity_id)
|
||||||
|
|
||||||
|
new_data = state.attributes
|
||||||
|
new_data[ATTR_ERRORS] = error
|
||||||
|
|
||||||
|
self.hass.states.set(entity_id, STATE_CONFIGURE, new_data)
|
||||||
|
|
||||||
|
def request_done(self, request_id):
|
||||||
|
""" Remove the config request. """
|
||||||
|
if not self._validate_request_id(request_id):
|
||||||
|
return
|
||||||
|
|
||||||
|
entity_id = self._requests.pop(request_id)[0]
|
||||||
|
|
||||||
|
# If we remove the state right away, it will not be passed down
|
||||||
|
# with the service request (limitation current design).
|
||||||
|
# Instead we will set it to configured right away and remove it soon.
|
||||||
|
def deferred_remove(event):
|
||||||
|
self.hass.states.remove(entity_id)
|
||||||
|
|
||||||
|
self.hass.bus.listen_once(EVENT_TIME_CHANGED, deferred_remove)
|
||||||
|
|
||||||
|
self.hass.states.set(entity_id, STATE_CONFIGURED)
|
||||||
|
|
||||||
|
def handle_service_call(self, call):
|
||||||
|
request_id = call.data.get(ATTR_CONFIGURE_ID)
|
||||||
|
|
||||||
|
if not self._validate_request_id(request_id):
|
||||||
|
return
|
||||||
|
|
||||||
|
entity_id, fields, callback = self._requests[request_id]
|
||||||
|
|
||||||
|
# TODO field validation?
|
||||||
|
|
||||||
|
callback(call.data.get(ATTR_FIELDS, {}))
|
||||||
|
|
||||||
|
def _generate_unique_id(self):
|
||||||
|
""" Generates a unique configurator id. """
|
||||||
|
self._cur_id += 1
|
||||||
|
return "{}-{}".format(id(self), self._cur_id)
|
||||||
|
|
||||||
|
def _validate_request_id(self, request_id):
|
||||||
|
if request_id not in self._requests:
|
||||||
|
_LOGGER.error("Invalid configure id received: %s", request_id)
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
@ -5,6 +5,7 @@ homeassistant.components.demo
|
|||||||
Sets up a demo environment that mimics interaction with devices
|
Sets up a demo environment that mimics interaction with devices
|
||||||
"""
|
"""
|
||||||
import random
|
import random
|
||||||
|
import time
|
||||||
|
|
||||||
import homeassistant as ha
|
import homeassistant as ha
|
||||||
import homeassistant.loader as loader
|
import homeassistant.loader as loader
|
||||||
@ -28,6 +29,7 @@ DEPENDENCIES = []
|
|||||||
def setup(hass, config):
|
def setup(hass, config):
|
||||||
""" Setup a demo environment. """
|
""" Setup a demo environment. """
|
||||||
group = loader.get_component('group')
|
group = loader.get_component('group')
|
||||||
|
configurator = loader.get_component('configurator')
|
||||||
|
|
||||||
config.setdefault(ha.DOMAIN, {})
|
config.setdefault(ha.DOMAIN, {})
|
||||||
config.setdefault(DOMAIN, {})
|
config.setdefault(DOMAIN, {})
|
||||||
@ -170,4 +172,30 @@ def setup(hass, config):
|
|||||||
ATTR_AWAY_MODE: STATE_OFF
|
ATTR_AWAY_MODE: STATE_OFF
|
||||||
})
|
})
|
||||||
|
|
||||||
|
configurator_ids = []
|
||||||
|
|
||||||
|
def hue_configuration_callback(data):
|
||||||
|
""" Fake callback, mark config as done. """
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
# First time it is called, pretend it failed.
|
||||||
|
if len(configurator_ids) == 1:
|
||||||
|
configurator.notify_errors(
|
||||||
|
hass, configurator_ids[0],
|
||||||
|
"Failed to register, please try again.")
|
||||||
|
|
||||||
|
configurator_ids.append(0)
|
||||||
|
else:
|
||||||
|
configurator.request_done(hass, configurator_ids[0])
|
||||||
|
|
||||||
|
request_id = configurator.request_config(
|
||||||
|
hass, "Philips Hue", hue_configuration_callback,
|
||||||
|
description=("Press the button on the bridge to register Philips Hue "
|
||||||
|
"with Home Assistant."),
|
||||||
|
description_image="/static/images/config_philips_hue.jpg",
|
||||||
|
submit_caption="I have pressed the button"
|
||||||
|
)
|
||||||
|
|
||||||
|
configurator_ids.append(request_id)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
""" DO NOT MODIFY. Auto-generated by build_frontend script """
|
""" DO NOT MODIFY. Auto-generated by build_frontend script """
|
||||||
VERSION = "951c0a4e0adb70ec1f0f7e4c76955ed9"
|
VERSION = "f299ce624d1641191f6f6a9b4b4d05bc"
|
||||||
|
File diff suppressed because one or more lines are too long
Binary file not shown.
After Width: | Height: | Size: 8.8 KiB |
@ -62,6 +62,9 @@
|
|||||||
case "sensor":
|
case "sensor":
|
||||||
return "visibility";
|
return "visibility";
|
||||||
|
|
||||||
|
case "configurator":
|
||||||
|
return "settings";
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return "bookmark-outline";
|
return "bookmark-outline";
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
var domainsWithCard = ['thermostat'];
|
var domainsWithCard = ['thermostat'];
|
||||||
var domainsWithMoreInfo = ['light', 'group', 'sun'];
|
var domainsWithMoreInfo = ['light', 'group', 'sun', 'configurator'];
|
||||||
|
|
||||||
State = function(json, api) {
|
State = function(json, api) {
|
||||||
this.api = api;
|
this.api = api;
|
||||||
|
@ -0,0 +1,87 @@
|
|||||||
|
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||||
|
<link rel="import" href="../bower_components/paper-button/paper-button.html">
|
||||||
|
<link rel="import" href="../bower_components/paper-spinner/paper-spinner.html">
|
||||||
|
|
||||||
|
<polymer-element name="more-info-configurator" attributes="stateObj api">
|
||||||
|
<template>
|
||||||
|
<style>
|
||||||
|
p {
|
||||||
|
margin: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
p > img {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.error {
|
||||||
|
color: #C62828;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.submit {
|
||||||
|
text-align: center;
|
||||||
|
height: 41px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.submit paper-spinner {
|
||||||
|
margin-right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.submit span {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div layout vertical>
|
||||||
|
<template if="{{stateObj.state == 'configure'}}">
|
||||||
|
|
||||||
|
<p hidden?="{{!stateObj.attributes.description}}">
|
||||||
|
{{stateObj.attributes.description}}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p class='error' hidden?="{{!stateObj.attributes.errors}}">
|
||||||
|
Errors: {{stateObj.attributes.errors}}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p hidden?="{{!stateObj.attributes.description_image}}">
|
||||||
|
<img src='{{stateObj.attributes.description_image}}' />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p class='submit'>
|
||||||
|
<paper-button raised on-click="{{submitClicked}}"
|
||||||
|
hidden?="{{action !== 'display'}}">
|
||||||
|
{{stateObj.attributes.submit_caption || "Set configuration"}}
|
||||||
|
</paper-button>
|
||||||
|
|
||||||
|
<span hidden?="{{action !== 'configuring'}}">
|
||||||
|
<paper-spinner active="true"></paper-spinner><span>Configuring…</span>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
Polymer({
|
||||||
|
action: "display",
|
||||||
|
|
||||||
|
submitClicked: function() {
|
||||||
|
this.action = "configuring";
|
||||||
|
var data = {
|
||||||
|
configure_id: this.stateObj.attributes.configure_id
|
||||||
|
};
|
||||||
|
|
||||||
|
this.api.call_service('configurator', 'configure', data, {
|
||||||
|
success: function() {
|
||||||
|
this.action = 'display';
|
||||||
|
this.api.fetchAll();
|
||||||
|
}.bind(this),
|
||||||
|
error: function() {
|
||||||
|
this.action = 'display';
|
||||||
|
}.bind(this)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</polymer-element>
|
@ -4,6 +4,7 @@
|
|||||||
<link rel="import" href="more-info-light.html">
|
<link rel="import" href="more-info-light.html">
|
||||||
<link rel="import" href="more-info-group.html">
|
<link rel="import" href="more-info-group.html">
|
||||||
<link rel="import" href="more-info-sun.html">
|
<link rel="import" href="more-info-sun.html">
|
||||||
|
<link rel="import" href="more-info-configurator.html">
|
||||||
|
|
||||||
<polymer-element name="more-info-content" attributes="api stateObj">
|
<polymer-element name="more-info-content" attributes="api stateObj">
|
||||||
<template>
|
<template>
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
<div layout vertical>
|
<div layout vertical>
|
||||||
|
|
||||||
<template repeat="{{key in stateObj.attributes | getKeys}}">
|
<template repeat="{{key in stateObj.attributes | getKeys}}">
|
||||||
<div layout justified horizontal class='data-entry' id='rising'>
|
<div layout justified horizontal class='data-entry'>
|
||||||
<div>
|
<div>
|
||||||
{{key}}
|
{{key}}
|
||||||
</div>
|
</div>
|
||||||
|
@ -57,7 +57,7 @@ import homeassistant.util as util
|
|||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID)
|
STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID)
|
||||||
from homeassistant.helpers import (
|
from homeassistant.helpers import (
|
||||||
extract_entity_ids, platform_devices_from_config)
|
generate_entity_id, extract_entity_ids, config_per_platform)
|
||||||
from homeassistant.components import group, discovery, wink
|
from homeassistant.components import group, discovery, wink
|
||||||
|
|
||||||
|
|
||||||
@ -93,6 +93,7 @@ LIGHT_PROFILES_FILE = "light_profiles.csv"
|
|||||||
# Maps discovered services to their platforms
|
# Maps discovered services to their platforms
|
||||||
DISCOVERY_PLATFORMS = {
|
DISCOVERY_PLATFORMS = {
|
||||||
wink.DISCOVER_LIGHTS: 'wink',
|
wink.DISCOVER_LIGHTS: 'wink',
|
||||||
|
discovery.services.PHILIPS_HUE: 'hue',
|
||||||
}
|
}
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -168,8 +169,32 @@ def setup(hass, config):
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
lights = platform_devices_from_config(
|
# Dict to track entity_id -> lights
|
||||||
config, DOMAIN, hass, ENTITY_ID_FORMAT, _LOGGER)
|
lights = {}
|
||||||
|
|
||||||
|
# Track all lights in a group
|
||||||
|
light_group = group.Group(hass, GROUP_NAME_ALL_LIGHTS, user_defined=False)
|
||||||
|
|
||||||
|
def add_lights(new_lights):
|
||||||
|
""" Add lights to the component to track. """
|
||||||
|
for light in new_lights:
|
||||||
|
if light is not None and light not in lights.values():
|
||||||
|
light.entity_id = generate_entity_id(
|
||||||
|
ENTITY_ID_FORMAT, light.name, lights.keys())
|
||||||
|
|
||||||
|
lights[light.entity_id] = light
|
||||||
|
|
||||||
|
light.update_ha_state(hass)
|
||||||
|
|
||||||
|
light_group.update_tracked_entity_ids(lights.keys())
|
||||||
|
|
||||||
|
for p_type, p_config in config_per_platform(config, DOMAIN, _LOGGER):
|
||||||
|
platform = get_component(ENTITY_ID_FORMAT.format(p_type))
|
||||||
|
|
||||||
|
if platform is None:
|
||||||
|
_LOGGER.error("Unknown type specified: %s", p_type)
|
||||||
|
|
||||||
|
platform.setup_platform(hass, p_config, add_lights)
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
def update_lights_state(now):
|
def update_lights_state(now):
|
||||||
@ -182,28 +207,12 @@ def setup(hass, config):
|
|||||||
|
|
||||||
update_lights_state(None)
|
update_lights_state(None)
|
||||||
|
|
||||||
# Track all lights in a group
|
|
||||||
light_group = group.Group(
|
|
||||||
hass, GROUP_NAME_ALL_LIGHTS, lights.keys(), False)
|
|
||||||
|
|
||||||
def light_discovered(service, info):
|
def light_discovered(service, info):
|
||||||
""" Called when a light is discovered. """
|
""" Called when a light is discovered. """
|
||||||
platform = get_component(
|
platform = get_component(
|
||||||
"{}.{}".format(DOMAIN, DISCOVERY_PLATFORMS[service]))
|
ENTITY_ID_FORMAT.format(DISCOVERY_PLATFORMS[service]))
|
||||||
|
|
||||||
discovered = platform.devices_discovered(hass, config, info)
|
platform.setup_platform(hass, {}, add_lights, info)
|
||||||
|
|
||||||
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.name)),
|
|
||||||
lights.keys())
|
|
||||||
|
|
||||||
lights[light.entity_id] = light
|
|
||||||
|
|
||||||
light.update_ha_state(hass)
|
|
||||||
|
|
||||||
light_group.update_tracked_entity_ids(lights.keys())
|
|
||||||
|
|
||||||
discovery.listen(hass, DISCOVERY_PLATFORMS.keys(), light_discovered)
|
discovery.listen(hass, DISCOVERY_PLATFORMS.keys(), light_discovered)
|
||||||
|
|
||||||
|
@ -2,7 +2,9 @@
|
|||||||
import logging
|
import logging
|
||||||
import socket
|
import socket
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
from homeassistant.loader import get_component
|
||||||
import homeassistant.util as util
|
import homeassistant.util as util
|
||||||
from homeassistant.helpers import ToggleDevice
|
from homeassistant.helpers import ToggleDevice
|
||||||
from homeassistant.const import ATTR_FRIENDLY_NAME, CONF_HOST
|
from homeassistant.const import ATTR_FRIENDLY_NAME, CONF_HOST
|
||||||
@ -16,27 +18,57 @@ MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(seconds=1)
|
|||||||
PHUE_CONFIG_FILE = "phue.conf"
|
PHUE_CONFIG_FILE = "phue.conf"
|
||||||
|
|
||||||
|
|
||||||
def get_devices(hass, config):
|
# Map ip to request id for configuring
|
||||||
|
_CONFIGURING = {}
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||||
""" Gets the Hue lights. """
|
""" Gets the Hue lights. """
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
try:
|
try:
|
||||||
import phue
|
import phue
|
||||||
except ImportError:
|
except ImportError:
|
||||||
logger.exception("Error while importing dependency phue.")
|
_LOGGER.exception("Error while importing dependency phue.")
|
||||||
|
|
||||||
return []
|
return
|
||||||
|
|
||||||
|
if discovery_info is not None:
|
||||||
|
host = urlparse(discovery_info).hostname
|
||||||
|
else:
|
||||||
host = config.get(CONF_HOST, None)
|
host = config.get(CONF_HOST, None)
|
||||||
|
|
||||||
|
# Only act if we are not already configuring this host
|
||||||
|
if host in _CONFIGURING:
|
||||||
|
return
|
||||||
|
|
||||||
|
setup_bridge(host, hass, add_devices_callback)
|
||||||
|
|
||||||
|
|
||||||
|
def setup_bridge(host, hass, add_devices_callback):
|
||||||
|
import phue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
bridge = phue.Bridge(
|
bridge = phue.Bridge(
|
||||||
host, config_file_path=hass.get_config_path(PHUE_CONFIG_FILE))
|
host, config_file_path=hass.get_config_path(PHUE_CONFIG_FILE))
|
||||||
except socket.error: # Error connecting using Phue
|
except ConnectionRefusedError: # Wrong host was given
|
||||||
logger.exception((
|
_LOGGER.exception("Error connecting to the Hue bridge at %s", host)
|
||||||
"Error while connecting to the bridge. "
|
|
||||||
"Did you follow the instructions to set it up?"))
|
|
||||||
|
|
||||||
return []
|
return
|
||||||
|
|
||||||
|
except phue.PhueRegistrationException:
|
||||||
|
_LOGGER.warning("Connected to Hue at %s but not registered.", host)
|
||||||
|
|
||||||
|
request_configuration(host, hass, add_devices_callback)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
# If we came here and configuring this host, mark as done
|
||||||
|
if host in _CONFIGURING:
|
||||||
|
request_id = _CONFIGURING.pop(host)
|
||||||
|
|
||||||
|
configurator = get_component('configurator')
|
||||||
|
|
||||||
|
configurator.request_done(hass, request_id)
|
||||||
|
|
||||||
lights = {}
|
lights = {}
|
||||||
|
|
||||||
@ -47,25 +79,53 @@ def get_devices(hass, config):
|
|||||||
api = bridge.get_api()
|
api = bridge.get_api()
|
||||||
except socket.error:
|
except socket.error:
|
||||||
# socket.error when we cannot reach Hue
|
# socket.error when we cannot reach Hue
|
||||||
logger.exception("Cannot reach the bridge")
|
_LOGGER.exception("Cannot reach the bridge")
|
||||||
return
|
return
|
||||||
|
|
||||||
api_states = api.get('lights')
|
api_states = api.get('lights')
|
||||||
|
|
||||||
if not isinstance(api_states, dict):
|
if not isinstance(api_states, dict):
|
||||||
logger.error("Got unexpected result from Hue API")
|
_LOGGER.error("Got unexpected result from Hue API")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
new_lights = []
|
||||||
|
|
||||||
for light_id, info in api_states.items():
|
for light_id, info in api_states.items():
|
||||||
if light_id not in lights:
|
if light_id not in lights:
|
||||||
lights[light_id] = HueLight(int(light_id), info,
|
lights[light_id] = HueLight(int(light_id), info,
|
||||||
bridge, update_lights)
|
bridge, update_lights)
|
||||||
|
new_lights.append(lights[light_id])
|
||||||
else:
|
else:
|
||||||
lights[light_id].info = info
|
lights[light_id].info = info
|
||||||
|
|
||||||
|
if new_lights:
|
||||||
|
add_devices_callback(new_lights)
|
||||||
|
|
||||||
update_lights()
|
update_lights()
|
||||||
|
|
||||||
return list(lights.values())
|
|
||||||
|
def request_configuration(host, hass, add_devices_callback):
|
||||||
|
""" Request configuration steps from the user. """
|
||||||
|
configurator = get_component('configurator')
|
||||||
|
|
||||||
|
# If this method called while we are configuring, means we got an error
|
||||||
|
if host in _CONFIGURING:
|
||||||
|
configurator.notify_errors(
|
||||||
|
hass, _CONFIGURING[host], "Failed to register, please try again.")
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def hue_configuration_callback(data):
|
||||||
|
""" Actions to do when our configuration callback is called. """
|
||||||
|
setup_bridge(host, hass, add_devices_callback)
|
||||||
|
|
||||||
|
_CONFIGURING[host] = configurator.request_config(
|
||||||
|
hass, "Philips Hue", hue_configuration_callback,
|
||||||
|
description=("Press the button on the bridge to register Philips Hue "
|
||||||
|
"with Home Assistant."),
|
||||||
|
description_image="/static/images/config_philips_hue.jpg",
|
||||||
|
submit_caption="I have pressed the button"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class HueLight(ToggleDevice):
|
class HueLight(ToggleDevice):
|
||||||
|
@ -10,30 +10,21 @@ from homeassistant.const import CONF_ACCESS_TOKEN
|
|||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
def get_devices(hass, config):
|
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||||
""" Find and return Wink lights. """
|
""" Find and return Wink lights. """
|
||||||
token = config.get(CONF_ACCESS_TOKEN)
|
token = config.get(CONF_ACCESS_TOKEN)
|
||||||
|
|
||||||
if token is None:
|
if not pywink.is_token_set() and token is None:
|
||||||
logging.getLogger(__name__).error(
|
logging.getLogger(__name__).error(
|
||||||
"Missing wink access_token - "
|
"Missing wink access_token - "
|
||||||
"get one at https://winkbearertoken.appspot.com/")
|
"get one at https://winkbearertoken.appspot.com/")
|
||||||
return []
|
return
|
||||||
|
|
||||||
|
elif token is not None:
|
||||||
pywink.set_bearer_token(token)
|
pywink.set_bearer_token(token)
|
||||||
|
|
||||||
return get_lights()
|
add_devices_callback(
|
||||||
|
WinkLight(light) for light in pywink.get_bulbs())
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
|
||||||
def devices_discovered(hass, config, info):
|
|
||||||
""" Called when a device is discovered. """
|
|
||||||
return get_lights()
|
|
||||||
|
|
||||||
|
|
||||||
def get_lights():
|
|
||||||
""" Returns the Wink switches. """
|
|
||||||
return [WinkLight(light) for light in pywink.get_bulbs()]
|
|
||||||
|
|
||||||
|
|
||||||
class WinkLight(WinkToggleDevice):
|
class WinkLight(WinkToggleDevice):
|
||||||
|
@ -11,7 +11,7 @@ import homeassistant.util as util
|
|||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID)
|
STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID)
|
||||||
from homeassistant.helpers import (
|
from homeassistant.helpers import (
|
||||||
extract_entity_ids, platform_devices_from_config)
|
generate_entity_id, extract_entity_ids, platform_devices_from_config)
|
||||||
from homeassistant.components import group, discovery, wink
|
from homeassistant.components import group, discovery, wink
|
||||||
|
|
||||||
DOMAIN = 'switch'
|
DOMAIN = 'switch'
|
||||||
@ -90,9 +90,8 @@ def setup(hass, config):
|
|||||||
|
|
||||||
for switch in discovered:
|
for switch in discovered:
|
||||||
if switch is not None and switch not in switches.values():
|
if switch is not None and switch not in switches.values():
|
||||||
switch.entity_id = util.ensure_unique_string(
|
switch.entity_id = generate_entity_id(
|
||||||
ENTITY_ID_FORMAT.format(util.slugify(switch.name)),
|
ENTITY_ID_FORMAT, switch.name, switches.keys())
|
||||||
switches.keys())
|
|
||||||
|
|
||||||
switches[switch.entity_id] = switch
|
switches[switch.entity_id] = switch
|
||||||
|
|
||||||
|
5
homeassistant/external/wink/pywink.py
vendored
5
homeassistant/external/wink/pywink.py
vendored
@ -389,6 +389,11 @@ def get_switches():
|
|||||||
def get_sensors():
|
def get_sensors():
|
||||||
return get_devices('sensor_pod_id', wink_sensor_pod)
|
return get_devices('sensor_pod_id', wink_sensor_pod)
|
||||||
|
|
||||||
|
def is_token_set():
|
||||||
|
""" Returns if an auth token has been set. """
|
||||||
|
return bool(headers)
|
||||||
|
|
||||||
|
|
||||||
def set_bearer_token(token):
|
def set_bearer_token(token):
|
||||||
global headers
|
global headers
|
||||||
|
|
||||||
|
@ -12,6 +12,17 @@ from homeassistant.const import (
|
|||||||
from homeassistant.util import ensure_unique_string, slugify
|
from homeassistant.util import ensure_unique_string, slugify
|
||||||
|
|
||||||
|
|
||||||
|
def generate_entity_id(entity_id_format, name, current_ids=None, hass=None):
|
||||||
|
if current_ids is None:
|
||||||
|
if hass is None:
|
||||||
|
raise RuntimeError("Missing required parameter currentids or hass")
|
||||||
|
|
||||||
|
current_ids = hass.states.entity_ids()
|
||||||
|
|
||||||
|
return ensure_unique_string(
|
||||||
|
entity_id_format.format(slugify(name)), current_ids)
|
||||||
|
|
||||||
|
|
||||||
def extract_entity_ids(hass, service):
|
def extract_entity_ids(hass, service):
|
||||||
"""
|
"""
|
||||||
Helper method to extract a list of entity ids from a service call.
|
Helper method to extract a list of entity ids from a service call.
|
||||||
@ -160,9 +171,8 @@ def platform_devices_from_config(config, domain, hass,
|
|||||||
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 = generate_entity_id(
|
||||||
entity_id_format.format(slugify(name)),
|
entity_id_format, name, device_dict.keys())
|
||||||
device_dict.keys())
|
|
||||||
|
|
||||||
device.entity_id = entity_id
|
device.entity_id = entity_id
|
||||||
device_dict[entity_id] = device
|
device_dict[entity_id] = device
|
||||||
|
Loading…
x
Reference in New Issue
Block a user