Merge remote-tracking branch 'upstream/dev' into edimax_smart_plug
Conflicts: requirements.txtpull/229/head
commit
a99484ebc8
|
@ -1,24 +1,12 @@
|
|||
[submodule "homeassistant/external/pynetgear"]
|
||||
path = homeassistant/external/pynetgear
|
||||
url = https://github.com/balloob/pynetgear.git
|
||||
[submodule "homeassistant/external/pywemo"]
|
||||
path = homeassistant/external/pywemo
|
||||
url = https://github.com/balloob/pywemo.git
|
||||
[submodule "homeassistant/external/netdisco"]
|
||||
path = homeassistant/external/netdisco
|
||||
url = https://github.com/balloob/netdisco.git
|
||||
[submodule "homeassistant/external/noop"]
|
||||
path = homeassistant/external/noop
|
||||
url = https://github.com/balloob/noop.git
|
||||
[submodule "homeassistant/components/frontend/www_static/polymer/home-assistant-js"]
|
||||
path = homeassistant/components/frontend/www_static/polymer/home-assistant-js
|
||||
url = https://github.com/balloob/home-assistant-js.git
|
||||
[submodule "homeassistant/external/vera"]
|
||||
path = homeassistant/external/vera
|
||||
url = https://github.com/jamespcole/home-assistant-vera-api.git
|
||||
[submodule "homeassistant/external/nzbclients"]
|
||||
path = homeassistant/external/nzbclients
|
||||
url = https://github.com/jamespcole/home-assistant-nzb-clients.git
|
||||
[submodule "homeassistant/external/pymysensors"]
|
||||
path = homeassistant/external/pymysensors
|
||||
url = https://github.com/theolind/pymysensors
|
||||
[submodule "homeassistant/components/frontend/www_static/home-assistant-polymer"]
|
||||
path = homeassistant/components/frontend/www_static/home-assistant-polymer
|
||||
url = https://github.com/balloob/home-assistant-polymer.git
|
||||
|
|
|
@ -5,8 +5,11 @@ import sys
|
|||
import os
|
||||
import argparse
|
||||
import subprocess
|
||||
import importlib
|
||||
|
||||
DEPENDENCIES = ['requests>=2.0', 'pyyaml>=3.11', 'pytz>=2015.2']
|
||||
IS_VIRTUAL = (getattr(sys, 'base_prefix', sys.prefix) != sys.prefix or
|
||||
hasattr(sys, 'real_prefix'))
|
||||
|
||||
|
||||
def validate_python():
|
||||
|
@ -18,18 +21,33 @@ def validate_python():
|
|||
sys.exit()
|
||||
|
||||
|
||||
def ensure_pip():
|
||||
""" Validate pip is installed so we can install packages on demand. """
|
||||
if importlib.find_loader('pip') is None:
|
||||
print("Your Python installation did not bundle 'pip'")
|
||||
print("Home Assistant requires 'pip' to be installed.")
|
||||
print("Please install pip: "
|
||||
"https://pip.pypa.io/en/latest/installing.html")
|
||||
sys.exit()
|
||||
|
||||
|
||||
# Copy of homeassistant.util.package because we can't import yet
|
||||
def install_package(package):
|
||||
"""Install a package on PyPi. Accepts pip compatible package strings.
|
||||
Return boolean if install successfull."""
|
||||
args = ['python3', '-m', 'pip', 'install', '--quiet', package]
|
||||
if sys.base_prefix == sys.prefix:
|
||||
args = [sys.executable, '-m', 'pip', 'install', '--quiet', package]
|
||||
if not IS_VIRTUAL:
|
||||
args.append('--user')
|
||||
return not subprocess.call(args)
|
||||
try:
|
||||
return 0 == subprocess.call(args)
|
||||
except subprocess.SubprocessError:
|
||||
return False
|
||||
|
||||
|
||||
def validate_dependencies():
|
||||
""" Validate all dependencies that HA uses. """
|
||||
ensure_pip()
|
||||
|
||||
print("Validating dependencies...")
|
||||
import_fail = False
|
||||
|
||||
|
@ -118,6 +136,9 @@ def main():
|
|||
validate_python()
|
||||
validate_dependencies()
|
||||
|
||||
# Windows needs this to pick up new modules
|
||||
importlib.invalidate_caches()
|
||||
|
||||
bootstrap = ensure_path_and_load_bootstrap()
|
||||
|
||||
validate_git_submodules()
|
||||
|
@ -128,11 +149,10 @@ def main():
|
|||
config_path = ensure_config_path(config_dir)
|
||||
|
||||
if args.demo_mode:
|
||||
from homeassistant.components import http, demo
|
||||
from homeassistant.components import frontend, demo
|
||||
|
||||
# Demo mode only requires http and demo components.
|
||||
hass = bootstrap.from_config_dict({
|
||||
http.DOMAIN: {},
|
||||
frontend.DOMAIN: {},
|
||||
demo.DOMAIN: {}
|
||||
})
|
||||
else:
|
||||
|
|
|
@ -43,6 +43,7 @@ from homeassistant.components.device_tracker import DOMAIN
|
|||
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
REQUIREMENTS = ['pynetgear>=0.1']
|
||||
|
||||
|
||||
def get_scanner(hass, config):
|
||||
|
@ -64,22 +65,10 @@ class NetgearDeviceScanner(object):
|
|||
""" This class queries a Netgear wireless router using the SOAP-API. """
|
||||
|
||||
def __init__(self, host, username, password):
|
||||
import pynetgear
|
||||
|
||||
self.last_results = []
|
||||
|
||||
try:
|
||||
# Pylint does not play nice if not every folders has an __init__.py
|
||||
# pylint: disable=no-name-in-module, import-error
|
||||
import homeassistant.external.pynetgear.pynetgear as pynetgear
|
||||
except ImportError:
|
||||
_LOGGER.exception(
|
||||
("Failed to import pynetgear. "
|
||||
"Did you maybe not run `git submodule init` "
|
||||
"and `git submodule update`?"))
|
||||
|
||||
self.success_init = False
|
||||
|
||||
return
|
||||
|
||||
self._api = pynetgear.Netgear(host, username, password)
|
||||
self.lock = threading.Lock()
|
||||
|
||||
|
|
|
@ -12,9 +12,6 @@ loaded before the EVENT_PLATFORM_DISCOVERED is fired.
|
|||
import logging
|
||||
import threading
|
||||
|
||||
# pylint: disable=no-name-in-module, import-error
|
||||
import homeassistant.external.netdisco.netdisco.const as services
|
||||
|
||||
from homeassistant import bootstrap
|
||||
from homeassistant.const import (
|
||||
EVENT_HOMEASSISTANT_START, EVENT_PLATFORM_DISCOVERED,
|
||||
|
@ -22,14 +19,20 @@ from homeassistant.const import (
|
|||
|
||||
DOMAIN = "discovery"
|
||||
DEPENDENCIES = []
|
||||
REQUIREMENTS = ['zeroconf>=0.16.0']
|
||||
REQUIREMENTS = ['netdisco>=0.1']
|
||||
|
||||
SCAN_INTERVAL = 300 # seconds
|
||||
|
||||
# Next 3 lines for now a mirror from netdisco.const
|
||||
# Should setup a mapping netdisco.const -> own constants
|
||||
SERVICE_WEMO = 'belkin_wemo'
|
||||
SERVICE_HUE = 'philips_hue'
|
||||
SERVICE_CAST = 'google_cast'
|
||||
|
||||
SERVICE_HANDLERS = {
|
||||
services.BELKIN_WEMO: "switch",
|
||||
services.GOOGLE_CAST: "media_player",
|
||||
services.PHILIPS_HUE: "light",
|
||||
SERVICE_WEMO: "switch",
|
||||
SERVICE_CAST: "media_player",
|
||||
SERVICE_HUE: "light",
|
||||
}
|
||||
|
||||
|
||||
|
@ -56,14 +59,7 @@ 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
|
||||
from netdisco.service import DiscoveryService
|
||||
|
||||
# Disable zeroconf logging, it spams
|
||||
logging.getLogger('zeroconf').setLevel(logging.CRITICAL)
|
||||
|
|
|
@ -55,7 +55,7 @@ def _handle_get_root(handler, path_match, data):
|
|||
handler.end_headers()
|
||||
|
||||
if handler.server.development:
|
||||
app_url = "polymer/home-assistant.html"
|
||||
app_url = "home-assistant-polymer/src/home-assistant.html"
|
||||
else:
|
||||
app_url = "frontend-{}.html".format(version.VERSION)
|
||||
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
""" DO NOT MODIFY. Auto-generated by build_frontend script """
|
||||
VERSION = "85f0078ea394a12dd95395799e345c83"
|
||||
VERSION = "ccfe7497d635ab4df3e6943b05adbd9b"
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1 @@
|
|||
Subproject commit 5a3fcc970b30d640e6a370b6f20904a745f69659
|
|
@ -1,46 +0,0 @@
|
|||
{
|
||||
"name": "Home Assistant",
|
||||
"version": "0.1.0",
|
||||
"authors": [
|
||||
"Paulus Schoutsen <Paulus@PaulusSchoutsen.nl>"
|
||||
],
|
||||
"main": "splash-login.html",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"ignore": [
|
||||
"bower_components"
|
||||
],
|
||||
"devDependencies": {
|
||||
"polymer": "Polymer/polymer#^1.0.0",
|
||||
"webcomponentsjs": "Polymer/webcomponentsjs#^0.7",
|
||||
"paper-header-panel": "PolymerElements/paper-header-panel#^1.0.0",
|
||||
"paper-toolbar": "PolymerElements/paper-toolbar#^1.0.0",
|
||||
"paper-menu": "PolymerElements/paper-menu#^1.0.0",
|
||||
"iron-input": "PolymerElements/iron-input#^1.0.0",
|
||||
"iron-icons": "PolymerElements/iron-icons#^1.0.0",
|
||||
"iron-image": "PolymerElements/iron-image#^1.0.0",
|
||||
"paper-toast": "PolymerElements/paper-toast#^1.0.0",
|
||||
"paper-dialog": "PolymerElements/paper-dialog#^1.0.0",
|
||||
"paper-dialog-scrollable": "polymerelements/paper-dialog-scrollable#^1.0.0",
|
||||
"paper-spinner": "PolymerElements/paper-spinner#^1.0.0",
|
||||
"paper-button": "PolymerElements/paper-button#^1.0.0",
|
||||
"paper-input": "PolymerElements/paper-input#^1.0.0",
|
||||
"paper-toggle-button": "PolymerElements/paper-toggle-button#^1.0.0",
|
||||
"paper-icon-button": "PolymerElements/paper-icon-button#^1.0.0",
|
||||
"paper-item": "PolymerElements/paper-item#^1.0.0",
|
||||
"paper-slider": "PolymerElements/paper-slider#^1.0.0",
|
||||
"paper-checkbox": "PolymerElements/paper-checkbox#^1.0.0",
|
||||
"paper-drawer-panel": "PolymerElements/paper-drawer-panel#^1.0.0",
|
||||
"paper-scroll-header-panel": "polymerelements/paper-scroll-header-panel#^1.0.0",
|
||||
"google-apis": "GoogleWebComponents/google-apis#0.8-preview",
|
||||
"moment": "^2.10.3",
|
||||
"layout": "Polymer/layout",
|
||||
"paper-styles": "polymerelements/paper-styles#^1.0.0",
|
||||
"lodash": "~3.9.3",
|
||||
"pikaday": "~1.3.2"
|
||||
},
|
||||
"resolutions": {
|
||||
"polymer": "^1.0.0",
|
||||
"webcomponentsjs": "^0.7.0"
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
<link rel='import' href='../bower_components/polymer/polymer.html'>
|
||||
|
||||
<link rel='import' href='./state-card-display.html'>
|
||||
<link rel='import' href='../components/state-info.html'>
|
||||
|
||||
<dom-module id='state-card-configurator'>
|
||||
<template>
|
||||
<state-card-display state-obj='[[stateObj]]'></state-card-display>
|
||||
|
||||
<!-- pre load the image so the dialog is rendered the proper size -->
|
||||
<template is='dom-if' if='[[stateObj.attributes.description_image]]'>
|
||||
<img hidden src='[[stateObj.attributes.description_image]]' />
|
||||
</template>
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
Polymer({
|
||||
is: 'state-card-configurator',
|
||||
|
||||
properties: {
|
||||
stateObj: {
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
});
|
||||
})();
|
||||
</script>
|
|
@ -1,50 +0,0 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
|
||||
<link rel="import" href="state-card-display.html">
|
||||
<link rel="import" href="state-card-toggle.html">
|
||||
<link rel="import" href="state-card-thermostat.html">
|
||||
<link rel="import" href="state-card-configurator.html">
|
||||
<link rel="import" href="state-card-scene.html">
|
||||
<link rel="import" href="state-card-media_player.html">
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var uiUtil = window.hass.uiUtil;
|
||||
|
||||
Polymer({
|
||||
is: 'state-card-content',
|
||||
|
||||
properties: {
|
||||
stateObj: {
|
||||
type: Object,
|
||||
observer: 'stateObjChanged',
|
||||
}
|
||||
},
|
||||
|
||||
stateObjChanged: function(newVal, oldVal) {
|
||||
var root = Polymer.dom(this);
|
||||
|
||||
if (!newVal) {
|
||||
if (root.lastChild) {
|
||||
root.removeChild(root.lastChild);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var newCardType = uiUtil.stateCardType(newVal);
|
||||
|
||||
if (!oldVal || uiUtil.stateCardType(oldVal) != newCardType) {
|
||||
if (root.lastChild) {
|
||||
root.removeChild(root.lastChild);
|
||||
}
|
||||
|
||||
var stateCard = document.createElement("state-card-" + newCardType);
|
||||
stateCard.stateObj = newVal;
|
||||
root.appendChild(stateCard);
|
||||
} else {
|
||||
root.lastChild.stateObj = newVal;
|
||||
}
|
||||
},
|
||||
});
|
||||
})();
|
||||
</script>
|
|
@ -1,36 +0,0 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
|
||||
<link rel="import" href="../components/state-info.html">
|
||||
|
||||
<dom-module id="state-card-display">
|
||||
<style>
|
||||
.state {
|
||||
margin-left: 16px;
|
||||
text-transform: capitalize;
|
||||
font-weight: 300;
|
||||
font-size: 1.3rem;
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div class='horizontal justified layout'>
|
||||
<state-info state-obj="[[stateObj]]"></state-info>
|
||||
<div class='state'>[[stateObj.stateDisplay]]</div>
|
||||
</div>
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
Polymer({
|
||||
is: 'state-card-display',
|
||||
|
||||
properties: {
|
||||
stateObj: {
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
});
|
||||
})();
|
||||
</script>
|
|
@ -1,91 +0,0 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
|
||||
<link rel="import" href="../components/state-info.html">
|
||||
|
||||
<dom-module id="state-card-media_player">
|
||||
<style>
|
||||
:host {
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.state {
|
||||
margin-left: 16px;
|
||||
text-align: right;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.main-text {
|
||||
white-space: nowrap;
|
||||
overflow-x: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-transform: capitalize;
|
||||
font-weight: 300;
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.secondary-text {
|
||||
color: darkgrey;
|
||||
margin-top: -2px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div class='horizontal justified layout'>
|
||||
<state-info state-obj="[[stateObj]]"></state-info>
|
||||
<div class='state'>
|
||||
<div class='main-text'>[[computePrimaryText(stateObj, isPlaying)]]</div>
|
||||
<div class='secondary-text'>[[computeSecondaryText(stateObj, isPlaying)]]</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var PLAYING_STATES = ['playing', 'paused'];
|
||||
Polymer({
|
||||
is: 'state-card-media_player',
|
||||
|
||||
properties: {
|
||||
stateObj: {
|
||||
type: Object,
|
||||
},
|
||||
|
||||
isPlaying: {
|
||||
type: Boolean,
|
||||
computed: 'computeIsPlaying(stateObj)',
|
||||
},
|
||||
},
|
||||
|
||||
computeIsPlaying: function(stateObj) {
|
||||
return PLAYING_STATES.indexOf(stateObj.state) !== -1;
|
||||
},
|
||||
|
||||
computePrimaryText: function(stateObj, isPlaying) {
|
||||
return isPlaying ? stateObj.attributes.media_title : stateObj.stateDisplay;
|
||||
},
|
||||
|
||||
computeSecondaryText: function(stateObj, isPlaying) {
|
||||
var text;
|
||||
|
||||
if (stateObj.attributes.media_content_type == 'music') {
|
||||
return stateObj.attributes.media_artist;
|
||||
|
||||
} else if (stateObj.attributes.media_content_type == 'tvshow') {
|
||||
text = stateObj.attributes.media_series_title;
|
||||
|
||||
if (stateObj.attributes.media_season && stateObj.attributes.media_episode) {
|
||||
text += ' S' + stateObj.attributes.media_season + 'E' + stateObj.attributes.media_episode;
|
||||
}
|
||||
return text;
|
||||
|
||||
} else if (stateObj.attributes.app_name) {
|
||||
return stateObj.attributes.app_name;
|
||||
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
});
|
||||
})();
|
||||
</script>
|
|
@ -1,39 +0,0 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
|
||||
<link rel="import" href="./state-card-display.html">
|
||||
<link rel="import" href="./state-card-toggle.html">
|
||||
|
||||
<dom-module id="state-card-scene">
|
||||
<template>
|
||||
<template is='dom-if' if='[[allowToggle]]'>
|
||||
<state-card-toggle state-obj="[[stateObj]]"></state-card-toggle>
|
||||
</template>
|
||||
<template is='dom-if' if='[[!allowToggle]]'>
|
||||
<state-card-display state-obj="[[stateObj]]"></state-card-display>
|
||||
</template>
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
Polymer({
|
||||
is: 'state-card-scene',
|
||||
|
||||
properties: {
|
||||
stateObj: {
|
||||
type: Object,
|
||||
},
|
||||
|
||||
allowToggle: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
computed: 'computeAllowToggle(stateObj)',
|
||||
},
|
||||
},
|
||||
|
||||
computeAllowToggle: function(stateObj) {
|
||||
return stateObj.state === 'off' || stateObj.attributes.active_requested;
|
||||
},
|
||||
});
|
||||
})();
|
||||
</script>
|
|
@ -1,57 +0,0 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
|
||||
<link rel="import" href="../components/state-info.html">
|
||||
|
||||
<dom-module id="state-card-thermostat">
|
||||
<style>
|
||||
:host {
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.state {
|
||||
margin-left: 16px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.target {
|
||||
text-transform: capitalize;
|
||||
font-weight: 300;
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.current {
|
||||
color: darkgrey;
|
||||
margin-top: -2px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div class='horizontal justified layout'>
|
||||
<state-info state-obj="[[stateObj]]"></state-info>
|
||||
<div class='state'>
|
||||
<div class='target'>[[stateObj.stateDisplay]]</div>
|
||||
|
||||
<div class='current'>
|
||||
<span>Currently: </span>
|
||||
<span>[[stateObj.attributes.current_temperature]]</span>
|
||||
<span> </span>
|
||||
<span>[[stateObj.attributes.unit_of_measurement]]</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
Polymer({
|
||||
is: 'state-card-thermostat',
|
||||
|
||||
properties: {
|
||||
stateObj: {
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
});
|
||||
})();
|
||||
</script>
|
|
@ -1,95 +0,0 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="../bower_components/paper-toggle-button/paper-toggle-button.html">
|
||||
|
||||
<link rel="import" href="../components/state-info.html">
|
||||
|
||||
<dom-module id="state-card-toggle">
|
||||
<style>
|
||||
paper-toggle-button {
|
||||
margin-left: 16px;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div class='horizontal justified layout'>
|
||||
<state-info state-obj="[[stateObj]]"></state-info>
|
||||
|
||||
<paper-toggle-button class='self-center'
|
||||
checked="[[toggleChecked]]"
|
||||
on-change="toggleChanged"
|
||||
on-tap="toggleTapped">
|
||||
</paper-toggle-button>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var serviceActions = window.hass.serviceActions;
|
||||
|
||||
Polymer({
|
||||
is: 'state-card-toggle',
|
||||
|
||||
properties: {
|
||||
stateObj: {
|
||||
type: Object,
|
||||
observer: 'stateObjChanged',
|
||||
},
|
||||
|
||||
toggleChecked: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
},
|
||||
|
||||
ready: function() {
|
||||
this.forceStateChange = this.forceStateChange.bind(this);
|
||||
this.forceStateChange();
|
||||
},
|
||||
|
||||
toggleTapped: function(ev) {
|
||||
ev.stopPropagation();
|
||||
},
|
||||
|
||||
toggleChanged: function(ev) {
|
||||
var newVal = ev.target.checked;
|
||||
|
||||
if(newVal && this.stateObj.state === "off") {
|
||||
this.turn_on();
|
||||
} else if(!newVal && this.stateObj.state === "on") {
|
||||
this.turn_off();
|
||||
}
|
||||
},
|
||||
|
||||
stateObjChanged: function(newVal) {
|
||||
if (newVal) {
|
||||
this.updateToggle(newVal);
|
||||
}
|
||||
},
|
||||
|
||||
updateToggle: function(stateObj) {
|
||||
this.toggleChecked = stateObj && stateObj.state === "on";
|
||||
},
|
||||
|
||||
forceStateChange: function() {
|
||||
this.updateToggle(this.stateObj);
|
||||
},
|
||||
|
||||
turn_on: function() {
|
||||
// We call updateToggle 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.callTurnOn(this.stateObj.entityId).then(this.forceStateChange);
|
||||
},
|
||||
|
||||
turn_off: function() {
|
||||
// We call updateToggle 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.callTurnOff(this.stateObj.entityId).then(this.forceStateChange);
|
||||
},
|
||||
});
|
||||
})();
|
||||
</script>
|
|
@ -1,55 +0,0 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
|
||||
<link rel="import" href="state-card-content.html">
|
||||
|
||||
<dom-module id="state-card">
|
||||
<style>
|
||||
:host {
|
||||
border-radius: 2px;
|
||||
box-shadow: rgba(0, 0, 0, 0.098) 0px 2px 4px, rgba(0, 0, 0, 0.098) 0px 0px 3px;
|
||||
transition: all 0.30s ease-out;
|
||||
|
||||
position: relative;
|
||||
background-color: white;
|
||||
padding: 16px;
|
||||
width: 100%;
|
||||
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
|
||||
-ms-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<state-card-content state-obj="[[stateObj]]"></state-card-content>
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
(function(){
|
||||
var moreInfoActions = window.hass.moreInfoActions;
|
||||
|
||||
Polymer({
|
||||
is: 'state-card',
|
||||
|
||||
properties: {
|
||||
stateObj: {
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
|
||||
listeners: {
|
||||
'tap': 'cardTapped',
|
||||
},
|
||||
|
||||
cardTapped: function(ev) {
|
||||
ev.stopPropagation();
|
||||
this.async(moreInfoActions.selectEntity.bind(
|
||||
this, this.stateObj.entityId), 100);
|
||||
},
|
||||
});
|
||||
})();
|
||||
</script>
|
|
@ -1,25 +0,0 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
|
||||
<dom-module id="display-time">
|
||||
<template>[[computeTime(dateObj)]]</template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var uiUtil = window.hass.uiUtil;
|
||||
|
||||
Polymer({
|
||||
is: 'display-time',
|
||||
|
||||
properties: {
|
||||
dateObj: {
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
|
||||
computeTime: function(dateObj) {
|
||||
return dateObj ? uiUtil.formatTime(dateObj) : '';
|
||||
},
|
||||
});
|
||||
})();
|
||||
</script>
|
|
@ -1,37 +0,0 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
|
||||
<link rel="import" href="../bower_components/iron-icon/iron-icon.html">
|
||||
|
||||
<link rel="import" href="../resources/home-assistant-icons.html">
|
||||
|
||||
<dom-module id="domain-icon">
|
||||
<template>
|
||||
<iron-icon icon="[[computeIcon(domain, state)]]"></iron-icon>
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var uiUtil = window.hass.uiUtil;
|
||||
|
||||
Polymer({
|
||||
is: 'domain-icon',
|
||||
|
||||
properties: {
|
||||
domain: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
|
||||
state: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
},
|
||||
|
||||
computeIcon: function(domain, state) {
|
||||
return uiUtil.domainIcon(domain, state);
|
||||
},
|
||||
});
|
||||
})();
|
||||
</script>
|
|
@ -1,58 +0,0 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
|
||||
<dom-module id="entity-list">
|
||||
<style>
|
||||
ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
list-style: none;
|
||||
line-height: 2em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<ul>
|
||||
<template is='dom-repeat' items='[[entities]]' as='entity'>
|
||||
<li><a href='#' on-click='entitySelected'>[[entity.entityId]]</a></li>
|
||||
</template>
|
||||
</ul>
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var entityGetters = window.hass.entityGetters;
|
||||
|
||||
Polymer({
|
||||
is: 'entity-list',
|
||||
|
||||
behaviors: [nuclearObserver],
|
||||
|
||||
properties: {
|
||||
entities: {
|
||||
type: Array,
|
||||
bindNuclear: [
|
||||
entityGetters.entityMap,
|
||||
function(map) {
|
||||
return map.valueSeq().
|
||||
sortBy(function(entity) { return entity.entityId; })
|
||||
.toArray();
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
entitySelected: function(ev) {
|
||||
ev.preventDefault();
|
||||
this.fire('entity-selected', {entityId: ev.model.entity.entityId});
|
||||
},
|
||||
});
|
||||
})();
|
||||
</script>
|
|
@ -1,61 +0,0 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
|
||||
<dom-module id="events-list">
|
||||
<style>
|
||||
ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
list-style: none;
|
||||
line-height: 2em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<ul>
|
||||
<template is='dom-repeat' items='[[events]]' as='event'>
|
||||
<li>
|
||||
<a href='#' on-click='eventSelected'>{{event.event}}</a>
|
||||
<span> (</span><span>{{event.listener_count}}</span><span> listeners)</span>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var eventGetters = window.hass.eventGetters;
|
||||
|
||||
Polymer({
|
||||
is: 'events-list',
|
||||
|
||||
behaviors: [nuclearObserver],
|
||||
|
||||
properties: {
|
||||
events: {
|
||||
type: Array,
|
||||
bindNuclear: [
|
||||
eventGetters.entityMap,
|
||||
function(map) {
|
||||
return map.valueSeq()
|
||||
.sortBy(function(event) { return event.event; })
|
||||
.toArray();
|
||||
}
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
eventSelected: function(ev) {
|
||||
ev.preventDefault();
|
||||
this.fire('event-selected', {eventType: ev.model.event.event});
|
||||
},
|
||||
});
|
||||
})();
|
||||
</script>
|
|
@ -1,169 +0,0 @@
|
|||
<link rel='import' href='../bower_components/polymer/polymer.html'>
|
||||
|
||||
<dom-module id='ha-color-picker'>
|
||||
<style>
|
||||
canvas {
|
||||
cursor: crosshair;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<canvas width='[[width]]' height='[[height]]'></canvas>
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
|
||||
/**
|
||||
* Color-picker custom element
|
||||
* Originally created by bbrewer97202 (Ben Brewer). MIT Licensed.
|
||||
* https://github.com/bbrewer97202/color-picker-element
|
||||
*
|
||||
* Adapted to work with Polymer.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
/**
|
||||
* given red, green, blue values, return the equivalent hexidecimal value
|
||||
* base source: http://stackoverflow.com/a/5624139
|
||||
*/
|
||||
var componentToHex = function(c) {
|
||||
var hex = c.toString(16);
|
||||
return hex.length === 1 ? "0" + hex : hex;
|
||||
};
|
||||
|
||||
var rgbToHex = function(color) {
|
||||
return "#" + componentToHex(color.r) + componentToHex(color.g) +
|
||||
componentToHex(color.b);
|
||||
};
|
||||
|
||||
Polymer({
|
||||
is: 'ha-color-picker',
|
||||
|
||||
properties: {
|
||||
width: {
|
||||
type: Number,
|
||||
value: 300,
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
value: 300,
|
||||
},
|
||||
color: {
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
|
||||
listeners: {
|
||||
'mousedown': 'onMouseDown',
|
||||
'mouseup': 'onMouseUp',
|
||||
'touchstart': 'onTouchStart',
|
||||
'touchend': 'onTouchEnd',
|
||||
'tap': 'onTap',
|
||||
},
|
||||
|
||||
onMouseDown: function(e) {
|
||||
this.onMouseMove(e);
|
||||
this.addEventListener('mousemove', this.onMouseMove);
|
||||
},
|
||||
|
||||
onMouseUp: function(e) {
|
||||
this.removeEventListener('mousemove', this.onMouseMove);
|
||||
},
|
||||
|
||||
onTouchStart: function(e) {
|
||||
this.onTouchMove(e);
|
||||
this.addEventListener('touchmove', this.onTouchMove);
|
||||
},
|
||||
|
||||
onTouchEnd: function(e) {
|
||||
this.removeEventListener('touchmove', this.onTouchMove);
|
||||
},
|
||||
|
||||
onTap: function(e) {
|
||||
e.stopPropagation();
|
||||
},
|
||||
|
||||
onTouchMove: function(e) {
|
||||
var touch = e.touches[0];
|
||||
this.onColorSelect(e, {x: touch.clientX, y: touch.clientY});
|
||||
},
|
||||
|
||||
onMouseMove: function(e) {
|
||||
e.preventDefault();
|
||||
if (this.mouseMoveIsThrottled) {
|
||||
this.mouseMoveIsThrottled = false;
|
||||
this.onColorSelect(e);
|
||||
this.async(
|
||||
function() { this.mouseMoveIsThrottled = true; }.bind(this), 100);
|
||||
}
|
||||
},
|
||||
|
||||
onColorSelect: function(e, coords) {
|
||||
if (this.context) {
|
||||
coords = coords || this.relativeMouseCoordinates(e);
|
||||
var data = this.context.getImageData(coords.x, coords.y, 1, 1).data;
|
||||
|
||||
this.setColor({r: data[0], g: data[1], b: data[2]});
|
||||
}
|
||||
},
|
||||
|
||||
setColor: function(rgb) {
|
||||
//save calculated color
|
||||
this.color = {hex: rgbToHex(rgb), rgb: rgb};
|
||||
|
||||
this.fire('colorselected', {
|
||||
rgb: this.color.rgb,
|
||||
hex: this.color.hex
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* given a mouse click event, return x,y coordinates relative to the clicked target
|
||||
* @returns object with x, y values
|
||||
*/
|
||||
relativeMouseCoordinates: function(e) {
|
||||
var x = 0, y = 0;
|
||||
|
||||
if (this.canvas) {
|
||||
var rect = this.canvas.getBoundingClientRect();
|
||||
x = e.clientX - rect.left;
|
||||
y = e.clientY - rect.top;
|
||||
}
|
||||
|
||||
return {
|
||||
x: x,
|
||||
y: y
|
||||
};
|
||||
},
|
||||
|
||||
ready: function() {
|
||||
this.setColor = this.setColor.bind(this);
|
||||
this.mouseMoveIsThrottled = true;
|
||||
this.canvas = this.children[0];
|
||||
this.context = this.canvas.getContext('2d');
|
||||
|
||||
var colorGradient = this.context.createLinearGradient(0, 0, this.width, 0);
|
||||
colorGradient.addColorStop(0, "rgb(255,0,0)");
|
||||
colorGradient.addColorStop(0.16, "rgb(255,0,255)");
|
||||
colorGradient.addColorStop(0.32, "rgb(0,0,255)");
|
||||
colorGradient.addColorStop(0.48, "rgb(0,255,255)");
|
||||
colorGradient.addColorStop(0.64, "rgb(0,255,0)");
|
||||
colorGradient.addColorStop(0.80, "rgb(255,255,0)");
|
||||
colorGradient.addColorStop(1, "rgb(255,0,0)");
|
||||
this.context.fillStyle = colorGradient;
|
||||
this.context.fillRect(0, 0, this.width, this.height);
|
||||
|
||||
var bwGradient = this.context.createLinearGradient(0, 0, 0, this.height);
|
||||
bwGradient.addColorStop(0, "rgba(255,255,255,1)");
|
||||
bwGradient.addColorStop(0.5, "rgba(255,255,255,0)");
|
||||
bwGradient.addColorStop(0.5, "rgba(0,0,0,0)");
|
||||
bwGradient.addColorStop(1, "rgba(0,0,0,1)");
|
||||
|
||||
this.context.fillStyle = bwGradient;
|
||||
this.context.fillRect(0, 0, this.width, this.height);
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
})();
|
||||
</script>
|
|
@ -1,39 +0,0 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
|
||||
<link rel="import" href="../components/logbook-entry.html">
|
||||
|
||||
<dom-module id="ha-logbook">
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
padding: 16px;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<template is='dom-if' if='[[noEntries(entries)]]'>
|
||||
No logbook entries found.
|
||||
</template>
|
||||
<template is='dom-repeat' items="[[entries]]">
|
||||
<logbook-entry entry-obj="[[item]]"></logbook-entry>
|
||||
</template>
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
Polymer({
|
||||
is: 'ha-logbook',
|
||||
|
||||
properties: {
|
||||
entries: {
|
||||
type: Object,
|
||||
value: [],
|
||||
},
|
||||
},
|
||||
|
||||
noEntries: function(entries) {
|
||||
return !entries.length;
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
|
@ -1,247 +0,0 @@
|
|||
<link rel='import' href='../bower_components/polymer/polymer.html'>
|
||||
<link rel='import' href='../bower_components/layout/layout.html'>
|
||||
|
||||
<link rel='import' href='../bower_components/paper-header-panel/paper-header-panel.html'>
|
||||
<link rel='import' href='../bower_components/paper-toolbar/paper-toolbar.html'>
|
||||
<!--
|
||||
Too broken for now.
|
||||
<link rel='import' href='../bower_components/paper-menu/paper-menu.html'> -->
|
||||
<link rel='import' href='../bower_components/iron-icon/iron-icon.html'>
|
||||
<link rel='import' href='../bower_components/paper-item/paper-item.html'>
|
||||
<link rel='import' href='../bower_components/paper-item/paper-icon-item.html'>
|
||||
<link rel='import' href='../bower_components/paper-icon-button/paper-icon-button.html'>
|
||||
|
||||
<link rel='import' href='../components/stream-status.html'>
|
||||
|
||||
<dom-module id='ha-sidebar'>
|
||||
<style>
|
||||
.sidenav {
|
||||
background: #fafafa;
|
||||
box-shadow: 1px 0 1px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
|
||||
white-space: nowrap;
|
||||
|
||||
-ms-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
}
|
||||
|
||||
/*.sidenav paper-menu {
|
||||
--paper-menu-color: var(--secondary-text-color);
|
||||
--paper-menu-background-color: #fafafa;
|
||||
}*/
|
||||
|
||||
div.menu {
|
||||
color: var(--secondary-text-color);
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
paper-icon-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
paper-icon-item.selected {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
paper-icon-item.logout {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.divider {
|
||||
border-top: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.text {
|
||||
padding: 16px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.dev-tools {
|
||||
padding: 0 8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<paper-header-panel mode='scroll' class='sidenav fit'>
|
||||
<paper-toolbar>
|
||||
<!-- forces paper toolbar to style title appropriate -->
|
||||
<paper-icon-button hidden></paper-icon-button>
|
||||
<div class="title">Home Assistant</div>
|
||||
</paper-toolbar>
|
||||
<!-- <paper-menu id='menu' selected='{{menuSelected}}'
|
||||
selectable='[data-panel]' attr-for-selected='data-panel'> -->
|
||||
<div class='menu'>
|
||||
<paper-icon-item on-click='menuClicked' data-panel='states'>
|
||||
<iron-icon item-icon icon='apps'></iron-icon> States
|
||||
</paper-icon-item>
|
||||
|
||||
<template is='dom-repeat' items='{{possibleFilters}}'>
|
||||
<paper-icon-item on-click='menuClicked' data-panel$='[[filterType(item)]]'>
|
||||
<iron-icon item-icon icon='[[filterIcon(item)]]'></iron-icon>
|
||||
<span>[[filterName(item)]]</span>
|
||||
</paper-icon-item>
|
||||
</template>
|
||||
|
||||
<template is='dom-if' if='[[hasHistoryComponent]]'>
|
||||
<paper-icon-item on-click='menuClicked' data-panel='history'>
|
||||
<iron-icon item-icon icon='assessment'></iron-icon>
|
||||
History
|
||||
</paper-icon-item>
|
||||
</template>
|
||||
|
||||
<template is='dom-if' if='[[hasLogbookComponent]]'>
|
||||
<paper-icon-item on-click='menuClicked' data-panel='logbook'>
|
||||
<iron-icon item-icon icon='list'></iron-icon>
|
||||
Logbook
|
||||
</paper-icon-item>
|
||||
</template>
|
||||
|
||||
<paper-icon-item on-click='menuClicked' data-panel='logout' class='logout'>
|
||||
<iron-icon item-icon icon='exit-to-app'></iron-icon>
|
||||
Log Out
|
||||
</paper-icon-item>
|
||||
|
||||
<paper-item class='divider horizontal layout justified'>
|
||||
<div>Streaming updates</div>
|
||||
<stream-status></stream-status>
|
||||
</paper-item>
|
||||
|
||||
<div class='text label divider'>Developer Tools</div>
|
||||
<div class='dev-tools layout horizontal justified'>
|
||||
<paper-icon-button
|
||||
icon='settings-remote' data-panel='devService'
|
||||
on-click='handleDevClick'></paper-icon-button>
|
||||
<paper-icon-button
|
||||
icon='settings-ethernet' data-panel='devState'
|
||||
on-click='handleDevClick'></paper-icon-button>
|
||||
<paper-icon-button
|
||||
icon='settings-input-antenna' data-panel='devEvent'
|
||||
on-click='handleDevClick'></paper-icon-button>
|
||||
</div>
|
||||
<!-- </paper-menu> -->
|
||||
</div>
|
||||
</paper-header-panel>
|
||||
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var configGetters = window.hass.configGetters;
|
||||
var navigationGetters = window.hass.navigationGetters;
|
||||
|
||||
var authActions = window.hass.authActions;
|
||||
var navigationActions = window.hass.navigationActions;
|
||||
|
||||
var uiUtil = window.hass.uiUtil;
|
||||
var entityDomainFilters = window.hass.util.entityDomainFilters;
|
||||
|
||||
Polymer({
|
||||
is: 'ha-sidebar',
|
||||
|
||||
behaviors: [nuclearObserver],
|
||||
|
||||
properties: {
|
||||
menuSelected: {
|
||||
type: String,
|
||||
// observer: 'menuSelectedChanged',
|
||||
},
|
||||
|
||||
selected: {
|
||||
type: String,
|
||||
bindNuclear: navigationGetters.activePage,
|
||||
observer: 'selectedChanged',
|
||||
},
|
||||
|
||||
possibleFilters: {
|
||||
type: Array,
|
||||
value: [],
|
||||
bindNuclear: [
|
||||
navigationGetters.possibleEntityDomainFilters,
|
||||
function(domains) { return domains.toArray(); }
|
||||
],
|
||||
},
|
||||
|
||||
hasHistoryComponent: {
|
||||
type: Boolean,
|
||||
bindNuclear: configGetters.isComponentLoaded('history'),
|
||||
},
|
||||
|
||||
hasLogbookComponent: {
|
||||
type: Boolean,
|
||||
bindNuclear: configGetters.isComponentLoaded('logbook'),
|
||||
},
|
||||
},
|
||||
|
||||
// menuSelectedChanged: function(newVal) {
|
||||
// if (this.selected !== newVal) {
|
||||
// this.selectPanel(newVal);
|
||||
// }
|
||||
// },
|
||||
|
||||
selectedChanged: function(newVal) {
|
||||
// if (this.menuSelected !== newVal) {
|
||||
// this.menuSelected = newVal;
|
||||
// }
|
||||
var menuItems = this.querySelectorAll('.menu [data-panel]');
|
||||
|
||||
for (var i = 0; i < menuItems.length; i++) {
|
||||
if(menuItems[i].dataset.panel === newVal) {
|
||||
menuItems[i].classList.add('selected');
|
||||
} else {
|
||||
menuItems[i].classList.remove('selected');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
menuClicked: function(ev) {
|
||||
var target = ev.target;
|
||||
var checks = 5;
|
||||
|
||||
// find panel to select
|
||||
while(checks && !target.dataset.panel) {
|
||||
target = target.parentElement;
|
||||
checks--;
|
||||
}
|
||||
|
||||
if (checks) {
|
||||
this.selectPanel(target.dataset.panel);
|
||||
}
|
||||
},
|
||||
|
||||
handleDevClick: function(ev) {
|
||||
// prevent it from highlighting first menu item
|
||||
document.activeElement.blur();
|
||||
this.menuClicked(ev);
|
||||
},
|
||||
|
||||
selectPanel: function(newChoice) {
|
||||
if(newChoice === this.selected) {
|
||||
return;
|
||||
} else if (newChoice == 'logout') {
|
||||
this.handleLogOut();
|
||||
return;
|
||||
}
|
||||
navigationActions.navigate.apply(null, newChoice.split('/'));
|
||||
},
|
||||
|
||||
handleLogOut: function() {
|
||||
authActions.logOut();
|
||||
},
|
||||
|
||||
filterIcon: function(filter) {
|
||||
return uiUtil.domainIcon(filter);
|
||||
},
|
||||
|
||||
filterName: function(filter) {
|
||||
return entityDomainFilters[filter];
|
||||
},
|
||||
|
||||
filterType: function(filter) {
|
||||
return 'states/' + filter;
|
||||
},
|
||||
});
|
||||
})();
|
||||
</script>
|
|
@ -1,61 +0,0 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
|
||||
<link rel="import" href="../bower_components/iron-icon/iron-icon.html">
|
||||
<link rel="import" href="../bower_components/paper-spinner/paper-spinner.html">
|
||||
|
||||
<dom-module id="ha-voice-command-progress">
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
iron-icon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.interimTranscript {
|
||||
color: darkgrey;
|
||||
}
|
||||
|
||||
.listening paper-spinner {
|
||||
float: right;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<iron-icon icon="av:hearing"></iron-icon>
|
||||
<span>{{finalTranscript}}</span>
|
||||
<span class='interimTranscript'>[[interimTranscript]]</span>
|
||||
<paper-spinner active$="[[isTransmitting]]" alt="Sending voice command to Home Assistant"></paper-spinner>
|
||||
</template>
|
||||
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
(function(){
|
||||
var voiceGetters = window.hass.voiceGetters;
|
||||
|
||||
Polymer({
|
||||
is: 'ha-voice-command-progress',
|
||||
|
||||
behaviors: [nuclearObserver],
|
||||
|
||||
properties: {
|
||||
isTransmitting: {
|
||||
type: Boolean,
|
||||
bindNuclear: voiceGetters.isTransmitting,
|
||||
},
|
||||
|
||||
interimTranscript: {
|
||||
type: String,
|
||||
bindNuclear: voiceGetters.extraInterimTranscript,
|
||||
},
|
||||
|
||||
finalTranscript: {
|
||||
type: String,
|
||||
bindNuclear: voiceGetters.finalTranscript,
|
||||
},
|
||||
},
|
||||
});
|
||||
})();
|
||||
</script>
|
|
@ -1,27 +0,0 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="../bower_components/paper-spinner/paper-spinner.html">
|
||||
|
||||
<dom-module id="loading-box">
|
||||
<style>
|
||||
.text {
|
||||
display: inline-block;
|
||||
line-height: 28px;
|
||||
vertical-align: top;
|
||||
margin-left: 8px;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div layout='horizontal'>
|
||||
<paper-spinner active="true"></paper-spinner>
|
||||
<div class='text'><content></content>…</div>
|
||||
</div>
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
Polymer({
|
||||
is: 'loading-box',
|
||||
});
|
||||
})();
|
||||
</script>
|
|
@ -1,69 +0,0 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
|
||||
<link rel="import" href="domain-icon.html">
|
||||
<link rel="import" href="display-time.html">
|
||||
<link rel="import" href="relative-ha-datetime.html">
|
||||
|
||||
<dom-module id="logbook-entry">
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
line-height: 2em;
|
||||
}
|
||||
|
||||
display-time {
|
||||
width: 55px;
|
||||
font-size: .8em;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
domain-icon {
|
||||
margin: 0 8px 0 16px;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
|
||||
.name {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.message {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div class='horizontal layout'>
|
||||
<display-time date-obj="[[entryObj.when]]"></display-time>
|
||||
<domain-icon domain="[[entryObj.domain]]" class='icon'></domain-icon>
|
||||
<div class='message' flex>
|
||||
<template is='dom-if' if="[[!entryObj.entityId]]">
|
||||
<span class='name'>[[entryObj.name]]</span>
|
||||
</template>
|
||||
<template is='dom-if' if="[[entryObj.entityId]]">
|
||||
<a href='#' on-click="entityClicked" class='name'>[[entryObj.name]]</a>
|
||||
<span> </span>
|
||||
</template>
|
||||
<span>[[entryObj.message]]</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var moreInfoActions = window.hass.moreInfoActions;
|
||||
|
||||
Polymer({
|
||||
is: 'logbook-entry',
|
||||
|
||||
entityClicked: function(ev) {
|
||||
ev.preventDefault();
|
||||
moreInfoActions.selectEntity(this.entryObj.entityId);
|
||||
}
|
||||
});
|
||||
|
||||
})();
|
||||
</script>
|
|
@ -1,74 +0,0 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
|
||||
<link rel="import" href="../resources/moment-js.html">
|
||||
|
||||
<dom-module id="relative-ha-datetime">
|
||||
<template>
|
||||
<span>[[relativeTime]]</span>
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var UPDATE_INTERVAL = 60000; // 60 seconds
|
||||
|
||||
var parseDateTime = window.hass.util.parseDateTime;
|
||||
|
||||
Polymer({
|
||||
is: 'relative-ha-datetime',
|
||||
|
||||
properties: {
|
||||
datetime: {
|
||||
type: String,
|
||||
observer: 'datetimeChanged',
|
||||
},
|
||||
|
||||
datetimeObj: {
|
||||
type: Object,
|
||||
observer: 'datetimeObjChanged',
|
||||
},
|
||||
|
||||
parsedDateTime: {
|
||||
type: Object,
|
||||
},
|
||||
|
||||
relativeTime: {
|
||||
type: String,
|
||||
value: 'not set',
|
||||
},
|
||||
},
|
||||
|
||||
relativeTime: "",
|
||||
parsedDateTime: null,
|
||||
|
||||
created: function() {
|
||||
this.updateRelative = this.updateRelative.bind(this);
|
||||
},
|
||||
|
||||
attached: function() {
|
||||
this._interval = setInterval(this.updateRelative, UPDATE_INTERVAL);
|
||||
},
|
||||
|
||||
detached: function() {
|
||||
clearInterval(this._interval);
|
||||
},
|
||||
|
||||
datetimeChanged: function(newVal) {
|
||||
this.parsedDateTime = newVal ? parseDateTime(newVal) : null;
|
||||
|
||||
this.updateRelative();
|
||||
},
|
||||
|
||||
datetimeObjChanged: function(newVal) {
|
||||
this.parsedDateTime = newVal;
|
||||
|
||||
this.updateRelative();
|
||||
},
|
||||
|
||||
updateRelative: function() {
|
||||
this.relativeTime = this.parsedDateTime ?
|
||||
moment(this.parsedDateTime).fromNow() : "";
|
||||
},
|
||||
});
|
||||
})();
|
||||
</script>
|
|
@ -1,71 +0,0 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
|
||||
<link rel="import" href="../bower_components/paper-menu/paper-menu.html">
|
||||
|
||||
<link rel="import" href="domain-icon.html">
|
||||
|
||||
<dom-module id="services-list">
|
||||
<style>
|
||||
ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
list-style: none;
|
||||
line-height: 2em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<ul>
|
||||
<template is='dom-repeat' items="[[serviceDomains]]" as="domain">
|
||||
<template is='dom-repeat' items="[[domain.services]]" as="service">
|
||||
<li><a href='#' on-click='serviceClicked'>
|
||||
<span>[[domain.domain]]</span>/<span>[[service]]</span>
|
||||
</a></li>
|
||||
</template>
|
||||
</template>
|
||||
</ul>
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var serviceGetters = window.hass.serviceGetters;
|
||||
|
||||
Polymer({
|
||||
is: 'services-list',
|
||||
|
||||
behaviors: [nuclearObserver],
|
||||
|
||||
properties: {
|
||||
serviceDomains: {
|
||||
type: Array,
|
||||
bindNuclear: [
|
||||
serviceGetters.entityMap,
|
||||
function(map) {
|
||||
return map.valueSeq()
|
||||
.sortBy(function(domain) { return domain.domain; })
|
||||
.toJS();
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
computeServices: function(domain) {
|
||||
return this.services.get(domain).toArray();
|
||||
},
|
||||
|
||||
serviceClicked: function(ev) {
|
||||
ev.preventDefault();
|
||||
this.fire(
|
||||
'service-selected', {domain: ev.model.domain.domain, service: ev.model.service});
|
||||
},
|
||||
});
|
||||
})();
|
||||
</script>
|
|
@ -1,105 +0,0 @@
|
|||
<link rel='import' href='../bower_components/polymer/polymer.html'>
|
||||
<link rel='import' href='../bower_components/iron-image/iron-image.html'>
|
||||
|
||||
<link rel='import' href='domain-icon.html'>
|
||||
|
||||
<dom-module id='state-badge'>
|
||||
<style>
|
||||
:host {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 45px;
|
||||
background-color: #4fc3f7;
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
div {
|
||||
height: 45px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
iron-image {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
domain-icon {
|
||||
margin: 0 auto;
|
||||
transition: color .3s ease-in-out;
|
||||
}
|
||||
|
||||
/* Color the icon if light or sun is on */
|
||||
domain-icon[data-domain=light][data-state=on],
|
||||
domain-icon[data-domain=switch][data-state=on],
|
||||
domain-icon[data-domain=sun][data-state=above_horizon] {
|
||||
color: #fff176;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div class='layout horizontal center'>
|
||||
<domain-icon id='icon'
|
||||
domain='[[stateObj.domain]]' data-domain$='[[stateObj.domain]]'
|
||||
state='[[stateObj.state]]' data-state$='[[stateObj.state]]'>
|
||||
</domain-icon>
|
||||
<template is='dom-if' if='[[stateObj.attributes.entity_picture]]'>
|
||||
<iron-image
|
||||
sizing='cover' class='fit'
|
||||
src$="[[stateObj.attributes.entity_picture]]"></iron-image>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
Polymer({
|
||||
is: 'state-badge',
|
||||
|
||||
properties: {
|
||||
stateObj: {
|
||||
type: Object,
|
||||
observer: 'updateIconColor',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when an attribute changes that influences the color of the icon.
|
||||
*/
|
||||
updateIconColor: function(newVal) {
|
||||
// for domain light, set color of icon to light color if available
|
||||
if(newVal.domain == "light" && newVal.state == "on" &&
|
||||
newVal.attributes.brightness && newVal.attributes.xy_color) {
|
||||
|
||||
var rgb = this.xyBriToRgb(newVal.attributes.xy_color[0],
|
||||
newVal.attributes.xy_color[1],
|
||||
newVal.attributes.brightness);
|
||||
this.$.icon.style.color = "rgb(" + rgb.map(Math.floor).join(",") + ")";
|
||||
} else {
|
||||
this.$.icon.style.color = null;
|
||||
}
|
||||
},
|
||||
|
||||
// from http://stackoverflow.com/questions/22894498/philips-hue-convert-xy-from-api-to-hex-or-rgb
|
||||
xyBriToRgb: function (x, y, bri) {
|
||||
z = 1.0 - x - y;
|
||||
Y = bri / 255.0; // Brightness of lamp
|
||||
X = (Y / y) * x;
|
||||
Z = (Y / y) * z;
|
||||
r = X * 1.612 - Y * 0.203 - Z * 0.302;
|
||||
g = -X * 0.509 + Y * 1.412 + Z * 0.066;
|
||||
b = X * 0.026 - Y * 0.072 + Z * 0.962;
|
||||
r = r <= 0.0031308 ? 12.92 * r : (1.0 + 0.055) * Math.pow(r, (1.0 / 2.4)) - 0.055;
|
||||
g = g <= 0.0031308 ? 12.92 * g : (1.0 + 0.055) * Math.pow(g, (1.0 / 2.4)) - 0.055;
|
||||
b = b <= 0.0031308 ? 12.92 * b : (1.0 + 0.055) * Math.pow(b, (1.0 / 2.4)) - 0.055;
|
||||
maxValue = Math.max(r,g,b);
|
||||
r /= maxValue;
|
||||
g /= maxValue;
|
||||
b /= maxValue;
|
||||
r = r * 255; if (r < 0) { r = 255; }
|
||||
g = g * 255; if (g < 0) { g = 255; }
|
||||
b = b * 255; if (b < 0) { b = 255; }
|
||||
return [r, g, b];
|
||||
}
|
||||
|
||||
});
|
||||
</script>
|
|
@ -1,80 +0,0 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
|
||||
<link rel="import" href="../cards/state-card.html">
|
||||
|
||||
<dom-module id="state-cards">
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@media all and (min-width: 1020px) {
|
||||
.state-card {
|
||||
width: calc(50% - 44px);
|
||||
margin: 8px 0 0 8px;
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (min-width: 1356px) {
|
||||
.state-card {
|
||||
width: calc(33% - 38px);
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (min-width: 1706px) {
|
||||
.state-card {
|
||||
width: calc(25% - 42px);
|
||||
}
|
||||
}
|
||||
|
||||
.no-states-content {
|
||||
max-width: 500px;
|
||||
background-color: #fff;
|
||||
border-radius: 2px;
|
||||
box-shadow: rgba(0, 0, 0, 0.098) 0px 2px 4px, rgba(0, 0, 0, 0.098) 0px 0px 3px;
|
||||
padding: 0 16px 8px;
|
||||
margin: 16px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div class='horizontal layout wrap'>
|
||||
|
||||
<template is='dom-repeat' items="{{states}}">
|
||||
<state-card class="state-card" state-obj="[[item]]"></state-card>
|
||||
</template>
|
||||
|
||||
<template is='dom-if' if="[[computeEmptyStates(states)]]">
|
||||
<div class='no-states-content'>
|
||||
<h3>Hi there!</h3>
|
||||
<p>
|
||||
It looks like we have nothing to show you right now. It could be that we have not yet discovered all your devices but it is more likely that you have not configured Home Assistant yet.
|
||||
</p>
|
||||
<p>
|
||||
Please see the <a href='https://home-assistant.io/getting-started/' target='_blank'>Getting Started</a> section on how to setup your devices.
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
Polymer({
|
||||
is: 'state-cards',
|
||||
|
||||
properties: {
|
||||
states: {
|
||||
type: Array,
|
||||
value: [],
|
||||
},
|
||||
},
|
||||
|
||||
computeEmptyStates: function(states) {
|
||||
return states.length === 0;
|
||||
},
|
||||
});
|
||||
})();
|
||||
</script>
|
|
@ -1,198 +0,0 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
|
||||
<link rel="import" href="../resources/lodash.html">
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
Polymer({
|
||||
is: 'state-history-chart-line',
|
||||
|
||||
properties: {
|
||||
data: {
|
||||
type: Object,
|
||||
observer: 'dataChanged',
|
||||
},
|
||||
|
||||
unit: {
|
||||
type: String,
|
||||
},
|
||||
|
||||
isSingleDevice: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
isAttached: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
observer: 'dataChanged',
|
||||
},
|
||||
},
|
||||
|
||||
created: function() {
|
||||
this.style.display = 'block';
|
||||
},
|
||||
|
||||
attached: function() {
|
||||
this.isAttached = true;
|
||||
},
|
||||
|
||||
dataChanged: function() {
|
||||
this.drawChart();
|
||||
},
|
||||
|
||||
/**************************************************
|
||||
The following code gererates line line graphs for devices with continuous
|
||||
values(which are devices that have a unit_of_measurement values defined).
|
||||
On each graph the devices are grouped by their unit of measurement, eg. all
|
||||
sensors measuring MB will be a separate line on single graph. The google
|
||||
chart API takes data as a 2 dimensional array in the format:
|
||||
|
||||
DateTime, device1, device2, device3
|
||||
2015-04-01, 1, 2, 0
|
||||
2015-04-01, 0, 1, 0
|
||||
2015-04-01, 2, 1, 1
|
||||
|
||||
NOTE: the first column is a javascript date objects.
|
||||
|
||||
The first thing we do is build up the data with rows for each time of a state
|
||||
change and initialise the values to 0. THen we loop through each device and
|
||||
fill in its data.
|
||||
|
||||
**************************************************/
|
||||
drawChart: function() {
|
||||
if (!this.isAttached) {
|
||||
return;
|
||||
}
|
||||
|
||||
var root = Polymer.dom(this);
|
||||
var unit = this.unit;
|
||||
var deviceStates = this.data;
|
||||
|
||||
while (root.lastChild) {
|
||||
root.removeChild(root.lastChild);
|
||||
}
|
||||
|
||||
if (deviceStates.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var chart = new google.visualization.LineChart(this);
|
||||
var dataTable = new google.visualization.DataTable();
|
||||
|
||||
dataTable.addColumn({ type: 'datetime', id: 'Time' });
|
||||
|
||||
var options = {
|
||||
legend: { position: 'top' },
|
||||
titlePosition: 'none',
|
||||
vAxes: {
|
||||
// Adds units to the left hand side of the graph
|
||||
0: {title: unit}
|
||||
},
|
||||
hAxis: {
|
||||
format: 'H:mm'
|
||||
},
|
||||
lineWidth: 1,
|
||||
chartArea:{left:'60',width:"95%"},
|
||||
explorer: {
|
||||
actions: ['dragToZoom', 'rightClickToReset', 'dragToPan'],
|
||||
keepInBounds: true,
|
||||
axis: 'horizontal',
|
||||
maxZoomIn: 0.1
|
||||
}
|
||||
};
|
||||
|
||||
if(this.isSingleDevice) {
|
||||
options.legend.position = 'none';
|
||||
options.vAxes[0].title = null;
|
||||
options.chartArea.left = 40;
|
||||
options.chartArea.height = '80%';
|
||||
options.chartArea.top = 5;
|
||||
options.enableInteractivity = false;
|
||||
}
|
||||
|
||||
// Get a unique list of times of state changes for all the device
|
||||
// for a particular unit of measureent.
|
||||
var times = _.pluck(_.flatten(deviceStates), "lastChangedAsDate");
|
||||
times = _.uniq(times, function(e) {
|
||||
return e.getTime();
|
||||
});
|
||||
|
||||
times = _.sortBy(times, function(o) { return o; });
|
||||
|
||||
var data = [];
|
||||
var empty = new Array(deviceStates.length);
|
||||
for(var i = 0; i < empty.length; i++) {
|
||||
empty[i] = 0;
|
||||
}
|
||||
|
||||
var timeIndex = 1;
|
||||
var endDate = new Date();
|
||||
var prevDate = times[0];
|
||||
|
||||
for(i = 0; i < times.length; i++) {
|
||||
var currentDate = new Date(prevDate);
|
||||
|
||||
// because we only have state changes we add an extra point at the same time
|
||||
// that holds the previous state which makes the line display correctly
|
||||
var beforePoint = new Date(times[i]);
|
||||
data.push([beforePoint].concat(empty));
|
||||
|
||||
data.push([times[i]].concat(empty));
|
||||
prevDate = times[i];
|
||||
timeIndex++;
|
||||
}
|
||||
data.push([endDate].concat(empty));
|
||||
|
||||
|
||||
var deviceCount = 0;
|
||||
deviceStates.forEach(function(device) {
|
||||
var attributes = device[device.length - 1].attributes;
|
||||
dataTable.addColumn('number', attributes.friendly_name);
|
||||
|
||||
var currentState = 0;
|
||||
var previousState = 0;
|
||||
var lastIndex = 0;
|
||||
var count = 0;
|
||||
var prevTime = data[0][0];
|
||||
device.forEach(function(state) {
|
||||
|
||||
currentState = state.state;
|
||||
var start = state.lastChangedAsDate;
|
||||
if(state.state == 'None') {
|
||||
currentState = previousState;
|
||||
}
|
||||
for(var i = lastIndex; i < data.length; i++) {
|
||||
data[i][1 + deviceCount] = parseFloat(previousState);
|
||||
// this is where data gets filled in for each time for the particular device
|
||||
// because for each time two entries were create we fill the first one with the
|
||||
// previous value and the second one with the new value
|
||||
if(prevTime.getTime() == data[i][0].getTime() && data[i][0].getTime() == start.getTime()) {
|
||||
data[i][1 + deviceCount] = parseFloat(currentState);
|
||||
lastIndex = i;
|
||||
prevTime = data[i][0];
|
||||
break;
|
||||
}
|
||||
prevTime = data[i][0];
|
||||
}
|
||||
|
||||
previousState = currentState;
|
||||
|
||||
count++;
|
||||
}.bind(this));
|
||||
|
||||
//fill in the rest of the Array
|
||||
for(var i = lastIndex; i < data.length; i++) {
|
||||
data[i][1 + deviceCount] = parseFloat(previousState);
|
||||
}
|
||||
|
||||
deviceCount++;
|
||||
}.bind(this));
|
||||
|
||||
dataTable.addRows(data);
|
||||
chart.draw(dataTable, options);
|
||||
},
|
||||
});
|
||||
})();
|
||||
|
||||
</script>
|
|
@ -1,120 +0,0 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
|
||||
<dom-module is='state-history-chart-timeline'>
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
<template></template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
Polymer({
|
||||
is: 'state-history-chart-timeline',
|
||||
|
||||
properties: {
|
||||
data: {
|
||||
type: Object,
|
||||
observer: 'dataChanged',
|
||||
},
|
||||
|
||||
isAttached: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
observer: 'dataChanged',
|
||||
},
|
||||
},
|
||||
|
||||
attached: function() {
|
||||
this.isAttached = true;
|
||||
},
|
||||
|
||||
dataChanged: function() {
|
||||
this.drawChart();
|
||||
},
|
||||
|
||||
drawChart: function() {
|
||||
if (!this.isAttached) {
|
||||
return;
|
||||
}
|
||||
var root = Polymer.dom(this);
|
||||
var stateHistory = this.data;
|
||||
|
||||
while (root.node.lastChild) {
|
||||
root.node.removeChild(root.node.lastChild);
|
||||
}
|
||||
|
||||
if (!stateHistory || stateHistory.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var chart = new google.visualization.Timeline(this);
|
||||
var dataTable = new google.visualization.DataTable();
|
||||
|
||||
dataTable.addColumn({ type: 'string', id: 'Entity' });
|
||||
dataTable.addColumn({ type: 'string', id: 'State' });
|
||||
dataTable.addColumn({ type: 'date', id: 'Start' });
|
||||
dataTable.addColumn({ type: 'date', id: 'End' });
|
||||
|
||||
var addRow = function(entityDisplay, stateStr, start, end) {
|
||||
stateStr = stateStr.replace(/_/g, ' ');
|
||||
dataTable.addRow([entityDisplay, stateStr, start, end]);
|
||||
};
|
||||
|
||||
var startTime = new Date(
|
||||
stateHistory.reduce(function(minTime, stateInfo) {
|
||||
return Math.min(
|
||||
minTime, stateInfo[0].lastChangedAsDate);
|
||||
}, new Date())
|
||||
);
|
||||
|
||||
// end time is Math.min(curTime, start time + 1 day)
|
||||
var endTime = new Date(startTime);
|
||||
endTime.setDate(endTime.getDate()+1);
|
||||
if (endTime > new Date()) {
|
||||
endTime = new Date();
|
||||
}
|
||||
|
||||
var numTimelines = 0;
|
||||
// stateHistory is a list of lists of sorted state objects
|
||||
stateHistory.forEach(function(stateInfo) {
|
||||
if(stateInfo.length === 0) return;
|
||||
|
||||
var entityDisplay = stateInfo[0].entityDisplay;
|
||||
var newLastChanged, prevState = null, prevLastChanged = null;
|
||||
|
||||
stateInfo.forEach(function(state) {
|
||||
if (prevState !== null && state.state !== prevState) {
|
||||
newLastChanged = state.lastChangedAsDate;
|
||||
|
||||
addRow(entityDisplay, prevState, prevLastChanged, newLastChanged);
|
||||
|
||||
prevState = state.state;
|
||||
prevLastChanged = newLastChanged;
|
||||
} else if (prevState === null) {
|
||||
prevState = state.state;
|
||||
prevLastChanged = state.lastChangedAsDate;
|
||||
}
|
||||
});
|
||||
|
||||
addRow(entityDisplay, prevState, prevLastChanged, endTime);
|
||||
numTimelines++;
|
||||
}.bind(this));
|
||||
|
||||
chart.draw(dataTable, {
|
||||
height: 55 + numTimelines * 42,
|
||||
|
||||
timeline: {
|
||||
showRowLabels: stateHistory.length > 1
|
||||
},
|
||||
|
||||
hAxis: {
|
||||
format: 'H:mm'
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
})();
|
||||
</script>
|
|
@ -1,158 +0,0 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
|
||||
<link rel="import" href="../bower_components/google-apis/google-legacy-loader.html">
|
||||
|
||||
<link rel="import" href="./loading-box.html">
|
||||
<link rel="import" href="./state-history-chart-timeline.html">
|
||||
<link rel="import" href="./state-history-chart-line.html">
|
||||
|
||||
<dom-module id="state-history-charts">
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
text-align: center;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.loading {
|
||||
height: 0px;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<google-legacy-loader on-api-load="googleApiLoaded"></google-legacy-loader>
|
||||
|
||||
<div hidden$="{{!isLoading}}" class='loading-container'>
|
||||
<loading-box>Updating history data</loading-box>
|
||||
</div>
|
||||
|
||||
<div class$='[[computeContentClasses(isLoading)]]'>
|
||||
<template is='dom-if' if='[[computeIsEmpty(stateHistory)]]'>
|
||||
No state history found.
|
||||
</template>
|
||||
|
||||
<state-history-chart-timeline
|
||||
data='[[groupedStateHistory.timeline]]'
|
||||
is-single-device='[[isSingleDevice]]'>
|
||||
</state-history-chart-timeline>
|
||||
|
||||
<template is='dom-repeat' items='[[groupedStateHistory.line]]'>
|
||||
<state-history-chart-line unit='[[extractUnit(item)]]'
|
||||
data='[[extractData(item)]]' is-single-device='[[isSingleDevice]]'>
|
||||
</state-history-chart-line>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
Polymer({
|
||||
is: 'state-history-charts',
|
||||
|
||||
properties: {
|
||||
stateHistory: {
|
||||
type: Object,
|
||||
},
|
||||
|
||||
isLoadingData: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
apiLoaded: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
isLoading: {
|
||||
type: Boolean,
|
||||
computed: 'computeIsLoading(isLoadingData, apiLoaded)',
|
||||
},
|
||||
|
||||
groupedStateHistory: {
|
||||
type: Object,
|
||||
computed: 'computeGroupedStateHistory(isLoading, stateHistory)',
|
||||
},
|
||||
|
||||
isSingleDevice: {
|
||||
type: Boolean,
|
||||
computed: 'computeIsSingleDevice(stateHistory)',
|
||||
},
|
||||
},
|
||||
|
||||
computeIsSingleDevice: function(stateHistory) {
|
||||
return stateHistory && stateHistory.size == 1;
|
||||
},
|
||||
|
||||
computeGroupedStateHistory: function(isLoading, stateHistory) {
|
||||
if (isLoading || !stateHistory) {
|
||||
return {line: [], timeline: []};
|
||||
}
|
||||
|
||||
var lineChartDevices = {};
|
||||
var timelineDevices = [];
|
||||
|
||||
stateHistory.forEach(function(stateInfo) {
|
||||
if (!stateInfo || stateInfo.size === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var stateWithUnit = stateInfo.find(function(state) {
|
||||
return 'unit_of_measurement' in state.attributes;
|
||||
});
|
||||
|
||||
var unit = stateWithUnit ?
|
||||
stateWithUnit.attributes.unit_of_measurement : false;
|
||||
|
||||
if (!unit) {
|
||||
timelineDevices.push(stateInfo.toArray());
|
||||
} else if(unit in lineChartDevices) {
|
||||
lineChartDevices[unit].push(stateInfo.toArray());
|
||||
} else {
|
||||
lineChartDevices[unit] = [stateInfo.toArray()];
|
||||
}
|
||||
});
|
||||
|
||||
timelineDevices = timelineDevices.length > 0 && timelineDevices;
|
||||
|
||||
var unitStates = Object.keys(lineChartDevices).map(function(unit) {
|
||||
return [unit, lineChartDevices[unit]]; });
|
||||
|
||||
return {line: unitStates, timeline: timelineDevices};
|
||||
},
|
||||
|
||||
googleApiLoaded: function() {
|
||||
google.load("visualization", "1", {
|
||||
packages: ["timeline", "corechart"],
|
||||
callback: function() {
|
||||
this.apiLoaded = true;
|
||||
}.bind(this)
|
||||
});
|
||||
},
|
||||
|
||||
computeContentClasses: function(isLoading) {
|
||||
return isLoading ? 'loading' : '';
|
||||
},
|
||||
|
||||
computeIsLoading: function(isLoadingData, apiLoaded) {
|
||||
return isLoadingData || !apiLoaded;
|
||||
},
|
||||
|
||||
computeIsEmpty: function(stateHistory) {
|
||||
return stateHistory && stateHistory.size === 0;
|
||||
},
|
||||
|
||||
extractUnit: function(arr) {
|
||||
return arr[0];
|
||||
},
|
||||
|
||||
extractData: function(arr) {
|
||||
return arr[1];
|
||||
},
|
||||
});
|
||||
})();
|
||||
</script>
|
|
@ -1,75 +0,0 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
<!-- <link rel="import" href="../bower_components/core-tooltip/core-tooltip.html"> -->
|
||||
|
||||
<link rel="import" href="state-badge.html">
|
||||
<link rel="import" href="relative-ha-datetime.html">
|
||||
|
||||
<dom-module id="state-info">
|
||||
<style>
|
||||
:host {
|
||||
line-height: normal;
|
||||
min-width: 150px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
state-badge {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.info {
|
||||
margin-left: 60px;
|
||||
}
|
||||
|
||||
.name {
|
||||
text-transform: capitalize;
|
||||
font-weight: 300;
|
||||
font-size: 1.3rem;
|
||||
text-overflow: ellipsis;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.time-ago {
|
||||
color: darkgrey;
|
||||
margin-top: -2px;
|
||||
font-size: 1rem;
|
||||
text-overflow: ellipsis;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<state-badge state-obj='[[stateObj]]'></state-badge>
|
||||
|
||||
<div class='info'>
|
||||
<div class='name'>[[stateObj.entityDisplay]]</div>
|
||||
|
||||
<div class='time-ago'>
|
||||
<!-- <core-tooltip label="[[computeTooltipLabel(stateObj)]]" position="bottom"> -->
|
||||
<relative-ha-datetime datetime-obj='[[stateObj.lastChangedAsDate]]'></relative-ha-datetime>
|
||||
<!-- </core-tooltip> -->
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
Polymer({
|
||||
is: 'state-info',
|
||||
|
||||
properties: {
|
||||
stateObj: {
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
|
||||
computeTooltipLabel: function(stateObj) {
|
||||
// stateObj.lastChangedAsDate | formatDateTime
|
||||
return 'Label TODO';
|
||||
},
|
||||
});
|
||||
})();
|
||||
</script>
|
|
@ -1,54 +0,0 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
|
||||
<link rel="import" href="../bower_components/iron-icon/iron-icon.html">
|
||||
<link rel="import" href="../bower_components/paper-toggle-button/paper-toggle-button.html">
|
||||
|
||||
<link rel="import" href="../bower_components/iron-icons/notification-icons.html">
|
||||
|
||||
<dom-module id="stream-status">
|
||||
<style>
|
||||
:host {
|
||||
display: inline-block;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
paper-toggle-button {
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<iron-icon icon="warning" hidden$="[[!hasError]]"></iron-icon>
|
||||
<paper-toggle-button id="toggle" on-change='toggleChanged' checked$='[[isStreaming]]' hidden$="[[hasError]]"></paper-toggle-button>
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
var streamGetters = window.hass.streamGetters;
|
||||
var streamActions = window.hass.streamActions;
|
||||
|
||||
Polymer({
|
||||
is: 'stream-status',
|
||||
|
||||
behaviors: [nuclearObserver],
|
||||
|
||||
properties: {
|
||||
isStreaming: {
|
||||
type: Boolean,
|
||||
bindNuclear: streamGetters.isStreamingEvents,
|
||||
},
|
||||
|
||||
hasError: {
|
||||
type: Boolean,
|
||||
bindNuclear: streamGetters.hasStreamingEventsError,
|
||||
},
|
||||
},
|
||||
|
||||
toggleChanged: function() {
|
||||
if (this.isStreaming) {
|
||||
streamActions.stop();
|
||||
} else {
|
||||
streamActions.start();
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -1,151 +0,0 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
|
||||
<link rel="import" href="../bower_components/paper-dialog/paper-dialog.html">
|
||||
<link rel="import" href="../bower_components/paper-dialog-scrollable/paper-dialog-scrollable.html">
|
||||
<!-- <link rel="import" href="../bower_components/neon-animation/animations/slide-up-animation.html">
|
||||
<link rel="import" href="../bower_components/neon-animation/animations/slide-down-animation.html">
|
||||
-->
|
||||
<link rel="import" href="../cards/state-card-content.html">
|
||||
<link rel="import" href="../components/state-history-charts.html">
|
||||
<link rel="import" href="../more-infos/more-info-content.html">
|
||||
|
||||
<dom-module id="more-info-dialog">
|
||||
<style>
|
||||
state-card-content {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
@media all and (max-width: 450px) {
|
||||
paper-dialog {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
max-height: calc(100% - 64px);
|
||||
|
||||
position: fixed !important;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
overflow: scroll;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<!-- entry-animation='slide-up-animation' exit-animation='slide-down-animation' -->
|
||||
<paper-dialog id="dialog" with-backdrop opened='{{dialogOpen}}'>
|
||||
<h2><state-card-content state-obj="[[stateObj]]"></state-card-content></h2>
|
||||
<div>
|
||||
<template is='dom-if' if="[[showHistoryComponent]]">
|
||||
<state-history-charts state-history="[[stateHistory]]"
|
||||
is-loading-data="[[isLoadingHistoryData]]"></state-history-charts>
|
||||
</template>
|
||||
<paper-dialog-scrollable>
|
||||
<more-info-content state-obj="[[stateObj]]"
|
||||
dialog-open="[[dialogOpen]]"></more-info-content>
|
||||
</paper-dialog-scrollable>
|
||||
</div>
|
||||
</paper-dialog>
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
|
||||
var configGetters = window.hass.configGetters;
|
||||
var entityHistoryGetters = window.hass.entityHistoryGetters;
|
||||
|
||||
var entityHistoryActions = window.hass.entityHistoryActions;
|
||||
var moreInfoGetters = window.hass.moreInfoGetters;
|
||||
var moreInfoActions = window.hass.moreInfoActions;
|
||||
|
||||
// if you don't want the history component to show add the domain to this array
|
||||
var DOMAINS_WITH_NO_HISTORY = ['camera'];
|
||||
|
||||
Polymer({
|
||||
is: 'more-info-dialog',
|
||||
|
||||
behaviors: [nuclearObserver],
|
||||
|
||||
properties: {
|
||||
stateObj: {
|
||||
type: Object,
|
||||
bindNuclear: moreInfoGetters.currentEntity,
|
||||
observer: 'stateObjChanged',
|
||||
},
|
||||
|
||||
stateHistory: {
|
||||
type: Object,
|
||||
bindNuclear: [
|
||||
moreInfoGetters.currentEntityHistory,
|
||||
function(history) {
|
||||
return history ? [history] : false;
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
isLoadingHistoryData: {
|
||||
type: Boolean,
|
||||
bindNuclear: entityHistoryGetters.isLoadingEntityHistory,
|
||||
},
|
||||
|
||||
hasHistoryComponent: {
|
||||
type: Boolean,
|
||||
bindNuclear: configGetters.isComponentLoaded('history'),
|
||||
observer: 'fetchHistoryData',
|
||||
},
|
||||
|
||||
shouldFetchHistory: {
|
||||
type: Boolean,
|
||||
bindNuclear: moreInfoGetters.isCurrentEntityHistoryStale,
|
||||
observer: 'fetchHistoryData',
|
||||
},
|
||||
|
||||
showHistoryComponent: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
dialogOpen: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
observer: 'dialogOpenChanged',
|
||||
},
|
||||
},
|
||||
|
||||
fetchHistoryData: function() {
|
||||
if (this.stateObj && this.hasHistoryComponent &&
|
||||
this.shouldFetchHistory) {
|
||||
entityHistoryActions.fetchRecent(this.stateObj.entityId);
|
||||
}
|
||||
if(this.stateObj) {
|
||||
if(DOMAINS_WITH_NO_HISTORY.indexOf(this.stateObj.domain) !== -1) {
|
||||
this.showHistoryComponent = false;
|
||||
}
|
||||
else {
|
||||
this.showHistoryComponent = this.hasHistoryComponent;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
stateObjChanged: function(newVal) {
|
||||
if (!newVal) {
|
||||
this.dialogOpen = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this.fetchHistoryData();
|
||||
|
||||
// allow dialog to render content before showing it so it is
|
||||
// positioned correctly.
|
||||
this.async(function() {
|
||||
this.dialogOpen = true;
|
||||
}.bind(this), 10);
|
||||
},
|
||||
|
||||
dialogOpenChanged: function(newVal) {
|
||||
if (!newVal) {
|
||||
moreInfoActions.deselectEntity();
|
||||
}
|
||||
},
|
||||
});
|
||||
})();
|
||||
</script>
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 94d8682c1e7679ae744e8419896d5d7b0bdd16cc
|
|
@ -1,78 +0,0 @@
|
|||
<link rel='import' href='bower_components/polymer/polymer.html'>
|
||||
|
||||
<link rel='import' href='bower_components/paper-styles/typography.html'>
|
||||
|
||||
<link rel='import' href='resources/home-assistant-js.html'>
|
||||
<link rel='import' href='resources/home-assistant-icons.html'>
|
||||
<link rel='import' href='resources/store-listener-behavior.html'>
|
||||
|
||||
<link rel='import' href='layouts/login-form.html'>
|
||||
<link rel='import' href='layouts/home-assistant-main.html'>
|
||||
|
||||
<link rel='import' href='resources/home-assistant-style.html'>
|
||||
|
||||
<dom-module id='home-assistant'>
|
||||
<style>
|
||||
:host {
|
||||
font-family: 'Roboto', 'Noto', sans-serif;
|
||||
font-weight: 300;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<home-assistant-icons></home-assistant-icons>
|
||||
<template is='dom-if' if='[[!loaded]]'>
|
||||
<login-form></login-form>
|
||||
</template>
|
||||
|
||||
<template is='dom-if' if='[[loaded]]'>
|
||||
<home-assistant-main></home-assistant-main>
|
||||
</template>
|
||||
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var uiActions = window.hass.uiActions;
|
||||
var syncGetters = window.hass.syncGetters;
|
||||
var preferences = window.hass.localStoragePreferences;
|
||||
|
||||
Polymer({
|
||||
is: 'home-assistant',
|
||||
|
||||
hostAttributes: {
|
||||
auth: null,
|
||||
},
|
||||
|
||||
behaviors: [nuclearObserver],
|
||||
|
||||
properties: {
|
||||
auth: {
|
||||
type: String,
|
||||
},
|
||||
loaded: {
|
||||
type: Boolean,
|
||||
bindNuclear: syncGetters.isDataLoaded,
|
||||
},
|
||||
},
|
||||
|
||||
ready: function() {
|
||||
// remove the HTML init message
|
||||
document.getElementById('init').remove();
|
||||
|
||||
// if auth was given, tell the backend
|
||||
if(this.auth) {
|
||||
uiActions.validateAuth(this.auth, false);
|
||||
} else if (preferences.authToken) {
|
||||
uiActions.validateAuth(preferences.authToken, true);
|
||||
}
|
||||
|
||||
preferences.startSync();
|
||||
},
|
||||
});
|
||||
|
||||
})();
|
||||
</script>
|
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"removeComments": true,
|
||||
"removeCommentsFromCDATA": true,
|
||||
"removeCDATASectionsFromCDATA": true,
|
||||
"collapseWhitespace": true,
|
||||
"collapseBooleanAttributes": true,
|
||||
"removeScriptTypeAttributes": true,
|
||||
"removeStyleLinkTypeAttributes": true,
|
||||
"minifyJS": true,
|
||||
"minifyCSS": true
|
||||
}
|
|
@ -1,131 +0,0 @@
|
|||
<link rel='import' href='../bower_components/polymer/polymer.html'>
|
||||
<link rel='import' href='../bower_components/layout/layout.html'>
|
||||
|
||||
<link rel='import' href='../bower_components/paper-drawer-panel/paper-drawer-panel.html'>
|
||||
|
||||
<link rel='import' href='../layouts/partial-states.html'>
|
||||
<link rel='import' href='../layouts/partial-logbook.html'>
|
||||
<link rel='import' href='../layouts/partial-history.html'>
|
||||
<link rel='import' href='../layouts/partial-dev-call-service.html'>
|
||||
<link rel='import' href='../layouts/partial-dev-fire-event.html'>
|
||||
<link rel='import' href='../layouts/partial-dev-set-state.html'>
|
||||
|
||||
<link rel='import' href='../managers/notification-manager.html'>
|
||||
<link rel="import" href="../dialogs/more-info-dialog.html">
|
||||
|
||||
<link rel='import' href='../components/ha-sidebar.html'>
|
||||
|
||||
<dom-module id='home-assistant-main'>
|
||||
<template>
|
||||
<notification-manager></notification-manager>
|
||||
<more-info-dialog></more-info-dialog>
|
||||
|
||||
<paper-drawer-panel id='drawer' narrow='{{narrow}}'>
|
||||
<ha-sidebar drawer></ha-sidebar>
|
||||
|
||||
<template is='dom-if' if='[[isSelectedStates]]'>
|
||||
<partial-states main narrow='[[narrow]]'>
|
||||
</partial-states>
|
||||
</template>
|
||||
<template is='dom-if' if='[[isSelectedLogbook]]'>
|
||||
<partial-logbook main narrow='[[narrow]]'></partial-logbook>
|
||||
</template>
|
||||
<template is='dom-if' if='[[isSelectedHistory]]'>
|
||||
<partial-history main narrow='[[narrow]]'></partial-history>
|
||||
</template>
|
||||
<template is='dom-if' if='[[isSelectedDevService]]'>
|
||||
<partial-dev-call-service main narrow='[[narrow]]'></partial-dev-call-service>
|
||||
</template>
|
||||
<template is='dom-if' if='[[isSelectedDevEvent]]'>
|
||||
<partial-dev-fire-event main narrow='[[narrow]]'></partial-dev-fire-event>
|
||||
</template>
|
||||
<template is='dom-if' if='[[isSelectedDevState]]'>
|
||||
<partial-dev-set-state main narrow='[[narrow]]'></partial-dev-set-state>
|
||||
</template>
|
||||
</paper-drawer-panel>
|
||||
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var configGetters = window.hass.configGetters;
|
||||
var entityGetters = window.hass.entityGetters;
|
||||
var navigationGetters = window.hass.navigationGetters;
|
||||
|
||||
var authActions = window.hass.authActions;
|
||||
var navigationActions = window.hass.navigationActions;
|
||||
|
||||
var uiUtil = window.hass.uiUtil;
|
||||
var entityDomainFilters = window.hass.util.entityDomainFilters;
|
||||
var urlSync = window.hass.urlSync;
|
||||
|
||||
Polymer({
|
||||
is: 'home-assistant-main',
|
||||
|
||||
behaviors: [nuclearObserver],
|
||||
|
||||
properties: {
|
||||
narrow: {
|
||||
type: Boolean,
|
||||
},
|
||||
|
||||
activePage: {
|
||||
type: String,
|
||||
bindNuclear: navigationGetters.activePage,
|
||||
observer: 'activePageChanged',
|
||||
},
|
||||
|
||||
isSelectedStates: {
|
||||
type: Boolean,
|
||||
bindNuclear: navigationGetters.isActivePane('states'),
|
||||
},
|
||||
|
||||
isSelectedHistory: {
|
||||
type: Boolean,
|
||||
bindNuclear: navigationGetters.isActivePane('history'),
|
||||
},
|
||||
|
||||
isSelectedLogbook: {
|
||||
type: Boolean,
|
||||
bindNuclear: navigationGetters.isActivePane('logbook'),
|
||||
},
|
||||
|
||||
isSelectedDevEvent: {
|
||||
type: Boolean,
|
||||
bindNuclear: navigationGetters.isActivePane('devEvent'),
|
||||
},
|
||||
|
||||
isSelectedDevState: {
|
||||
type: Boolean,
|
||||
bindNuclear: navigationGetters.isActivePane('devState'),
|
||||
},
|
||||
|
||||
isSelectedDevService: {
|
||||
type: Boolean,
|
||||
bindNuclear: navigationGetters.isActivePane('devService'),
|
||||
},
|
||||
},
|
||||
|
||||
listeners: {
|
||||
'open-menu': 'openDrawer',
|
||||
},
|
||||
|
||||
openDrawer: function() {
|
||||
this.$.drawer.openDrawer();
|
||||
},
|
||||
|
||||
activePageChanged: function() {
|
||||
this.$.drawer.closeDrawer();
|
||||
},
|
||||
|
||||
attached: function() {
|
||||
urlSync.startSync();
|
||||
},
|
||||
|
||||
detached: function() {
|
||||
urlSync.stopSync();
|
||||
},
|
||||
});
|
||||
})();
|
||||
</script>
|
|
@ -1,160 +0,0 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="../bower_components/layout/layout.html">
|
||||
|
||||
<!-- WIP <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-container.html">
|
||||
<link rel="import" href="../bower_components/paper-input/paper-input-error.html">
|
||||
<link rel="import" href="../bower_components/iron-input/iron-input.html">
|
||||
<link rel="import" href="../bower_components/paper-spinner/paper-spinner.html">
|
||||
|
||||
<link rel="import" href="../resources/store-listener-behavior.html">
|
||||
|
||||
<dom-module id="login-form">
|
||||
<style>
|
||||
:host {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#passwordDecorator {
|
||||
display: block;
|
||||
height: 57px;
|
||||
}
|
||||
|
||||
paper-checkbox {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
#validatebox {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.validatemessage {
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div class="layout horizontal center fit login" id="splash">
|
||||
<div class="layout vertical center flex">
|
||||
|
||||
<img src="/static/favicon-192x192.png" />
|
||||
<h1>Home Assistant</h1>
|
||||
|
||||
<a href="#" id="hideKeyboardOnFocus"></a>
|
||||
|
||||
<div class='interact'>
|
||||
<div id='loginform' hidden$="[[isValidating]]">
|
||||
<paper-input-container id="passwordDecorator" invalid="[[isInvalid]]">
|
||||
<label>Password</label>
|
||||
<input is="iron-input" type="password" id="passwordInput" />
|
||||
<paper-input-error invalid="[[isInvalid]]">[[errorMessage]]</paper-input-error>
|
||||
</paper-input-container>
|
||||
|
||||
<div class="layout horizontal center">
|
||||
<paper-checkbox for id='rememberLogin'>Remember</paper-checkbox>
|
||||
<paper-button id='loginButton'>Log In</paper-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="validatebox" hidden$="[[!isValidating]]">
|
||||
<paper-spinner active="true"></paper-spinner><br />
|
||||
<div class="validatemessage">Loading data…</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var uiActions = window.hass.uiActions;
|
||||
var authGetters = window.hass.authGetters;
|
||||
|
||||
Polymer({
|
||||
is: 'login-form',
|
||||
|
||||
behaviors: [nuclearObserver],
|
||||
|
||||
properties: {
|
||||
isValidating: {
|
||||
type: Boolean,
|
||||
observer: 'isValidatingChanged',
|
||||
bindNuclear: authGetters.isValidating,
|
||||
},
|
||||
|
||||
isInvalid: {
|
||||
type: Boolean,
|
||||
bindNuclear: authGetters.isInvalidAttempt,
|
||||
},
|
||||
|
||||
errorMessage: {
|
||||
type: String,
|
||||
bindNuclear: authGetters.attemptErrorMessage,
|
||||
},
|
||||
},
|
||||
|
||||
listeners: {
|
||||
'keydown': 'passwordKeyDown',
|
||||
'loginButton.click': 'validatePassword',
|
||||
},
|
||||
|
||||
observers: [
|
||||
'validatingChanged(isValidating, isInvalid)',
|
||||
],
|
||||
|
||||
validatingChanged: function(isValidating, isInvalid) {
|
||||
if (!isValidating && !isInvalid) {
|
||||
this.$.passwordInput.value = '';
|
||||
}
|
||||
},
|
||||
|
||||
isValidatingChanged: function(newVal) {
|
||||
if (!newVal) {
|
||||
this.async(function() { this.$.passwordInput.focus(); }.bind(this), 10);
|
||||
}
|
||||
},
|
||||
|
||||
passwordKeyDown: function(ev) {
|
||||
// validate on enter
|
||||
if(ev.keyCode === 13) {
|
||||
this.validatePassword();
|
||||
ev.preventDefault();
|
||||
|
||||
// clear error after we start typing again
|
||||
} else if(this.isInvalid) {
|
||||
this.isInvalid = false;
|
||||
}
|
||||
},
|
||||
|
||||
validatePassword: function() {
|
||||
this.$.hideKeyboardOnFocus.focus();
|
||||
|
||||
uiActions.validateAuth(this.$.passwordInput.value, this.$.rememberLogin.checked);
|
||||
},
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
<link rel='import' href='../bower_components/polymer/polymer.html'>
|
||||
|
||||
<link rel='import' href='../bower_components/paper-scroll-header-panel/paper-scroll-header-panel.html'>
|
||||
|
||||
<link rel='import' href='../bower_components/paper-toolbar/paper-toolbar.html'>
|
||||
<link rel='import' href='../bower_components/paper-icon-button/paper-icon-button.html'>
|
||||
|
||||
<dom-module id='partial-base'>
|
||||
<style>
|
||||
:host {
|
||||
-ms-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<paper-scroll-header-panel class='fit'>
|
||||
<paper-toolbar>
|
||||
<paper-icon-button icon='menu' hidden$='[[!narrow]]' on-click='toggleMenu'></paper-icon-button>
|
||||
<div class="title">
|
||||
<content select='[header-title]'></content>
|
||||
</div>
|
||||
<content select='[header-buttons]'></content>
|
||||
</paper-toolbar>
|
||||
|
||||
<content></content>
|
||||
</paper-scroll-header-panel>
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
Polymer({
|
||||
is: 'partial-base',
|
||||
|
||||
properties: {
|
||||
narrow: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
},
|
||||
|
||||
toggleMenu: function() {
|
||||
this.fire('open-menu');
|
||||
},
|
||||
});
|
||||
})();
|
||||
</script>
|
|
@ -1,101 +0,0 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
|
||||
<link rel="import" href="../bower_components/paper-button/paper-button.html">
|
||||
<link rel="import" href="../bower_components/paper-input/paper-input.html">
|
||||
<link rel="import" href="../bower_components/paper-input/paper-textarea.html">
|
||||
|
||||
<link rel="import" href="./partial-base.html">
|
||||
|
||||
<link rel="import" href="../components/services-list.html">
|
||||
|
||||
<dom-module id="partial-dev-call-service">
|
||||
<style>
|
||||
.form {
|
||||
padding: 24px;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.ha-form {
|
||||
margin-right: 16px;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<partial-base narrow="[[narrow]]">
|
||||
<span header-title>Call Service</span>
|
||||
|
||||
<div class='form fit'>
|
||||
<p>
|
||||
Call a service from a component.
|
||||
</p>
|
||||
|
||||
<div class$='[[computeFormClasses(narrow)]]'>
|
||||
<div class='ha-form flex'>
|
||||
<paper-input label="Domain" autofocus value='{{domain}}'></paper-input>
|
||||
<paper-input label="Service" value='{{service}}'></paper-input>
|
||||
<paper-textarea label="Service Data (JSON, optional)" value='{{serviceData}}'></paper-textarea>
|
||||
<paper-button on-click='callService' raised>Call Service</paper-button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4>Available services:</h4>
|
||||
<services-list on-service-selected='serviceSelected'></services-list>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</partial-base>
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var serviceActions = window.hass.serviceActions;
|
||||
|
||||
Polymer({
|
||||
is: 'partial-dev-call-service',
|
||||
|
||||
properties: {
|
||||
narrow: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
domain: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
|
||||
service: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
|
||||
serviceData: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
},
|
||||
|
||||
serviceSelected: function(ev) {
|
||||
this.domain = ev.detail.domain;
|
||||
this.service = ev.detail.service;
|
||||
},
|
||||
|
||||
callService: function() {
|
||||
var serviceData;
|
||||
|
||||
try {
|
||||
serviceData = this.serviceData ? JSON.parse(this.serviceData): {};
|
||||
} catch (err) {
|
||||
alert("Error parsing JSON: " + err);
|
||||
return;
|
||||
}
|
||||
|
||||
serviceActions.callService(this.domain, this.service, serviceData);
|
||||
},
|
||||
|
||||
computeFormClasses: function(narrow) {
|
||||
return 'layout ' + (narrow ? 'vertical' : 'horizontal');
|
||||
},
|
||||
});
|
||||
})();
|
||||
</script>
|
|
@ -1,91 +0,0 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
|
||||
<link rel="import" href="../bower_components/paper-button/paper-button.html">
|
||||
<link rel="import" href="../bower_components/paper-input/paper-input.html">
|
||||
<link rel="import" href="../bower_components/paper-input/paper-textarea.html">
|
||||
|
||||
<link rel="import" href="./partial-base.html">
|
||||
|
||||
<link rel="import" href="../components/events-list.html">
|
||||
|
||||
<dom-module id="partial-dev-fire-event">
|
||||
<style>
|
||||
.form {
|
||||
padding: 24px;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.ha-form {
|
||||
margin-right: 16px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<partial-base narrow="{{narrow}}">
|
||||
<span header-title>Fire Event</span>
|
||||
|
||||
<div class='form fit'>
|
||||
<p>
|
||||
Fire an event on the event bus.
|
||||
</p>
|
||||
|
||||
<div class$='[[computeFormClasses(narrow)]]'>
|
||||
<div class='ha-form flex'>
|
||||
<paper-input label="Event Type" autofocus required value='{{eventType}}'></paper-input>
|
||||
<paper-textarea label="Event Data (JSON, optional)" value='{{eventData}}'></paper-textarea>
|
||||
<paper-button on-click='fireEvent' raised>Fire Event</paper-button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4>Available events:</h4>
|
||||
<events-list on-event-selected='eventSelected'></event-list>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</partial-base>
|
||||
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var eventActions = window.hass.eventActions;
|
||||
|
||||
Polymer({
|
||||
is: 'partial-dev-fire-event',
|
||||
|
||||
properties: {
|
||||
eventType: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
|
||||
eventData: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
},
|
||||
|
||||
eventSelected: function(ev) {
|
||||
this.eventType = ev.detail.eventType;
|
||||
},
|
||||
|
||||
fireEvent: function() {
|
||||
var eventData;
|
||||
|
||||
try {
|
||||
eventData = this.eventData ? JSON.parse(this.eventData) : {};
|
||||
} catch (err) {
|
||||
alert("Error parsing JSON: " + err);
|
||||
return;
|
||||
}
|
||||
|
||||
eventActions.fireEvent(this.eventType, eventData);
|
||||
},
|
||||
|
||||
computeFormClasses: function(narrow) {
|
||||
return 'layout ' + (narrow ? 'vertical' : 'horizontal');
|
||||
},
|
||||
});
|
||||
})();
|
||||
</script>
|
|
@ -1,115 +0,0 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
|
||||
<link rel="import" href="../bower_components/paper-button/paper-button.html">
|
||||
<link rel="import" href="../bower_components/paper-input/paper-input.html">
|
||||
<link rel="import" href="../bower_components/paper-input/paper-textarea.html">
|
||||
|
||||
<link rel="import" href="./partial-base.html">
|
||||
|
||||
<link rel="import" href="../components/entity-list.html">
|
||||
|
||||
<dom-module id="partial-dev-set-state">
|
||||
<style>
|
||||
.form {
|
||||
padding: 24px;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.ha-form {
|
||||
margin-right: 16px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<partial-base narrow="[[narrow]]">
|
||||
<span header-title>Set State</span>
|
||||
|
||||
<div class='form fit'>
|
||||
<div>
|
||||
Set the representation of a device within Home Assistant.<br />
|
||||
This will not communicate with the actual device.
|
||||
</div>
|
||||
|
||||
<div class$='[[computeFormClasses(narrow)]]'>
|
||||
<div class='ha-form flex'>
|
||||
<paper-input label="Entity ID" autofocus required value='{{entityId}}'></paper-input>
|
||||
<paper-input label="State" required value='{{state}}'></paper-input>
|
||||
<paper-textarea label="State attributes (JSON, optional)" value='{{stateAttributes}}'></paper-textarea>
|
||||
<paper-button on-click='handleSetState' raised>Set State</paper-button>
|
||||
</div>
|
||||
|
||||
<div class='sidebar'>
|
||||
<h4>Current entities:</h4>
|
||||
<entity-list on-entity-selected='entitySelected'></entity-list>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</partial-base>
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var reactor = window.hass.reactor;
|
||||
var entityGetters = window.hass.entityGetters;
|
||||
var entityActions = window.hass.entityActions;
|
||||
|
||||
Polymer({
|
||||
is: 'partial-dev-set-state',
|
||||
|
||||
properties: {
|
||||
entityId: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
|
||||
state: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
|
||||
stateAttributes: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
},
|
||||
|
||||
setStateData: function(stateData) {
|
||||
var value = stateData ? JSON.stringify(stateData, null, ' ') : "";
|
||||
|
||||
this.$.inputData.value = value;
|
||||
|
||||
// not according to the spec but it works...
|
||||
this.$.inputDataWrapper.update(this.$.inputData);
|
||||
},
|
||||
|
||||
entitySelected: function(ev) {
|
||||
var state = reactor.evaluate(entityGetters.byId(ev.detail.entityId));
|
||||
|
||||
this.entityId = state.entityId;
|
||||
this.state = state.state;
|
||||
this.stateAttributes = JSON.stringify(state.attributes, null, ' ');
|
||||
},
|
||||
|
||||
handleSetState: function() {
|
||||
var attr;
|
||||
try {
|
||||
attr = this.stateAttributes ? JSON.parse(this.stateAttributes) : {};
|
||||
} catch (err) {
|
||||
alert("Error parsing JSON: " + err);
|
||||
return;
|
||||
}
|
||||
|
||||
entityActions.save({
|
||||
entityId: this.entityId,
|
||||
state: this.state,
|
||||
attributes: attr,
|
||||
});
|
||||
},
|
||||
|
||||
computeFormClasses: function(narrow) {
|
||||
return 'layout ' + (narrow ? 'vertical' : 'horizontal');
|
||||
},
|
||||
});
|
||||
})();
|
||||
</script>
|
|
@ -1,116 +0,0 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="../bower_components/paper-icon-button/paper-icon-button.html">
|
||||
<link rel="import" href="../bower_components/paper-input/paper-input.html">
|
||||
|
||||
<link rel="import" href="./partial-base.html">
|
||||
|
||||
<link rel="import" href="../components/state-history-charts.html">
|
||||
|
||||
<link rel="import" href="../resources/pikaday-js.html">
|
||||
|
||||
<dom-module id="partial-history">
|
||||
<style>
|
||||
.content {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.content.wide {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
paper-input {
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
.narrow paper-input {
|
||||
margin-left: 8px;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<partial-base narrow="[[narrow]]">
|
||||
<span header-title>History</span>
|
||||
|
||||
<paper-icon-button icon="refresh" header-buttons
|
||||
on-click="handleRefreshClick"></paper-icon-button>
|
||||
|
||||
<div class$="[[computeContentClasses(narrow)]]">
|
||||
<paper-input label='Showing entries for' id='datePicker'
|
||||
value='[[selectedDate]]'></paper-input>
|
||||
|
||||
<state-history-charts state-history="[[stateHistory]]"
|
||||
is-loading-data="[[isLoadingData]]"></state-history-charts>
|
||||
</div>
|
||||
</partial-base>
|
||||
</template>
|
||||
</dom-module>
|
||||
<script>
|
||||
(function() {
|
||||
var entityHistoryGetters = window.hass.entityHistoryGetters;
|
||||
var entityHistoryActions = window.hass.entityHistoryActions;
|
||||
var uiActions = window.hass.uiActions;
|
||||
|
||||
Polymer({
|
||||
is: 'partial-history',
|
||||
|
||||
behaviors: [nuclearObserver],
|
||||
|
||||
properties: {
|
||||
narrow: {
|
||||
type: Boolean,
|
||||
},
|
||||
|
||||
isDataLoaded: {
|
||||
type: Boolean,
|
||||
bindNuclear: entityHistoryGetters.hasDataForCurrentDate,
|
||||
observer: 'isDataLoadedChanged',
|
||||
},
|
||||
|
||||
stateHistory: {
|
||||
type: Object,
|
||||
bindNuclear: entityHistoryGetters.entityHistoryForCurrentDate,
|
||||
},
|
||||
|
||||
isLoadingData: {
|
||||
type: Boolean,
|
||||
bindNuclear: entityHistoryGetters.isLoadingEntityHistory,
|
||||
},
|
||||
|
||||
selectedDate: {
|
||||
type: String,
|
||||
value: null,
|
||||
bindNuclear: entityHistoryGetters.currentDate,
|
||||
},
|
||||
},
|
||||
|
||||
isDataLoadedChanged: function(newVal) {
|
||||
if (!newVal) {
|
||||
entityHistoryActions.fetchSelectedDate();
|
||||
}
|
||||
},
|
||||
|
||||
handleRefreshClick: function() {
|
||||
entityHistoryActions.fetchSelectedDate();
|
||||
},
|
||||
|
||||
datepickerFocus: function() {
|
||||
this.datePicker.adjustPosition();
|
||||
this.datePicker.gotoDate(moment('2015-06-30').toDate());
|
||||
},
|
||||
|
||||
attached: function() {
|
||||
this.datePicker = new Pikaday({
|
||||
field: this.$.datePicker.inputElement,
|
||||
onSelect: entityHistoryActions.changeCurrentDate,
|
||||
});
|
||||
},
|
||||
|
||||
detached: function() {
|
||||
this.datePicker.destroy();
|
||||
},
|
||||
|
||||
computeContentClasses: function(narrow) {
|
||||
return 'flex content ' + (narrow ? 'narrow' : 'wide');
|
||||
},
|
||||
});
|
||||
})();
|
||||
</script>
|
|
@ -1,119 +0,0 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
|
||||
<link rel="import" href="../bower_components/paper-icon-button/paper-icon-button.html">
|
||||
<link rel="import" href="../bower_components/paper-input/paper-input.html">
|
||||
|
||||
<link rel="import" href="./partial-base.html">
|
||||
|
||||
<link rel="import" href="../components/ha-logbook.html">
|
||||
<link rel="import" href="../components/loading-box.html">
|
||||
|
||||
<link rel="import" href="../resources/pikaday-js.html">
|
||||
|
||||
<dom-module id="partial-logbook">
|
||||
<style>
|
||||
.selected-date-container {
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
paper-input {
|
||||
max-width: 200px;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<partial-base narrow="[[narrow]]">
|
||||
<span header-title>Logbook</span>
|
||||
|
||||
<paper-icon-button icon="refresh" header-buttons
|
||||
on-click="handleRefresh"></paper-icon-button>
|
||||
|
||||
<div>
|
||||
<div class='selected-date-container'>
|
||||
<paper-input label='Showing entries for' id='datePicker'
|
||||
value='[[selectedDate]]' on-focus='datepickerFocus'></paper-input>
|
||||
|
||||
<loading-box hidden$='[[!isLoading]]'>Loading logbook entries</loading-box>
|
||||
</div>
|
||||
<ha-logbook entries="[[entries]]" hidden$='[[isLoading]]'></ha-logbook>
|
||||
</div>
|
||||
</partial-base>
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var logbookGetters = window.hass.logbookGetters;
|
||||
var logbookActions = window.hass.logbookActions;
|
||||
var uiActions = window.hass.uiActions;
|
||||
var dateToStr = window.hass.util.dateToStr;
|
||||
|
||||
Polymer({
|
||||
is: 'partial-logbook',
|
||||
|
||||
behaviors: [nuclearObserver],
|
||||
|
||||
properties: {
|
||||
narrow: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
selectedDate: {
|
||||
type: String,
|
||||
bindNuclear: logbookGetters.currentDate,
|
||||
},
|
||||
|
||||
isLoading: {
|
||||
type: Boolean,
|
||||
bindNuclear: logbookGetters.isLoadingEntries,
|
||||
},
|
||||
|
||||
isStale: {
|
||||
type: Boolean,
|
||||
bindNuclear: logbookGetters.isCurrentStale,
|
||||
observer: 'isStaleChanged',
|
||||
},
|
||||
|
||||
entries: {
|
||||
type: Array,
|
||||
bindNuclear: [
|
||||
logbookGetters.currentEntries,
|
||||
function(entries) { return entries.toArray(); },
|
||||
],
|
||||
},
|
||||
|
||||
datePicker: {
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
|
||||
isStaleChanged: function(newVal) {
|
||||
if (newVal) {
|
||||
// isLoading wouldn't update without async <_<
|
||||
this.async(
|
||||
function() { logbookActions.fetchDate(this.selectedDate); }, 10);
|
||||
}
|
||||
},
|
||||
|
||||
handleRefresh: function() {
|
||||
logbookActions.fetchDate(this.selectedDate);
|
||||
},
|
||||
|
||||
datepickerFocus: function() {
|
||||
this.datePicker.adjustPosition();
|
||||
this.datePicker.gotoDate(moment('2015-06-30').toDate());
|
||||
},
|
||||
|
||||
attached: function() {
|
||||
this.datePicker = new Pikaday({
|
||||
field: this.$.datePicker.inputElement,
|
||||
onSelect: logbookActions.changeCurrentDate,
|
||||
});
|
||||
},
|
||||
|
||||
detached: function() {
|
||||
this.datePicker.destroy();
|
||||
},
|
||||
});
|
||||
})();
|
||||
</script>
|
|
@ -1,185 +0,0 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
|
||||
<link rel="import" href="../bower_components/paper-icon-button/paper-icon-button.html">
|
||||
|
||||
<link rel="import" href="./partial-base.html">
|
||||
|
||||
<link rel="import" href="../components/state-cards.html">
|
||||
<link rel="import" href="../components/ha-voice-command-progress.html">
|
||||
|
||||
<dom-module id="partial-states">
|
||||
<style>
|
||||
.content-wrapper {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
background-color: #E5E5E5;
|
||||
}
|
||||
|
||||
.content-wrapper ::content .listening {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
|
||||
border-radius: 2px;
|
||||
box-shadow: rgba(0, 0, 0, 0.098) 0px 2px 4px, rgba(0, 0, 0, 0.098) 0px 0px 3px;
|
||||
padding: 16px;
|
||||
background-color: rgba(255, 255, 255, 0.95);
|
||||
line-height: 2em;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.interimTranscript {
|
||||
color: darkgrey;
|
||||
}
|
||||
|
||||
.listening paper-spinner {
|
||||
float: right;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<partial-base narrow="[[narrow]]">
|
||||
<span header-title>[[computeHeaderTitle(filter)]]</span>
|
||||
|
||||
<span header-buttons>
|
||||
<paper-icon-button
|
||||
icon="refresh"
|
||||
class$="[[computeRefreshButtonClass(isFetching)]]"
|
||||
on-click="handleRefresh" hidden$="[[isStreaming]]"
|
||||
></paper-icon-button>
|
||||
<paper-icon-button
|
||||
icon="[[computeListenButtonIcon(isListening)]]"
|
||||
hidden$='[[!canListen]]'
|
||||
on-click="handleListenClick"></paper-icon-button>
|
||||
</span>
|
||||
|
||||
<div class='content-wrapper'>
|
||||
<div class='listening' hidden$="[[!showListenInterface]]"
|
||||
on-click="handleListenClick">
|
||||
<ha-voice-command-progress></ha-voice-command-progress>
|
||||
</div>
|
||||
|
||||
<state-cards states="[[states]]">
|
||||
<h3>Hi there!</h3>
|
||||
<p>
|
||||
It looks like we have nothing to show you right now. It could be that we have not yet discovered all your devices but it is more likely that you have not configured Home Assistant yet.
|
||||
</p>
|
||||
<p>
|
||||
Please see the <a href='https://home-assistant.io/getting-started/' target='_blank'>Getting Started</a> section on how to setup your devices.
|
||||
</p>
|
||||
</state-cards>
|
||||
</div>
|
||||
</partial-base>
|
||||
</template>
|
||||
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
(function(){
|
||||
var configGetters = window.hass.configGetters;
|
||||
var navigationGetters = window.hass.navigationGetters;
|
||||
var voiceGetters = window.hass.voiceGetters;
|
||||
var streamGetters = window.hass.streamGetters;
|
||||
var serviceGetters = window.hass.serviceGetters;
|
||||
var syncGetters = window.hass.syncGetters;
|
||||
|
||||
var syncActions = window.hass.syncActions;
|
||||
var voiceActions = window.hass.voiceActions;
|
||||
|
||||
var entityDomainFilters = window.hass.util.entityDomainFilters;
|
||||
|
||||
Polymer({
|
||||
is: 'partial-states',
|
||||
|
||||
behaviors: [nuclearObserver],
|
||||
|
||||
properties: {
|
||||
narrow: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
filter: {
|
||||
type: String,
|
||||
bindNuclear: navigationGetters.activeFilter,
|
||||
},
|
||||
|
||||
isFetching: {
|
||||
type: Boolean,
|
||||
bindNuclear: syncGetters.isFetching,
|
||||
},
|
||||
|
||||
isStreaming: {
|
||||
type: Boolean,
|
||||
bindNuclear: streamGetters.isStreamingEvents,
|
||||
},
|
||||
|
||||
canListen: {
|
||||
type: Boolean,
|
||||
bindNuclear: [
|
||||
voiceGetters.isVoiceSupported,
|
||||
configGetters.isComponentLoaded('conversation'),
|
||||
function(isVoiceSupported, componentLoaded) {
|
||||
return isVoiceSupported && componentLoaded;
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
isListening: {
|
||||
type: Boolean,
|
||||
bindNuclear: voiceGetters.isListening,
|
||||
},
|
||||
|
||||
showListenInterface: {
|
||||
type: Boolean,
|
||||
bindNuclear: [
|
||||
voiceGetters.isListening,
|
||||
voiceGetters.isTransmitting,
|
||||
function(isListening, isTransmitting) {
|
||||
return isListening || isTransmitting;
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
states: {
|
||||
type: Array,
|
||||
bindNuclear: [
|
||||
navigationGetters.filteredStates,
|
||||
// are here so a change to services causes a re-render.
|
||||
// we need this to decide if we show toggles for states.
|
||||
serviceGetters.entityMap,
|
||||
function(states) { return states.toArray(); },
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
handleRefresh: function() {
|
||||
syncActions.fetchAll();
|
||||
},
|
||||
|
||||
handleListenClick: function() {
|
||||
if (this.isListening) {
|
||||
voiceActions.stop();
|
||||
} else {
|
||||
voiceActions.listen();
|
||||
}
|
||||
},
|
||||
|
||||
computeHeaderTitle: function(filter) {
|
||||
return filter ? entityDomainFilters[filter] : 'States';
|
||||
},
|
||||
|
||||
computeListenButtonIcon: function(isListening) {
|
||||
return isListening ? 'av:mic-off' : 'av:mic';
|
||||
},
|
||||
|
||||
computeRefreshButtonClass: function(isFetching) {
|
||||
if (isFetching) {
|
||||
return 'ha-spin';
|
||||
}
|
||||
},
|
||||
});
|
||||
})();
|
||||
</script>
|
|
@ -1,40 +0,0 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
|
||||
<link rel="import" href="../bower_components/paper-toast/paper-toast.html">
|
||||
|
||||
<dom-module id="notification-manager">
|
||||
<style>
|
||||
paper-toast {
|
||||
z-index: 1;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<paper-toast id="toast" text='{{text}}'></paper-toast>
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var notificationGetters = window.hass.notificationGetters;
|
||||
|
||||
Polymer({
|
||||
is: 'notification-manager',
|
||||
|
||||
behaviors: [nuclearObserver],
|
||||
|
||||
properties: {
|
||||
text: {
|
||||
type: String,
|
||||
bindNuclear: notificationGetters.lastNotificationMessage,
|
||||
observer: 'showNotification',
|
||||
},
|
||||
},
|
||||
|
||||
showNotification: function(newText) {
|
||||
if (newText) {
|
||||
this.$.toast.show();
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
|
@ -1,40 +0,0 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var authGetters = window.hass.authGetters;
|
||||
var streamGetters = window.hass.streamGetters;
|
||||
|
||||
Polymer({
|
||||
is: 'preferences-manager',
|
||||
|
||||
behaviors: [nuclearObserver],
|
||||
|
||||
properties: {
|
||||
authToken: {
|
||||
type: String,
|
||||
bindNuclear: authGetters.currentAuthToken,
|
||||
observer: 'updateStorage',
|
||||
},
|
||||
useStreaming: {
|
||||
type: String,
|
||||
bindNuclear: ,
|
||||
observer: 'updateStorage',
|
||||
},
|
||||
},
|
||||
|
||||
updateStorage: function() {
|
||||
if (!('localStorage' in window)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var storage = localStorage;
|
||||
|
||||
Object.keys(this.properties).forEach(function(prop) {
|
||||
storage[prop] = this.prop;
|
||||
});
|
||||
},
|
||||
|
||||
});
|
||||
})();
|
||||
</script>
|
|
@ -1,52 +0,0 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
|
||||
<dom-module id='more-info-camera'>
|
||||
<style>
|
||||
:host {
|
||||
max-width:640px;
|
||||
}
|
||||
|
||||
.camera-image {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<img class='camera-image' src="[[computeCameraImageUrl(dialogOpen)]]"
|
||||
on-load='imageLoaded' />
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var demo = hass.demo;
|
||||
|
||||
Polymer({
|
||||
is: 'more-info-camera',
|
||||
|
||||
properties: {
|
||||
stateObj: {
|
||||
type: Object,
|
||||
},
|
||||
dialogOpen: {
|
||||
type: Boolean,
|
||||
},
|
||||
},
|
||||
|
||||
imageLoaded: function() {
|
||||
this.fire('iron-resize');
|
||||
},
|
||||
|
||||
computeCameraImageUrl: function(dialogOpen) {
|
||||
if (demo) {
|
||||
return 'http://194.218.96.92/jpg/image.jpg';
|
||||
} else if (dialogOpen) {
|
||||
return '/api/camera_proxy_stream/' + this.stateObj.entityId;
|
||||
} else {
|
||||
// Return an empty image if dialog is not open
|
||||
return 'data:image/gif;base64,R0lGODlhAQABAAAAACw=';
|
||||
}
|
||||
},
|
||||
});
|
||||
})();
|
||||
</script>
|
|
@ -1,125 +0,0 @@
|
|||
<link rel='import' href='../bower_components/polymer/polymer.html'>
|
||||
<link rel='import' href='../bower_components/paper-button/paper-button.html'>
|
||||
|
||||
<link rel='import' href='../components/loading-box.html'>
|
||||
|
||||
<dom-module id='more-info-configurator'>
|
||||
<style>
|
||||
p {
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
p > img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
p.center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
p.error {
|
||||
color: #C62828;
|
||||
}
|
||||
|
||||
p.submit {
|
||||
text-align: center;
|
||||
height: 41px;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div class='layout vertical'>
|
||||
<template is='dom-if' if='[[isConfigurable]]'>
|
||||
|
||||
<p hidden$='[[!stateObj.attributes.description]]'>[[stateObj.attributes.description]]</p>
|
||||
|
||||
<p class='error' hidden$='[[!stateObj.attributes.errors]]'>[[stateObj.attributes.errors]]</p>
|
||||
|
||||
<p class='center' hidden$='[[!stateObj.attributes.description_image]]'>
|
||||
<img src='[[stateObj.attributes.description_image]]' />
|
||||
</p>
|
||||
|
||||
<p class='submit'>
|
||||
<paper-button raised on-click='submitClicked'
|
||||
hidden$='[[isConfiguring]]'>[[submitCaption]]</paper-button>
|
||||
|
||||
<loading-box hidden$='[[!isConfiguring]]'>Configuring</loading-box>
|
||||
</p>
|
||||
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var streamGetters = window.hass.streamGetters;
|
||||
var syncActions = window.hass.syncActions;
|
||||
var serviceActions = window.hass.serviceActions;
|
||||
|
||||
Polymer({
|
||||
is: 'more-info-configurator',
|
||||
|
||||
behaviors: [nuclearObserver],
|
||||
|
||||
properties: {
|
||||
stateObj: {
|
||||
type: Object,
|
||||
},
|
||||
|
||||
action: {
|
||||
type: String,
|
||||
value: 'display',
|
||||
},
|
||||
|
||||
isStreaming: {
|
||||
type: Boolean,
|
||||
bindNuclear: streamGetters.isStreamingEvents,
|
||||
},
|
||||
|
||||
isConfigurable: {
|
||||
type: Boolean,
|
||||
computed: 'computeIsConfigurable(stateObj)',
|
||||
},
|
||||
|
||||
isConfiguring: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
submitCaption: {
|
||||
type: String,
|
||||
computed: 'computeSubmitCaption(stateObj)',
|
||||
},
|
||||
},
|
||||
|
||||
computeIsConfigurable: function(stateObj) {
|
||||
return stateObj.state == 'configure';
|
||||
},
|
||||
|
||||
computeSubmitCaption: function(stateObj) {
|
||||
return stateObj.attributes.submit_caption || 'Set configuration';
|
||||
},
|
||||
|
||||
submitClicked: function() {
|
||||
this.isConfiguring = true;
|
||||
|
||||
var data = {
|
||||
configure_id: this.stateObj.attributes.configure_id
|
||||
};
|
||||
|
||||
serviceActions.callService('configurator', 'configure', data).then(
|
||||
function() {
|
||||
this.isConfiguring = false;
|
||||
|
||||
if (!this.isStreaming) {
|
||||
syncActions.fetchAll();
|
||||
}
|
||||
}.bind(this),
|
||||
|
||||
function() {
|
||||
this.isConfiguring = false;
|
||||
}.bind(this));
|
||||
},
|
||||
});
|
||||
})();
|
||||
</script>
|
|
@ -1,81 +0,0 @@
|
|||
<link rel='import' href='../bower_components/polymer/polymer.html'>
|
||||
|
||||
<link rel='import' href='more-info-default.html'>
|
||||
<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'>
|
||||
<link rel='import' href='more-info-script.html'>
|
||||
<link rel='import' href='more-info-light.html'>
|
||||
<link rel='import' href='more-info-media_player.html'>
|
||||
<link rel='import' href='more-info-camera.html'>
|
||||
|
||||
<dom-module id='more-info-content'>
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var uiUtil = window.hass.uiUtil;
|
||||
|
||||
Polymer({
|
||||
is: 'more-info-content',
|
||||
|
||||
properties: {
|
||||
stateObj: {
|
||||
type: Object,
|
||||
observer: 'stateObjChanged',
|
||||
},
|
||||
|
||||
dialogOpen: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
observer: 'dialogOpenChanged',
|
||||
},
|
||||
},
|
||||
|
||||
dialogOpenChanged: function(newVal, oldVal) {
|
||||
var root = Polymer.dom(this);
|
||||
|
||||
if (root.lastChild) {
|
||||
root.lastChild.dialogOpen = newVal;
|
||||
}
|
||||
},
|
||||
|
||||
stateObjChanged: function(newVal, oldVal) {
|
||||
var root = Polymer.dom(this);
|
||||
|
||||
if (!newVal) {
|
||||
if (root.lastChild) {
|
||||
root.removeChild(root.lastChild);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var newMoreInfoType = uiUtil.stateMoreInfoType(newVal);
|
||||
|
||||
if (!oldVal || uiUtil.stateMoreInfoType(oldVal) != newMoreInfoType) {
|
||||
|
||||
if (root.lastChild) {
|
||||
root.removeChild(root.lastChild);
|
||||
}
|
||||
|
||||
var moreInfo = document.createElement('more-info-' + newMoreInfoType);
|
||||
moreInfo.stateObj = newVal;
|
||||
moreInfo.dialogOpen = this.dialogOpen;
|
||||
root.appendChild(moreInfo);
|
||||
|
||||
} else {
|
||||
|
||||
root.lastChild.dialogOpen = this.dialogOpen;
|
||||
root.lastChild.stateObj = newVal;
|
||||
|
||||
}
|
||||
},
|
||||
});
|
||||
})();
|
||||
</script>
|
|
@ -1,49 +0,0 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
|
||||
<dom-module id="more-info-default">
|
||||
<style>
|
||||
.data-entry .value {
|
||||
max-width: 200px;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div class='layout vertical'>
|
||||
<template is='dom-repeat' items="[[computeDisplayAttributes(stateObj)]]" as="attribute">
|
||||
<div class='data-entry layout justified horizontal'>
|
||||
<div class='key'>[[attribute]]</div>
|
||||
<div class='value'>[[getAttributeValue(stateObj, attribute)]]</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var FILTER_KEYS = ['entity_picture', 'friendly_name', 'unit_of_measurement'];
|
||||
|
||||
Polymer({
|
||||
is: 'more-info-default',
|
||||
|
||||
properties: {
|
||||
stateObj: {
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
|
||||
computeDisplayAttributes: function(stateObj) {
|
||||
if (!stateObj) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return Object.keys(stateObj.attributes).filter(function(key) {
|
||||
return FILTER_KEYS.indexOf(key) === -1;
|
||||
});
|
||||
},
|
||||
|
||||
getAttributeValue: function(stateObj, attribute) {
|
||||
return stateObj.attributes[attribute];
|
||||
},
|
||||
});
|
||||
})();
|
||||
</script>
|
|
@ -1,63 +0,0 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
|
||||
<link rel="import" href="../cards/state-card-content.html">
|
||||
|
||||
<dom-module id="more-info-group">
|
||||
<style>
|
||||
.child-card {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.child-card:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<template is='dom-repeat' items="[[states]]" as='state'>
|
||||
<div class='child-card'>
|
||||
<state-card-content state-obj="[[state]]"></state-card-content>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var entityGetters = window.hass.entityGetters;
|
||||
var moreInfoGetters = window.hass.moreInfoGetters;
|
||||
|
||||
Polymer({
|
||||
is: 'more-info-group',
|
||||
|
||||
behaviors: [nuclearObserver],
|
||||
|
||||
properties: {
|
||||
stateObj: {
|
||||
type: Object,
|
||||
},
|
||||
|
||||
states: {
|
||||
type: Array,
|
||||
bindNuclear: [
|
||||
moreInfoGetters.currentEntity,
|
||||
entityGetters.entityMap,
|
||||
function(currentEntity, entities) {
|
||||
// weird bug??
|
||||
if (!currentEntity) {
|
||||
return;
|
||||
}
|
||||
return currentEntity.attributes.entity_id.map(
|
||||
entities.get.bind(entities));
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
updateStates: function() {
|
||||
this.states = this.stateObj && this.stateObj.attributes.entity_id ?
|
||||
stateStore.gets(this.stateObj.attributes.entity_id).toArray() : [];
|
||||
},
|
||||
});
|
||||
})();
|
||||
</script>
|
|
@ -1,111 +0,0 @@
|
|||
<link rel='import' href='../bower_components/polymer/polymer.html'>
|
||||
<link rel='import' href='../bower_components/paper-slider/paper-slider.html'>
|
||||
|
||||
<link rel='import' href='../components/ha-color-picker.html'>
|
||||
|
||||
<dom-module id='more-info-light'>
|
||||
<style>
|
||||
.brightness {
|
||||
margin-bottom: 8px;
|
||||
|
||||
max-height: 0px;
|
||||
overflow: hidden;
|
||||
transition: max-height .5s ease-in;
|
||||
}
|
||||
|
||||
ha-color-picker {
|
||||
display: block;
|
||||
width: 350px;
|
||||
margin: 0 auto;
|
||||
|
||||
max-height: 0px;
|
||||
overflow: hidden;
|
||||
transition: max-height .5s ease-in;
|
||||
}
|
||||
|
||||
.has-brightness .brightness {
|
||||
max-height: 40px;
|
||||
}
|
||||
|
||||
.has-xy_color ha-color-picker {
|
||||
max-height: 500px;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div class$='[[computeClassNames(stateObj)]]'>
|
||||
<div class='brightness center horizontal layout'>
|
||||
<div>Brightness</div>
|
||||
<paper-slider
|
||||
max='255' id='brightness' value='{{brightnessSliderValue}}'
|
||||
on-change='brightnessSliderChanged' class='flex'>
|
||||
</paper-slider>
|
||||
</div>
|
||||
|
||||
<ha-color-picker on-colorselected='colorPicked' width='350' height='200'>
|
||||
</color-picker>
|
||||
</div>
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var serviceActions = window.hass.serviceActions;
|
||||
var uiUtil = window.hass.uiUtil;
|
||||
var ATTRIBUTE_CLASSES = ['brightness', 'xy_color'];
|
||||
|
||||
Polymer({
|
||||
is: 'more-info-light',
|
||||
|
||||
properties: {
|
||||
stateObj: {
|
||||
type: Object,
|
||||
observer: 'stateObjChanged',
|
||||
},
|
||||
|
||||
brightnessSliderValue: {
|
||||
type: Number,
|
||||
value: 0,
|
||||
}
|
||||
},
|
||||
|
||||
stateObjChanged: function(newVal, oldVal) {
|
||||
if (newVal && newVal.state === 'on') {
|
||||
this.brightnessSliderValue = newVal.attributes.brightness;
|
||||
}
|
||||
|
||||
this.async(function() {
|
||||
this.fire('iron-resize');
|
||||
}.bind(this), 500);
|
||||
},
|
||||
|
||||
computeClassNames: function(stateObj) {
|
||||
return uiUtil.attributeClassNames(stateObj, ATTRIBUTE_CLASSES);
|
||||
},
|
||||
|
||||
brightnessSliderChanged: function(ev) {
|
||||
var bri = parseInt(ev.target.value);
|
||||
|
||||
if(isNaN(bri)) return;
|
||||
|
||||
if(bri === 0) {
|
||||
serviceActions.callTurnOff(this.stateObj.entityId);
|
||||
} else {
|
||||
serviceActions.callService('light', 'turn_on', {
|
||||
entity_id: this.stateObj.entityId,
|
||||
brightness: bri
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
colorPicked: function(ev) {
|
||||
var color = ev.detail.rgb;
|
||||
|
||||
serviceActions.callService('light', 'turn_on', {
|
||||
entity_id: this.stateObj.entityId,
|
||||
rgb_color: [color.r, color.g, color.b]
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
})();
|
||||
</script>
|
|
@ -1,210 +0,0 @@
|
|||
<link rel='import' href='../bower_components/polymer/polymer.html'>
|
||||
|
||||
<link rel='import' href='../bower_components/paper-icon-button/paper-icon-button.html'>
|
||||
|
||||
<dom-module id='more-info-media_player'>
|
||||
<style>
|
||||
.media-state {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
paper-icon-button[highlight] {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.volume {
|
||||
margin-bottom: 8px;
|
||||
|
||||
max-height: 0px;
|
||||
overflow: hidden;
|
||||
transition: max-height .5s ease-in;
|
||||
}
|
||||
|
||||
.has-volume_level .volume {
|
||||
max-height: 40px;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div class$='[[computeClassNames(stateObj)]]'>
|
||||
<div class='layout horizontal'>
|
||||
<div class='flex'>
|
||||
<paper-icon-button icon='power-settings-new' highlight$='[[isOff]]'
|
||||
on-tap='handleTogglePower'
|
||||
hidden$='[[computeHidePowerButton(isOff, supportsTurnOn, supportsTurnOff)]]'></paper-icon-button>
|
||||
</div>
|
||||
<div>
|
||||
<template is='dom-if' if='[[!isOff]]'>
|
||||
<paper-icon-button icon='av:skip-previous' on-tap='handlePrevious'
|
||||
hidden$='[[!supportsPreviousTrack]]'></paper-icon-button>
|
||||
<paper-icon-button icon='[[computePlaybackControlIcon(stateObj)]]'
|
||||
on-tap='handlePlaybackControl' highlight></paper-icon-button>
|
||||
<paper-icon-button icon='av:skip-next' on-tap='handleNext'
|
||||
hidden$='[[!supportsNextTrack]]'></paper-icon-button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class='volume center horizontal layout' hidden$='[[!supportsVolumeSet]]'>
|
||||
<paper-icon-button on-tap="handleVolumeTap"
|
||||
icon="[[computeMuteVolumeIcon(isMuted)]]"></paper-icon-button>
|
||||
<paper-slider disabled$='[[isMuted]]'
|
||||
min='0' max='100' value='[[volumeSliderValue]]'
|
||||
on-change='volumeSliderChanged' class='flex'>
|
||||
</paper-slider>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var serviceActions = window.hass.serviceActions;
|
||||
var uiUtil = window.hass.uiUtil;
|
||||
var ATTRIBUTE_CLASSES = ['volume_level'];
|
||||
|
||||
Polymer({
|
||||
is: 'more-info-media_player',
|
||||
|
||||
properties: {
|
||||
stateObj: {
|
||||
type: Object,
|
||||
observer: 'stateObjChanged',
|
||||
},
|
||||
|
||||
isOff: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
isPlaying: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
isMuted: {
|
||||
type: Boolean,
|
||||
value: false
|
||||
},
|
||||
|
||||
volumeSliderValue: {
|
||||
type: Number,
|
||||
value: 0,
|
||||
},
|
||||
|
||||
supportsPause: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
supportsVolumeSet: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
supportsVolumeMute: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
supportsPreviousTrack: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
supportsNextTrack: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
supportsTurnOn: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
supportsTurnOff: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
stateObjChanged: function(newVal) {
|
||||
if (newVal) {
|
||||
this.isOff = newVal.state == 'off';
|
||||
this.isPlaying = newVal.state == 'playing';
|
||||
this.volumeSliderValue = newVal.attributes.volume_level * 100;
|
||||
this.isMuted = newVal.attributes.is_volume_muted;
|
||||
this.supportsPause = (newVal.attributes.supported_media_commands & 1) !== 0;
|
||||
this.supportsVolumeSet = (newVal.attributes.supported_media_commands & 4) !== 0;
|
||||
this.supportsVolumeMute = (newVal.attributes.supported_media_commands & 8) !== 0;
|
||||
this.supportsPreviousTrack = (newVal.attributes.supported_media_commands & 16) !== 0;
|
||||
this.supportsNextTrack = (newVal.attributes.supported_media_commands & 32) !== 0;
|
||||
this.supportsTurnOn = (newVal.attributes.supported_media_commands & 128) !== 0;
|
||||
this.supportsTurnOff = (newVal.attributes.supported_media_commands & 256) !== 0;
|
||||
}
|
||||
|
||||
this.async(function() { this.fire('iron-resize'); }.bind(this), 500);
|
||||
},
|
||||
|
||||
computeClassNames: function(stateObj) {
|
||||
return uiUtil.attributeClassNames(stateObj, ATTRIBUTE_CLASSES);
|
||||
},
|
||||
|
||||
computeIsOff: function(stateObj) {
|
||||
return stateObj.state == 'off';
|
||||
},
|
||||
|
||||
computeMuteVolumeIcon: function(isMuted) {
|
||||
return isMuted ? 'av:volume-off' : 'av:volume-up';
|
||||
},
|
||||
|
||||
computePlaybackControlIcon: function(stateObj) {
|
||||
if (this.isPlaying) {
|
||||
return this.supportsPause ? 'av:pause' : 'av:stop';
|
||||
}
|
||||
return 'av:play-arrow';
|
||||
},
|
||||
|
||||
computeHidePowerButton: function(isOff, supportsTurnOn, supportsTurnOff) {
|
||||
return isOff ? !supportsTurnOn : !supportsTurnOff;
|
||||
},
|
||||
|
||||
handleTogglePower: function() {
|
||||
this.callService(this.isOff ? 'turn_on' : 'turn_off');
|
||||
},
|
||||
|
||||
handlePrevious: function() {
|
||||
this.callService('media_previous_track');
|
||||
},
|
||||
|
||||
handlePlaybackControl: function() {
|
||||
if (this.isPlaying && !this.supportsPause) {
|
||||
alert('This case is not supported yet');
|
||||
}
|
||||
this.callService('media_play_pause');
|
||||
},
|
||||
|
||||
handleNext: function() {
|
||||
this.callService('media_next_track');
|
||||
},
|
||||
|
||||
handleVolumeTap: function() {
|
||||
if (!this.supportsVolumeMute) {
|
||||
return;
|
||||
}
|
||||
this.callService('volume_mute', { is_volume_muted: !this.isMuted });
|
||||
},
|
||||
|
||||
volumeSliderChanged: function(ev) {
|
||||
var volPercentage = parseFloat(ev.target.value);
|
||||
var vol = volPercentage > 0 ? volPercentage / 100 : 0;
|
||||
this.callService('volume_set', { volume_level: vol });
|
||||
},
|
||||
|
||||
callService: function(service, data) {
|
||||
data = data || {};
|
||||
data.entity_id = this.stateObj.entityId;
|
||||
serviceActions.callService('media_player', service, data);
|
||||
},
|
||||
});
|
||||
})();
|
||||
</script>
|
|
@ -1,26 +0,0 @@
|
|||
<link rel='import' href='../bower_components/polymer/polymer.html'>
|
||||
|
||||
<dom-module id='more-info-script'>
|
||||
<template>
|
||||
<div class='layout vertical'>
|
||||
<div class='data-entry layout justified horizontal'>
|
||||
<div class='key'>Last Action</div>
|
||||
<div class='value'>[[stateObj.attributes.last_action]]</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
Polymer({
|
||||
is: 'more-info-script',
|
||||
|
||||
properties: {
|
||||
stateObj: {
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
});
|
||||
})();
|
||||
</script>
|
|
@ -1,71 +0,0 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
|
||||
<link rel="import" href="../components/relative-ha-datetime.html">
|
||||
|
||||
<dom-module id="more-info-sun">
|
||||
<template>
|
||||
<div class='data-entry layout justified horizontal' id='rising'>
|
||||
<div class='key'>
|
||||
Rising <relative-ha-datetime datetime-obj="[[risingDate]]"></relative-ha-datetime>
|
||||
</div>
|
||||
<div class='value'>[[risingTime]]</div>
|
||||
</div>
|
||||
|
||||
<div class='data-entry layout justified horizontal' id='setting'>
|
||||
<div class='key'>
|
||||
Setting <relative-ha-datetime datetime-obj="[[settingDate]]"></relative-ha-datetime>
|
||||
</div>
|
||||
<div class='value'>[[settingTime]]</div>
|
||||
</div>
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var parseDateTime = window.hass.util.parseDateTime;
|
||||
var formatTime = window.hass.uiUtil.formatTime;
|
||||
|
||||
Polymer({
|
||||
is: 'more-info-sun',
|
||||
|
||||
properties: {
|
||||
stateObj: {
|
||||
type: Object,
|
||||
observer: 'stateObjChanged',
|
||||
},
|
||||
|
||||
risingDate: {
|
||||
type: Object,
|
||||
},
|
||||
|
||||
settingDate: {
|
||||
type: Object,
|
||||
},
|
||||
|
||||
risingTime: {
|
||||
type: String,
|
||||
},
|
||||
|
||||
settingTime: {
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
|
||||
stateObjChanged: function() {
|
||||
this.risingDate = parseDateTime(this.stateObj.attributes.next_rising);
|
||||
this.risingTime = formatTime(this.risingDate);
|
||||
|
||||
this.settingDate = parseDateTime(this.stateObj.attributes.next_setting);
|
||||
this.settingTime = formatTime(this.settingDate);
|
||||
|
||||
var root = Polymer.dom(this);
|
||||
|
||||
if(self.risingDate > self.settingDate) {
|
||||
root.appendChild(this.$.rising);
|
||||
} else {
|
||||
root.appendChild(this.$.setting);
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
|
@ -1,129 +0,0 @@
|
|||
<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'>
|
||||
|
||||
<dom-module id='more-info-thermostat'>
|
||||
<style>
|
||||
paper-slider {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.away-mode-toggle {
|
||||
display: none;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.has-away_mode .away-mode-toggle {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div class$='[[computeClassNames(stateObj)]]'>
|
||||
<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 class='center horizontal layout'>
|
||||
<div class='flex'>Away Mode</div>
|
||||
<paper-toggle-button checked='[[awayToggleChecked]]' on-change='toggleChanged'>
|
||||
</paper-toggle-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var temperatureUnits = window.hass.util.temperatureUnits;
|
||||
var serviceActions = window.hass.serviceActions;
|
||||
var uiUtil = window.hass.uiUtil;
|
||||
var ATTRIBUTE_CLASSES = ['away_mode'];
|
||||
|
||||
Polymer({
|
||||
is: 'more-info-thermostat',
|
||||
|
||||
properties: {
|
||||
stateObj: {
|
||||
type: Object,
|
||||
observer: 'stateObjChanged',
|
||||
},
|
||||
|
||||
tempMin: {
|
||||
type: Number,
|
||||
},
|
||||
|
||||
tempMax: {
|
||||
type: Number,
|
||||
},
|
||||
|
||||
targetTemperatureSliderValue: {
|
||||
type: Number,
|
||||
},
|
||||
|
||||
awayToggleChecked: {
|
||||
type: Boolean,
|
||||
},
|
||||
},
|
||||
|
||||
stateObjChanged: function(newVal, oldVal) {
|
||||
this.targetTemperatureSliderValue = this.stateObj.state;
|
||||
this.awayToggleChecked = this.stateObj.attributes.away_mode == 'on';
|
||||
|
||||
if (this.stateObj.attributes.unit_of_measurement ===
|
||||
temperatureUnits.UNIT_TEMP_F) {
|
||||
this.tempMin = 45;
|
||||
this.tempMax = 95;
|
||||
} else {
|
||||
this.tempMin = 7;
|
||||
this.tempMax = 35;
|
||||
}
|
||||
},
|
||||
|
||||
computeClassNames: function(stateObj) {
|
||||
return uiUtil.attributeClassNames(stateObj, ATTRIBUTE_CLASSES);
|
||||
},
|
||||
|
||||
targetTemperatureSliderChanged: function(ev) {
|
||||
var temp = parseInt(ev.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);
|
||||
}
|
||||
},
|
||||
|
||||
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.stateObjChanged(this.stateObj);
|
||||
}.bind(this));
|
||||
},
|
||||
});
|
||||
})();
|
||||
</script>
|
|
@ -1,91 +0,0 @@
|
|||
<link rel="import" href="../bower_components/iron-iconset-svg/iron-iconset-svg.html">
|
||||
|
||||
<link rel="import" href="../bower_components/iron-icons/iron-icons.html">
|
||||
<link rel="import" href="../bower_components/iron-icons/social-icons.html">
|
||||
<link rel="import" href="../bower_components/iron-icons/image-icons.html">
|
||||
<link rel="import" href="../bower_components/iron-icons/hardware-icons.html">
|
||||
<link rel="import" href="../bower_components/iron-icons/av-icons.html">
|
||||
|
||||
<iron-iconset-svg name="homeassistant-100" size="100">
|
||||
<svg><defs>
|
||||
<!--
|
||||
Thermostat icon created by Scott Lewis from the Noun Project
|
||||
Licensed under CC BY 3.0 - http://creativecommons.org/licenses/by/3.0/us/
|
||||
-->
|
||||
<g id="thermostat"><path d="M66.861,60.105V17.453c0-9.06-7.347-16.405-16.408-16.405c-9.06,0-16.404,7.345-16.404,16.405v42.711 c-4.04,4.14-6.533,9.795-6.533,16.035c0,12.684,10.283,22.967,22.967,22.967c12.682,0,22.964-10.283,22.964-22.967 C73.447,69.933,70.933,64.254,66.861,60.105z M60.331,20.38h-13.21v6.536h6.63v6.539h-6.63v6.713h6.63v6.538h-6.63v6.5h6.63v6.536 h-6.63v7.218c-3.775,1.373-6.471,4.993-6.471,9.24h-6.626c0-5.396,2.598-10.182,6.61-13.185V17.446c0-0.038,0.004-0.075,0.004-0.111 l-0.004-0.007c0-5.437,4.411-9.846,9.849-9.846c5.438,0,9.848,4.409,9.848,9.846V20.38z"/></g>
|
||||
</defs></svg>
|
||||
</iron-iconset-svg>
|
||||
|
||||
<iron-iconset-svg name="homeassistant-24" size="24">
|
||||
<svg><defs>
|
||||
<!--
|
||||
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
Code distributed by Google as part of the polymer project is also
|
||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
-->
|
||||
<g id="group"><path d="M9 12c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm5-3c0-1.1-.9-2-2-2s-2 .9-2 2 .9 2 2 2 2-.9 2-2zm-2-7c-5.52 0-10 4.48-10 10s4.48 10 10 10 10-4.48 10-10-4.48-10-10-10zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm3-8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"/></g>
|
||||
</defs></svg>
|
||||
</iron-iconset-svg>
|
||||
|
||||
<script>
|
||||
window.hass.uiUtil.domainIcon = function(domain, state) {
|
||||
switch(domain) {
|
||||
case "homeassistant":
|
||||
return "home";
|
||||
|
||||
case "group":
|
||||
return "homeassistant-24:group";
|
||||
|
||||
case "device_tracker":
|
||||
return "social:person";
|
||||
|
||||
case "switch":
|
||||
return "image:flash-on";
|
||||
|
||||
case "media_player":
|
||||
var icon = "hardware:cast";
|
||||
|
||||
if (state && state !== "off" && state !== 'idle') {
|
||||
icon += "-connected";
|
||||
}
|
||||
|
||||
return icon;
|
||||
|
||||
case "sun":
|
||||
return "image:wb-sunny";
|
||||
|
||||
case "light":
|
||||
return "image:wb-incandescent";
|
||||
|
||||
case "simple_alarm":
|
||||
return "social:notifications";
|
||||
|
||||
case "notify":
|
||||
return "announcement";
|
||||
|
||||
case "thermostat":
|
||||
return "homeassistant-100:thermostat";
|
||||
|
||||
case "sensor":
|
||||
return "visibility";
|
||||
|
||||
case "configurator":
|
||||
return "settings";
|
||||
|
||||
case "conversation":
|
||||
return "av:hearing";
|
||||
|
||||
case "script":
|
||||
return "description";
|
||||
|
||||
case 'scene':
|
||||
return 'social:pages';
|
||||
|
||||
default:
|
||||
return "bookmark";
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -1,54 +0,0 @@
|
|||
<script src="../home-assistant-js/dist/homeassistant.min.js"></script>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var DOMAINS_WITH_CARD = ['thermostat', 'configurator', 'scene', 'media_player'];
|
||||
var DOMAINS_WITH_MORE_INFO = [
|
||||
'light', 'group', 'sun', 'configurator', 'thermostat', 'script', 'media_player', 'camera'
|
||||
];
|
||||
|
||||
var reactor = window.hass.reactor;
|
||||
var serviceGetters = window.hass.serviceGetters;
|
||||
var authActions = window.hass.authActions;
|
||||
var preferences = window.hass.localStoragePreferences;
|
||||
|
||||
window.hass.uiActions = {
|
||||
validateAuth: function(authToken, rememberAuth) {
|
||||
authActions.validate(authToken, {
|
||||
rememberAuth: rememberAuth,
|
||||
useStreaming: preferences.useStreaming,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
// UI specific util methods
|
||||
window.hass.uiUtil = {
|
||||
stateCardType: function(state) {
|
||||
if(DOMAINS_WITH_CARD.indexOf(state.domain) !== -1) {
|
||||
return state.domain;
|
||||
} else if(reactor.evaluate(serviceGetters.canToggle(state.entityId))) {
|
||||
return "toggle";
|
||||
} else {
|
||||
return "display";
|
||||
}
|
||||
},
|
||||
|
||||
stateMoreInfoType: function(state) {
|
||||
if(DOMAINS_WITH_MORE_INFO.indexOf(state.domain) !== -1) {
|
||||
return state.domain;
|
||||
} else {
|
||||
return 'default';
|
||||
}
|
||||
},
|
||||
|
||||
attributeClassNames: function(stateObj, attributes) {
|
||||
if (!stateObj) return '';
|
||||
return attributes.map(function(attribute) {
|
||||
return attribute in stateObj.attributes ? 'has-' + attribute : '';
|
||||
}).join(' ');
|
||||
},
|
||||
};
|
||||
})();
|
||||
</script>
|
||||
|
||||
<link rel="import" href="./moment-js.html">
|
|
@ -1,46 +0,0 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
|
||||
<style is="custom-style">
|
||||
:root {
|
||||
--dark-primary-color: #0288D1;
|
||||
--default-primary-color: #03A9F4;
|
||||
--light-primary-color: #B3E5FC;
|
||||
--text-primary-color: #ffffff;
|
||||
--accent-color: #FF9800;
|
||||
--primary-background-color: #ffffff;
|
||||
--primary-text-color: #212121;
|
||||
--secondary-text-color: #727272;
|
||||
--disabled-text-color: #bdbdbd;
|
||||
--divider-color: #B6B6B6;
|
||||
|
||||
--paper-toggle-button-checked-ink-color: #039be5;
|
||||
--paper-toggle-button-checked-button-color: #039be5;
|
||||
--paper-toggle-button-checked-bar-color: #039be5;
|
||||
}
|
||||
|
||||
@-webkit-keyframes ha-spin {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(359deg);
|
||||
transform: rotate(359deg);
|
||||
}
|
||||
}
|
||||
@keyframes ha-spin {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(359deg);
|
||||
transform: rotate(359deg);
|
||||
}
|
||||
}
|
||||
|
||||
body /deep/ .ha-spin {
|
||||
-webkit-animation: ha-spin 2s infinite linear;
|
||||
animation: ha-spin 2s infinite linear;
|
||||
}
|
||||
</style>
|
|
@ -1,5 +0,0 @@
|
|||
<!--
|
||||
Wrapping JS in an HTML file will prevent it from being loaded twice.
|
||||
-->
|
||||
|
||||
<script src="../bower_components/lodash/lodash.min.js"></script>
|
|
@ -1,20 +0,0 @@
|
|||
<!--
|
||||
Wrapping JS in an HTML file will prevent it from being loaded twice.
|
||||
-->
|
||||
|
||||
<script src="../bower_components/moment/min/moment.min.js"></script>
|
||||
|
||||
<script>
|
||||
window.hass.uiUtil.formatTime = function(dateObj) {
|
||||
return moment(dateObj).format('LT');
|
||||
};
|
||||
|
||||
window.hass.uiUtil.formatDateTime = function(dateObj) {
|
||||
return moment(dateObj).format('lll');
|
||||
};
|
||||
|
||||
window.hass.uiUtil.formatDate = function(dateObj) {
|
||||
return moment(dateObj).format('ll');
|
||||
};
|
||||
|
||||
</script>
|
|
@ -1,2 +0,0 @@
|
|||
<script src="../bower_components/pikaday/pikaday.js"></script>
|
||||
<link href="../bower_components/pikaday/css/pikaday.css" media="all" rel="stylesheet" />
|
|
@ -1,42 +0,0 @@
|
|||
<script>
|
||||
|
||||
(function() {
|
||||
var NuclearObserver = function NuclearObserver(reactor) {
|
||||
return {
|
||||
|
||||
attached: function() {
|
||||
var component = this;
|
||||
this.__unwatchFns = Object.keys(component.properties).reduce(
|
||||
function(unwatchFns, key) {
|
||||
if (!('bindNuclear' in component.properties[key])) {
|
||||
return unwatchFns;
|
||||
}
|
||||
var getter = component.properties[key].bindNuclear;
|
||||
|
||||
if (!getter) {
|
||||
throw 'Undefined getter specified for key ' + key;
|
||||
}
|
||||
|
||||
// console.log(key, getter);
|
||||
|
||||
component[key] = reactor.evaluate(getter);
|
||||
|
||||
return unwatchFns.concat(reactor.observe(getter, function(val) {
|
||||
// console.log('New value for', key, val);
|
||||
component[key] = val;
|
||||
}));
|
||||
}, []);
|
||||
},
|
||||
|
||||
detached: function() {
|
||||
while (this.__unwatchFns.length) {
|
||||
this.__unwatchFns.shift()();
|
||||
}
|
||||
},
|
||||
|
||||
};
|
||||
};
|
||||
window.nuclearObserver = NuclearObserver(window.hass.reactor);
|
||||
})();
|
||||
|
||||
</script>
|
|
@ -0,0 +1,2 @@
|
|||
""" DO NOT MODIFY. Auto-generated by build_frontend script """
|
||||
VERSION = ""
|
|
@ -99,7 +99,7 @@ LIGHT_PROFILES_FILE = "light_profiles.csv"
|
|||
DISCOVERY_PLATFORMS = {
|
||||
wink.DISCOVER_LIGHTS: 'wink',
|
||||
isy994.DISCOVER_LIGHTS: 'isy994',
|
||||
discovery.services.PHILIPS_HUE: 'hue',
|
||||
discovery.SERVICE_HUE: 'hue',
|
||||
}
|
||||
|
||||
PROP_TO_ATTR = {
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
""" Support for Hue lights. """
|
||||
""" Support for Wink lights. """
|
||||
import logging
|
||||
|
||||
# pylint: disable=no-name-in-module, import-error
|
||||
import homeassistant.external.wink.pywink as pywink
|
||||
|
||||
from homeassistant.components.light import ATTR_BRIGHTNESS
|
||||
from homeassistant.components.wink import WinkToggleDevice
|
||||
from homeassistant.const import CONF_ACCESS_TOKEN
|
||||
|
@ -11,6 +8,8 @@ from homeassistant.const import CONF_ACCESS_TOKEN
|
|||
|
||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||
""" Find and return Wink lights. """
|
||||
import pywink
|
||||
|
||||
token = config.get(CONF_ACCESS_TOKEN)
|
||||
|
||||
if not pywink.is_token_set() and token is None:
|
||||
|
|
|
@ -24,7 +24,7 @@ SCAN_INTERVAL = 30
|
|||
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
||||
|
||||
DISCOVERY_PLATFORMS = {
|
||||
discovery.services.GOOGLE_CAST: 'cast',
|
||||
discovery.SERVICE_CAST: 'cast',
|
||||
}
|
||||
|
||||
SERVICE_YOUTUBE_VIDEO = 'play_youtube_video'
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
"""
|
||||
homeassistant.components.sensor.forecast
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Forecast.io service.
|
||||
|
||||
Configuration:
|
||||
|
@ -121,10 +120,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
|
||||
# pylint: disable=too-few-public-methods
|
||||
class ForeCastSensor(Entity):
|
||||
""" Implements an OpenWeatherMap sensor. """
|
||||
""" Implements an Forecast.io sensor. """
|
||||
|
||||
def __init__(self, weather_data, sensor_type, unit):
|
||||
self.client_name = 'Forecast'
|
||||
self.client_name = 'Weather'
|
||||
self._name = SENSOR_TYPES[sensor_type][0]
|
||||
self.forecast_client = weather_data
|
||||
self._unit = unit
|
||||
|
@ -135,7 +134,7 @@ class ForeCastSensor(Entity):
|
|||
|
||||
@property
|
||||
def name(self):
|
||||
return '{} - {}'.format(self.client_name, self._name)
|
||||
return '{} {}'.format(self.client_name, self._name)
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
|
@ -157,10 +156,6 @@ class ForeCastSensor(Entity):
|
|||
try:
|
||||
if self.type == 'summary':
|
||||
self._state = data.summary
|
||||
# elif self.type == 'sunrise_time':
|
||||
# self._state = data.sunriseTime
|
||||
# elif self.type == 'sunset_time':
|
||||
# self._state = data.sunsetTime
|
||||
elif self.type == 'precip_intensity':
|
||||
if data.precipIntensity == 0:
|
||||
self._state = 'None'
|
||||
|
|
|
@ -21,9 +21,6 @@ Port of your connection to your MySensors device.
|
|||
"""
|
||||
import logging
|
||||
|
||||
# pylint: disable=no-name-in-module, import-error
|
||||
import homeassistant.external.pymysensors.mysensors.mysensors as mysensors
|
||||
import homeassistant.external.pymysensors.mysensors.const as const
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
from homeassistant.const import (
|
||||
|
@ -39,12 +36,16 @@ ATTR_NODE_ID = "node_id"
|
|||
ATTR_CHILD_ID = "child_id"
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
REQUIREMENTS = ['pyserial>=2.7']
|
||||
REQUIREMENTS = ['https://github.com/theolind/pymysensors/archive/master.zip'
|
||||
'#egg=pymysensors-0.1']
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
""" Setup the mysensors platform. """
|
||||
|
||||
import mysensors.mysensors as mysensors
|
||||
import mysensors.const as const
|
||||
|
||||
devices = {} # keep track of devices added to HA
|
||||
# Just assume celcius means that the user wants metric for now.
|
||||
# It may make more sense to make this a global config option in the future.
|
||||
|
@ -69,7 +70,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
name = '{} {}.{}'.format(sensor.sketch_name, nid, child.id)
|
||||
node[child_id][value_type] = \
|
||||
MySensorsNodeValue(
|
||||
nid, child_id, name, value_type, is_metric)
|
||||
nid, child_id, name, value_type, is_metric, const)
|
||||
new_devices.append(node[child_id][value_type])
|
||||
else:
|
||||
node[child_id][value_type].update_sensor(
|
||||
|
@ -102,8 +103,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
|
||||
class MySensorsNodeValue(Entity):
|
||||
""" Represents the value of a MySensors child node. """
|
||||
# pylint: disable=too-many-arguments
|
||||
def __init__(self, node_id, child_id, name, value_type, metric):
|
||||
# pylint: disable=too-many-arguments, too-many-instance-attributes
|
||||
def __init__(self, node_id, child_id, name, value_type, metric, const):
|
||||
self._name = name
|
||||
self.node_id = node_id
|
||||
self.child_id = child_id
|
||||
|
@ -111,6 +112,7 @@ class MySensorsNodeValue(Entity):
|
|||
self.value_type = value_type
|
||||
self.metric = metric
|
||||
self._value = ''
|
||||
self.const = const
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
|
@ -130,11 +132,11 @@ class MySensorsNodeValue(Entity):
|
|||
@property
|
||||
def unit_of_measurement(self):
|
||||
""" Unit of measurement of this entity. """
|
||||
if self.value_type == const.SetReq.V_TEMP:
|
||||
if self.value_type == self.const.SetReq.V_TEMP:
|
||||
return TEMP_CELCIUS if self.metric else TEMP_FAHRENHEIT
|
||||
elif self.value_type == const.SetReq.V_HUM or \
|
||||
self.value_type == const.SetReq.V_DIMMER or \
|
||||
self.value_type == const.SetReq.V_LIGHT_LEVEL:
|
||||
elif self.value_type == self.const.SetReq.V_HUM or \
|
||||
self.value_type == self.const.SetReq.V_DIMMER or \
|
||||
self.value_type == self.const.SetReq.V_LIGHT_LEVEL:
|
||||
return '%'
|
||||
return None
|
||||
|
||||
|
@ -150,8 +152,8 @@ class MySensorsNodeValue(Entity):
|
|||
def update_sensor(self, value, battery_level):
|
||||
""" Update a sensor with the latest value from the controller. """
|
||||
_LOGGER.info("%s value = %s", self._name, value)
|
||||
if self.value_type == const.SetReq.V_TRIPPED or \
|
||||
self.value_type == const.SetReq.V_ARMED:
|
||||
if self.value_type == self.const.SetReq.V_TRIPPED or \
|
||||
self.value_type == self.const.SetReq.V_ARMED:
|
||||
self._value = STATE_ON if int(value) == 1 else STATE_OFF
|
||||
else:
|
||||
self._value = value
|
||||
|
|
|
@ -48,7 +48,7 @@ from homeassistant.util import Throttle
|
|||
from homeassistant.const import (CONF_API_KEY, TEMP_CELCIUS, TEMP_FAHRENHEIT)
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
REQUIREMENTS = ['pywm>=2.2.1']
|
||||
REQUIREMENTS = ['pyowm>=2.2.1']
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
SENSOR_TYPES = {
|
||||
'weather': ['Condition', ''],
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
"""
|
||||
homeassistant.components.sensor.rfxtrx
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Shows sensor values from rfxtrx sensors.
|
||||
|
||||
Possible config keys:
|
||||
device="path to rfxtrx device"
|
||||
|
||||
Example:
|
||||
|
||||
sensor 2:
|
||||
platform: rfxtrx
|
||||
device : /dev/serial/by-id/usb-RFXCOM_RFXtrx433_A1Y0NJGR-if00-port0
|
||||
|
||||
"""
|
||||
import logging
|
||||
from collections import OrderedDict
|
||||
|
||||
from homeassistant.const import (TEMP_CELCIUS)
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
REQUIREMENTS = ['https://github.com/Danielhiversen/pyRFXtrx/archive/master.zip'
|
||||
'#RFXtrx>=0.15']
|
||||
|
||||
DATA_TYPES = OrderedDict([
|
||||
('Temperature', TEMP_CELCIUS),
|
||||
('Humidity', '%'),
|
||||
('Barometer', ''),
|
||||
('Wind direction', ''),
|
||||
('Rain rate', '')])
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
""" Setup the rfxtrx platform. """
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
sensors = {} # keep track of sensors added to HA
|
||||
|
||||
def sensor_update(event):
|
||||
""" Callback for sensor updates from the RFXtrx gateway. """
|
||||
if event.device.id_string in sensors:
|
||||
sensors[event.device.id_string].event = event
|
||||
else:
|
||||
logger.info("adding new sensor: %s", event.device.type_string)
|
||||
new_sensor = RfxtrxSensor(event)
|
||||
sensors[event.device.id_string] = new_sensor
|
||||
add_devices([new_sensor])
|
||||
try:
|
||||
import RFXtrx as rfxtrx
|
||||
except ImportError:
|
||||
logger.exception(
|
||||
"Failed to import rfxtrx")
|
||||
return False
|
||||
|
||||
device = config.get("device", "")
|
||||
rfxtrx.Core(device, sensor_update)
|
||||
|
||||
|
||||
class RfxtrxSensor(Entity):
|
||||
""" Represents a Rfxtrx Sensor. """
|
||||
|
||||
def __init__(self, event):
|
||||
self.event = event
|
||||
|
||||
self._unit_of_measurement = None
|
||||
self._data_type = None
|
||||
for data_type in DATA_TYPES:
|
||||
if data_type in self.event.values:
|
||||
self._unit_of_measurement = DATA_TYPES[data_type]
|
||||
self._data_type = data_type
|
||||
break
|
||||
|
||||
id_string = int(event.device.id_string.replace(":", ""), 16)
|
||||
self._name = "{} {} ({})".format(self._data_type,
|
||||
self.event.device.type_string,
|
||||
id_string)
|
||||
|
||||
def __str__(self):
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
if self._data_type:
|
||||
return self.event.values[self._data_type]
|
||||
return None
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" Get the mame of the sensor. """
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def state_attributes(self):
|
||||
return self.event.values
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
return self._unit_of_measurement
|
|
@ -77,7 +77,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
try:
|
||||
sensor_name = config[ts_sensor.id]
|
||||
except KeyError:
|
||||
if 'only_named' in config:
|
||||
if util.convert(config.get('only_named'), bool, False):
|
||||
continue
|
||||
sensor_name = str(ts_sensor.id)
|
||||
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
""" Support for Wink sensors. """
|
||||
import logging
|
||||
|
||||
# pylint: disable=no-name-in-module, import-error
|
||||
import homeassistant.external.wink.pywink as pywink
|
||||
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.const import CONF_ACCESS_TOKEN, STATE_OPEN, STATE_CLOSED
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
""" Sets up the Wink platform. """
|
||||
import pywink
|
||||
|
||||
if discovery_info is None:
|
||||
token = config.get(CONF_ACCESS_TOKEN)
|
||||
|
||||
|
|
|
@ -22,21 +22,18 @@ The sun event need to have the type 'sun', which service to call, which event
|
|||
import logging
|
||||
from datetime import timedelta
|
||||
|
||||
try:
|
||||
import ephem
|
||||
except ImportError:
|
||||
# Will be fixed during setup
|
||||
ephem = None
|
||||
|
||||
import homeassistant.util as util
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.components.scheduler import ServiceEventListener
|
||||
|
||||
DEPENDENCIES = []
|
||||
REQUIREMENTS = ['pyephem>=3.7']
|
||||
REQUIREMENTS = ['astral>=0.8.1']
|
||||
DOMAIN = "sun"
|
||||
ENTITY_ID = "sun.sun"
|
||||
|
||||
CONF_ELEVATION = 'elevation'
|
||||
|
||||
STATE_ABOVE_HORIZON = "above_horizon"
|
||||
STATE_BELOW_HORIZON = "below_horizon"
|
||||
|
||||
|
@ -99,24 +96,43 @@ def next_rising_utc(hass, entity_id=None):
|
|||
|
||||
def setup(hass, config):
|
||||
""" Tracks the state of the sun. """
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
global ephem # pylint: disable=invalid-name
|
||||
if ephem is None:
|
||||
import ephem as ephem_
|
||||
ephem = ephem_
|
||||
|
||||
if None in (hass.config.latitude, hass.config.longitude):
|
||||
logger.error("Latitude or longitude not set in Home Assistant config")
|
||||
_LOGGER.error("Latitude or longitude not set in Home Assistant config")
|
||||
return False
|
||||
|
||||
try:
|
||||
sun = Sun(hass, str(hass.config.latitude), str(hass.config.longitude))
|
||||
except ValueError:
|
||||
# Raised when invalid latitude or longitude is given to Observer
|
||||
logger.exception("Invalid value for latitude or longitude")
|
||||
latitude = util.convert(hass.config.latitude, float)
|
||||
longitude = util.convert(hass.config.longitude, float)
|
||||
errors = []
|
||||
|
||||
if latitude is None:
|
||||
errors.append('Latitude needs to be a decimal value')
|
||||
elif -90 > latitude < 90:
|
||||
errors.append('Latitude needs to be -90 .. 90')
|
||||
|
||||
if longitude is None:
|
||||
errors.append('Longitude needs to be a decimal value')
|
||||
elif -180 > longitude < 180:
|
||||
errors.append('Longitude needs to be -180 .. 180')
|
||||
|
||||
if errors:
|
||||
_LOGGER.error('Invalid configuration received: %s', ", ".join(errors))
|
||||
return False
|
||||
|
||||
platform_config = config.get(DOMAIN, {})
|
||||
|
||||
elevation = platform_config.get(CONF_ELEVATION)
|
||||
|
||||
from astral import Location, GoogleGeocoder
|
||||
|
||||
location = Location(('', '', latitude, longitude, hass.config.time_zone,
|
||||
elevation or 0))
|
||||
|
||||
if elevation is None:
|
||||
google = GoogleGeocoder()
|
||||
google._get_elevation(location) # pylint: disable=protected-access
|
||||
_LOGGER.info('Retrieved elevation from Google: %s', location.elevation)
|
||||
|
||||
sun = Sun(hass, location)
|
||||
sun.point_in_time_listener(dt_util.utcnow())
|
||||
|
||||
return True
|
||||
|
@ -127,14 +143,9 @@ class Sun(Entity):
|
|||
|
||||
entity_id = ENTITY_ID
|
||||
|
||||
def __init__(self, hass, latitude, longitude):
|
||||
def __init__(self, hass, location):
|
||||
self.hass = hass
|
||||
self.observer = ephem.Observer()
|
||||
# pylint: disable=assigning-non-slot
|
||||
self.observer.lat = latitude
|
||||
# pylint: disable=assigning-non-slot
|
||||
self.observer.long = longitude
|
||||
|
||||
self.location = location
|
||||
self._state = self.next_rising = self.next_setting = None
|
||||
|
||||
@property
|
||||
|
@ -167,17 +178,24 @@ class Sun(Entity):
|
|||
|
||||
def update_as_of(self, utc_point_in_time):
|
||||
""" Calculate sun state at a point in UTC time. """
|
||||
sun = ephem.Sun() # pylint: disable=no-member
|
||||
mod = -1
|
||||
while True:
|
||||
next_rising_dt = self.location.sunrise(
|
||||
utc_point_in_time + timedelta(days=mod), local=False)
|
||||
if next_rising_dt > utc_point_in_time:
|
||||
break
|
||||
mod += 1
|
||||
|
||||
# pylint: disable=assigning-non-slot
|
||||
self.observer.date = ephem.date(utc_point_in_time)
|
||||
mod = -1
|
||||
while True:
|
||||
next_setting_dt = (self.location.sunset(
|
||||
utc_point_in_time + timedelta(days=mod), local=False))
|
||||
if next_setting_dt > utc_point_in_time:
|
||||
break
|
||||
mod += 1
|
||||
|
||||
self.next_rising = self.observer.next_rising(
|
||||
sun,
|
||||
start=utc_point_in_time).datetime().replace(tzinfo=dt_util.UTC)
|
||||
self.next_setting = self.observer.next_setting(
|
||||
sun,
|
||||
start=utc_point_in_time).datetime().replace(tzinfo=dt_util.UTC)
|
||||
self.next_rising = next_rising_dt
|
||||
self.next_setting = next_setting_dt
|
||||
|
||||
def point_in_time_listener(self, now):
|
||||
""" Called when the state of the sun has changed. """
|
||||
|
|
|
@ -29,7 +29,7 @@ MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
|
|||
|
||||
# Maps discovered services to their platforms
|
||||
DISCOVERY_PLATFORMS = {
|
||||
discovery.services.BELKIN_WEMO: 'wemo',
|
||||
discovery.SERVICE_WEMO: 'wemo',
|
||||
wink.DISCOVER_SWITCHES: 'wink',
|
||||
isy994.DISCOVER_SWITCHES: 'isy994',
|
||||
}
|
||||
|
|
|
@ -12,17 +12,8 @@ from homeassistant.components.switch import SwitchDevice
|
|||
# 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
|
||||
except ImportError:
|
||||
logging.getLogger(__name__).exception((
|
||||
"Failed to import pywemo. "
|
||||
"Did you maybe not run `git submodule init` "
|
||||
"and `git submodule update`?"))
|
||||
|
||||
return
|
||||
import pywemo
|
||||
import pywemo.discovery as discovery
|
||||
|
||||
if discovery_info is not None:
|
||||
device = discovery.device_from_description(discovery_info)
|
||||
|
|
|
@ -6,15 +6,14 @@ Support for Wink switches.
|
|||
"""
|
||||
import logging
|
||||
|
||||
# pylint: disable=no-name-in-module, import-error
|
||||
import homeassistant.external.wink.pywink as pywink
|
||||
|
||||
from homeassistant.components.wink import WinkToggleDevice
|
||||
from homeassistant.const import CONF_ACCESS_TOKEN
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
""" Sets up the Wink platform. """
|
||||
import pywink
|
||||
|
||||
if discovery_info is None:
|
||||
token = config.get(CONF_ACCESS_TOKEN)
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ from homeassistant.helpers.entity_component import EntityComponent
|
|||
import homeassistant.util as util
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID, ATTR_TEMPERATURE, STATE_ON, STATE_OFF)
|
||||
ATTR_ENTITY_ID, ATTR_TEMPERATURE, STATE_ON, STATE_OFF, TEMP_CELCIUS)
|
||||
|
||||
DOMAIN = "thermostat"
|
||||
DEPENDENCIES = []
|
||||
|
@ -24,6 +24,8 @@ SERVICE_SET_TEMPERATURE = "set_temperature"
|
|||
|
||||
ATTR_CURRENT_TEMPERATURE = "current_temperature"
|
||||
ATTR_AWAY_MODE = "away_mode"
|
||||
ATTR_MAX_TEMP = "max_temp"
|
||||
ATTR_MIN_TEMP = "min_temp"
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -131,6 +133,9 @@ class ThermostatDevice(Entity):
|
|||
if device_attr is not None:
|
||||
data.update(device_attr)
|
||||
|
||||
data[ATTR_MIN_TEMP] = self.min_temp
|
||||
data[ATTR_MAX_TEMP] = self.max_temp
|
||||
|
||||
return data
|
||||
|
||||
@property
|
||||
|
@ -162,3 +167,13 @@ class ThermostatDevice(Entity):
|
|||
def turn_away_mode_off(self):
|
||||
""" Turns away mode off. """
|
||||
pass
|
||||
|
||||
@property
|
||||
def min_temp(self):
|
||||
""" Return minimum temperature. """
|
||||
return self.hass.config.temperature(7, TEMP_CELCIUS)[0]
|
||||
|
||||
@property
|
||||
def max_temp(self):
|
||||
""" Return maxmum temperature. """
|
||||
return self.hass.config.temperature(35, TEMP_CELCIUS)[0]
|
||||
|
|
|
@ -62,6 +62,7 @@ import logging
|
|||
import datetime
|
||||
import homeassistant.components as core
|
||||
|
||||
import homeassistant.util as util
|
||||
from homeassistant.components.thermostat import ThermostatDevice
|
||||
from homeassistant.const import TEMP_CELCIUS, STATE_ON, STATE_OFF
|
||||
|
||||
|
@ -90,16 +91,18 @@ class HeatControl(ThermostatDevice):
|
|||
self.target_sensor_entity_id = config.get("target_sensor")
|
||||
|
||||
self.time_temp = []
|
||||
for time_temp in list(config.get("time_temp").split(",")):
|
||||
time, temp = time_temp.split(':')
|
||||
time_start, time_end = time.split('-')
|
||||
start_time = datetime.datetime.time(datetime.datetime.
|
||||
strptime(time_start, '%H%M'))
|
||||
end_time = datetime.datetime.time(datetime.datetime.
|
||||
strptime(time_end, '%H%M'))
|
||||
self.time_temp.append((start_time, end_time, float(temp)))
|
||||
if config.get("time_temp"):
|
||||
for time_temp in list(config.get("time_temp").split(",")):
|
||||
time, temp = time_temp.split(':')
|
||||
time_start, time_end = time.split('-')
|
||||
start_time = datetime.datetime.time(
|
||||
datetime.datetime.strptime(time_start, '%H%M'))
|
||||
end_time = datetime.datetime.time(
|
||||
datetime.datetime.strptime(time_end, '%H%M'))
|
||||
self.time_temp.append((start_time, end_time, float(temp)))
|
||||
|
||||
self.min_temp = float(config.get("min_temp"))
|
||||
self._min_temp = util.convert(config.get("min_temp"), float, 0)
|
||||
self._max_temp = util.convert(config.get("max_temp"), float, 100)
|
||||
|
||||
self._manual_sat_temp = None
|
||||
self._away = False
|
||||
|
@ -178,7 +181,7 @@ class HeatControl(ThermostatDevice):
|
|||
if not self._heater_manual_changed:
|
||||
pass
|
||||
else:
|
||||
self.set_temperature(100)
|
||||
self.set_temperature(self.max_temp)
|
||||
|
||||
self._heater_manual_changed = True
|
||||
|
||||
|
@ -194,3 +197,13 @@ class HeatControl(ThermostatDevice):
|
|||
def turn_away_mode_off(self):
|
||||
""" Turns away mode off. """
|
||||
self._away = False
|
||||
|
||||
@property
|
||||
def min_temp(self):
|
||||
""" Return minimum temperature. """
|
||||
return self._min_temp
|
||||
|
||||
@property
|
||||
def max_temp(self):
|
||||
""" Return maxmum temperature. """
|
||||
return self._max_temp
|
||||
|
|
|
@ -6,9 +6,6 @@ Connects to a Wink hub and loads relevant components to control its devices.
|
|||
"""
|
||||
import logging
|
||||
|
||||
# pylint: disable=no-name-in-module, import-error
|
||||
import homeassistant.external.wink.pywink as pywink
|
||||
|
||||
from homeassistant import bootstrap
|
||||
from homeassistant.loader import get_component
|
||||
from homeassistant.helpers import validate_config
|
||||
|
@ -19,6 +16,8 @@ from homeassistant.const import (
|
|||
|
||||
DOMAIN = "wink"
|
||||
DEPENDENCIES = []
|
||||
REQUIREMENTS = ['https://github.com/balloob/python-wink/archive/master.zip'
|
||||
'#pywink>=0.1']
|
||||
|
||||
DISCOVER_LIGHTS = "wink.lights"
|
||||
DISCOVER_SWITCHES = "wink.switches"
|
||||
|
@ -32,6 +31,7 @@ def setup(hass, config):
|
|||
if not validate_config(config, {DOMAIN: [CONF_ACCESS_TOKEN]}, logger):
|
||||
return False
|
||||
|
||||
import pywink
|
||||
pywink.set_bearer_token(config[DOMAIN][CONF_ACCESS_TOKEN])
|
||||
|
||||
# Load components for the devices in the Wink that we support
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
Subproject commit b2cad7c2b959efa8eee9b5ac62d87232bf0b5176
|
|
@ -1 +0,0 @@
|
|||
Subproject commit cd5ef892eeec0ad027727f7e8f757e7f2927da97
|
|
@ -1 +0,0 @@
|
|||
Subproject commit e946ecf7926b9b2adaa1e3127a9738201a1b1fc7
|
|
@ -1 +0,0 @@
|
|||
Subproject commit ca94e41faa48c783f600a2efd550c6b7dae01b0d
|
|
@ -1,408 +0,0 @@
|
|||
__author__ = 'JOHNMCL'
|
||||
|
||||
import json
|
||||
import time
|
||||
|
||||
import requests
|
||||
|
||||
baseUrl = "https://winkapi.quirky.com"
|
||||
|
||||
headers = {}
|
||||
|
||||
|
||||
class wink_sensor_pod(object):
|
||||
""" represents a wink.py sensor
|
||||
json_obj holds the json stat at init (and if there is a refresh it's updated
|
||||
it's the native format for this objects methods
|
||||
and looks like so:
|
||||
{
|
||||
"data": {
|
||||
"last_event": {
|
||||
"brightness_occurred_at": None,
|
||||
"loudness_occurred_at": None,
|
||||
"vibration_occurred_at": None
|
||||
},
|
||||
"model_name": "Tripper",
|
||||
"capabilities": {
|
||||
"sensor_types": [
|
||||
{
|
||||
"field": "opened",
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"field": "battery",
|
||||
"type": "percentage"
|
||||
}
|
||||
]
|
||||
},
|
||||
"manufacturer_device_model": "quirky_ge_tripper",
|
||||
"location": "",
|
||||
"radio_type": "zigbee",
|
||||
"manufacturer_device_id": None,
|
||||
"gang_id": None,
|
||||
"sensor_pod_id": "37614",
|
||||
"subscription": {
|
||||
},
|
||||
"units": {
|
||||
},
|
||||
"upc_id": "184",
|
||||
"hidden_at": None,
|
||||
"last_reading": {
|
||||
"battery_voltage_threshold_2": 0,
|
||||
"opened": False,
|
||||
"battery_alarm_mask": 0,
|
||||
"opened_updated_at": 1421697092.7347496,
|
||||
"battery_voltage_min_threshold_updated_at": 1421697092.7347229,
|
||||
"battery_voltage_min_threshold": 0,
|
||||
"connection": None,
|
||||
"battery_voltage": 25,
|
||||
"battery_voltage_threshold_1": 25,
|
||||
"connection_updated_at": None,
|
||||
"battery_voltage_threshold_3": 0,
|
||||
"battery_voltage_updated_at": 1421697092.7347066,
|
||||
"battery_voltage_threshold_1_updated_at": 1421697092.7347302,
|
||||
"battery_voltage_threshold_3_updated_at": 1421697092.7347434,
|
||||
"battery_voltage_threshold_2_updated_at": 1421697092.7347374,
|
||||
"battery": 1.0,
|
||||
"battery_updated_at": 1421697092.7347553,
|
||||
"battery_alarm_mask_updated_at": 1421697092.734716
|
||||
},
|
||||
"triggers": [
|
||||
],
|
||||
"name": "MasterBathroom",
|
||||
"lat_lng": [
|
||||
37.550773,
|
||||
-122.279182
|
||||
],
|
||||
"uuid": "a2cb868a-dda3-4211-ab73-fc08087aeed7",
|
||||
"locale": "en_us",
|
||||
"device_manufacturer": "quirky_ge",
|
||||
"created_at": 1421523277,
|
||||
"local_id": "2",
|
||||
"hub_id": "88264"
|
||||
},
|
||||
}
|
||||
|
||||
"""
|
||||
def __init__(self, aJSonObj, objectprefix="sensor_pods"):
|
||||
self.jsonState = aJSonObj
|
||||
self.objectprefix = objectprefix
|
||||
|
||||
def __str__(self):
|
||||
return "%s %s %s" % (self.name(), self.deviceId(), self.state())
|
||||
|
||||
def __repr__(self):
|
||||
return "<Wink sensor %s %s %s>" % (self.name(), self.deviceId(), self.state())
|
||||
|
||||
@property
|
||||
def _last_reading(self):
|
||||
return self.jsonState.get('last_reading') or {}
|
||||
|
||||
def name(self):
|
||||
return self.jsonState.get('name', "Unknown Name")
|
||||
|
||||
def state(self):
|
||||
return self._last_reading.get('opened', False)
|
||||
|
||||
def deviceId(self):
|
||||
return self.jsonState.get('sensor_pod_id', self.name())
|
||||
|
||||
def refresh_state_at_hub(self):
|
||||
"""
|
||||
Tell hub to query latest status from device and upload to Wink.
|
||||
PS: Not sure if this even works..
|
||||
"""
|
||||
urlString = baseUrl + "/%s/%s/refresh" % (self.objectprefix, self.deviceId())
|
||||
requests.get(urlString, headers=headers)
|
||||
|
||||
def updateState(self):
|
||||
""" Update state with latest info from Wink API. """
|
||||
urlString = baseUrl + "/%s/%s" % (self.objectprefix, self.deviceId())
|
||||
arequest = requests.get(urlString, headers=headers)
|
||||
self._updateStateFromResponse(arequest.json())
|
||||
|
||||
def _updateStateFromResponse(self, response_json):
|
||||
"""
|
||||
:param response_json: the json obj returned from query
|
||||
:return:
|
||||
"""
|
||||
self.jsonState = response_json.get('data')
|
||||
|
||||
class wink_binary_switch(object):
|
||||
""" represents a wink.py switch
|
||||
json_obj holds the json stat at init (and if there is a refresh it's updated
|
||||
it's the native format for this objects methods
|
||||
and looks like so:
|
||||
|
||||
{
|
||||
"data": {
|
||||
"binary_switch_id": "4153",
|
||||
"name": "Garage door indicator",
|
||||
"locale": "en_us",
|
||||
"units": {},
|
||||
"created_at": 1411614982,
|
||||
"hidden_at": null,
|
||||
"capabilities": {},
|
||||
"subscription": {},
|
||||
"triggers": [],
|
||||
"desired_state": {
|
||||
"powered": false
|
||||
},
|
||||
"manufacturer_device_model": "leviton_dzs15",
|
||||
"manufacturer_device_id": null,
|
||||
"device_manufacturer": "leviton",
|
||||
"model_name": "Switch",
|
||||
"upc_id": "94",
|
||||
"gang_id": null,
|
||||
"hub_id": "11780",
|
||||
"local_id": "9",
|
||||
"radio_type": "zwave",
|
||||
"last_reading": {
|
||||
"powered": false,
|
||||
"powered_updated_at": 1411614983.6153464,
|
||||
"powering_mode": null,
|
||||
"powering_mode_updated_at": null,
|
||||
"consumption": null,
|
||||
"consumption_updated_at": null,
|
||||
"cost": null,
|
||||
"cost_updated_at": null,
|
||||
"budget_percentage": null,
|
||||
"budget_percentage_updated_at": null,
|
||||
"budget_velocity": null,
|
||||
"budget_velocity_updated_at": null,
|
||||
"summation_delivered": null,
|
||||
"summation_delivered_updated_at": null,
|
||||
"sum_delivered_multiplier": null,
|
||||
"sum_delivered_multiplier_updated_at": null,
|
||||
"sum_delivered_divisor": null,
|
||||
"sum_delivered_divisor_updated_at": null,
|
||||
"sum_delivered_formatting": null,
|
||||
"sum_delivered_formatting_updated_at": null,
|
||||
"sum_unit_of_measure": null,
|
||||
"sum_unit_of_measure_updated_at": null,
|
||||
"desired_powered": false,
|
||||
"desired_powered_updated_at": 1417893563.7567682,
|
||||
"desired_powering_mode": null,
|
||||
"desired_powering_mode_updated_at": null
|
||||
},
|
||||
"current_budget": null,
|
||||
"lat_lng": [
|
||||
38.429996,
|
||||
-122.653721
|
||||
],
|
||||
"location": "",
|
||||
"order": 0
|
||||
},
|
||||
"errors": [],
|
||||
"pagination": {}
|
||||
}
|
||||
|
||||
"""
|
||||
def __init__(self, aJSonObj, objectprefix="binary_switches"):
|
||||
self.jsonState = aJSonObj
|
||||
self.objectprefix = objectprefix
|
||||
# Tuple (desired state, time)
|
||||
self._last_call = (0, None)
|
||||
|
||||
def __str__(self):
|
||||
return "%s %s %s" % (self.name(), self.deviceId(), self.state())
|
||||
|
||||
def __repr__(self):
|
||||
return "<Wink switch %s %s %s>" % (self.name(), self.deviceId(), self.state())
|
||||
|
||||
@property
|
||||
def _last_reading(self):
|
||||
return self.jsonState.get('last_reading') or {}
|
||||
|
||||
def name(self):
|
||||
return self.jsonState.get('name', "Unknown Name")
|
||||
|
||||
def state(self):
|
||||
# Optimistic approach to setState:
|
||||
# Within 15 seconds of a call to setState we assume it worked.
|
||||
if self._recent_state_set():
|
||||
return self._last_call[1]
|
||||
|
||||
return self._last_reading.get('powered', False)
|
||||
|
||||
def deviceId(self):
|
||||
return self.jsonState.get('binary_switch_id', self.name())
|
||||
|
||||
def setState(self, state):
|
||||
"""
|
||||
:param state: a boolean of true (on) or false ('off')
|
||||
:return: nothing
|
||||
"""
|
||||
urlString = baseUrl + "/%s/%s" % (self.objectprefix, self.deviceId())
|
||||
values = {"desired_state": {"powered": state}}
|
||||
arequest = requests.put(urlString, data=json.dumps(values), headers=headers)
|
||||
self._updateStateFromResponse(arequest.json())
|
||||
|
||||
self._last_call = (time.time(), state)
|
||||
|
||||
def refresh_state_at_hub(self):
|
||||
"""
|
||||
Tell hub to query latest status from device and upload to Wink.
|
||||
PS: Not sure if this even works..
|
||||
"""
|
||||
urlString = baseUrl + "/%s/%s/refresh" % (self.objectprefix, self.deviceId())
|
||||
requests.get(urlString, headers=headers)
|
||||
|
||||
def updateState(self):
|
||||
""" Update state with latest info from Wink API. """
|
||||
urlString = baseUrl + "/%s/%s" % (self.objectprefix, self.deviceId())
|
||||
arequest = requests.get(urlString, headers=headers)
|
||||
self._updateStateFromResponse(arequest.json())
|
||||
|
||||
def wait_till_desired_reached(self):
|
||||
""" Wait till desired state reached. Max 10s. """
|
||||
if self._recent_state_set():
|
||||
return
|
||||
|
||||
# self.refresh_state_at_hub()
|
||||
tries = 1
|
||||
|
||||
while True:
|
||||
self.updateState()
|
||||
last_read = self._last_reading
|
||||
|
||||
if last_read.get('desired_powered') == last_read.get('powered') \
|
||||
or tries == 5:
|
||||
break
|
||||
|
||||
time.sleep(2)
|
||||
|
||||
tries += 1
|
||||
self.updateState()
|
||||
last_read = self._last_reading
|
||||
|
||||
def _updateStateFromResponse(self, response_json):
|
||||
"""
|
||||
:param response_json: the json obj returned from query
|
||||
:return:
|
||||
"""
|
||||
self.jsonState = response_json.get('data')
|
||||
|
||||
def _recent_state_set(self):
|
||||
return time.time() - self._last_call[0] < 15
|
||||
|
||||
|
||||
class wink_bulb(wink_binary_switch):
|
||||
""" represents a wink.py bulb
|
||||
json_obj holds the json stat at init (and if there is a refresh it's updated
|
||||
it's the native format for this objects methods
|
||||
and looks like so:
|
||||
|
||||
"light_bulb_id": "33990",
|
||||
"name": "downstaurs lamp",
|
||||
"locale": "en_us",
|
||||
"units":{},
|
||||
"created_at": 1410925804,
|
||||
"hidden_at": null,
|
||||
"capabilities":{},
|
||||
"subscription":{},
|
||||
"triggers":[],
|
||||
"desired_state":{"powered": true, "brightness": 1},
|
||||
"manufacturer_device_model": "lutron_p_pkg1_w_wh_d",
|
||||
"manufacturer_device_id": null,
|
||||
"device_manufacturer": "lutron",
|
||||
"model_name": "Caseta Wireless Dimmer & Pico",
|
||||
"upc_id": "3",
|
||||
"hub_id": "11780",
|
||||
"local_id": "8",
|
||||
"radio_type": "lutron",
|
||||
"linked_service_id": null,
|
||||
"last_reading":{
|
||||
"brightness": 1,
|
||||
"brightness_updated_at": 1417823487.490747,
|
||||
"connection": true,
|
||||
"connection_updated_at": 1417823487.4907365,
|
||||
"powered": true,
|
||||
"powered_updated_at": 1417823487.4907532,
|
||||
"desired_powered": true,
|
||||
"desired_powered_updated_at": 1417823485.054675,
|
||||
"desired_brightness": 1,
|
||||
"desired_brightness_updated_at": 1417409293.2591703
|
||||
},
|
||||
"lat_lng":[38.429962, -122.653715],
|
||||
"location": "",
|
||||
"order": 0
|
||||
|
||||
"""
|
||||
jsonState = {}
|
||||
|
||||
def __init__(self, ajsonobj):
|
||||
super().__init__(ajsonobj, "light_bulbs")
|
||||
|
||||
def deviceId(self):
|
||||
return self.jsonState.get('light_bulb_id', self.name())
|
||||
|
||||
def brightness(self):
|
||||
return self._last_reading.get('brightness')
|
||||
|
||||
def setState(self, state, brightness=None):
|
||||
"""
|
||||
:param state: a boolean of true (on) or false ('off')
|
||||
:return: nothing
|
||||
"""
|
||||
urlString = baseUrl + "/light_bulbs/%s" % self.deviceId()
|
||||
values = {
|
||||
"desired_state": {
|
||||
"powered": state
|
||||
}
|
||||
}
|
||||
|
||||
if brightness is not None:
|
||||
values["desired_state"]["brightness"] = brightness
|
||||
|
||||
urlString = baseUrl + "/light_bulbs/%s" % self.deviceId()
|
||||
arequest = requests.put(urlString, data=json.dumps(values), headers=headers)
|
||||
self._updateStateFromResponse(arequest.json())
|
||||
|
||||
self._last_call = (time.time(), state)
|
||||
|
||||
def __repr__(self):
|
||||
return "<Wink Bulb %s %s %s>" % (
|
||||
self.name(), self.deviceId(), self.state())
|
||||
|
||||
|
||||
def get_devices(filter, constructor):
|
||||
arequestUrl = baseUrl + "/users/me/wink_devices"
|
||||
j = requests.get(arequestUrl, headers=headers).json()
|
||||
|
||||
items = j.get('data')
|
||||
|
||||
devices = []
|
||||
for item in items:
|
||||
id = item.get(filter)
|
||||
if (id is not None and item.get("hidden_at") is None):
|
||||
devices.append(constructor(item))
|
||||
|
||||
return devices
|
||||
|
||||
def get_bulbs():
|
||||
return get_devices('light_bulb_id', wink_bulb)
|
||||
|
||||
def get_switches():
|
||||
return get_devices('binary_switch_id', wink_binary_switch)
|
||||
|
||||
def get_sensors():
|
||||
return get_devices('sensor_pod_id', wink_sensor_pod)
|
||||
|
||||
def is_token_set():
|
||||
""" Returns if an auth token has been set. """
|
||||
return bool(headers)
|
||||
|
||||
|
||||
def set_bearer_token(token):
|
||||
global headers
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": "Bearer {}".format(token)
|
||||
}
|
||||
|
||||
if __name__ == "__main__":
|
||||
sw = get_bulbs()
|
||||
lamp = sw[3]
|
||||
lamp.setState(False)
|
|
@ -5,4 +5,5 @@ import sys
|
|||
def is_virtual():
|
||||
""" Return if we run in a virtual environtment. """
|
||||
# Check supports venv && virtualenv
|
||||
return sys.base_prefix != sys.prefix or hasattr(sys, 'real_prefix')
|
||||
return (getattr(sys, 'base_prefix', sys.prefix) != sys.prefix or
|
||||
hasattr(sys, 'real_prefix'))
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
"""Helpers to install PyPi packages."""
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from . import environment as env
|
||||
|
||||
|
@ -11,9 +12,12 @@ def install_package(package, upgrade=False, user=INSTALL_USER):
|
|||
"""Install a package on PyPi. Accepts pip compatible package strings.
|
||||
Return boolean if install successfull."""
|
||||
# Not using 'import pip; pip.main([])' because it breaks the logger
|
||||
args = ['python3', '-m', 'pip', 'install', '--quiet', package]
|
||||
args = [sys.executable, '-m', 'pip', 'install', '--quiet', package]
|
||||
if upgrade:
|
||||
args.append('--upgrade')
|
||||
if user:
|
||||
args.append('--user')
|
||||
return not subprocess.call(args)
|
||||
try:
|
||||
return 0 == subprocess.call(args)
|
||||
except subprocess.SubprocessError:
|
||||
return False
|
||||
|
|
|
@ -5,11 +5,8 @@ pytz>=2015.2
|
|||
|
||||
# Optional, needed for specific components
|
||||
|
||||
# Discovery platform (discovery)
|
||||
zeroconf>=0.16.0
|
||||
|
||||
# Sun (sun)
|
||||
pyephem>=3.7
|
||||
astral>=0.8.1
|
||||
|
||||
# Philips Hue library (lights.hue)
|
||||
phue>=0.8
|
||||
|
@ -77,8 +74,20 @@ python-forecastio>=1.3.3
|
|||
# Firmata Bindings (*.arduino)
|
||||
PyMata==2.07a
|
||||
|
||||
# Mysensors serial gateway
|
||||
pyserial>=2.7
|
||||
#Rfxtrx sensor
|
||||
https://github.com/Danielhiversen/pyRFXtrx/archive/master.zip
|
||||
|
||||
# PyEdimax
|
||||
git+https://github.com/rkabadi/pyedimax.git
|
||||
# Mysensors
|
||||
https://github.com/theolind/pymysensors/archive/master.zip#egg=pymysensors-0.1
|
||||
|
||||
# Netgear (device_tracker.netgear)
|
||||
pynetgear>=0.1
|
||||
|
||||
# Netdisco (discovery)
|
||||
netdisco>=0.1
|
||||
|
||||
# Wemo (switch.wemo)
|
||||
pywemo>=0.1
|
||||
|
||||
# Wink (*.wink)
|
||||
https://github.com/balloob/python-wink/archive/master.zip#pywink>=0.1
|
||||
|
|
|
@ -1,29 +1,19 @@
|
|||
# Builds the frontend for production
|
||||
# Call 'build_frontend demo' to build a demo frontend.
|
||||
|
||||
# If current pwd is scripts, go 1 up.
|
||||
if [ ${PWD##*/} == "scripts" ]; then
|
||||
cd ..
|
||||
fi
|
||||
|
||||
scripts/build_js $1
|
||||
cd homeassistant/components/frontend/www_static/home-assistant-polymer
|
||||
npm install
|
||||
npm run frontend_prod
|
||||
|
||||
# To build the frontend, you need node, bower, vulcanize and html-minifier
|
||||
# npm install -g bower vulcanize html-minifier
|
||||
|
||||
# Install dependencies
|
||||
cd homeassistant/components/frontend/www_static/polymer
|
||||
bower install
|
||||
cd ..
|
||||
cp polymer/bower_components/webcomponentsjs/webcomponents-lite.min.js .
|
||||
|
||||
vulcanize --inline-css --inline-scripts --strip-comments polymer/home-assistant.html > frontend.html
|
||||
|
||||
# html-minifier crashes on frontend, minimize kills the CSS
|
||||
# html-minifier --config-file polymer/html-minifier.conf -o frontend.html frontend.html
|
||||
cp bower_components/webcomponentsjs/webcomponents-lite.min.js ..
|
||||
cp build/frontend.html ..
|
||||
|
||||
# Generate the MD5 hash of the new frontend
|
||||
cd ..
|
||||
cd ../..
|
||||
echo '""" DO NOT MODIFY. Auto-generated by build_frontend script """' > version.py
|
||||
if [ $(command -v md5) ]; then
|
||||
echo 'VERSION = "'`md5 -q www_static/frontend.html`'"' >> version.py
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
# Builds the JS for production
|
||||
|
||||
# If current pwd is scripts, go 1 up.
|
||||
if [ ${PWD##*/} == "scripts" ]; then
|
||||
cd ..
|
||||
fi
|
||||
|
||||
cd homeassistant/components/frontend/www_static/polymer/home-assistant-js
|
||||
|
||||
npm install
|
||||
|
||||
if [ "$1" = "demo" ]; then
|
||||
echo "Building a demo mode build!"
|
||||
npm run demo
|
||||
else
|
||||
npm run prod
|
||||
fi
|
|
@ -1,11 +0,0 @@
|
|||
# Builds the JS for developing, rebuilds when files change
|
||||
|
||||
# If current pwd is scripts, go 1 up.
|
||||
if [ ${PWD##*/} == "scripts" ]; then
|
||||
cd ..
|
||||
fi
|
||||
|
||||
cd homeassistant/components/frontend/www_static/polymer/home-assistant-js
|
||||
|
||||
npm install
|
||||
npm run dev
|
|
@ -12,7 +12,7 @@
|
|||
# Created with: https://gist.github.com/naholyr/4275302#file-new-service-sh
|
||||
#
|
||||
# Installation:
|
||||
# 1) Populate RUNAS and RUNDIR folders
|
||||
# 1) Populate RUNAS and RUNDIR variables
|
||||
# 2) Create Log -- sudo touch /var/log/homeassistant.log
|
||||
# 3) Set Log Ownership -- sudo chown USER:GROUP /var/log/homeassistant.log
|
||||
# 4) Create PID File -- sudo touch /var/run/homeassistant.pid
|
||||
|
@ -29,7 +29,7 @@
|
|||
# If you are not, the SCRIPT variable must be modified to point to the correct
|
||||
# Python environment.
|
||||
|
||||
SCRIPT="./bin/python -m homeassistant"
|
||||
SCRIPT="source bin/activate; ./bin/python -m homeassistant"
|
||||
RUNAS=<USER TO RUN SERVER AS>
|
||||
RUNDIR=<LOCATION OF home-assistant DIR>
|
||||
PIDFILE=/var/run/homeassistant.pid
|
||||
|
|
|
@ -67,7 +67,7 @@ class TestDeviceSunLightTrigger(unittest.TestCase):
|
|||
light.DOMAIN: {CONF_PLATFORM: 'test'}
|
||||
})
|
||||
|
||||
sun.setup(self.hass, {})
|
||||
sun.setup(self.hass, {sun.DOMAIN: {sun.CONF_ELEVATION: 0}})
|
||||
|
||||
def tearDown(self): # pylint: disable=invalid-name
|
||||
""" Stop down stuff we started. """
|
||||
|
|
|
@ -8,7 +8,7 @@ Tests Sun component.
|
|||
import unittest
|
||||
from datetime import timedelta
|
||||
|
||||
import ephem
|
||||
from astral import Astral
|
||||
|
||||
import homeassistant as ha
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
@ -34,29 +34,35 @@ class TestSun(unittest.TestCase):
|
|||
|
||||
def test_setting_rising(self):
|
||||
""" Test retrieving sun setting and rising. """
|
||||
latitude = 32.87336
|
||||
longitude = 117.22743
|
||||
|
||||
# Compare it with the real data
|
||||
self.hass.config.latitude = '32.87336'
|
||||
self.hass.config.longitude = '117.22743'
|
||||
sun.setup(self.hass, None)
|
||||
|
||||
observer = ephem.Observer()
|
||||
observer.lat = '32.87336' # pylint: disable=assigning-non-slot
|
||||
observer.long = '117.22743' # pylint: disable=assigning-non-slot
|
||||
self.hass.config.latitude = latitude
|
||||
self.hass.config.longitude = longitude
|
||||
sun.setup(self.hass, {sun.DOMAIN: {sun.CONF_ELEVATION: 0}})
|
||||
|
||||
astral = Astral()
|
||||
utc_now = dt_util.utcnow()
|
||||
body_sun = ephem.Sun() # pylint: disable=no-member
|
||||
next_rising_dt = observer.next_rising(
|
||||
body_sun, start=utc_now).datetime().replace(tzinfo=dt_util.UTC)
|
||||
next_setting_dt = observer.next_setting(
|
||||
body_sun, start=utc_now).datetime().replace(tzinfo=dt_util.UTC)
|
||||
|
||||
# Home Assistant strips out microseconds
|
||||
# strip it out of the datetime objects
|
||||
next_rising_dt = dt_util.strip_microseconds(next_rising_dt)
|
||||
next_setting_dt = dt_util.strip_microseconds(next_setting_dt)
|
||||
mod = -1
|
||||
while True:
|
||||
next_rising = (astral.sunrise_utc(utc_now +
|
||||
timedelta(days=mod), latitude, longitude))
|
||||
if next_rising > utc_now:
|
||||
break
|
||||
mod += 1
|
||||
|
||||
self.assertEqual(next_rising_dt, sun.next_rising_utc(self.hass))
|
||||
self.assertEqual(next_setting_dt, sun.next_setting_utc(self.hass))
|
||||
mod = -1
|
||||
while True:
|
||||
next_setting = (astral.sunset_utc(utc_now +
|
||||
timedelta(days=mod), latitude, longitude))
|
||||
if next_setting > utc_now:
|
||||
break
|
||||
mod += 1
|
||||
|
||||
self.assertEqual(next_rising, sun.next_rising_utc(self.hass))
|
||||
self.assertEqual(next_setting, sun.next_setting_utc(self.hass))
|
||||
|
||||
# Point it at a state without the proper attributes
|
||||
self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON)
|
||||
|
@ -71,7 +77,7 @@ class TestSun(unittest.TestCase):
|
|||
""" Test if the state changes at next setting/rising. """
|
||||
self.hass.config.latitude = '32.87336'
|
||||
self.hass.config.longitude = '117.22743'
|
||||
sun.setup(self.hass, None)
|
||||
sun.setup(self.hass, {})
|
||||
|
||||
if sun.is_on(self.hass):
|
||||
test_state = sun.STATE_BELOW_HORIZON
|
||||
|
|
Loading…
Reference in New Issue