Merging with the upstream master to get the compnent architecture changes
This commit is contained in:
jamespcole 2015-03-07 12:25:49 +11:00
commit ff0099b6a7
51 changed files with 1144 additions and 591 deletions

View File

@ -3,4 +3,10 @@ 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 clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \
pip3 install cython && \
scripts/build_python_openzwave
CMD [ "python", "-m", "homeassistant", "--config", "/config" ]

View File

@ -0,0 +1,104 @@
homeassistant:
# Location required to calculate the time the sun rises and sets
latitude: 32.87336
longitude: 117.22743
http:
api_password: mypass
# Set to 1 to enable development mode
# development: 1
light:
# platform: hue
wink:
# Get your token at https://winkbearertoken.appspot.com
access_token: 'YOUR_TOKEN'
device_tracker:
# The following types are available: netgear, tomato, luci, nmap_tracker
platform: netgear
host: 192.168.1.1
username: admin
password: PASSWORD
# http_id is needed for Tomato routers only
# http_id: ABCDEFGHH
# For nmap_tracker, only the IP addresses to scan are needed:
# hosts: 192.168.1.1/24 # netmask prefix notation or
# hosts: 192.168.1.1-255 # address range
chromecast:
switch:
platform: wemo
thermostat:
platform: nest
# Required: username and password that are used to login to the Nest thermostat.
username: myemail@mydomain.com
password: mypassword
downloader:
download_dir: downloads
notify:
platform: pushbullet
api_key: ABCDEFGHJKLMNOPQRSTUVXYZ
device_sun_light_trigger:
# Optional: specify a specific light/group of lights that has to be turned on
light_group: group.living_room
# Optional: specify which light profile to use when turning lights on
light_profile: relax
# Optional: disable lights being turned off when everybody leaves the house
# disable_turn_off: 1
# A comma seperated list of states that have to be tracked as a single group
# Grouped states should share the same type of states (ON/OFF or HOME/NOT_HOME)
group:
living_room:
- light.Bowl
- light.Ceiling
- light.TV_back_light
children:
- device_tracker.child_1
- device_tracker.child_2
process:
# items are which processes to look for: <entity_id>: <search string within ps>
xbmc: XBMC.App
example:
simple_alarm:
# Which light/light group has to flash when a known device comes home
known_light: light.Bowl
# Which light/light group has to flash red when light turns on while no one home
unknown_light: group.living_room
browser:
keyboard:
automation:
platform: state
alias: Sun starts shining
state_entity_id: sun.sun
# Next two are optional, omit to match all
state_from: below_horizon
state_to: above_horizon
execute_service: light.turn_off
service_entity_id: group.living_room
automation 2:
platform: time
alias: Beer o Clock
time_hours: 16
time_minutes: 0
time_seconds: 0
execute_service: notify.notify
service_data: {"message":"It's 4, time for beer!"}

View File

@ -1,99 +0,0 @@
[homeassistant]
# Location required to calculate the time the sun rises and sets
latitude=32.87336
longitude=-117.22743
[http]
api_password=mypass
# Set to 1 to load each Polymer component separately
# development=1
[light]
platform=hue
[wink]
# Get your token at https://winkbearertoken.appspot.com
access_token=YOUR_TOKEN
[device_tracker]
# The following types are available: netgear, tomato, luci, nmap_tracker
platform=netgear
host=192.168.1.1
username=admin
password=PASSWORD
# http_id is needed for Tomato routers only
# http_id=ABCDEFGHH
# For nmap_tracker, only the IP addresses to scan are needed:
# hosts=192.168.1.1/24 # netmask prefix notation or
# hosts=192.168.1.1-255 # address range
[chromecast]
[switch]
platform=wemo
[thermostat]
platform=nest
# Required: username and password that are used to login to the Nest thermostat.
username=myemail@mydomain.com
password=mypassword
[downloader]
download_dir=downloads
[notify]
platform=pushbullet
api_key=ABCDEFGHJKLMNOPQRSTUVXYZ
[device_sun_light_trigger]
# Optional: specify a specific light/group of lights that has to be turned on
light_group=group.living_room
# Optional: specify which light profile to use when turning lights on
light_profile=relax
# Optional: disable lights being turned off when everybody leaves the house
# disable_turn_off=1
# A comma seperated list of states that have to be tracked as a single group
# Grouped states should share the same type of states (ON/OFF or HOME/NOT_HOME)
[group]
living_room=light.Bowl,light.Ceiling,light.TV_back_light
children=device_tracker.child_1,device_tracker.child_2
[process]
# items are which processes to look for: <entity_id>=<search string within ps>
xbmc=XBMC.App
[example]
[simple_alarm]
# Which light/light group has to flash when a known device comes home
known_light=light.Bowl
# Which light/light group has to flash red when light turns on while no one home
unknown_light=group.living_room
[browser]
[keyboard]
[automation]
platform=state
alias=Sun starts shining
state_entity_id=sun.sun
# Next two are optional, omit to match all
state_from=below_horizon
state_to=above_horizon
execute_service=light.turn_off
service_entity_id=group.living_room
[automation 2]
platform=time
alias=Beer o Clock
time_hours=16
time_minutes=0
time_seconds=0
execute_service=notify.notify
service_data={"message":"It's 4, time for beer!"}

View File

@ -72,15 +72,19 @@ def ensure_config_path(config_dir):
'directory {} ').format(config_dir))
sys.exit()
config_path = os.path.join(config_dir, 'home-assistant.conf')
# Try to use yaml configuration first
config_path = os.path.join(config_dir, 'configuration.yaml')
if not os.path.isfile(config_path):
config_path = os.path.join(config_dir, 'home-assistant.conf')
# Ensure a config file exists to make first time usage easier
if not os.path.isfile(config_path):
config_path = os.path.join(config_dir, 'configuration.yaml')
try:
with open(config_path, 'w') as conf:
conf.write("[frontend]\n\n")
conf.write("[discovery]\n\n")
conf.write("[history]\n\n")
conf.write("frontend:\n\n")
conf.write("discovery:\n\n")
conf.write("history:\n\n")
except IOError:
print(('Fatal Error: No configuration file found and unable '
'to write a default one to {}').format(config_path))

View File

