Merge pull request #51 from balloob/dev

Merge dev into master
This commit is contained in:
Theodor Lindquist 2015-03-08 08:56:01 +01:00
commit 851bff1e12
47 changed files with 1070 additions and 822 deletions

40
.coveragerc Normal file
View File

@ -0,0 +1,40 @@
[run]
source = homeassistant
omit =
homeassistant/external/*
# omit pieces of code that rely on external devices being present
homeassistant/components/wink.py
homeassistant/components/*/wink.py
homeassistant/components/zwave.py
homeassistant/components/*/zwave.py
homeassistant/components/*/tellstick.py
homeassistant/components/keyboard.py
homeassistant/components/switch/wemo.py
homeassistant/components/thermostat/nest.py
homeassistant/components/light/hue.py
homeassistant/components/sensor/systemmonitor.py
homeassistant/components/notify/pushbullet.py
homeassistant/components/media_player/cast.py
homeassistant/components/device_tracker/luci.py
homeassistant/components/device_tracker/tomato.py
homeassistant/components/device_tracker/netgear.py
homeassistant/components/device_tracker/nmap_tracker.py
[report]
# Regexes for lines to exclude from consideration
exclude_lines =
# Have to re-enable the standard pragma
pragma: no cover
# Don't complain about missing debug-only code:
def __repr__
# Don't complain if tests don't hit defensive assertion code:
raise AssertionError
raise NotImplementedError

View File

@ -7,6 +7,6 @@ install:
script:
- flake8 homeassistant --exclude bower_components,external
- pylint homeassistant
- coverage run --source=homeassistant --omit "homeassistant/external/*" -m unittest discover tests
- coverage run -m unittest discover tests
after_success:
- coveralls

View File

@ -102,3 +102,21 @@ automation 2:
execute_service: notify.notify
service_data: {"message":"It's 4, time for beer!"}
sensor:
platform: systemmonitor
resources:
- type: 'disk_use_percent'
arg: '/'
- type: 'disk_use_percent'
arg: '/home'
- type: 'disk_use'
arg: '/home'
- type: 'disk_free'
arg: '/'
- type: 'memory_use_percent'
- type: 'memory_use'
- type: 'memory_free'
- type: 'processor_use'
- type: 'process'
arg: 'octave-cli'

View File

@ -1,339 +0,0 @@
"""
homeassistant.components.chromecast
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides functionality to interact with Chromecasts.
"""
import logging
try:
import pychromecast
except ImportError:
# Ignore, we will raise appropriate error later
pass
from homeassistant.loader import get_component
import homeassistant.util as util
from homeassistant.helpers import extract_entity_ids
from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, SERVICE_TURN_OFF, SERVICE_VOLUME_UP,
SERVICE_VOLUME_DOWN, SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PLAY,
SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREV_TRACK)
DOMAIN = 'chromecast'
DEPENDENCIES = []
SERVICE_YOUTUBE_VIDEO = 'play_youtube_video'
ENTITY_ID_FORMAT = DOMAIN + '.{}'
STATE_NO_APP = 'idle'
ATTR_STATE = 'state'
ATTR_OPTIONS = 'options'
ATTR_MEDIA_STATE = 'media_state'
ATTR_MEDIA_CONTENT_ID = 'media_content_id'
ATTR_MEDIA_TITLE = 'media_title'
ATTR_MEDIA_ARTIST = 'media_artist'
ATTR_MEDIA_ALBUM = 'media_album'
ATTR_MEDIA_IMAGE_URL = 'media_image_url'
ATTR_MEDIA_VOLUME = 'media_volume'
ATTR_MEDIA_DURATION = 'media_duration'
MEDIA_STATE_UNKNOWN = 'unknown'
MEDIA_STATE_PLAYING = 'playing'
MEDIA_STATE_STOPPED = 'stopped'
def is_on(hass, entity_id=None):
""" Returns true if specified ChromeCast entity_id is on.
Will check all chromecasts if no entity_id specified. """
entity_ids = [entity_id] if entity_id else hass.states.entity_ids(DOMAIN)
return any(not hass.states.is_state(entity_id, STATE_NO_APP)
for entity_id in entity_ids)
def turn_off(hass, entity_id=None):
""" Will turn off specified Chromecast or all. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_TURN_OFF, data)
def volume_up(hass, entity_id=None):
""" Send the chromecast the command for volume up. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_VOLUME_UP, data)
def volume_down(hass, entity_id=None):
""" Send the chromecast the command for volume down. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_VOLUME_DOWN, data)
def media_play_pause(hass, entity_id=None):
""" Send the chromecast the command for play/pause. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_MEDIA_PLAY_PAUSE, data)
def media_play(hass, entity_id=None):
""" Send the chromecast the command for play/pause. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_MEDIA_PLAY, data)
def media_pause(hass, entity_id=None):
""" Send the chromecast the command for play/pause. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_MEDIA_PAUSE, data)
def media_next_track(hass, entity_id=None):
""" Send the chromecast the command for next track. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_MEDIA_NEXT_TRACK, data)
def media_prev_track(hass, entity_id=None):
""" Send the chromecast the command for prev track. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_MEDIA_PREV_TRACK, data)
def setup_chromecast(casts, host):
""" Tries to convert host to Chromecast object and set it up. """
# Check if already setup
if any(cast.host == host for cast in casts.values()):
return
try:
cast = pychromecast.PyChromecast(host)
entity_id = util.ensure_unique_string(
ENTITY_ID_FORMAT.format(
util.slugify(cast.device.friendly_name)),
casts.keys())
casts[entity_id] = cast
except pychromecast.ChromecastConnectionError:
pass
def setup(hass, config):
# pylint: disable=unused-argument,too-many-locals
""" Listen for chromecast events. """
logger = logging.getLogger(__name__)
discovery = get_component('discovery')
try:
# pylint: disable=redefined-outer-name
import pychromecast
except ImportError:
logger.exception(("Failed to import pychromecast. "
"Did you maybe not install the 'pychromecast' "
"dependency?"))
return False
casts = {}
# If discovery component not loaded, scan ourselves
if discovery.DOMAIN not in hass.components:
logger.info("Scanning for Chromecasts")
hosts = pychromecast.discover_chromecasts()
for host in hosts:
setup_chromecast(casts, host)
def chromecast_discovered(service, info):
""" Called when a Chromecast has been discovered. """
logger.info("New Chromecast discovered: %s", info[0])
setup_chromecast(casts, info[0])
discovery.listen(
hass, discovery.services.GOOGLE_CAST, chromecast_discovered)
def update_chromecast_state(entity_id, chromecast):
""" Retrieve state of Chromecast and update statemachine. """
chromecast.refresh()
status = chromecast.app
state_attr = {ATTR_FRIENDLY_NAME:
chromecast.device.friendly_name}
if status and status.app_id != pychromecast.APP_ID['HOME']:
state = status.app_id
ramp = chromecast.get_protocol(pychromecast.PROTOCOL_RAMP)
if ramp and ramp.state != pychromecast.RAMP_STATE_UNKNOWN:
if ramp.state == pychromecast.RAMP_STATE_PLAYING:
state_attr[ATTR_MEDIA_STATE] = MEDIA_STATE_PLAYING
else:
state_attr[ATTR_MEDIA_STATE] = MEDIA_STATE_STOPPED
if ramp.content_id:
state_attr[ATTR_MEDIA_CONTENT_ID] = ramp.content_id
if ramp.title:
state_attr[ATTR_MEDIA_TITLE] = ramp.title
if ramp.artist:
state_attr[ATTR_MEDIA_ARTIST] = ramp.artist
if ramp.album:
state_attr[ATTR_MEDIA_ALBUM] = ramp.album
if ramp.image_url:
state_attr[ATTR_MEDIA_IMAGE_URL] = ramp.image_url
if ramp.duration:
state_attr[ATTR_MEDIA_DURATION] = ramp.duration
state_attr[ATTR_MEDIA_VOLUME] = ramp.volume
else:
state = STATE_NO_APP
hass.states.set(entity_id, state, state_attr)
def update_chromecast_states(time):
""" Updates all chromecast states. """
if casts:
logger.info("Updating Chromecast status")
for entity_id, cast in casts.items():
update_chromecast_state(entity_id, cast)
def _service_to_entities(service):
""" Helper method to get entities from service. """
entity_ids = extract_entity_ids(hass, service)
if entity_ids:
for entity_id in entity_ids:
cast = casts.get(entity_id)
if cast:
yield entity_id, cast
else:
yield from casts.items()
def turn_off_service(service):
""" Service to exit any running app on the specified ChromeCast and
shows idle screen. Will quit all ChromeCasts if nothing specified.
"""
for entity_id, cast in _service_to_entities(service):
cast.quit_app()
update_chromecast_state(entity_id, cast)
def volume_up_service(service):
""" Service to send the chromecast the command for volume up. """
for _, cast in _service_to_entities(service):
ramp = cast.get_protocol(pychromecast.PROTOCOL_RAMP)
if ramp:
ramp.volume_up()
def volume_down_service(service):
""" Service to send the chromecast the command for volume down. """
for _, cast in _service_to_entities(service):
ramp = cast.get_protocol(pychromecast.PROTOCOL_RAMP)
if ramp:
ramp.volume_down()
def media_play_pause_service(service):
""" Service to send the chromecast the command for play/pause. """
for _, cast in _service_to_entities(service):
ramp = cast.get_protocol(pychromecast.PROTOCOL_RAMP)
if ramp:
ramp.playpause()
def media_play_service(service):
""" Service to send the chromecast the command for play/pause. """
for _, cast in _service_to_entities(service):
ramp = cast.get_protocol(pychromecast.PROTOCOL_RAMP)
if ramp and ramp.state == pychromecast.RAMP_STATE_STOPPED:
ramp.playpause()
def media_pause_service(service):
""" Service to send the chromecast the command for play/pause. """
for _, cast in _service_to_entities(service):
ramp = cast.get_protocol(pychromecast.PROTOCOL_RAMP)
if ramp and ramp.state == pychromecast.RAMP_STATE_PLAYING:
ramp.playpause()
def media_next_track_service(service):
""" Service to send the chromecast the command for next track. """
for entity_id, cast in _service_to_entities(service):
ramp = cast.get_protocol(pychromecast.PROTOCOL_RAMP)
if ramp:
next(ramp)
update_chromecast_state(entity_id, cast)
def play_youtube_video_service(service, video_id):
""" Plays specified video_id on the Chromecast's YouTube channel. """
if video_id: # if service.data.get('video') returned None
for entity_id, cast in _service_to_entities(service):
pychromecast.play_youtube_video(video_id, cast.host)
update_chromecast_state(entity_id, cast)
hass.track_time_change(update_chromecast_states, second=range(0, 60, 15))
hass.services.register(DOMAIN, SERVICE_TURN_OFF,
turn_off_service)
hass.services.register(DOMAIN, SERVICE_VOLUME_UP,
volume_up_service)
hass.services.register(DOMAIN, SERVICE_VOLUME_DOWN,
volume_down_service)
hass.services.register(DOMAIN, SERVICE_MEDIA_PLAY_PAUSE,
media_play_pause_service)
hass.services.register(DOMAIN, SERVICE_MEDIA_PLAY,
media_play_service)
hass.services.register(DOMAIN, SERVICE_MEDIA_PAUSE,
media_pause_service)
hass.services.register(DOMAIN, SERVICE_MEDIA_NEXT_TRACK,
media_next_track_service)
hass.services.register(DOMAIN, "start_fireplace",
lambda service:
play_youtube_video_service(service, "eyU3bRy2x44"))
hass.services.register(DOMAIN, "start_epic_sax",
lambda service:
play_youtube_video_service(service, "kxopViU98Xo"))
hass.services.register(DOMAIN, SERVICE_YOUTUBE_VIDEO,
lambda service:
play_youtube_video_service(service,
service.data.get(
'video')))
update_chromecast_states(None)
return True

