Merge remote-tracking branch 'origin/dev' into dev

Conflicts:
	homeassistant/components/http/frontend.py
	homeassistant/components/http/www_static/frontend.html
This commit is contained in:
Geoff Norton 2015-01-22 05:27:26 +00:00
commit 8b947e2fab
59 changed files with 800 additions and 202 deletions

3
.gitmodules vendored
View File

@ -7,3 +7,6 @@
[submodule "homeassistant/external/netdisco"] [submodule "homeassistant/external/netdisco"]
path = homeassistant/external/netdisco path = homeassistant/external/netdisco
url = https://github.com/balloob/netdisco.git url = https://github.com/balloob/netdisco.git
[submodule "homeassistant/external/noop"]
path = homeassistant/external/noop
url = https://github.com/balloob/noop.git

View File

@ -34,7 +34,6 @@ SERVICE_FLASH = 'flash'
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument
def setup(hass, config): def setup(hass, config):
""" Setup example component. """ """ Setup example component. """

View File

@ -426,9 +426,11 @@ class State(object):
state: the state of the entity state: the state of the entity
attributes: extra information on entity and state attributes: extra information on entity and state
last_changed: last time the state was changed, not the attributes. last_changed: last time the state was changed, not the attributes.
last_updated: last time this object was updated.
""" """
__slots__ = ['entity_id', 'state', 'attributes', 'last_changed'] __slots__ = ['entity_id', 'state', 'attributes',
'last_changed', 'last_updated']
def __init__(self, entity_id, state, attributes=None, last_changed=None): def __init__(self, entity_id, state, attributes=None, last_changed=None):
if not ENTITY_ID_PATTERN.match(entity_id): if not ENTITY_ID_PATTERN.match(entity_id):
@ -439,13 +441,14 @@ class State(object):
self.entity_id = entity_id self.entity_id = entity_id
self.state = state self.state = state
self.attributes = attributes or {} self.attributes = attributes or {}
self.last_updated = dt.datetime.now()
# Strip microsecond from last_changed else we cannot guarantee # Strip microsecond from last_changed else we cannot guarantee
# state == State.from_dict(state.as_dict()) # state == State.from_dict(state.as_dict())
# This behavior occurs because to_dict uses datetime_to_str # This behavior occurs because to_dict uses datetime_to_str
# which does not preserve microseconds # which does not preserve microseconds
self.last_changed = util.strip_microseconds( self.last_changed = util.strip_microseconds(
last_changed or dt.datetime.now()) last_changed or self.last_updated)
def copy(self): def copy(self):
""" Creates a copy of itself. """ """ Creates a copy of itself. """
@ -527,15 +530,12 @@ class StateMachine(object):
def get_since(self, point_in_time): def get_since(self, point_in_time):
""" """
Returns all states that have been changed since point_in_time. Returns all states that have been changed since point_in_time.
Note: States keep track of last_changed -without- microseconds.
Therefore your point_in_time will also be stripped of microseconds.
""" """
point_in_time = util.strip_microseconds(point_in_time) point_in_time = util.strip_microseconds(point_in_time)
with self._lock: with self._lock:
return [state for state in self._states.values() return [state for state in self._states.values()
if state.last_changed >= point_in_time] if state.last_updated >= point_in_time]
def is_state(self, entity_id, state): def is_state(self, entity_id, state):
""" Returns True if entity exists and is specified state. """ """ Returns True if entity exists and is specified state. """

View File