@ -11,6 +11,8 @@ start by calling homeassistant.start_home_assistant(bus)
import os
import configparser
import yaml
import io
import logging
from collections import defaultdict
@ -77,7 +79,9 @@ def from_config_dict(config, hass=None):
# Make a copy because we are mutating it.
# Convert it to defaultdict so components can always have config dict
config = defaultdict(dict, config)
# Convert values to dictionaries if they are None
config = defaultdict(
dict, {key: value or {} for key, value in config.items()})
# Filter out the repeating and common config section [homeassistant]
components = (key for key in config.keys()
@ -110,17 +114,21 @@ def from_config_file(config_path, hass=None):
# Set config dir to directory holding config file
hass.config_dir = os.path.abspath(os.path.dirname(config_path))
# Read config
config = configparser.ConfigParser()
config.read(config_path)
config_dict = {}
# check config file type
if os.path.splitext(config_path)[1] == '.yaml':
# Read yaml
config_dict = yaml.load(io.open(config_path, 'r'))
else:
# Read config
config = configparser.ConfigParser()
config.read(config_path)
for section in config.sections():
config_dict[section] = {}
for section in config.sections():
config_dict[section] = {}
for key, val in config.items(section):
config_dict[section][key] = val
for key, val in config.items(section):
config_dict[section][key] = val
return from_config_dict(config_dict, hass)

View File

@ -4,27 +4,21 @@ homeassistant.components.demo
Sets up a demo environment that mimics interaction with devices
"""
import random
import time
import homeassistant as ha
import homeassistant.bootstrap as bootstrap
import homeassistant.loader as loader
from homeassistant.helpers import extract_entity_ids
from homeassistant.const import (
SERVICE_TURN_ON, SERVICE_TURN_OFF,
STATE_ON, STATE_OFF, TEMP_CELCIUS,
ATTR_ENTITY_PICTURE, ATTR_ENTITY_ID, ATTR_UNIT_OF_MEASUREMENT,
CONF_PLATFORM, ATTR_ENTITY_PICTURE, STATE_ON,
CONF_LATITUDE, CONF_LONGITUDE)
from homeassistant.components.light import (
ATTR_XY_COLOR, ATTR_RGB_COLOR, ATTR_BRIGHTNESS, GROUP_NAME_ALL_LIGHTS)
from homeassistant.components.thermostat import (
ATTR_CURRENT_TEMPERATURE, ATTR_AWAY_MODE)
from homeassistant.util import split_entity_id, color_RGB_to_xy
DOMAIN = "demo"
DEPENDENCIES = []
COMPONENTS_WITH_DEMO_PLATFORM = ['switch', 'light', 'thermostat', 'sensor']
def setup(hass, config):
""" Setup a demo environment. """
@ -37,57 +31,6 @@ def setup(hass, config):
if config[DOMAIN].get('hide_demo_state') != '1':
hass.states.set('a.Demo_Mode', 'Enabled')
light_colors = [
[0.861, 0.3259],
[0.6389, 0.3028],
[0.1684, 0.0416]
]
def mock_turn_on(service):
""" Will fake the component has been turned on. """
if service.data and ATTR_ENTITY_ID in service.data:
entity_ids = extract_entity_ids(hass, service)
else:
entity_ids = hass.states.entity_ids(service.domain)
for entity_id in entity_ids:
domain, _ = split_entity_id(entity_id)
if domain == "light":
rgb_color = service.data.get(ATTR_RGB_COLOR)
if rgb_color:
color = color_RGB_to_xy(
rgb_color[0], rgb_color[1], rgb_color[2])
else:
cur_state = hass.states.get(entity_id)
# Use current color if available
if cur_state and cur_state.attributes.get(ATTR_XY_COLOR):
color = cur_state.attributes.get(ATTR_XY_COLOR)
else:
color = random.choice(light_colors)
data = {
ATTR_BRIGHTNESS: service.data.get(ATTR_BRIGHTNESS, 200),
ATTR_XY_COLOR: color
}
else:
data = None
hass.states.set(entity_id, STATE_ON, data)
def mock_turn_off(service):
""" Will fake the component has been turned off. """
if service.data and ATTR_ENTITY_ID in service.data:
entity_ids = extract_entity_ids(hass, service)
else:
entity_ids = hass.states.entity_ids(service.domain)
for entity_id in entity_ids:
hass.states.set(entity_id, STATE_OFF)
# Setup sun
if CONF_LATITUDE not in config[ha.DOMAIN]:
config[ha.DOMAIN][CONF_LATITUDE] = '32.87336'
@ -97,34 +40,16 @@ def setup(hass, config):
loader.get_component('sun').setup(hass, config)
# Setup fake lights
lights = ['light.Bowl', 'light.Ceiling', 'light.TV_Back_light',
'light.Bed_light']
hass.services.register('light', SERVICE_TURN_ON, mock_turn_on)
hass.services.register('light', SERVICE_TURN_OFF, mock_turn_off)
mock_turn_on(ha.ServiceCall('light', SERVICE_TURN_ON,
{'entity_id': lights[0:2]}))
mock_turn_off(ha.ServiceCall('light', SERVICE_TURN_OFF,
{'entity_id': lights[2:]}))
group.setup_group(hass, GROUP_NAME_ALL_LIGHTS, lights, False)
# Setup switch
switches = ['switch.AC', 'switch.Christmas_Lights']
hass.services.register('switch', SERVICE_TURN_ON, mock_turn_on)
hass.services.register('switch', SERVICE_TURN_OFF, mock_turn_off)
mock_turn_on(ha.ServiceCall('switch', SERVICE_TURN_ON,
{'entity_id': switches[0:1]}))
mock_turn_off(ha.ServiceCall('switch', SERVICE_TURN_OFF,
{'entity_id': switches[1:]}))
# Setup demo platforms
for component in COMPONENTS_WITH_DEMO_PLATFORM:
bootstrap.setup_component(
hass, component, {component: {CONF_PLATFORM: 'demo'}})
# Setup room groups
group.setup_group(hass, 'living room', lights[0:3] + switches[0:1])
group.setup_group(hass, 'bedroom', [lights[3]] + switches[1:])
lights = hass.states.entity_ids('light')
switches = hass.states.entity_ids('switch')
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)
@ -152,26 +77,7 @@ def setup(hass, config):
ATTR_ENTITY_PICTURE:
'http://graph.facebook.com/KillBillMovie/picture'})
# Setup tellstick sensors
hass.states.set("tellstick_sensor.Outside_temperature", "15.6",
{
'friendly_name': 'Outside temperature',
'unit_of_measurement': '°C'
})
hass.states.set("tellstick_sensor.Outside_humidity", "54",
{
'friendly_name': 'Outside humidity',
'unit_of_measurement': '%'
})
# Nest demo
hass.states.set("thermostat.Nest", "23",
{
ATTR_UNIT_OF_MEASUREMENT: TEMP_CELCIUS,
ATTR_CURRENT_TEMPERATURE: '18',
ATTR_AWAY_MODE: STATE_OFF
})
# Setup configurator
configurator_ids = []
def hue_configuration_callback(data):

View File

@ -11,7 +11,6 @@ import logging
import threading
# pylint: disable=no-name-in-module, import-error
from homeassistant.external.netdisco.netdisco import DiscoveryService
import homeassistant.external.netdisco.netdisco.const as services
from homeassistant import bootstrap
@ -52,12 +51,20 @@ def listen(hass, service, callback):
def setup(hass, config):
""" Starts a discovery service. """
logger = logging.getLogger(__name__)
try:
from homeassistant.external.netdisco.netdisco.service import \
DiscoveryService
except ImportError:
logger.exception(
"Unable to import netdisco. "
"Did you install all the zeroconf dependency?")
return False
# Disable zeroconf logging, it spams
logging.getLogger('zeroconf').setLevel(logging.CRITICAL)
logger = logging.getLogger(__name__)
lock = threading.Lock()
def new_service_listener(service, info):

View File

@ -1,2 +1,2 @@
""" DO NOT MODIFY. Auto-generated by build_frontend script """
VERSION = "10b554c3f2db1c5441d5e74c0f6d8469"
VERSION = "30729f23c19a66d9364d78b2379867cc"

File diff suppressed because one or more lines are too long

View File

@ -11,32 +11,32 @@
"bower_components"
],
"dependencies": {
"webcomponentsjs": "Polymer/webcomponentsjs#~0.5.4",
"font-roboto": "Polymer/font-roboto#~0.5.4",
"core-header-panel": "polymer/core-header-panel#~0.5.4",
"core-toolbar": "polymer/core-toolbar#~0.5.4",
"core-tooltip": "Polymer/core-tooltip#~0.5.4",
"core-menu": "polymer/core-menu#~0.5.4",
"core-item": "Polymer/core-item#~0.5.4",
"core-input": "Polymer/core-input#~0.5.4",
"core-icons": "polymer/core-icons#~0.5.4",
"core-image": "polymer/core-image#~0.5.4",
"core-style": "polymer/core-style#~0.5.4",
"paper-toast": "Polymer/paper-toast#~0.5.4",
"paper-dialog": "Polymer/paper-dialog#~0.5.4",
"paper-spinner": "Polymer/paper-spinner#~0.5.4",
"paper-button": "Polymer/paper-button#~0.5.4",
"paper-input": "Polymer/paper-input#~0.5.4",
"paper-toggle-button": "polymer/paper-toggle-button#~0.5.4",
"paper-icon-button": "polymer/paper-icon-button#~0.5.4",
"paper-menu-button": "polymer/paper-menu-button#~0.5.4",
"paper-dropdown": "polymer/paper-dropdown#~0.5.4",
"paper-item": "polymer/paper-item#~0.5.4",
"paper-slider": "polymer/paper-slider#~0.5.4",
"webcomponentsjs": "Polymer/webcomponentsjs#~0.5.5",
"font-roboto": "Polymer/font-roboto#~0.5.5",
"core-header-panel": "polymer/core-header-panel#~0.5.5",
"core-toolbar": "polymer/core-toolbar#~0.5.5",
"core-tooltip": "Polymer/core-tooltip#~0.5.5",
"core-menu": "polymer/core-menu#~0.5.5",
"core-item": "Polymer/core-item#~0.5.5",
"core-input": "Polymer/core-input#~0.5.5",
"core-icons": "polymer/core-icons#~0.5.5",
"core-image": "polymer/core-image#~0.5.5",
"core-style": "polymer/core-style#~0.5.5",
"paper-toast": "Polymer/paper-toast#~0.5.5",
"paper-dialog": "Polymer/paper-dialog#~0.5.5",
"paper-spinner": "Polymer/paper-spinner#~0.5.5",
"paper-button": "Polymer/paper-button#~0.5.5",
"paper-input": "Polymer/paper-input#~0.5.5",
"paper-toggle-button": "polymer/paper-toggle-button#~0.5.5",
"paper-icon-button": "polymer/paper-icon-button#~0.5.5",
"paper-menu-button": "polymer/paper-menu-button#~0.5.5",
"paper-dropdown": "polymer/paper-dropdown#~0.5.5",
"paper-item": "polymer/paper-item#~0.5.5",
"paper-slider": "polymer/paper-slider#~0.5.5",
"color-picker-element": "~0.0.2",
"google-apis": "GoogleWebComponents/google-apis#~0.4.2",
"core-drawer-panel": "polymer/core-drawer-panel#~0.5.4",
"core-scroll-header-panel": "polymer/core-scroll-header-panel#~0.5.4",
"core-drawer-panel": "polymer/core-drawer-panel#~0.5.5",
"core-scroll-header-panel": "polymer/core-scroll-header-panel#~0.5.5",
"moment": "~2.9.0"
}
}

@ -1 +1 @@
Subproject commit 9b5c89e05277c276c941166c582d2cb5159e9425
Subproject commit 2f96b21d42c6b1c01a235b39fbbd2e0cf1b8d651

View File

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

View File

@ -0,0 +1,114 @@
<link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="../bower_components/paper-slider/paper-slider.html">
<link rel="import" href="../bower_components/paper-toggle-button/paper-toggle-button.html">
<polymer-element name="more-info-thermostat" attributes="stateObj">
<template>
<style>
paper-slider {
width: 100%;
}
paper-slider::shadow #sliderKnobInner,
paper-slider::shadow #sliderBar::shadow #activeProgress {
background-color: #039be5;
}
.away-mode-toggle {
display: none;
margin-top: 16px;
}
:host-context(.has-away_mode) .away-mode-toggle {
display: block;
}
</style>
<div>
<div>
<div>Target Temperature</div>
<paper-slider
min="{{tempMin}}" max="{{tempMax}}"
value='{{targetTemperatureSliderValue}}' pin
on-change="{{targetTemperatureSliderChanged}}">
</paper-slider>
</div>
<div class='away-mode-toggle'>
<div center horizontal layout>
<div flex>Away Mode</div>
<paper-toggle-button
checked="{{awayToggleChecked}}"
on-change="{{toggleChanged}}">
</paper-toggle-button>
</div>
</div>
</div>
</template>
<script>
var constants = window.hass.constants;
Polymer({
tempMin: 10,
tempMax: 40,
targetTemperatureSliderValue: 0,
awayToggleChecked: false,
observe: {
'stateObj.attributes.away_mode': 'awayChanged'
},
stateObjChanged: function(oldVal, newVal) {
this.targetTemperatureSliderValue = this.stateObj.state;
if (this.stateObj.attributes.unit_of_measurement === constants.UNIT_TEMP_F) {
this.tempMin = 45;
this.tempMax = 95;
} else {
this.tempMin = 7;
this.tempMax = 35;
}
},
targetTemperatureSliderChanged: function(ev, details, target) {
var temp = parseInt(target.value);
if(isNaN(temp)) return;
serviceActions.callService("thermostat", "set_temperature", {
entity_id: this.stateObj.entityId,
temperature: temp
});
},
toggleChanged: function(ev) {
var newVal = ev.target.checked;
if(newVal && this.stateObj.attributes.away_mode === 'off') {
this.service_set_away(true);
} else if(!newVal && this.stateObj.attributes.away_mode === 'on') {
this.service_set_away(false);
}
},
awayChanged: function(oldVal, newVal) {
this.awayToggleChecked = newVal == 'on';
},
service_set_away: function(away_mode) {
// We call stateChanged after a successful call to re-sync the toggle
// with the state. It will be out of sync if our service call did not
// result in the entity to be turned on. Since the state is not changing,
// the resync is not called automatic.
serviceActions.callService(
'thermostat', 'set_away_mode',
{entity_id: this.stateObj.entityId, away_mode: away_mode})
.then(function() {
this.awayChanged(null, this.stateObj.attributes.away_mode);
}.bind(this));
},
});
</script>
</polymer-element>

View File

@ -3,7 +3,9 @@
<script>
(function() {
var DOMAINS_WITH_CARD = ['thermostat', 'configurator'];
var DOMAINS_WITH_MORE_INFO = ['light', 'group', 'sun', 'configurator'];
var DOMAINS_WITH_MORE_INFO = [
'light', 'group', 'sun', 'configurator', 'thermostat'
];
// Register some polymer filters

View File

@ -101,7 +101,11 @@ def get_entity_ids(hass, entity_id, domain_filter=None):
def setup(hass, config):
""" Sets up all groups found definded in the configuration. """
for name, entity_ids in config.get(DOMAIN, {}).items():
setup_group(hass, name, entity_ids.split(","))
# Support old deprecated method - 2/28/2015
if isinstance(entity_ids, str):
entity_ids = entity_ids.split(",")
setup_group(hass, name, entity_ids)
return True

View File

@ -52,17 +52,18 @@ import logging
import os
import csv
from homeassistant.loader import get_component
from homeassistant.helpers.device_component import DeviceComponent
import homeassistant.util as util
from homeassistant.const import (
STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID)
from homeassistant.helpers import (
generate_entity_id, extract_entity_ids, config_per_platform)
from homeassistant.helpers import extract_entity_ids
from homeassistant.components import group, discovery, wink
DOMAIN = "light"
DEPENDENCIES = []
SCAN_INTERVAL = 30
GROUP_NAME_ALL_LIGHTS = 'all lights'
ENTITY_ID_ALL_LIGHTS = group.ENTITY_ID_FORMAT.format('all_lights')
@ -140,6 +141,13 @@ def turn_off(hass, entity_id=None, transition=None):
def setup(hass, config):
""" Exposes light control via statemachine and services. """
component = DeviceComponent(
_LOGGER, DOMAIN, hass, SCAN_INTERVAL, DISCOVERY_PLATFORMS,
GROUP_NAME_ALL_LIGHTS)
component.setup(config)
lights = component.devices
# Load built-in profiles and custom profiles
profile_paths = [os.path.join(os.path.dirname(__file__),
LIGHT_PROFILES_FILE),
@ -168,52 +176,6 @@ def setup(hass, config):
return False
# Dict to track entity_id -> lights
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)
def update_lights_state(now):
""" Update the states of all the lights. """
if lights:
_LOGGER.info("Updating light states")
for light in lights.values():
light.update_ha_state(hass, True)
update_lights_state(None)
def light_discovered(service, info):
""" Called when a light is discovered. """
platform = get_component(
ENTITY_ID_FORMAT.format(DISCOVERY_PLATFORMS[service]))
platform.setup_platform(hass, {}, add_lights, info)
discovery.listen(hass, DISCOVERY_PLATFORMS.keys(), light_discovered)
def handle_light_service(service):
""" Hande a turn light on or off service call. """
# Get and validate data
@ -298,10 +260,7 @@ def setup(hass, config):
light.turn_on(**params)
for light in target_lights:
light.update_ha_state(hass, True)
# Update light state every 30 seconds
hass.track_time_change(update_lights_state, second=[0, 30])
light.update_ha_state(True)
# Listen for light on and light off service calls
hass.services.register(DOMAIN, SERVICE_TURN_ON,

View File

@ -0,0 +1,69 @@
""" Provides demo lights. """
import random
from homeassistant.helpers import ToggleDevice
from homeassistant.const import STATE_ON, STATE_OFF, DEVICE_DEFAULT_NAME
from homeassistant.components.light import ATTR_BRIGHTNESS, ATTR_XY_COLOR
LIGHT_COLORS = [
[0.861, 0.3259],
[0.6389, 0.3028],
[0.1684, 0.0416]
]
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Find and return demo lights. """
add_devices_callback([
DemoLight("Bed Light", STATE_OFF),
DemoLight("Ceiling", STATE_ON),
DemoLight("Kitchen", STATE_ON)
])
class DemoLight(ToggleDevice):
""" Provides a demo switch. """
def __init__(self, name, state, xy=None, brightness=180):
self._name = name or DEVICE_DEFAULT_NAME
self._state = state
self._xy = xy or random.choice(LIGHT_COLORS)
self._brightness = brightness
@property
def name(self):
""" Returns the name of the device if any. """
return self._name
@property
def state(self):
""" Returns the name of the device if any. """
return self._state
@property
def state_attributes(self):
""" Returns optional state attributes. """
if self.is_on:
return {
ATTR_BRIGHTNESS: self._brightness,
ATTR_XY_COLOR: self._xy,
}
@property
def is_on(self):
""" True if device is on. """
return self._state == STATE_ON
def turn_on(self, **kwargs):
""" Turn the device on. """
self._state = STATE_ON
if ATTR_XY_COLOR in kwargs:
self._xy = kwargs[ATTR_XY_COLOR]
if ATTR_BRIGHTNESS in kwargs:
self._brightness = kwargs[ATTR_BRIGHTNESS]
def turn_off(self, **kwargs):
""" Turn the device off. """
self._state = STATE_OFF

View File

@ -7,7 +7,7 @@ from urllib.parse import urlparse
from homeassistant.loader import get_component
import homeassistant.util as util
from homeassistant.helpers import ToggleDevice
from homeassistant.const import ATTR_FRIENDLY_NAME, CONF_HOST
from homeassistant.const import CONF_HOST
from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_XY_COLOR, ATTR_TRANSITION,
ATTR_FLASH, FLASH_LONG, FLASH_SHORT)
@ -153,9 +153,7 @@ class HueLight(ToggleDevice):
@property
def state_attributes(self):
""" Returns optional state attributes. """
attr = {
ATTR_FRIENDLY_NAME: self.name
}
attr = {}
if self.is_on:
attr[ATTR_BRIGHTNESS] = self.info['state']['bri']