View File

@ -17,7 +17,8 @@ DOMAIN = "demo"
DEPENDENCIES = []
COMPONENTS_WITH_DEMO_PLATFORM = ['switch', 'light', 'thermostat', 'sensor']
COMPONENTS_WITH_DEMO_PLATFORM = [
'switch', 'light', 'thermostat', 'sensor', 'media_player']
def setup(hass, config):
@ -71,12 +72,6 @@ def setup(hass, config):
]
})
# Setup chromecast
hass.states.set("chromecast.Living_Rm", "Plex",
{'friendly_name': 'Living Room',
ATTR_ENTITY_PICTURE:
'http://graph.facebook.com/KillBillMovie/picture'})
# Setup configurator
configurator_ids = []

View File

@ -16,7 +16,7 @@ import homeassistant.util as util
from homeassistant.const import (
STATE_HOME, STATE_NOT_HOME, ATTR_ENTITY_PICTURE, ATTR_FRIENDLY_NAME,
CONF_PLATFORM, CONF_TYPE)
CONF_PLATFORM)
from homeassistant.components import group
DOMAIN = "device_tracker"
@ -53,21 +53,11 @@ def is_on(hass, entity_id=None):
def setup(hass, config):
""" Sets up the device tracker. """
# CONF_TYPE is deprecated for CONF_PLATOFRM. We keep supporting it for now.
if not (validate_config(config, {DOMAIN: [CONF_PLATFORM]}, _LOGGER) or
validate_config(config, {DOMAIN: [CONF_TYPE]}, _LOGGER)):
if not validate_config(config, {DOMAIN: [CONF_PLATFORM]}, _LOGGER):
return False
tracker_type = config[DOMAIN].get(CONF_PLATFORM)
if tracker_type is None:
tracker_type = config[DOMAIN][CONF_TYPE]
_LOGGER.warning((
"Please update your config for %s to use 'platform' "
"instead of 'type'"), tracker_type)
tracker_implementation = get_component(
'device_tracker.{}'.format(tracker_type))

View File

@ -25,7 +25,7 @@ SCAN_INTERVAL = 300 # seconds
SERVICE_HANDLERS = {
services.BELKIN_WEMO: "switch",
services.GOOGLE_CAST: "chromecast",
services.GOOGLE_CAST: "media_player",
services.PHILIPS_HUE: "light",
}

View File