@ -1,24 +1,19 @@
""" Starts home assistant. """ """ Starts home assistant. """
from __future__ import print_function
import sys import sys
import os import os
import argparse import argparse
import importlib import importlib
try:
from homeassistant import bootstrap
except ImportError: def validate_python():
# This is to add support to load Home Assistant using """ Validate we're running the right Python version. """
# `python3 homeassistant` instead of `python3 -m homeassistant` major, minor = sys.version_info[:2]
# Insert the parent directory of this file into the module search path if major < 3 or (major == 3 and minor < 4):
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) print("Home Assistant requires atleast Python 3.4")
sys.exit()
from homeassistant import bootstrap
from homeassistant.const import EVENT_HOMEASSISTANT_START
from homeassistant.components import http, demo
def validate_dependencies(): def validate_dependencies():
@ -39,6 +34,34 @@ def validate_dependencies():
sys.exit() sys.exit()
def ensure_path_and_load_bootstrap():
""" Ensure sys load path is correct and load Home Assistant bootstrap. """
try:
from homeassistant import bootstrap
except ImportError:
# This is to add support to load Home Assistant using
# `python3 homeassistant` instead of `python3 -m homeassistant`
# Insert the parent directory of this file into the module search path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
from homeassistant import bootstrap
return bootstrap
def validate_git_submodules():
""" Validate the git submodules are cloned. """
try:
# pylint: disable=no-name-in-module, unused-variable
from homeassistant.external.noop import WORKING # noqa
except ImportError:
print("Repository submodules have not been initialized")
print("Please run: git submodule update --init --recursive")
sys.exit()
def ensure_config_path(config_dir): def ensure_config_path(config_dir):
""" Gets the path to the configuration file. """ Gets the path to the configuration file.
Creates one if it not exists. """ Creates one if it not exists. """
@ -65,9 +88,8 @@ def ensure_config_path(config_dir):
return config_path return config_path
def main(): def get_arguments():
""" Starts Home Assistant. Will create demo config if no config found. """ """ Get parsed passed in arguments. """
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument( parser.add_argument(
'-c', '--config', '-c', '--config',
@ -83,15 +105,26 @@ def main():
action='store_true', action='store_true',
help='Open the webinterface in a browser') help='Open the webinterface in a browser')
args = parser.parse_args() return parser.parse_args()
def main():
""" Starts Home Assistant. """
validate_python()
validate_dependencies() validate_dependencies()
config_dir = os.path.join(os.getcwd(), args.config) bootstrap = ensure_path_and_load_bootstrap()
validate_git_submodules()
args = get_arguments()
config_dir = os.path.join(os.getcwd(), args.config)
config_path = ensure_config_path(config_dir) config_path = ensure_config_path(config_dir)
if args.demo_mode: if args.demo_mode:
from homeassistant.components import http, demo
# Demo mode only requires http and demo components. # Demo mode only requires http and demo components.
hass = bootstrap.from_config_dict({ hass = bootstrap.from_config_dict({
http.DOMAIN: {}, http.DOMAIN: {},
@ -101,7 +134,8 @@ def main():
hass = bootstrap.from_config_file(config_path) hass = bootstrap.from_config_file(config_path)
if args.open_ui: if args.open_ui:
# pylint: disable=unused-argument from homeassistant.const import EVENT_HOMEASSISTANT_START
def open_browser(event): def open_browser(event):
""" Open the webinterface in a browser. """ """ Open the webinterface in a browser. """
if hass.local_api is not None: if hass.local_api is not None:

View File

@ -70,7 +70,6 @@ def turn_off(hass, entity_id=None, **service_data):
hass.services.call(ha.DOMAIN, SERVICE_TURN_OFF, service_data) hass.services.call(ha.DOMAIN, SERVICE_TURN_OFF, service_data)
# pylint: disable=unused-argument
def setup(hass, config): def setup(hass, config):
""" Setup general services related to homeassistant. """ """ Setup general services related to homeassistant. """

View File

@ -26,7 +26,6 @@ def register(hass, config, action):
from_state = config.get(CONF_FROM, MATCH_ALL) from_state = config.get(CONF_FROM, MATCH_ALL)
to_state = config.get(CONF_TO, MATCH_ALL) to_state = config.get(CONF_TO, MATCH_ALL)
# pylint: disable=unused-argument
def state_automation_listener(entity, from_s, to_s): def state_automation_listener(entity, from_s, to_s):
""" Listens for state changes and calls action. """ """ Listens for state changes and calls action. """
action() action()

View File

@ -17,7 +17,6 @@ def register(hass, config, action):
minutes = convert(config.get(CONF_MINUTES), int) minutes = convert(config.get(CONF_MINUTES), int)
seconds = convert(config.get(CONF_SECONDS), int) seconds = convert(config.get(CONF_SECONDS), int)
# pylint: disable=unused-argument
def time_automation_listener(now): def time_automation_listener(now):
""" Listens for time changes and calls action. """ """ Listens for time changes and calls action. """
action() action()

View File

@ -11,7 +11,6 @@ DEPENDENCIES = []
SERVICE_BROWSE_URL = "browse_url" SERVICE_BROWSE_URL = "browse_url"
# pylint: disable=unused-argument
def setup(hass, config): def setup(hass, config):
""" Listen for browse_url events and open """ Listen for browse_url events and open
the url in the default webbrowser. """ the url in the default webbrowser. """

View File

@ -158,7 +158,6 @@ def setup(hass, config):
for host in hosts: for host in hosts:
setup_chromecast(casts, host) setup_chromecast(casts, host)
# pylint: disable=unused-argument
def chromecast_discovered(service, info): def chromecast_discovered(service, info):
""" Called when a Chromecast has been discovered. """ """ Called when a Chromecast has been discovered. """
logger.info("New Chromecast discovered: %s", info[0]) logger.info("New Chromecast discovered: %s", info[0])
@ -212,7 +211,7 @@ def setup(hass, config):
hass.states.set(entity_id, state, state_attr) hass.states.set(entity_id, state, state_attr)
def update_chromecast_states(time): # pylint: disable=unused-argument def update_chromecast_states(time):
""" Updates all chromecast states. """ """ Updates all chromecast states. """
if casts: if casts:
logger.info("Updating Chromecast status") logger.info("Updating Chromecast status")
@ -298,7 +297,7 @@ def setup(hass, config):
pychromecast.play_youtube_video(video_id, cast.host) pychromecast.play_youtube_video(video_id, cast.host)
update_chromecast_state(entity_id, cast) update_chromecast_state(entity_id, cast)
hass.track_time_change(update_chromecast_states) hass.track_time_change(update_chromecast_states, second=[0, 15, 30, 45])
hass.services.register(DOMAIN, SERVICE_TURN_OFF, hass.services.register(DOMAIN, SERVICE_TURN_OFF,
turn_off_service) turn_off_service)

View File

@ -0,0 +1,190 @@
"""
homeassistant.components.configurator
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A component to allow pieces of code to request configuration from the user.
Initiate a request by calling the `request_config` method with a callback.
This will return a request id that has to be used for future calls.
A callback has to be provided to `request_config` which will be called when
the user has submitted configuration information.
"""
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"
_REQUESTS = {}
_INSTANCES = {}
_LOGGER = logging.getLogger(__name__)
# pylint: disable=too-many-arguments
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. """
instance = _get_instance(hass)
request_id = instance.request_config(
name, callback,
description, description_image, submit_caption, fields)
_REQUESTS[request_id] = instance
return request_id
def notify_errors(request_id, error):
""" Add errors to a config request. """
try:
_REQUESTS[request_id].notify_errors(request_id, error)
except KeyError:
# If request_id does not exist
pass
def request_done(request_id):
""" Mark a config request as done. """
try:
_REQUESTS.pop(request_id).request_done(request_id)
except KeyError:
# If request_id does not exist
pass
def setup(hass, config):
""" Set up Configurator. """
return True
def _get_instance(hass):
""" Get an instance per hass object. """
try:
return _INSTANCES[hass]
except KeyError:
_INSTANCES[hass] = Configurator(hass)
if DOMAIN not in hass.components:
hass.components.append(DOMAIN)
return _INSTANCES[hass]
class Configurator(object):
"""
Class to keep track of current configuration requests.
"""
def __init__(self, hass):
self.hass = hass
self._cur_id = 0
self._requests = {}
hass.services.register(
DOMAIN, SERVICE_CONFIGURE, self.handle_service_call)
# pylint: disable=too-many-arguments
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 included with
# the result fo the service call (current design limitation).
# Instead, we will set it to configured to give as feedback but delete
# it shortly after so that it is deleted when the client updates.
self.hass.states.set(entity_id, STATE_CONFIGURED)
def deferred_remove(event):
""" Remove the request state. """
self.hass.states.remove(entity_id)
self.hass.bus.listen_once(EVENT_TIME_CHANGED, deferred_remove)
def handle_service_call(self, call):
""" Handle a configure service call. """
request_id = call.data.get(ATTR_CONFIGURE_ID)
if not self._validate_request_id(request_id):
return
# pylint: disable=unused-variable
entity_id, fields, callback = self._requests[request_id]
# field validation goes here?
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):
""" Validate that the request belongs to this instance. """
return request_id in self._requests

View File

@ -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(
configurator_ids[0],
"Failed to register, please try again.")
configurator_ids.append(0)
else:
configurator.request_done(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

View File

@ -66,7 +66,6 @@ def setup(hass, config):
else: else:
return None return None
# pylint: disable=unused-argument
def schedule_light_on_sun_rise(entity, old_state, new_state): def schedule_light_on_sun_rise(entity, old_state, new_state):
"""The moment sun sets we want to have all the lights on. """The moment sun sets we want to have all the lights on.
We will schedule to have each light start after one another We will schedule to have each light start after one another

View File

@ -24,9 +24,8 @@ DEPENDENCIES = []
SERVICE_DEVICE_TRACKER_RELOAD = "reload_devices_csv" SERVICE_DEVICE_TRACKER_RELOAD = "reload_devices_csv"
GROUP_NAME_ALL_DEVICES = 'all_devices' GROUP_NAME_ALL_DEVICES = 'all devices'
ENTITY_ID_ALL_DEVICES = group.ENTITY_ID_FORMAT.format( ENTITY_ID_ALL_DEVICES = group.ENTITY_ID_FORMAT.format('all_devices')
GROUP_NAME_ALL_DEVICES)
ENTITY_ID_FORMAT = DOMAIN + '.{}' ENTITY_ID_FORMAT = DOMAIN + '.{}'
@ -114,7 +113,6 @@ class DeviceTracker(object):
dev_group = group.Group( dev_group = group.Group(
hass, GROUP_NAME_ALL_DEVICES, user_defined=False) hass, GROUP_NAME_ALL_DEVICES, user_defined=False)
# pylint: disable=unused-argument
def reload_known_devices_service(service): def reload_known_devices_service(service):
""" Reload known devices file. """ """ Reload known devices file. """
self._read_known_devices_file() self._read_known_devices_file()
@ -128,7 +126,8 @@ class DeviceTracker(object):
if self.invalid_known_devices_file: if self.invalid_known_devices_file:
return return
hass.track_time_change(update_device_state) hass.track_time_change(
update_device_state, second=[0, 12, 24, 36, 48])
hass.services.register(DOMAIN, hass.services.register(DOMAIN,
SERVICE_DEVICE_TRACKER_RELOAD, SERVICE_DEVICE_TRACKER_RELOAD,

View File

@ -17,7 +17,6 @@ MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument
def get_scanner(hass, config): def get_scanner(hass, config):
""" Validates config and returns a Luci scanner. """ """ Validates config and returns a Luci scanner. """
if not validate_config(config, if not validate_config(config,

View File

@ -14,7 +14,6 @@ MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument
def get_scanner(hass, config): def get_scanner(hass, config):
""" Validates config and returns a Netgear scanner. """ """ Validates config and returns a Netgear scanner. """
if not validate_config(config, if not validate_config(config,

View File

@ -20,7 +20,6 @@ MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument
def get_scanner(hass, config): def get_scanner(hass, config):
""" Validates config and returns a Nmap scanner. """ """ Validates config and returns a Nmap scanner. """
if not validate_config(config, {DOMAIN: [CONF_HOSTS]}, if not validate_config(config, {DOMAIN: [CONF_HOSTS]},

View File

@ -20,7 +20,6 @@ CONF_HTTP_ID = "http_id"
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument
def get_scanner(hass, config): def get_scanner(hass, config):
""" Validates config and returns a Tomato scanner. """ """ Validates config and returns a Tomato scanner. """
if not validate_config(config, if not validate_config(config,

View File

@ -75,7 +75,6 @@ def setup(hass, config):
ATTR_DISCOVERED: info ATTR_DISCOVERED: info
}) })
# pylint: disable=unused-argument
def start_discovery(event): def start_discovery(event):
""" Start discovering. """ """ Start discovering. """
netdisco = DiscoveryService(SCAN_INTERVAL) netdisco = DiscoveryService(SCAN_INTERVAL)

View File

@ -163,7 +163,6 @@ class Group(object):
self.hass.bus.remove_listener( self.hass.bus.remove_listener(
ha.EVENT_STATE_CHANGED, self._update_group_state) ha.EVENT_STATE_CHANGED, self._update_group_state)
# pylint: disable=unused-argument
def _update_group_state(self, entity_id, old_state, new_state): def _update_group_state(self, entity_id, old_state, new_state):
""" Updates the group state based on a state change by """ Updates the group state based on a state change by
a tracked entity. """ a tracked entity. """

View File

@ -351,7 +351,6 @@ class RequestHandler(SimpleHTTPRequestHandler):
""" DELETE request handler. """ """ DELETE request handler. """
self._handle_request('DELETE') self._handle_request('DELETE')
# pylint: disable=unused-argument
def _handle_get_root(self, path_match, data): def _handle_get_root(self, path_match, data):
""" Renders the debug interface. """ """ Renders the debug interface. """
@ -390,17 +389,14 @@ class RequestHandler(SimpleHTTPRequestHandler):
"<splash-login auth='{}'></splash-login>" "<splash-login auth='{}'></splash-login>"
"</body></html>").format(app_url, auth)) "</body></html>").format(app_url, auth))
# pylint: disable=unused-argument
def _handle_get_api(self, path_match, data): def _handle_get_api(self, path_match, data):
""" Renders the debug interface. """ """ Renders the debug interface. """
self._json_message("API running.") self._json_message("API running.")
# pylint: disable=unused-argument
def _handle_get_api_states(self, path_match, data): def _handle_get_api_states(self, path_match, data):
""" Returns a dict containing all entity ids and their state. """ """ Returns a dict containing all entity ids and their state. """
self._write_json(self.server.hass.states.all()) self._write_json(self.server.hass.states.all())
# pylint: disable=unused-argument
def _handle_get_api_states_entity(self, path_match, data): def _handle_get_api_states_entity(self, path_match, data):
""" Returns the state of a specific entity. """ """ Returns the state of a specific entity. """
entity_id = path_match.group('entity_id') entity_id = path_match.group('entity_id')

View File

@ -1,2 +1,2 @@
""" DO NOT MODIFY. Auto-generated by build_frontend script """ """ DO NOT MODIFY. Auto-generated by build_frontend script """
VERSION = "1d7f65f99c286d2c897b900518ebb663" VERSION = "43699d5ec727d3444985a1028d21e0d9"

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

View File

@ -0,0 +1,28 @@
<script src="../bower_components/moment/moment.js"></script>
<link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="../components/state-info.html">
<polymer-element name="state-card-configurator" attributes="stateObj api" noscript>
<template>
<style>
.state {
margin-left: 16px;
text-transform: capitalize;
font-weight: 300;
font-size: 1.3rem;
text-align: right;
}
</style>
<div horizontal justified layout>
<state-info stateObj="{{stateObj}}"></state-info>
<div class='state'>{{stateObj.stateDisplay}}</div>
</div>
<!-- pre load the image so the dialog is rendered the proper size -->
<template if="{{stateObj.attributes.description_image}}">
<img hidden src="{{stateObj.attributes.description_image}}" />
</template>
</template>
</polymer-element>

View File

@ -3,6 +3,7 @@
<link rel="import" href="state-card-display.html"> <link rel="import" href="state-card-display.html">
<link rel="import" href="state-card-toggle.html"> <link rel="import" href="state-card-toggle.html">
<link rel="import" href="state-card-thermostat.html"> <link rel="import" href="state-card-thermostat.html">
<link rel="import" href="state-card-configurator.html">
<polymer-element name="state-card-content" attributes="api stateObj"> <polymer-element name="state-card-content" attributes="api stateObj">
<template> <template>

View File

@ -62,6 +62,9 @@
case "sensor": case "sensor":
return "visibility"; return "visibility";
case "configurator":
return "settings";
default: default:
return "bookmark-outline"; return "bookmark-outline";
} }

View File

@ -36,7 +36,6 @@ Polymer({
/** /**
* Whenever the attributes change, the more info component can * Whenever the attributes change, the more info component can
* hide or show elements. We will reposition the dialog. * hide or show elements. We will reposition the dialog.
* DISABLED FOR NOW - BAD UX
*/ */
reposition: function(oldVal, newVal) { reposition: function(oldVal, newVal) {
// Only resize if already open // Only resize if already open

View File

@ -39,8 +39,8 @@
<more-info-dialog id="moreInfoDialog" api={{api}}></more-info-dialog> <more-info-dialog id="moreInfoDialog" api={{api}}></more-info-dialog>
</template> </template>
<script> <script>
var domainsWithCard = ['thermostat']; var domainsWithCard = ['thermostat', 'configurator'];
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;
@ -137,6 +137,14 @@
}, },
// local methods // local methods
removeState: function(entityId) {
var state = this.getState(entityId);
if (state !== null) {
this.states.splice(this.states.indexOf(state), 1);
}
},
getState: function(entityId) { getState: function(entityId) {
var found = this.states.filter(function(state) { var found = this.states.filter(function(state) {
return state.entity_id == entityId; return state.entity_id == entityId;
@ -158,6 +166,11 @@
return states; return states;
}, },
getEntityIDs: function() {
return this.states.map(
function(state) { return state.entity_id; });
},
hasService: function(domain, service) { hasService: function(domain, service) {
var found = this.services.filter(function(serv) { var found = this.services.filter(function(serv) {
return serv.domain == domain && serv.services.indexOf(service) !== -1; return serv.domain == domain && serv.services.indexOf(service) !== -1;
@ -179,8 +192,8 @@
this.stateUpdateTimeout = setTimeout(this.fetchStates.bind(this), 60000); this.stateUpdateTimeout = setTimeout(this.fetchStates.bind(this), 60000);
}, },
_sortStates: function(states) { _sortStates: function() {
states.sort(function(one, two) { this.states.sort(function(one, two) {
if (one.entity_id > two.entity_id) { if (one.entity_id > two.entity_id) {
return 1; return 1;
} else if (one.entity_id < two.entity_id) { } else if (one.entity_id < two.entity_id) {
@ -191,34 +204,62 @@
}); });
}, },
/**
* Pushes a new state to the state machine.
* Will resort the states after a push and fire states-updated event.
*/
_pushNewState: function(new_state) { _pushNewState: function(new_state) {
var state; if (this.__pushNewState(new_state)) {
var stateFound = false; this._sortStates();
for(var i = 0; i < this.states.length; i++) {
if(this.states[i].entity_id == new_state.entity_id) {
state = this.states[i];
state.attributes = new_state.attributes;
state.last_changed = new_state.last_changed;
state.state = new_state.state;
stateFound = true;
break;
}
}
if(!stateFound) {
this.states.push(new State(new_state, this));
this._sortStates(this.states);
} }
this.fire('states-updated'); this.fire('states-updated');
}, },
_pushNewStates: function(new_states) { /**
new_states.map(function(state) { * Creates or updates a state. Returns if a new state was added.
this._pushNewState(state); */
__pushNewState: function(new_state) {
var curState = this.getState(new_state.entity_id);
if (curState === null) {
this.states.push(new State(new_state, this));
return true;
} else {
curState.attributes = new_state.attributes;
curState.last_changed = new_state.last_changed;
curState.state = new_state.state;
return false;
}
},
_pushNewStates: function(newStates, removeNonPresent) {
removeNonPresent = !!removeNonPresent;
var currentEntityIds = removeNonPresent ? this.getEntityIDs() : [];
var hasNew = newStates.reduce(function(hasNew, newState) {
var isNewState = this.__pushNewState(newState);
if (isNewState) {
return true;
} else if(removeNonPresent) {
currentEntityIds.splice(currentEntityIds.indexOf(newState.entity_id), 1);
}
return hasNew;
}.bind(this), false);
currentEntityIds.forEach(function(entityId) {
this.removeState(entityId);
}.bind(this)); }.bind(this));
if (hasNew) {
this._sortStates();
}
this.fire('states-updated');
}, },
// call api methods // call api methods
@ -238,13 +279,7 @@
fetchStates: function(onSuccess, onError) { fetchStates: function(onSuccess, onError) {
var successStatesUpdate = function(newStates) { var successStatesUpdate = function(newStates) {
this._sortStates(newStates); this._pushNewStates(newStates, true);
this.states = newStates.map(function(json) {
return new State(json, this);
}.bind(this));
this.fire('states-updated');
this._laterFetchStates(); this._laterFetchStates();

View File

@ -94,15 +94,17 @@
</paper-dropdown> </paper-dropdown>
</paper-menu-button> </paper-menu-button>
<div class="bottom fit" horizontal layout> <template if="{{hasCustomGroups}}">
<paper-tabs id="tabsHolder" noink flex <div class="bottom fit" horizontal layout>
selected="0" on-core-select="{{tabClicked}}"> <paper-tabs id="tabsHolder" noink flex
selected="0" on-core-select="{{tabClicked}}">
<paper-tab>ALL</paper-tab>
<paper-tab data-filter='customgroup'>GROUPS</paper-tab> <paper-tab>ALL</paper-tab>
<paper-tab data-filter='customgroup'>GROUPS</paper-tab>
</paper-tabs>
</div> </paper-tabs>
</div>
</template>
</core-toolbar> </core-toolbar>
<state-cards <state-cards

View File

@ -0,0 +1,91 @@
<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.center {
text-align: center;
}
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}}">
{{stateObj.attributes.errors}}
</p>
<p class='center' 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>

View File

@ -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>

View File

@ -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>

View File

@ -46,7 +46,6 @@ def media_prev_track(hass):
hass.services.call(DOMAIN, SERVICE_MEDIA_PREV_TRACK) hass.services.call(DOMAIN, SERVICE_MEDIA_PREV_TRACK)
# pylint: disable=unused-argument
def setup(hass, config): def setup(hass, config):
""" Listen for keyboard events. """ """ Listen for keyboard events. """
try: try:

View File

@ -57,16 +57,15 @@ 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
DOMAIN = "light" DOMAIN = "light"
DEPENDENCIES = [] DEPENDENCIES = []
GROUP_NAME_ALL_LIGHTS = 'all_lights' GROUP_NAME_ALL_LIGHTS = 'all lights'
ENTITY_ID_ALL_LIGHTS = group.ENTITY_ID_FORMAT.format( ENTITY_ID_ALL_LIGHTS = group.ENTITY_ID_FORMAT.format('all_lights')
GROUP_NAME_ALL_LIGHTS)
ENTITY_ID_FORMAT = DOMAIN + ".{}" ENTITY_ID_FORMAT = DOMAIN + ".{}"
@ -93,6 +92,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,10 +168,33 @@ 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
def update_lights_state(now): def update_lights_state(now):
""" Update the states of all the lights. """ """ Update the states of all the lights. """
if lights: if lights:
@ -182,28 +205,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)

View File

@ -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,59 @@ 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 # pylint: disable=unused-variable
import phue # noqa
except ImportError: except ImportError:
logger.exception("Error while importing dependency phue.") _LOGGER.exception("Error while importing dependency phue.")
return [] return
host = config.get(CONF_HOST, None) if discovery_info is not None:
host = urlparse(discovery_info).hostname
else:
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):
""" Setup a phue bridge based on host parameter. """
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(request_id)
lights = {} lights = {}
@ -47,25 +81,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')
# We got an error if this method is called while we are configuring
if host in _CONFIGURING:
configurator.notify_errors(
_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):

View File

@ -9,31 +9,21 @@ from homeassistant.components.wink import WinkToggleDevice
from homeassistant.const import CONF_ACCESS_TOKEN from homeassistant.const import CONF_ACCESS_TOKEN
# pylint: disable=unused-argument def setup_platform(hass, config, add_devices_callback, discovery_info=None):
def get_devices(hass, config):
""" 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
pywink.set_bearer_token(token) elif token is not None:
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):

View File

@ -11,7 +11,6 @@ from homeassistant.const import CONF_API_KEY
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument
def get_service(hass, config): def get_service(hass, config):
""" Get the pushbullet notification service. """ """ Get the pushbullet notification service. """

View File

@ -30,7 +30,6 @@ def setup(hass, config):
entities = {ENTITY_ID_FORMAT.format(util.slugify(pname)): pstring entities = {ENTITY_ID_FORMAT.format(util.slugify(pname)): pstring
for pname, pstring in config[DOMAIN].items()} for pname, pstring in config[DOMAIN].items()}
# pylint: disable=unused-argument
def update_process_states(time): def update_process_states(time):
""" Check ps for currently running processes and update states. """ """ Check ps for currently running processes and update states. """
with os.popen(PS_STRING, 'r') as psfile: with os.popen(PS_STRING, 'r') as psfile:

View File

@ -47,7 +47,6 @@ def setup(hass, config):
sensors = platform_devices_from_config( sensors = platform_devices_from_config(
config, DOMAIN, hass, ENTITY_ID_FORMAT, logger) config, DOMAIN, hass, ENTITY_ID_FORMAT, logger)
# pylint: disable=unused-argument
@util.Throttle(MIN_TIME_BETWEEN_SCANS) @util.Throttle(MIN_TIME_BETWEEN_SCANS)
def update_sensor_states(now): def update_sensor_states(now):
""" Update states of all sensors. """ """ Update states of all sensors. """

View File

@ -8,7 +8,6 @@ from homeassistant.components.wink import WinkSensorDevice
from homeassistant.const import CONF_ACCESS_TOKEN from homeassistant.const import CONF_ACCESS_TOKEN
# pylint: disable=unused-argument
def get_devices(hass, config): def get_devices(hass, config):
""" Find and return Wink sensors. """ """ Find and return Wink sensors. """
token = config.get(CONF_ACCESS_TOKEN) token = config.get(CONF_ACCESS_TOKEN)
@ -24,7 +23,6 @@ def get_devices(hass, config):
return get_sensors() return get_sensors()
# pylint: disable=unused-argument
def devices_discovered(hass, config, info): def devices_discovered(hass, config, info):
""" Called when a device is discovered. """ """ Called when a device is discovered. """
return get_sensors() return get_sensors()

View File

@ -78,7 +78,6 @@ def setup(hass, config):
hass.services.register( hass.services.register(
DOMAIN, SERVICE_TEST_UNKNOWN_ALARM, lambda call: unknown_alarm()) DOMAIN, SERVICE_TEST_UNKNOWN_ALARM, lambda call: unknown_alarm())
# pylint: disable=unused-argument
def unknown_alarm_if_lights_on(entity_id, old_state, new_state): def unknown_alarm_if_lights_on(entity_id, old_state, new_state):
""" Called when a light has been turned on. """ """ Called when a light has been turned on. """
if not device_tracker.is_on(hass): if not device_tracker.is_on(hass):
@ -88,7 +87,6 @@ def setup(hass, config):
light.ENTITY_ID_ALL_LIGHTS, light.ENTITY_ID_ALL_LIGHTS,
unknown_alarm_if_lights_on, STATE_OFF, STATE_ON) unknown_alarm_if_lights_on, STATE_OFF, STATE_ON)
# pylint: disable=unused-argument
def ring_known_alarm(entity_id, old_state, new_state): def ring_known_alarm(entity_id, old_state, new_state):
""" Called when a known person comes home. """ """ Called when a known person comes home. """
if light.is_on(hass, known_light_id): if light.is_on(hass, known_light_id):

View File

@ -11,15 +11,14 @@ 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'
DEPENDENCIES = [] DEPENDENCIES = []
GROUP_NAME_ALL_SWITCHES = 'all_switches' GROUP_NAME_ALL_SWITCHES = 'all switches'
ENTITY_ID_ALL_SWITCHES = group.ENTITY_ID_FORMAT.format( ENTITY_ID_ALL_SWITCHES = group.ENTITY_ID_FORMAT.format('all_switches')
GROUP_NAME_ALL_SWITCHES)
ENTITY_ID_FORMAT = DOMAIN + '.{}' ENTITY_ID_FORMAT = DOMAIN + '.{}'
@ -65,7 +64,6 @@ def setup(hass, config):
switches = platform_devices_from_config( switches = platform_devices_from_config(
config, DOMAIN, hass, ENTITY_ID_FORMAT, logger) config, DOMAIN, hass, ENTITY_ID_FORMAT, logger)
# pylint: disable=unused-argument
@util.Throttle(MIN_TIME_BETWEEN_SCANS) @util.Throttle(MIN_TIME_BETWEEN_SCANS)
def update_states(now): def update_states(now):
""" Update states of all switches. """ """ Update states of all switches. """
@ -90,9 +88,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

View File

@ -11,7 +11,6 @@ except ImportError:
pass pass
# pylint: disable=unused-argument
def get_devices(hass, config): def get_devices(hass, config):
""" Find and return Tellstick switches. """ """ Find and return Tellstick switches. """
try: try:
@ -54,12 +53,10 @@ class TellstickSwitch(ToggleDevice):
return last_command == tc_constants.TELLSTICK_TURNON return last_command == tc_constants.TELLSTICK_TURNON
# pylint: disable=unused-argument
def turn_on(self, **kwargs): def turn_on(self, **kwargs):
""" Turns the switch on. """ """ Turns the switch on. """
self.tellstick.turn_on() self.tellstick.turn_on()
# pylint: disable=unused-argument
def turn_off(self, **kwargs): def turn_off(self, **kwargs):
""" Turns the switch off. """ """ Turns the switch off. """
self.tellstick.turn_off() self.tellstick.turn_off()

View File

@ -7,7 +7,6 @@ from homeassistant.components.switch import (
ATTR_TODAY_MWH, ATTR_CURRENT_POWER_MWH) ATTR_TODAY_MWH, ATTR_CURRENT_POWER_MWH)
# pylint: disable=unused-argument
def get_devices(hass, config): def get_devices(hass, config):
""" Find and return WeMo switches. """ """ Find and return WeMo switches. """