View File

@ -4,86 +4,29 @@ homeassistant.components.sensor
Component to interface with various sensors that can be monitored.
"""
import logging
from datetime import timedelta
from homeassistant.loader import get_component
import homeassistant.util as util
from homeassistant.const import (
STATE_OPEN)
from homeassistant.helpers import (
platform_devices_from_config)
from homeassistant.components import group, discovery, wink
from homeassistant.helpers.device_component import DeviceComponent
from homeassistant.components import wink, zwave
DOMAIN = 'sensor'
DEPENDENCIES = []
GROUP_NAME_ALL_SENSORS = 'all_sensors'
ENTITY_ID_ALL_SENSORS = group.ENTITY_ID_FORMAT.format(
GROUP_NAME_ALL_SENSORS)
SCAN_INTERVAL = 30
ENTITY_ID_FORMAT = DOMAIN + '.{}'
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=1)
# Maps discovered services to their platforms
DISCOVERY_PLATFORMS = {
wink.DISCOVER_SENSORS: 'wink',
zwave.DISCOVER_SENSORS: 'zwave',
}
_LOGGER = logging.getLogger(__name__)
def is_on(hass, entity_id=None):
""" Returns if the sensor is open based on the statemachine. """
entity_id = entity_id or ENTITY_ID_ALL_SENSORS
return hass.states.is_state(entity_id, STATE_OPEN)
def setup(hass, config):
""" Track states and offer events for sensors. """
logger = logging.getLogger(__name__)
component = DeviceComponent(
logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL,
DISCOVERY_PLATFORMS)
sensors = platform_devices_from_config(
config, DOMAIN, hass, ENTITY_ID_FORMAT, logger)
@util.Throttle(MIN_TIME_BETWEEN_SCANS)
def update_sensor_states(now):
""" Update states of all sensors. """
if sensors:
logger.info("Updating sensor states")
for sensor in sensors.values():
sensor.update_ha_state(hass, True)
update_sensor_states(None)
# Track all sensors in a group
sensor_group = group.Group(
hass, GROUP_NAME_ALL_SENSORS, sensors.keys(), False)
def sensor_discovered(service, info):
""" Called when a sensor is discovered. """
platform = get_component("{}.{}".format(
DOMAIN, DISCOVERY_PLATFORMS[service]))
discovered = platform.devices_discovered(hass, config, info)
for sensor in discovered:
if sensor is not None and sensor not in sensors.values():
sensor.entity_id = util.ensure_unique_string(
ENTITY_ID_FORMAT.format(util.slugify(sensor.name)),
sensors.keys())
sensors[sensor.entity_id] = sensor
sensor.update_ha_state(hass)
sensor_group.update_tracked_entity_ids(sensors.keys())
discovery.listen(hass, DISCOVERY_PLATFORMS.keys(), sensor_discovered)
# Fire every 3 seconds
hass.track_time_change(update_sensor_states, second=range(0, 60, 3))
component.setup(config)
return True

View File

@ -0,0 +1,40 @@
""" Support for Wink sensors. """
from homeassistant.helpers import Device
from homeassistant.const import (
TEMP_CELCIUS, ATTR_UNIT_OF_MEASUREMENT, ATTR_FRIENDLY_NAME)
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the Demo sensors. """
add_devices([
DemoSensor('Outside Temperature', 15.6, TEMP_CELCIUS),
DemoSensor('Outside Humidity', 54, '%'),
])
class DemoSensor(Device):
""" A Demo sensor. """
def __init__(self, name, state, unit_of_measurement):
self._name = name
self._state = state
self._unit_of_measurement = unit_of_measurement
@property
def name(self):
""" Returns the name of the device. """
return self._name
@property
def state(self):
""" Returns the state of the device. """
return self._state
@property
def state_attributes(self):
""" Returns the state attributes. """
return {
ATTR_FRIENDLY_NAME: self._name,
ATTR_UNIT_OF_MEASUREMENT: self._unit_of_measurement,
}