@ -15,6 +15,8 @@ from homeassistant.const import URL_ROOT, HTTP_OK
DOMAIN = 'frontend'
DEPENDENCIES = ['api']
INDEX_PATH = os.path.join(os.path.dirname(__file__), 'index.html.template')
_LOGGER = logging.getLogger(__name__)
@ -40,10 +42,6 @@ def setup(hass, config):
def _handle_get_root(handler, path_match, data):
""" Renders the debug interface. """
def write(txt):
""" Helper to write text to the output. """
handler.wfile.write((txt + "\n").encode("UTF-8"))
handler.send_response(HTTP_OK)
handler.send_header('Content-type', 'text/html; charset=utf-8')
handler.end_headers()
@ -54,28 +52,16 @@ def _handle_get_root(handler, path_match, data):
app_url = "frontend-{}.html".format(version.VERSION)
# auto login if no password was set, else check api_password param
auth = (handler.server.api_password if handler.server.no_password_set
auth = ('no_password_set' if handler.server.no_password_set
else data.get('api_password', ''))
write(("<!doctype html>"
"<html>"
"<head><title>Home Assistant</title>"
"<meta name='mobile-web-app-capable' content='yes'>"
"<link rel='shortcut icon' href='/static/favicon.ico' />"
"<link rel='icon' type='image/png' "
" href='/static/favicon-192x192.png' sizes='192x192'>"
"<meta name='viewport' content='width=device-width, "
" user-scalable=no, initial-scale=1.0, "
" minimum-scale=1.0, maximum-scale=1.0' />"
"<meta name='theme-color' content='#03a9f4'>"
"</head>"
"<body fullbleed>"
"<h3 id='init' align='center'>Initializing Home Assistant</h3>"
"<script"
" src='/static/webcomponents.min.js'></script>"
"<link rel='import' href='/static/{}' />"
"<home-assistant auth='{}'></home-assistant>"
"</body></html>").format(app_url, auth))
with open(INDEX_PATH) as template_file:
template_html = template_file.read()
template_html = template_html.replace('{{ app_url }}', app_url)
template_html = template_html.replace('{{ auth }}', auth)
handler.wfile.write(template_html.encode("UTF-8"))
def _handle_get_static(handler, path_match, data):

View File

@ -0,0 +1,28 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Home Assistant</title>
<link rel='manifest' href='/static/manifest.json' />
<meta name='apple-mobile-web-app-capable' content='yes'>
<meta name='mobile-web-app-capable' content='yes'>
<meta name='viewport' content='width=device-width,
user-scalable=no' />
<link rel='shortcut icon' href='/static/favicon.ico' />
<link rel='icon' type='image/png'
href='/static/favicon-192x192.png' sizes='192x192'>
<link rel='apple-touch-icon' sizes='180x180'
href='/apple-icon-180x180.png'>
<meta name='theme-color' content='#03a9f4'>
</head>
<body fullbleed>
<h3 id='init' align='center'>Initializing Home Assistant</h3>
<script src='/static/webcomponents.min.js'></script>
<link rel='import' href='/static/{{ app_url }}' />
<home-assistant auth='{{ auth }}'></home-assistant>
</body>
</html>

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 18 KiB

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,14 @@
{
"name": "Home Assistant",
"short_name": "Assistant",
"start_url": "/",
"display": "standalone",
"icons": [
{
"src": "\/static\/favicon-192x192.png",
"sizes": "192x192",
"type": "image\/png",
"density": "4.0"
}
]
}

View File

@ -22,6 +22,7 @@
"core-icons": "polymer/core-icons#~0.5.5",
"core-image": "polymer/core-image#~0.5.5",
"core-style": "polymer/core-style#~0.5.5",
"core-label": "polymer/core-label#~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",
@ -33,6 +34,7 @@
"paper-dropdown": "polymer/paper-dropdown#~0.5.5",
"paper-item": "polymer/paper-item#~0.5.5",
"paper-slider": "polymer/paper-slider#~0.5.5",
"paper-checkbox": "polymer/paper-checkbox#~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.5",

View File

@ -29,7 +29,7 @@
case "switch":
return "image:flash-on";
case "chromecast":
case "media_player":
var icon = "hardware:cast";
if (state !== "idle") {
@ -47,9 +47,6 @@
case "light":
return "image:wb-incandescent";
case "tellstick_sensor":
return "trending-up";
case "simple_alarm":
return "social:notifications";

@ -1 +1 @@
Subproject commit 2f96b21d42c6b1c01a235b39fbbd2e0cf1b8d651
Subproject commit 8ea3a9e858a8c39d4c3aa46b719362b33f4a358f

View File

@ -30,8 +30,9 @@
</template>
<script>
var storeListenerMixIn = window.hass.storeListenerMixIn;
var uiActions = window.hass.uiActions;
var storeListenerMixIn = window.hass.storeListenerMixIn,
uiActions = window.hass.uiActions,
preferenceStore = window.hass.preferenceStore;
Polymer(Polymer.mixin({
loaded: false,
@ -42,7 +43,9 @@
// if auth was given, tell the backend
if(this.auth) {
uiActions.validateAuth(this.auth);
uiActions.validateAuth(this.auth, false);
} else if (preferenceStore.hasAuthToken()) {
uiActions.validateAuth(preferenceStore.getAuthToken(), false);
}
},

View File

@ -1,5 +1,7 @@
<link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="../bower_components/core-label/core-label.html">
<link rel="import" href="../bower_components/paper-checkbox/paper-checkbox.html">
<link rel="import" href="../bower_components/paper-button/paper-button.html">
<link rel="import" href="../bower_components/paper-input/paper-input-decorator.html">
<link rel="import" href="../bower_components/core-input/core-input.html">
@ -8,15 +10,29 @@
<polymer-element name="login-form">
<template>
<style>
paper-input {
#passwordDecorator {
display: block;
height: 57px;
}
.login paper-button {
margin-left: 242px;
paper-checkbox {
margin-right: 8px;
}
.login .interact {
paper-checkbox::shadow #checkbox.checked {
background-color: #03a9f4;
border-color: #03a9f4;
}
paper-checkbox::shadow #ink[checked] {
color: #03a9f4;
}
paper-button {
margin-left: 72px;
}
.interact {
height: 125px;
}
@ -44,7 +60,14 @@
<input is="core-input" type="password" id="passwordInput"
value="{{authToken}}" on-keyup="{{passwordKeyup}}">
</paper-input-decorator>
<paper-button on-click={{validatePassword}}>Log In</paper-button>
<div horizontal center layout>
<core-label horizontal layout>
<paper-checkbox for checked={{rememberLogin}}></paper-checkbox><b>Remember</b>
</core-label>
<paper-button on-click={{validatePassword}}>Log In</paper-button>
</div>
</div>
<div id="validatebox" hidden?="{{!(isValidating || isLoggedIn)}}">
@ -66,6 +89,7 @@
MSG_LOADING_DATA: "Loading data…",
authToken: "",
rememberLogin: false,
isValidating: false,
isLoggedIn: false,
@ -87,7 +111,7 @@
this.spinnerMessage = this.isValidating ? this.MSG_VALIDATING : this.MSG_LOADING_DATA;
if (authStore.wasLastAttemptInvalid()) {
this.$.passwordDecorator.error = this.authStore.getLastAttemptMessage();
this.$.passwordDecorator.error = authStore.getLastAttemptMessage();
this.$.passwordDecorator.isInvalid = true;
}
@ -114,7 +138,7 @@
validatePassword: function() {
this.$.hideKeyboardOnFocus.focus();
uiActions.validateAuth(this.authToken);
uiActions.validateAuth(this.authToken, this.rememberLogin);
},
}, storeListenerMixIn));
</script>

View File

@ -61,8 +61,11 @@
});
},
validateAuth: function(authToken) {
authActions.validate(authToken, preferenceStore.useStreaming());
validateAuth: function(authToken, rememberLogin) {
authActions.validate(authToken, {
useStreaming: preferenceStore.useStreaming(),
rememberLogin: rememberLogin,
});
},
};
})();

View File

@ -111,7 +111,7 @@ def setup(hass, config=None):
if config is None or DOMAIN not in config:
config = {DOMAIN: {}}
api_password = config[DOMAIN].get(CONF_API_PASSWORD)
api_password = str(config[DOMAIN].get(CONF_API_PASSWORD))
no_password_set = api_password is None
@ -123,7 +123,7 @@ def setup(hass, config=None):
server_port = config[DOMAIN].get(CONF_SERVER_PORT, SERVER_PORT)
development = config[DOMAIN].get(CONF_DEVELOPMENT, "") == "1"
development = str(config[DOMAIN].get(CONF_DEVELOPMENT, "")) == "1"
server = HomeAssistantHTTPServer(
(server_host, server_port), RequestHandler, hass, api_password,
@ -341,17 +341,7 @@ class RequestHandler(SimpleHTTPRequestHandler):
self.send_response(HTTP_OK)
self.send_header(HTTP_HEADER_CONTENT_TYPE, content_type)
# Add cache if not development
if not self.server.development:
# 1 year in seconds
cache_time = 365 * 86400
self.send_header(
HTTP_HEADER_CACHE_CONTROL,
"public, max-age={}".format(cache_time))
self.send_header(
HTTP_HEADER_EXPIRES,
self.date_time_string(time.time()+cache_time))
self.set_cache_header()
if do_gzip:
gzip_data = gzip.compress(inp.read())
@ -374,3 +364,16 @@ class RequestHandler(SimpleHTTPRequestHandler):
else:
self.copyfile(inp, self.wfile)
def set_cache_header(self):
""" Add cache headers if not in development """
if not self.server.development:
# 1 year in seconds
cache_time = 365 * 86400
self.send_header(
HTTP_HEADER_CACHE_CONTROL,
"public, max-age={}".format(cache_time))
self.send_header(
HTTP_HEADER_EXPIRES,
self.date_time_string(time.time()+cache_time))

View File

@ -57,7 +57,6 @@ 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 extract_entity_ids
from homeassistant.components import group, discovery, wink
@ -146,8 +145,6 @@ def setup(hass, config):
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),
@ -182,12 +179,7 @@ def setup(hass, config):
dat = service.data
# Convert the entity ids to valid light ids
target_lights = [lights[entity_id] for entity_id
in extract_entity_ids(hass, service)
if entity_id in lights]
if not target_lights:
target_lights = lights.values()
target_lights = component.extract_from_service(service)
params = {}