View File

@ -8,7 +8,6 @@ from homeassistant.components.wink import WinkToggleDevice
from homeassistant.const import CONF_ACCESS_TOKEN from homeassistant.const import CONF_ACCESS_TOKEN
# pylint: disable=unused-argument
def get_devices(hass, config): def get_devices(hass, config):
""" Find and return Wink switches. """ """ Find and return Wink switches. """
token = config.get(CONF_ACCESS_TOKEN) token = config.get(CONF_ACCESS_TOKEN)
@ -24,7 +23,6 @@ def get_devices(hass, config):
return get_switches() return get_switches()
# pylint: disable=unused-argument
def devices_discovered(hass, config, info): def devices_discovered(hass, config, info):
""" Called when a device is discovered. """ """ Called when a device is discovered. """
return get_switches() return get_switches()

View File

@ -129,7 +129,6 @@ def setup(hass, config):
sensor.has_value(datatype): sensor.has_value(datatype):
update_sensor_value_state(sensor_name, sensor.value(datatype)) update_sensor_value_state(sensor_name, sensor.value(datatype))
# pylint: disable=unused-argument
def update_sensors_state(time): def update_sensors_state(time):
""" Update the state of all sensors """ """ Update the state of all sensors """
for sensor in sensors: for sensor in sensors:

View File

@ -67,7 +67,6 @@ def setup(hass, config):
if not thermostats: if not thermostats:
return False return False
# pylint: disable=unused-argument
@util.Throttle(MIN_TIME_BETWEEN_SCANS) @util.Throttle(MIN_TIME_BETWEEN_SCANS)
def update_state(now): def update_state(now):
""" Update thermostat state. """ """ Update thermostat state. """

View File

@ -7,7 +7,6 @@ from homeassistant.components.thermostat import ThermostatDevice
from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD, TEMP_CELCIUS) from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD, TEMP_CELCIUS)
# pylint: disable=unused-argument
def get_devices(hass, config): def get_devices(hass, config):
""" Gets Nest thermostats. """ """ Gets Nest thermostats. """
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

1
homeassistant/external/noop vendored Submodule

@ -0,0 +1 @@
Subproject commit 45fae73c1f44342010fa07f3ed8909bf2819a508

View File

@ -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

View File

@ -12,6 +12,18 @@ 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):
""" Generate a unique entity ID based on given entity IDs or used ids. """
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 +172,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

View File

@ -8,12 +8,14 @@ reports=no
# cyclic-import - doesn't test if both import on load # cyclic-import - doesn't test if both import on load
# abstract-class-little-used - Prevents from setting right foundation # abstract-class-little-used - Prevents from setting right foundation
# abstract-class-not-used - is flaky, should not show up but does # abstract-class-not-used - is flaky, should not show up but does
# unused-argument - generic callbacks and setup methods create a lot of warnings
disable= disable=
locally-disabled, locally-disabled,
duplicate-code, duplicate-code,
cyclic-import, cyclic-import,
abstract-class-little-used, abstract-class-little-used,
abstract-class-not-used abstract-class-not-used,
unused-argument
[EXCEPTIONS] [EXCEPTIONS]
overgeneral-exceptions=Exception,HomeAssistantError overgeneral-exceptions=Exception,HomeAssistantError