View File

@ -8,26 +8,17 @@ from homeassistant.components.wink import WinkSensorDevice
from homeassistant.const import CONF_ACCESS_TOKEN
def get_devices(hass, config):
""" Find and return Wink sensors. """
token = config.get(CONF_ACCESS_TOKEN)
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the Wink platform. """
if discovery_info is None:
token = config.get(CONF_ACCESS_TOKEN)
if token is None:
logging.getLogger(__name__).error(
"Missing wink access_token - "
"get one at https://winkbearertoken.appspot.com/")
return []
if token is None:
logging.getLogger(__name__).error(
"Missing wink access_token - "
"get one at https://winkbearertoken.appspot.com/")
return
pywink.set_bearer_token(token)
pywink.set_bearer_token(token)
return get_sensors()
def devices_discovered(hass, config, info):
""" Called when a device is discovered. """
return get_sensors()
def get_sensors():
""" Returns the Wink sensors. """
return [WinkSensorDevice(sensor) for sensor in pywink.get_sensors()]
add_devices(WinkSensorDevice(sensor) for sensor in pywink.get_sensors())

View File

@ -0,0 +1,135 @@
"""
homeassistant.components.sensor.zwave
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Interfaces with Z-Wave sensors.
"""
# pylint: disable=import-error
from openzwave.network import ZWaveNetwork
from pydispatch import dispatcher
import homeassistant.components.zwave as zwave
from homeassistant.helpers import Device
from homeassistant.const import (
ATTR_BATTERY_LEVEL, ATTR_UNIT_OF_MEASUREMENT, STATE_ON, STATE_OFF,
TEMP_CELCIUS, TEMP_FAHRENHEIT, ATTR_LOCATION)
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up Z-Wave sensors. """
node = zwave.NETWORK.nodes[discovery_info[zwave.ATTR_NODE_ID]]
value = node.values[discovery_info[zwave.ATTR_VALUE_ID]]
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 value.command_class == zwave.COMMAND_CLASS_SENSOR_BINARY:
return [ZWaveBinarySensor(value)]
elif value.command_class == zwave.COMMAND_CLASS_SENSOR_MULTILEVEL:
return [ZWaveMultilevelSensor(value)]
class ZWaveSensor(Device):
""" Represents a Z-Wave sensor. """
def __init__(self, sensor_value):
self._value = sensor_value
self._node = sensor_value.node
dispatcher.connect(
self._value_changed, ZWaveNetwork.SIGNAL_VALUE_CHANGED)
@property
def should_poll(self):
""" False because we will push our own state to HA when changed. """
return False
@property
def unique_id(self):
""" Returns a unique id. """
return "ZWAVE-{}-{}".format(self._node.node_id, self._value.object_id)
@property
def name(self):
""" Returns the name of the device. """
name = self._node.name or "{} {}".format(
self._node.manufacturer_name, self._node.product_name)
return "{} {}".format(name, self._value.label)
@property
def state(self):
""" Returns the state of the sensor. """
return self._value.data
@property
def state_attributes(self):
""" Returns the state attributes. """
attrs = {
zwave.ATTR_NODE_ID: self._node.node_id,
}
battery_level = self._node.get_battery_level()
if battery_level is not None:
attrs[ATTR_BATTERY_LEVEL] = battery_level
unit = self.unit
if unit:
attrs[ATTR_UNIT_OF_MEASUREMENT] = unit
location = self._node.location
if location:
attrs[ATTR_LOCATION] = location
return attrs
@property
def unit(self):
""" Unit if sensor has one. """
return self._value.units
def _value_changed(self, value):
""" Called when a value has changed on the network. """
if self._value.value_id == value.value_id:
self.update_ha_state()
# pylint: disable=too-few-public-methods
class ZWaveBinarySensor(ZWaveSensor):
""" Represents a binary sensor within Z-Wave. """
@property
def state(self):
""" Returns the state of the sensor. """
return STATE_ON if self._value.data else STATE_OFF
class ZWaveMultilevelSensor(ZWaveSensor):
""" Represents a multi level sensor Z-Wave sensor. """
@property
def state(self):
""" Returns the state of the sensor. """
value = self._value.data
if self._value.units in ('C', 'F'):
return round(value, 1)
return value
@property
def unit(self):
""" Unit of this sensor. """
unit = self._value.units
if unit == 'C':
return TEMP_CELCIUS
elif unit == 'F':
return TEMP_FAHRENHEIT
else:
return unit