View File

@ -1,7 +1,7 @@
""" Provides demo lights. """
import random
from homeassistant.helpers import ToggleDevice
from homeassistant.helpers.device import ToggleDevice
from homeassistant.const import STATE_ON, STATE_OFF, DEVICE_DEFAULT_NAME
from homeassistant.components.light import ATTR_BRIGHTNESS, ATTR_XY_COLOR

View File

@ -6,14 +6,14 @@ from urllib.parse import urlparse
from homeassistant.loader import get_component
import homeassistant.util as util
from homeassistant.helpers import ToggleDevice
from homeassistant.helpers.device import ToggleDevice
from homeassistant.const import CONF_HOST
from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_XY_COLOR, ATTR_TRANSITION,
ATTR_FLASH, FLASH_LONG, FLASH_SHORT)
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(seconds=1)
MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(milliseconds=100)
PHUE_CONFIG_FILE = "phue.conf"

View File

@ -3,7 +3,7 @@ import logging
# pylint: disable=no-name-in-module, import-error
from homeassistant.components.light import ATTR_BRIGHTNESS
from homeassistant.const import ATTR_FRIENDLY_NAME
from homeassistant.helpers import ToggleDevice
from homeassistant.helpers.device import ToggleDevice
import tellcore.constants as tellcore_constants

View File

@ -0,0 +1,207 @@
"""
homeassistant.components.media_player
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Component to interface with various media players
"""
import logging
from homeassistant.components import discovery
from homeassistant.helpers.device import Device
from homeassistant.helpers.device_component import DeviceComponent
from homeassistant.const import (
ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_VOLUME_UP,
SERVICE_VOLUME_DOWN, SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PLAY,
SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREV_TRACK)
DOMAIN = 'media_player'
DEPENDENCIES = []
SCAN_INTERVAL = 30
ENTITY_ID_FORMAT = DOMAIN + '.{}'
DISCOVERY_PLATFORMS = {
discovery.services.GOOGLE_CAST: 'cast',
}
SERVICE_YOUTUBE_VIDEO = 'play_youtube_video'
STATE_NO_APP = 'idle'
ATTR_STATE = 'state'
ATTR_OPTIONS = 'options'
ATTR_MEDIA_STATE = 'media_state'
ATTR_MEDIA_CONTENT_ID = 'media_content_id'
ATTR_MEDIA_TITLE = 'media_title'
ATTR_MEDIA_ARTIST = 'media_artist'
ATTR_MEDIA_ALBUM = 'media_album'
ATTR_MEDIA_IMAGE_URL = 'media_image_url'
ATTR_MEDIA_VOLUME = 'media_volume'
ATTR_MEDIA_DURATION = 'media_duration'
MEDIA_STATE_UNKNOWN = 'unknown'
MEDIA_STATE_PLAYING = 'playing'
MEDIA_STATE_STOPPED = 'stopped'
YOUTUBE_COVER_URL_FORMAT = 'http://img.youtube.com/vi/{}/1.jpg'
def is_on(hass, entity_id=None):
""" Returns true if specified media player entity_id is on.
Will check all media player if no entity_id specified. """
entity_ids = [entity_id] if entity_id else hass.states.entity_ids(DOMAIN)
return any(not hass.states.is_state(entity_id, STATE_NO_APP)
for entity_id in entity_ids)
def turn_off(hass, entity_id=None):
""" Will turn off specified media player or all. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_TURN_OFF, data)
def volume_up(hass, entity_id=None):
""" Send the media player the command for volume up. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_VOLUME_UP, data)
def volume_down(hass, entity_id=None):
""" Send the media player the command for volume down. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_VOLUME_DOWN, data)
def media_play_pause(hass, entity_id=None):
""" Send the media player the command for play/pause. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_MEDIA_PLAY_PAUSE, data)
def media_play(hass, entity_id=None):
""" Send the media player the command for play/pause. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_MEDIA_PLAY, data)
def media_pause(hass, entity_id=None):
""" Send the media player the command for play/pause. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_MEDIA_PAUSE, data)
def media_next_track(hass, entity_id=None):
""" Send the media player the command for next track. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_MEDIA_NEXT_TRACK, data)
def media_prev_track(hass, entity_id=None):
""" Send the media player the command for prev track. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_MEDIA_PREV_TRACK, data)
SERVICE_TO_METHOD = {
SERVICE_TURN_OFF: 'turn_off',
SERVICE_VOLUME_UP: 'volume_up',
SERVICE_VOLUME_DOWN: 'volume_down',
SERVICE_MEDIA_PLAY_PAUSE: 'media_play_pause',
SERVICE_MEDIA_PLAY: 'media_play',
SERVICE_MEDIA_PAUSE: 'media_pause',
SERVICE_MEDIA_NEXT_TRACK: 'media_next_track',
}
def setup(hass, config):
""" Track states and offer events for media_players. """
component = DeviceComponent(
logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL,
DISCOVERY_PLATFORMS)
component.setup(config)
def media_player_service_handler(service):
""" Maps services to methods on MediaPlayerDevice. """
target_players = component.extract_from_service(service)
method = SERVICE_TO_METHOD[service.service]
for player in target_players:
getattr(player, method)()
if player.should_poll:
player.update_ha_state(True)
for service in SERVICE_TO_METHOD:
hass.services.register(DOMAIN, service, media_player_service_handler)
def play_youtube_video_service(service, media_id):
""" Plays specified media_id on the media player. """
target_players = component.extract_from_service(service)
if media_id:
for player in target_players:
player.play_youtube(media_id)
hass.services.register(DOMAIN, "start_fireplace",
lambda service:
play_youtube_video_service(service, "eyU3bRy2x44"))
hass.services.register(DOMAIN, "start_epic_sax",
lambda service:
play_youtube_video_service(service, "kxopViU98Xo"))
hass.services.register(DOMAIN, SERVICE_YOUTUBE_VIDEO,
lambda service:
play_youtube_video_service(
service, service.data.get('video')))
return True
class MediaPlayerDevice(Device):
""" ABC for media player devices. """
def turn_off(self):
""" turn_off media player. """
pass
def volume_up(self):
""" volume_up media player. """
pass
def volume_down(self):
""" volume_down media player. """
pass
def media_play_pause(self):
""" media_play_pause media player. """
pass
def media_play(self):
""" media_play media player. """
pass
def media_pause(self):
""" media_pause media player. """
pass
def media_next_track(self):
""" media_next_track media player. """
pass
def play_youtube(self, media_id):
""" Plays a YouTube media. """
pass

View File

