Fixed gitmodules merge conflict

This commit is contained in:
jamespcole 2015-03-21 10:14:03 +11:00
commit 30c78b4054
66 changed files with 1580 additions and 359 deletions

View File

@ -24,6 +24,9 @@ omit =
homeassistant/components/device_tracker/tomato.py
homeassistant/components/device_tracker/netgear.py
homeassistant/components/device_tracker/nmap_tracker.py
homeassistant/components/light/vera.py
homeassistant/components/sensor/vera.py
homeassistant/components/switch/vera.py
[report]

7
.gitmodules vendored
View File

@ -13,6 +13,9 @@
[submodule "homeassistant/components/frontend/www_static/polymer/home-assistant-js"]
path = homeassistant/components/frontend/www_static/polymer/home-assistant-js
url = https://github.com/balloob/home-assistant-js.git
[submodule "homeassistant/external/vera"]
path = homeassistant/external/vera
url = https://github.com/jamespcole/home-assistant-vera-api.git
[submodule "homeassistant/external/nzbclients"]
path = homeassistant/external/nzbclients
url = https://github.com/jamespcole/home-assistant-nzb-clients.git
path = homeassistant/external/nzbclients
url = https://github.com/jamespcole/home-assistant-nzb-clients.git

View File

@ -4,7 +4,7 @@ MAINTAINER Paulus Schoutsen <Paulus@PaulusSchoutsen.nl>
VOLUME /config
RUN apt-get update && \
apt-get install -y cython3 libudev-dev python-sphinx python3-setuptools mercurial && \
apt-get install -y cython3 libudev-dev && \
apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \
pip3 install cython && \
scripts/build_python_openzwave

View File

@ -101,7 +101,8 @@ automation 2:
time_seconds: 0
execute_service: notify.notify
service_data: {"message":"It's 4, time for beer!"}
service_data:
message: It's 4, time for beer!
sensor:
platform: systemmonitor
@ -119,4 +120,31 @@ sensor:
- type: 'memory_free'
- type: 'processor_use'
- type: 'process'
arg: 'octave-cli'
arg: 'octave-cli'
script:
# Turns on the bedroom lights and then the living room lights 1 minute later
wakeup:
alias: Wake Up
sequence:
# alias is optional
- alias: Bedroom lights on
execute_service: light.turn_on
service_data:
entity_id: group.bedroom
- delay:
# supports seconds, milliseconds, minutes, hours, etc.
minutes: 1
- alias: Living room lights on
execute_service: light.turn_on
service_data:
entity_id: group.living_room
scene:
- name: Romantic
entities:
light.tv_back_light: on
light.ceiling:
state: on
color: [0.33, 0.66]
brightness: 200

View File

