mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 16:57:53 +00:00
Merge branch 'master' of https://github.com/balloob/home-assistant
Merging with the upstream master to get the compnent architecture changes
This commit is contained in:
commit
ff0099b6a7
@ -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" ]
|
||||
|
104
config/configuration.yaml.example
Normal file
104
config/configuration.yaml.example
Normal 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!"}
|
@ -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!"}
|
@ -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))
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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
@ -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
|
@ -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>
|
||||
|
@ -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>
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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,
|
||||
|
69
homeassistant/components/light/demo.py
Normal file
69
homeassistant/components/light/demo.py
Normal 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
|
@ -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']
|
||||
|
@ -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
|
||||
|
40
homeassistant/components/sensor/demo.py
Normal file
40
homeassistant/components/sensor/demo.py
Normal 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,
|
||||
}
|
@ -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())
|
||||
|
135
homeassistant/components/sensor/zwave.py
Normal file
135
homeassistant/components/sensor/zwave.py
Normal 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
|
@ -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()
|
||||
|
@ -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)
|
||||
|
||||
|
42
homeassistant/components/switch/demo.py
Normal file
42
homeassistant/components/switch/demo.py
Normal 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
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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())
|
||||
|
@ -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)
|
||||
|
64
homeassistant/components/thermostat/demo.py
Normal file
64
homeassistant/components/thermostat/demo.py
Normal 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
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
126
homeassistant/components/zwave.py
Normal file
126
homeassistant/components/zwave.py
Normal 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
|
@ -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"
|
||||
|
||||
|
2
homeassistant/external/netdisco
vendored
2
homeassistant/external/netdisco
vendored
@ -1 +1 @@
|
||||
Subproject commit 68877783cb989b874cbcaec5f388a8a4345891a6
|
||||
Subproject commit 6e712dd65e474bf623b35c54f5290dbac192c7e4
|
@ -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. """
|
127
homeassistant/helpers/device_component.py
Normal file
127
homeassistant/helpers/device_component.py
Normal 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)
|
@ -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)
|
||||
|
||||
|
@ -29,3 +29,9 @@ pushbullet.py>=0.7.1
|
||||
|
||||
# thermostat.nest
|
||||
python-nest>=2.1
|
||||
|
||||
# z-wave
|
||||
pydispatcher>=2.0.5
|
||||
|
||||
# pyyaml
|
||||
pyyaml
|
||||
|
@ -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.
|
||||
|
@ -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
22
scripts/build_python_openzwave
Executable 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
|
@ -1,3 +1,5 @@
|
||||
# Run style checks
|
||||
|
||||
# If current pwd is scripts, go 1 up.
|
||||
if [ ${PWD##*/} == "scripts" ]; then
|
||||
cd ..
|
||||
|
@ -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 \
|
||||
|
@ -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
19
scripts/dev_openzwave_docker
Executable 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
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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')
|
||||
|
||||
|
@ -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. """
|
||||
|
Loading…
x
Reference in New Issue
Block a user