@ -0,0 +1,162 @@
"""
homeassistant.components.media_player.chromecast
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides functionality to interact with Cast devices on the network.
WARNING: This platform is currently not working due to a changed Cast API
"""
import logging
try:
import pychromecast
except ImportError:
# We will throw error later
pass
from homeassistant.components.media_player import (
MediaPlayerDevice, STATE_NO_APP, ATTR_MEDIA_STATE,
ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_TITLE, ATTR_MEDIA_ARTIST,
ATTR_MEDIA_ALBUM, ATTR_MEDIA_IMAGE_URL, ATTR_MEDIA_DURATION,
ATTR_MEDIA_VOLUME, MEDIA_STATE_PLAYING, MEDIA_STATE_STOPPED)
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the cast platform. """
logger = logging.getLogger(__name__)
try:
# pylint: disable=redefined-outer-name
import pychromecast
except ImportError:
logger.exception(("Failed to import pychromecast. "
"Did you maybe not install the 'pychromecast' "
"dependency?"))
return
if discovery_info:
hosts = [discovery_info[0]]
else:
hosts = pychromecast.discover_chromecasts()
casts = []
for host in hosts:
try:
casts.append(CastDevice(host))
except pychromecast.ChromecastConnectionError:
pass
add_devices(casts)
class CastDevice(MediaPlayerDevice):
""" Represents a Cast device on the network. """
def __init__(self, host):
self.cast = pychromecast.PyChromecast(host)
@property
def name(self):
""" Returns the name of the device. """
return self.cast.device.friendly_name
@property
def state(self):
""" Returns the state of the device. """
status = self.cast.app
if status is None or status.app_id == pychromecast.APP_ID['HOME']:
return STATE_NO_APP
else:
return status.description
@property
def state_attributes(self):
""" Returns the state attributes. """
ramp = self.cast.get_protocol(pychromecast.PROTOCOL_RAMP)
if ramp and ramp.state != pychromecast.RAMP_STATE_UNKNOWN:
state_attr = {}
if ramp.state == pychromecast.RAMP_STATE_PLAYING:
state_attr[ATTR_MEDIA_STATE] = MEDIA_STATE_PLAYING
else:
state_attr[ATTR_MEDIA_STATE] = MEDIA_STATE_STOPPED
if ramp.content_id:
state_attr[ATTR_MEDIA_CONTENT_ID] = ramp.content_id
if ramp.title:
state_attr[ATTR_MEDIA_TITLE] = ramp.title
if ramp.artist:
state_attr[ATTR_MEDIA_ARTIST] = ramp.artist
if ramp.album:
state_attr[ATTR_MEDIA_ALBUM] = ramp.album
if ramp.image_url:
state_attr[ATTR_MEDIA_IMAGE_URL] = ramp.image_url
if ramp.duration:
state_attr[ATTR_MEDIA_DURATION] = ramp.duration
state_attr[ATTR_MEDIA_VOLUME] = ramp.volume
return state_attr
def turn_off(self):
""" Service to exit any running app on the specimedia player ChromeCast and
shows idle screen. Will quit all ChromeCasts if nothing specified.
"""
self.cast.quit_app()
def volume_up(self):
""" Service to send the chromecast the command for volume up. """
ramp = self.cast.get_protocol(pychromecast.PROTOCOL_RAMP)
if ramp:
ramp.volume_up()
def volume_down(self):
""" Service to send the chromecast the command for volume down. """
ramp = self.cast.get_protocol(pychromecast.PROTOCOL_RAMP)
if ramp:
ramp.volume_down()
def media_play_pause(self):
""" Service to send the chromecast the command for play/pause. """
ramp = self.cast.get_protocol(pychromecast.PROTOCOL_RAMP)
if ramp:
ramp.playpause()
def media_play(self):
""" Service to send the chromecast the command for play/pause. """
ramp = self.cast.get_protocol(pychromecast.PROTOCOL_RAMP)
if ramp and ramp.state == pychromecast.RAMP_STATE_STOPPED:
ramp.playpause()
def media_pause(self):
""" Service to send the chromecast the command for play/pause. """
ramp = self.cast.get_protocol(pychromecast.PROTOCOL_RAMP)
if ramp and ramp.state == pychromecast.RAMP_STATE_PLAYING:
ramp.playpause()
def media_next_track(self):
""" Service to send the chromecast the command for next track. """
ramp = self.cast.get_protocol(pychromecast.PROTOCOL_RAMP)
if ramp:
ramp.next()
def play_youtube_video(self, video_id):
""" Plays specified video_id on the Chromecast's YouTube channel. """
pychromecast.play_youtube_video(video_id, self.cast.host)

View File

@ -0,0 +1,100 @@
"""
homeassistant.components.media_player.chromecast
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Demo implementation of the media player.
"""
from homeassistant.components.media_player import (
MediaPlayerDevice, STATE_NO_APP, ATTR_MEDIA_STATE,
ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_TITLE, ATTR_MEDIA_DURATION,
ATTR_MEDIA_VOLUME, MEDIA_STATE_PLAYING, MEDIA_STATE_STOPPED,
YOUTUBE_COVER_URL_FORMAT)
from homeassistant.const import ATTR_ENTITY_PICTURE
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the cast platform. """
add_devices([
DemoMediaPlayer(
'Living Room', 'eyU3bRy2x44',
'♥♥ The Best Fireplace Video (3 hours)'),
DemoMediaPlayer('Bedroom', 'kxopViU98Xo', 'Epic sax guy 10 hours')
])
class DemoMediaPlayer(MediaPlayerDevice):
""" A Demo media player that only supports YouTube. """
def __init__(self, name, youtube_id=None, media_title=None):
self._name = name
self.is_playing = youtube_id is not None
self.youtube_id = youtube_id
self.media_title = media_title
self.volume = 1.0
@property
def name(self):
""" Returns the name of the device. """
return self._name
@property
def state(self):
""" Returns the state of the device. """
return STATE_NO_APP if self.youtube_id is None else "YouTube"
@property
def state_attributes(self):
""" Returns the state attributes. """
if self.youtube_id is None:
return
state_attr = {
ATTR_MEDIA_CONTENT_ID: self.youtube_id,
ATTR_MEDIA_TITLE: self.media_title,
ATTR_MEDIA_DURATION: 100,
ATTR_MEDIA_VOLUME: self.volume,
ATTR_ENTITY_PICTURE:
YOUTUBE_COVER_URL_FORMAT.format(self.youtube_id)
}
if self.is_playing:
state_attr[ATTR_MEDIA_STATE] = MEDIA_STATE_PLAYING
else:
state_attr[ATTR_MEDIA_STATE] = MEDIA_STATE_STOPPED
return state_attr
def turn_off(self):
""" turn_off media player. """
self.youtube_id = None
self.is_playing = False
def volume_up(self):
""" volume_up media player. """
if self.volume < 1:
self.volume += 0.1
def volume_down(self):
""" volume_down media player. """
if self.volume > 0:
self.volume -= 0.1
def media_play_pause(self):
""" media_play_pause media player. """
self.is_playing = not self.is_playing
def media_play(self):
""" media_play media player. """
self.is_playing = True
def media_pause(self):
""" media_pause media player. """
self.is_playing = False
def play_youtube(self, media_id):
""" Plays a YouTube media. """
self.youtube_id = media_id
self.media_title = 'Demo media title'
self.is_playing = True

View File

@ -1,5 +1,5 @@
""" Support for Wink sensors. """
from homeassistant.helpers import Device
from homeassistant.helpers.device import Device
from homeassistant.const import (
TEMP_CELCIUS, ATTR_UNIT_OF_MEASUREMENT, ATTR_FRIENDLY_NAME)

View File

@ -0,0 +1,97 @@
"""
homeassistant.components.sensor.systemmonitor
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Shows system monitor values such as: disk, memory and processor use
"""
from homeassistant.helpers.device import Device
from homeassistant.const import (
ATTR_UNIT_OF_MEASUREMENT, ATTR_FRIENDLY_NAME, STATE_ON, STATE_OFF)
import psutil
import logging
SENSOR_TYPES = {
'disk_use_percent': ['Disk Use', '%'],
'disk_use': ['Disk Use', 'GiB'],
'disk_free': ['Disk Free', 'GiB'],
'memory_use_percent': ['RAM Use', '%'],
'memory_use': ['RAM Use', 'MiB'],
'memory_free': ['RAM Free', 'MiB'],
'processor_use': ['CPU Use', '%'],
'process': ['Process', ''],
}
_LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the sensors """
dev = []
for resource in config['resources']:
if 'arg' not in resource:
resource['arg'] = ''
if resource['type'] not in SENSOR_TYPES:
_LOGGER.error('Sensor type: "%s" does not exist', resource['type'])
else:
dev.append(SystemMonitorSensor(resource['type'], resource['arg']))
add_devices(dev)
class SystemMonitorSensor(Device):
""" A system monitor sensor """
def __init__(self, sensor_type, argument=''):
self._name = SENSOR_TYPES[sensor_type][0] + ' ' + argument
self.argument = argument
self.type = sensor_type
self._state = None
self.unit_of_measurement = SENSOR_TYPES[sensor_type][1]
self.update()
@property
def name(self):
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,
}
def update(self):
if self.type == 'disk_use_percent':
self._state = psutil.disk_usage(self.argument).percent
elif self.type == 'disk_use':
self._state = round(psutil.disk_usage(self.argument).used /
1024**3, 1)
elif self.type == 'disk_free':
self._state = round(psutil.disk_usage(self.argument).free /
1024**3, 1)
elif self.type == 'memory_use_percent':
self._state = psutil.virtual_memory().percent
elif self.type == 'memory_use':
self._state = round((psutil.virtual_memory().total -
psutil.virtual_memory().available) /
1024**2, 1)
elif self.type == 'memory_free':
self._state = round(psutil.virtual_memory().available / 1024**2, 1)
elif self.type == 'processor_use':
self._state = round(psutil.cpu_percent(interval=None))
elif self.type == 'process':
if any(self.argument in l.name() for l in psutil.process_iter()):
self._state = STATE_ON
else:
self._state = STATE_OFF