@ -115,6 +115,7 @@ class HomeAssistant(object):
action(now)
self.bus.listen(EVENT_TIME_CHANGED, point_in_time_listener)
return point_in_time_listener
# pylint: disable=too-many-arguments
def track_time_change(self, action,
@ -154,6 +155,7 @@ class HomeAssistant(object):
action(event.data[ATTR_NOW])
self.bus.listen(EVENT_TIME_CHANGED, time_listener)
return time_listener
def stop(self):
""" Stops Home Assistant and shuts down all threads. """
@ -457,6 +459,11 @@ class State(object):
self.last_changed = util.strip_microseconds(
last_changed or self.last_updated)
@property
def domain(self):
""" Returns domain of this state. """
return util.split_entity_id(self.entity_id)[0]
def copy(self):
""" Creates a copy of itself. """
return State(self.entity_id, self.state,

View File

@ -119,6 +119,11 @@ def from_config_file(config_path, hass=None):
if os.path.splitext(config_path)[1] == '.yaml':
# Read yaml
config_dict = yaml.load(io.open(config_path, 'r'))
# If YAML file was empty
if config_dict is None:
config_dict = {}
else:
# Read config
config = configparser.ConfigParser()

View File

@ -10,7 +10,7 @@ import threading
import json
import homeassistant as ha
from homeassistant.helpers import TrackStates
from homeassistant.helpers.state import TrackStates
import homeassistant.remote as rem
from homeassistant.const import (
URL_API, URL_API_STATES, URL_API_EVENTS, URL_API_SERVICES, URL_API_STREAM,

View File

@ -5,11 +5,10 @@ homeassistant.components.automation
Allows to setup simple automation rules via the config file.
"""
import logging
import json
from homeassistant.loader import get_component
from homeassistant.helpers import config_per_platform
from homeassistant.util import convert, split_entity_id
from homeassistant.util import split_entity_id
from homeassistant.const import ATTR_ENTITY_ID
DOMAIN = "automation"
@ -54,8 +53,7 @@ def _get_action(hass, config):
if CONF_SERVICE in config:
domain, service = split_entity_id(config[CONF_SERVICE])
service_data = convert(
config.get(CONF_SERVICE_DATA), json.loads, {})
service_data = config.get(CONF_SERVICE_DATA, {})
if not isinstance(service_data, dict):
_LOGGER.error(

View File

@ -5,8 +5,6 @@ homeassistant.components.automation.event
Offers event listening automation rules.
"""
import logging
import json
from homeassistant.util import convert
CONF_EVENT_TYPE = "event_type"
CONF_EVENT_DATA = "event_data"
@ -22,7 +20,7 @@ def register(hass, config, action):
_LOGGER.error("Missing configuration key %s", CONF_EVENT_TYPE)
return False
event_data = convert(config.get(CONF_EVENT_DATA), json.loads, {})
event_data = config.get(CONF_EVENT_DATA, {})
def handle_event(event):
""" Listens for events and calls the action when data matches. """

View File

@ -0,0 +1,72 @@
"""
homeassistant.components.conversation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides functionality to have conversations with Home Assistant.
This is more a proof of concept.
"""
import logging
import re
import homeassistant
from homeassistant.const import (
ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF)
DOMAIN = "conversation"
DEPENDENCIES = []
SERVICE_PROCESS = "process"
ATTR_TEXT = "text"
REGEX_TURN_COMMAND = re.compile(r'turn (?P<name>(?: |\w)+) (?P<command>\w+)')
def setup(hass, config):
""" Registers the process service. """
logger = logging.getLogger(__name__)
def process(service):
""" Parses text into commands for Home Assistant. """
if ATTR_TEXT not in service.data:
logger.error("Received process service call without a text")
return
text = service.data[ATTR_TEXT].lower()
match = REGEX_TURN_COMMAND.match(text)
if not match:
logger.error("Unable to process: %s", text)
return
name, command = match.groups()
entity_ids = [
state.entity_id for state in hass.states.all()
if state.attributes.get(ATTR_FRIENDLY_NAME, "").lower() == name]
if not entity_ids:
logger.error(
"Could not find entity id %s from text %s", name, text)
return
if command == 'on':
hass.services.call(
homeassistant.DOMAIN, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: entity_ids,
}, blocking=True)
elif command == 'off':
hass.services.call(
homeassistant.DOMAIN, SERVICE_TURN_OFF, {
ATTR_ENTITY_ID: entity_ids,
}, blocking=True)
else:
logger.error(
'Got unsupported command %s from text %s', command, text)
hass.services.register(DOMAIN, SERVICE_PROCESS, process)
return True

View File

@ -10,7 +10,7 @@ import homeassistant as ha
import homeassistant.bootstrap as bootstrap
import homeassistant.loader as loader
from homeassistant.const import (
CONF_PLATFORM, ATTR_ENTITY_PICTURE, STATE_ON,
CONF_PLATFORM, ATTR_ENTITY_PICTURE, ATTR_ENTITY_ID,
CONF_LATITUDE, CONF_LONGITUDE)
DOMAIN = "demo"
@ -18,7 +18,7 @@ DOMAIN = "demo"
DEPENDENCIES = []
COMPONENTS_WITH_DEMO_PLATFORM = [
'switch', 'light', 'thermostat', 'sensor', 'media_player']
'switch', 'light', 'thermostat', 'sensor', 'media_player', 'notify']
def setup(hass, config):
@ -29,16 +29,12 @@ def setup(hass, config):
config.setdefault(ha.DOMAIN, {})
config.setdefault(DOMAIN, {})
if config[DOMAIN].get('hide_demo_state') != '1':
if config[DOMAIN].get('hide_demo_state') != 1:
hass.states.set('a.Demo_Mode', 'Enabled')
# Setup sun
if CONF_LATITUDE not in config[ha.DOMAIN]:
config[ha.DOMAIN][CONF_LATITUDE] = '32.87336'
if CONF_LONGITUDE not in config[ha.DOMAIN]:
config[ha.DOMAIN][CONF_LONGITUDE] = '-117.22743'
config[ha.DOMAIN].setdefault(CONF_LATITUDE, '32.87336')
config[ha.DOMAIN].setdefault(CONF_LONGITUDE, '-117.22743')
loader.get_component('sun').setup(hass, config)
# Setup demo platforms
@ -52,21 +48,57 @@ def setup(hass, config):
group.setup_group(hass, 'living room', [lights[0], lights[1], switches[0]])
group.setup_group(hass, 'bedroom', [lights[2], switches[1]])
# Setup process
hass.states.set("process.XBMC", STATE_ON)
# Setup scripts
bootstrap.setup_component(
hass, 'script',
{'script': {
'demo': {
'alias': 'Demo {}'.format(lights[0]),
'sequence': [{
'execute_service': 'light.turn_off',
'service_data': {ATTR_ENTITY_ID: lights[0]}
}, {
'delay': {'seconds': 5}
}, {
'execute_service': 'light.turn_on',
'service_data': {ATTR_ENTITY_ID: lights[0]}
}, {
'delay': {'seconds': 5}
}, {
'execute_service': 'light.turn_off',
'service_data': {ATTR_ENTITY_ID: lights[0]}
}]
}}})
# Setup device tracker
hass.states.set("device_tracker.Paulus", "home",
# Setup scenes
bootstrap.setup_component(
hass, 'scene',
{'scene': [
{'name': 'Romantic lights',
'entities': {
lights[0]: True,
lights[1]: {'state': 'on', 'xy_color': [0.33, 0.66],
'brightness': 200},
}},
{'name': 'Switch on and off',
'entities': {
switches[0]: True,
switches[1]: False,
}},
]})
# Setup fake device tracker
hass.states.set("device_tracker.paulus", "home",
{ATTR_ENTITY_PICTURE:
"http://graph.facebook.com/schoutsen/picture"})
hass.states.set("device_tracker.Anne_Therese", "not_home",
hass.states.set("device_tracker.anne_therese", "not_home",
{ATTR_ENTITY_PICTURE:
"http://graph.facebook.com/anne.t.frederiksen/picture"})
hass.states.set("group.all_devices", "home",
{
"auto": True,
"entity_id": [
ATTR_ENTITY_ID: [
"device_tracker.Paulus",
"device_tracker.Anne_Therese"
]

View File

@ -153,7 +153,7 @@ def setup(hass, config):
logger.info(
"Everyone has left but there are lights on. Turning them off")
light.turn_off(hass)
light.turn_off(hass, light_ids)
# Track home coming of each device
hass.states.track_change(

View File

@ -236,7 +236,7 @@ class DeviceTracker(object):
try:
for row in csv.DictReader(inp):
device = row['device']
device = row['device'].upper()
if row['track'] == '1':
if device in self.tracked:

View File

@ -1,6 +1,6 @@
""" Supports scanning using nmap. """
import logging
from datetime import timedelta
from datetime import timedelta, datetime
import threading
from collections import namedtuple
import subprocess
@ -11,7 +11,7 @@ from libnmap.parser import NmapParser, NmapParserException
from homeassistant.const import CONF_HOSTS
from homeassistant.helpers import validate_config
from homeassistant.util import Throttle
from homeassistant.util import Throttle, convert
from homeassistant.components.device_tracker import DOMAIN
# Return cached results if last scan was less then this time ago
@ -19,6 +19,9 @@ MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__)
# interval in minutes to exclude devices from a scan while they are home
CONF_HOME_INTERVAL = "home_interval"
def get_scanner(hass, config):
""" Validates config and returns a Nmap scanner. """
@ -30,7 +33,7 @@ def get_scanner(hass, config):
return scanner if scanner.success_init else None
Device = namedtuple("Device", ["mac", "name"])
Device = namedtuple("Device", ["mac", "name", "ip", "last_update"])
def _arp(ip_address):
@ -53,6 +56,8 @@ class NmapDeviceScanner(object):
self.lock = threading.Lock()
self.hosts = config[CONF_HOSTS]
minutes = convert(config.get(CONF_HOME_INTERVAL), int, 0)
self.home_interval = timedelta(minutes=minutes)
self.success_init = True
self._update_info()
@ -77,6 +82,33 @@ class NmapDeviceScanner(object):
else:
return None
def _parse_results(self, stdout):
""" Parses results from an nmap scan.
Returns True if successful, False otherwise. """
try:
results = NmapParser.parse(stdout)
now = datetime.now()
self.last_results = []
for host in results.hosts:
if host.is_up():
if host.hostnames:
name = host.hostnames[0]
else:
name = host.ipv4
if host.mac:
mac = host.mac
else:
mac = _arp(host.ipv4)
if mac:
device = Device(mac, name, host.ipv4, now)
self.last_results.append(device)
_LOGGER.info("nmap scan successful")
return True
except NmapParserException as parse_exc:
_LOGGER.error("failed to parse nmap results: %s", parse_exc.msg)
self.last_results = []
return False
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
""" Scans the network for devices.
@ -87,35 +119,24 @@ class NmapDeviceScanner(object):
with self.lock:
_LOGGER.info("Scanning")
nmap = NmapProcess(targets=self.hosts, options="-F")
options = "-F"
exclude_targets = set()
if self.home_interval:
now = datetime.now()
for host in self.last_results:
if host.last_update + self.home_interval > now:
exclude_targets.add(host)
if len(exclude_targets) > 0:
target_list = [t.ip for t in exclude_targets]
options += " --exclude {}".format(",".join(target_list))
nmap = NmapProcess(targets=self.hosts, options=options)
nmap.run()
if nmap.rc == 0:
try:
results = NmapParser.parse(nmap.stdout)
self.last_results = []
for host in results.hosts:
if host.is_up():
if host.hostnames:
name = host.hostnames[0]
else:
name = host.ipv4
if host.mac:
mac = host.mac
else:
mac = _arp(host.ipv4)
if mac:
device = Device(mac, name)
self.last_results.append(device)
_LOGGER.info("nmap scan successful")
return True
except NmapParserException as parse_exc:
_LOGGER.error("failed to parse nmap results: %s",
parse_exc.msg)
self.last_results = []
return False
if self._parse_results(nmap.stdout):
self.last_results.extend(exclude_targets)
else:
self.last_results = []
_LOGGER.error(nmap.stderr)

View File

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

File diff suppressed because one or more lines are too long

View File

@ -1,23 +1,11 @@
<link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="./state-card-display.html">
<link rel="import" href="../components/state-info.html">
<polymer-element name="state-card-configurator" attributes="stateObj" 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>
<state-card-display stateObj="{{stateObj}}"></state-card-display>
<!-- pre load the image so the dialog is rendered the proper size -->
<template if="{{stateObj.attributes.description_image}}">

View File

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

View File

@ -0,0 +1,27 @@
<link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="./state-card-display.html">
<link rel="import" href="./state-card-toggle.html">
<polymer-element name="state-card-scene" attributes="stateObj">
<template>
<template if={{allowToggle}}>
<state-card-toggle stateObj="{{stateObj}}"></state-card-toggle>
</template>
<template if={{!allowToggle}}>
<state-card-display stateObj="{{stateObj}}"></state-card-display>
</template>
</template>
<script>
(function() {
Polymer({
allowToggle: false,
stateObjChanged: function(oldVal, newVal) {
this.allowToggle = newVal.state === 'off' ||
newVal.attributes.active_requested;
},
});
})();
</script>
</polymer-element>

View File

@ -1,72 +1,24 @@
<link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="../bower_components/core-icon/core-icon.html">
<link rel="import" href="../bower_components/core-icons/social-icons.html">
<link rel="import" href="../bower_components/core-icons/image-icons.html">
<link rel="import" href="../bower_components/core-icons/hardware-icons.html">
<link rel="import" href="../resources/home-assistant-icons.html">
<polymer-element name="domain-icon"
attributes="domain state" constructor="DomainIcon">
<template>
<core-icon icon="{{icon(domain, state)}}"></core-icon>
<core-icon icon="{{icon}}"></core-icon>
</template>
<script>
Polymer({
icon: '',
icon: function(domain, state) {
switch(domain) {
case "homeassistant":
return "home";
case "group":
return "homeassistant-24:group";
case "device_tracker":
return "social:person";
case "switch":
return "image:flash-on";
case "media_player":
var icon = "hardware:cast";
if (state !== "idle") {
icon += "-connected";
}
return icon;
case "process":
return "hardware:memory";
case "sun":
return "image:wb-sunny";
case "light":
return "image:wb-incandescent";
case "simple_alarm":
return "social:notifications";
case "notify":
return "announcement";
case "thermostat":
return "homeassistant-100:thermostat";
case "sensor":
return "visibility";
case "configurator":
return "settings";
default:
return "bookmark-outline";
}
}
observe: {
'domain': 'updateIcon',
'state' : 'updateIcon',
},
updateIcon: function() {
this.icon = window.hass.uiUtil.domainIcon(this.domain, this.state);
},
});
</script>
</polymer-element>

View File

@ -22,9 +22,9 @@
</template>
<div>
<template repeat="{{state in states}}">
<template repeat="{{entityID in entityIDs}}">
<div class='eventContainer'>
<a on-click={{handleClick}}>{{state.entityId}}</a>
<a on-click={{handleClick}}>{{entityID}}</a>
</div>
</template>
@ -35,7 +35,7 @@
Polymer(Polymer.mixin({
cbEventClicked: null,
states: [],
entityIDs: [],
attached: function() {
this.listenToStores(true);
@ -46,7 +46,7 @@
},
stateStoreChanged: function(stateStore) {
this.states = stateStore.all();
this.entityIDs = stateStore.entityIDs.toArray();
},
handleClick: function(ev) {

View File

@ -48,7 +48,7 @@
},
eventStoreChanged: function(eventStore) {
this.events = eventStore.all();
this.events = eventStore.all.toArray();
},
handleClick: function(ev) {

View File

@ -11,7 +11,7 @@
Polymer(Polymer.mixin({
lastId: null,
ready: function() {
attached: function() {
this.listenToStores(true);
},
@ -22,11 +22,13 @@
notificationStoreChanged: function(notificationStore) {
if (notificationStore.hasNewNotifications(this.lastId)) {
var toast = this.$.toast;
var notification = notificationStore.getLastNotification();
var notification = notificationStore.lastNotification;
this.lastId = notification.id;
toast.text = notification.message;
toast.show();
if (notification) {
this.lastId = notification.id;
toast.text = notification.message;
toast.show();
}
}
},

View File

@ -7,35 +7,37 @@
{{ relativeTime }}
</template>
<script>
var UPDATE_INTERVAL = 60000; // 60 seconds
(function() {
var UPDATE_INTERVAL = 60000; // 60 seconds
var parseDateTime = window.hass.util.parseDateTime;
var parseDateTime = window.hass.util.parseDateTime;
Polymer({
relativeTime: "",
parsedDateTime: null,
Polymer({
relativeTime: "",
parsedDateTime: null,
created: function() {
this.updateRelative = this.updateRelative.bind(this);
},
created: function() {
this.updateRelative = this.updateRelative.bind(this);
},
attached: function() {
this._interval = setInterval(this.updateRelative, UPDATE_INTERVAL);
},
attached: function() {
this._interval = setInterval(this.updateRelative, UPDATE_INTERVAL);
},
detached: function() {
clearInterval(this._interval);
},
detached: function() {
clearInterval(this._interval);
},
datetimeChanged: function(oldVal, newVal) {
this.parsedDateTime = newVal ? parseDateTime(newVal) : null;
datetimeChanged: function(oldVal, newVal) {
this.parsedDateTime = newVal ? parseDateTime(newVal) : null;
this.updateRelative();
},
this.updateRelative();
},
updateRelative: function() {
this.relativeTime = this.parsedDateTime ? moment(this.parsedDateTime).fromNow() : "";
},
});
updateRelative: function() {
this.relativeTime = this.parsedDateTime ? moment(this.parsedDateTime).fromNow() : "";
},
});
})();
</script>
</polymer-element>

View File

@ -34,10 +34,10 @@
<div>
<core-menu selected="0">
<template repeat="{{serv in services}}">
<core-submenu icon="{{serv.domain | getIcon}}" label="{{serv.domain}}">
<template repeat="{{service in serv.services}}">
<a on-click={{serviceClicked}} data-domain={{serv.domain}}>{{service}}</a>
<template repeat="{{domain in domains}}">
<core-submenu icon="{{domain | getIcon}}" label="{{domain}}">
<template repeat="{{service in domain | getServices}}">
<a on-click={{serviceClicked}} data-domain={{domain}}>{{service}}</a>
</template>
</core-submenu>
</template>
@ -50,7 +50,8 @@
var storeListenerMixIn = window.hass.storeListenerMixIn;
Polymer(Polymer.mixin({
services: [],
domains: [],
services: null,
cbServiceClicked: null,
attached: function() {
@ -62,11 +63,16 @@
},
getIcon: function(domain) {
return (new DomainIcon()).icon(domain);
return hass.uiUtil.domainIcon(domain);
},
getServices: function(domain) {
return this.services.get(domain).toArray();
},
serviceStoreChanged: function(serviceStore) {
this.services = serviceStore.all();
this.services = serviceStore.all;
this.domains = this.services.keySeq().sort().toArray();
},
serviceClicked: function(ev) {

View File

@ -42,17 +42,15 @@
},
streamStoreChanged: function(streamStore) {
this.isStreaming = streamStore.isStreaming();
this.hasError = streamStore.hasError();
this.$.toggle.checked = this.isStreaming;
this.hasError = streamStore.hasError;
this.$.toggle.checked = this.isStreaming = streamStore.isStreaming;
},
toggleChanged: function(ev) {
if (this.isStreaming) {
streamActions.stop();
} else {
streamActions.start(authStore.getAuthToken());
streamActions.start(authStore.authToken);
}
},
}, storeListenerMixIn));

@ -1 +1 @@
Subproject commit 8ea3a9e858a8c39d4c3aa46b719362b33f4a358f
Subproject commit e048bf6ece91983b9f03aafeb414ae5c535288a2

View File

@ -44,8 +44,8 @@
// if auth was given, tell the backend
if(this.auth) {
uiActions.validateAuth(this.auth, false);
} else if (preferenceStore.hasAuthToken()) {
uiActions.validateAuth(preferenceStore.getAuthToken(), false);
} else if (preferenceStore.hasAuthToken) {
uiActions.validateAuth(preferenceStore.authToken, false);
}
},
@ -58,7 +58,7 @@
},
syncStoreChanged: function(syncStore) {
this.loaded = syncStore.initialLoadDone();
this.loaded = syncStore.initialLoadDone;
},
}, storeListenerMixIn));
</script>

View File

@ -23,24 +23,20 @@
<core-style ref="ha-headers"></core-style>
<style>
core-header-panel {
.sidenav {
background: #fafafa;
box-shadow: 1px 0 1px rgba(0, 0, 0, 0.1);
color: #757575;
overflow: hidden;
}
core-menu core-icon {
margin-right: 24px;
}
core-toolbar {
font-weight: normal;
padding-left: 24px;
}
core-menu {
overflow: scroll;
.sidenav-menu {
overflow: auto;
position: absolute;
top: 0px;
left: 0px;
@ -48,7 +44,11 @@
bottom: 0px;
}
paper-item {
.sidenav-menu core-icon {
margin-right: 24px;
}
.sidenav-menu > paper-item {
min-height: 53px;
}
@ -70,21 +70,24 @@
<ha-modals></ha-modals>
<core-drawer-panel id="drawer" on-core-responsive-change="{{responsiveChanged}}">
<core-header-panel mode="scroll" drawer>
<core-header-panel mode="scroll" drawer class='sidenav'>
<core-toolbar>
Home Assistant
</core-toolbar>
<core-menu id="menu"
<core-menu id="menu" class="sidenav-menu"
selected="0" excludedLocalNames="div" on-core-select="{{menuSelect}}"
layout vertical>
<paper-item data-panel="states">
<core-icon icon="apps"></core-icon>
States
</paper-item>
<paper-item data-panel="group">
<core-icon icon="homeassistant-24:group"></core-icon>
Groups
</paper-item>
<template repeat="{{activeFilters as filter}}">
<paper-item data-panel="states_{{filter}}">
<core-icon icon="{{filter | filterIcon}}"></core-icon>
{{filter | filterName}}
</paper-item>
</template>
<template if="{{hasHistoryComponent}}">
<paper-item data-panel="history">
@ -124,10 +127,10 @@
This is the main partial, never remove it from the DOM but hide it
to speed up when people click on states.
-->
<partial-states hidden?="{{selected != 'states' && selected != 'group'}}"
<partial-states hidden?="{{hideStates}}"
main narrow="{{narrow}}"
togglePanel="{{togglePanel}}"
filter="{{selected == 'group' ? 'group' : null}}">
filter="{{stateFilter}}">
</partial-states>
<template if="{{selected == 'history'}}">
@ -146,17 +149,24 @@
</template>
<script>
(function() {
var storeListenerMixIn = window.hass.storeListenerMixIn;
var authActions = window.hass.authActions;
var uiUtil = window.hass.uiUtil;
var uiConstants = window.hass.uiConstants;
Polymer(Polymer.mixin({
selected: "states",
stateFilter: null,
narrow: false,
activeFilters: [],
hasHistoryComponent: false,
isStreaming: false,
hasStreamError: false,
hideStates: false,
attached: function() {
this.togglePanel = this.togglePanel.bind(this);
@ -167,15 +177,20 @@ Polymer(Polymer.mixin({
this.stopListeningToStores();
},
stateStoreChanged: function(stateStore) {
this.activeFilters = stateStore.domains.filter(function(domain) {
return domain in uiConstants.STATE_FILTERS;
}).toArray();
},
componentStoreChanged: function(componentStore) {
this.hasHistoryComponent = componentStore.isLoaded('history');
this.hasScriptComponent = componentStore.isLoaded('script');
},
streamStoreChanged: function(streamStore) {
var state = streamStore.getState();
this.isStreaming = state === streamStore.STATE_CONNECTED;
this.hasStreamError = state === streamStore.STATE_ERROR;
this.isStreaming = streamStore.isStreaming;
this.hasStreamError = streamStore.hasError;
},
menuSelect: function(ev, detail, sender) {
@ -196,6 +211,14 @@ Polymer(Polymer.mixin({
this.togglePanel();
this.selected = newChoice;
}
if (this.selected.substr(0, 7) === 'states_') {
this.hideStates = false;
this.stateFilter = this.selected.substr(7);
} else {
this.hideStates = this.selected !== 'states';
this.stateFilter = null;
}
},
responsiveChanged: function(ev, detail, sender) {
@ -209,6 +232,15 @@ Polymer(Polymer.mixin({
handleLogOutClick: function() {
authActions.logOut();
},
filterIcon: function(filter) {
return uiUtil.domainIcon(filter);
},
filterName: function(filter) {
return uiConstants.STATE_FILTERS[filter];
},
}, storeListenerMixIn));
})();
</script>
</polymer-element>

View File

@ -63,7 +63,8 @@
<div horizontal center layout>
<core-label horizontal layout>
<paper-checkbox for checked={{rememberLogin}}></paper-checkbox><b>Remember</b>
<paper-checkbox for checked={{rememberLogin}}></paper-checkbox>
Remember
</core-label>
<paper-button on-click={{validatePassword}}>Log In</paper-button>
@ -106,12 +107,12 @@
},
authStoreChanged: function(authStore) {
this.isValidating = authStore.isValidating();
this.isLoggedIn = authStore.isLoggedIn();
this.isValidating = authStore.isValidating;
this.isLoggedIn = authStore.isLoggedIn;
this.spinnerMessage = this.isValidating ? this.MSG_VALIDATING : this.MSG_LOADING_DATA;
if (authStore.wasLastAttemptInvalid()) {
this.$.passwordDecorator.error = authStore.getLastAttemptMessage();
if (authStore.lastAttemptInvalid) {
this.$.passwordDecorator.error = authStore.lastAttemptMessage;
this.$.passwordDecorator.isInvalid = true;
}

View File

@ -45,7 +45,7 @@
<div class='sidebar'>
<b>Available services:</b>
<services-list cbServiceClicked={{serviceSelected}}></event-list>
<services-list cbServiceClicked={{serviceSelected}}></services-list>
</div>
</div>
</div>

View File

@ -50,7 +50,7 @@
stateHistoryActions.fetchAll();
}
this.stateHistory = stateHistoryStore.all();
this.stateHistory = stateHistoryStore.all;
},
handleRefreshClick: function() {

View File

@ -1,4 +1,5 @@
<link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="../bower_components/core-icon/core-icon.html">
<link rel="import" href="../bower_components/core-style/core-style.html">
<link rel="import" href="../bower_components/paper-icon-button/paper-icon-button.html">
@ -10,14 +11,47 @@
<template>
<core-style ref="ha-animations"></core-style>
<style>
.listening {
position: absolute;
top: 0;
left: 0;
right: 0;
z-index: 1;
border-radius: 2px;
box-shadow: rgba(0, 0, 0, 0.098) 0px 2px 4px, rgba(0, 0, 0, 0.098) 0px 0px 3px;
padding: 16px;
background-color: rgba(255, 255, 255, 0.95);
line-height: 2em;
cursor: pointer;
}
.interimTranscript {
color: darkgrey;
}
.listening paper-spinner {
float: right;
}
</style>
<partial-base narrow="{{narrow}}" togglePanel="{{togglePanel}}">
<span header-title>{{headerTitle}}</span>
<span header-buttons>
<paper-icon-button icon="refresh" class="{{isFetching && 'ha-spin'}}"
on-click="{{handleRefreshClick}}" hidden?="{{isStreaming}}"></paper-icon-button>
<paper-icon-button icon="{{isListening ? 'av:mic-off' : 'av:mic' }}" hidden?={{!canListen}}
on-click="{{handleListenClick}}"></paper-icon-button>
</span>
<div class='listening' hidden?="{{!isListening && !isTransmitting}}" on-click={{handleListenClick}}>
<core-icon icon="av:hearing"></core-icon> {{finalTranscript}}
<span class='interimTranscript'>{{interimTranscript}}</span>
<paper-spinner active?="{{isTransmitting}}"></paper-spinner>
</div>
<state-cards states="{{states}}">
<h3>Hi there!</h3>
<p>
@ -30,9 +64,12 @@
</partial-base>
</template>
<script>
(function(){
var storeListenerMixIn = window.hass.storeListenerMixIn;
var syncActions = window.hass.syncActions;
var voiceActions = window.hass.voiceActions;
var stateStore = window.hass.stateStore;
var uiConstants = window.hass.uiConstants;
Polymer(Polymer.mixin({
headerTitle: "States",
@ -40,6 +77,18 @@
isFetching: false,
isStreaming: false,
canListen: false,
voiceSupported: false,
hasConversationComponent: false,
isListening: false,
isTransmittingVoice: false,
interimTranscript: '',
finalTranscript: '',
ready: function() {
this.voiceSupported = voiceActions.isSupported();
},
attached: function() {
this.listenToStores(true);
},
@ -48,47 +97,68 @@
this.stopListeningToStores();
},
componentStoreChanged: function(componentStore) {
this.canListen = this.voiceSupported &&
componentStore.isLoaded('conversation');
},
stateStoreChanged: function() {
this.refreshStates();
},
syncStoreChanged: function(syncStore) {
this.isFetching = syncStore.isFetching();
this.isFetching = syncStore.isFetching;
},
streamStoreChanged: function(streamStore) {
this.isStreaming = streamStore.isStreaming();
this.isStreaming = streamStore.isStreaming;
},
voiceStoreChanged: function(voiceStore) {
this.isListening = voiceStore.isListening;
this.isTransmitting = voiceStore.isTransmitting;
this.finalTranscript = voiceStore.finalTranscript;
this.interimTranscript = voiceStore.interimTranscript.slice(
this.finalTranscript.length);
},
filterChanged: function() {
this.refreshStates();
switch (this.filter) {
case "group":
this.headerTitle = "Groups";
break;
default:
this.headerTitle = "States";
break;
}
this.headerTitle = uiConstants.STATE_FILTERS[this.filter] || 'States';
},
refreshStates: function() {
if (this.filter == 'group') {
this.states = _.filter(stateStore.all(), function(state) {
return state.domain === 'group';
var states;
if (this.filter) {
var filter = this.filter;
states = stateStore.all.filter(function(state) {
return state.domain === filter;
});
} else {
this.states = _.filter(stateStore.all(), function(state) {
return state.domain !== 'group';
// all but the STATE_FILTER keys
states = stateStore.all.filter(function(state) {
return !(state.domain in uiConstants.STATE_FILTERS);
});
}
this.states = states.toArray();
},
handleRefreshClick: function() {
syncActions.fetchAll();
},
handleListenClick: function() {
if (this.isListening) {
voiceActions.stop();
} else {
voiceActions.listen();
}
},
}, storeListenerMixIn));
})();
</script>
</polymer>
</polymer>

View File

@ -84,7 +84,7 @@
},
streamStoreChanged: function(streamStore) {
this.isStreaming = streamStore.isStreaming();
this.isStreaming = streamStore.isStreaming;
},
submitClicked: function() {

View File

@ -6,6 +6,7 @@
<link rel="import" href="more-info-sun.html">
<link rel="import" href="more-info-configurator.html">
<link rel="import" href="more-info-thermostat.html">
<link rel="import" href="more-info-script.html">
<polymer-element name="more-info-content" attributes="stateObj">
<template>

View File

@ -41,11 +41,8 @@ Polymer(Polymer.mixin({
},
updateStates: function() {
if (this.stateObj && this.stateObj.attributes.entity_id) {
this.states = stateStore.gets(this.stateObj.attributes.entity_id);
} else {
this.states = [];
}
this.states = this.stateObj && this.stateObj.attributes.entity_id ?
stateStore.gets(this.stateObj.attributes.entity_id).toArray() : [];
},
}, storeListenerMixIn));
</script>

View File

@ -0,0 +1,22 @@
<link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="../bower_components/core-style/core-style.html">
<polymer-element name="more-info-script" attributes="stateObj" noscript>
<template>
<core-style ref='ha-key-value-table'></core-style>
<style>
.data-entry .value {
max-width: 200px;
}
</style>
<div layout vertical>
<div layout justified horizontal class='data-entry'>
<div class='key'>Last Action</div>
<div class='value'>
{{stateObj.attributes.last_action}}
</div>
</div>
</div>
</template>
</polymer-element>

View File

@ -1,6 +1,12 @@
<link rel="import" href="../bower_components/core-icon/core-icon.html">
<link rel="import" href="../bower_components/core-iconset-svg/core-iconset-svg.html">
<link rel="import" href="../bower_components/core-icon/core-icon.html">
<link rel="import" href="../bower_components/core-icons/social-icons.html">
<link rel="import" href="../bower_components/core-icons/image-icons.html">
<link rel="import" href="../bower_components/core-icons/hardware-icons.html">
<link rel="import" href="../bower_components/core-icons/av-icons.html">
<core-iconset-svg id="homeassistant-100" iconSize="100">
<svg><defs>
<g id="thermostat">
@ -26,3 +32,63 @@
</defs></svg>
</core-iconset-svg>
<script>
window.hass.uiUtil.domainIcon = function(domain, state) {
switch(domain) {
case "homeassistant":
return "home";
case "group":
return "homeassistant-24:group";
case "device_tracker":
return "social:person";
case "switch":
return "image:flash-on";
case "media_player":
var icon = "hardware:cast";
if (state !== "idle") {
icon += "-connected";
}
return icon;
case "sun":
return "image:wb-sunny";
case "light":
return "image:wb-incandescent";
case "simple_alarm":
return "social:notifications";
case "notify":
return "announcement";
case "thermostat":
return "homeassistant-100:thermostat";
case "sensor":
return "visibility";
case "configurator":
return "settings";
case "conversation":
return "av:hearing";
case "script":
return "description";
case 'scene':
return 'social:pages';
default:
return "bookmark-outline";
}
}
</script>

View File

@ -2,9 +2,9 @@
<script>
(function() {
var DOMAINS_WITH_CARD = ['thermostat', 'configurator'];
var DOMAINS_WITH_CARD = ['thermostat', 'configurator', 'scene'];
var DOMAINS_WITH_MORE_INFO = [
'light', 'group', 'sun', 'configurator', 'thermostat'
'light', 'group', 'sun', 'configurator', 'thermostat', 'script'
];
// Register some polymer filters
@ -51,9 +51,17 @@
preferenceStore = window.hass.preferenceStore,
authActions = window.hass.authActions;
window.hass.uiActions = {
window.hass.uiConstants = {
ACTION_SHOW_DIALOG_MORE_INFO: 'ACTION_SHOW_DIALOG_MORE_INFO',
STATE_FILTERS: {
'group': 'Groups',
'script': 'Scripts',
'scene': 'Scenes',
},
};
window.hass.uiActions = {
showMoreInfoDialog: function(entityId) {
dispatcher.dispatch({
actionType: this.ACTION_SHOW_DIALOG_MORE_INFO,
@ -63,10 +71,13 @@
validateAuth: function(authToken, rememberLogin) {
authActions.validate(authToken, {
useStreaming: preferenceStore.useStreaming(),
useStreaming: preferenceStore.useStreaming,
rememberLogin: rememberLogin,
});
},
};
// UI specific util methods
window.hass.uiUtil = {};
})();
</script>

View File

@ -111,7 +111,7 @@ def setup(hass, config=None):
if config is None or DOMAIN not in config:
config = {DOMAIN: {}}
api_password = str(config[DOMAIN].get(CONF_API_PASSWORD))
api_password = util.convert(config[DOMAIN].get(CONF_API_PASSWORD), str)
no_password_set = api_password is None

View File

@ -190,7 +190,6 @@ def setup(hass, config):
if service.service == SERVICE_TURN_OFF:
for light in target_lights:
# pylint: disable=star-args
light.turn_off(**params)
else:
@ -248,7 +247,6 @@ def setup(hass, config):
params[ATTR_FLASH] = FLASH_LONG
for light in target_lights:
# pylint: disable=star-args
light.turn_on(**params)
for light in target_lights:

View File

@ -30,6 +30,11 @@ class DemoLight(ToggleDevice):
self._xy = xy or random.choice(LIGHT_COLORS)
self._brightness = brightness
@property
def should_poll(self):
""" No polling needed for a demo light. """
return False
@property
def name(self):
""" Returns the name of the device if any. """

View File

@ -0,0 +1,94 @@
"""
Support for Vera lights.
Configuration:
This component is useful if you wish for switches connected to your Vera
controller to appear as lights in homeassistant. All switches will be added
as a light unless you exclude them in the config.
To use the Vera lights you will need to add something like the following to
your config/configuration.yaml
light:
platform: vera
vera_controller_url: http://YOUR_VERA_IP:3480/
device_data:
12:
name: My awesome switch
exclude: true
13:
name: Another switch
VARIABLES:
vera_controller_url
*Required
This is the base URL of your vera controller including the port number if not
running on 80
Example: http://192.168.1.21:3480/
device_data
*Optional
This contains an array additional device info for your Vera devices. It is not
required and if not specified all lights configured in your Vera controller
will be added with default values. You should use the id of your vera device
as the key for the device within device_data
These are the variables for the device_data array:
name
*Optional
This parameter allows you to override the name of your Vera device in the HA
interface, if not specified the value configured for the device in your Vera
will be used
exclude
*Optional
This parameter allows you to exclude the specified device from homeassistant,
it should be set to "true" if you want this device excluded
"""
import logging
from requests.exceptions import RequestException
from homeassistant.components.switch.vera import VeraSwitch
# pylint: disable=no-name-in-module, import-error
import homeassistant.external.vera.vera as veraApi
_LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Find and return Vera lights. """
base_url = config.get('vera_controller_url')
if not base_url:
_LOGGER.error(
"The required parameter 'vera_controller_url'"
" was not found in config"
)
return False
device_data = config.get('device_data', {})
controller = veraApi.VeraController(base_url)
devices = []
try:
devices = controller.get_devices('Switch')
except RequestException:
# There was a network related error connecting to the vera controller
_LOGGER.exception("Error communicating with Vera API")
return False
lights = []
for device in devices:
extra_data = device_data.get(device.deviceId, {})
exclude = extra_data.get('exclude', False)
if exclude is not True:
lights.append(VeraSwitch(device, extra_data))
add_devices_callback(lights)

View File

@ -34,6 +34,11 @@ class DemoMediaPlayer(MediaPlayerDevice):
self.media_title = media_title
self.volume = 1.0
@property
def should_poll(self):
""" No polling needed for a demo componentn. """
return False
@property
def name(self):
""" Returns the name of the device. """

View File

@ -0,0 +1,31 @@
"""
homeassistant.components.notify.demo
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Demo notification service.
"""
from homeassistant.components.notify import ATTR_TITLE, BaseNotificationService
EVENT_NOTIFY = "notify"
def get_service(hass, config):
""" Get the demo notification service. """
return DemoNotificationService(hass)
# pylint: disable=too-few-public-methods
class DemoNotificationService(BaseNotificationService):
""" Implements demo notification service. """
def __init__(self, hass):
self.hass = hass
def send_message(self, message="", **kwargs):
""" Send a message to a user. """
title = kwargs.get(ATTR_TITLE)
self.hass.bus.fire(EVENT_NOTIFY, {"title": title, "message": message})

View File

@ -7,7 +7,7 @@ on the host machine.
Author: Markus Stenberg <fingon@iki.fi>
"""
import logging
import os
from homeassistant.const import STATE_ON, STATE_OFF
@ -27,6 +27,11 @@ def setup(hass, config):
in process list.
"""
# Deprecated as of 3/7/2015
logging.getLogger(__name__).warning(
"This component has been deprecated and will be removed in the future."
" Please use sensor.systemmonitor with the process type")
entities = {ENTITY_ID_FORMAT.format(util.slugify(pname)): pstring
for pname, pstring in config[DOMAIN].items()}

View File

@ -0,0 +1,190 @@
"""
homeassistant.components.scene
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Allows users to set and activate scenes within Home Assistant.
A scene is a set of states that describe how you want certain entities to be.
For example, light A should be red with 100 brightness. Light B should be on.
A scene is active if all states of the scene match the real states.
If a scene is manually activated it will store the previous state of the
entities. These will be restored when the state is deactivated manually.
If one of the enties that are being tracked change state on its own, the
old state will not be restored when it is being deactivated.
"""
import logging
from collections import namedtuple
from homeassistant import State
from homeassistant.helpers.device import ToggleDevice
from homeassistant.helpers.device_component import DeviceComponent
from homeassistant.helpers.state import reproduce_state
from homeassistant.const import (
ATTR_ENTITY_ID, STATE_OFF, STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF)
DOMAIN = 'scene'
DEPENDENCIES = ['group']
ATTR_ACTIVE_REQUESTED = "active_requested"
CONF_ENTITIES = "entities"
SceneConfig = namedtuple('SceneConfig', ['name', 'states'])
def setup(hass, config):
""" Sets up scenes. """
logger = logging.getLogger(__name__)
scene_configs = config.get(DOMAIN)
if not isinstance(scene_configs, list):
logger.error('Scene config should be a list of scenes')
return False
component = DeviceComponent(logger, DOMAIN, hass)
component.add_devices(Scene(hass, _process_config(scene_config))
for scene_config in scene_configs)
def handle_scene_service(service):
""" Handles calls to the switch services. """
target_scenes = component.extract_from_service(service)
for scene in target_scenes:
if service.service == SERVICE_TURN_ON:
scene.turn_on()
else:
scene.turn_off()
hass.services.register(DOMAIN, SERVICE_TURN_OFF, handle_scene_service)
hass.services.register(DOMAIN, SERVICE_TURN_ON, handle_scene_service)
return True
def _process_config(scene_config):
""" Process passed in config into a format to work with. """
name = scene_config.get('name')
states = {}
c_entities = dict(scene_config.get(CONF_ENTITIES, {}))
for entity_id in c_entities:
if isinstance(c_entities[entity_id], dict):
state = c_entities[entity_id].pop('state', None)
attributes = c_entities[entity_id]
else:
state = c_entities[entity_id]
attributes = {}
# YAML translates 'on' to a boolean
# http://yaml.org/type/bool.html
if isinstance(state, bool):
state = STATE_ON if state else STATE_OFF
else:
state = str(state)
states[entity_id.lower()] = State(entity_id, state, attributes)
return SceneConfig(name, states)
class Scene(ToggleDevice):
""" A scene is a group of entities and the states we want them to be. """
def __init__(self, hass, scene_config):
self.hass = hass
self.scene_config = scene_config
self.is_active = False
self.prev_states = None
self.ignore_updates = False
self.hass.states.track_change(
self.entity_ids, self.entity_state_changed)
self.update()
@property
def should_poll(self):
return False
@property
def name(self):
return self.scene_config.name
@property
def is_on(self):
return self.is_active
@property
def entity_ids(self):
""" Entity IDs part of this scene. """
return self.scene_config.states.keys()
@property
def state_attributes(self):
""" Scene state attributes. """
return {
ATTR_ENTITY_ID: list(self.entity_ids),
ATTR_ACTIVE_REQUESTED: self.prev_states is not None,
}
def turn_on(self):
""" Activates scene. Tries to get entities into requested state. """
self.prev_states = tuple(self.hass.states.get(entity_id)
for entity_id in self.entity_ids)
self._reproduce_state(self.scene_config.states.values())
def turn_off(self):
""" Deactivates scene and restores old states. """
if self.prev_states:
self._reproduce_state(self.prev_states)
self.prev_states = None
def entity_state_changed(self, entity_id, old_state, new_state):
""" Called when an entity part of this scene changes state. """
if self.ignore_updates:
return
# If new state is not what we expect, it can never be active
if self._state_as_requested(new_state):
self.update()
else:
self.is_active = False
self.prev_states = None
self.update_ha_state()
def update(self):
"""
Update if the scene is active.
Will look at each requested state and see if the current entity
has the same state and has at least the same attributes with the
same values. The real state can have more attributes.
"""
self.is_active = all(
self._state_as_requested(self.hass.states.get(entity_id))
for entity_id in self.entity_ids)
def _state_as_requested(self, cur_state):
""" Returns if given state is as requested. """
state = self.scene_config.states.get(cur_state and cur_state.entity_id)
return (cur_state is not None and state.state == cur_state.state and
all(value == cur_state.attributes.get(key)
for key, value in state.attributes.items()))
def _reproduce_state(self, states):
""" Wraps reproduce state with Scence specific logic. """
self.ignore_updates = True
reproduce_state(self.hass, states, True)
self.ignore_updates = False
self.update_ha_state(True)

View File

@ -0,0 +1,140 @@
"""
homeassistant.components.script
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Scripts are a sequence of actions that can be triggered manually
by the user or automatically based upon automation events, etc.
"""
import logging
from datetime import datetime, timedelta
import threading
from homeassistant.util import split_entity_id
from homeassistant.const import (
STATE_ON, STATE_OFF, SERVICE_TURN_ON, SERVICE_TURN_OFF, EVENT_TIME_CHANGED)
DOMAIN = "script"
DEPENDENCIES = ["group"]
CONF_ALIAS = "alias"
CONF_SERVICE = "execute_service"
CONF_SERVICE_DATA = "service_data"
CONF_SEQUENCE = "sequence"
CONF_DELAY = "delay"
_LOGGER = logging.getLogger(__name__)
def setup(hass, config):
""" Load the scripts from the configuration. """
scripts = []
for name, cfg in config[DOMAIN].items():
if CONF_SEQUENCE not in cfg:
_LOGGER.warn("Missing key 'sequence' for script %s", name)
continue
alias = cfg.get(CONF_ALIAS, name)
entity_id = "{}.{}".format(DOMAIN, name)
script = Script(hass, entity_id, alias, cfg[CONF_SEQUENCE])
hass.services.register(DOMAIN, name, script)
scripts.append(script)
def turn_on(service):
""" Calls a script. """
for entity_id in service.data['entity_id']:
domain, service = split_entity_id(entity_id)
hass.services.call(domain, service, {})
def turn_off(service):
""" Cancels a script. """
for entity_id in service.data['entity_id']:
for script in scripts:
if script.entity_id == entity_id:
script.cancel()
hass.services.register(DOMAIN, SERVICE_TURN_ON, turn_on)
hass.services.register(DOMAIN, SERVICE_TURN_OFF, turn_off)
return True
class Script(object):
# pylint: disable=attribute-defined-outside-init
# pylint: disable=too-many-instance-attributes
# pylint: disable=too-few-public-methods
"""
A script contains a sequence of service calls or configured delays
that are executed in order.
Each script also has a state (on/off) indicating whether the script is
running or not.
"""
def __init__(self, hass, entity_id, alias, sequence):
self.hass = hass
self.alias = alias
self.sequence = sequence
self.entity_id = entity_id
self._lock = threading.Lock()
self._reset()
def cancel(self):
""" Cancels a running script and resets the state back to off. """
_LOGGER.info("Cancelled script %s", self.alias)
with self._lock:
if self.listener:
self.hass.bus.remove_listener(EVENT_TIME_CHANGED,
self.listener)
self.listener = None
self._reset()
def _reset(self):
""" Resets a script back to default state so that it is ready to
run from the start again. """
self.actions = None
self.listener = None
self.last_action = "Not Running"
self.hass.states.set(self.entity_id, STATE_OFF, {
"friendly_name": self.alias,
"last_action": self.last_action
})
def _execute_until_done(self):
""" Executes a sequence of actions until finished or until a delay
is encountered. If a delay action is encountered, the script
registers itself to be called again in the future, when
_execute_until_done will resume.
Returns True if finished, False otherwise. """
for action in self.actions:
if CONF_SERVICE in action:
self._call_service(action)
elif CONF_DELAY in action:
delay = timedelta(**action[CONF_DELAY])
point_in_time = datetime.now() + delay
self.listener = self.hass.track_point_in_time(
self, point_in_time)
return False
return True
def __call__(self, *args, **kwargs):
""" Executes the script. """
_LOGGER.info("Executing script %s", self.alias)
with self._lock:
if self.actions is None:
self.actions = (action for action in self.sequence)
if not self._execute_until_done():
state = self.hass.states.get(self.entity_id)
state.attributes['last_action'] = self.last_action
self.hass.states.set(self.entity_id, STATE_ON,
state.attributes)
else:
self._reset()
def _call_service(self, action):
""" Calls the service specified in the action. """
self.last_action = action.get(CONF_ALIAS, action[CONF_SERVICE])
_LOGGER.info("Executing script %s step %s", self.alias,
self.last_action)
domain, service = split_entity_id(action[CONF_SERVICE])
data = action.get(CONF_SERVICE_DATA, {})
self.hass.services.call(domain, service, data)

View File

@ -21,6 +21,11 @@ class DemoSensor(Device):
self._state = state
self._unit_of_measurement = unit_of_measurement
@property
def should_poll(self):
""" No polling needed for a demo sensor. """
return False
@property
def name(self):
""" Returns the name of the device. """

View File

@ -32,6 +32,7 @@ import tellcore.constants as tellcore_constants
from homeassistant.const import (
ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT, TEMP_CELCIUS)
from homeassistant.helpers.device import Device
import homeassistant.util as util
DatatypeDescription = namedtuple("DatatypeDescription", ['name', 'unit'])
@ -71,6 +72,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
return
sensors = []
datatype_mask = util.convert(config.get('datatype_mask'), int, 127)
for ts_sensor in core.sensors():
try:
@ -81,8 +83,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
sensor_name = str(ts_sensor.id)
for datatype in sensor_value_descriptions.keys():
if datatype & int(config['datatype_mask']) and \
ts_sensor.has_value(datatype):
if datatype & datatype_mask and ts_sensor.has_value(datatype):
sensor_info = sensor_value_descriptions[datatype]

View File

@ -0,0 +1,162 @@
"""
Support for Vera sensors.
Configuration:
To use the Vera sensors you will need to add something like the following to
your config/configuration.yaml
sensor:
platform: vera
vera_controller_url: http://YOUR_VERA_IP:3480/
device_data:
12:
name: My awesome sensor
exclude: true
13:
name: Another sensor
VARIABLES:
vera_controller_url
*Required
This is the base URL of your vera controller including the port number if not
running on 80
Example: http://192.168.1.21:3480/
device_data
*Optional
This contains an array additional device info for your Vera devices. It is not
required and if not specified all sensors configured in your Vera controller
will be added with default values. You should use the id of your vera device
as the key for the device within device_data
These are the variables for the device_data array:
name
*Optional
This parameter allows you to override the name of your Vera device in the HA
interface, if not specified the value configured for the device in your Vera
will be used
exclude
*Optional
This parameter allows you to exclude the specified device from homeassistant,
it should be set to "true" if you want this device excluded
"""
import logging
import time
from requests.exceptions import RequestException
from homeassistant.helpers import Device
from homeassistant.const import (
ATTR_BATTERY_LEVEL, ATTR_TRIPPED, ATTR_ARMED, ATTR_LAST_TRIP_TIME)
# pylint: disable=no-name-in-module, import-error
import homeassistant.external.vera.vera as veraApi
_LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument
def get_devices(hass, config):
""" Find and return Vera Sensors. """
base_url = config.get('vera_controller_url')
if not base_url:
_LOGGER.error(
"The required parameter 'vera_controller_url'"
" was not found in config"
)
return False
device_data = config.get('device_data', {})
vera_controller = veraApi.VeraController(base_url)
categories = ['Temperature Sensor', 'Light Sensor', 'Sensor']
devices = []
try:
devices = vera_controller.get_devices(categories)
except RequestException:
# There was a network related error connecting to the vera controller
_LOGGER.exception("Error communicating with Vera API")
return False
vera_sensors = []
for device in devices:
extra_data = device_data.get(device.deviceId, {})
exclude = extra_data.get('exclude', False)
if exclude is not True:
vera_sensors.append(VeraSensor(device, extra_data))
return vera_sensors
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Performs setup for Vera controller devices """
add_devices(get_devices(hass, config))
class VeraSensor(Device):
""" Represents a Vera Sensor """
def __init__(self, vera_device, extra_data=None):
self.vera_device = vera_device
self.extra_data = extra_data
if self.extra_data and self.extra_data.get('name'):
self._name = self.extra_data.get('name')
else:
self._name = self.vera_device.name
self.current_value = ''
def __str__(self):
return "%s %s %s" % (self.name, self.vera_device.deviceId, self.state)
@property
def state(self):
return self.current_value
@property
def name(self):
""" Get the mame of the sensor. """
return self._name
@property
def state_attributes(self):
attr = super().state_attributes
if self.vera_device.has_battery:
attr[ATTR_BATTERY_LEVEL] = self.vera_device.battery_level + '%'
if self.vera_device.is_armable:
armed = self.vera_device.refresh_value('Armed')
attr[ATTR_ARMED] = 'True' if armed == '1' else 'False'
if self.vera_device.is_trippable:
last_tripped = self.vera_device.refresh_value('LastTrip')
trip_time_str = time.strftime(
"%Y-%m-%d %H:%M",
time.localtime(int(last_tripped))
)
attr[ATTR_LAST_TRIP_TIME] = trip_time_str
tripped = self.vera_device.refresh_value('Tripped')
attr[ATTR_TRIPPED] = 'True' if tripped == '1' else 'False'
attr['Vera Device Id'] = self.vera_device.vera_device_id
return attr
def update(self):
if self.vera_device.category == "Temperature Sensor":
self.vera_device.refresh_value('CurrentTemperature')
current_temp = self.vera_device.get_value('CurrentTemperature')
vera_temp_units = self.vera_device.veraController.temperature_units
self.current_value = current_temp + '°' + vera_temp_units
elif self.vera_device.category == "Light Sensor":
self.vera_device.refresh_value('CurrentLevel')
self.current_value = self.vera_device.get_value('CurrentLevel')
elif self.vera_device.category == "Sensor":
tripped = self.vera_device.refresh_value('Tripped')
self.current_value = 'Tripped' if tripped == '1' else 'Not Tripped'
else:
self.current_value = 'Unknown'

View File

@ -22,14 +22,15 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
value.set_change_verified(False)
if zwave.NETWORK.controller.node_id not in node.groups[1].associations:
node.groups[1].add_association(zwave.NETWORK.controller.node_id)
# if 1 in groups and (zwave.NETWORK.controller.node_id not in
# groups[1].associations):
# node.groups[1].add_association(zwave.NETWORK.controller.node_id)
if value.command_class == zwave.COMMAND_CLASS_SENSOR_BINARY:
return [ZWaveBinarySensor(value)]
add_devices([ZWaveBinarySensor(value)])
elif value.command_class == zwave.COMMAND_CLASS_SENSOR_MULTILEVEL:
return [ZWaveMultilevelSensor(value)]
add_devices([ZWaveMultilevelSensor(value)])
class ZWaveSensor(Device):
@ -119,6 +120,8 @@ class ZWaveMultilevelSensor(ZWaveSensor):
if self._value.units in ('C', 'F'):
return round(value, 1)
elif isinstance(value, float):
return round(value, 2)
return value

View File

@ -18,6 +18,11 @@ class DemoSwitch(ToggleDevice):
self._name = name or DEVICE_DEFAULT_NAME
self._state = state
@property
def should_poll(self):
""" No polling needed for a demo switch. """
return False
@property
def name(self):
""" Returns the name of the device if any. """

View File

@ -0,0 +1,166 @@
"""
Support for Vera switches.
Configuration:
To use the Vera lights you will need to add something like the following to
your config/configuration.yaml
switch:
platform: vera
vera_controller_url: http://YOUR_VERA_IP:3480/
device_data:
12:
name: My awesome switch
exclude: true
13:
name: Another Switch
VARIABLES:
vera_controller_url
*Required
This is the base URL of your vera controller including the port number if not
running on 80
Example: http://192.168.1.21:3480/
device_data
*Optional
This contains an array additional device info for your Vera devices. It is not
required and if not specified all lights configured in your Vera controller
will be added with default values. You should use the id of your vera device
as the key for the device within device_data
These are the variables for the device_data array:
name
*Optional
This parameter allows you to override the name of your Vera device in the HA
interface, if not specified the value configured for the device in your Vera
will be used
exclude
*Optional
This parameter allows you to exclude the specified device from homeassistant,
it should be set to "true" if you want this device excluded
"""
import logging
import time
from requests.exceptions import RequestException
from homeassistant.helpers import ToggleDevice
from homeassistant.const import (
ATTR_BATTERY_LEVEL, ATTR_TRIPPED, ATTR_ARMED, ATTR_LAST_TRIP_TIME)
# pylint: disable=no-name-in-module, import-error
import homeassistant.external.vera.vera as veraApi
_LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument
def get_devices(hass, config):
""" Find and return Vera switches. """
base_url = config.get('vera_controller_url')
if not base_url:
_LOGGER.error(
"The required parameter 'vera_controller_url'"
" was not found in config"
)
return False
device_data = config.get('device_data', {})
vera_controller = veraApi.VeraController(base_url)
devices = []
try:
devices = vera_controller.get_devices(['Switch', 'Armable Sensor'])
except RequestException:
# There was a network related error connecting to the vera controller
_LOGGER.exception("Error communicating with Vera API")
return False
vera_switches = []
for device in devices:
extra_data = device_data.get(device.deviceId, {})
exclude = extra_data.get('exclude', False)
if exclude is not True:
vera_switches.append(VeraSwitch(device, extra_data))
return vera_switches
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Find and return Vera lights. """
add_devices(get_devices(hass, config))
class VeraSwitch(ToggleDevice):
""" Represents a Vera Switch """
def __init__(self, vera_device, extra_data=None):
self.vera_device = vera_device
self.extra_data = extra_data
if self.extra_data and self.extra_data.get('name'):
self._name = self.extra_data.get('name')
else:
self._name = self.vera_device.name
self.is_on_status = False
# for debouncing status check after command is sent
self.last_command_send = 0
@property
def name(self):
""" Get the mame of the switch. """
return self._name
@property
def state_attributes(self):
attr = super().state_attributes
if self.vera_device.has_battery:
attr[ATTR_BATTERY_LEVEL] = self.vera_device.battery_level + '%'
if self.vera_device.is_armable:
armed = self.vera_device.refresh_value('Armed')
attr[ATTR_ARMED] = 'True' if armed == '1' else 'False'
if self.vera_device.is_trippable:
last_tripped = self.vera_device.refresh_value('LastTrip')
trip_time_str = time.strftime(
"%Y-%m-%d %H:%M",
time.localtime(int(last_tripped))
)
attr[ATTR_LAST_TRIP_TIME] = trip_time_str
tripped = self.vera_device.refresh_value('Tripped')
attr[ATTR_TRIPPED] = 'True' if tripped == '1' else 'False'
attr['Vera Device Id'] = self.vera_device.vera_device_id
return attr
def turn_on(self, **kwargs):
self.last_command_send = time.time()
self.vera_device.switch_on()
self.is_on_status = True
def turn_off(self, **kwargs):
self.last_command_send = time.time()
self.vera_device.switch_off()
self.is_on_status = False
@property
def is_on(self):
""" True if device is on. """
return self.is_on_status
def update(self):
# We need to debounce the status call after turning switch on or off
# because the vera has some lag in updating the device status
if (self.last_command_send + 5) < time.time():
self.is_on_status = self.vera_device.is_switched_on()

View File

@ -26,6 +26,11 @@ class DemoThermostat(ThermostatDevice):
self._away = away
self._current_temperature = current_temperature
@property
def should_poll(self):
""" No polling needed for a demo thermostat. """
return False
@property
def name(self):
""" Returns the name. """

View File

@ -81,9 +81,9 @@ def setup(hass, config):
if use_debug:
def log_all(signal, value=None):
""" Log all the louie signals. """
""" Log all the signals. """
print("")
print("LOUIE SIGNAL *****", signal)
print("SIGNAL *****", signal)
if value and signal in (ZWaveNetwork.SIGNAL_VALUE_CHANGED,
ZWaveNetwork.SIGNAL_VALUE_ADDED):
pprint(_obj_to_dict(value))

View File

@ -73,6 +73,16 @@ ATTR_LOCATION = "location"
ATTR_BATTERY_LEVEL = "battery_level"
# For devices which support an armed state
ATTR_ARMED = "device_armed"
# For sensors that support 'tripping', eg. motion and door sensors
ATTR_TRIPPED = "device_tripped"
# For sensors that support 'tripping' this holds the most recent
# time the device was tripped
ATTR_LAST_TRIP_TIME = "last_tripped_time"
# #### SERVICES ####
SERVICE_HOMEASSISTANT_STOP = "stop"

1
homeassistant/external/vera vendored Submodule

@ -0,0 +1 @@
Subproject commit fedbb5c3af1e5f36b7008d894e9fc1ecf3cc2ea8

View File

@ -1,8 +1,6 @@
"""
Helper methods for components within Home Assistant.
"""
from datetime import datetime
from homeassistant.loader import get_component
from homeassistant.const import ATTR_ENTITY_ID, CONF_PLATFORM
from homeassistant.util import ensure_unique_string, slugify
@ -43,25 +41,6 @@ def extract_entity_ids(hass, service):
return [ent_id for ent_id in group.expand_entity_ids(hass, service_ent_id)]
# pylint: disable=too-few-public-methods, attribute-defined-outside-init
class TrackStates(object):
"""
Records the time when the with-block is entered. Will add all states
that have changed since the start time to the return list when with-block
is exited.
"""
def __init__(self, hass):
self.hass = hass
self.states = []
def __enter__(self):
self.now = datetime.now()
return self.states
def __exit__(self, exc_type, exc_value, traceback):
self.states.extend(self.hass.states.get_since(self.now))
def validate_config(config, items, logger):
"""
Validates if all items are available in the configuration.
@ -98,14 +77,17 @@ def config_per_platform(config, domain, logger):
while config_key in config:
platform_config = config[config_key]
if not isinstance(platform_config, list):
platform_config = [platform_config]
platform_type = platform_config.get(CONF_PLATFORM)
for item in platform_config:
platform_type = item.get(CONF_PLATFORM)
if platform_type is None:
logger.warning('No platform specified for %s', config_key)
break
if platform_type is None:
logger.warning('No platform specified for %s', config_key)
continue
yield platform_type, platform_config
yield platform_type, item
found += 1
config_key = "{} {}".format(domain, found)

View File

@ -7,6 +7,8 @@ from homeassistant.helpers import (
from homeassistant.components import group, discovery
from homeassistant.const import ATTR_ENTITY_ID
DEFAULT_SCAN_INTERVAL = 15
class DeviceComponent(object):
# pylint: disable=too-many-instance-attributes
@ -14,7 +16,8 @@ class DeviceComponent(object):
"""
Helper class that will help a device component manage its devices.
"""
def __init__(self, logger, domain, hass, scan_interval,
def __init__(self, logger, domain, hass,
scan_interval=DEFAULT_SCAN_INTERVAL,
discovery_platforms=None, group_name=None):
self.logger = logger
self.hass = hass
@ -33,17 +36,8 @@ class DeviceComponent(object):
"""
Sets up a full device component:
- Loads the platforms from the config
- Will update devices on an interval
- Will listen for supported discovered platforms
"""
# only setup group if name is given
if self.group_name is None:
self.group = None
else:
self.group = group.Group(self.hass, self.group_name,
user_defined=False)
# Look in config for Domain, Domain 2, Domain 3 etc and load them
for p_type, p_config in \
config_per_platform(config, self.domain, self.logger):
@ -54,6 +48,31 @@ class DeviceComponent(object):
discovery.listen(self.hass, self.discovery_platforms.keys(),
self._device_discovered)
def add_devices(self, new_devices):
"""
Takes in a list of new devices. For each device will see if it already
exists. If not, will add it, set it up and push the first state.
"""
for device in new_devices:
if device is not None and device not in self.devices.values():
device.hass = self.hass
device.entity_id = generate_entity_id(
self.entity_id_format, device.name, self.devices.keys())
self.devices[device.entity_id] = device
device.update_ha_state()
if self.group is None and self.group_name is not None:
self.group = group.Group(self.hass, self.group_name,
user_defined=False)
if self.group is not None:
self.group.update_tracked_entity_ids(self.devices.keys())
self._start_polling()
def extract_from_service(self, service):
"""
Takes a service and extracts all known devices.
@ -81,27 +100,6 @@ class DeviceComponent(object):
self._setup_platform(self.discovery_platforms[service], {}, info)
def _add_devices(self, new_devices):
"""
Takes in a list of new devices. For each device will see if it already
exists. If not, will add it, set it up and push the first state.
"""
for device in new_devices:
if device is not None and device not in self.devices.values():
device.hass = self.hass
device.entity_id = generate_entity_id(
self.entity_id_format, device.name, self.devices.keys())
self.devices[device.entity_id] = device
device.update_ha_state()
if self.group is not None:
self.group.update_tracked_entity_ids(self.devices.keys())
self._start_polling()
def _start_polling(self):
""" Start polling device states if necessary. """
if self.is_polling or \
@ -125,7 +123,7 @@ class DeviceComponent(object):
try:
platform.setup_platform(
self.hass, config, self._add_devices, discovery_info)
self.hass, config, self.add_devices, discovery_info)
except AttributeError:
# Support old deprecated method for now - 3/1/2015
if hasattr(platform, 'get_devices'):
@ -133,7 +131,7 @@ class DeviceComponent(object):
"Please upgrade %s to return new devices using "
"setup_platform. See %s/demo.py for an example.",
platform_name, self.domain)
self._add_devices(platform.get_devices(self.hass, config))
self.add_devices(platform.get_devices(self.hass, config))
else:
# AttributeError if setup_platform does not exist

View File

@ -0,0 +1,58 @@
"""
homeassistant.helpers.state
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Helpers that help with state related things.
"""
import logging
from datetime import datetime
from homeassistant import State
from homeassistant.const import (
STATE_ON, STATE_OFF, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID)
_LOGGER = logging.getLogger(__name__)
# pylint: disable=too-few-public-methods, attribute-defined-outside-init
class TrackStates(object):
"""
Records the time when the with-block is entered. Will add all states
that have changed since the start time to the return list when with-block
is exited.
"""
def __init__(self, hass):
self.hass = hass
self.states = []
def __enter__(self):
self.now = datetime.now()
return self.states
def __exit__(self, exc_type, exc_value, traceback):
self.states.extend(self.hass.states.get_since(self.now))
def reproduce_state(hass, states, blocking=False):
""" Takes in a state and will try to have the entity reproduce it. """
if isinstance(states, State):
states = [states]
for state in states:
current_state = hass.states.get(state.entity_id)
if current_state is None:
continue
if state.state == STATE_ON:
service = SERVICE_TURN_ON
elif state.state == STATE_OFF:
service = SERVICE_TURN_OFF
else:
_LOGGER.warning("Unable to reproduce state for %s", state)
continue
service_data = dict(state.attributes)
service_data[ATTR_ENTITY_ID] = state.entity_id
hass.services.call(state.domain, service, service_data, blocking)

View File

@ -157,28 +157,19 @@ def load_order_components(components):
"""
_check_prepared()
group = get_component('group')
recorder = get_component('recorder')
load_order = OrderedSet()
# Sort the list of modules on if they depend on group component or not.
# We do this because the components that do not depend on the group
# component usually set up states that the group component requires to be
# created before it can group them.
# This does not matter in the future if we can setup groups without the
# states existing yet.
# Components that do not depend on the group usually set up states.
# Components that depend on group usually use states in their setup.
for comp_load_order in sorted((load_order_component(component)
for component in components),
# Test if group component exists in case
# above get_component call had an error.
key=lambda order:
group and group.DOMAIN in order):
key=lambda order: 'group' in order):
load_order.update(comp_load_order)
# Push recorder to first place in load order
if recorder.DOMAIN in load_order:
load_order.promote(recorder.DOMAIN)
if 'recorder' in load_order:
load_order.promote('recorder')
return load_order

View File

@ -14,6 +14,7 @@ import logging
import json
import enum
import urllib.parse
import os
import requests
@ -49,13 +50,16 @@ class API(object):
""" Object to pass around Home Assistant API location and credentials. """
# pylint: disable=too-few-public-methods
def __init__(self, host, api_password, port=None):
def __init__(self, host, api_password=None, port=None):
self.host = host
self.port = port or SERVER_PORT
self.api_password = api_password
self.base_url = "http://{}:{}".format(host, self.port)
self.status = None
self._headers = {HTTP_HEADER_HA_AUTH: api_password}
self._headers = {}
if api_password is not None:
self._headers[HTTP_HEADER_HA_AUTH] = api_password
def validate_api(self, force_validate=False):
""" Tests if we can communicate with the API. """
@ -95,7 +99,7 @@ class API(object):
class HomeAssistant(ha.HomeAssistant):
""" Home Assistant that forwards work. """
# pylint: disable=super-init-not-called
# pylint: disable=super-init-not-called,too-many-instance-attributes
def __init__(self, remote_api, local_api=None):
if not remote_api.validate_api():
@ -106,13 +110,15 @@ class HomeAssistant(ha.HomeAssistant):
self.remote_api = remote_api
self.local_api = local_api
self._pool = pool = ha.create_worker_pool()
self.pool = pool = ha.create_worker_pool()
self.bus = EventBus(remote_api, pool)
self.services = ha.ServiceRegistry(self.bus, pool)
self.states = StateMachine(self.bus, self.remote_api)
self.components = []
self.config_dir = os.path.join(os.getcwd(), 'config')
def start(self):
# Ensure a local API exists to connect with remote
if self.local_api is None:
@ -142,9 +148,9 @@ class HomeAssistant(ha.HomeAssistant):
disconnect_remote_events(self.remote_api, self.local_api)
# Wait till all responses to homeassistant_stop are done
self._pool.block_till_done()
self.pool.block_till_done()
self._pool.stop()
self.pool.stop()
class EventBus(ha.EventBus):

View File

@ -1,6 +1,6 @@
# Sets up and builds python open zwave to be used with Home Assistant
# Dependencies that need to be installed:
# apt-get install cython3 libudev-dev python-sphinx python3-setuptools mercurial
# apt-get install cython3 libudev-dev python-sphinx python3-setuptools
# pip3 install cython
# If current pwd is scripts, go 1 up.
@ -8,15 +8,23 @@ if [ ${PWD##*/} == "scripts" ]; then
cd ..
fi
mkdir build
if [ ! -d build ]; then
mkdir build
fi
cd build
hg clone https://code.google.com/r/balloob-python-openzwave/
cd balloob-python-openzwave
./update.sh
if [ -d python-openzwave ]; then
cd python-openzwave
git pull --recurse-submodules=yes
git submodule update --init --recursive
else
git clone --recursive https://github.com/balloob/python-openzwave.git
cd python-openzwave
fi
# Fix an issue with openzwave
sed -i '253s/.*//' openzwave/cpp/src/value_classes/ValueID.h
./compile.sh
./install.sh
./compile.sh --python3
./install.sh --python3

View File

@ -63,12 +63,14 @@ class TestDemo(unittest.TestCase):
self.assertEqual(
STATE_OFF, self.hass.states.get(entity_id).state)
def test_hiding_demo_state(self):
""" Test if you can hide the demo card. """
demo.setup(self.hass, {demo.DOMAIN: {'hide_demo_state': '1'}})
self.assertIsNone(self.hass.states.get('a.Demo_Mode'))
demo.setup(self.hass, {demo.DOMAIN: {'hide_demo_state': '0'}})
def test_if_demo_state_shows_by_default(self):
""" Test if demo state shows if we give no configuration. """
demo.setup(self.hass, {demo.DOMAIN: {}})
self.assertIsNotNone(self.hass.states.get('a.Demo_Mode'))
def test_hiding_demo_state(self):
""" Test if you can hide the demo card. """
demo.setup(self.hass, {demo.DOMAIN: {'hide_demo_state': 1}})
self.assertIsNone(self.hass.states.get('a.Demo_Mode'))

View File

@ -208,7 +208,7 @@ class TestRemoteClasses(unittest.TestCase):
slave.states.set("remote.test", "remote.statemachine test")
# Wait till slave tells master
slave._pool.block_till_done()
slave.pool.block_till_done()
# Wait till master gives updated state
hass.pool.block_till_done()
@ -228,7 +228,7 @@ class TestRemoteClasses(unittest.TestCase):
slave.bus.fire("test.event_no_data")
# Wait till slave tells master
slave._pool.block_till_done()
slave.pool.block_till_done()
# Wait till master gives updated event
hass.pool.block_till_done()