View File

@ -5,3 +5,4 @@ fi
git pull --recurse-submodules=yes git pull --recurse-submodules=yes
git submodule update --init --recursive git submodule update --init --recursive
python3 -m pip install -r requirements.txt

View File

@ -24,6 +24,11 @@ def init(empty=False):
] ]
def get_lights(hass, config): def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Returns mock devices. """ """ Returns mock devices. """
add_devices_callback(DEVICES)
def get_lights():
""" Helper method to get current light objects. """
return DEVICES return DEVICES

View File

@ -0,0 +1,120 @@
"""
tests.test_component_configurator
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Tests Configurator component.
"""
# pylint: disable=too-many-public-methods,protected-access
import unittest
import homeassistant as ha
import homeassistant.components.configurator as configurator
from homeassistant.const import EVENT_TIME_CHANGED
class TestConfigurator(unittest.TestCase):
""" Test the chromecast module. """
def setUp(self): # pylint: disable=invalid-name
self.hass = ha.HomeAssistant()
def tearDown(self): # pylint: disable=invalid-name
""" Stop down stuff we started. """
self.hass.stop()
def test_request_least_info(self):
""" Test request config with least amount of data. """
request_id = configurator.request_config(
self.hass, "Test Request", lambda _: None)
self.assertEqual(
1, len(self.hass.services.services.get(configurator.DOMAIN, [])),
"No new service registered")
states = self.hass.states.all()
self.assertEqual(1, len(states), "Expected a new state registered")
state = states[0]
self.assertEqual(configurator.STATE_CONFIGURE, state.state)
self.assertEqual(
request_id, state.attributes.get(configurator.ATTR_CONFIGURE_ID))
def test_request_all_info(self):
""" Test request config with all possible info. """
values = [
"config_description", "config image url",
"config submit caption", []]
keys = [
configurator.ATTR_DESCRIPTION, configurator.ATTR_DESCRIPTION_IMAGE,
configurator.ATTR_SUBMIT_CAPTION, configurator.ATTR_FIELDS]
exp_attr = dict(zip(keys, values))
exp_attr[configurator.ATTR_CONFIGURE_ID] = configurator.request_config(
self.hass, "Test Request", lambda _: None,
*values)
states = self.hass.states.all()
self.assertEqual(1, len(states))
state = states[0]
self.assertEqual(configurator.STATE_CONFIGURE, state.state)
self.assertEqual(exp_attr, state.attributes)
def test_callback_called_on_configure(self):
""" Test if our callback gets called when configure service called. """
calls = []
request_id = configurator.request_config(
self.hass, "Test Request", lambda _: calls.append(1))
self.hass.services.call(
configurator.DOMAIN, configurator.SERVICE_CONFIGURE,
{configurator.ATTR_CONFIGURE_ID: request_id})
self.hass.pool.block_till_done()
self.assertEqual(1, len(calls), "Callback not called")
def test_state_change_on_notify_errors(self):
""" Test state change on notify errors. """
request_id = configurator.request_config(
self.hass, "Test Request", lambda _: None)
error = "Oh no bad bad bad"
configurator.notify_errors(request_id, error)
state = self.hass.states.all()[0]
self.assertEqual(error, state.attributes.get(configurator.ATTR_ERRORS))
def test_notify_errors_fail_silently_on_bad_request_id(self):
""" Test if notify errors fails silently with a bad request id. """
configurator.notify_errors(2015, "Try this error")
def test_request_done_works(self):
""" Test if calling request done works. """
request_id = configurator.request_config(
self.hass, "Test Request", lambda _: None)
configurator.request_done(request_id)
self.assertEqual(1, len(self.hass.states.all()))
self.hass.bus.fire(EVENT_TIME_CHANGED)
self.hass.pool.block_till_done()
self.assertEqual(0, len(self.hass.states.all()))
def test_request_done_fail_silently_on_bad_request_id(self):
""" Test that request_done fails silently with a bad request id. """
configurator.request_done(2016)

View File

@ -117,9 +117,15 @@ class TestComponentsDeviceTracker(unittest.TestCase):
dev3 = device_tracker.ENTITY_ID_FORMAT.format('DEV3') dev3 = device_tracker.ENTITY_ID_FORMAT.format('DEV3')
now = datetime.now() now = datetime.now()
nowAlmostMinGone = (now + device_tracker.TIME_DEVICE_NOT_FOUND -
timedelta(seconds=1)) # Device scanner scans every 12 seconds. We need to sync our times to
nowMinGone = nowAlmostMinGone + timedelta(seconds=2) # be every 12 seconds or else the time_changed event will be ignored.
nowAlmostMinimumGone = now + device_tracker.TIME_DEVICE_NOT_FOUND
nowAlmostMinimumGone -= timedelta(
seconds=12+(nowAlmostMinimumGone.second % 12))
nowMinimumGone = now + device_tracker.TIME_DEVICE_NOT_FOUND
nowMinimumGone += timedelta(seconds=12-(nowMinimumGone.second % 12))
# Test initial is correct # Test initial is correct
self.assertTrue(device_tracker.is_on(self.hass)) self.assertTrue(device_tracker.is_on(self.hass))
@ -145,10 +151,10 @@ class TestComponentsDeviceTracker(unittest.TestCase):
fil.write('dev2,Device 2,1,http://example.com/picture.jpg\n') fil.write('dev2,Device 2,1,http://example.com/picture.jpg\n')
fil.write('dev3,DEV3,1,\n') fil.write('dev3,DEV3,1,\n')
# reload dev file
scanner.come_home('dev1') scanner.come_home('dev1')
scanner.leave_home('dev2') scanner.leave_home('dev2')
# reload dev file
self.hass.services.call( self.hass.services.call(
device_tracker.DOMAIN, device_tracker.DOMAIN,
device_tracker.SERVICE_DEVICE_TRACKER_RELOAD) device_tracker.SERVICE_DEVICE_TRACKER_RELOAD)
@ -168,7 +174,7 @@ class TestComponentsDeviceTracker(unittest.TestCase):
# Test if device leaves what happens, test the time span # Test if device leaves what happens, test the time span
self.hass.bus.fire( self.hass.bus.fire(
ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: nowAlmostMinGone}) ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: nowAlmostMinimumGone})
self.hass.pool.block_till_done() self.hass.pool.block_till_done()
@ -179,7 +185,8 @@ class TestComponentsDeviceTracker(unittest.TestCase):
self.assertTrue(device_tracker.is_on(self.hass, dev3)) self.assertTrue(device_tracker.is_on(self.hass, dev3))
# Now test if gone for longer then error margin # Now test if gone for longer then error margin
self.hass.bus.fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: nowMinGone}) self.hass.bus.fire(
ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: nowMinimumGone})
self.hass.pool.block_till_done() self.hass.pool.block_till_done()

View File

@ -184,7 +184,7 @@ class TestHTTP(unittest.TestCase):
""" Test if the API allows us to fire an event. """ """ Test if the API allows us to fire an event. """
test_value = [] test_value = []
def listener(event): # pylint: disable=unused-argument def listener(event):
""" Helper method that will verify our event got called. """ """ Helper method that will verify our event got called. """
test_value.append(1) test_value.append(1)
@ -203,7 +203,7 @@ class TestHTTP(unittest.TestCase):
""" Test if the API allows us to fire an event. """ """ Test if the API allows us to fire an event. """
test_value = [] test_value = []
def listener(event): # pylint: disable=unused-argument def listener(event):
""" Helper method that will verify that our event got called and """ Helper method that will verify that our event got called and
that test if our data came through. """ that test if our data came through. """
if "test" in event.data: if "test" in event.data:
@ -225,7 +225,7 @@ class TestHTTP(unittest.TestCase):
""" Test if the API allows us to fire an event. """ """ Test if the API allows us to fire an event. """
test_value = [] test_value = []
def listener(event): # pylint: disable=unused-argument def listener(event):
""" Helper method that will verify our event got called. """ """ Helper method that will verify our event got called. """
test_value.append(1) test_value.append(1)
@ -281,7 +281,7 @@ class TestHTTP(unittest.TestCase):
""" Test if the API allows us to call a service. """ """ Test if the API allows us to call a service. """
test_value = [] test_value = []
def listener(service_call): # pylint: disable=unused-argument def listener(service_call):
""" Helper method that will verify that our service got called. """ """ Helper method that will verify that our service got called. """
test_value.append(1) test_value.append(1)
@ -300,7 +300,7 @@ class TestHTTP(unittest.TestCase):
""" Test if the API allows us to call a service. """ """ Test if the API allows us to call a service. """
test_value = [] test_value = []
def listener(service_call): # pylint: disable=unused-argument def listener(service_call):
""" Helper method that will verify that our service got called and """ Helper method that will verify that our service got called and
that test if our data came through. """ that test if our data came through. """
if "test" in service_call.data: if "test" in service_call.data:

View File

@ -8,7 +8,6 @@ Tests switch component.
import unittest import unittest
import os import os
import homeassistant as ha
import homeassistant.loader as loader import homeassistant.loader as loader
import homeassistant.util as util import homeassistant.util as util
from homeassistant.const import ( from homeassistant.const import (
@ -104,7 +103,7 @@ class TestLight(unittest.TestCase):
self.assertTrue( self.assertTrue(
light.setup(self.hass, {light.DOMAIN: {CONF_TYPE: 'test'}})) light.setup(self.hass, {light.DOMAIN: {CONF_TYPE: 'test'}}))
dev1, dev2, dev3 = platform.get_lights(None, None) dev1, dev2, dev3 = platform.get_lights()
# Test init # Test init
self.assertTrue(light.is_on(self.hass, dev1.entity_id)) self.assertTrue(light.is_on(self.hass, dev1.entity_id))
@ -214,7 +213,7 @@ class TestLight(unittest.TestCase):
light.ATTR_XY_COLOR: [prof_x, prof_y]}, light.ATTR_XY_COLOR: [prof_x, prof_y]},
data) data)
def test_light_profiles(self): def test_broken_light_profiles(self):
""" Test light profiles. """ """ Test light profiles. """
platform = loader.get_component('light.test') platform = loader.get_component('light.test')
platform.init() platform.init()
@ -230,8 +229,12 @@ class TestLight(unittest.TestCase):
self.hass, {light.DOMAIN: {CONF_TYPE: 'test'}} self.hass, {light.DOMAIN: {CONF_TYPE: 'test'}}
)) ))
# Clean up broken file def test_light_profiles(self):
os.remove(user_light_file) """ Test light profiles. """
platform = loader.get_component('light.test')
platform.init()
user_light_file = self.hass.get_config_path(light.LIGHT_PROFILES_FILE)
with open(user_light_file, 'w') as user_file: with open(user_light_file, 'w') as user_file:
user_file.write('id,x,y,brightness\n') user_file.write('id,x,y,brightness\n')
@ -241,7 +244,7 @@ class TestLight(unittest.TestCase):
self.hass, {light.DOMAIN: {CONF_TYPE: 'test'}} self.hass, {light.DOMAIN: {CONF_TYPE: 'test'}}
)) ))
dev1, dev2, dev3 = platform.get_lights(None, None) dev1, dev2, dev3 = platform.get_lights()
light.turn_on(self.hass, dev1.entity_id, profile='test') light.turn_on(self.hass, dev1.entity_id, profile='test')

View File

@ -93,7 +93,7 @@ class TestRemoteMethods(unittest.TestCase):
""" Test Python API fire_event. """ """ Test Python API fire_event. """
test_value = [] test_value = []
def listener(event): # pylint: disable=unused-argument def listener(event):
""" Helper method that will verify our event got called. """ """ Helper method that will verify our event got called. """
test_value.append(1) test_value.append(1)
@ -158,7 +158,7 @@ class TestRemoteMethods(unittest.TestCase):
""" Test Python API services.call. """ """ Test Python API services.call. """
test_value = [] test_value = []
def listener(service_call): # pylint: disable=unused-argument def listener(service_call):
""" Helper method that will verify that our service got called. """ """ Helper method that will verify that our service got called. """
test_value.append(1) test_value.append(1)
@ -214,7 +214,7 @@ class TestRemoteClasses(unittest.TestCase):
""" Test if events fired from the eventbus get fired. """ """ Test if events fired from the eventbus get fired. """
test_value = [] test_value = []
def listener(event): # pylint: disable=unused-argument def listener(event):
""" Helper method that will verify our event got called. """ """ Helper method that will verify our event got called. """
test_value.append(1) test_value.append(1)