View File

@ -0,0 +1,126 @@
"""
homeassistant.components.sensor.tellstick
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Shows sensor values from tellstick sensors.
Possible config keys:
id of the sensor: Name the sensor with ID
135=Outside
only_named: Only show the named sensors
only_named=1
temperature_scale: The scale of the temperature value
temperature_scale=°C
datatype_mask: mask to determine which sensor values to show based on
https://tellcore-py.readthedocs.org
/en/v1.0.4/constants.html#module-tellcore.constants
datatype_mask=1 # only show temperature
datatype_mask=12 # only show rain rate and rain total
datatype_mask=127 # show all sensor values
"""
import logging
from collections import namedtuple
import tellcore.telldus as telldus
import tellcore.constants as tellcore_constants
from homeassistant.const import (
ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT, TEMP_CELCIUS)
from homeassistant.helpers.device import Device
DatatypeDescription = namedtuple("DatatypeDescription", ['name', 'unit'])
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up Tellstick sensors. """
sensor_value_descriptions = {
tellcore_constants.TELLSTICK_TEMPERATURE:
DatatypeDescription(
'temperature', config.get('temperature_scale', TEMP_CELCIUS)),
tellcore_constants.TELLSTICK_HUMIDITY:
DatatypeDescription('humidity', '%'),
tellcore_constants.TELLSTICK_RAINRATE:
DatatypeDescription('rain rate', ''),
tellcore_constants.TELLSTICK_RAINTOTAL:
DatatypeDescription('rain total', ''),
tellcore_constants.TELLSTICK_WINDDIRECTION:
DatatypeDescription('wind direction', ''),
tellcore_constants.TELLSTICK_WINDAVERAGE:
DatatypeDescription('wind average', ''),
tellcore_constants.TELLSTICK_WINDGUST:
DatatypeDescription('wind gust', '')
}
try:
core = telldus.TelldusCore()
except OSError:
logging.getLogger(__name__).exception(
'Could not initialize Tellstick.')
return
sensors = []
for ts_sensor in core.sensors():
try:
sensor_name = config[str(ts_sensor.id)]
except KeyError:
if 'only_named' in config:
continue
sensor_name = str(ts_sensor.id)
for datatype in sensor_value_descriptions.keys():
if datatype & int(config['datatype_mask']) and \
ts_sensor.has_value(datatype):
sensor_info = sensor_value_descriptions[datatype]
sensors.append(
TellstickSensor(
sensor_name, ts_sensor, datatype, sensor_info))
add_devices(sensors)
class TellstickSensor(Device):
""" Represents a Tellstick sensor. """
def __init__(self, name, sensor, datatype, sensor_info):
self.datatype = datatype
self.sensor = sensor
self.unit = sensor_info.unit or None
self._name = "{} {}".format(name, sensor_info.name)
@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.sensor.value(self.datatype).value
@property
def state_attributes(self):
""" Returns the state attributes. """
attrs = {
ATTR_FRIENDLY_NAME: self._name,
}
if self.unit:
attrs[ATTR_UNIT_OF_MEASUREMENT] = self.unit
return attrs

View File

@ -9,7 +9,7 @@ from openzwave.network import ZWaveNetwork
from pydispatch import dispatcher
import homeassistant.components.zwave as zwave
from homeassistant.helpers import Device
from homeassistant.helpers.device import Device
from homeassistant.const import (
ATTR_BATTERY_LEVEL, ATTR_UNIT_OF_MEASUREMENT, STATE_ON, STATE_OFF,
TEMP_CELCIUS, TEMP_FAHRENHEIT, ATTR_LOCATION)

View File

@ -10,7 +10,6 @@ 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 extract_entity_ids
from homeassistant.components import group, discovery, wink
DOMAIN = 'switch'
@ -64,16 +63,9 @@ def setup(hass, config):
GROUP_NAME_ALL_SWITCHES)
component.setup(config)
switches = component.devices
def handle_switch_service(service):
""" Handles calls to the switch services. """
target_switches = [switches[entity_id] for entity_id
in extract_entity_ids(hass, service)
if entity_id in switches]
if not target_switches:
target_switches = switches.values()
target_switches = component.extract_from_service(service)
for switch in target_switches:
if service.service == SERVICE_TURN_ON:

View File

@ -1,5 +1,5 @@
""" Demo platform that has two fake switchces. """
from homeassistant.helpers import ToggleDevice
from homeassistant.helpers.device import ToggleDevice
from homeassistant.const import STATE_ON, STATE_OFF, DEVICE_DEFAULT_NAME

View File

@ -3,7 +3,7 @@ import logging
from homeassistant.const import ATTR_FRIENDLY_NAME
from homeassistant.helpers import ToggleDevice
from homeassistant.helpers.device import ToggleDevice
import tellcore.constants as tellcore_constants

View File

@ -1,7 +1,7 @@
""" Support for WeMo switchces. """
import logging
from homeassistant.helpers import ToggleDevice
from homeassistant.helpers.device import ToggleDevice
from homeassistant.components.switch import (
ATTR_TODAY_MWH, ATTR_CURRENT_POWER_MWH)

View File

@ -1,141 +0,0 @@
"""
homeassistant.components.tellstick_sensor
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Shows sensor values from tellstick sensors.
Possible config keys:
id of the sensor: Name the sensor with ID
135=Outside
only_named: Only show the named sensors
only_named=1
temperature_scale: The scale of the temperature value
temperature_scale=°C
datatype_mask: mask to determine which sensor values to show based on
https://tellcore-py.readthedocs.org
/en/v1.0.4/constants.html#module-tellcore.constants
datatype_mask=1 # only show temperature
datatype_mask=12 # only show rain rate and rain total
datatype_mask=127 # show all sensor values
"""
import logging
from collections import namedtuple
import homeassistant.util as util
from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT
# The domain of your component. Should be equal to the name of your component
DOMAIN = "tellstick_sensor"
# List of component names (string) your component depends upon
# If you are setting up a group but not using a group for anything,
# don't depend on group
DEPENDENCIES = []
ENTITY_ID_FORMAT = DOMAIN + '.{}'
DatatypeDescription = namedtuple("DatatypeDescription", ['name', 'unit'])
def setup(hass, config):
""" Register services or listen for events that your component needs. """
logger = logging.getLogger(__name__)
try:
import tellcore.telldus as telldus
import tellcore.constants as tellcore_constants
except ImportError:
logger.exception(
"Failed to import tellcore")
return False
core = telldus.TelldusCore()
sensors = core.sensors()
if len(sensors) == 0:
logger.error("No Tellstick sensors found")
return False
sensor_value_descriptions = {
tellcore_constants.TELLSTICK_TEMPERATURE:
DatatypeDescription(
'temperature', config[DOMAIN]['temperature_scale']),
tellcore_constants.TELLSTICK_HUMIDITY:
DatatypeDescription('humidity', ' %'),
tellcore_constants.TELLSTICK_RAINRATE:
DatatypeDescription('rain rate', ''),
tellcore_constants.TELLSTICK_RAINTOTAL:
DatatypeDescription('rain total', ''),
tellcore_constants.TELLSTICK_WINDDIRECTION:
DatatypeDescription('wind direction', ''),
tellcore_constants.TELLSTICK_WINDAVERAGE:
DatatypeDescription('wind average', ''),
tellcore_constants.TELLSTICK_WINDGUST:
DatatypeDescription('wind gust', '')
}
def update_sensor_value_state(sensor_name, sensor_value):
""" Update the state of a sensor value """
sensor_value_description = \
sensor_value_descriptions[sensor_value.datatype]
sensor_value_name = '{} {}'.format(
sensor_name, sensor_value_description.name)
entity_id = ENTITY_ID_FORMAT.format(
util.slugify(sensor_value_name))
state = sensor_value.value
state_attr = {
ATTR_FRIENDLY_NAME: sensor_value_name,
ATTR_UNIT_OF_MEASUREMENT: sensor_value_description.unit
}
hass.states.set(entity_id, state, state_attr)
sensor_value_datatypes = [
tellcore_constants.TELLSTICK_TEMPERATURE,
tellcore_constants.TELLSTICK_HUMIDITY,
tellcore_constants.TELLSTICK_RAINRATE,
tellcore_constants.TELLSTICK_RAINTOTAL,
tellcore_constants.TELLSTICK_WINDDIRECTION,
tellcore_constants.TELLSTICK_WINDAVERAGE,
tellcore_constants.TELLSTICK_WINDGUST
]
def update_sensor_state(sensor):
""" Updates all the sensor values from the sensor """
try:
sensor_name = config[DOMAIN][str(sensor.id)]
except KeyError:
if 'only_named' in config[DOMAIN]:
return
sensor_name = str(sensor.id)
for datatype in sensor_value_datatypes:
if datatype & int(config[DOMAIN]['datatype_mask']) and \
sensor.has_value(datatype):
update_sensor_value_state(sensor_name, sensor.value(datatype))
def update_sensors_state(time):
""" Update the state of all sensors """
for sensor in sensors:
update_sensor_state(sensor)
update_sensors_state(None)
hass.track_time_change(update_sensors_state, second=[0, 30])
return True

View File

@ -9,7 +9,7 @@ import logging
from homeassistant.helpers.device_component import DeviceComponent
import homeassistant.util as util
from homeassistant.helpers import Device, extract_entity_ids
from homeassistant.helpers.device import Device
from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_TEMPERATURE, ATTR_UNIT_OF_MEASUREMENT,
STATE_ON, STATE_OFF)
@ -56,18 +56,11 @@ def setup(hass, config):
component = DeviceComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL)
component.setup(config)
thermostats = component.devices
def thermostat_service(service):
""" Handles calls to the services. """
# Convert the entity ids to valid light ids
target_thermostats = [thermostats[entity_id] for entity_id
in extract_entity_ids(hass, service)
if entity_id in thermostats]
if not target_thermostats:
target_thermostats = thermostats.values()
target_thermostats = component.extract_from_service(service)
if service.service == SERVICE_SET_AWAY_MODE:
away_mode = service.data.get(ATTR_AWAY_MODE)

View File

@ -67,7 +67,7 @@ def setup(hass, config):
from openzwave.option import ZWaveOption
from openzwave.network import ZWaveNetwork
use_debug = config[DOMAIN].get(CONF_DEBUG) == '1'
use_debug = str(config[DOMAIN].get(CONF_DEBUG)) == '1'
# Setup options
options = ZWaveOption(

View File

@ -9,9 +9,6 @@ DEVICE_DEFAULT_NAME = "Unnamed Device"
CONF_LATITUDE = "latitude"
CONF_LONGITUDE = "longitude"
# This one is deprecated. Use platform instead.
CONF_TYPE = "type"
CONF_PLATFORM = "platform"
CONF_HOST = "host"
CONF_HOSTS = "hosts"

View File

@ -3,14 +3,14 @@ Helper methods for components within Home Assistant.
"""
from datetime import datetime
from homeassistant import NoEntitySpecifiedError
from homeassistant.loader import get_component
from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, STATE_ON, STATE_OFF, CONF_PLATFORM,
CONF_TYPE, DEVICE_DEFAULT_NAME)
from homeassistant.const import ATTR_ENTITY_ID, CONF_PLATFORM
from homeassistant.util import ensure_unique_string, slugify
# Deprecated 3/5/2015 - Moved to homeassistant.helpers.device
# pylint: disable=unused-import
from .device import Device, ToggleDevice # noqa
def generate_entity_id(entity_id_format, name, current_ids=None, hass=None):
""" Generate a unique entity ID based on given entity IDs or used ids. """
@ -101,15 +101,6 @@ def config_per_platform(config, domain, logger):
platform_type = platform_config.get(CONF_PLATFORM)
# DEPRECATED, still supported for now.
if platform_type is None:
platform_type = platform_config.get(CONF_TYPE)
if platform_type is not None:
logger.warning((
'Please update your config for {}.{} to use "platform" '
'instead of "type"').format(domain, platform_type))
if platform_type is None:
logger.warning('No platform specified for %s', config_key)
break
@ -118,169 +109,3 @@ def config_per_platform(config, domain, logger):
found += 1
config_key = "{} {}".format(domain, found)
def platform_devices_from_config(config, domain, hass,
entity_id_format, logger):
""" Parses the config for specified domain.
Loads different platforms and retrieve domains. """
devices = []
for p_type, p_config in config_per_platform(config, domain, logger):
platform = get_component('{}.{}'.format(domain, p_type))
if platform is None:
logger.error("Unknown %s type specified: %s", domain, p_type)
else:
try:
p_devices = platform.get_devices(hass, p_config)
except AttributeError:
# DEPRECATED, still supported for now
logger.warning(
'Platform %s should migrate to use the method get_devices',
p_type)
if domain == 'light':
p_devices = platform.get_lights(hass, p_config)
elif domain == 'switch':
p_devices = platform.get_switches(hass, p_config)
else:
raise
logger.info("Found %d %s %ss", len(p_devices), p_type, domain)
devices.extend(p_devices)
# Setup entity IDs for each device
device_dict = {}
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
if name == DEVICE_DEFAULT_NAME:
no_name_count += 1
name = "{} {}".format(domain, no_name_count)
entity_id = generate_entity_id(
entity_id_format, name, device_dict.keys())
device.entity_id = entity_id
device_dict[entity_id] = device
return device_dict
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. """
return "{}.{}".format(self.__class__, id(self))
@property
def name(self):
""" Returns the name of the device. """
return self.get_name()
@property
def state(self):
""" Returns the state of the device. """
return self.get_state()
@property
def state_attributes(self):
""" Returns the state attributes. """
return {}
# DEPRECATION NOTICE:
# Device is moving from getters to properties.
# For now the new properties will call the old functions
# This will be removed in the future.
def get_name(self):
""" Returns the name of the device if any. """
return DEVICE_DEFAULT_NAME
def get_state(self):
""" Returns state of the device. """
return "Unknown"
def get_state_attributes(self):
""" Returns optional state attributes. """
return None
def update(self):
""" Retrieve latest state from the real device. """
pass
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))
if force_refresh:
self.update()
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. """
# pylint: disable=no-self-use
@property
def state(self):
""" Returns the state. """
return STATE_ON if self.is_on else STATE_OFF
@property
def is_on(self):
""" True if device is on. """
return False
def turn_on(self, **kwargs):
""" Turn the device on. """
pass
def turn_off(self, **kwargs):
""" Turn the device off. """
pass