View File

@ -96,8 +96,8 @@ def setup(hass, config):
sun = ephem.Sun() # pylint: disable=no-member
latitude = config[ha.DOMAIN][CONF_LATITUDE]
longitude = config[ha.DOMAIN][CONF_LONGITUDE]
latitude = str(config[ha.DOMAIN][CONF_LATITUDE])
longitude = str(config[ha.DOMAIN][CONF_LONGITUDE])
# Validate latitude and longitude
observer = ephem.Observer()

View File

@ -6,16 +6,16 @@ Component to interface with various switches that can be controlled remotely.
import logging
from datetime import timedelta
from homeassistant.loader import get_component
import homeassistant.util as util
from homeassistant.helpers.device_component import DeviceComponent
from homeassistant.const import (
STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID)
from homeassistant.helpers import (
generate_entity_id, extract_entity_ids, platform_devices_from_config)
from homeassistant.helpers import extract_entity_ids
from homeassistant.components import group, discovery, wink
DOMAIN = 'switch'
DEPENDENCIES = []
SCAN_INTERVAL = 30
GROUP_NAME_ALL_SWITCHES = 'all switches'
ENTITY_ID_ALL_SWITCHES = group.ENTITY_ID_FORMAT.format('all_switches')
@ -59,45 +59,12 @@ def turn_off(hass, entity_id=None):
def setup(hass, config):
""" Track states and offer events for switches. """
logger = logging.getLogger(__name__)
component = DeviceComponent(
_LOGGER, DOMAIN, hass, SCAN_INTERVAL, DISCOVERY_PLATFORMS,
GROUP_NAME_ALL_SWITCHES)
component.setup(config)
switches = platform_devices_from_config(
config, DOMAIN, hass, ENTITY_ID_FORMAT, logger)
@util.Throttle(MIN_TIME_BETWEEN_SCANS)
def update_states(now):
""" Update states of all switches. """
if switches:
logger.info("Updating switch states")
for switch in switches.values():
switch.update_ha_state(hass, True)
update_states(None)
# Track all switches in a group
switch_group = group.Group(
hass, GROUP_NAME_ALL_SWITCHES, switches.keys(), False)
def switch_discovered(service, info):
""" Called when a switch is discovered. """
platform = get_component("{}.{}".format(
DOMAIN, DISCOVERY_PLATFORMS[service]))
discovered = platform.devices_discovered(hass, config, info)
for switch in discovered:
if switch is not None and switch not in switches.values():
switch.entity_id = generate_entity_id(
ENTITY_ID_FORMAT, switch.name, switches.keys())
switches[switch.entity_id] = switch
switch.update_ha_state(hass)
switch_group.update_tracked_entity_ids(switches.keys())
discovery.listen(hass, DISCOVERY_PLATFORMS.keys(), switch_discovered)
switches = component.devices
def handle_switch_service(service):
""" Handles calls to the switch services. """
@ -114,10 +81,7 @@ def setup(hass, config):
else:
switch.turn_off()
switch.update_ha_state(hass)
# Update state every 30 seconds
hass.track_time_change(update_states, second=[0, 30])
switch.update_ha_state(True)
hass.services.register(DOMAIN, SERVICE_TURN_OFF, handle_switch_service)

View File

@ -0,0 +1,42 @@
""" Demo platform that has two fake switchces. """
from homeassistant.helpers import ToggleDevice
from homeassistant.const import STATE_ON, STATE_OFF, DEVICE_DEFAULT_NAME
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Find and return demo switches. """
add_devices_callback([
DemoSwitch('Ceiling', STATE_ON),
DemoSwitch('AC', STATE_OFF)
])
class DemoSwitch(ToggleDevice):
""" Provides a demo switch. """
def __init__(self, name, state):
self._name = name or DEVICE_DEFAULT_NAME
self._state = state
@property
def name(self):
""" Returns the name of the device if any. """
return self._name
@property
def state(self):
""" Returns the name of the device if any. """
return self._state
@property
def is_on(self):
""" True if device is on. """
return self._state == STATE_ON
def turn_on(self, **kwargs):
""" Turn the device on. """
self._state = STATE_ON
def turn_off(self, **kwargs):
""" Turn the device off. """
self._state = STATE_OFF

View File

@ -7,14 +7,15 @@ from homeassistant.helpers import ToggleDevice
import tellcore.constants as tellcore_constants
def get_devices(hass, config):
""" Find and return Tellstick switches. """
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Find and return tellstick switches. """
try:
import tellcore.telldus as telldus
except ImportError:
logging.getLogger(__name__).exception(
"Failed to import tellcore")
return []
return
core = telldus.TelldusCore()
switches_and_lights = core.devices()
@ -25,7 +26,7 @@ def get_devices(hass, config):
if not switch.methods(tellcore_constants.TELLSTICK_DIM):
switches.append(TellstickSwitchDevice(switch))
return switches
add_devices_callback(switches)
class TellstickSwitchDevice(ToggleDevice):

View File