View File

@ -0,0 +1,120 @@
"""
homeassistant.helpers.device
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides ABC for devices in HA.
"""
from homeassistant import NoEntitySpecifiedError
from homeassistant.const import (
ATTR_FRIENDLY_NAME, STATE_ON, STATE_OFF, DEVICE_DEFAULT_NAME)
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. """
return "{}.{}".format(self.__class__, id(self))
@property
def name(self):
""" Returns the name of the device. """
return self.get_name()
@property
def state(self):
""" Returns the state of the device. """
return self.get_state()
@property
def state_attributes(self):
""" Returns the state attributes. """
return {}
# DEPRECATION NOTICE:
# Device is moving from getters to properties.
# For now the new properties will call the old functions
# This will be removed in the future.
def get_name(self):
""" Returns the name of the device if any. """
return DEVICE_DEFAULT_NAME
def get_state(self):
""" Returns state of the device. """
return "Unknown"
def get_state_attributes(self):
""" Returns optional state attributes. """
return None
def update(self):
""" Retrieve latest state from the real device. """
pass
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))
if force_refresh:
self.update()
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. """
# pylint: disable=no-self-use
@property
def state(self):
""" Returns the state. """
return STATE_ON if self.is_on else STATE_OFF
@property
def is_on(self):
""" True if device is on. """
return False
def turn_on(self, **kwargs):
""" Turn the device on. """
pass
def turn_off(self, **kwargs):
""" Turn the device off. """
pass

View File