@ -2,55 +2,40 @@
import logging
from homeassistant.helpers import ToggleDevice
from homeassistant.const import ATTR_FRIENDLY_NAME
from homeassistant.components.switch import (
ATTR_TODAY_MWH, ATTR_CURRENT_POWER_MWH)
def get_devices(hass, config):
""" Find and return WeMo switches. """
pywemo, _ = get_pywemo()
if pywemo is None:
return []
logging.getLogger(__name__).info("Scanning for WeMo devices")
switches = pywemo.discover_devices()
# Filter out the switches and wrap in WemoSwitch object
return [WemoSwitch(switch) for switch in switches
if isinstance(switch, pywemo.Switch)]
def devices_discovered(hass, config, info):
""" Called when a device is discovered. """
_, discovery = get_pywemo()
if discovery is None:
return []
device = discovery.device_from_description(info)
return [] if device is None else [WemoSwitch(device)]
def get_pywemo():
""" Tries to import PyWemo. """
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Find and return wemo switches. """
try:
# pylint: disable=no-name-in-module, import-error
import homeassistant.external.pywemo.pywemo as pywemo
import homeassistant.external.pywemo.pywemo.discovery as discovery
return pywemo, discovery
except ImportError:
logging.getLogger(__name__).exception((
"Failed to import pywemo. "
"Did you maybe not run `git submodule init` "
"and `git submodule update`?"))
return None, None
return
if discovery_info is not None:
device = discovery.device_from_description(discovery_info)
if device:
add_devices_callback([WemoSwitch(device)])
return
logging.getLogger(__name__).info("Scanning for WeMo devices")
switches = pywemo.discover_devices()
# Filter out the switches and wrap in WemoSwitch object
add_devices_callback(
[WemoSwitch(switch) for switch in switches
if isinstance(switch, pywemo.Switch)])
class WemoSwitch(ToggleDevice):
@ -75,12 +60,9 @@ class WemoSwitch(ToggleDevice):
cur_info = self.wemo.insight_params
return {
ATTR_FRIENDLY_NAME: self.wemo.name,
ATTR_CURRENT_POWER_MWH: cur_info['currentpower'],
ATTR_TODAY_MWH: cur_info['todaymw']
}
else:
return {ATTR_FRIENDLY_NAME: self.wemo.name}
@property
def is_on(self):

View File

@ -8,26 +8,17 @@ from homeassistant.components.wink import WinkToggleDevice
from homeassistant.const import CONF_ACCESS_TOKEN
def get_devices(hass, config):
""" Find and return Wink switches. """
token = config.get(CONF_ACCESS_TOKEN)
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the Wink platform. """
if discovery_info is None:
token = config.get(CONF_ACCESS_TOKEN)
if token is None:
logging.getLogger(__name__).error(
"Missing wink access_token - "
"get one at https://winkbearertoken.appspot.com/")
return []
if token is None:
logging.getLogger(__name__).error(
"Missing wink access_token - "
"get one at https://winkbearertoken.appspot.com/")
return
pywink.set_bearer_token(token)
pywink.set_bearer_token(token)
return get_switches()
def devices_discovered(hass, config, info):
""" Called when a device is discovered. """
return get_switches()
def get_switches():
""" Returns the Wink switches. """
return [WinkToggleDevice(switch) for switch in pywink.get_switches()]
add_devices(WinkToggleDevice(switch) for switch in pywink.get_switches())

View File

@ -5,25 +5,22 @@ homeassistant.components.thermostat
Provides functionality to interact with thermostats.
"""
import logging
from datetime import timedelta
from homeassistant.helpers import (
extract_entity_ids, platform_devices_from_config)
from homeassistant.helpers.device_component import DeviceComponent
import homeassistant.util as util
from homeassistant.helpers import Device
from homeassistant.helpers import Device, extract_entity_ids
from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_TEMPERATURE, ATTR_UNIT_OF_MEASUREMENT,
STATE_ON, STATE_OFF)
DOMAIN = "thermostat"
ENTITY_ID_FORMAT = DOMAIN + ".{}"
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
DEPENDENCIES = []
SERVICE_TURN_AWAY_MODE_ON = "turn_away_mode_on"
SERVICE_TURN_AWAY_MODE_OFF = "turn_away_mode_off"
ENTITY_ID_FORMAT = DOMAIN + ".{}"
SCAN_INTERVAL = 60
SERVICE_SET_AWAY_MODE = "set_away_mode"
SERVICE_SET_TEMPERATURE = "set_temperature"
ATTR_CURRENT_TEMPERATURE = "current_temperature"
@ -32,18 +29,16 @@ ATTR_AWAY_MODE = "away_mode"
_LOGGER = logging.getLogger(__name__)
def turn_away_mode_on(hass, entity_id=None):
def set_away_mode(hass, away_mode, entity_id=None):
""" Turn all or specified thermostat away mode on. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
data = {
ATTR_AWAY_MODE: away_mode
}
hass.services.call(DOMAIN, SERVICE_TURN_AWAY_MODE_ON, data)
if entity_id:
data[ATTR_ENTITY_ID] = entity_id
def turn_away_mode_off(hass, entity_id=None):
""" Turn all or specified thermostat away mode off. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
hass.services.call(DOMAIN, SERVICE_TURN_AWAY_MODE_OFF, data)
hass.services.call(DOMAIN, SERVICE_SET_AWAY_MODE, data)
def set_temperature(hass, temperature, entity_id=None):
@ -58,26 +53,10 @@ def set_temperature(hass, temperature, entity_id=None):
def setup(hass, config):
""" Setup thermostats. """
component = DeviceComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL)
component.setup(config)
logger = logging.getLogger(__name__)
thermostats = platform_devices_from_config(
config, DOMAIN, hass, ENTITY_ID_FORMAT, _LOGGER)
if not thermostats:
return False
@util.Throttle(MIN_TIME_BETWEEN_SCANS)
def update_state(now):
""" Update thermostat state. """
logger.info("Updating thermostat state")
for thermostat in thermostats.values():
thermostat.update_ha_state(hass, True)
# Update state every minute
hass.track_time_change(update_state, second=[0])
update_state(None)
thermostats = component.devices
def thermostat_service(service):
""" Handles calls to the services. """
@ -90,13 +69,20 @@ def setup(hass, config):
if not target_thermostats:
target_thermostats = thermostats.values()
if service.service == SERVICE_TURN_AWAY_MODE_ON:
for thermostat in target_thermostats:
thermostat.turn_away_mode_on()
if service.service == SERVICE_SET_AWAY_MODE:
away_mode = service.data.get(ATTR_AWAY_MODE)
elif service.service == SERVICE_TURN_AWAY_MODE_OFF:
for thermostat in target_thermostats:
thermostat.turn_away_mode_off()
if away_mode is None:
_LOGGER.error(
"Received call to %s without attribute %s",
SERVICE_SET_AWAY_MODE, ATTR_AWAY_MODE)
elif away_mode:
for thermostat in target_thermostats:
thermostat.turn_away_mode_on()
else:
for thermostat in target_thermostats:
thermostat.turn_away_mode_off()
elif service.service == SERVICE_SET_TEMPERATURE:
temperature = util.convert(
@ -109,13 +95,10 @@ def setup(hass, config):
thermostat.set_temperature(temperature)
for thermostat in target_thermostats:
thermostat.update_ha_state(hass, True)
thermostat.update_ha_state(True)
hass.services.register(
DOMAIN, SERVICE_TURN_AWAY_MODE_OFF, thermostat_service)
hass.services.register(
DOMAIN, SERVICE_TURN_AWAY_MODE_ON, thermostat_service)
DOMAIN, SERVICE_SET_AWAY_MODE, thermostat_service)
hass.services.register(
DOMAIN, SERVICE_SET_TEMPERATURE, thermostat_service)

View File

@ -0,0 +1,64 @@
"""
Demo platform that offers a fake thermostat.
"""
from homeassistant.components.thermostat import ThermostatDevice
from homeassistant.const import TEMP_CELCIUS, TEMP_FAHRENHEIT
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the Demo thermostats. """
add_devices([
DemoThermostat("Nest", 21, TEMP_CELCIUS, False, 19),
DemoThermostat("Thermostat", 68, TEMP_FAHRENHEIT, True, 77),
])
# pylint: disable=too-many-arguments
class DemoThermostat(ThermostatDevice):
""" Represents a HeatControl within Home Assistant. """
def __init__(self, name, target_temperature, unit_of_measurement,
away, current_temperature):
self._name = name
self._target_temperature = target_temperature
self._unit_of_measurement = unit_of_measurement
self._away = away
self._current_temperature = current_temperature
@property
def name(self):
""" Returns the name. """
return self._name
@property
def unit_of_measurement(self):
""" Returns the unit of measurement. """
return self._unit_of_measurement
@property
def current_temperature(self):
""" Returns the current temperature. """
return self._current_temperature
@property
def target_temperature(self):
""" Returns the temperature we try to reach. """
return self._target_temperature
@property
def is_away_mode_on(self):
""" Returns if away mode is on. """
return self._away
def set_temperature(self, temperature):
""" Set new target temperature """
self._target_temperature = temperature
def turn_away_mode_on(self):
""" Turns away mode on. """
self._away = True
def turn_away_mode_off(self):
""" Turns away mode off. """
self._away = False

View File

@ -69,11 +69,11 @@ TOL_TEMP = 0.3
# pylint: disable=unused-argument
def get_devices(hass, config):
""" Gets thermostats. """
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the heat control thermostat. """
logger = logging.getLogger(__name__)
return [HeatControl(hass, config, logger)]
add_devices([HeatControl(hass, config, logger)])
# pylint: disable=too-many-instance-attributes

View File

@ -7,8 +7,9 @@ from homeassistant.components.thermostat import ThermostatDevice
from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD, TEMP_CELCIUS)
def get_devices(hass, config):
""" Gets Nest thermostats. """
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the nest thermostat. """
logger = logging.getLogger(__name__)
username = config.get(CONF_USERNAME)
@ -17,7 +18,7 @@ def get_devices(hass, config):
if username is None or password is None:
logger.error("Missing required configuration items %s or %s",
CONF_USERNAME, CONF_PASSWORD)
return []
return
try:
import nest
@ -26,14 +27,15 @@ def get_devices(hass, config):
"Error while importing dependency nest. "
"Did you maybe not install the python-nest dependency?")
return []
return
napi = nest.Nest(username, password)
return [
add_devices([
NestThermostat(structure, device)
for structure in napi.structures
for device in structure.devices]
for device in structure.devices
])
class NestThermostat(ThermostatDevice):

View File

@ -92,7 +92,7 @@ class WinkSensorDevice(Device):
class WinkToggleDevice(ToggleDevice):
""" represents a WeMo switch within home assistant. """
""" represents a Wink switch within home assistant. """
def __init__(self, wink):
self.wink = wink

View File

@ -0,0 +1,126 @@
"""
homeassistant.components.zwave
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Connects Home Assistant to a Z-Wave network.
"""
from pprint import pprint
from homeassistant import bootstrap
from homeassistant.const import (
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP,
EVENT_PLATFORM_DISCOVERED, ATTR_SERVICE, ATTR_DISCOVERED)
DOMAIN = "zwave"
DEPENDENCIES = []
CONF_USB_STICK_PATH = "usb_path"
DEFAULT_CONF_USB_STICK_PATH = "/zwaveusbstick"
CONF_DEBUG = "debug"
DISCOVER_SENSORS = "zwave.sensors"
COMMAND_CLASS_SENSOR_BINARY = 48
COMMAND_CLASS_SENSOR_MULTILEVEL = 49
COMMAND_CLASS_BATTERY = 128
# list of tuple (DOMAIN, discovered service, supported command classes)
DISCOVERY_COMPONENTS = [
('sensor', DISCOVER_SENSORS,
[COMMAND_CLASS_SENSOR_BINARY, COMMAND_CLASS_SENSOR_MULTILEVEL]),
]
ATTR_NODE_ID = "node_id"
ATTR_VALUE_ID = "value_id"
NETWORK = None
def _obj_to_dict(obj):
""" Converts an obj into a hash for debug. """
return {key: getattr(obj, key) for key
in dir(obj)
if key[0] != '_' and not hasattr(getattr(obj, key), '__call__')}
def nice_print_node(node):
""" Prints a nice formatted node to the output (debug method) """
node_dict = _obj_to_dict(node)
node_dict['values'] = {value_id: _obj_to_dict(value)
for value_id, value in node.values.items()}
print("\n\n\n")
print("FOUND NODE", node.product_name)
pprint(node_dict)
print("\n\n\n")
def setup(hass, config):
"""
Setup Z-wave.
Will automatically load components to support devices found on the network.
"""
# pylint: disable=global-statement, import-error
global NETWORK
from pydispatch import dispatcher
from openzwave.option import ZWaveOption
from openzwave.network import ZWaveNetwork
use_debug = config[DOMAIN].get(CONF_DEBUG) == '1'
# Setup options
options = ZWaveOption(
config[DOMAIN].get(CONF_USB_STICK_PATH, DEFAULT_CONF_USB_STICK_PATH),
user_path=hass.config_dir)
options.set_console_output(use_debug)
options.lock()
NETWORK = ZWaveNetwork(options, autostart=False)
if use_debug:
def log_all(signal, value=None):
""" Log all the louie signals. """
print("")
print("LOUIE SIGNAL *****", signal)
if value and signal in (ZWaveNetwork.SIGNAL_VALUE_CHANGED,
ZWaveNetwork.SIGNAL_VALUE_ADDED):
pprint(_obj_to_dict(value))
print("")
dispatcher.connect(log_all, weak=False)
def value_added(node, value):
""" Called when a value is added to a node on the network. """
for component, discovery_service, command_ids in DISCOVERY_COMPONENTS:
if value.command_class in command_ids:
# Ensure component is loaded
if component not in hass.components:
bootstrap.setup_component(hass, component, config)
# Fire discovery event
hass.bus.fire(EVENT_PLATFORM_DISCOVERED, {
ATTR_SERVICE: discovery_service,
ATTR_DISCOVERED: {
ATTR_NODE_ID: node.node_id,
ATTR_VALUE_ID: value.value_id,
}
})
dispatcher.connect(
value_added, ZWaveNetwork.SIGNAL_VALUE_ADDED, weak=False)
def stop_zwave(event):
""" Stop Z-wave. """
NETWORK.stop()
def start_zwave(event):
""" Called when Home Assistant starts up. """
NETWORK.start()
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_zwave)
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_zwave)
return True

View File

@ -65,14 +65,17 @@ ATTR_UNIT_OF_MEASUREMENT = "unit_of_measurement"
# Temperature attribute
ATTR_TEMPERATURE = "temperature"
# #### MISC ####
TEMP_CELCIUS = "°C"
TEMP_FAHRENHEIT = "°F"
# Contains the information that is discovered
ATTR_DISCOVERED = "discovered"
# Location of the device/sensor
ATTR_LOCATION = "location"
ATTR_BATTERY_LEVEL = "battery_level"
# #### SERVICES ####
SERVICE_HOMEASSISTANT_STOP = "stop"

@ -1 +1 @@
Subproject commit 68877783cb989b874cbcaec5f388a8a4345891a6
Subproject commit 6e712dd65e474bf623b35c54f5290dbac192c7e4

View File