@ -2,13 +2,15 @@
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.helpers import (
generate_entity_id, config_per_platform, extract_entity_ids)
from homeassistant.components import group, discovery
from homeassistant.const import ATTR_ENTITY_ID
class DeviceComponent(object):
# pylint: disable=too-many-instance-attributes
# pylint: disable=too-many-arguments,too-few-public-methods
# pylint: disable=too-many-arguments
"""
Helper class that will help a device component manage its devices.
"""
@ -52,6 +54,18 @@ class DeviceComponent(object):
discovery.listen(self.hass, self.discovery_platforms.keys(),
self._device_discovered)
def extract_from_service(self, service):
"""
Takes a service and extracts all known devices.
Will return all if no entity IDs given in service.
"""
if ATTR_ENTITY_ID not in service.data:
return self.devices.values()
else:
return [self.devices[entity_id] for entity_id
in extract_entity_ids(self.hass, service)
if entity_id in self.devices]
def _update_device_states(self, now):
""" Update the states of all the lights. """
self.logger.info("Updating %s states", self.domain)

View File

@ -1,5 +1,6 @@
# required
# required for Home Assistant core
requests>=2.0
pyyaml>=3.11
# optional, needed for specific components
@ -12,17 +13,17 @@ pyephem>=3.7
# lights.hue
phue>=0.8
# chromecast
# media_player.cast
pychromecast>=0.5
# keyboard
pyuserinput>=0.1.9
# switch.tellstick, tellstick_sensor
# switch.tellstick, sensor.tellstick
tellcore-py>=1.0.4
# device_tracker.nmap
python-libnmap
python-libnmap>=0.6.2
# notify.pushbullet
pushbullet.py>=0.7.1
@ -33,5 +34,5 @@ python-nest>=2.1
# z-wave
pydispatcher>=2.0.5
# pyyaml
pyyaml
# sensor.systemmonitor
psutil>=2.2.1

View File

@ -7,7 +7,7 @@ Helper method for writing tests.
import os
import homeassistant as ha
from homeassistant.helpers import ToggleDevice
from homeassistant.helpers.device import ToggleDevice
from homeassistant.const import STATE_ON, STATE_OFF, DEVICE_DEFAULT_NAME

View File

@ -11,7 +11,7 @@ import os
import homeassistant.loader as loader
import homeassistant.util as util
from homeassistant.const import (
ATTR_ENTITY_ID, STATE_ON, STATE_OFF, CONF_TYPE,
ATTR_ENTITY_ID, STATE_ON, STATE_OFF, CONF_PLATFORM,
SERVICE_TURN_ON, SERVICE_TURN_OFF)
import homeassistant.components.light as light
@ -101,7 +101,7 @@ class TestLight(unittest.TestCase):
platform.init()
self.assertTrue(
light.setup(self.hass, {light.DOMAIN: {CONF_TYPE: 'test'}}))
light.setup(self.hass, {light.DOMAIN: {CONF_PLATFORM: 'test'}}))
dev1, dev2, dev3 = platform.DEVICES
@ -226,7 +226,7 @@ class TestLight(unittest.TestCase):
user_file.write('I,WILL,NOT,WORK\n')
self.assertFalse(light.setup(
self.hass, {light.DOMAIN: {CONF_TYPE: 'test'}}
self.hass, {light.DOMAIN: {CONF_PLATFORM: 'test'}}
))
def test_light_profiles(self):
@ -241,7 +241,7 @@ class TestLight(unittest.TestCase):
user_file.write('test,.4,.6,100\n')
self.assertTrue(light.setup(
self.hass, {light.DOMAIN: {CONF_TYPE: 'test'}}
self.hass, {light.DOMAIN: {CONF_PLATFORM: 'test'}}
))
dev1, dev2, dev3 = platform.DEVICES

View File

@ -1,8 +1,8 @@
"""
tests.test_component_chromecast
tests.test_component_media_player
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Tests Chromecast component.
Tests media_player component.
"""
# pylint: disable=too-many-public-methods,protected-access
import logging
@ -12,28 +12,27 @@ import homeassistant as ha
from homeassistant.const import (
SERVICE_TURN_OFF, SERVICE_VOLUME_UP, SERVICE_VOLUME_DOWN,
SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PAUSE,
SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREV_TRACK, ATTR_ENTITY_ID,
CONF_HOSTS)
import homeassistant.components.chromecast as chromecast
SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREV_TRACK, ATTR_ENTITY_ID)
import homeassistant.components.media_player as media_player
from helpers import mock_service
def setUpModule(): # pylint: disable=invalid-name
""" Setup to ignore chromecast errors. """
""" Setup to ignore media_player errors. """
logging.disable(logging.CRITICAL)
class TestChromecast(unittest.TestCase):
""" Test the chromecast module. """
class TestMediaPlayer(unittest.TestCase):
""" Test the media_player module. """
def setUp(self): # pylint: disable=invalid-name
self.hass = ha.HomeAssistant()
self.test_entity = chromecast.ENTITY_ID_FORMAT.format('living_room')
self.hass.states.set(self.test_entity, chromecast.STATE_NO_APP)
self.test_entity = media_player.ENTITY_ID_FORMAT.format('living_room')
self.hass.states.set(self.test_entity, media_player.STATE_NO_APP)
self.test_entity2 = chromecast.ENTITY_ID_FORMAT.format('bedroom')
self.hass.states.set(self.test_entity2, "Youtube")
self.test_entity2 = media_player.ENTITY_ID_FORMAT.format('bedroom')
self.hass.states.set(self.test_entity2, "YouTube")
def tearDown(self): # pylint: disable=invalid-name
""" Stop down stuff we started. """
@ -41,33 +40,33 @@ class TestChromecast(unittest.TestCase):
def test_is_on(self):
""" Test is_on method. """
self.assertFalse(chromecast.is_on(self.hass, self.test_entity))
self.assertTrue(chromecast.is_on(self.hass, self.test_entity2))
self.assertFalse(media_player.is_on(self.hass, self.test_entity))
self.assertTrue(media_player.is_on(self.hass, self.test_entity2))
def test_services(self):
"""
Test if the call service methods conver to correct service calls.
"""
services = {
SERVICE_TURN_OFF: chromecast.turn_off,
SERVICE_VOLUME_UP: chromecast.volume_up,
SERVICE_VOLUME_DOWN: chromecast.volume_down,
SERVICE_MEDIA_PLAY_PAUSE: chromecast.media_play_pause,
SERVICE_MEDIA_PLAY: chromecast.media_play,
SERVICE_MEDIA_PAUSE: chromecast.media_pause,
SERVICE_MEDIA_NEXT_TRACK: chromecast.media_next_track,
SERVICE_MEDIA_PREV_TRACK: chromecast.media_prev_track
SERVICE_TURN_OFF: media_player.turn_off,
SERVICE_VOLUME_UP: media_player.volume_up,
SERVICE_VOLUME_DOWN: media_player.volume_down,
SERVICE_MEDIA_PLAY_PAUSE: media_player.media_play_pause,
SERVICE_MEDIA_PLAY: media_player.media_play,
SERVICE_MEDIA_PAUSE: media_player.media_pause,
SERVICE_MEDIA_NEXT_TRACK: media_player.media_next_track,
SERVICE_MEDIA_PREV_TRACK: media_player.media_prev_track
}
for service_name, service_method in services.items():
calls = mock_service(self.hass, chromecast.DOMAIN, service_name)
calls = mock_service(self.hass, media_player.DOMAIN, service_name)
service_method(self.hass)
self.hass.pool.block_till_done()
self.assertEqual(1, len(calls))
call = calls[-1]
self.assertEqual(chromecast.DOMAIN, call.domain)
self.assertEqual(media_player.DOMAIN, call.domain)
self.assertEqual(service_name, call.service)
service_method(self.hass, self.test_entity)
@ -75,7 +74,7 @@ class TestChromecast(unittest.TestCase):
self.assertEqual(2, len(calls))
call = calls[-1]
self.assertEqual(chromecast.DOMAIN, call.domain)
self.assertEqual(media_player.DOMAIN, call.domain)
self.assertEqual(service_name, call.service)
self.assertEqual(self.test_entity,
call.data.get(ATTR_ENTITY_ID))