@ -7,8 +7,8 @@ from homeassistant import NoEntitySpecifiedError
from homeassistant.loader import get_component
from homeassistant.const import (
ATTR_ENTITY_ID, STATE_ON, STATE_OFF, CONF_PLATFORM, CONF_TYPE,
DEVICE_DEFAULT_NAME)
ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, STATE_ON, STATE_OFF, CONF_PLATFORM,
CONF_TYPE, DEVICE_DEFAULT_NAME)
from homeassistant.util import ensure_unique_string, slugify
@ -159,6 +159,8 @@ def platform_devices_from_config(config, domain, hass,
no_name_count = 0
for device in devices:
device.hass = hass
# Get the name or set to default if none given
name = device.name or DEVICE_DEFAULT_NAME
@ -179,8 +181,17 @@ class Device(object):
""" ABC for Home Assistant devices. """
# pylint: disable=no-self-use
hass = None
entity_id = None
@property
def should_poll(self):
"""
Return True if device has to be polled for state.
False if device pushes its state to HA.
"""
return True
@property
def unique_id(self):
""" Returns a unique id. """
@ -216,17 +227,20 @@ class Device(object):
def get_state_attributes(self):
""" Returns optional state attributes. """
return {}
return None
def update(self):
""" Retrieve latest state from the real device. """
pass
def update_ha_state(self, hass, force_refresh=False):
def update_ha_state(self, force_refresh=False):
"""
Updates Home Assistant with current state of device.
If force_refresh == True will update device before setting state.
"""
if self.hass is None:
raise RuntimeError("Attribute hass is None for {}".format(self))
if self.entity_id is None:
raise NoEntitySpecifiedError(
"No entity specified for device {}".format(self.name))
@ -234,13 +248,20 @@ class Device(object):
if force_refresh:
self.update()
return hass.states.set(self.entity_id, self.state,
self.state_attributes)
attr = self.state_attributes or {}
if ATTR_FRIENDLY_NAME not in attr and self.name:
attr[ATTR_FRIENDLY_NAME] = self.name
return self.hass.states.set(self.entity_id, self.state, attr)
def __eq__(self, other):
return (isinstance(other, Device) and
other.unique_id == self.unique_id)
def __repr__(self):
return "<Device {}: {}>".format(self.name, self.state)
class ToggleDevice(Device):
""" ABC for devices that can be turned on and off. """

View File

@ -0,0 +1,127 @@
"""
Provides helpers for components that handle devices.
"""
from homeassistant.loader import get_component
from homeassistant.helpers import generate_entity_id, config_per_platform
from homeassistant.components import group, discovery
class DeviceComponent(object):
# pylint: disable=too-many-instance-attributes
# pylint: disable=too-many-arguments,too-few-public-methods
"""
Helper class that will help a device component manage its devices.
"""
def __init__(self, logger, domain, hass, scan_interval,
discovery_platforms=None, group_name=None):
self.logger = logger
self.hass = hass
self.domain = domain
self.entity_id_format = domain + '.{}'
self.scan_interval = scan_interval
self.discovery_platforms = discovery_platforms
self.group_name = group_name
self.devices = {}
self.group = None
self.is_polling = False
def setup(self, config):
"""
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):
self._setup_platform(p_type, p_config)
if self.discovery_platforms:
discovery.listen(self.hass, self.discovery_platforms.keys(),
self._device_discovered)
def _update_device_states(self, now):
""" Update the states of all the lights. """
self.logger.info("Updating %s states", self.domain)
for device in self.devices.values():
if device.should_poll:
device.update_ha_state(True)
def _device_discovered(self, service, info):
""" Called when a device is discovered. """
if service not in self.discovery_platforms:
return
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 \
not any(device.should_poll for device in self.devices.values()):
return
self.is_polling = True
self.hass.track_time_change(
self._update_device_states,
second=range(0, 60, self.scan_interval))
def _setup_platform(self, platform_type, config, discovery_info=None):
""" Tries to setup a platform for this component. """
platform_name = '{}.{}'.format(self.domain, platform_type)
platform = get_component(platform_name)
if platform is None:
self.logger.error('Unable to find platform %s', platform_type)
return
try:
platform.setup_platform(
self.hass, config, self._add_devices, discovery_info)
except AttributeError:
# Support old deprecated method for now - 3/1/2015
if hasattr(platform, 'get_devices'):
self.logger.warning(
"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))
else:
# AttributeError if setup_platform does not exist
self.logger.exception(
"Error setting up %s", platform_type)

View File

@ -132,10 +132,13 @@ def get_component(comp_name):
return module
except ImportError:
_LOGGER.exception(
("Error loading %s. Make sure all "
"dependencies are installed"), path)
except ImportError as err:
# This error happens if for example custom_components/switch
# exists and we try to load switch.demo.
if str(err) != "No module named '{}'".format(path):
_LOGGER.exception(
("Error loading %s. Make sure all "
"dependencies are installed"), path)
_LOGGER.error("Unable to find component %s", comp_name)

View File

@ -29,3 +29,9 @@ pushbullet.py>=0.7.1
# thermostat.nest
python-nest>=2.1
# z-wave
pydispatcher>=2.0.5
# pyyaml
pyyaml

View File

@ -1,3 +1,4 @@
# Builds the frontend for production
# Call 'build_frontend demo' to build a demo frontend.
# If current pwd is scripts, go 1 up.

View File

@ -1,3 +1,5 @@
# Builds the JS for production
# If current pwd is scripts, go 1 up.
if [ ${PWD##*/} == "scripts" ]; then
cd ..

22
scripts/build_python_openzwave Executable file
View File

@ -0,0 +1,22 @@
# 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
# pip3 install cython
# If current pwd is scripts, go 1 up.
if [ ${PWD##*/} == "scripts" ]; then
cd ..
fi
mkdir build
cd build
hg clone https://code.google.com/r/balloob-python-openzwave/
cd balloob-python-openzwave
./update.sh
# Fix an issue with openzwave
sed -i '253s/.*//' openzwave/cpp/src/value_classes/ValueID.h
./compile.sh
./install.sh

View File

@ -1,3 +1,5 @@
# Run style checks
# If current pwd is scripts, go 1 up.
if [ ${PWD##*/} == "scripts" ]; then
cd ..

View File

@ -1,3 +1,5 @@
# Build and run Home Assinstant in Docker
# Optional: pass in a timezone as first argument
# If not given will attempt to mount /etc/localtime
@ -12,6 +14,7 @@ if [ $# -gt 0 ]
then
docker run \
--net=host \
--device=/dev/ttyUSB0:/zwaveusbstick:rwm \
-e "TZ=$1" \
-v `pwd`:/usr/src/app \
-v `pwd`/config:/config \

View File

@ -1,3 +1,5 @@
# Builds the JS for developing, rebuilds when files change
# If current pwd is scripts, go 1 up.
if [ ${PWD##*/} == "scripts" ]; then
cd ..

19
scripts/dev_openzwave_docker Executable file
View File

@ -0,0 +1,19 @@
# Open a docker that can be used to debug/dev python-openzwave
# Pass in a command line argument to build
# If current pwd is scripts, go 1 up.
if [ ${PWD##*/} == "scripts" ]; then
cd ..
fi
if [ $# -gt 0 ]
then
docker build -t home-assistant-dev .
fi
docker run \
--device=/dev/ttyUSB0:/zwaveusbstick:rwm \
-v `pwd`:/usr/src/app \
-p 8123:8123 \
-t -i home-assistant-dev \
/bin/bash

View File

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

View File

@ -24,6 +24,6 @@ def init(empty=False):
]
def get_switches(hass, config):
""" Returns mock devices. """
return DEVICES
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Find and return test switches. """
add_devices_callback(DEVICES)

View File

@ -103,7 +103,7 @@ class TestLight(unittest.TestCase):
self.assertTrue(
light.setup(self.hass, {light.DOMAIN: {CONF_TYPE: 'test'}}))
dev1, dev2, dev3 = platform.get_lights()
dev1, dev2, dev3 = platform.DEVICES
# Test init
self.assertTrue(light.is_on(self.hass, dev1.entity_id))
@ -244,7 +244,7 @@ class TestLight(unittest.TestCase):
self.hass, {light.DOMAIN: {CONF_TYPE: 'test'}}
))
dev1, dev2, dev3 = platform.get_lights()
dev1, dev2, dev3 = platform.DEVICES
light.turn_on(self.hass, dev1.entity_id, profile='test')

View File

@ -30,7 +30,7 @@ class TestSwitch(unittest.TestCase):
# Switch 1 is ON, switch 2 is OFF
self.switch_1, self.switch_2, self.switch_3 = \
platform.get_switches(None, None)
platform.DEVICES
def tearDown(self): # pylint: disable=invalid-name
""" Stop down stuff we started. """