From 892573e53ed2db6d619730fd5f25c74bd817fd4a Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 23 Jun 2015 12:34:55 +0200 Subject: [PATCH 001/224] remove unused stuff and update the names (same as in owm sensor) --- homeassistant/components/sensor/forecast.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/sensor/forecast.py b/homeassistant/components/sensor/forecast.py index 98e088d5139..bcc5cd3b050 100644 --- a/homeassistant/components/sensor/forecast.py +++ b/homeassistant/components/sensor/forecast.py @@ -1,7 +1,6 @@ """ homeassistant.components.sensor.forecast ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Forecast.io service. Configuration: @@ -113,10 +112,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 @@ -127,7 +126,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): @@ -149,10 +148,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' From db5060b32374c24cbe6e41b7405a16166fde0ecf Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 23 Jun 2015 12:34:55 +0200 Subject: [PATCH 002/224] remove unused stuff and update the names (same as in owm sensor) --- homeassistant/components/sensor/forecast.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/sensor/forecast.py b/homeassistant/components/sensor/forecast.py index 98e088d5139..bcc5cd3b050 100644 --- a/homeassistant/components/sensor/forecast.py +++ b/homeassistant/components/sensor/forecast.py @@ -1,7 +1,6 @@ """ homeassistant.components.sensor.forecast ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Forecast.io service. Configuration: @@ -113,10 +112,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 @@ -127,7 +126,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): @@ -149,10 +148,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' From a34742040c5622216c01797307cd8db2a710b0fe Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 23 Jun 2015 12:34:55 +0200 Subject: [PATCH 003/224] remove unused stuff and update the names (same as in owm sensor) --- homeassistant/components/sensor/forecast.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/sensor/forecast.py b/homeassistant/components/sensor/forecast.py index 98e088d5139..bcc5cd3b050 100644 --- a/homeassistant/components/sensor/forecast.py +++ b/homeassistant/components/sensor/forecast.py @@ -1,7 +1,6 @@ """ homeassistant.components.sensor.forecast ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Forecast.io service. Configuration: @@ -113,10 +112,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 @@ -127,7 +126,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): @@ -149,10 +148,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' From d1e4387997593b4071b79263bb59329e5d50bd75 Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Tue, 7 Jul 2015 23:19:34 -0400 Subject: [PATCH 004/224] Light update to PyISY version 1.0.5 This is a lighter update to version 1.0.5 to fix Issue #201 more immediately. --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index bebc21b3464..0de3e2b9bed 100644 --- a/requirements.txt +++ b/requirements.txt @@ -39,7 +39,7 @@ python-nest>=2.3.1 pydispatcher>=2.0.5 # ISY994 bindings (*.isy994) -PyISY>=1.0.2 +PyISY>=1.0.5 # PSutil (sensor.systemmonitor) psutil>=2.2.1 From 4d12c69d68b3d07423cefdd2f928698d2d53830a Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 15 Jul 2015 18:37:24 -0700 Subject: [PATCH 005/224] Increase robustness dependency installation --- homeassistant/__main__.py | 11 ++++++++--- homeassistant/util/environment.py | 3 ++- homeassistant/util/package.py | 8 ++++++-- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/homeassistant/__main__.py b/homeassistant/__main__.py index 0a575afede0..6f937d6916e 100644 --- a/homeassistant/__main__.py +++ b/homeassistant/__main__.py @@ -7,6 +7,8 @@ import argparse import subprocess 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(): @@ -22,10 +24,13 @@ def validate_python(): 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(): diff --git a/homeassistant/util/environment.py b/homeassistant/util/environment.py index 4227c485290..53364899030 100644 --- a/homeassistant/util/environment.py +++ b/homeassistant/util/environment.py @@ -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')) diff --git a/homeassistant/util/package.py b/homeassistant/util/package.py index ec49893177f..d220a5a7e61 100644 --- a/homeassistant/util/package.py +++ b/homeassistant/util/package.py @@ -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 From 6631ebfdfa5a735f14058be8e76f57af807a555a Mon Sep 17 00:00:00 2001 From: Rohit Kabadi Date: Mon, 20 Jul 2015 20:16:54 -0700 Subject: [PATCH 006/224] - Added git submodule @ https://github.com/rkabadi/pyedimax - Added edimax.py module to interface with Edimax SP-1101W and SP-2101W --- .gitmodules | 3 ++ homeassistant/components/switch/edimax.py | 52 +++++++++++++++++++++++ homeassistant/external/pyedimax | 1 + 3 files changed, 56 insertions(+) create mode 100644 homeassistant/components/switch/edimax.py create mode 160000 homeassistant/external/pyedimax diff --git a/.gitmodules b/.gitmodules index ca0b1f024b8..5d994cf6ceb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,3 +22,6 @@ [submodule "homeassistant/external/pymysensors"] path = homeassistant/external/pymysensors url = https://github.com/theolind/pymysensors +[submodule "homeassistant/external/pyedimax"] + path = homeassistant/external/pyedimax + url = https://github.com/rkabadi/pyedimax diff --git a/homeassistant/components/switch/edimax.py b/homeassistant/components/switch/edimax.py new file mode 100644 index 00000000000..615b52ea80c --- /dev/null +++ b/homeassistant/components/switch/edimax.py @@ -0,0 +1,52 @@ +""" +homeassistant.components.switch.edimax +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Support for Edimax switches. +""" +import logging + +from homeassistant.components.switch import SwitchDevice + + +# pylint: disable=unused-argument +def setup_platform(hass, config, add_devices_callback, discovery_info=None): + """ Find and return Edimax Smart Plugs. """ + try: + # pylint: disable=no-name-in-module, import-error + from homeassistant.external.pyedimax.smartplug import SmartPlug + except ImportError: + logging.getLogger(__name__).exception(( + "Failed to import pyedimax. " + "Did you maybe not run `git submodule init` " + "and `git submodule update`?")) + + return + + + add_devices_callback([ + SmartPlugSwitch(SmartPlug( + host = config.get('host'), + auth=( + config.get('user', 'admin'), + config.get('password', '1234')))) + ]) + + +class SmartPlugSwitch(SwitchDevice): + """ Represents a Edimax Smart Plug switch within Home Assistant. """ + def __init__(self, smartplug): + self.smartplug = smartplug + + @property + def is_on(self): + """ True if switch is on. """ + return self.smartplug.get_state() + + def turn_on(self, **kwargs): + """ Turns the switch on. """ + self.smartplug.state = 'ON' + + def turn_off(self): + """ Turns the switch off. """ + self.smartplug.state = 'OFF' \ No newline at end of file diff --git a/homeassistant/external/pyedimax b/homeassistant/external/pyedimax new file mode 160000 index 00000000000..3815f3bd99f --- /dev/null +++ b/homeassistant/external/pyedimax @@ -0,0 +1 @@ +Subproject commit 3815f3bd99fb9dcd4d9e5e6fc58626f5873e43db From fac194f66cf35c256c234c9e26a8bdeee69919c0 Mon Sep 17 00:00:00 2001 From: Rohit Kabadi Date: Mon, 20 Jul 2015 23:27:25 -0700 Subject: [PATCH 007/224] - Added for smartplug - Added error check for host param in config.yaml - Fixed SmartPlugSwitch is_on method - Edimax smartplug works now! --- homeassistant/components/switch/edimax.py | 26 +++++++++++++++-------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/switch/edimax.py b/homeassistant/components/switch/edimax.py index 615b52ea80c..3754b452a2d 100644 --- a/homeassistant/components/switch/edimax.py +++ b/homeassistant/components/switch/edimax.py @@ -7,7 +7,7 @@ Support for Edimax switches. import logging from homeassistant.components.switch import SwitchDevice - +from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD # pylint: disable=unused-argument def setup_platform(hass, config, add_devices_callback, discovery_info=None): @@ -23,14 +23,16 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): return + host = config.get(CONF_HOST) + auth=(config.get(CONF_USERNAME, 'admin'), + config.get(CONF_PASSWORD, '1234')) - add_devices_callback([ - SmartPlugSwitch(SmartPlug( - host = config.get('host'), - auth=( - config.get('user', 'admin'), - config.get('password', '1234')))) - ]) + if not host: + logging.getLogger(__name__).error('Missing config variable %s', CONF_HOST) + return False + + + add_devices_callback([SmartPlugSwitch(SmartPlug(host, auth))]) class SmartPlugSwitch(SwitchDevice): @@ -38,10 +40,16 @@ class SmartPlugSwitch(SwitchDevice): def __init__(self, smartplug): self.smartplug = smartplug + @property + def name(self): + """ Returns the name of the Smart Plug, if any. """ + #TODO: dynamically get name from device using requests + return 'Edimax Smart Plug' + @property def is_on(self): """ True if switch is on. """ - return self.smartplug.get_state() + return self.smartplug.state == 'ON' def turn_on(self, **kwargs): """ Turns the switch on. """ From 6a7e28cc8565eec9a540cb0d8ea61f75e4372131 Mon Sep 17 00:00:00 2001 From: Rohit Kabadi Date: Sat, 25 Jul 2015 18:46:47 -0700 Subject: [PATCH 008/224] - Added support for getting power on SP2101W devices (returns None on SP1101W) --- .coveragerc | 1 + .../www_static/polymer/home-assistant-js | 2 +- homeassistant/components/switch/edimax.py | 27 +++++++++++++++---- homeassistant/external/netdisco | 2 +- homeassistant/external/pyedimax | 2 +- homeassistant/external/pymysensors | 2 +- homeassistant/external/pynetgear | 2 +- homeassistant/external/pywemo | 2 +- 8 files changed, 29 insertions(+), 11 deletions(-) diff --git a/.coveragerc b/.coveragerc index 39a3dee22bf..a2ad399f1a3 100644 --- a/.coveragerc +++ b/.coveragerc @@ -53,6 +53,7 @@ omit = homeassistant/components/sensor/systemmonitor.py homeassistant/components/sensor/time_date.py homeassistant/components/sensor/transmission.py + homeassistant/components/sensor/edimax.py homeassistant/components/switch/hikvisioncam.py homeassistant/components/switch/wemo.py homeassistant/components/thermostat/nest.py diff --git a/homeassistant/components/frontend/www_static/polymer/home-assistant-js b/homeassistant/components/frontend/www_static/polymer/home-assistant-js index 94d8682c1e7..232302b2f58 160000 --- a/homeassistant/components/frontend/www_static/polymer/home-assistant-js +++ b/homeassistant/components/frontend/www_static/polymer/home-assistant-js @@ -1 +1 @@ -Subproject commit 94d8682c1e7679ae744e8419896d5d7b0bdd16cc +Subproject commit 232302b2f589fa216b6531e65dae5dafd851f6f0 diff --git a/homeassistant/components/switch/edimax.py b/homeassistant/components/switch/edimax.py index 3754b452a2d..1b8ec4e0a4a 100644 --- a/homeassistant/components/switch/edimax.py +++ b/homeassistant/components/switch/edimax.py @@ -9,6 +9,7 @@ import logging from homeassistant.components.switch import SwitchDevice from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD + # pylint: disable=unused-argument def setup_platform(hass, config, add_devices_callback, discovery_info=None): """ Find and return Edimax Smart Plugs. """ @@ -24,14 +25,14 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): return host = config.get(CONF_HOST) - auth=(config.get(CONF_USERNAME, 'admin'), - config.get(CONF_PASSWORD, '1234')) + auth = (config.get(CONF_USERNAME, 'admin'), + config.get(CONF_PASSWORD, '1234')) if not host: - logging.getLogger(__name__).error('Missing config variable %s', CONF_HOST) + logging.getLogger(__name__).error( + 'Missing config variable %s', CONF_HOST) return False - add_devices_callback([SmartPlugSwitch(SmartPlug(host, auth))]) @@ -46,6 +47,22 @@ class SmartPlugSwitch(SwitchDevice): #TODO: dynamically get name from device using requests return 'Edimax Smart Plug' + @property + def current_power_mwh(self): + """ Current power usage in mwh. """ + try: + return float(self.smartplug.now_power) / 1000000.0 + except ValueError: + return None + + @property + def today_power_mw(self): + """ Today total power usage in mw. """ + try: + return float(self.smartplug.now_energy_day) / 1000.0 + except ValueError: + return None + @property def is_on(self): """ True if switch is on. """ @@ -57,4 +74,4 @@ class SmartPlugSwitch(SwitchDevice): def turn_off(self): """ Turns the switch off. """ - self.smartplug.state = 'OFF' \ No newline at end of file + self.smartplug.state = 'OFF' diff --git a/homeassistant/external/netdisco b/homeassistant/external/netdisco index b2cad7c2b95..0e2a4d4e3ec 160000 --- a/homeassistant/external/netdisco +++ b/homeassistant/external/netdisco @@ -1 +1 @@ -Subproject commit b2cad7c2b959efa8eee9b5ac62d87232bf0b5176 +Subproject commit 0e2a4d4e3eccc0895872d1046ef748b05d26ba90 diff --git a/homeassistant/external/pyedimax b/homeassistant/external/pyedimax index 3815f3bd99f..674ada04c42 160000 --- a/homeassistant/external/pyedimax +++ b/homeassistant/external/pyedimax @@ -1 +1 @@ -Subproject commit 3815f3bd99fb9dcd4d9e5e6fc58626f5873e43db +Subproject commit 674ada04c42da5c1103205293a078be73f661fd6 diff --git a/homeassistant/external/pymysensors b/homeassistant/external/pymysensors index cd5ef892eee..7fb5c0ef877 160000 --- a/homeassistant/external/pymysensors +++ b/homeassistant/external/pymysensors @@ -1 +1 @@ -Subproject commit cd5ef892eeec0ad027727f7e8f757e7f2927da97 +Subproject commit 7fb5c0ef877c285d5d98ca0c0c6cbee552164d34 diff --git a/homeassistant/external/pynetgear b/homeassistant/external/pynetgear index e946ecf7926..8863fdd3565 160000 --- a/homeassistant/external/pynetgear +++ b/homeassistant/external/pynetgear @@ -1 +1 @@ -Subproject commit e946ecf7926b9b2adaa1e3127a9738201a1b1fc7 +Subproject commit 8863fdd356556bc82e6d236ad2bc662e7d091ff0 diff --git a/homeassistant/external/pywemo b/homeassistant/external/pywemo index ca94e41faa4..eef7dae12a0 160000 --- a/homeassistant/external/pywemo +++ b/homeassistant/external/pywemo @@ -1 +1 @@ -Subproject commit ca94e41faa48c783f600a2efd550c6b7dae01b0d +Subproject commit eef7dae12a073db7b8ac58340bf1cd6a1fea78c6 From c659be7e17249597f60fc90b85dd3c846b0c8c35 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 25 Jul 2015 23:45:49 -0700 Subject: [PATCH 009/224] Sun component will work now without internet --- homeassistant/components/sun.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sun.py b/homeassistant/components/sun.py index fd2cfa46b72..af4a93825ac 100644 --- a/homeassistant/components/sun.py +++ b/homeassistant/components/sun.py @@ -21,6 +21,7 @@ The sun event need to have the type 'sun', which service to call, which event """ import logging from datetime import timedelta +import urllib import homeassistant.util as util import homeassistant.util.dt as dt_util @@ -129,8 +130,13 @@ def setup(hass, config): if elevation is None: google = GoogleGeocoder() - google._get_elevation(location) # pylint: disable=protected-access - _LOGGER.info('Retrieved elevation from Google: %s', location.elevation) + try: + google._get_elevation(location) # pylint: disable=protected-access + _LOGGER.info( + 'Retrieved elevation from Google: %s', location.elevation) + except urllib.error.URLError: + # If no internet connection available etc. + pass sun = Sun(hass, location) sun.point_in_time_listener(dt_util.utcnow()) From bb0ace3a615b799c27b442619dba4a3c62dcadda Mon Sep 17 00:00:00 2001 From: Rohit Kabadi Date: Sat, 25 Jul 2015 23:59:48 -0700 Subject: [PATCH 010/224] - Reverted submodule updates --- homeassistant/components/switch/edimax.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/switch/edimax.py b/homeassistant/components/switch/edimax.py index 1b8ec4e0a4a..426c016b59e 100644 --- a/homeassistant/components/switch/edimax.py +++ b/homeassistant/components/switch/edimax.py @@ -37,7 +37,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): class SmartPlugSwitch(SwitchDevice): - """ Represents a Edimax Smart Plug switch within Home Assistant. """ + """ Represents an Edimax Smart Plug switch within Home Assistant. """ def __init__(self, smartplug): self.smartplug = smartplug From 613c0122c0768f8133b8f6a9c979359d6f88d185 Mon Sep 17 00:00:00 2001 From: Rohit Kabadi Date: Sun, 26 Jul 2015 00:08:57 -0700 Subject: [PATCH 011/224] - Reverted submodule updates. This is the 2nd attempt since the first one did not work --- .../components/frontend/www_static/polymer/home-assistant-js | 2 +- homeassistant/external/netdisco | 2 +- homeassistant/external/pymysensors | 2 +- homeassistant/external/pynetgear | 2 +- homeassistant/external/pywemo | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/frontend/www_static/polymer/home-assistant-js b/homeassistant/components/frontend/www_static/polymer/home-assistant-js index 232302b2f58..94d8682c1e7 160000 --- a/homeassistant/components/frontend/www_static/polymer/home-assistant-js +++ b/homeassistant/components/frontend/www_static/polymer/home-assistant-js @@ -1 +1 @@ -Subproject commit 232302b2f589fa216b6531e65dae5dafd851f6f0 +Subproject commit 94d8682c1e7679ae744e8419896d5d7b0bdd16cc diff --git a/homeassistant/external/netdisco b/homeassistant/external/netdisco index 0e2a4d4e3ec..b2cad7c2b95 160000 --- a/homeassistant/external/netdisco +++ b/homeassistant/external/netdisco @@ -1 +1 @@ -Subproject commit 0e2a4d4e3eccc0895872d1046ef748b05d26ba90 +Subproject commit b2cad7c2b959efa8eee9b5ac62d87232bf0b5176 diff --git a/homeassistant/external/pymysensors b/homeassistant/external/pymysensors index 7fb5c0ef877..cd5ef892eee 160000 --- a/homeassistant/external/pymysensors +++ b/homeassistant/external/pymysensors @@ -1 +1 @@ -Subproject commit 7fb5c0ef877c285d5d98ca0c0c6cbee552164d34 +Subproject commit cd5ef892eeec0ad027727f7e8f757e7f2927da97 diff --git a/homeassistant/external/pynetgear b/homeassistant/external/pynetgear index 8863fdd3565..e946ecf7926 160000 --- a/homeassistant/external/pynetgear +++ b/homeassistant/external/pynetgear @@ -1 +1 @@ -Subproject commit 8863fdd356556bc82e6d236ad2bc662e7d091ff0 +Subproject commit e946ecf7926b9b2adaa1e3127a9738201a1b1fc7 diff --git a/homeassistant/external/pywemo b/homeassistant/external/pywemo index eef7dae12a0..ca94e41faa4 160000 --- a/homeassistant/external/pywemo +++ b/homeassistant/external/pywemo @@ -1 +1 @@ -Subproject commit eef7dae12a073db7b8ac58340bf1cd6a1fea78c6 +Subproject commit ca94e41faa48c783f600a2efd550c6b7dae01b0d From fed36d2cd035b527d0ebb398e017bbd2d09e15c3 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 26 Jul 2015 00:14:55 -0700 Subject: [PATCH 012/224] Better error reporting remote classes --- homeassistant/components/http.py | 12 ++++++++---- homeassistant/remote.py | 5 +++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/http.py b/homeassistant/components/http.py index c1c0899c9ef..cb8e87490f3 100644 --- a/homeassistant/components/http.py +++ b/homeassistant/components/http.py @@ -119,7 +119,6 @@ _LOGGER = logging.getLogger(__name__) def setup(hass, config=None): """ Sets up the HTTP API and debug interface. """ - if config is None or DOMAIN not in config: config = {DOMAIN: {}} @@ -139,9 +138,14 @@ def setup(hass, config=None): sessions_enabled = config[DOMAIN].get(CONF_SESSIONS_ENABLED, True) - server = HomeAssistantHTTPServer( - (server_host, server_port), RequestHandler, hass, api_password, - development, no_password_set, sessions_enabled) + try: + server = HomeAssistantHTTPServer( + (server_host, server_port), RequestHandler, hass, api_password, + development, no_password_set, sessions_enabled) + except OSError: + # Happens if address already in use + _LOGGER.exception("Error setting up HTTP server") + return False hass.bus.listen_once( ha.EVENT_HOMEASSISTANT_START, diff --git a/homeassistant/remote.py b/homeassistant/remote.py index bd576f50e48..3decfa3ce3e 100644 --- a/homeassistant/remote.py +++ b/homeassistant/remote.py @@ -120,8 +120,9 @@ class HomeAssistant(ha.HomeAssistant): def start(self): # Ensure a local API exists to connect with remote if self.config.api is None: - bootstrap.setup_component(self, 'http') - bootstrap.setup_component(self, 'api') + if not bootstrap.setup_component(self, 'api'): + raise ha.HomeAssistantError( + 'Unable to setup local API to receive events') ha.Timer(self) From 0c56fde5a930c27c0a36ec2a5c3a909de70542b1 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 26 Jul 2015 10:17:01 +0200 Subject: [PATCH 013/224] Reorg tests folder --- tests/{helpers.py => common.py} | 0 tests/components/__init__.py | 0 tests/{test_component_api.py => components/test_api.py} | 0 .../test_configurator.py} | 0 tests/{test_component_demo.py => components/test_demo.py} | 2 +- .../test_device_sun_light_trigger.py} | 2 +- .../test_device_tracker.py} | 2 +- .../{test_component_frontend.py => components/test_frontend.py} | 0 tests/{test_component_group.py => components/test_group.py} | 0 tests/{test_component_history.py => components/test_history.py} | 2 +- tests/{test_component_core.py => components/test_init.py} | 0 tests/{test_component_light.py => components/test_light.py} | 2 +- tests/{test_component_logbook.py => components/test_logbook.py} | 2 +- .../test_media_player.py} | 2 +- .../{test_component_recorder.py => components/test_recorder.py} | 2 +- tests/{test_component_sun.py => components/test_sun.py} | 0 tests/{test_component_switch.py => components/test_switch.py} | 2 +- tests/config/custom_components/light/test.py | 2 +- tests/config/custom_components/switch/test.py | 2 +- tests/helpers/__init__.py | 0 tests/{test_helper_entity.py => helpers/test_entity.py} | 0 tests/{test_helpers.py => helpers/test_init.py} | 2 +- tests/test_config.py | 2 +- tests/{test_core.py => test_init.py} | 0 tests/test_loader.py | 2 +- tests/util/__init__.py | 0 tests/{test_util_color.py => util/test_color.py} | 0 tests/{test_util_dt.py => util/test_dt.py} | 0 tests/{test_util.py => util/test_init.py} | 0 29 files changed, 14 insertions(+), 14 deletions(-) rename tests/{helpers.py => common.py} (100%) create mode 100644 tests/components/__init__.py rename tests/{test_component_api.py => components/test_api.py} (100%) rename tests/{test_component_configurator.py => components/test_configurator.py} (100%) rename tests/{test_component_demo.py => components/test_demo.py} (96%) rename tests/{test_component_device_sun_light_trigger.py => components/test_device_sun_light_trigger.py} (99%) rename tests/{test_component_device_tracker.py => components/test_device_tracker.py} (99%) rename tests/{test_component_frontend.py => components/test_frontend.py} (100%) rename tests/{test_component_group.py => components/test_group.py} (100%) rename tests/{test_component_history.py => components/test_history.py} (99%) rename tests/{test_component_core.py => components/test_init.py} (100%) rename tests/{test_component_light.py => components/test_light.py} (99%) rename tests/{test_component_logbook.py => components/test_logbook.py} (97%) rename tests/{test_component_media_player.py => components/test_media_player.py} (98%) rename tests/{test_component_recorder.py => components/test_recorder.py} (97%) rename tests/{test_component_sun.py => components/test_sun.py} (100%) rename tests/{test_component_switch.py => components/test_switch.py} (98%) create mode 100644 tests/helpers/__init__.py rename tests/{test_helper_entity.py => helpers/test_entity.py} (100%) rename tests/{test_helpers.py => helpers/test_init.py} (97%) rename tests/{test_core.py => test_init.py} (100%) create mode 100644 tests/util/__init__.py rename tests/{test_util_color.py => util/test_color.py} (100%) rename tests/{test_util_dt.py => util/test_dt.py} (100%) rename tests/{test_util.py => util/test_init.py} (100%) diff --git a/tests/helpers.py b/tests/common.py similarity index 100% rename from tests/helpers.py rename to tests/common.py diff --git a/tests/components/__init__.py b/tests/components/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/test_component_api.py b/tests/components/test_api.py similarity index 100% rename from tests/test_component_api.py rename to tests/components/test_api.py diff --git a/tests/test_component_configurator.py b/tests/components/test_configurator.py similarity index 100% rename from tests/test_component_configurator.py rename to tests/components/test_configurator.py diff --git a/tests/test_component_demo.py b/tests/components/test_demo.py similarity index 96% rename from tests/test_component_demo.py rename to tests/components/test_demo.py index d7a64167622..628c7e84bfe 100644 --- a/tests/test_component_demo.py +++ b/tests/components/test_demo.py @@ -9,7 +9,7 @@ import unittest import homeassistant as ha import homeassistant.components.demo as demo -from helpers import mock_http_component +from common import mock_http_component class TestDemo(unittest.TestCase): diff --git a/tests/test_component_device_sun_light_trigger.py b/tests/components/test_device_sun_light_trigger.py similarity index 99% rename from tests/test_component_device_sun_light_trigger.py rename to tests/components/test_device_sun_light_trigger.py index 05452a830ec..16b89468201 100644 --- a/tests/test_component_device_sun_light_trigger.py +++ b/tests/components/test_device_sun_light_trigger.py @@ -14,7 +14,7 @@ from homeassistant.components import ( device_tracker, light, sun, device_sun_light_trigger) -from helpers import ( +from common import ( get_test_home_assistant, ensure_sun_risen, ensure_sun_set, trigger_device_tracker_scan) diff --git a/tests/test_component_device_tracker.py b/tests/components/test_device_tracker.py similarity index 99% rename from tests/test_component_device_tracker.py rename to tests/components/test_device_tracker.py index 143c28c9cdb..1d595df3f5a 100644 --- a/tests/test_component_device_tracker.py +++ b/tests/components/test_device_tracker.py @@ -18,7 +18,7 @@ from homeassistant.const import ( DEVICE_DEFAULT_NAME) import homeassistant.components.device_tracker as device_tracker -from helpers import get_test_home_assistant +from common import get_test_home_assistant def setUpModule(): # pylint: disable=invalid-name diff --git a/tests/test_component_frontend.py b/tests/components/test_frontend.py similarity index 100% rename from tests/test_component_frontend.py rename to tests/components/test_frontend.py diff --git a/tests/test_component_group.py b/tests/components/test_group.py similarity index 100% rename from tests/test_component_group.py rename to tests/components/test_group.py diff --git a/tests/test_component_history.py b/tests/components/test_history.py similarity index 99% rename from tests/test_component_history.py rename to tests/components/test_history.py index 7ad657f54b5..1d0d787b95d 100644 --- a/tests/test_component_history.py +++ b/tests/components/test_history.py @@ -13,7 +13,7 @@ import homeassistant as ha import homeassistant.util.dt as dt_util from homeassistant.components import history, recorder -from helpers import ( +from common import ( mock_http_component, mock_state_change_event, get_test_home_assistant) diff --git a/tests/test_component_core.py b/tests/components/test_init.py similarity index 100% rename from tests/test_component_core.py rename to tests/components/test_init.py diff --git a/tests/test_component_light.py b/tests/components/test_light.py similarity index 99% rename from tests/test_component_light.py rename to tests/components/test_light.py index 07f8e8e14c9..5142823e8ef 100644 --- a/tests/test_component_light.py +++ b/tests/components/test_light.py @@ -15,7 +15,7 @@ from homeassistant.const import ( SERVICE_TURN_ON, SERVICE_TURN_OFF) import homeassistant.components.light as light -from helpers import mock_service, get_test_home_assistant +from common import mock_service, get_test_home_assistant class TestLight(unittest.TestCase): diff --git a/tests/test_component_logbook.py b/tests/components/test_logbook.py similarity index 97% rename from tests/test_component_logbook.py rename to tests/components/test_logbook.py index 33b7b5f915b..2a426d0fcdc 100644 --- a/tests/test_component_logbook.py +++ b/tests/components/test_logbook.py @@ -14,7 +14,7 @@ from homeassistant.const import ( import homeassistant.util.dt as dt_util from homeassistant.components import logbook -from helpers import get_test_home_assistant, mock_http_component +from common import get_test_home_assistant, mock_http_component class TestComponentHistory(unittest.TestCase): diff --git a/tests/test_component_media_player.py b/tests/components/test_media_player.py similarity index 98% rename from tests/test_component_media_player.py rename to tests/components/test_media_player.py index b7f0b847e80..d79ad89609f 100644 --- a/tests/test_component_media_player.py +++ b/tests/components/test_media_player.py @@ -15,7 +15,7 @@ from homeassistant.const import ( SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREVIOUS_TRACK, ATTR_ENTITY_ID) import homeassistant.components.media_player as media_player -from helpers import mock_service +from common import mock_service def setUpModule(): # pylint: disable=invalid-name diff --git a/tests/test_component_recorder.py b/tests/components/test_recorder.py similarity index 97% rename from tests/test_component_recorder.py rename to tests/components/test_recorder.py index 68c63b637d0..eb90f2331d9 100644 --- a/tests/test_component_recorder.py +++ b/tests/components/test_recorder.py @@ -11,7 +11,7 @@ import os from homeassistant.const import MATCH_ALL from homeassistant.components import recorder -from helpers import get_test_home_assistant +from common import get_test_home_assistant class TestRecorder(unittest.TestCase): diff --git a/tests/test_component_sun.py b/tests/components/test_sun.py similarity index 100% rename from tests/test_component_sun.py rename to tests/components/test_sun.py diff --git a/tests/test_component_switch.py b/tests/components/test_switch.py similarity index 98% rename from tests/test_component_switch.py rename to tests/components/test_switch.py index cbc161be853..273c5987be2 100644 --- a/tests/test_component_switch.py +++ b/tests/components/test_switch.py @@ -11,7 +11,7 @@ import homeassistant.loader as loader from homeassistant.const import STATE_ON, STATE_OFF, CONF_PLATFORM import homeassistant.components.switch as switch -from helpers import get_test_home_assistant +from common import get_test_home_assistant class TestSwitch(unittest.TestCase): diff --git a/tests/config/custom_components/light/test.py b/tests/config/custom_components/light/test.py index f7f355c4b30..1512d080b05 100644 --- a/tests/config/custom_components/light/test.py +++ b/tests/config/custom_components/light/test.py @@ -7,7 +7,7 @@ Provides a mock switch platform. Call init before using it in your tests to ensure clean test data. """ from homeassistant.const import STATE_ON, STATE_OFF -from tests.helpers import MockToggleDevice +from tests.common import MockToggleDevice DEVICES = [] diff --git a/tests/config/custom_components/switch/test.py b/tests/config/custom_components/switch/test.py index 178faf7fcdc..bb95154a94b 100644 --- a/tests/config/custom_components/switch/test.py +++ b/tests/config/custom_components/switch/test.py @@ -7,7 +7,7 @@ Provides a mock switch platform. Call init before using it in your tests to ensure clean test data. """ from homeassistant.const import STATE_ON, STATE_OFF -from tests.helpers import MockToggleDevice +from tests.common import MockToggleDevice DEVICES = [] diff --git a/tests/helpers/__init__.py b/tests/helpers/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/test_helper_entity.py b/tests/helpers/test_entity.py similarity index 100% rename from tests/test_helper_entity.py rename to tests/helpers/test_entity.py diff --git a/tests/test_helpers.py b/tests/helpers/test_init.py similarity index 97% rename from tests/test_helpers.py rename to tests/helpers/test_init.py index adc4b0d0788..9257dd634ef 100644 --- a/tests/test_helpers.py +++ b/tests/helpers/test_init.py @@ -7,7 +7,7 @@ Tests component helpers. # pylint: disable=protected-access,too-many-public-methods import unittest -from helpers import get_test_home_assistant +from common import get_test_home_assistant import homeassistant as ha import homeassistant.loader as loader diff --git a/tests/test_config.py b/tests/test_config.py index 0ea18eead82..368f660eeb7 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -16,7 +16,7 @@ from homeassistant.const import ( CONF_LATITUDE, CONF_LONGITUDE, CONF_TEMPERATURE_UNIT, CONF_NAME, CONF_TIME_ZONE) -from helpers import get_test_config_dir +from common import get_test_config_dir CONFIG_DIR = get_test_config_dir() YAML_PATH = os.path.join(CONFIG_DIR, config_util.YAML_CONFIG_FILE) diff --git a/tests/test_core.py b/tests/test_init.py similarity index 100% rename from tests/test_core.py rename to tests/test_init.py diff --git a/tests/test_loader.py b/tests/test_loader.py index dd80587b247..03bd7e7419c 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -10,7 +10,7 @@ import unittest import homeassistant.loader as loader import homeassistant.components.http as http -from helpers import get_test_home_assistant, MockModule +from common import get_test_home_assistant, MockModule class TestLoader(unittest.TestCase): diff --git a/tests/util/__init__.py b/tests/util/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/test_util_color.py b/tests/util/test_color.py similarity index 100% rename from tests/test_util_color.py rename to tests/util/test_color.py diff --git a/tests/test_util_dt.py b/tests/util/test_dt.py similarity index 100% rename from tests/test_util_dt.py rename to tests/util/test_dt.py diff --git a/tests/test_util.py b/tests/util/test_init.py similarity index 100% rename from tests/test_util.py rename to tests/util/test_init.py From e0468f8b8e8667f05888a1ed218440d3ee2b9d31 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 26 Jul 2015 10:45:49 +0200 Subject: [PATCH 014/224] Extract helpers.event from core + misc cleanup --- homeassistant/__init__.py | 538 +++++++++++---------------------- homeassistant/helpers/event.py | 161 ++++++++++ homeassistant/helpers/state.py | 11 +- homeassistant/remote.py | 2 +- tests/helpers/test_event.py | 126 ++++++++ 5 files changed, 477 insertions(+), 361 deletions(-) create mode 100644 homeassistant/helpers/event.py create mode 100644 tests/helpers/test_event.py diff --git a/homeassistant/__init__.py b/homeassistant/__init__.py index 09069924e6b..c62128453e7 100644 --- a/homeassistant/__init__.py +++ b/homeassistant/__init__.py @@ -13,6 +13,7 @@ import threading import enum import re import functools as ft +from collections import namedtuple from homeassistant.const import ( EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, @@ -41,6 +42,9 @@ ENTITY_ID_PATTERN = re.compile(r"^(?P\w+)\.(?P\w+)$") _LOGGER = logging.getLogger(__name__) +# Temporary addition to proxy deprecated methods +_MockHA = namedtuple("MockHomeAssistant", ['bus']) + class HomeAssistant(object): """ Core class to route all communication to right components. """ @@ -52,39 +56,12 @@ class HomeAssistant(object): self.states = StateMachine(self.bus) self.config = Config() - @property - def components(self): - """ DEPRECATED 3/21/2015. Use hass.config.components """ - _LOGGER.warning( - 'hass.components is deprecated. Use hass.config.components') - return self.config.components - - @property - def local_api(self): - """ DEPRECATED 3/21/2015. Use hass.config.api """ - _LOGGER.warning( - 'hass.local_api is deprecated. Use hass.config.api') - return self.config.api - - @property - def config_dir(self): - """ DEPRECATED 3/18/2015. Use hass.config.config_dir """ - _LOGGER.warning( - 'hass.config_dir is deprecated. Use hass.config.config_dir') - return self.config.config_dir - - def get_config_path(self, path): - """ DEPRECATED 3/18/2015. Use hass.config.path """ - _LOGGER.warning( - 'hass.get_config_path is deprecated. Use hass.config.path') - return self.config.path(path) - def start(self): """ Start home assistant. """ _LOGGER.info( "Starting Home Assistant (%d threads)", self.pool.worker_count) - Timer(self) + create_timer(self) self.bus.fire(EVENT_HOMEASSISTANT_START) @@ -105,98 +82,6 @@ class HomeAssistant(object): self.stop() - def track_point_in_time(self, action, point_in_time): - """ - Adds a listener that fires once after a spefic point in time. - """ - utc_point_in_time = date_util.as_utc(point_in_time) - - @ft.wraps(action) - def utc_converter(utc_now): - """ Converts passed in UTC now to local now. """ - action(date_util.as_local(utc_now)) - - self.track_point_in_utc_time(utc_converter, utc_point_in_time) - - def track_point_in_utc_time(self, action, point_in_time): - """ - Adds a listener that fires once after a specific point in UTC time. - """ - - @ft.wraps(action) - def point_in_time_listener(event): - """ Listens for matching time_changed events. """ - now = event.data[ATTR_NOW] - - if now >= point_in_time and \ - not hasattr(point_in_time_listener, 'run'): - - # Set variable so that we will never run twice. - # Because the event bus might have to wait till a thread comes - # available to execute this listener it might occur that the - # listener gets lined up twice to be executed. This will make - # sure the second time it does nothing. - point_in_time_listener.run = True - - self.bus.remove_listener(EVENT_TIME_CHANGED, - point_in_time_listener) - - action(now) - - self.bus.listen(EVENT_TIME_CHANGED, point_in_time_listener) - return point_in_time_listener - - # pylint: disable=too-many-arguments - def track_utc_time_change(self, action, - year=None, month=None, day=None, - hour=None, minute=None, second=None): - """ Adds a listener that will fire if time matches a pattern. """ - self.track_time_change( - action, year, month, day, hour, minute, second, utc=True) - - # pylint: disable=too-many-arguments - def track_time_change(self, action, - year=None, month=None, day=None, - hour=None, minute=None, second=None, utc=False): - """ Adds a listener that will fire if UTC time matches a pattern. """ - - # We do not have to wrap the function with time pattern matching logic - # if no pattern given - if any((val is not None for val in - (year, month, day, hour, minute, second))): - - pmp = _process_match_param - year, month, day = pmp(year), pmp(month), pmp(day) - hour, minute, second = pmp(hour), pmp(minute), pmp(second) - - @ft.wraps(action) - def time_listener(event): - """ Listens for matching time_changed events. """ - now = event.data[ATTR_NOW] - - if not utc: - now = date_util.as_local(now) - - mat = _matcher - - if mat(now.year, year) and \ - mat(now.month, month) and \ - mat(now.day, day) and \ - mat(now.hour, hour) and \ - mat(now.minute, minute) and \ - mat(now.second, second): - - action(now) - - else: - @ft.wraps(action) - def time_listener(event): - """ Fires every time event that comes in. """ - action(event.data[ATTR_NOW]) - - self.bus.listen(EVENT_TIME_CHANGED, time_listener) - return time_listener - def stop(self): """ Stops Home Assistant and shuts down all threads. """ _LOGGER.info("Stopping") @@ -208,76 +93,45 @@ class HomeAssistant(object): self.pool.stop() - def get_entity_ids(self, domain_filter=None): - """ - Returns known entity ids. - - THIS METHOD IS DEPRECATED. Use hass.states.entity_ids - """ + def track_point_in_time(self, action, point_in_time): + """Deprecated method to track point in time.""" _LOGGER.warning( - "hass.get_entiy_ids is deprecated. Use hass.states.entity_ids") + 'hass.track_point_in_time is deprecated. ' + 'Please use homeassistant.helpers.event.track_point_in_time') + import homeassistant.helpers.event as helper + helper.track_point_in_time(self, action, point_in_time) - return self.states.entity_ids(domain_filter) - - def listen_once_event(self, event_type, listener): - """ Listen once for event of a specific type. - - To listen to all events specify the constant ``MATCH_ALL`` - as event_type. - - Note: at the moment it is impossible to remove a one time listener. - - THIS METHOD IS DEPRECATED. Please use hass.events.listen_once. - """ + def track_point_in_utc_time(self, action, point_in_time): + """Deprecated method to track point in UTC time.""" _LOGGER.warning( - "hass.listen_once_event is deprecated. Use hass.bus.listen_once") + 'hass.track_point_in_utc_time is deprecated. ' + 'Please use homeassistant.helpers.event.track_point_in_utc_time') + import homeassistant.helpers.event as helper + helper.track_point_in_utc_time(self, action, point_in_time) - self.bus.listen_once(event_type, listener) + def track_utc_time_change(self, action, + year=None, month=None, day=None, + hour=None, minute=None, second=None): + """Deprecated method to track UTC time change.""" + # pylint: disable=too-many-arguments + _LOGGER.warning( + 'hass.track_utc_time_change is deprecated. ' + 'Please use homeassistant.helpers.event.track_utc_time_change') + import homeassistant.helpers.event as helper + helper.track_utc_time_change(self, action, year, month, day, hour, + minute, second) - def track_state_change(self, entity_ids, action, - from_state=None, to_state=None): - """ - Track specific state changes. - entity_ids, from_state and to_state can be string or list. - Use list to match multiple. - - THIS METHOD IS DEPRECATED. Use hass.states.track_change - """ - _LOGGER.warning(( - "hass.track_state_change is deprecated. " - "Use hass.states.track_change")) - - self.states.track_change(entity_ids, action, from_state, to_state) - - def call_service(self, domain, service, service_data=None): - """ - Fires event to call specified service. - - THIS METHOD IS DEPRECATED. Use hass.services.call - """ - _LOGGER.warning(( - "hass.services.call is deprecated. " - "Use hass.services.call")) - - self.services.call(domain, service, service_data) - - -def _process_match_param(parameter): - """ Wraps parameter in a list if it is not one and returns it. """ - if parameter is None or parameter == MATCH_ALL: - return MATCH_ALL - elif isinstance(parameter, str) or not hasattr(parameter, '__iter__'): - return (parameter,) - else: - return tuple(parameter) - - -def _matcher(subject, pattern): - """ Returns True if subject matches the pattern. - - Pattern is either a list of allowed subjects or a `MATCH_ALL`. - """ - return MATCH_ALL == pattern or subject in pattern + def track_time_change(self, action, + year=None, month=None, day=None, + hour=None, minute=None, second=None, utc=False): + """Deprecated method to track time change.""" + # pylint: disable=too-many-arguments + _LOGGER.warning( + 'hass.track_time_change is deprecated. ' + 'Please use homeassistant.helpers.event.track_time_change') + import homeassistant.helpers.event as helper + helper.track_time_change(self, action, year, month, day, hour, + minute, second) class JobPriority(util.OrderedEnum): @@ -305,33 +159,6 @@ class JobPriority(util.OrderedEnum): return JobPriority.EVENT_DEFAULT -def create_worker_pool(): - """ Creates a worker pool to be used. """ - - def job_handler(job): - """ Called whenever a job is available to do. """ - try: - func, arg = job - func(arg) - except Exception: # pylint: disable=broad-except - # Catch any exception our service/event_listener might throw - # We do not want to crash our ThreadPool - _LOGGER.exception("BusHandler:Exception doing job") - - def busy_callback(worker_count, current_jobs, pending_jobs_count): - """ Callback to be called when the pool queue gets too big. """ - - _LOGGER.warning( - "WorkerPool:All %d threads are busy and %d jobs pending", - worker_count, pending_jobs_count) - - for start, job in current_jobs: - _LOGGER.warning("WorkerPool:Current job from %s: %s", - date_util.datetime_to_local_str(start), job) - - return util.ThreadPool(job_handler, MIN_WORKER_THREAD, busy_callback) - - class EventOrigin(enum.Enum): """ Distinguish between origin of event. """ # pylint: disable=no-init,too-few-public-methods @@ -446,25 +273,28 @@ class EventBus(object): To listen to all events specify the constant ``MATCH_ALL`` as event_type. - Note: at the moment it is impossible to remove a one time listener. + Returns registered listener that can be used with remove_listener. """ @ft.wraps(listener) def onetime_listener(event): """ Removes listener from eventbus and then fires listener. """ - if not hasattr(onetime_listener, 'run'): - # Set variable so that we will never run twice. - # Because the event bus might have to wait till a thread comes - # available to execute this listener it might occur that the - # listener gets lined up twice to be executed. - # This will make sure the second time it does nothing. - onetime_listener.run = True + if hasattr(onetime_listener, 'run'): + return + # Set variable so that we will never run twice. + # Because the event bus might have to wait till a thread comes + # available to execute this listener it might occur that the + # listener gets lined up twice to be executed. + # This will make sure the second time it does nothing. + onetime_listener.run = True - self.remove_listener(event_type, onetime_listener) + self.remove_listener(event_type, onetime_listener) - listener(event) + listener(event) self.listen(event_type, onetime_listener) + return onetime_listener + def remove_listener(self, event_type, listener): """ Removes a listener of a specific event_type. """ with self._lock: @@ -596,18 +426,19 @@ class StateMachine(object): def entity_ids(self, domain_filter=None): """ List of entity ids that are being tracked. """ - if domain_filter is not None: - domain_filter = domain_filter.lower() - - return [state.entity_id for key, state - in self._states.items() - if util.split_entity_id(key)[0] == domain_filter] - else: + if domain_filter is None: return list(self._states.keys()) + domain_filter = domain_filter.lower() + + return [state.entity_id for key, state + in self._states.items() + if util.split_entity_id(key)[0] == domain_filter] + def all(self): """ Returns a list of all states. """ - return [state.copy() for state in self._states.values()] + with self._lock: + return [state.copy() for state in self._states.values()] def get(self, entity_id): """ Returns the state of the specified entity. """ @@ -616,16 +447,6 @@ class StateMachine(object): # Make a copy so people won't mutate the state return state.copy() if state else None - def get_since(self, point_in_time): - """ - Returns all states that have been changed since point_in_time. - """ - point_in_time = date_util.strip_microseconds(point_in_time) - - with self._lock: - return [state for state in self._states.values() - if state.last_updated >= point_in_time] - def is_state(self, entity_id, state): """ Returns True if entity exists and is specified state. """ entity_id = entity_id.lower() @@ -661,59 +482,32 @@ class StateMachine(object): same_state = is_existing and old_state.state == new_state same_attr = is_existing and old_state.attributes == attributes + if same_state and same_attr: + return + # If state did not exist or is different, set it - if not (same_state and same_attr): - last_changed = old_state.last_changed if same_state else None + last_changed = old_state.last_changed if same_state else None - state = State(entity_id, new_state, attributes, last_changed) - self._states[entity_id] = state + state = State(entity_id, new_state, attributes, last_changed) + self._states[entity_id] = state - event_data = {'entity_id': entity_id, 'new_state': state} + event_data = {'entity_id': entity_id, 'new_state': state} - if old_state: - event_data['old_state'] = old_state + if old_state: + event_data['old_state'] = old_state - self._bus.fire(EVENT_STATE_CHANGED, event_data) + self._bus.fire(EVENT_STATE_CHANGED, event_data) def track_change(self, entity_ids, action, from_state=None, to_state=None): """ - Track specific state changes. - entity_ids, from_state and to_state can be string or list. - Use list to match multiple. - - Returns the listener that listens on the bus for EVENT_STATE_CHANGED. - Pass the return value into hass.bus.remove_listener to remove it. + DEPRECATED """ - from_state = _process_match_param(from_state) - to_state = _process_match_param(to_state) - - # Ensure it is a lowercase list with entity ids we want to match on - if isinstance(entity_ids, str): - entity_ids = (entity_ids.lower(),) - else: - entity_ids = tuple(entity_id.lower() for entity_id in entity_ids) - - @ft.wraps(action) - def state_listener(event): - """ The listener that listens for specific state changes. """ - if event.data['entity_id'] not in entity_ids: - return - - if 'old_state' in event.data: - old_state = event.data['old_state'].state - else: - old_state = None - - if _matcher(old_state, from_state) and \ - _matcher(event.data['new_state'].state, to_state): - - action(event.data['entity_id'], - event.data.get('old_state'), - event.data['new_state']) - - self._bus.listen(EVENT_STATE_CHANGED, state_listener) - - return state_listener + _LOGGER.warning( + 'hass.states.track_change is deprecated. ' + 'Use homeassistant.helpers.event.track_state_change instead.') + import homeassistant.helpers.event as helper + helper.track_state_change(_MockHA(self._bus), entity_ids, action, + from_state, to_state) # pylint: disable=too-few-public-methods @@ -826,15 +620,16 @@ class ServiceRegistry(object): domain = service_data.pop(ATTR_DOMAIN, None) service = service_data.pop(ATTR_SERVICE, None) - with self._lock: - if domain in self._services and service in self._services[domain]: - service_call = ServiceCall(domain, service, service_data) + if not self.has_service(domain, service): + return - # Add a job to the pool that calls _execute_service - self._pool.add_job(JobPriority.EVENT_SERVICE, - (self._execute_service, - (self._services[domain][service], - service_call))) + service_handler = self._services[domain][service] + service_call = ServiceCall(domain, service, service_data) + + # Add a job to the pool that calls _execute_service + self._pool.add_job(JobPriority.EVENT_SERVICE, + (self._execute_service, + (service_handler, service_call))) def _execute_service(self, service_and_call): """ Executes a service and fires a SERVICE_EXECUTED event. """ @@ -843,9 +638,8 @@ class ServiceRegistry(object): service(call) self._bus.fire( - EVENT_SERVICE_EXECUTED, { - ATTR_SERVICE_CALL_ID: call.data[ATTR_SERVICE_CALL_ID] - }) + EVENT_SERVICE_EXECUTED, + {ATTR_SERVICE_CALL_ID: call.data[ATTR_SERVICE_CALL_ID]}) def _generate_unique_id(self): """ Generates a unique service call id. """ @@ -853,70 +647,6 @@ class ServiceRegistry(object): return "{}-{}".format(id(self), self._cur_id) -class Timer(threading.Thread): - """ Timer will sent out an event every TIMER_INTERVAL seconds. """ - - def __init__(self, hass, interval=None): - threading.Thread.__init__(self) - - self.daemon = True - self.hass = hass - self.interval = interval or TIMER_INTERVAL - self._stop_event = threading.Event() - - # We want to be able to fire every time a minute starts (seconds=0). - # We want this so other modules can use that to make sure they fire - # every minute. - assert 60 % self.interval == 0, "60 % TIMER_INTERVAL should be 0!" - - hass.bus.listen_once(EVENT_HOMEASSISTANT_START, - lambda event: self.start()) - - def run(self): - """ Start the timer. """ - - self.hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, - lambda event: self._stop_event.set()) - - _LOGGER.info("Timer:starting") - - last_fired_on_second = -1 - - calc_now = date_util.utcnow - interval = self.interval - - while not self._stop_event.isSet(): - now = calc_now() - - # First check checks if we are not on a second matching the - # timer interval. Second check checks if we did not already fire - # this interval. - if now.second % interval or \ - now.second == last_fired_on_second: - - # Sleep till it is the next time that we have to fire an event. - # Aim for halfway through the second that fits TIMER_INTERVAL. - # If TIMER_INTERVAL is 10 fire at .5, 10.5, 20.5, etc seconds. - # This will yield the best results because time.sleep() is not - # 100% accurate because of non-realtime OS's - slp_seconds = interval - now.second % interval + \ - .5 - now.microsecond/1000000.0 - - time.sleep(slp_seconds) - - now = calc_now() - - last_fired_on_second = now.second - - # Event might have been set while sleeping - if not self._stop_event.isSet(): - try: - self.hass.bus.fire(EVENT_TIME_CHANGED, {ATTR_NOW: now}) - except HomeAssistantError: - # HA raises error if firing event after it has shut down - break - - class Config(object): """ Configuration settings for Home Assistant. """ @@ -986,3 +716,93 @@ class InvalidEntityFormatError(HomeAssistantError): class NoEntitySpecifiedError(HomeAssistantError): """ When no entity is specified. """ pass + + +def create_timer(hass, interval=TIMER_INTERVAL): + """ Creates a timer. Timer will start on HOMEASSISTANT_START. """ + # We want to be able to fire every time a minute starts (seconds=0). + # We want this so other modules can use that to make sure they fire + # every minute. + assert 60 % interval == 0, "60 % TIMER_INTERVAL should be 0!" + + def timer(): + """Send an EVENT_TIME_CHANGED on interval.""" + stop_event = threading.Event() + + def stop_timer(event): + """Stop the timer.""" + stop_event.set() + + hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_timer) + + _LOGGER.info("Timer:starting") + + last_fired_on_second = -1 + + calc_now = date_util.utcnow + + while not stop_event.isSet(): + now = calc_now() + + # First check checks if we are not on a second matching the + # timer interval. Second check checks if we did not already fire + # this interval. + if now.second % interval or \ + now.second == last_fired_on_second: + + # Sleep till it is the next time that we have to fire an event. + # Aim for halfway through the second that fits TIMER_INTERVAL. + # If TIMER_INTERVAL is 10 fire at .5, 10.5, 20.5, etc seconds. + # This will yield the best results because time.sleep() is not + # 100% accurate because of non-realtime OS's + slp_seconds = interval - now.second % interval + \ + .5 - now.microsecond/1000000.0 + + time.sleep(slp_seconds) + + now = calc_now() + + last_fired_on_second = now.second + + # Event might have been set while sleeping + if not stop_event.isSet(): + try: + hass.bus.fire(EVENT_TIME_CHANGED, {ATTR_NOW: now}) + except HomeAssistantError: + # HA raises error if firing event after it has shut down + break + + def start_timer(event): + """Start the timer.""" + thread = threading.Thread(target=timer) + thread.daemon = True + thread.start() + + hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_timer) + + +def create_worker_pool(): + """ Creates a worker pool to be used. """ + + def job_handler(job): + """ Called whenever a job is available to do. """ + try: + func, arg = job + func(arg) + except Exception: # pylint: disable=broad-except + # Catch any exception our service/event_listener might throw + # We do not want to crash our ThreadPool + _LOGGER.exception("BusHandler:Exception doing job") + + def busy_callback(worker_count, current_jobs, pending_jobs_count): + """ Callback to be called when the pool queue gets too big. """ + + _LOGGER.warning( + "WorkerPool:All %d threads are busy and %d jobs pending", + worker_count, pending_jobs_count) + + for start, job in current_jobs: + _LOGGER.warning("WorkerPool:Current job from %s: %s", + date_util.datetime_to_local_str(start), job) + + return util.ThreadPool(job_handler, MIN_WORKER_THREAD, busy_callback) diff --git a/homeassistant/helpers/event.py b/homeassistant/helpers/event.py new file mode 100644 index 00000000000..194cacdc19d --- /dev/null +++ b/homeassistant/helpers/event.py @@ -0,0 +1,161 @@ +""" +Helpers for listening to events +""" +import functools as ft + +from ..util import dt as dt_util +from ..const import ( + ATTR_NOW, EVENT_STATE_CHANGED, EVENT_TIME_CHANGED, MATCH_ALL) + + +def track_state_change(hass, entity_ids, action, from_state=None, + to_state=None): + """ + Track specific state changes. + entity_ids, from_state and to_state can be string or list. + Use list to match multiple. + + Returns the listener that listens on the bus for EVENT_STATE_CHANGED. + Pass the return value into hass.bus.remove_listener to remove it. + """ + from_state = _process_match_param(from_state) + to_state = _process_match_param(to_state) + + # Ensure it is a lowercase list with entity ids we want to match on + if isinstance(entity_ids, str): + entity_ids = (entity_ids.lower(),) + else: + entity_ids = tuple(entity_id.lower() for entity_id in entity_ids) + + @ft.wraps(action) + def state_change_listener(event): + """ The listener that listens for specific state changes. """ + if event.data['entity_id'] not in entity_ids: + return + + if 'old_state' in event.data: + old_state = event.data['old_state'].state + else: + old_state = None + + if _matcher(old_state, from_state) and \ + _matcher(event.data['new_state'].state, to_state): + + action(event.data['entity_id'], + event.data.get('old_state'), + event.data['new_state']) + + hass.bus.listen(EVENT_STATE_CHANGED, state_change_listener) + + return state_change_listener + + +def track_point_in_time(hass, action, point_in_time): + """ + Adds a listener that fires once after a spefic point in time. + """ + utc_point_in_time = dt_util.as_utc(point_in_time) + + @ft.wraps(action) + def utc_converter(utc_now): + """ Converts passed in UTC now to local now. """ + action(dt_util.as_local(utc_now)) + + return track_point_in_utc_time(hass, utc_converter, utc_point_in_time) + + +def track_point_in_utc_time(hass, action, point_in_time): + """ + Adds a listener that fires once after a specific point in UTC time. + """ + + @ft.wraps(action) + def point_in_time_listener(event): + """ Listens for matching time_changed events. """ + now = event.data[ATTR_NOW] + + if now >= point_in_time and \ + not hasattr(point_in_time_listener, 'run'): + + # Set variable so that we will never run twice. + # Because the event bus might have to wait till a thread comes + # available to execute this listener it might occur that the + # listener gets lined up twice to be executed. This will make + # sure the second time it does nothing. + point_in_time_listener.run = True + + hass.bus.remove_listener(EVENT_TIME_CHANGED, + point_in_time_listener) + + action(now) + + hass.bus.listen(EVENT_TIME_CHANGED, point_in_time_listener) + return point_in_time_listener + + +# pylint: disable=too-many-arguments +def track_utc_time_change(hass, action, year=None, month=None, day=None, + hour=None, minute=None, second=None, local=False): + """ Adds a listener that will fire if time matches a pattern. """ + # We do not have to wrap the function with time pattern matching logic + # if no pattern given + if all(val is None for val in (year, month, day, hour, minute, second)): + @ft.wraps(action) + def time_change_listener(event): + """ Fires every time event that comes in. """ + action(event.data[ATTR_NOW]) + + hass.bus.listen(EVENT_TIME_CHANGED, time_change_listener) + return time_change_listener + + pmp = _process_match_param + year, month, day = pmp(year), pmp(month), pmp(day) + hour, minute, second = pmp(hour), pmp(minute), pmp(second) + + @ft.wraps(action) + def pattern_time_change_listener(event): + """ Listens for matching time_changed events. """ + now = event.data[ATTR_NOW] + + if local: + now = dt_util.as_local(now) + + mat = _matcher + + if mat(now.year, year) and \ + mat(now.month, month) and \ + mat(now.day, day) and \ + mat(now.hour, hour) and \ + mat(now.minute, minute) and \ + mat(now.second, second): + + action(now) + + hass.bus.listen(EVENT_TIME_CHANGED, pattern_time_change_listener) + return pattern_time_change_listener + + +# pylint: disable=too-many-arguments +def track_time_change(hass, action, year=None, month=None, day=None, + hour=None, minute=None, second=None): + """ Adds a listener that will fire if UTC time matches a pattern. """ + track_utc_time_change(hass, action, year, month, day, hour, minute, second, + local=True) + + +def _process_match_param(parameter): + """ Wraps parameter in a tuple if it is not one and returns it. """ + if parameter is None or parameter == MATCH_ALL: + return MATCH_ALL + elif isinstance(parameter, str) or not hasattr(parameter, '__iter__'): + return (parameter,) + else: + return tuple(parameter) + + +def _matcher(subject, pattern): + """ Returns True if subject matches the pattern. + + Pattern is either a tuple of allowed subjects or a `MATCH_ALL`. + """ + return MATCH_ALL == pattern or subject in pattern diff --git a/homeassistant/helpers/state.py b/homeassistant/helpers/state.py index 18c68808e94..66e9a448d8e 100644 --- a/homeassistant/helpers/state.py +++ b/homeassistant/helpers/state.py @@ -30,7 +30,16 @@ class TrackStates(object): return self.states def __exit__(self, exc_type, exc_value, traceback): - self.states.extend(self.hass.states.get_since(self.now)) + self.states.extend(get_changed_since(self.hass.states.all(), self.now)) + + +def get_changed_since(states, utc_point_in_time): + """ + Returns all states that have been changed since utc_point_in_time. + """ + point_in_time = dt_util.strip_microseconds(utc_point_in_time) + + return [state for state in states if state.last_updated >= point_in_time] def reproduce_state(hass, states, blocking=False): diff --git a/homeassistant/remote.py b/homeassistant/remote.py index 3decfa3ce3e..5a0a828bb21 100644 --- a/homeassistant/remote.py +++ b/homeassistant/remote.py @@ -124,7 +124,7 @@ class HomeAssistant(ha.HomeAssistant): raise ha.HomeAssistantError( 'Unable to setup local API to receive events') - ha.Timer(self) + ha.create_timer(self) self.bus.fire(ha.EVENT_HOMEASSISTANT_START, origin=ha.EventOrigin.remote) diff --git a/tests/helpers/test_event.py b/tests/helpers/test_event.py new file mode 100644 index 00000000000..c6fdf3e276a --- /dev/null +++ b/tests/helpers/test_event.py @@ -0,0 +1,126 @@ +""" +tests.helpers.event_test +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Tests event helpers. +""" +# pylint: disable=protected-access,too-many-public-methods +# pylint: disable=too-few-public-methods +import unittest +from datetime import datetime + +import homeassistant as ha +from homeassistant.helpers.event import * + + +class TestEventHelpers(unittest.TestCase): + """ + Tests the Home Assistant event helpers. + """ + + def setUp(self): # pylint: disable=invalid-name + """ things to be run when tests are started. """ + self.hass = ha.HomeAssistant() + self.hass.states.set("light.Bowl", "on") + self.hass.states.set("switch.AC", "off") + + def tearDown(self): # pylint: disable=invalid-name + """ Stop down stuff we started. """ + self.hass.stop() + + def test_track_point_in_time(self): + """ Test track point in time. """ + before_birthday = datetime(1985, 7, 9, 12, 0, 0) + birthday_paulus = datetime(1986, 7, 9, 12, 0, 0) + after_birthday = datetime(1987, 7, 9, 12, 0, 0) + + runs = [] + + track_point_in_utc_time( + self.hass, lambda x: runs.append(1), birthday_paulus) + + self._send_time_changed(before_birthday) + self.hass.pool.block_till_done() + self.assertEqual(0, len(runs)) + + self._send_time_changed(birthday_paulus) + self.hass.pool.block_till_done() + self.assertEqual(1, len(runs)) + + # A point in time tracker will only fire once, this should do nothing + self._send_time_changed(birthday_paulus) + self.hass.pool.block_till_done() + self.assertEqual(1, len(runs)) + + track_point_in_utc_time( + self.hass, lambda x: runs.append(1), birthday_paulus) + + self._send_time_changed(after_birthday) + self.hass.pool.block_till_done() + self.assertEqual(2, len(runs)) + + def test_track_time_change(self): + """ Test tracking time change. """ + wildcard_runs = [] + specific_runs = [] + + track_time_change(self.hass, lambda x: wildcard_runs.append(1)) + track_time_change( + self.hass, lambda x: specific_runs.append(1), second=[0, 30]) + + self._send_time_changed(datetime(2014, 5, 24, 12, 0, 0)) + self.hass.pool.block_till_done() + self.assertEqual(1, len(specific_runs)) + self.assertEqual(1, len(wildcard_runs)) + + self._send_time_changed(datetime(2014, 5, 24, 12, 0, 15)) + self.hass.pool.block_till_done() + self.assertEqual(1, len(specific_runs)) + self.assertEqual(2, len(wildcard_runs)) + + self._send_time_changed(datetime(2014, 5, 24, 12, 0, 30)) + self.hass.pool.block_till_done() + self.assertEqual(2, len(specific_runs)) + self.assertEqual(3, len(wildcard_runs)) + + def test_track_state_change(self): + """ Test states.track_change. """ + # 2 lists to track how often our callbacks get called + specific_runs = [] + wildcard_runs = [] + + track_state_change( + self.hass, 'light.Bowl', lambda a, b, c: specific_runs.append(1), + 'on', 'off') + + track_state_change( + self.hass, 'light.Bowl', lambda a, b, c: wildcard_runs.append(1), + ha.MATCH_ALL, ha.MATCH_ALL) + + # Set same state should not trigger a state change/listener + self.hass.states.set('light.Bowl', 'on') + self.hass.pool.block_till_done() + self.assertEqual(0, len(specific_runs)) + self.assertEqual(0, len(wildcard_runs)) + + # State change off -> on + self.hass.states.set('light.Bowl', 'off') + self.hass.pool.block_till_done() + self.assertEqual(1, len(specific_runs)) + self.assertEqual(1, len(wildcard_runs)) + + # State change off -> off + self.hass.states.set('light.Bowl', 'off', {"some_attr": 1}) + self.hass.pool.block_till_done() + self.assertEqual(1, len(specific_runs)) + self.assertEqual(2, len(wildcard_runs)) + + # State change off -> on + self.hass.states.set('light.Bowl', 'on') + self.hass.pool.block_till_done() + self.assertEqual(1, len(specific_runs)) + self.assertEqual(3, len(wildcard_runs)) + + def _send_time_changed(self, now): + """ Send a time changed event. """ + self.hass.bus.fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: now}) From 4845c1290c4306afdfd95108639a26ba1d09ac5f Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 23 Jun 2015 12:34:55 +0200 Subject: [PATCH 015/224] remove unused stuff and update the names (same as in owm sensor) --- homeassistant/components/sensor/forecast.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/sensor/forecast.py b/homeassistant/components/sensor/forecast.py index abd3cdadb73..613dd35d640 100644 --- a/homeassistant/components/sensor/forecast.py +++ b/homeassistant/components/sensor/forecast.py @@ -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' From c1b428489fb5cf959ddeb10374ff2866bf263832 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Mon, 27 Jul 2015 18:58:32 +0200 Subject: [PATCH 016/224] fix requirement --- homeassistant/components/sensor/openweathermap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/openweathermap.py b/homeassistant/components/sensor/openweathermap.py index 22720748034..f4635cd13ca 100644 --- a/homeassistant/components/sensor/openweathermap.py +++ b/homeassistant/components/sensor/openweathermap.py @@ -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', ''], From f6811e858ac80f47a71394931ff9e95d4b092ee4 Mon Sep 17 00:00:00 2001 From: Rohit Kabadi Date: Wed, 29 Jul 2015 00:24:42 -0700 Subject: [PATCH 017/224] - Removed https://github.com/rkabadi/pyedimax as submodule - Added https://github.com/rkabadi/pyedimax to requirements - Modified edimax.py to import pyedimax from python3 default packages --- .gitmodules | 3 --- homeassistant/components/switch/edimax.py | 6 ++---- homeassistant/external/pyedimax | 1 - requirements.txt | 3 +++ 4 files changed, 5 insertions(+), 8 deletions(-) delete mode 160000 homeassistant/external/pyedimax diff --git a/.gitmodules b/.gitmodules index 5d994cf6ceb..ca0b1f024b8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,6 +22,3 @@ [submodule "homeassistant/external/pymysensors"] path = homeassistant/external/pymysensors url = https://github.com/theolind/pymysensors -[submodule "homeassistant/external/pyedimax"] - path = homeassistant/external/pyedimax - url = https://github.com/rkabadi/pyedimax diff --git a/homeassistant/components/switch/edimax.py b/homeassistant/components/switch/edimax.py index 426c016b59e..e988ffec0f0 100644 --- a/homeassistant/components/switch/edimax.py +++ b/homeassistant/components/switch/edimax.py @@ -15,12 +15,10 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): """ Find and return Edimax Smart Plugs. """ try: # pylint: disable=no-name-in-module, import-error - from homeassistant.external.pyedimax.smartplug import SmartPlug + from pyedimax.smartplug import SmartPlug except ImportError: logging.getLogger(__name__).exception(( - "Failed to import pyedimax. " - "Did you maybe not run `git submodule init` " - "and `git submodule update`?")) + "Failed to import pyedimax. ")) return diff --git a/homeassistant/external/pyedimax b/homeassistant/external/pyedimax deleted file mode 160000 index 674ada04c42..00000000000 --- a/homeassistant/external/pyedimax +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 674ada04c42da5c1103205293a078be73f661fd6 diff --git a/requirements.txt b/requirements.txt index 83780721c2c..d77fe9b3b89 100644 --- a/requirements.txt +++ b/requirements.txt @@ -79,3 +79,6 @@ PyMata==2.07a # Mysensors serial gateway pyserial>=2.7 + +# PyEdimax +git+https://github.com/rkabadi/pyedimax.git \ No newline at end of file From f82b63483a1e460a9838bd43735de72184757217 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vitor=20Esp=C3=ADndola?= Date: Wed, 29 Jul 2015 14:04:32 -0300 Subject: [PATCH 018/224] Modbus coil support --- homeassistant/components/sensor/modbus.py | 64 ++++++++++------- homeassistant/components/switch/modbus.py | 83 +++++++++++++++-------- 2 files changed, 94 insertions(+), 53 deletions(-) diff --git a/homeassistant/components/sensor/modbus.py b/homeassistant/components/sensor/modbus.py index 90593875a54..b7b122d8d66 100644 --- a/homeassistant/components/sensor/modbus.py +++ b/homeassistant/components/sensor/modbus.py @@ -18,6 +18,9 @@ sensor: name: My boolean sensor 2: name: My other boolean sensor + coils: + 0: + name: My coil switch VARIABLES: @@ -25,6 +28,7 @@ VARIABLES: - "unit" = unit to attach to value (optional, ignored for boolean sensors) - "registers" contains a list of relevant registers to read from it can contain a "bits" section, listing relevant bits + - "coils" contains a list of relevant coils to read from - each named register will create an integer sensor - each named bit will create a boolean sensor @@ -49,21 +53,30 @@ def setup_platform(hass, config, add_devices, discovery_info=None): _LOGGER.error("No slave number provided for serial Modbus") return False registers = config.get("registers") - for regnum, register in registers.items(): - if register.get("name"): - sensors.append(ModbusSensor(register.get("name"), + if registers: + for regnum, register in registers.items(): + if register.get("name"): + sensors.append(ModbusSensor(register.get("name"), + slave, + regnum, + None, + register.get("unit"))) + if register.get("bits"): + bits = register.get("bits") + for bitnum, bit in bits.items(): + if bit.get("name"): + sensors.append(ModbusSensor(bit.get("name"), + slave, + regnum, + bitnum)) + coils = config.get("coils") + if coils: + for coilnum, coil in coils.items(): + sensors.append(ModbusSensor(coil.get("name"), slave, - regnum, - None, - register.get("unit"))) - if register.get("bits"): - bits = register.get("bits") - for bitnum, bit in bits.items(): - if bit.get("name"): - sensors.append(ModbusSensor(bit.get("name"), - slave, - regnum, - bitnum)) + coilnum, + coil=True)) + add_devices(sensors) @@ -71,13 +84,14 @@ class ModbusSensor(Entity): # pylint: disable=too-many-arguments """ Represents a Modbus Sensor """ - def __init__(self, name, slave, register, bit=None, unit=None): + def __init__(self, name, slave, register, bit=None, unit=None, coil=False): self._name = name self.slave = int(slave) if slave else 1 self.register = int(register) self.bit = int(bit) if bit else None self._value = None self._unit = unit + self._coil = coil def __str__(self): return "%s: %s" % (self.name, self.state) @@ -124,13 +138,17 @@ class ModbusSensor(Entity): return attr def update(self): - result = modbus.NETWORK.read_holding_registers(unit=self.slave, + if self._coil: + result = modbus.NETWORK.read_coils(self.register, 1) + self._value = result.bits[0] + else: + result = modbus.NETWORK.read_holding_registers(unit=self.slave, address=self.register, count=1) - val = 0 - for i, res in enumerate(result.registers): - val += res * (2**(i*16)) - if self.bit: - self._value = val & (0x0001 << self.bit) - else: - self._value = val + val = 0 + for i, res in enumerate(result.registers): + val += res * (2**(i*16)) + if self.bit: + self._value = val & (0x0001 << self.bit) + else: + self._value = val diff --git a/homeassistant/components/switch/modbus.py b/homeassistant/components/switch/modbus.py index 6513ba71f4a..41c45597de4 100644 --- a/homeassistant/components/switch/modbus.py +++ b/homeassistant/components/switch/modbus.py @@ -18,12 +18,16 @@ sensor: name: My switch 2: name: My other switch + coils: + 0: + name: My coil switch VARIABLES: - "slave" = slave number (ignored and can be omitted if not serial Modbus) - "registers" contains a list of relevant registers to read from - it must contain a "bits" section, listing relevant bits + - "coils" contains a list of relevant coils to read from/write to - each named bit will create a switch """ @@ -44,28 +48,38 @@ def setup_platform(hass, config, add_devices, discovery_info=None): _LOGGER.error("No slave number provided for serial Modbus") return False registers = config.get("registers") - for regnum, register in registers.items(): - bits = register.get("bits") - for bitnum, bit in bits.items(): - if bit.get("name"): - switches.append(ModbusSwitch(bit.get("name"), - slave, - regnum, - bitnum)) + if registers: + for regnum, register in registers.items(): + bits = register.get("bits") + for bitnum, bit in bits.items(): + if bit.get("name"): + switches.append(ModbusSwitch(bit.get("name"), + slave, + regnum, + bitnum)) + coils = config.get("coils") + if coils: + for coilnum, coil in coils.items(): + switches.append(ModbusSwitch(coil.get("name"), + slave, + coilnum, + 0, + coil=True)) add_devices(switches) class ModbusSwitch(ToggleEntity): """ Represents a Modbus switch. """ - def __init__(self, name, slave, register, bit): + def __init__(self, name, slave, register, bit, coil=False): self._name = name self.slave = int(slave) if slave else 1 self.register = int(register) self.bit = int(bit) + self._coil = coil self._is_on = None self.register_value = None - + def __str__(self): return "%s: %s" % (self.name, self.state) @@ -98,27 +112,36 @@ class ModbusSwitch(ToggleEntity): return attr def turn_on(self, **kwargs): - if self.register_value is None: - self.update() - val = self.register_value | (0x0001 << self.bit) - modbus.NETWORK.write_register(unit=self.slave, - address=self.register, - value=val) + if self._coil: + modbus.NETWORK.write_coil(self.register, True) + else: + if self.register_value is None: + self.update() + val = self.register_value | (0x0001 << self.bit) + modbus.NETWORK.write_register(unit=self.slave, + address=self.register, + value=val) def turn_off(self, **kwargs): - if self.register_value is None: - self.update() - val = self.register_value & ~(0x0001 << self.bit) - modbus.NETWORK.write_register(unit=self.slave, - address=self.register, - value=val) + if self._coil: + r = modbus.NETWORK.write_coil(self.register, False) + else: + if self.register_value is None: + self.update() + val = self.register_value & ~(0x0001 << self.bit) + modbus.NETWORK.write_register(unit=self.slave, + address=self.register, + value=val) def update(self): - result = modbus.NETWORK.read_holding_registers(unit=self.slave, - address=self.register, - count=1) - val = 0 - for i, res in enumerate(result.registers): - val += res * (2**(i*16)) - self.register_value = val - self._is_on = (val & (0x0001 << self.bit) > 0) + if self._coil: + result = modbus.NETWORK.read_coils(self.register, 1) + self.register_value = result.bits[0] + self._is_on = self.register_value + else: + result = modbus.NETWORK.read_holding_registers(unit=self.slave, address=self.register, count=1) + val = 0 + for i, res in enumerate(result.registers): + val += res * (2**(i*16)) + self.register_value = val + self._is_on = (val & (0x0001 << self.bit) > 0) From 6a239bf18a7e010b09e7606fd09afc03a06f1691 Mon Sep 17 00:00:00 2001 From: Rohit Kabadi Date: Thu, 30 Jul 2015 00:10:16 -0700 Subject: [PATCH 019/224] Used validate_config to ensure 'host' parameter in edimax config. Added name option to edimax config --- homeassistant/components/switch/edimax.py | 45 ++++++++++++++--------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/switch/edimax.py b/homeassistant/components/switch/edimax.py index e988ffec0f0..58cc757a1f8 100644 --- a/homeassistant/components/switch/edimax.py +++ b/homeassistant/components/switch/edimax.py @@ -6,9 +6,17 @@ Support for Edimax switches. """ import logging -from homeassistant.components.switch import SwitchDevice -from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD +from homeassistant.helpers import validate_config +from homeassistant.components.switch import SwitchDevice, DOMAIN +from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD, CONF_NAME +# constants +DEFAULT_USERNAME = 'admin' +DEFAULT_PASSWORD = '1234' +DEVICE_DEFAULT_NAME = 'Edimax Smart Plug' + +# setup logger +_LOGGER = logging.getLogger(__name__) # pylint: disable=unused-argument def setup_platform(hass, config, add_devices_callback, discovery_info=None): @@ -17,33 +25,34 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): # pylint: disable=no-name-in-module, import-error from pyedimax.smartplug import SmartPlug except ImportError: - logging.getLogger(__name__).exception(( - "Failed to import pyedimax. ")) - - return - - host = config.get(CONF_HOST) - auth = (config.get(CONF_USERNAME, 'admin'), - config.get(CONF_PASSWORD, '1234')) - - if not host: - logging.getLogger(__name__).error( - 'Missing config variable %s', CONF_HOST) + _LOGGER.error('Failed to import pyedimax') return False - add_devices_callback([SmartPlugSwitch(SmartPlug(host, auth))]) + # pylint: disable=global-statement + # check for required values in configuration file + if not validate_config({DOMAIN: config}, + {DOMAIN: [CONF_HOST]}, + _LOGGER): + return False + + host = config.get(CONF_HOST) + auth = (config.get(CONF_USERNAME, DEFAULT_USERNAME), + config.get(CONF_PASSWORD, DEFAULT_PASSWORD)) + name = config.get(CONF_NAME, DEVICE_DEFAULT_NAME) + + add_devices_callback([SmartPlugSwitch(SmartPlug(host, auth), name)]) class SmartPlugSwitch(SwitchDevice): """ Represents an Edimax Smart Plug switch within Home Assistant. """ - def __init__(self, smartplug): + def __init__(self, smartplug, name): self.smartplug = smartplug + self._name = name @property def name(self): """ Returns the name of the Smart Plug, if any. """ - #TODO: dynamically get name from device using requests - return 'Edimax Smart Plug' + return self._name @property def current_power_mwh(self): From f351ab9544d50dc39b806e3a31c05bc17f76b2a9 Mon Sep 17 00:00:00 2001 From: Rohit Kabadi Date: Thu, 30 Jul 2015 00:37:11 -0700 Subject: [PATCH 020/224] Updated branch to avoid conflicts in requirements.txt --- requirements.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/requirements.txt b/requirements.txt index f0f49e5a248..b4adce86d22 100644 --- a/requirements.txt +++ b/requirements.txt @@ -91,3 +91,6 @@ pywemo>=0.1 # Wink (*.wink) https://github.com/balloob/python-wink/archive/master.zip#pywink>=0.1 + +# PyEdimax +https://github.com/rkabadi/pyedimax/archive/master.zip \ No newline at end of file From ed0164843a2f0f71797d49431a4ee03c8a3c21d5 Mon Sep 17 00:00:00 2001 From: Per Sandstrom Date: Thu, 30 Jul 2015 11:30:31 +0200 Subject: [PATCH 021/224] Added support for ASUSWRT based routers --- .coveragerc | 1 + README.md | 2 +- .../components/device_tracker/asuswrt.py | 165 ++++++++++++++++++ 3 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/device_tracker/asuswrt.py diff --git a/.coveragerc b/.coveragerc index 39a3dee22bf..7c21d3a6ed5 100644 --- a/.coveragerc +++ b/.coveragerc @@ -26,6 +26,7 @@ omit = homeassistant/components/*/vera.py homeassistant/components/browser.py + homeassistant/components/device_tracker/asuswrt.py homeassistant/components/device_tracker/ddwrt.py homeassistant/components/device_tracker/luci.py homeassistant/components/device_tracker/netgear.py diff --git a/README.md b/README.md index 18a01345741..11d1e3ffd85 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Check out [the website](https://home-assistant.io) for installation instructions Examples of devices it can interface it: - * Monitoring connected devices to a wireless router: [OpenWrt](https://openwrt.org/), [Tomato](http://www.polarcloud.com/tomato), [Netgear](http://netgear.com), [DD-WRT](http://www.dd-wrt.com/site/index), [TPLink](http://www.tp-link.us/) + * Monitoring connected devices to a wireless router: [OpenWrt](https://openwrt.org/), [Tomato](http://www.polarcloud.com/tomato), [Netgear](http://netgear.com), [DD-WRT](http://www.dd-wrt.com/site/index), [TPLink](http://www.tp-link.us/), [ASUSWRT](http://event.asus.com/2013/nw/ASUSWRT/) * [Philips Hue](http://meethue.com) lights, [WeMo](http://www.belkin.com/us/Products/home-automation/c/wemo-home-automation/) switches, and [Tellstick](http://www.telldus.se/products/tellstick) devices and sensors * [Google Chromecasts](http://www.google.com/intl/en/chrome/devices/chromecast), [Music Player Daemon](http://www.musicpd.org/) and [Kodi (XBMC)](http://kodi.tv/) * Support for [ISY994](https://www.universal-devices.com/residential/isy994i-series/) (Insteon and X10 devices), [Z-Wave](http://www.z-wave.com/), [Nest Thermostats](https://nest.com/), and [Modbus](http://www.modbus.org/) diff --git a/homeassistant/components/device_tracker/asuswrt.py b/homeassistant/components/device_tracker/asuswrt.py new file mode 100644 index 00000000000..e38e410fe54 --- /dev/null +++ b/homeassistant/components/device_tracker/asuswrt.py @@ -0,0 +1,165 @@ +""" +homeassistant.components.device_tracker.asuswrt +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Device tracker platform that supports scanning a ASUSWRT router for device +presence. + +This device tracker needs telnet to be enabled on the router + +Configuration: + +To use the ASUSWRT tracker you will need to add something like the following +to your config/configuration.yaml + +device_tracker: + platform: asuswrt + host: YOUR_ROUTER_IP + username: YOUR_ADMIN_USERNAME + password: YOUR_ADMIN_PASSWORD + +Variables: + +host +*Required +The IP address of your router, e.g. 192.168.1.1. + +username +*Required +The username of an user with administrative privileges, usually 'admin'. + +password +*Required +The password for your given admin account. +""" +import logging +from datetime import timedelta +import re +import threading +import telnetlib + +from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD +from homeassistant.helpers import validate_config +from homeassistant.util import Throttle +from homeassistant.components.device_tracker import DOMAIN + +# Return cached results if last scan was less then this time ago +MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) + +_LOGGER = logging.getLogger(__name__) + +_LEASES_REGEX = re.compile( + r'\w+\s' + + r'(?P(([0-9a-f]{2}[:-]){5}([0-9a-f]{2})))\s' + + r'(?P([0-9]{1,3}[\.]){3}[0-9]{1,3})\s' + + r'(?P([^\s]+))') + +_IP_NEIGH_REGEX = re.compile( + r'(?P([0-9]{1,3}[\.]){3}[0-9]{1,3})\s' + + r'\w+\s' + + r'\w+\s' + + r'(\w+\s(?P(([0-9a-f]{2}[:-]){5}([0-9a-f]{2}))))?\s' + + r'(?P(\w+))') + + +def get_scanner(hass, config): + """ Validates config and returns a DD-WRT scanner. """ + if not validate_config(config, + {DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]}, + _LOGGER): + return None + + scanner = AsusWrtDeviceScanner(config[DOMAIN]) + + return scanner if scanner.success_init else None + + +class AsusWrtDeviceScanner(object): + """ This class queries a router running ASUSWRT firmware + for connected devices. Adapted from DD-WRT scanner. + """ + + def __init__(self, config): + self.host = config[CONF_HOST] + self.username = config[CONF_USERNAME] + self.password = config[CONF_PASSWORD] + + self.lock = threading.Lock() + + self.last_results = {} + + # Test the router is accessible + data = self.get_asuswrt_data() + self.success_init = data is not None + + def scan_devices(self): + """ Scans for new devices and return a + list containing found device ids. """ + + self._update_info() + return [client['mac'] for client in self.last_results] + + def get_device_name(self, device): + """ Returns the name of the given device or None if we don't know. """ + if not self.last_results: + return None + for client in self.last_results: + if client['mac'] == device: + return client['host'] + return None + + @Throttle(MIN_TIME_BETWEEN_SCANS) + def _update_info(self): + """ Ensures the information from the ASUSWRT router is up to date. + Returns boolean if scanning successful. """ + if not self.success_init: + return False + + with self.lock: + _LOGGER.info("Checking ARP") + data = self.get_asuswrt_data() + if not data: + return False + + active_clients = [client for client in data.values() if + client['status'] == 'REACHABLE' or + client['status'] == 'DELAY' or + client['status'] == 'STALE'] + self.last_results = active_clients + return True + + def get_asuswrt_data(self): + """ Retrieve data from ASUSWRT and return parsed result. """ + try: + telnet = telnetlib.Telnet(self.host) + telnet.read_until(b'login: ') + telnet.write((self.username + '\n').encode('ascii')) + telnet.read_until(b'Password: ') + telnet.write((self.password + '\n').encode('ascii')) + prompt_string = telnet.read_until(b'#').split(b'\n')[-1] + telnet.write('ip neigh\n'.encode('ascii')) + neighbors = telnet.read_until(prompt_string).split(b'\n')[1:-1] + telnet.write('cat /var/lib/misc/dnsmasq.leases\n'.encode('ascii')) + leases_result = telnet.read_until(prompt_string).split(b'\n')[1:-1] + telnet.write('exit\n'.encode('ascii')) + except EOFError: + _LOGGER.exception("Unexpected response from router") + return + except ConnectionRefusedError: + _LOGGER.exception("Connection refused by router, is telnet enabled?") + return + + devices = {} + for lease in leases_result: + match = _LEASES_REGEX.search(lease.decode('utf-8')) + devices[match.group('ip')] = { + 'ip': match.group('ip'), + 'mac': match.group('mac').upper(), + 'host': match.group('host') + } + + for neighbor in neighbors: + match = _IP_NEIGH_REGEX.search(neighbor.decode('utf-8')) + if match.group('ip') in devices: + devices[match.group('ip')]['status'] = match.group('status') + return devices From ffde7e183e81031de7fd6fabbc5f65b137ac205b Mon Sep 17 00:00:00 2001 From: Rohit Kabadi Date: Thu, 30 Jul 2015 21:05:00 -0700 Subject: [PATCH 022/224] Fixed flake8 violations --- homeassistant/components/switch/edimax.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/switch/edimax.py b/homeassistant/components/switch/edimax.py index 58cc757a1f8..ab461f4de07 100644 --- a/homeassistant/components/switch/edimax.py +++ b/homeassistant/components/switch/edimax.py @@ -8,7 +8,8 @@ import logging from homeassistant.helpers import validate_config from homeassistant.components.switch import SwitchDevice, DOMAIN -from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD, CONF_NAME +from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD,\ + CONF_NAME # constants DEFAULT_USERNAME = 'admin' @@ -18,6 +19,7 @@ DEVICE_DEFAULT_NAME = 'Edimax Smart Plug' # setup logger _LOGGER = logging.getLogger(__name__) + # pylint: disable=unused-argument def setup_platform(hass, config, add_devices_callback, discovery_info=None): """ Find and return Edimax Smart Plugs. """ From 6873504cc0524e1bf471b2eac552d73b786bf97b Mon Sep 17 00:00:00 2001 From: jamespcole Date: Sat, 1 Aug 2015 06:45:41 +1000 Subject: [PATCH 023/224] Fixed linting errors --- homeassistant/components/notify/slack.py | 94 ++++++++++++++++++++++++ requirements.txt | 3 + 2 files changed, 97 insertions(+) create mode 100644 homeassistant/components/notify/slack.py diff --git a/homeassistant/components/notify/slack.py b/homeassistant/components/notify/slack.py new file mode 100644 index 00000000000..adf22fa5bea --- /dev/null +++ b/homeassistant/components/notify/slack.py @@ -0,0 +1,94 @@ +""" +homeassistant.components.notify.slack +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Slack platform for notify component. + +Configuration: + +To use the Slack notifier you will need to add something like the following +to your config/configuration.yaml + +notify: + platform: slack + api_key: ABCDEFGHJKLMNOPQRSTUVXYZ + default_channel: '#general' + +Variables: + +api_key +*Required +The slack API token to use for sending slack messages. +You can get your slack API token here https://api.slack.com/web?sudo=1 + +default_channel +*Required +The default channel to post to if no channel is explicitly specified when +sending the notification message. + +""" +import logging + +from homeassistant.helpers import validate_config +from homeassistant.components.notify import ( + DOMAIN, BaseNotificationService) +from homeassistant.const import CONF_API_KEY + +REQUIREMENTS = ['slacker>=0.6.8'] +_LOGGER = logging.getLogger(__name__) + + +# pylint: disable=unused-variable +def get_service(hass, config): + """ Get the slack notification service. """ + + if not validate_config(config, + {DOMAIN: ['default_channel', CONF_API_KEY]}, + _LOGGER): + return None + + try: + # pylint: disable=no-name-in-module, unused-variable + from slacker import Error as SlackError + + except ImportError: + _LOGGER.exception( + "Unable to import slacker. " + "Did you maybe not install the 'slacker.py' package?") + + return None + + try: + api_token = config[DOMAIN].get(CONF_API_KEY) + + return SlackNotificationService( + config[DOMAIN]['default_channel'], + api_token) + + except SlackError as ex: + _LOGGER.error( + "Slack authentication failed") + _LOGGER.exception(ex) + + +# pylint: disable=too-few-public-methods +class SlackNotificationService(BaseNotificationService): + """ Implements notification service for Slack. """ + + def __init__(self, default_channel, api_token): + from slacker import Slacker + self._default_channel = default_channel + self._api_token = api_token + self.slack = Slacker(self._api_token) + self.slack.auth.test() + + def send_message(self, message="", **kwargs): + """ Send a message to a user. """ + + from slacker import Error as SlackError + channel = kwargs.get('channel', self._default_channel) + try: + self.slack.chat.post_message(channel, message) + except SlackError as ex: + _LOGGER.exception("Could not send slack notification") + _LOGGER.exception(ex) diff --git a/requirements.txt b/requirements.txt index f0f49e5a248..763db9c2539 100644 --- a/requirements.txt +++ b/requirements.txt @@ -91,3 +91,6 @@ pywemo>=0.1 # Wink (*.wink) https://github.com/balloob/python-wink/archive/master.zip#pywink>=0.1 + +# Slack notifier +slacker>=0.6.8 \ No newline at end of file From 1b3a45aba9aba980c7d31a9003e9885e271c7a33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vitor=20Esp=C3=ADndola?= Date: Wed, 29 Jul 2015 14:04:32 -0300 Subject: [PATCH 024/224] Modbus coil support --- homeassistant/components/sensor/modbus.py | 72 ++++++++++-------- homeassistant/components/switch/modbus.py | 91 ++++++++++++++--------- 2 files changed, 100 insertions(+), 63 deletions(-) diff --git a/homeassistant/components/sensor/modbus.py b/homeassistant/components/sensor/modbus.py index 90593875a54..7893ee5935e 100644 --- a/homeassistant/components/sensor/modbus.py +++ b/homeassistant/components/sensor/modbus.py @@ -18,6 +18,9 @@ sensor: name: My boolean sensor 2: name: My other boolean sensor + coils: + 0: + name: My coil switch VARIABLES: @@ -25,6 +28,7 @@ VARIABLES: - "unit" = unit to attach to value (optional, ignored for boolean sensors) - "registers" contains a list of relevant registers to read from it can contain a "bits" section, listing relevant bits + - "coils" contains a list of relevant coils to read from - each named register will create an integer sensor - each named bit will create a boolean sensor @@ -49,21 +53,30 @@ def setup_platform(hass, config, add_devices, discovery_info=None): _LOGGER.error("No slave number provided for serial Modbus") return False registers = config.get("registers") - for regnum, register in registers.items(): - if register.get("name"): - sensors.append(ModbusSensor(register.get("name"), + if registers: + for regnum, register in registers.items(): + if register.get("name"): + sensors.append(ModbusSensor(register.get("name"), + slave, + regnum, + None, + register.get("unit"))) + if register.get("bits"): + bits = register.get("bits") + for bitnum, bit in bits.items(): + if bit.get("name"): + sensors.append(ModbusSensor(bit.get("name"), + slave, + regnum, + bitnum)) + coils = config.get("coils") + if coils: + for coilnum, coil in coils.items(): + sensors.append(ModbusSensor(coil.get("name"), slave, - regnum, - None, - register.get("unit"))) - if register.get("bits"): - bits = register.get("bits") - for bitnum, bit in bits.items(): - if bit.get("name"): - sensors.append(ModbusSensor(bit.get("name"), - slave, - regnum, - bitnum)) + coilnum, + coil=True)) + add_devices(sensors) @@ -71,13 +84,14 @@ class ModbusSensor(Entity): # pylint: disable=too-many-arguments """ Represents a Modbus Sensor """ - def __init__(self, name, slave, register, bit=None, unit=None): + def __init__(self, name, slave, register, bit=None, unit=None, coil=False): self._name = name self.slave = int(slave) if slave else 1 self.register = int(register) self.bit = int(bit) if bit else None self._value = None self._unit = unit + self._coil = coil def __str__(self): return "%s: %s" % (self.name, self.state) @@ -118,19 +132,19 @@ class ModbusSensor(Entity): else: return self._unit - @property - def state_attributes(self): - attr = super().state_attributes - return attr - def update(self): - result = modbus.NETWORK.read_holding_registers(unit=self.slave, - address=self.register, - count=1) - val = 0 - for i, res in enumerate(result.registers): - val += res * (2**(i*16)) - if self.bit: - self._value = val & (0x0001 << self.bit) + """ Update the state of the sensor. """ + if self._coil: + result = modbus.NETWORK.read_coils(self.register, 1) + self._value = result.bits[0] else: - self._value = val + result = modbus.NETWORK.read_holding_registers( + unit=self.slave, address=self.register, + count=1) + val = 0 + for i, res in enumerate(result.registers): + val += res * (2**(i*16)) + if self.bit: + self._value = val & (0x0001 << self.bit) + else: + self._value = val diff --git a/homeassistant/components/switch/modbus.py b/homeassistant/components/switch/modbus.py index 6513ba71f4a..21a4e0087c2 100644 --- a/homeassistant/components/switch/modbus.py +++ b/homeassistant/components/switch/modbus.py @@ -18,12 +18,16 @@ sensor: name: My switch 2: name: My other switch + coils: + 0: + name: My coil switch VARIABLES: - "slave" = slave number (ignored and can be omitted if not serial Modbus) - "registers" contains a list of relevant registers to read from - it must contain a "bits" section, listing relevant bits + - "coils" contains a list of relevant coils to read from/write to - each named bit will create a switch """ @@ -44,25 +48,35 @@ def setup_platform(hass, config, add_devices, discovery_info=None): _LOGGER.error("No slave number provided for serial Modbus") return False registers = config.get("registers") - for regnum, register in registers.items(): - bits = register.get("bits") - for bitnum, bit in bits.items(): - if bit.get("name"): - switches.append(ModbusSwitch(bit.get("name"), - slave, - regnum, - bitnum)) + if registers: + for regnum, register in registers.items(): + bits = register.get("bits") + for bitnum, bit in bits.items(): + if bit.get("name"): + switches.append(ModbusSwitch(bit.get("name"), + slave, + regnum, + bitnum)) + coils = config.get("coils") + if coils: + for coilnum, coil in coils.items(): + switches.append(ModbusSwitch(coil.get("name"), + slave, + coilnum, + 0, + coil=True)) add_devices(switches) class ModbusSwitch(ToggleEntity): """ Represents a Modbus switch. """ - def __init__(self, name, slave, register, bit): + def __init__(self, name, slave, register, bit, coil=False): self._name = name self.slave = int(slave) if slave else 1 self.register = int(register) self.bit = int(bit) + self._coil = coil self._is_on = None self.register_value = None @@ -92,33 +106,42 @@ class ModbusSwitch(ToggleEntity): """ Get the name of the switch. """ return self._name - @property - def state_attributes(self): - attr = super().state_attributes - return attr - def turn_on(self, **kwargs): - if self.register_value is None: - self.update() - val = self.register_value | (0x0001 << self.bit) - modbus.NETWORK.write_register(unit=self.slave, - address=self.register, - value=val) + """ Set switch on. """ + if self._coil: + modbus.NETWORK.write_coil(self.register, True) + else: + if self.register_value is None: + self.update() + val = self.register_value | (0x0001 << self.bit) + modbus.NETWORK.write_register(unit=self.slave, + address=self.register, + value=val) def turn_off(self, **kwargs): - if self.register_value is None: - self.update() - val = self.register_value & ~(0x0001 << self.bit) - modbus.NETWORK.write_register(unit=self.slave, - address=self.register, - value=val) + """ Set switch off. """ + if self._coil: + modbus.NETWORK.write_coil(self.register, False) + else: + if self.register_value is None: + self.update() + val = self.register_value & ~(0x0001 << self.bit) + modbus.NETWORK.write_register(unit=self.slave, + address=self.register, + value=val) def update(self): - result = modbus.NETWORK.read_holding_registers(unit=self.slave, - address=self.register, - count=1) - val = 0 - for i, res in enumerate(result.registers): - val += res * (2**(i*16)) - self.register_value = val - self._is_on = (val & (0x0001 << self.bit) > 0) + """ Update the state of the switch. """ + if self._coil: + result = modbus.NETWORK.read_coils(self.register, 1) + self.register_value = result.bits[0] + self._is_on = self.register_value + else: + result = modbus.NETWORK.read_holding_registers( + unit=self.slave, address=self.register, + count=1) + val = 0 + for i, res in enumerate(result.registers): + val += res * (2**(i*16)) + self.register_value = val + self._is_on = (val & (0x0001 << self.bit) > 0) From 3c08a5ee6ebff7f8fee61cc559065864e301a1b0 Mon Sep 17 00:00:00 2001 From: Rohit Kabadi Date: Sat, 1 Aug 2015 12:20:29 -0700 Subject: [PATCH 025/224] Added support for temper temperature sensors --- homeassistant/components/sensor/temper.py | 50 +++++++++++++++++++++++ requirements.txt | 3 ++ 2 files changed, 53 insertions(+) create mode 100644 homeassistant/components/sensor/temper.py diff --git a/homeassistant/components/sensor/temper.py b/homeassistant/components/sensor/temper.py new file mode 100644 index 00000000000..1888ba05d3c --- /dev/null +++ b/homeassistant/components/sensor/temper.py @@ -0,0 +1,50 @@ +""" +homeassistant.components.sensor.temper +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Support for getting temperature from TEMPer devices +""" + +import logging +from homeassistant.helpers.entity import Entity + +_LOGGER = logging.getLogger(__name__) + + +# pylint: disable=unused-argument +def setup_platform(hass, config, add_devices_callback, discovery_info=None): + """ Find and return Temper sensors. """ + try: + # pylint: disable=no-name-in-module, import-error + from temperusb.temper import TemperHandler + except ImportError: + _LOGGER.error('Failed to import temperusb') + return False + + temp_unit = hass.config.temperature_unit + temper_devices = TemperHandler().get_devices() + add_devices_callback([TemperSensor(dev, temp_unit) for dev in temper_devices]) + + +class TemperSensor(Entity): + def __init__(self, temper_device, temp_unit): + self.temper_device = temper_device + self.temp_unit = temp_unit + self.current_value = None + + @property + def state(self): + """ Returns the state of the entity. """ + return self.current_value + + @property + def unit_of_measurement(self): + """ Unit of measurement of this entity, if any. """ + return self.temp_unit + + def update(self): + """ Retrieve latest state. """ + try: + self.current_value = self.temper_device.get_temperature() + except Exception: + _LOGGER.error('Failed to get temperature due to insufficient permissions') diff --git a/requirements.txt b/requirements.txt index c7a569fad33..a16eadeaeb9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -79,3 +79,6 @@ PyMata==2.07a # Mysensors serial gateway pyserial>=2.7 + +# Temper sensors +https://github.com/rkabadi/temper-python/archive/master.zip From 0e153183d4b760d1124aaf2f4361130388d9ad15 Mon Sep 17 00:00:00 2001 From: Rohit Kabadi Date: Sat, 1 Aug 2015 12:46:28 -0700 Subject: [PATCH 026/224] Added name for temper, added to coveragerc, --- .coveragerc | 1 + homeassistant/components/sensor/temper.py | 16 +++++++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/.coveragerc b/.coveragerc index 39a3dee22bf..ac312bad8c2 100644 --- a/.coveragerc +++ b/.coveragerc @@ -51,6 +51,7 @@ omit = homeassistant/components/sensor/sabnzbd.py homeassistant/components/sensor/swiss_public_transport.py homeassistant/components/sensor/systemmonitor.py + homeassistant/components/sensor/temper.py homeassistant/components/sensor/time_date.py homeassistant/components/sensor/transmission.py homeassistant/components/switch/hikvisioncam.py diff --git a/homeassistant/components/sensor/temper.py b/homeassistant/components/sensor/temper.py index 1888ba05d3c..b99ee407400 100644 --- a/homeassistant/components/sensor/temper.py +++ b/homeassistant/components/sensor/temper.py @@ -7,6 +7,7 @@ Support for getting temperature from TEMPer devices import logging from homeassistant.helpers.entity import Entity +from homeassistant.const import CONF_NAME, DEVICE_DEFAULT_NAME _LOGGER = logging.getLogger(__name__) @@ -22,15 +23,23 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): return False temp_unit = hass.config.temperature_unit + name = config.get(CONF_NAME, DEVICE_DEFAULT_NAME) temper_devices = TemperHandler().get_devices() - add_devices_callback([TemperSensor(dev, temp_unit) for dev in temper_devices]) + add_devices_callback([TemperSensor(dev, temp_unit, name + '_' + str(idx)) + for idx, dev in enumerate(temper_devices)]) class TemperSensor(Entity): - def __init__(self, temper_device, temp_unit): + def __init__(self, temper_device, temp_unit, name): self.temper_device = temper_device self.temp_unit = temp_unit self.current_value = None + self._name = name + + @property + def name(self): + """ Returns the name of the temperature sensor. """ + return self._name @property def state(self): @@ -47,4 +56,5 @@ class TemperSensor(Entity): try: self.current_value = self.temper_device.get_temperature() except Exception: - _LOGGER.error('Failed to get temperature due to insufficient permissions') + _LOGGER.error('Failed to get temperature due to insufficient ' + 'permissions. Try running with "sudo"') From c248d5455e7f21971527df8ab4637e3f8189882c Mon Sep 17 00:00:00 2001 From: Rohit Kabadi Date: Sun, 2 Aug 2015 18:01:31 -0700 Subject: [PATCH 027/224] Added REQUIREMENTS lilst to edimax.py --- homeassistant/components/switch/edimax.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/switch/edimax.py b/homeassistant/components/switch/edimax.py index ab461f4de07..171183900df 100644 --- a/homeassistant/components/switch/edimax.py +++ b/homeassistant/components/switch/edimax.py @@ -15,6 +15,7 @@ from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD,\ DEFAULT_USERNAME = 'admin' DEFAULT_PASSWORD = '1234' DEVICE_DEFAULT_NAME = 'Edimax Smart Plug' +REQUIREMENTS = ['https://github.com/rkabadi/pyedimax/archive/master.zip'] # setup logger _LOGGER = logging.getLogger(__name__) From e6aabb9706fde49dd5f6417c522c0bdf728e763c Mon Sep 17 00:00:00 2001 From: Rohit Kabadi Date: Sun, 2 Aug 2015 18:51:13 -0700 Subject: [PATCH 028/224] Fixed flake8 violations --- homeassistant/components/sensor/temper.py | 3 ++- requirements.txt | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/temper.py b/homeassistant/components/sensor/temper.py index b99ee407400..ab95ab70793 100644 --- a/homeassistant/components/sensor/temper.py +++ b/homeassistant/components/sensor/temper.py @@ -30,6 +30,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): class TemperSensor(Entity): + """ Represents an Temper temperature sensor within Home Assistant. """ def __init__(self, temper_device, temp_unit, name): self.temper_device = temper_device self.temp_unit = temp_unit @@ -55,6 +56,6 @@ class TemperSensor(Entity): """ Retrieve latest state. """ try: self.current_value = self.temper_device.get_temperature() - except Exception: + except IOError: _LOGGER.error('Failed to get temperature due to insufficient ' 'permissions. Try running with "sudo"') diff --git a/requirements.txt b/requirements.txt index f0f49e5a248..7d8e7839d68 100644 --- a/requirements.txt +++ b/requirements.txt @@ -91,3 +91,6 @@ pywemo>=0.1 # Wink (*.wink) https://github.com/balloob/python-wink/archive/master.zip#pywink>=0.1 + +# Temper sensors +https://github.com/rkabadi/temper-python/archive/master.zip \ No newline at end of file From 6c6ae9cb1af9c49375ee8df751fa52f7a654d88e Mon Sep 17 00:00:00 2001 From: Rohit Kabadi Date: Sun, 2 Aug 2015 18:55:30 -0700 Subject: [PATCH 029/224] Added REQUIREMENTS list to temper.py --- homeassistant/components/sensor/temper.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/sensor/temper.py b/homeassistant/components/sensor/temper.py index ab95ab70793..c881ce801ce 100644 --- a/homeassistant/components/sensor/temper.py +++ b/homeassistant/components/sensor/temper.py @@ -11,6 +11,7 @@ from homeassistant.const import CONF_NAME, DEVICE_DEFAULT_NAME _LOGGER = logging.getLogger(__name__) +REQUIREMENTS = ['https://github.com/rkabadi/temper-python/archive/master.zip'] # pylint: disable=unused-argument def setup_platform(hass, config, add_devices_callback, discovery_info=None): From 65d32c742559cf9b52eed3989fa12af2d42a154f Mon Sep 17 00:00:00 2001 From: Rohit Kabadi Date: Sun, 2 Aug 2015 18:58:30 -0700 Subject: [PATCH 030/224] Added blank line to temper.py --- homeassistant/components/sensor/temper.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/sensor/temper.py b/homeassistant/components/sensor/temper.py index c881ce801ce..d52164fd0e1 100644 --- a/homeassistant/components/sensor/temper.py +++ b/homeassistant/components/sensor/temper.py @@ -13,6 +13,7 @@ _LOGGER = logging.getLogger(__name__) REQUIREMENTS = ['https://github.com/rkabadi/temper-python/archive/master.zip'] + # pylint: disable=unused-argument def setup_platform(hass, config, add_devices_callback, discovery_info=None): """ Find and return Temper sensors. """ From 7870e9a5e23b5a35b1514089af21befd234f4416 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 3 Aug 2015 17:05:33 +0200 Subject: [PATCH 031/224] Minor cleanup core --- homeassistant/__init__.py | 14 +++++---- homeassistant/bootstrap.py | 31 +++++++++---------- homeassistant/components/http.py | 12 +++++--- homeassistant/config.py | 11 +++---- homeassistant/helpers/entity_component.py | 16 ---------- homeassistant/loader.py | 37 +++++++++++------------ homeassistant/util/__init__.py | 7 ++--- 7 files changed, 55 insertions(+), 73 deletions(-) diff --git a/homeassistant/__init__.py b/homeassistant/__init__.py index c62128453e7..bfbb944b23f 100644 --- a/homeassistant/__init__.py +++ b/homeassistant/__init__.py @@ -33,7 +33,7 @@ TIMER_INTERVAL = 1 # seconds SERVICE_CALL_LIMIT = 10 # seconds # Define number of MINIMUM worker threads. -# During bootstrap of HA (see bootstrap.from_config_dict()) worker threads +# During bootstrap of HA (see bootstrap._setup_component()) worker threads # will be added for each component that polls devices. MIN_WORKER_THREAD = 2 @@ -42,7 +42,7 @@ ENTITY_ID_PATTERN = re.compile(r"^(?P\w+)\.(?P\w+)$") _LOGGER = logging.getLogger(__name__) -# Temporary addition to proxy deprecated methods +# Temporary to support deprecated methods _MockHA = namedtuple("MockHomeAssistant", ['bus']) @@ -62,7 +62,6 @@ class HomeAssistant(object): "Starting Home Assistant (%d threads)", self.pool.worker_count) create_timer(self) - self.bus.fire(EVENT_HOMEASSISTANT_START) def block_till_stopped(self): @@ -70,13 +69,16 @@ class HomeAssistant(object): will block until called. """ request_shutdown = threading.Event() - self.services.register(DOMAIN, SERVICE_HOMEASSISTANT_STOP, - lambda service: request_shutdown.set()) + def stop_homeassistant(service): + """ Stops Home Assistant. """ + request_shutdown.set() + + self.services.register( + DOMAIN, SERVICE_HOMEASSISTANT_STOP, stop_homeassistant) while not request_shutdown.isSet(): try: time.sleep(1) - except KeyboardInterrupt: break diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 2da2f4fb7b5..663ed611de4 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -83,33 +83,30 @@ def _setup_component(hass, domain, config): _LOGGER.error( 'Not initializing %s because not all dependencies loaded: %s', domain, ", ".join(missing_deps)) - return False if not _handle_requirements(component, domain): return False try: - if component.setup(hass, config): - hass.config.components.append(component.DOMAIN) - - # Assumption: if a component does not depend on groups - # it communicates with devices - if group.DOMAIN not in component.DEPENDENCIES: - hass.pool.add_worker() - - hass.bus.fire( - EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: component.DOMAIN}) - - return True - - else: + if not component.setup(hass, config): _LOGGER.error('component %s failed to initialize', domain) - + return False except Exception: # pylint: disable=broad-except _LOGGER.exception('Error during setup of component %s', domain) + return False - return False + hass.config.components.append(component.DOMAIN) + + # Assumption: if a component does not depend on groups + # it communicates with devices + if group.DOMAIN not in component.DEPENDENCIES: + hass.pool.add_worker() + + hass.bus.fire( + EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: component.DOMAIN}) + + return True def prepare_setup_platform(hass, config, domain, platform_name): diff --git a/homeassistant/components/http.py b/homeassistant/components/http.py index cb8e87490f3..fcd17356885 100644 --- a/homeassistant/components/http.py +++ b/homeassistant/components/http.py @@ -187,10 +187,12 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer): _LOGGER.info("running http in development mode") def start(self): - """ Starts the server. """ - self.hass.bus.listen_once( - ha.EVENT_HOMEASSISTANT_STOP, - lambda event: self.shutdown()) + """ Starts the HTTP server. """ + def stop_http(event): + """ Stops the HTTP server. """ + self.shutdown() + + self.hass.bus.listen_once(ha.EVENT_HOMEASSISTANT_STOP, stop_http) _LOGGER.info( "Starting web interface at http://%s:%d", *self.server_address) @@ -203,7 +205,7 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer): self.serve_forever() def register_path(self, method, url, callback, require_auth=True): - """ Regitsters a path wit the server. """ + """ Registers a path wit the server. """ self.paths.append((method, url, callback, require_auth)) diff --git a/homeassistant/config.py b/homeassistant/config.py index ec177776d8f..629001b972f 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -120,17 +120,16 @@ def load_yaml_config_file(config_path): import yaml def parse(fname): - """ Actually parse the file. """ + """ Parse a YAML file. """ try: with open(fname) as conf_file: # If configuration file is empty YAML returns None # We convert that to an empty dict - conf_dict = yaml.load(conf_file) or {} + return yaml.load(conf_file) or {} except yaml.YAMLError: - _LOGGER.exception('Error reading YAML configuration file %s', - fname) - raise HomeAssistantError() - return conf_dict + error = 'Error reading YAML configuration file {}'.format(fname) + _LOGGER.exception(error) + raise HomeAssistantError(error) def yaml_include(loader, node): """ diff --git a/homeassistant/helpers/entity_component.py b/homeassistant/helpers/entity_component.py index 381584fabb8..0a120afa2b3 100644 --- a/homeassistant/helpers/entity_component.py +++ b/homeassistant/helpers/entity_component.py @@ -135,22 +135,6 @@ class EntityComponent(object): self.hass, platform_config, self.add_entities, discovery_info) self.hass.config.components.append(platform_name) - - except AttributeError: - # AttributeError if setup_platform does not exist - # Support old deprecated method for now - 3/1/2015 - if hasattr(platform, 'get_devices'): - self.logger.warning( - 'Please upgrade %s to return new entities using ' - 'setup_platform. See %s/demo.py for an example.', - platform_name, self.domain) - self.add_entities( - platform.get_devices(self.hass, platform_config)) - - else: - self.logger.exception( - 'Error while setting up platform %s', platform_type) - except Exception: # pylint: disable=broad-except self.logger.exception( 'Error while setting up platform %s', platform_type) diff --git a/homeassistant/loader.py b/homeassistant/loader.py index d38e0df4465..b0eb8d287db 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -61,11 +61,10 @@ def prepare(hass): # python components. If this assumption is not true, HA won't break, # just might output more errors. for fil in os.listdir(custom_path): - if os.path.isdir(os.path.join(custom_path, fil)): - if fil != '__pycache__': - AVAILABLE_COMPONENTS.append( - 'custom_components.{}'.format(fil)) - + if fil == '__pycache__': + continue + elif os.path.isdir(os.path.join(custom_path, fil)): + AVAILABLE_COMPONENTS.append('custom_components.{}'.format(fil)) else: # For files we will strip out .py extension AVAILABLE_COMPONENTS.append( @@ -195,24 +194,24 @@ def _load_order_component(comp_name, load_order, loading): for dependency in component.DEPENDENCIES: # Check not already loaded - if dependency not in load_order: - # If we are already loading it, we have a circular dependency - if dependency in loading: - _LOGGER.error('Circular dependency detected: %s -> %s', - comp_name, dependency) + if dependency in load_order: + continue - return OrderedSet() + # If we are already loading it, we have a circular dependency + if dependency in loading: + _LOGGER.error('Circular dependency detected: %s -> %s', + comp_name, dependency) + return OrderedSet() - dep_load_order = _load_order_component( - dependency, load_order, loading) + dep_load_order = _load_order_component(dependency, load_order, loading) - # length == 0 means error loading dependency or children - if len(dep_load_order) == 0: - _LOGGER.error('Error loading %s dependency: %s', - comp_name, dependency) - return OrderedSet() + # length == 0 means error loading dependency or children + if len(dep_load_order) == 0: + _LOGGER.error('Error loading %s dependency: %s', + comp_name, dependency) + return OrderedSet() - load_order.update(dep_load_order) + load_order.update(dep_load_order) load_order.add(comp_name) loading.remove(comp_name) diff --git a/homeassistant/util/__init__.py b/homeassistant/util/__init__.py index 90476088d25..a75d9837de6 100644 --- a/homeassistant/util/__init__.py +++ b/homeassistant/util/__init__.py @@ -94,13 +94,12 @@ def get_local_ip(): # Use Google Public DNS server to determine own IP sock.connect(('8.8.8.8', 80)) - ip_addr = sock.getsockname()[0] - sock.close() - - return ip_addr + return sock.getsockname()[0] except socket.error: return socket.gethostbyname(socket.gethostname()) + finally: + sock.close() # Taken from http://stackoverflow.com/a/23728630 From 382c1de981266019665ad5b695177cbff96396ff Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 3 Aug 2015 17:08:13 +0200 Subject: [PATCH 032/224] Built-in components no longer use deprecated methods --- homeassistant/components/automation/time.py | 6 +++--- homeassistant/components/device_sun_light_trigger.py | 7 ++++--- homeassistant/components/device_tracker/__init__.py | 3 ++- homeassistant/components/scheduler/time.py | 3 ++- homeassistant/components/script.py | 5 +++-- homeassistant/components/sun.py | 8 +++++--- homeassistant/helpers/entity_component.py | 5 +++-- 7 files changed, 22 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/automation/time.py b/homeassistant/components/automation/time.py index 7e38960534d..77bd40a7a41 100644 --- a/homeassistant/components/automation/time.py +++ b/homeassistant/components/automation/time.py @@ -5,6 +5,7 @@ homeassistant.components.automation.time Offers time listening automation rules. """ from homeassistant.util import convert +from homeassistant.helpers.event import track_time_change CONF_HOURS = "time_hours" CONF_MINUTES = "time_minutes" @@ -21,8 +22,7 @@ def register(hass, config, action): """ Listens for time changes and calls action. """ action() - hass.track_time_change( - time_automation_listener, - hour=hours, minute=minutes, second=seconds) + track_time_change(hass, time_automation_listener, + hour=hours, minute=minutes, second=seconds) return True diff --git a/homeassistant/components/device_sun_light_trigger.py b/homeassistant/components/device_sun_light_trigger.py index c53fff0e4f3..7c3be8894c4 100644 --- a/homeassistant/components/device_sun_light_trigger.py +++ b/homeassistant/components/device_sun_light_trigger.py @@ -8,6 +8,7 @@ the state of the sun and devices. import logging from datetime import timedelta +from homeassistant.helpers.event import track_point_in_time import homeassistant.util.dt as dt_util from homeassistant.const import STATE_HOME, STATE_NOT_HOME from . import light, sun, device_tracker, group @@ -91,9 +92,9 @@ def setup(hass, config): if start_point: for index, light_id in enumerate(light_ids): - hass.track_point_in_time(turn_on(light_id), - (start_point + - index * LIGHT_TRANSITION_TIME)) + track_point_in_time( + hass, turn_on(light_id), + (start_point + index * LIGHT_TRANSITION_TIME)) # Track every time sun rises so we can schedule a time-based # pre-sun set event diff --git a/homeassistant/components/device_tracker/__init__.py b/homeassistant/components/device_tracker/__init__.py index 611136aac5b..452480b12c9 100644 --- a/homeassistant/components/device_tracker/__init__.py +++ b/homeassistant/components/device_tracker/__init__.py @@ -15,6 +15,7 @@ from homeassistant.helpers import validate_config import homeassistant.util as util import homeassistant.util.dt as dt_util +from homeassistant.helpers.event import track_utc_time_change from homeassistant.const import ( STATE_HOME, STATE_NOT_HOME, ATTR_ENTITY_PICTURE, ATTR_FRIENDLY_NAME, CONF_PLATFORM, DEVICE_DEFAULT_NAME) @@ -134,7 +135,7 @@ class DeviceTracker(object): seconds = range(0, 60, seconds) _LOGGER.info("Device tracker interval second=%s", seconds) - hass.track_utc_time_change(update_device_state, second=seconds) + track_utc_time_change(hass, update_device_state, second=seconds) hass.services.register(DOMAIN, SERVICE_DEVICE_TRACKER_RELOAD, diff --git a/homeassistant/components/scheduler/time.py b/homeassistant/components/scheduler/time.py index 793f33d0502..9fec19fbe57 100644 --- a/homeassistant/components/scheduler/time.py +++ b/homeassistant/components/scheduler/time.py @@ -17,6 +17,7 @@ from datetime import timedelta import logging import homeassistant.util.dt as dt_util +from homeassistant.helpers.event import track_point_in_time from homeassistant.components.scheduler import ServiceEventListener _LOGGER = logging.getLogger(__name__) @@ -62,7 +63,7 @@ class TimeEventListener(ServiceEventListener): """ Call the execute method """ self.execute(hass) - hass.track_point_in_time(execute, next_time) + track_point_in_time(hass, execute, next_time) _LOGGER.info( 'TimeEventListener scheduled for %s, will call service %s.%s', diff --git a/homeassistant/components/script.py b/homeassistant/components/script.py index 97e12c47a46..788eb8af96d 100644 --- a/homeassistant/components/script.py +++ b/homeassistant/components/script.py @@ -10,6 +10,7 @@ from datetime import timedelta import homeassistant.util.dt as date_util import threading +from homeassistant.helpers.event import track_point_in_time from homeassistant.util import split_entity_id from homeassistant.const import ( STATE_ON, STATE_OFF, SERVICE_TURN_ON, SERVICE_TURN_OFF, EVENT_TIME_CHANGED) @@ -111,8 +112,8 @@ class Script(object): elif CONF_DELAY in action: delay = timedelta(**action[CONF_DELAY]) point_in_time = date_util.now() + delay - self.listener = self.hass.track_point_in_time( - self, point_in_time) + self.listener = track_point_in_time( + self.hass, self, point_in_time) return False return True diff --git a/homeassistant/components/sun.py b/homeassistant/components/sun.py index af4a93825ac..507c4a2b63b 100644 --- a/homeassistant/components/sun.py +++ b/homeassistant/components/sun.py @@ -25,6 +25,8 @@ import urllib import homeassistant.util as util import homeassistant.util.dt as dt_util +from homeassistant.helpers.event import ( + track_point_in_utc_time, track_point_in_time) from homeassistant.helpers.entity import Entity from homeassistant.components.scheduler import ServiceEventListener @@ -209,8 +211,8 @@ class Sun(Entity): self.update_ha_state() # Schedule next update at next_change+1 second so sun state has changed - self.hass.track_point_in_utc_time( - self.point_in_time_listener, + track_point_in_utc_time( + self.hass, self.point_in_time_listener, self.next_change + timedelta(seconds=1)) @@ -272,7 +274,7 @@ class SunEventListener(ServiceEventListener): """ Call the execute method. """ self.execute(hass) - hass.track_point_in_time(execute, next_time) + track_point_in_time(hass, execute, next_time) return next_time diff --git a/homeassistant/helpers/entity_component.py b/homeassistant/helpers/entity_component.py index 0a120afa2b3..80084178fe0 100644 --- a/homeassistant/helpers/entity_component.py +++ b/homeassistant/helpers/entity_component.py @@ -7,6 +7,7 @@ Provides helpers for components that manage entities. from homeassistant.bootstrap import prepare_setup_platform from homeassistant.helpers import ( generate_entity_id, config_per_platform, extract_entity_ids) +from homeassistant.helpers.event import track_utc_time_change from homeassistant.components import group, discovery from homeassistant.const import ATTR_ENTITY_ID @@ -115,8 +116,8 @@ class EntityComponent(object): self.is_polling = True - self.hass.track_time_change( - self._update_entity_states, + track_utc_time_change( + self.hass, self._update_entity_states, second=range(0, 60, self.scan_interval)) def _setup_platform(self, platform_type, platform_config, From 4096a672519822bd53dce1eb0b0f51882bee340d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 3 Aug 2015 17:42:28 +0200 Subject: [PATCH 033/224] Built-in component cleanup --- homeassistant/components/light/__init__.py | 131 +++++++++--------- .../components/media_player/__init__.py | 46 +++--- homeassistant/components/switch/__init__.py | 4 - 3 files changed, 85 insertions(+), 96 deletions(-) diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index 58c3c3cd8f2..fe1991c7ed1 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -166,26 +166,25 @@ def setup(hass, config): profiles = {} for profile_path in profile_paths: + if not os.path.isfile(profile_path): + continue + with open(profile_path) as inp: + reader = csv.reader(inp) - if os.path.isfile(profile_path): - with open(profile_path) as inp: - reader = csv.reader(inp) + # Skip the header + next(reader, None) - # Skip the header - next(reader, None) + try: + for profile_id, color_x, color_y, brightness in reader: + profiles[profile_id] = (float(color_x), float(color_y), + int(brightness)) + except ValueError: + # ValueError if not 4 values per row + # ValueError if convert to float/int failed + _LOGGER.error( + "Error parsing light profiles from %s", profile_path) - try: - for profile_id, color_x, color_y, brightness in reader: - profiles[profile_id] = (float(color_x), float(color_y), - int(brightness)) - - except ValueError: - # ValueError if not 4 values per row - # ValueError if convert to float/int failed - _LOGGER.error( - "Error parsing light profiles from %s", profile_path) - - return False + return False def handle_light_service(service): """ Hande a turn light on or off service call. """ @@ -206,66 +205,70 @@ def setup(hass, config): for light in target_lights: light.turn_off(**params) - else: - # Processing extra data for turn light on request + if light.should_poll: + for light in target_lights: + light.update_ha_state(True) + return - # We process the profile first so that we get the desired - # behavior that extra service data attributes overwrite - # profile values - profile = profiles.get(dat.get(ATTR_PROFILE)) + # Processing extra data for turn light on request - if profile: - *params[ATTR_XY_COLOR], params[ATTR_BRIGHTNESS] = profile + # We process the profile first so that we get the desired + # behavior that extra service data attributes overwrite + # profile values + profile = profiles.get(dat.get(ATTR_PROFILE)) - if ATTR_BRIGHTNESS in dat: - # We pass in the old value as the default parameter if parsing - # of the new one goes wrong. - params[ATTR_BRIGHTNESS] = util.convert( - dat.get(ATTR_BRIGHTNESS), int, params.get(ATTR_BRIGHTNESS)) + if profile: + *params[ATTR_XY_COLOR], params[ATTR_BRIGHTNESS] = profile - if ATTR_XY_COLOR in dat: - try: - # xy_color should be a list containing 2 floats - xycolor = dat.get(ATTR_XY_COLOR) + if ATTR_BRIGHTNESS in dat: + # We pass in the old value as the default parameter if parsing + # of the new one goes wrong. + params[ATTR_BRIGHTNESS] = util.convert( + dat.get(ATTR_BRIGHTNESS), int, params.get(ATTR_BRIGHTNESS)) - # Without this check, a xycolor with value '99' would work - if not isinstance(xycolor, str): - params[ATTR_XY_COLOR] = [float(val) for val in xycolor] + if ATTR_XY_COLOR in dat: + try: + # xy_color should be a list containing 2 floats + xycolor = dat.get(ATTR_XY_COLOR) - except (TypeError, ValueError): - # TypeError if xy_color is not iterable - # ValueError if value could not be converted to float - pass + # Without this check, a xycolor with value '99' would work + if not isinstance(xycolor, str): + params[ATTR_XY_COLOR] = [float(val) for val in xycolor] - if ATTR_RGB_COLOR in dat: - try: - # rgb_color should be a list containing 3 ints - rgb_color = dat.get(ATTR_RGB_COLOR) + except (TypeError, ValueError): + # TypeError if xy_color is not iterable + # ValueError if value could not be converted to float + pass - if len(rgb_color) == 3: - params[ATTR_XY_COLOR] = \ - color_util.color_RGB_to_xy(int(rgb_color[0]), - int(rgb_color[1]), - int(rgb_color[2])) + if ATTR_RGB_COLOR in dat: + try: + # rgb_color should be a list containing 3 ints + rgb_color = dat.get(ATTR_RGB_COLOR) - except (TypeError, ValueError): - # TypeError if rgb_color is not iterable - # ValueError if not all values can be converted to int - pass + if len(rgb_color) == 3: + params[ATTR_XY_COLOR] = \ + color_util.color_RGB_to_xy(int(rgb_color[0]), + int(rgb_color[1]), + int(rgb_color[2])) - if ATTR_FLASH in dat: - if dat[ATTR_FLASH] == FLASH_SHORT: - params[ATTR_FLASH] = FLASH_SHORT + except (TypeError, ValueError): + # TypeError if rgb_color is not iterable + # ValueError if not all values can be converted to int + pass - elif dat[ATTR_FLASH] == FLASH_LONG: - params[ATTR_FLASH] = FLASH_LONG + if ATTR_FLASH in dat: + if dat[ATTR_FLASH] == FLASH_SHORT: + params[ATTR_FLASH] = FLASH_SHORT - if ATTR_EFFECT in dat: - if dat[ATTR_EFFECT] == EFFECT_COLORLOOP: - params[ATTR_EFFECT] = EFFECT_COLORLOOP + elif dat[ATTR_FLASH] == FLASH_LONG: + params[ATTR_FLASH] = FLASH_LONG - for light in target_lights: - light.turn_on(**params) + if ATTR_EFFECT in dat: + if dat[ATTR_EFFECT] == EFFECT_COLORLOOP: + params[ATTR_EFFECT] = EFFECT_COLORLOOP + + for light in target_lights: + light.turn_on(**params) for light in target_lights: if light.should_poll: diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py index 0cb4608ba1b..d4b55807ab2 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -98,9 +98,7 @@ ATTR_TO_PROPERTY = [ def is_on(hass, entity_id=None): """ Returns true if specified media player entity_id is on. Will check all media player if no entity_id specified. """ - entity_ids = [entity_id] if entity_id else hass.states.entity_ids(DOMAIN) - return any(not hass.states.is_state(entity_id, STATE_OFF) for entity_id in entity_ids) @@ -108,28 +106,24 @@ def is_on(hass, entity_id=None): def turn_on(hass, entity_id=None): """ Will turn on specified media player or all. """ data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} - hass.services.call(DOMAIN, SERVICE_TURN_ON, data) def turn_off(hass, entity_id=None): """ Will turn off specified media player or all. """ data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} - hass.services.call(DOMAIN, SERVICE_TURN_OFF, data) def volume_up(hass, entity_id=None): """ Send the media player the command for volume up. """ data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} - hass.services.call(DOMAIN, SERVICE_VOLUME_UP, data) def volume_down(hass, entity_id=None): """ Send the media player the command for volume down. """ data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} - hass.services.call(DOMAIN, SERVICE_VOLUME_DOWN, data) @@ -156,35 +150,30 @@ def set_volume_level(hass, volume, entity_id=None): def media_play_pause(hass, entity_id=None): """ Send the media player the command for play/pause. """ data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} - hass.services.call(DOMAIN, SERVICE_MEDIA_PLAY_PAUSE, data) def media_play(hass, entity_id=None): """ Send the media player the command for play/pause. """ data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} - hass.services.call(DOMAIN, SERVICE_MEDIA_PLAY, data) def media_pause(hass, entity_id=None): """ Send the media player the command for play/pause. """ data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} - hass.services.call(DOMAIN, SERVICE_MEDIA_PAUSE, data) def media_next_track(hass, entity_id=None): """ Send the media player the command for next track. """ data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} - hass.services.call(DOMAIN, SERVICE_MEDIA_NEXT_TRACK, data) def media_previous_track(hass, entity_id=None): """ Send the media player the command for prev track. """ data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} - hass.services.call(DOMAIN, SERVICE_MEDIA_PREVIOUS_TRACK, data) @@ -262,29 +251,30 @@ def setup(hass, config): hass.services.register(DOMAIN, SERVICE_MEDIA_SEEK, media_seek_service) - def play_youtube_video_service(service, media_id): + def play_youtube_video_service(service, media_id=None): """ Plays specified media_id on the media player. """ - target_players = component.extract_from_service(service) + if media_id is None: + service.data.get('video') - if media_id: - for player in target_players: - player.play_youtube(media_id) + if media_id is None: + return - if player.should_poll: - player.update_ha_state(True) + for player in component.extract_from_service(service): + player.play_youtube(media_id) - hass.services.register(DOMAIN, "start_fireplace", - lambda service: - play_youtube_video_service(service, "eyU3bRy2x44")) + if player.should_poll: + player.update_ha_state(True) - hass.services.register(DOMAIN, "start_epic_sax", - lambda service: - play_youtube_video_service(service, "kxopViU98Xo")) + hass.services.register( + DOMAIN, "start_fireplace", + lambda service: play_youtube_video_service(service, "eyU3bRy2x44")) - hass.services.register(DOMAIN, SERVICE_YOUTUBE_VIDEO, - lambda service: - play_youtube_video_service( - service, service.data.get('video'))) + hass.services.register( + DOMAIN, "start_epic_sax", + lambda service: play_youtube_video_service(service, "kxopViU98Xo")) + + hass.services.register( + DOMAIN, SERVICE_YOUTUBE_VIDEO, play_youtube_video_service) return True diff --git a/homeassistant/components/switch/__init__.py b/homeassistant/components/switch/__init__.py index 95457b66f5f..e2772aed8fd 100644 --- a/homeassistant/components/switch/__init__.py +++ b/homeassistant/components/switch/__init__.py @@ -45,21 +45,18 @@ _LOGGER = logging.getLogger(__name__) def is_on(hass, entity_id=None): """ Returns if the switch is on based on the statemachine. """ entity_id = entity_id or ENTITY_ID_ALL_SWITCHES - return hass.states.is_state(entity_id, STATE_ON) def turn_on(hass, entity_id=None): """ Turns all or specified switch on. """ data = {ATTR_ENTITY_ID: entity_id} if entity_id else None - hass.services.call(DOMAIN, SERVICE_TURN_ON, data) def turn_off(hass, entity_id=None): """ Turns all or specified switch off. """ data = {ATTR_ENTITY_ID: entity_id} if entity_id else None - hass.services.call(DOMAIN, SERVICE_TURN_OFF, data) @@ -84,7 +81,6 @@ def setup(hass, config): switch.update_ha_state(True) hass.services.register(DOMAIN, SERVICE_TURN_OFF, handle_switch_service) - hass.services.register(DOMAIN, SERVICE_TURN_ON, handle_switch_service) return True From 76f63ee262c3a4c55d86f3b9397fb5199792655d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 3 Aug 2015 17:57:12 +0200 Subject: [PATCH 034/224] Clean up test code --- tests/common.py | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/tests/common.py b/tests/common.py index 65fd0659108..bad0723530b 100644 --- a/tests/common.py +++ b/tests/common.py @@ -52,30 +52,28 @@ def mock_service(hass, domain, service): return calls +def fire_time_changed(hass, time): + hass.bus.fire(EVENT_TIME_CHANGED, {'now': time}) + + def trigger_device_tracker_scan(hass): """ Triggers the device tracker to scan. """ - hass.bus.fire( - EVENT_TIME_CHANGED, - {'now': - dt_util.utcnow().replace(second=0) + timedelta(hours=1)}) + fire_time_changed( + hass, dt_util.utcnow().replace(second=0) + timedelta(hours=1)) def ensure_sun_risen(hass): """ Trigger sun to rise if below horizon. """ - if not sun.is_on(hass): - hass.bus.fire( - EVENT_TIME_CHANGED, - {'now': - sun.next_rising_utc(hass) + timedelta(seconds=10)}) + if sun.is_on(hass): + return + fire_time_changed(hass, sun.next_rising_utc(hass) + timedelta(seconds=10)) def ensure_sun_set(hass): """ Trigger sun to set if above horizon. """ - if sun.is_on(hass): - hass.bus.fire( - EVENT_TIME_CHANGED, - {'now': - sun.next_setting_utc(hass) + timedelta(seconds=10)}) + if not sun.is_on(hass): + return + fire_time_changed(hass, sun.next_setting_utc(hass) + timedelta(seconds=10)) def mock_state_change_event(hass, new_state, old_state=None): From bed30a5307f3da8b5c13455df4623f7d53cf51ff Mon Sep 17 00:00:00 2001 From: Per Sandstrom Date: Tue, 4 Aug 2015 17:22:56 +0200 Subject: [PATCH 035/224] added support for logitech squeezebox --- .../components/media_player/squeezebox.py | 273 ++++++++++++++++++ 1 file changed, 273 insertions(+) create mode 100644 homeassistant/components/media_player/squeezebox.py diff --git a/homeassistant/components/media_player/squeezebox.py b/homeassistant/components/media_player/squeezebox.py new file mode 100644 index 00000000000..4d88d226f9d --- /dev/null +++ b/homeassistant/components/media_player/squeezebox.py @@ -0,0 +1,273 @@ +""" +homeassistant.components.media_player.squeezebox +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Provides an interface to the Logitech SqueezeBox API + +Configuration: + +To use SqueezeBox add something like this to your configuration: + +media_player: + platform: squeezebox + name: SqueezeBox + server: 192.168.1.21 + player: Player1 + port: 9090 + user: user + password: password + +Variables: + +name +*Optional +The name of the device + +server +*Required +The address of the Logitech Media Server + +player +*Required +The unique name of the player + +port +*Optional +Telnet port to Logitech Media Server, default 9090 + +user +*Optional +User, if password protection is enabled + +password +*Optional +Password, if password protection is enabled +""" + +import logging +import telnetlib +import urllib.parse + +from homeassistant.components.media_player import ( + MediaPlayerDevice, SUPPORT_PAUSE, SUPPORT_SEEK, SUPPORT_VOLUME_SET, + SUPPORT_VOLUME_MUTE, SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK, + MEDIA_TYPE_MUSIC) +from homeassistant.const import ( + STATE_IDLE, STATE_PLAYING, STATE_PAUSED, STATE_OFF, STATE_UNKNOWN) + +_LOGGER = logging.getLogger(__name__) + +SUPPORT_SQUEEZEBOX = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE |\ + SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_SEEK + + +# pylint: disable=unused-argument +def setup_platform(hass, config, add_devices, discovery_info=None): + """ Sets up the squeezebox platform. """ + add_devices([ + SqueezeBoxDevice( + config.get('name', 'SqueezeBox'), + config.get('server'), + config.get('player'), + config.get('port', '9090'), + config.get('user', None), + config.get('password', None) + )]) + + +# pylint: disable=too-many-instance-attributes +# pylint: disable=too-many-public-methods +class SqueezeBoxDevice(MediaPlayerDevice): + """ Represents a SqueezeBox device. """ + + def __init__(self, name, server, player, port, user, password): + super(SqueezeBoxDevice, self).__init__() + self._name = name + self._server = server + self._player = player + self._port = port + self._user = user + self._password = password + self._status = {} + self.update() + self._http_port = self._query('pref', 'httpport', '?') + + @property + def name(self): + """ Returns the name of the device. """ + return self._name + + @property + def state(self): + """ Returns the state of the device. """ + if ('power' in self._status and self._status['power'] == '0'): + return STATE_OFF + if('mode' in self._status): + if self._status['mode'] == 'pause': + return STATE_PAUSED + if self._status['mode'] == 'play': + return STATE_PLAYING + if self._status['mode'] == 'stop': + return STATE_IDLE + return STATE_UNKNOWN + + def update(self): + if(self._user and self._password): + self._query('login', self._user, self._password) + self._get_status() + + def _query(self, *parameters): + """ Send request and await response from server """ + telnet = telnetlib.Telnet(self._server, self._port) + message = '{}\n'.format(' '.join(parameters)) + telnet.write(message.encode('UTF-8')) + response = telnet.read_until(b'\n', timeout=3)\ + .decode('UTF-8')\ + .split(' ')[-1]\ + .strip() + telnet.write(b'exit\n') + return urllib.parse.unquote(response) + + def _get_status(self): + """ request status and parse result """ + # (title) : Song title + # Requested Information + # a (artist): Artist name 'artist' + # d (duration): Song duration in seconds 'duration' + # K (artwork_url): URL to remote artwork + tags = 'adK' + new_status = {} + telnet = telnetlib.Telnet(self._server, self._port) + telnet.write('{player} status - 1 tags:{tags}\n'.format( + player=self._player, + tags=tags + ).encode('UTF-8')) + response = telnet.read_until(b'\n', timeout=3)\ + .decode('UTF-8')\ + .split(' ') + telnet.write(b'exit\n') + for item in response: + parts = urllib.parse.unquote(item).partition(':') + new_status[parts[0]] = parts[2] + self._status = new_status + + @property + def volume_level(self): + """ Volume level of the media player (0..1). """ + if('mixer volume' in self._status): + return int(self._status['mixer volume']) / 100.0 + + @property + def is_volume_muted(self): + if('mixer volume' in self._status): + return int(self._status['mixer volume']) < 0 + + @property + def media_content_id(self): + """ Content ID of current playing media. """ + if('current_title' in self._status): + return self._status['current_title'] + + @property + def media_content_type(self): + """ Content type of current playing media. """ + return MEDIA_TYPE_MUSIC + + @property + def media_duration(self): + """ Duration of current playing media in seconds. """ + if('duration' in self._status): + return int(float(self._status['duration'])) + + @property + def media_image_url(self): + """ Image url of current playing media. """ + if('artwork_url' in self._status): + return self._status['artwork_url'] + return 'http://{server}:{port}/music/current/cover.jpg?player={player}'\ + .format( + server=self._server, + port=self._http_port, + player=self._player) + + @property + def media_title(self): + """ Title of current playing media. """ + if('artist' in self._status and 'title' in self._status): + return '{artist} - {title}'.format( + artist=self._status['artist'], + title=self._status['title'] + ) + if('current_title' in self._status): + return self._status['current_title'] + + @property + def supported_media_commands(self): + """ Flags of media commands that are supported. """ + return SUPPORT_SQUEEZEBOX + + def turn_off(self): + """ turn_off media player. """ + self._query(self._player, 'power', '0') + self.update_ha_state() + + def volume_up(self): + """ volume_up media player. """ + self._query(self._player, 'mixer', 'volume', '+5') + self.update_ha_state() + + def volume_down(self): + """ volume_down media player. """ + self._query(self._player, 'mixer', 'volume', '-5') + self.update_ha_state() + + def set_volume_level(self, volume): + """ set volume level, range 0..1. """ + volume_percent = str(int(volume*100)) + self._query(self._player, 'mixer', 'volume', volume_percent) + self.update_ha_state() + + def mute_volume(self, mute): + """ mute (true) or unmute (false) media player. """ + mute_numeric = '1' if mute else '0' + self._query(self._player, 'mixer', 'muting', mute_numeric) + self.update_ha_state() + + def media_play_pause(self): + """ media_play_pause media player. """ + self._query(self._player, 'pause') + self.update_ha_state() + + def media_play(self): + """ media_play media player. """ + self._query(self._player, 'play') + self.update_ha_state() + + def media_pause(self): + """ media_pause media player. """ + self._query(self._player, 'pause', '0') + self.update_ha_state() + + def media_next_track(self): + """ Send next track command. """ + self._query(self._player, 'playlist', 'index', '+1') + self.update_ha_state() + + def media_previous_track(self): + """ Send next track command. """ + self._query(self._player, 'playlist', 'index', '-1') + self.update_ha_state() + + def media_seek(self, position): + """ Send seek command. """ + self._query(self._player, 'time', position) + self.update_ha_state() + + def turn_on(self): + """ turn the media player on. """ + self._query(self._player, 'power', '1') + self.update_ha_state() + + def play_youtube(self, media_id): + """ Plays a YouTube media. """ + raise NotImplementedError() From e47ac9658776c8a6d40917741e38e3d270890ef7 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 4 Aug 2015 18:13:35 +0200 Subject: [PATCH 036/224] Remove more deprecated method calls --- homeassistant/components/automation/state.py | 5 +++-- .../components/device_sun_light_trigger.py | 14 +++++++------- homeassistant/components/group.py | 5 +++-- homeassistant/components/scene.py | 5 +++-- homeassistant/components/simple_alarm.py | 9 +++++---- .../components/thermostat/heat_control.py | 13 +++++++------ 6 files changed, 28 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/automation/state.py b/homeassistant/components/automation/state.py index c8adfe95bbe..ba96debf9ac 100644 --- a/homeassistant/components/automation/state.py +++ b/homeassistant/components/automation/state.py @@ -6,6 +6,7 @@ Offers state listening automation rules. """ import logging +from homeassistant.helpers.event import track_state_change from homeassistant.const import MATCH_ALL @@ -30,7 +31,7 @@ def register(hass, config, action): """ Listens for state changes and calls action. """ action() - hass.states.track_change( - entity_id, state_automation_listener, from_state, to_state) + track_state_change( + hass, entity_id, state_automation_listener, from_state, to_state) return True diff --git a/homeassistant/components/device_sun_light_trigger.py b/homeassistant/components/device_sun_light_trigger.py index 7c3be8894c4..67da9e26a82 100644 --- a/homeassistant/components/device_sun_light_trigger.py +++ b/homeassistant/components/device_sun_light_trigger.py @@ -8,7 +8,7 @@ the state of the sun and devices. import logging from datetime import timedelta -from homeassistant.helpers.event import track_point_in_time +from homeassistant.helpers.event import track_point_in_time, track_state_change import homeassistant.util.dt as dt_util from homeassistant.const import STATE_HOME, STATE_NOT_HOME from . import light, sun, device_tracker, group @@ -98,8 +98,8 @@ def setup(hass, config): # Track every time sun rises so we can schedule a time-based # pre-sun set event - hass.states.track_change(sun.ENTITY_ID, schedule_light_on_sun_rise, - sun.STATE_BELOW_HORIZON, sun.STATE_ABOVE_HORIZON) + track_state_change(hass, sun.ENTITY_ID, schedule_light_on_sun_rise, + sun.STATE_BELOW_HORIZON, sun.STATE_ABOVE_HORIZON) # If the sun is already above horizon # schedule the time-based pre-sun set event @@ -158,13 +158,13 @@ def setup(hass, config): light.turn_off(hass, light_ids) # Track home coming of each device - hass.states.track_change( - device_entity_ids, check_light_on_dev_state_change, + track_state_change( + hass, device_entity_ids, check_light_on_dev_state_change, STATE_NOT_HOME, STATE_HOME) # Track when all devices are gone to shut down lights - hass.states.track_change( - device_group, check_light_on_dev_state_change, + track_state_change( + hass, device_group, check_light_on_dev_state_change, STATE_HOME, STATE_NOT_HOME) return True diff --git a/homeassistant/components/group.py b/homeassistant/components/group.py index 20931f2b363..ac5e8cd4116 100644 --- a/homeassistant/components/group.py +++ b/homeassistant/components/group.py @@ -7,6 +7,7 @@ Provides functionality to group devices that can be turned on or off. import homeassistant as ha from homeassistant.helpers import generate_entity_id +from homeassistant.helpers.event import track_state_change from homeassistant.helpers.entity import Entity import homeassistant.util as util from homeassistant.const import ( @@ -162,8 +163,8 @@ class Group(Entity): def start(self): """ Starts the tracking. """ - self.hass.states.track_change( - self.tracking, self._state_changed_listener) + track_state_change( + self.hass, self.tracking, self._state_changed_listener) def stop(self): """ Unregisters the group from Home Assistant. """ diff --git a/homeassistant/components/scene.py b/homeassistant/components/scene.py index 979b3281300..a748e17ec5d 100644 --- a/homeassistant/components/scene.py +++ b/homeassistant/components/scene.py @@ -19,6 +19,7 @@ import logging from collections import namedtuple from homeassistant import State +from homeassistant.helpers.event import track_state_change from homeassistant.helpers.entity import ToggleEntity from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.state import reproduce_state @@ -104,8 +105,8 @@ class Scene(ToggleEntity): self.prev_states = None self.ignore_updates = False - self.hass.states.track_change( - self.entity_ids, self.entity_state_changed) + track_state_change( + self.hass, self.entity_ids, self.entity_state_changed) self.update() diff --git a/homeassistant/components/simple_alarm.py b/homeassistant/components/simple_alarm.py index 4f2dbd768d5..46cb52fa950 100644 --- a/homeassistant/components/simple_alarm.py +++ b/homeassistant/components/simple_alarm.py @@ -9,6 +9,7 @@ Provides a simple alarm feature: import logging import homeassistant.loader as loader +from homeassistant.helpers.event import track_state_change from homeassistant.const import STATE_ON, STATE_OFF, STATE_HOME, STATE_NOT_HOME DOMAIN = "simple_alarm" @@ -83,8 +84,8 @@ def setup(hass, config): if not device_tracker.is_on(hass): unknown_alarm() - hass.states.track_change( - light.ENTITY_ID_ALL_LIGHTS, + track_state_change( + hass, light.ENTITY_ID_ALL_LIGHTS, unknown_alarm_if_lights_on, STATE_OFF, STATE_ON) def ring_known_alarm(entity_id, old_state, new_state): @@ -93,8 +94,8 @@ def setup(hass, config): known_alarm() # Track home coming of each device - hass.states.track_change( - hass.states.entity_ids(device_tracker.DOMAIN), + track_state_change( + hass, hass.states.entity_ids(device_tracker.DOMAIN), ring_known_alarm, STATE_NOT_HOME, STATE_HOME) return True diff --git a/homeassistant/components/thermostat/heat_control.py b/homeassistant/components/thermostat/heat_control.py index d21245dae3a..6b10a1b46f3 100644 --- a/homeassistant/components/thermostat/heat_control.py +++ b/homeassistant/components/thermostat/heat_control.py @@ -63,6 +63,7 @@ import datetime import homeassistant.components as core from homeassistant.components.thermostat import ThermostatDevice +from homeassistant.helpers.event import track_state_change from homeassistant.const import TEMP_CELCIUS, STATE_ON, STATE_OFF TOL_TEMP = 0.3 @@ -105,12 +106,12 @@ class HeatControl(ThermostatDevice): self._away = False self._heater_manual_changed = True - hass.states.track_change(self.heater_entity_id, - self._heater_turned_on, - STATE_OFF, STATE_ON) - hass.states.track_change(self.heater_entity_id, - self._heater_turned_off, - STATE_ON, STATE_OFF) + track_state_change(hass, self.heater_entity_id, + self._heater_turned_on, + STATE_OFF, STATE_ON) + track_state_change(hass, self.heater_entity_id, + self._heater_turned_off, + STATE_ON, STATE_OFF) @property def name(self): From 14023a15e6f8a7f3b8374cea16fb6911cb080df5 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 4 Aug 2015 18:13:55 +0200 Subject: [PATCH 037/224] Minor code cleanup --- homeassistant/__init__.py | 24 ++++++++-------------- homeassistant/components/light/__init__.py | 4 ++-- homeassistant/helpers/event.py | 2 ++ 3 files changed, 12 insertions(+), 18 deletions(-) diff --git a/homeassistant/__init__.py b/homeassistant/__init__.py index bfbb944b23f..f5129fd1c95 100644 --- a/homeassistant/__init__.py +++ b/homeassistant/__init__.py @@ -598,23 +598,15 @@ class ServiceRegistry(object): if call.data[ATTR_SERVICE_CALL_ID] == call_id: executed_event.set() - self._bus.remove_listener( - EVENT_SERVICE_EXECUTED, service_executed) - self._bus.listen(EVENT_SERVICE_EXECUTED, service_executed) self._bus.fire(EVENT_CALL_SERVICE, event_data) if blocking: - # wait will return False if event not set after our limit has - # passed. If not set, clean up the listener - if not executed_event.wait(SERVICE_CALL_LIMIT): - self._bus.remove_listener( - EVENT_SERVICE_EXECUTED, service_executed) - - return False - - return True + success = executed_event.wait(SERVICE_CALL_LIMIT) + self._bus.remove_listener( + EVENT_SERVICE_EXECUTED, service_executed) + return success def _event_to_service_call(self, event): """ Calls a service from an event. """ @@ -675,8 +667,8 @@ class Config(object): def temperature(self, value, unit): """ Converts temperature to user preferred unit if set. """ - if not (unit and self.temperature_unit and - unit != self.temperature_unit): + if not (unit in (TEMP_CELCIUS, TEMP_FAHRENHEIT) and + self.temperature_unit and unit != self.temperature_unit): return value, unit try: @@ -783,7 +775,7 @@ def create_timer(hass, interval=TIMER_INTERVAL): hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_timer) -def create_worker_pool(): +def create_worker_pool(worker_count=MIN_WORKER_THREAD): """ Creates a worker pool to be used. """ def job_handler(job): @@ -807,4 +799,4 @@ def create_worker_pool(): _LOGGER.warning("WorkerPool:Current job from %s: %s", date_util.datetime_to_local_str(start), job) - return util.ThreadPool(job_handler, MIN_WORKER_THREAD, busy_callback) + return util.ThreadPool(job_handler, worker_count, busy_callback) diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index fe1991c7ed1..3d1408b49bf 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -205,8 +205,8 @@ def setup(hass, config): for light in target_lights: light.turn_off(**params) - if light.should_poll: - for light in target_lights: + for light in target_lights: + if light.should_poll: light.update_ha_state(True) return diff --git a/homeassistant/helpers/event.py b/homeassistant/helpers/event.py index 194cacdc19d..60377fd1f5d 100644 --- a/homeassistant/helpers/event.py +++ b/homeassistant/helpers/event.py @@ -68,6 +68,8 @@ def track_point_in_utc_time(hass, action, point_in_time): """ Adds a listener that fires once after a specific point in UTC time. """ + # Ensure point_in_time is UTC + point_in_time = dt_util.as_utc(point_in_time) @ft.wraps(action) def point_in_time_listener(event): From df3ee6005a0dffd7c7e71455bb907ce7a86f44f1 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 4 Aug 2015 18:15:22 +0200 Subject: [PATCH 038/224] Nicer test imports unittest changes import path so old style worked but is confusing --- tests/components/test_demo.py | 2 +- tests/components/test_device_sun_light_trigger.py | 2 +- tests/components/test_device_tracker.py | 2 +- tests/components/test_history.py | 2 +- tests/components/test_light.py | 2 +- tests/components/test_logbook.py | 2 +- tests/components/test_media_player.py | 2 +- tests/components/test_recorder.py | 2 +- tests/components/test_switch.py | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/components/test_demo.py b/tests/components/test_demo.py index 628c7e84bfe..9e697fb0c74 100644 --- a/tests/components/test_demo.py +++ b/tests/components/test_demo.py @@ -9,7 +9,7 @@ import unittest import homeassistant as ha import homeassistant.components.demo as demo -from common import mock_http_component +from tests.common import mock_http_component class TestDemo(unittest.TestCase): diff --git a/tests/components/test_device_sun_light_trigger.py b/tests/components/test_device_sun_light_trigger.py index 16b89468201..05b2bf11bea 100644 --- a/tests/components/test_device_sun_light_trigger.py +++ b/tests/components/test_device_sun_light_trigger.py @@ -14,7 +14,7 @@ from homeassistant.components import ( device_tracker, light, sun, device_sun_light_trigger) -from common import ( +from tests.common import ( get_test_home_assistant, ensure_sun_risen, ensure_sun_set, trigger_device_tracker_scan) diff --git a/tests/components/test_device_tracker.py b/tests/components/test_device_tracker.py index 1d595df3f5a..bbc647987c9 100644 --- a/tests/components/test_device_tracker.py +++ b/tests/components/test_device_tracker.py @@ -18,7 +18,7 @@ from homeassistant.const import ( DEVICE_DEFAULT_NAME) import homeassistant.components.device_tracker as device_tracker -from common import get_test_home_assistant +from tests.common import get_test_home_assistant def setUpModule(): # pylint: disable=invalid-name diff --git a/tests/components/test_history.py b/tests/components/test_history.py index 1d0d787b95d..675e2d022d9 100644 --- a/tests/components/test_history.py +++ b/tests/components/test_history.py @@ -13,7 +13,7 @@ import homeassistant as ha import homeassistant.util.dt as dt_util from homeassistant.components import history, recorder -from common import ( +from tests.common import ( mock_http_component, mock_state_change_event, get_test_home_assistant) diff --git a/tests/components/test_light.py b/tests/components/test_light.py index 5142823e8ef..e56dcbb02ad 100644 --- a/tests/components/test_light.py +++ b/tests/components/test_light.py @@ -15,7 +15,7 @@ from homeassistant.const import ( SERVICE_TURN_ON, SERVICE_TURN_OFF) import homeassistant.components.light as light -from common import mock_service, get_test_home_assistant +from tests.common import mock_service, get_test_home_assistant class TestLight(unittest.TestCase): diff --git a/tests/components/test_logbook.py b/tests/components/test_logbook.py index 2a426d0fcdc..cd415590d2d 100644 --- a/tests/components/test_logbook.py +++ b/tests/components/test_logbook.py @@ -14,7 +14,7 @@ from homeassistant.const import ( import homeassistant.util.dt as dt_util from homeassistant.components import logbook -from common import get_test_home_assistant, mock_http_component +from tests.common import get_test_home_assistant, mock_http_component class TestComponentHistory(unittest.TestCase): diff --git a/tests/components/test_media_player.py b/tests/components/test_media_player.py index d79ad89609f..f3f0128af3e 100644 --- a/tests/components/test_media_player.py +++ b/tests/components/test_media_player.py @@ -15,7 +15,7 @@ from homeassistant.const import ( SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREVIOUS_TRACK, ATTR_ENTITY_ID) import homeassistant.components.media_player as media_player -from common import mock_service +from tests.common import mock_service def setUpModule(): # pylint: disable=invalid-name diff --git a/tests/components/test_recorder.py b/tests/components/test_recorder.py index eb90f2331d9..26e5fdfd6b7 100644 --- a/tests/components/test_recorder.py +++ b/tests/components/test_recorder.py @@ -11,7 +11,7 @@ import os from homeassistant.const import MATCH_ALL from homeassistant.components import recorder -from common import get_test_home_assistant +from tests.common import get_test_home_assistant class TestRecorder(unittest.TestCase): diff --git a/tests/components/test_switch.py b/tests/components/test_switch.py index 273c5987be2..642c7f45aa9 100644 --- a/tests/components/test_switch.py +++ b/tests/components/test_switch.py @@ -11,7 +11,7 @@ import homeassistant.loader as loader from homeassistant.const import STATE_ON, STATE_OFF, CONF_PLATFORM import homeassistant.components.switch as switch -from common import get_test_home_assistant +from tests.common import get_test_home_assistant class TestSwitch(unittest.TestCase): From 2075de3d81bc7f0a8e690f5d4d463bb02e00750d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 4 Aug 2015 18:16:10 +0200 Subject: [PATCH 039/224] Extended test_init tests to cover all --- tests/helpers/test_event.py | 12 +- tests/test_init.py | 291 ++++++++++++++++++++++++++++++++---- 2 files changed, 266 insertions(+), 37 deletions(-) diff --git a/tests/helpers/test_event.py b/tests/helpers/test_event.py index c6fdf3e276a..69338cf431b 100644 --- a/tests/helpers/test_event.py +++ b/tests/helpers/test_event.py @@ -30,9 +30,9 @@ class TestEventHelpers(unittest.TestCase): def test_track_point_in_time(self): """ Test track point in time. """ - before_birthday = datetime(1985, 7, 9, 12, 0, 0) - birthday_paulus = datetime(1986, 7, 9, 12, 0, 0) - after_birthday = datetime(1987, 7, 9, 12, 0, 0) + before_birthday = datetime(1985, 7, 9, 12, 0, 0, tzinfo=dt_util.UTC) + birthday_paulus = datetime(1986, 7, 9, 12, 0, 0, tzinfo=dt_util.UTC) + after_birthday = datetime(1987, 7, 9, 12, 0, 0, tzinfo=dt_util.UTC) runs = [] @@ -52,7 +52,7 @@ class TestEventHelpers(unittest.TestCase): self.hass.pool.block_till_done() self.assertEqual(1, len(runs)) - track_point_in_utc_time( + track_point_in_time( self.hass, lambda x: runs.append(1), birthday_paulus) self._send_time_changed(after_birthday) @@ -65,7 +65,7 @@ class TestEventHelpers(unittest.TestCase): specific_runs = [] track_time_change(self.hass, lambda x: wildcard_runs.append(1)) - track_time_change( + track_utc_time_change( self.hass, lambda x: specific_runs.append(1), second=[0, 30]) self._send_time_changed(datetime(2014, 5, 24, 12, 0, 0)) @@ -84,7 +84,7 @@ class TestEventHelpers(unittest.TestCase): self.assertEqual(3, len(wildcard_runs)) def test_track_state_change(self): - """ Test states.track_change. """ + """ Test track_state_change. """ # 2 lists to track how often our callbacks get called specific_runs = [] wildcard_runs = [] diff --git a/tests/test_init.py b/tests/test_init.py index 58052fe43f0..4d484be580e 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -8,18 +8,27 @@ Provides tests to verify that Home Assistant core works. # pylint: disable=too-few-public-methods import os import unittest +import unittest.mock as mock import time import threading from datetime import datetime +import pytz + import homeassistant as ha +import homeassistant.util.dt as dt_util +from homeassistant.helpers.event import track_state_change +from homeassistant.const import ( + EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, + ATTR_FRIENDLY_NAME, TEMP_CELCIUS, + TEMP_FAHRENHEIT) + +PST = pytz.timezone('America/Los_Angeles') class TestHomeAssistant(unittest.TestCase): """ Tests the Home Assistant core classes. - Currently only includes tests to test cases that do not - get tested in the API integration tests. """ def setUp(self): # pylint: disable=invalid-name @@ -36,13 +45,13 @@ class TestHomeAssistant(unittest.TestCase): # Already stopped after the block till stopped test pass - def test_get_config_path(self): - """ Test get_config_path method. """ - self.assertEqual(os.path.join(os.getcwd(), "config"), - self.hass.config.config_dir) - - self.assertEqual(os.path.join(os.getcwd(), "config", "test.conf"), - self.hass.config.path("test.conf")) + def test_start(self): + calls = [] + self.hass.bus.listen_once(EVENT_HOMEASSISTANT_START, + lambda event: calls.append(1)) + self.hass.start() + self.hass.pool.block_till_done() + self.assertEqual(1, len(calls)) def test_block_till_stoped(self): """ Test if we can block till stop service is called. """ @@ -51,28 +60,48 @@ class TestHomeAssistant(unittest.TestCase): self.assertFalse(blocking_thread.is_alive()) blocking_thread.start() - # Python will now give attention to the other thread - time.sleep(1) + + # Threads are unpredictable, try 20 times if we're ready + wait_loops = 0 + while not blocking_thread.is_alive() and wait_loops < 20: + wait_loops += 1 + time.sleep(0.05) self.assertTrue(blocking_thread.is_alive()) self.hass.services.call(ha.DOMAIN, ha.SERVICE_HOMEASSISTANT_STOP) self.hass.pool.block_till_done() - # hass.block_till_stopped checks every second if it should quit - # we have to wait worst case 1 second + # Threads are unpredictable, try 20 times if we're ready wait_loops = 0 - while blocking_thread.is_alive() and wait_loops < 50: + while blocking_thread.is_alive() and wait_loops < 20: wait_loops += 1 - time.sleep(0.1) + time.sleep(0.05) self.assertFalse(blocking_thread.is_alive()) + def test_stopping_with_keyboardinterrupt(self): + calls = [] + self.hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, + lambda event: calls.append(1)) + + def raise_keyboardinterrupt(length): + # We don't want to patch the sleep of the timer. + if length == 1: + raise KeyboardInterrupt + + self.hass.start() + + with mock.patch('time.sleep', raise_keyboardinterrupt): + self.hass.block_till_stopped() + + self.assertEqual(1, len(calls)) + def test_track_point_in_time(self): """ Test track point in time. """ - before_birthday = datetime(1985, 7, 9, 12, 0, 0) - birthday_paulus = datetime(1986, 7, 9, 12, 0, 0) - after_birthday = datetime(1987, 7, 9, 12, 0, 0) + before_birthday = datetime(1985, 7, 9, 12, 0, 0, tzinfo=dt_util.UTC) + birthday_paulus = datetime(1986, 7, 9, 12, 0, 0, tzinfo=dt_util.UTC) + after_birthday = datetime(1987, 7, 9, 12, 0, 0, tzinfo=dt_util.UTC) runs = [] @@ -92,7 +121,7 @@ class TestHomeAssistant(unittest.TestCase): self.hass.pool.block_till_done() self.assertEqual(1, len(runs)) - self.hass.track_point_in_utc_time( + self.hass.track_point_in_time( lambda x: runs.append(1), birthday_paulus) self._send_time_changed(after_birthday) @@ -105,7 +134,7 @@ class TestHomeAssistant(unittest.TestCase): specific_runs = [] self.hass.track_time_change(lambda x: wildcard_runs.append(1)) - self.hass.track_time_change( + self.hass.track_utc_time_change( lambda x: specific_runs.append(1), second=[0, 30]) self._send_time_changed(datetime(2014, 5, 24, 12, 0, 0)) @@ -130,6 +159,16 @@ class TestHomeAssistant(unittest.TestCase): class TestEvent(unittest.TestCase): """ Test Event class. """ + def test_eq(self): + now = dt_util.utcnow() + data = {'some': 'attr'} + event1, event2 = [ + ha.Event('some_type', data, time_fired=now) + for _ in range(2) + ] + + self.assertEqual(event1, event2) + def test_repr(self): """ Test that repr method works. #MoreCoverage """ self.assertEqual( @@ -142,13 +181,27 @@ class TestEvent(unittest.TestCase): {"beer": "nice"}, ha.EventOrigin.remote))) + def test_as_dict(self): + event_type = 'some_type' + now = dt_util.utcnow() + data = {'some': 'attr'} + + event = ha.Event(event_type, data, ha.EventOrigin.local, now) + expected = { + 'event_type': event_type, + 'data': data, + 'origin': 'LOCAL', + 'time_fired': dt_util.datetime_to_str(now), + } + self.assertEqual(expected, event.as_dict()) + class TestEventBus(unittest.TestCase): """ Test EventBus methods. """ def setUp(self): # pylint: disable=invalid-name """ things to be run when tests are started. """ - self.bus = ha.EventBus() + self.bus = ha.EventBus(ha.create_worker_pool(0)) self.bus.listen('test_event', lambda x: len) def tearDown(self): # pylint: disable=invalid-name @@ -157,6 +210,7 @@ class TestEventBus(unittest.TestCase): def test_add_remove_listener(self): """ Test remove_listener method. """ + self.bus._pool.add_worker() old_count = len(self.bus.listeners) listener = lambda x: len @@ -182,11 +236,10 @@ class TestEventBus(unittest.TestCase): self.bus.listen_once('test_event', lambda x: runs.append(1)) self.bus.fire('test_event') - self.bus._pool.block_till_done() - self.assertEqual(1, len(runs)) - # Second time it should not increase runs self.bus.fire('test_event') + + self.bus._pool.add_worker() self.bus._pool.block_till_done() self.assertEqual(1, len(runs)) @@ -200,6 +253,37 @@ class TestState(unittest.TestCase): ha.InvalidEntityFormatError, ha.State, 'invalid_entity_format', 'test_state') + def test_domain(self): + state = ha.State('some_domain.hello', 'world') + self.assertEqual('some_domain', state.domain) + + def test_object_id(self): + state = ha.State('domain.hello', 'world') + self.assertEqual('hello', state.object_id) + + def test_name_if_no_friendly_name_attr(self): + state = ha.State('domain.hello_world', 'world') + self.assertEqual('hello world', state.name) + + def test_name_if_friendly_name_attr(self): + name = 'Some Unique Name' + state = ha.State('domain.hello_world', 'world', + {ATTR_FRIENDLY_NAME: name}) + self.assertEqual(name, state.name) + + def test_copy(self): + state = ha.State('domain.hello', 'world', {'some': 'attr'}) + self.assertEqual(state, state.copy()) + + def test_dict_conversion(self): + state = ha.State('domain.hello', 'world', {'some': 'attr'}) + self.assertEqual(state, ha.State.from_dict(state.as_dict())) + + def test_dict_conversion_with_wrong_data(self): + self.assertIsNone(ha.State.from_dict(None)) + self.assertIsNone(ha.State.from_dict({'state': 'yes'})) + self.assertIsNone(ha.State.from_dict({'entity_id': 'yes'})) + def test_repr(self): """ Test state.repr """ self.assertEqual("", @@ -218,14 +302,15 @@ class TestStateMachine(unittest.TestCase): def setUp(self): # pylint: disable=invalid-name """ things to be run when tests are started. """ - self.bus = ha.EventBus() + self.pool = ha.create_worker_pool(0) + self.bus = ha.EventBus(self.pool) self.states = ha.StateMachine(self.bus) self.states.set("light.Bowl", "on") self.states.set("switch.AC", "off") def tearDown(self): # pylint: disable=invalid-name """ Stop down stuff we started. """ - self.bus._pool.stop() + self.pool.stop() def test_is_state(self): """ Test is_state method. """ @@ -244,6 +329,10 @@ class TestStateMachine(unittest.TestCase): self.assertEqual(1, len(ent_ids)) self.assertTrue('light.bowl' in ent_ids) + def test_all(self): + states = sorted(state.entity_id for state in self.states.all()) + self.assertEqual(['light.bowl', 'switch.ac'], states) + def test_remove(self): """ Test remove method. """ self.assertTrue('light.bowl' in self.states.entity_ids()) @@ -255,6 +344,8 @@ class TestStateMachine(unittest.TestCase): def test_track_change(self): """ Test states.track_change. """ + self.pool.add_worker() + # 2 lists to track how often our callbacks got called specific_runs = [] wildcard_runs = [] @@ -291,10 +382,11 @@ class TestStateMachine(unittest.TestCase): self.assertEqual(3, len(wildcard_runs)) def test_case_insensitivty(self): + self.pool.add_worker() runs = [] - self.states.track_change( - 'light.BoWl', lambda a, b, c: runs.append(1), + track_state_change( + ha._MockHA(self.bus), 'light.BoWl', lambda a, b, c: runs.append(1), ha.MATCH_ALL, ha.MATCH_ALL) self.states.set('light.BOWL', 'off') @@ -332,16 +424,153 @@ class TestServiceRegistry(unittest.TestCase): def setUp(self): # pylint: disable=invalid-name """ things to be run when tests are started. """ - self.pool = ha.create_worker_pool() + self.pool = ha.create_worker_pool(0) self.bus = ha.EventBus(self.pool) self.services = ha.ServiceRegistry(self.bus, self.pool) - self.services.register("test_domain", "test_service", lambda x: len) + self.services.register("test_domain", "test_service", lambda x: None) def tearDown(self): # pylint: disable=invalid-name """ Stop down stuff we started. """ - self.pool.stop() + if self.pool.worker_count: + self.pool.stop() def test_has_service(self): """ Test has_service method. """ self.assertTrue( self.services.has_service("test_domain", "test_service")) + self.assertFalse( + self.services.has_service("test_domain", "non_existing")) + self.assertFalse( + self.services.has_service("non_existing", "test_service")) + + def test_services(self): + expected = { + 'test_domain': ['test_service'] + } + self.assertEqual(expected, self.services.services) + + def test_call_with_blocking_done_in_time(self): + self.pool.add_worker() + self.pool.add_worker() + calls = [] + self.services.register("test_domain", "register_calls", + lambda x: calls.append(1)) + + self.assertTrue( + self.services.call('test_domain', 'register_calls', blocking=True)) + self.assertEqual(1, len(calls)) + + def test_call_with_blocking_not_done_in_time(self): + calls = [] + self.services.register("test_domain", "register_calls", + lambda x: calls.append(1)) + + orig_limit = ha.SERVICE_CALL_LIMIT + ha.SERVICE_CALL_LIMIT = 0.01 + self.assertFalse( + self.services.call('test_domain', 'register_calls', blocking=True)) + self.assertEqual(0, len(calls)) + ha.SERVICE_CALL_LIMIT = orig_limit + + def test_call_non_existing_with_blocking(self): + self.pool.add_worker() + self.pool.add_worker() + orig_limit = ha.SERVICE_CALL_LIMIT + ha.SERVICE_CALL_LIMIT = 0.01 + self.assertFalse( + self.services.call('test_domain', 'i_do_not_exist', blocking=True)) + ha.SERVICE_CALL_LIMIT = orig_limit + + +class TestConfig(unittest.TestCase): + def setUp(self): # pylint: disable=invalid-name + """ things to be run when tests are started. """ + self.config = ha.Config() + + def test_config_dir_set_correct(self): + """ Test config dir set correct. """ + self.assertEqual(os.path.join(os.getcwd(), "config"), + self.config.config_dir) + + def test_path_with_file(self): + """ Test get_config_path method. """ + self.assertEqual(os.path.join(os.getcwd(), "config", "test.conf"), + self.config.path("test.conf")) + + def test_path_with_dir_and_file(self): + """ Test get_config_path method. """ + self.assertEqual( + os.path.join(os.getcwd(), "config", "dir", "test.conf"), + self.config.path("dir", "test.conf")) + + def test_temperature_not_convert_if_no_preference(self): + """ No unit conversion to happen if no preference. """ + self.assertEqual( + (25, TEMP_CELCIUS), + self.config.temperature(25, TEMP_CELCIUS)) + self.assertEqual( + (80, TEMP_FAHRENHEIT), + self.config.temperature(80, TEMP_FAHRENHEIT)) + + def test_temperature_not_convert_if_invalid_value(self): + """ No unit conversion to happen if no preference. """ + self.config.temperature_unit = TEMP_FAHRENHEIT + self.assertEqual( + ('25a', TEMP_CELCIUS), + self.config.temperature('25a', TEMP_CELCIUS)) + + def test_temperature_not_convert_if_invalid_unit(self): + """ No unit conversion to happen if no preference. """ + self.assertEqual( + (25, 'Invalid unit'), + self.config.temperature(25, 'Invalid unit')) + + def test_temperature_to_convert_to_celcius(self): + self.config.temperature_unit = TEMP_CELCIUS + + self.assertEqual( + (25, TEMP_CELCIUS), + self.config.temperature(25, TEMP_CELCIUS)) + self.assertEqual( + (26.7, TEMP_CELCIUS), + self.config.temperature(80, TEMP_FAHRENHEIT)) + + def test_temperature_to_convert_to_fahrenheit(self): + self.config.temperature_unit = TEMP_FAHRENHEIT + + self.assertEqual( + (77, TEMP_FAHRENHEIT), + self.config.temperature(25, TEMP_CELCIUS)) + self.assertEqual( + (80, TEMP_FAHRENHEIT), + self.config.temperature(80, TEMP_FAHRENHEIT)) + + def test_as_dict(self): + expected = { + 'latitude': None, + 'longitude': None, + 'temperature_unit': None, + 'location_name': None, + 'time_zone': 'UTC', + 'components': [], + } + + self.assertEqual(expected, self.config.as_dict()) + + +class TestWorkerPool(unittest.TestCase): + def test_exception_during_job(self): + pool = ha.create_worker_pool(1) + + def malicious_job(_): + raise Exception("Test breaking worker pool") + + calls = [] + + def register_call(_): + calls.append(1) + + pool.add_job(ha.JobPriority.EVENT_DEFAULT, (malicious_job, None)) + pool.add_job(ha.JobPriority.EVENT_DEFAULT, (register_call, None)) + pool.block_till_done() + self.assertEqual(1, len(calls)) From 4284a3f5dc3e0e8ff5907eb1655e82549a74719e Mon Sep 17 00:00:00 2001 From: Per Sandstrom Date: Tue, 4 Aug 2015 19:35:53 +0200 Subject: [PATCH 040/224] Fixed pylint conventions --- .../components/media_player/squeezebox.py | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/media_player/squeezebox.py b/homeassistant/components/media_player/squeezebox.py index 4d88d226f9d..70e74f3e895 100644 --- a/homeassistant/components/media_player/squeezebox.py +++ b/homeassistant/components/media_player/squeezebox.py @@ -80,6 +80,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class SqueezeBoxDevice(MediaPlayerDevice): """ Represents a SqueezeBox device. """ + # pylint: disable=too-many-arguments def __init__(self, name, server, player, port, user, password): super(SqueezeBoxDevice, self).__init__() self._name = name @@ -100,9 +101,9 @@ class SqueezeBoxDevice(MediaPlayerDevice): @property def state(self): """ Returns the state of the device. """ - if ('power' in self._status and self._status['power'] == '0'): + if 'power' in self._status and self._status['power'] == '0': return STATE_OFF - if('mode' in self._status): + if 'mode' in self._status: if self._status['mode'] == 'pause': return STATE_PAUSED if self._status['mode'] == 'play': @@ -112,7 +113,7 @@ class SqueezeBoxDevice(MediaPlayerDevice): return STATE_UNKNOWN def update(self): - if(self._user and self._password): + if self._user and self._password: self._query('login', self._user, self._password) self._get_status() @@ -154,18 +155,18 @@ class SqueezeBoxDevice(MediaPlayerDevice): @property def volume_level(self): """ Volume level of the media player (0..1). """ - if('mixer volume' in self._status): + if 'mixer volume' in self._status: return int(self._status['mixer volume']) / 100.0 @property def is_volume_muted(self): - if('mixer volume' in self._status): + if 'mixer volume' in self._status: return int(self._status['mixer volume']) < 0 @property def media_content_id(self): """ Content ID of current playing media. """ - if('current_title' in self._status): + if 'current_title' in self._status: return self._status['current_title'] @property @@ -176,13 +177,13 @@ class SqueezeBoxDevice(MediaPlayerDevice): @property def media_duration(self): """ Duration of current playing media in seconds. """ - if('duration' in self._status): + if 'duration' in self._status: return int(float(self._status['duration'])) @property def media_image_url(self): """ Image url of current playing media. """ - if('artwork_url' in self._status): + if 'artwork_url' in self._status: return self._status['artwork_url'] return 'http://{server}:{port}/music/current/cover.jpg?player={player}'\ .format( @@ -193,12 +194,12 @@ class SqueezeBoxDevice(MediaPlayerDevice): @property def media_title(self): """ Title of current playing media. """ - if('artist' in self._status and 'title' in self._status): + if 'artist' in self._status and 'title' in self._status: return '{artist} - {title}'.format( artist=self._status['artist'], title=self._status['title'] ) - if('current_title' in self._status): + if 'current_title' in self._status: return self._status['current_title'] @property From e6c09f7413b76137361d178c97f2408b9649c0a6 Mon Sep 17 00:00:00 2001 From: Per Sandstrom Date: Tue, 4 Aug 2015 20:08:48 +0200 Subject: [PATCH 041/224] Fixed bug with password protected LMS --- homeassistant/components/media_player/squeezebox.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/media_player/squeezebox.py b/homeassistant/components/media_player/squeezebox.py index 70e74f3e895..496e0170dc9 100644 --- a/homeassistant/components/media_player/squeezebox.py +++ b/homeassistant/components/media_player/squeezebox.py @@ -113,13 +113,16 @@ class SqueezeBoxDevice(MediaPlayerDevice): return STATE_UNKNOWN def update(self): - if self._user and self._password: - self._query('login', self._user, self._password) self._get_status() def _query(self, *parameters): """ Send request and await response from server """ telnet = telnetlib.Telnet(self._server, self._port) + if self._user and self._password: + telnet.write('login {user} {password}\n'.format( + user=self._user, + password=self._password).encode('UTF-8')) + telnet.read_until(b'\n', timeout=3) message = '{}\n'.format(' '.join(parameters)) telnet.write(message.encode('UTF-8')) response = telnet.read_until(b'\n', timeout=3)\ From 30e24296c44aa68ccc1b934240f176a929d7a29a Mon Sep 17 00:00:00 2001 From: Per Sandstrom Date: Tue, 4 Aug 2015 20:30:01 +0200 Subject: [PATCH 042/224] Fixed flake8 --- homeassistant/components/media_player/squeezebox.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/media_player/squeezebox.py b/homeassistant/components/media_player/squeezebox.py index 496e0170dc9..70eab958000 100644 --- a/homeassistant/components/media_player/squeezebox.py +++ b/homeassistant/components/media_player/squeezebox.py @@ -120,7 +120,7 @@ class SqueezeBoxDevice(MediaPlayerDevice): telnet = telnetlib.Telnet(self._server, self._port) if self._user and self._password: telnet.write('login {user} {password}\n'.format( - user=self._user, + user=self._user, password=self._password).encode('UTF-8')) telnet.read_until(b'\n', timeout=3) message = '{}\n'.format(' '.join(parameters)) From d2b5f429fea6f0e423e6333116ada0cc315cb6c1 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 4 Aug 2015 16:21:09 -0400 Subject: [PATCH 043/224] Remove deprecated code --- homeassistant/__init__.py | 12 +++--- homeassistant/bootstrap.py | 15 ++++--- homeassistant/components/group.py | 4 -- homeassistant/components/process.py | 52 ----------------------- homeassistant/helpers/__init__.py | 4 -- homeassistant/helpers/device.py | 10 ----- homeassistant/helpers/device_component.py | 10 ----- homeassistant/util/__init__.py | 6 +-- tests/components/test_group.py | 3 +- 9 files changed, 17 insertions(+), 99 deletions(-) delete mode 100644 homeassistant/components/process.py delete mode 100644 homeassistant/helpers/device.py delete mode 100644 homeassistant/helpers/device_component.py diff --git a/homeassistant/__init__.py b/homeassistant/__init__.py index f5129fd1c95..bf21dc0a15d 100644 --- a/homeassistant/__init__.py +++ b/homeassistant/__init__.py @@ -96,7 +96,7 @@ class HomeAssistant(object): self.pool.stop() def track_point_in_time(self, action, point_in_time): - """Deprecated method to track point in time.""" + """Deprecated method as of 8/4/2015 to track point in time.""" _LOGGER.warning( 'hass.track_point_in_time is deprecated. ' 'Please use homeassistant.helpers.event.track_point_in_time') @@ -104,7 +104,7 @@ class HomeAssistant(object): helper.track_point_in_time(self, action, point_in_time) def track_point_in_utc_time(self, action, point_in_time): - """Deprecated method to track point in UTC time.""" + """Deprecated method as of 8/4/2015 to track point in UTC time.""" _LOGGER.warning( 'hass.track_point_in_utc_time is deprecated. ' 'Please use homeassistant.helpers.event.track_point_in_utc_time') @@ -114,7 +114,7 @@ class HomeAssistant(object): def track_utc_time_change(self, action, year=None, month=None, day=None, hour=None, minute=None, second=None): - """Deprecated method to track UTC time change.""" + """Deprecated method as of 8/4/2015 to track UTC time change.""" # pylint: disable=too-many-arguments _LOGGER.warning( 'hass.track_utc_time_change is deprecated. ' @@ -126,7 +126,7 @@ class HomeAssistant(object): def track_time_change(self, action, year=None, month=None, day=None, hour=None, minute=None, second=None, utc=False): - """Deprecated method to track time change.""" + """Deprecated method as of 8/4/2015 to track time change.""" # pylint: disable=too-many-arguments _LOGGER.warning( 'hass.track_time_change is deprecated. ' @@ -183,7 +183,7 @@ class Event(object): self.event_type = event_type self.data = data or {} self.origin = origin - self.time_fired = util.strip_microseconds( + self.time_fired = date_util.strip_microseconds( time_fired or date_util.utcnow()) def as_dict(self): @@ -502,7 +502,7 @@ class StateMachine(object): def track_change(self, entity_ids, action, from_state=None, to_state=None): """ - DEPRECATED + DEPRECATED AS OF 8/4/2015 """ _LOGGER.warning( 'hass.states.track_change is deprecated. ' diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 663ed611de4..514c1adce57 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -63,12 +63,15 @@ def setup_component(hass, domain, config=None): def _handle_requirements(component, name): """ Installs requirements for component. """ - if hasattr(component, 'REQUIREMENTS'): - for req in component.REQUIREMENTS: - if not pkg_util.install_package(req): - _LOGGER.error('Not initializing %s because could not install ' - 'dependency %s', name, req) - return False + if not hasattr(component, 'REQUIREMENTS'): + return True + + for req in component.REQUIREMENTS: + if not pkg_util.install_package(req): + _LOGGER.error('Not initializing %s because could not install ' + 'dependency %s', name, req) + return False + return True diff --git a/homeassistant/components/group.py b/homeassistant/components/group.py index ac5e8cd4116..7c24c505add 100644 --- a/homeassistant/components/group.py +++ b/homeassistant/components/group.py @@ -103,10 +103,6 @@ def get_entity_ids(hass, entity_id, domain_filter=None): def setup(hass, config): """ Sets up all groups found definded in the configuration. """ for name, entity_ids in config.get(DOMAIN, {}).items(): - # Support old deprecated method - 2/28/2015 - if isinstance(entity_ids, str): - entity_ids = entity_ids.split(",") - setup_group(hass, name, entity_ids) return True diff --git a/homeassistant/components/process.py b/homeassistant/components/process.py deleted file mode 100644 index 21343aa977b..00000000000 --- a/homeassistant/components/process.py +++ /dev/null @@ -1,52 +0,0 @@ -""" -homeassistant.components.process -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Provides functionality to watch for specific processes running -on the host machine. - -Author: Markus Stenberg -""" -import logging -import os - -from homeassistant.const import STATE_ON, STATE_OFF -import homeassistant.util as util - -DOMAIN = 'process' -DEPENDENCIES = [] -ENTITY_ID_FORMAT = DOMAIN + '.{}' - -PS_STRING = 'ps awx' - - -def setup(hass, config): - """ Sets up a check if specified processes are running. - - processes: dict mapping entity id to substring to search for - in process list. - """ - - # Deprecated as of 3/7/2015 - logging.getLogger(__name__).warning( - "This component has been deprecated and will be removed in the future." - " Please use sensor.systemmonitor with the process type") - - entities = {ENTITY_ID_FORMAT.format(util.slugify(pname)): pstring - for pname, pstring in config[DOMAIN].items()} - - def update_process_states(time): - """ Check ps for currently running processes and update states. """ - with os.popen(PS_STRING, 'r') as psfile: - lines = list(psfile) - - for entity_id, pstring in entities.items(): - state = STATE_ON if any(pstring in l for l in lines) else STATE_OFF - - hass.states.set(entity_id, state) - - update_process_states(None) - - hass.track_time_change(update_process_states, second=[0, 30]) - - return True diff --git a/homeassistant/helpers/__init__.py b/homeassistant/helpers/__init__.py index 086cddc35e2..286eed4654e 100644 --- a/homeassistant/helpers/__init__.py +++ b/homeassistant/helpers/__init__.py @@ -6,10 +6,6 @@ from homeassistant.const import ( ATTR_ENTITY_ID, CONF_PLATFORM, DEVICE_DEFAULT_NAME) from homeassistant.util import ensure_unique_string, slugify -# Deprecated 3/5/2015 - Moved to homeassistant.helpers.entity -# pylint: disable=unused-import -from .entity import Entity as Device, ToggleEntity as ToggleDevice # noqa - def generate_entity_id(entity_id_format, name, current_ids=None, hass=None): """ Generate a unique entity ID based on given entity IDs or used ids. """ diff --git a/homeassistant/helpers/device.py b/homeassistant/helpers/device.py deleted file mode 100644 index 4c713693c43..00000000000 --- a/homeassistant/helpers/device.py +++ /dev/null @@ -1,10 +0,0 @@ -""" -Deprecated since 3/21/2015 - please use helpers.entity -""" -import logging - -# pylint: disable=unused-import -from .entity import Entity as Device, ToggleEntity as ToggleDevice # noqa - -logging.getLogger(__name__).warning( - 'This file is deprecated. Please use helpers.entity') diff --git a/homeassistant/helpers/device_component.py b/homeassistant/helpers/device_component.py deleted file mode 100644 index 248297a9694..00000000000 --- a/homeassistant/helpers/device_component.py +++ /dev/null @@ -1,10 +0,0 @@ -""" -Deprecated since 3/21/2015 - please use helpers.entity_component -""" -import logging - -# pylint: disable=unused-import -from .entity_component import EntityComponent as DeviceComponent # noqa - -logging.getLogger(__name__).warning( - 'This file is deprecated. Please use helpers.entity_component') diff --git a/homeassistant/util/__init__.py b/homeassistant/util/__init__.py index a75d9837de6..2e399384e63 100644 --- a/homeassistant/util/__init__.py +++ b/homeassistant/util/__init__.py @@ -16,11 +16,7 @@ import random import string from functools import wraps -# DEPRECATED AS OF 4/27/2015 - moved to homeassistant.util.dt package -# pylint: disable=unused-import -from .dt import ( # noqa - datetime_to_str, str_to_datetime, strip_microseconds, - datetime_to_local_str, utcnow) +from .dt import datetime_to_local_str, utcnow RE_SANITIZE_FILENAME = re.compile(r'(~|\.\.|/|\\)') diff --git a/tests/components/test_group.py b/tests/components/test_group.py index a476efdeea0..d1e62b02cdb 100644 --- a/tests/components/test_group.py +++ b/tests/components/test_group.py @@ -199,8 +199,7 @@ class TestComponentsGroup(unittest.TestCase): self.hass, { group.DOMAIN: { - 'second_group': ','.join((self.group_entity_id, - 'light.Bowl')) + 'second_group': (self.group_entity_id, 'light.Bowl') } })) From aa20b9492721f25ee9446058c9d9a263183b34a3 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 4 Aug 2015 16:21:25 -0400 Subject: [PATCH 044/224] Remove support for old home-assistant.conf file --- homeassistant/config.py | 45 +++++++---------------------------------- tests/test_config.py | 35 ++------------------------------ 2 files changed, 9 insertions(+), 71 deletions(-) diff --git a/homeassistant/config.py b/homeassistant/config.py index 629001b972f..3b2dd1ce740 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -13,14 +13,11 @@ from homeassistant.const import ( CONF_TIME_ZONE) import homeassistant.util.location as loc_util - _LOGGER = logging.getLogger(__name__) - YAML_CONFIG_FILE = 'configuration.yaml' -CONF_CONFIG_FILE = 'home-assistant.conf' -DEFAULT_CONFIG = [ +DEFAULT_CONFIG = ( # Tuples (attribute, default, auto detect property, description) (CONF_NAME, 'Home', None, 'Name of the location where Home Assistant is ' 'running'), @@ -30,9 +27,9 @@ DEFAULT_CONFIG = [ (CONF_TEMPERATURE_UNIT, 'C', None, 'C for Celcius, F for Fahrenheit'), (CONF_TIME_ZONE, 'UTC', 'time_zone', 'Pick yours from here: http://en.wiki' 'pedia.org/wiki/List_of_tz_database_time_zones'), -] -DEFAULT_COMPONENTS = [ - 'discovery', 'frontend', 'conversation', 'history', 'logbook', 'sun'] +) +DEFAULT_COMPONENTS = ( + 'discovery', 'frontend', 'conversation', 'history', 'logbook', 'sun') def ensure_config_exists(config_dir, detect_location=True): @@ -95,24 +92,14 @@ def create_default_config(config_dir, detect_location=True): def find_config_file(config_dir): """ Looks in given directory for supported config files. """ - for filename in (YAML_CONFIG_FILE, CONF_CONFIG_FILE): - config_path = os.path.join(config_dir, filename) + config_path = os.path.join(config_dir, YAML_CONFIG_FILE) - if os.path.isfile(config_path): - return config_path - - return None + return config_path if os.path.isfile(config_path) else None def load_config_file(config_path): """ Loads given config file. """ - config_ext = os.path.splitext(config_path)[1] - - if config_ext == '.yaml': - return load_yaml_config_file(config_path) - - elif config_ext == '.conf': - return load_conf_config_file(config_path) + return load_yaml_config_file(config_path) def load_yaml_config_file(config_path): @@ -152,21 +139,3 @@ def load_yaml_config_file(config_path): raise HomeAssistantError() return conf_dict - - -def load_conf_config_file(config_path): - """ Parse the old style conf configuration. """ - import configparser - - config_dict = {} - - config = configparser.ConfigParser() - config.read(config_path) - - for section in config.sections(): - config_dict[section] = {} - - for key, val in config.items(section): - config_dict[section][key] = val - - return config_dict diff --git a/tests/test_config.py b/tests/test_config.py index 368f660eeb7..235549f6cef 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -20,7 +20,6 @@ from common import get_test_config_dir CONFIG_DIR = get_test_config_dir() YAML_PATH = os.path.join(CONFIG_DIR, config_util.YAML_CONFIG_FILE) -CONF_PATH = os.path.join(CONFIG_DIR, config_util.CONF_CONFIG_FILE) def create_file(path): @@ -51,9 +50,8 @@ class TestConfig(unittest.TestCase): def tearDown(self): # pylint: disable=invalid-name """ Clean up. """ - for path in (YAML_PATH, CONF_PATH): - if os.path.isfile(path): - os.remove(path) + if os.path.isfile(YAML_PATH): + os.remove(YAML_PATH) def test_create_default_config(self): """ Test creationg of default config. """ @@ -69,21 +67,6 @@ class TestConfig(unittest.TestCase): self.assertEqual(YAML_PATH, config_util.find_config_file(CONFIG_DIR)) - def test_find_config_file_conf(self): - """ Test if it finds the old CONF config file. """ - - create_file(CONF_PATH) - - self.assertEqual(CONF_PATH, config_util.find_config_file(CONFIG_DIR)) - - def test_find_config_file_prefers_yaml_over_conf(self): - """ Test if find config prefers YAML over CONF if both exist. """ - - create_file(YAML_PATH) - create_file(CONF_PATH) - - self.assertEqual(YAML_PATH, config_util.find_config_file(CONFIG_DIR)) - def test_ensure_config_exists_creates_config(self): """ Test that calling ensure_config_exists creates a new config file if none exists. """ @@ -135,20 +118,6 @@ class TestConfig(unittest.TestCase): self.assertEqual({'hello': 'world'}, config_util.load_config_file(YAML_PATH)) - def test_load_config_loads_conf_config(self): - """ Test correct YAML config loading. """ - create_file(CONF_PATH) - - self.assertEqual({}, config_util.load_config_file(CONF_PATH)) - - def test_conf_config_file(self): - """ Test correct CONF config loading. """ - with open(CONF_PATH, 'w') as f: - f.write('[ha]\ntime_zone=America/Los_Angeles') - - self.assertEqual({'ha': {'time_zone': 'America/Los_Angeles'}}, - config_util.load_conf_config_file(CONF_PATH)) - def test_create_default_config_detect_location(self): """ Test that detect location sets the correct config keys. """ with mock.patch('homeassistant.util.location.detect_location_info', From 4f9fa7f8ad7c5b7aaac3a92681f191c972d51712 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 4 Aug 2015 16:33:35 -0400 Subject: [PATCH 045/224] Update coveragerc --- .coveragerc | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/.coveragerc b/.coveragerc index 39a3dee22bf..15c76376eef 100644 --- a/.coveragerc +++ b/.coveragerc @@ -10,30 +10,36 @@ omit = homeassistant/components/arduino.py homeassistant/components/*/arduino.py + homeassistant/components/isy994.py + homeassistant/components/*/isy994.py + + homeassistant/components/modbus.py + homeassistant/components/*/modbus.py + homeassistant/components/wink.py homeassistant/components/*/wink.py homeassistant/components/zwave.py homeassistant/components/*/zwave.py - homeassistant/components/modbus.py - homeassistant/components/*/modbus.py - - homeassistant/components/isy994.py - homeassistant/components/*/isy994.py - homeassistant/components/*/tellstick.py homeassistant/components/*/vera.py homeassistant/components/browser.py + homeassistant/components/camera/* homeassistant/components/device_tracker/ddwrt.py homeassistant/components/device_tracker/luci.py homeassistant/components/device_tracker/netgear.py homeassistant/components/device_tracker/nmap_tracker.py homeassistant/components/device_tracker/tomato.py + homeassistant/components/device_tracker/tplink.py + homeassistant/components/discovery.py + homeassistant/components/downloader.py homeassistant/components/keyboard.py homeassistant/components/light/hue.py + homeassistant/components/light/limitlessled.py homeassistant/components/media_player/cast.py + homeassistant/components/media_player/kodi.py homeassistant/components/media_player/mpd.py homeassistant/components/notify/file.py homeassistant/components/notify/instapush.py @@ -53,7 +59,9 @@ omit = homeassistant/components/sensor/systemmonitor.py homeassistant/components/sensor/time_date.py homeassistant/components/sensor/transmission.py + homeassistant/components/switch/command_switch.py homeassistant/components/switch/hikvisioncam.py + homeassistant/components/switch/transmission.py homeassistant/components/switch/wemo.py homeassistant/components/thermostat/nest.py From 52ec4ac1d8509f6450b1d9c1bccd72e300510cd9 Mon Sep 17 00:00:00 2001 From: Per Sandstrom Date: Wed, 5 Aug 2015 10:22:03 +0200 Subject: [PATCH 046/224] flake8 and pylint --- homeassistant/components/device_tracker/asuswrt.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/device_tracker/asuswrt.py b/homeassistant/components/device_tracker/asuswrt.py index e38e410fe54..e7126d7df91 100644 --- a/homeassistant/components/device_tracker/asuswrt.py +++ b/homeassistant/components/device_tracker/asuswrt.py @@ -62,6 +62,7 @@ _IP_NEIGH_REGEX = re.compile( r'(?P(\w+))') +# pylint: disable=unused-argument def get_scanner(hass, config): """ Validates config and returns a DD-WRT scanner. """ if not validate_config(config, @@ -146,7 +147,8 @@ class AsusWrtDeviceScanner(object): _LOGGER.exception("Unexpected response from router") return except ConnectionRefusedError: - _LOGGER.exception("Connection refused by router, is telnet enabled?") + _LOGGER.exception("Connection refused by router," + + " is telnet enabled?") return devices = {} From db2cbf33c3172c889fcc70e95efb39bdd8d7e0f1 Mon Sep 17 00:00:00 2001 From: Per Sandstrom Date: Wed, 5 Aug 2015 13:49:45 +0200 Subject: [PATCH 047/224] Added support for multiple players --- .../components/media_player/squeezebox.py | 221 ++++++++++-------- 1 file changed, 129 insertions(+), 92 deletions(-) diff --git a/homeassistant/components/media_player/squeezebox.py b/homeassistant/components/media_player/squeezebox.py index 70eab958000..fa05e304c29 100644 --- a/homeassistant/components/media_player/squeezebox.py +++ b/homeassistant/components/media_player/squeezebox.py @@ -10,34 +10,24 @@ To use SqueezeBox add something like this to your configuration: media_player: platform: squeezebox - name: SqueezeBox - server: 192.168.1.21 - player: Player1 + host: 192.168.1.21 port: 9090 - user: user + username: user password: password Variables: -name -*Optional -The name of the device - -server +host *Required -The address of the Logitech Media Server - -player -*Required -The unique name of the player +The host name or address of the Logitech Media Server port *Optional Telnet port to Logitech Media Server, default 9090 -user +usermame *Optional -User, if password protection is enabled +Username, if password protection is enabled password *Optional @@ -51,8 +41,10 @@ import urllib.parse from homeassistant.components.media_player import ( MediaPlayerDevice, SUPPORT_PAUSE, SUPPORT_SEEK, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_MUTE, SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK, - MEDIA_TYPE_MUSIC) + MEDIA_TYPE_MUSIC, DOMAIN) + from homeassistant.const import ( + CONF_HOST, CONF_USERNAME, CONF_PASSWORD, STATE_IDLE, STATE_PLAYING, STATE_PAUSED, STATE_OFF, STATE_UNKNOWN) _LOGGER = logging.getLogger(__name__) @@ -64,15 +56,104 @@ SUPPORT_SQUEEZEBOX = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE |\ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): """ Sets up the squeezebox platform. """ - add_devices([ - SqueezeBoxDevice( - config.get('name', 'SqueezeBox'), - config.get('server'), - config.get('player'), - config.get('port', '9090'), - config.get('user', None), - config.get('password', None) - )]) + if not config.get(CONF_HOST): + _LOGGER.error( + "Missing required configuration items in {}: {}".format( + DOMAIN, CONF_HOST)) + return False + + lms = LogitechMediaServer( + config.get(CONF_HOST), + config.get('port', '9090'), + config.get(CONF_USERNAME), + config.get(CONF_PASSWORD)) + + if not lms.init_success: + return False + + add_devices(lms.create_players()) + + return True + + +class LogitechMediaServer(object): + def __init__(self, host, port, username, password): + self.host = host + self.port = port + self._username = username + self._password = password + self.http_port = self._get_http_port() + self.init_success = True if self.http_port else False + + def _get_http_port(self): + """ Get http port from media server, it is used to get cover art """ + http_port = None + try: + http_port = self.query('pref', 'httpport', '?') + if not http_port: + _LOGGER.error( + "Unable to read data from server {}:{}".format( + self.host, + self.port)) + return + return http_port + except ConnectionError as ex: + _LOGGER.error( + "Failed to connect to server {}:{} - {}".format( + self.host, + self.port, + ex)) + return + + def create_players(self): + """ Create a list of SqueezeBoxDevices connected to the LMS """ + players = [] + count = self.query('player', 'count', '?') + for index in range(0, int(count)): + player_id = self.query('player', 'id', str(index), '?') + player = SqueezeBoxDevice(self, player_id) + players.append(player) + return players + + def query(self, *parameters): + """ Send request and await response from server """ + telnet = telnetlib.Telnet(self.host, self.port) + if self._username and self._password: + telnet.write('login {username} {password}\n'.format( + username=self._username, + password=self._password).encode('UTF-8')) + telnet.read_until(b'\n', timeout=3) + message = '{}\n'.format(' '.join(parameters)) + telnet.write(message.encode('UTF-8')) + response = telnet.read_until(b'\n', timeout=3)\ + .decode('UTF-8')\ + .split(' ')[-1]\ + .strip() + telnet.write(b'exit\n') + return urllib.parse.unquote(response) + + def get_player_status(self, player): + """ Get ithe status of a player """ + # (title) : Song title + # Requested Information + # a (artist): Artist name 'artist' + # d (duration): Song duration in seconds 'duration' + # K (artwork_url): URL to remote artwork + tags = 'adK' + new_status = {} + telnet = telnetlib.Telnet(self.host, self.port) + telnet.write('{player} status - 1 tags:{tags}\n'.format( + player=player, + tags=tags + ).encode('UTF-8')) + response = telnet.read_until(b'\n', timeout=3)\ + .decode('UTF-8')\ + .split(' ') + telnet.write(b'exit\n') + for item in response: + parts = urllib.parse.unquote(item).partition(':') + new_status[parts[0]] = parts[2] + return new_status # pylint: disable=too-many-instance-attributes @@ -81,17 +162,12 @@ class SqueezeBoxDevice(MediaPlayerDevice): """ Represents a SqueezeBox device. """ # pylint: disable=too-many-arguments - def __init__(self, name, server, player, port, user, password): + def __init__(self, lms, player_id): super(SqueezeBoxDevice, self).__init__() - self._name = name - self._server = server - self._player = player - self._port = port - self._user = user - self._password = password - self._status = {} - self.update() - self._http_port = self._query('pref', 'httpport', '?') + self._lms = lms + self._id = player_id + self._name = self._lms.query(self._id, 'name', '?') + self._status = self._lms.get_player_status(self._id) @property def name(self): @@ -113,47 +189,8 @@ class SqueezeBoxDevice(MediaPlayerDevice): return STATE_UNKNOWN def update(self): - self._get_status() - - def _query(self, *parameters): - """ Send request and await response from server """ - telnet = telnetlib.Telnet(self._server, self._port) - if self._user and self._password: - telnet.write('login {user} {password}\n'.format( - user=self._user, - password=self._password).encode('UTF-8')) - telnet.read_until(b'\n', timeout=3) - message = '{}\n'.format(' '.join(parameters)) - telnet.write(message.encode('UTF-8')) - response = telnet.read_until(b'\n', timeout=3)\ - .decode('UTF-8')\ - .split(' ')[-1]\ - .strip() - telnet.write(b'exit\n') - return urllib.parse.unquote(response) - - def _get_status(self): - """ request status and parse result """ - # (title) : Song title - # Requested Information - # a (artist): Artist name 'artist' - # d (duration): Song duration in seconds 'duration' - # K (artwork_url): URL to remote artwork - tags = 'adK' - new_status = {} - telnet = telnetlib.Telnet(self._server, self._port) - telnet.write('{player} status - 1 tags:{tags}\n'.format( - player=self._player, - tags=tags - ).encode('UTF-8')) - response = telnet.read_until(b'\n', timeout=3)\ - .decode('UTF-8')\ - .split(' ') - telnet.write(b'exit\n') - for item in response: - parts = urllib.parse.unquote(item).partition(':') - new_status[parts[0]] = parts[2] - self._status = new_status + """ Retrieve latest state. """ + self._status = self._lms.get_player_status(self._name) @property def volume_level(self): @@ -190,9 +227,9 @@ class SqueezeBoxDevice(MediaPlayerDevice): return self._status['artwork_url'] return 'http://{server}:{port}/music/current/cover.jpg?player={player}'\ .format( - server=self._server, - port=self._http_port, - player=self._player) + server=self._lms.host, + port=self._lms.http_port, + player=self._id) @property def media_title(self): @@ -212,64 +249,64 @@ class SqueezeBoxDevice(MediaPlayerDevice): def turn_off(self): """ turn_off media player. """ - self._query(self._player, 'power', '0') + self._lms.query(self._id, 'power', '0') self.update_ha_state() def volume_up(self): """ volume_up media player. """ - self._query(self._player, 'mixer', 'volume', '+5') + self._lms.query(self._id, 'mixer', 'volume', '+5') self.update_ha_state() def volume_down(self): """ volume_down media player. """ - self._query(self._player, 'mixer', 'volume', '-5') + self._lms.query(self._id, 'mixer', 'volume', '-5') self.update_ha_state() def set_volume_level(self, volume): """ set volume level, range 0..1. """ volume_percent = str(int(volume*100)) - self._query(self._player, 'mixer', 'volume', volume_percent) + self._lms.query(self._id, 'mixer', 'volume', volume_percent) self.update_ha_state() def mute_volume(self, mute): """ mute (true) or unmute (false) media player. """ mute_numeric = '1' if mute else '0' - self._query(self._player, 'mixer', 'muting', mute_numeric) + self._lms.query(self._id, 'mixer', 'muting', mute_numeric) self.update_ha_state() def media_play_pause(self): """ media_play_pause media player. """ - self._query(self._player, 'pause') + self._lms.query(self._id, 'pause') self.update_ha_state() def media_play(self): """ media_play media player. """ - self._query(self._player, 'play') + self._lms.query(self._id, 'play') self.update_ha_state() def media_pause(self): """ media_pause media player. """ - self._query(self._player, 'pause', '0') + self._lms.query(self._id, 'pause', '0') self.update_ha_state() def media_next_track(self): """ Send next track command. """ - self._query(self._player, 'playlist', 'index', '+1') + self._lms.query(self._id, 'playlist', 'index', '+1') self.update_ha_state() def media_previous_track(self): """ Send next track command. """ - self._query(self._player, 'playlist', 'index', '-1') + self._lms.query(self._id, 'playlist', 'index', '-1') self.update_ha_state() def media_seek(self, position): """ Send seek command. """ - self._query(self._player, 'time', position) + self._lms.query(self._id, 'time', position) self.update_ha_state() def turn_on(self): """ turn the media player on. """ - self._query(self._player, 'power', '1') + self._lms.query(self._id, 'power', '1') self.update_ha_state() def play_youtube(self, media_id): From eb83621fce2cb88e6773a26a928a536f5490a237 Mon Sep 17 00:00:00 2001 From: Per Sandstrom Date: Wed, 5 Aug 2015 20:02:39 +0200 Subject: [PATCH 048/224] fixing pylint issues --- .../components/media_player/squeezebox.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/media_player/squeezebox.py b/homeassistant/components/media_player/squeezebox.py index fa05e304c29..16f1b3ef523 100644 --- a/homeassistant/components/media_player/squeezebox.py +++ b/homeassistant/components/media_player/squeezebox.py @@ -58,8 +58,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None): """ Sets up the squeezebox platform. """ if not config.get(CONF_HOST): _LOGGER.error( - "Missing required configuration items in {}: {}".format( - DOMAIN, CONF_HOST)) + "Missing required configuration items in %s: %s", + DOMAIN, CONF_HOST) return False lms = LogitechMediaServer( @@ -77,6 +77,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class LogitechMediaServer(object): + """ Represents a Logitech media server. """ + def __init__(self, host, port, username, password): self.host = host self.port = port @@ -92,17 +94,17 @@ class LogitechMediaServer(object): http_port = self.query('pref', 'httpport', '?') if not http_port: _LOGGER.error( - "Unable to read data from server {}:{}".format( + "Unable to read data from server %s:%s", self.host, - self.port)) + self.port) return return http_port except ConnectionError as ex: _LOGGER.error( - "Failed to connect to server {}:{} - {}".format( + "Failed to connect to server %s:%s - %s", self.host, self.port, - ex)) + ex) return def create_players(self): From 03f93063f84d4086d4cb2e503088082032ee7345 Mon Sep 17 00:00:00 2001 From: Per Sandstrom Date: Wed, 5 Aug 2015 20:09:20 +0200 Subject: [PATCH 049/224] fixed flake8 issues --- .../components/media_player/squeezebox.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/media_player/squeezebox.py b/homeassistant/components/media_player/squeezebox.py index 16f1b3ef523..95715b1e555 100644 --- a/homeassistant/components/media_player/squeezebox.py +++ b/homeassistant/components/media_player/squeezebox.py @@ -59,7 +59,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None): if not config.get(CONF_HOST): _LOGGER.error( "Missing required configuration items in %s: %s", - DOMAIN, CONF_HOST) + DOMAIN, + CONF_HOST) return False lms = LogitechMediaServer( @@ -78,7 +79,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class LogitechMediaServer(object): """ Represents a Logitech media server. """ - + def __init__(self, host, port, username, password): self.host = host self.port = port @@ -95,16 +96,16 @@ class LogitechMediaServer(object): if not http_port: _LOGGER.error( "Unable to read data from server %s:%s", - self.host, - self.port) + self.host, + self.port) return return http_port except ConnectionError as ex: _LOGGER.error( "Failed to connect to server %s:%s - %s", - self.host, - self.port, - ex) + self.host, + self.port, + ex) return def create_players(self): From 450b510d084cdd9a1c9e8a6693f4429e93f6f590 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 5 Aug 2015 11:55:59 -0700 Subject: [PATCH 050/224] Fix sensor.forecastio to treat Fahrenheit wrong Fixes #245 --- homeassistant/components/sensor/forecast.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/forecast.py b/homeassistant/components/sensor/forecast.py index 613dd35d640..6222ad4d664 100644 --- a/homeassistant/components/sensor/forecast.py +++ b/homeassistant/components/sensor/forecast.py @@ -215,5 +215,6 @@ class ForeCastData(object): forecast = forecastio.load_forecast(self._api_key, self.latitude, - self.longitude) + self.longitude, + units='si') self.data = forecast.currently() From 393e88e7324428f4c1b8b5f29a4a6081e2d636f2 Mon Sep 17 00:00:00 2001 From: Per Sandstrom Date: Wed, 5 Aug 2015 22:25:03 +0200 Subject: [PATCH 051/224] add to .coveragerc --- .coveragerc | 1 + homeassistant/components/media_player/squeezebox.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.coveragerc b/.coveragerc index 39a3dee22bf..386480fa36b 100644 --- a/.coveragerc +++ b/.coveragerc @@ -35,6 +35,7 @@ omit = homeassistant/components/light/hue.py homeassistant/components/media_player/cast.py homeassistant/components/media_player/mpd.py + homeassistant/components/media_player/squeezebox.py homeassistant/components/notify/file.py homeassistant/components/notify/instapush.py homeassistant/components/notify/nma.py diff --git a/homeassistant/components/media_player/squeezebox.py b/homeassistant/components/media_player/squeezebox.py index 95715b1e555..911f8b0faab 100644 --- a/homeassistant/components/media_player/squeezebox.py +++ b/homeassistant/components/media_player/squeezebox.py @@ -204,7 +204,8 @@ class SqueezeBoxDevice(MediaPlayerDevice): @property def is_volume_muted(self): if 'mixer volume' in self._status: - return int(self._status['mixer volume']) < 0 + _LOGGER.info(self._status['mixer volume']) + return self._status['mixer volume'].startswith('-') @property def media_content_id(self): From ac19ac8b83ba14ad11179e6f4fae499c79554adf Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 5 Aug 2015 21:20:28 -0700 Subject: [PATCH 052/224] Update frontend dependencies --- homeassistant/components/frontend/version.py | 2 +- .../frontend/www_static/frontend.html | 388 +++++++++++------- .../www_static/home-assistant-polymer | 2 +- .../www_static/webcomponents-lite.min.js | 7 +- 4 files changed, 245 insertions(+), 154 deletions(-) diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py index 6f30746f137..4e4be1af065 100644 --- a/homeassistant/components/frontend/version.py +++ b/homeassistant/components/frontend/version.py @@ -1,2 +1,2 @@ """ DO NOT MODIFY. Auto-generated by build_frontend script """ -VERSION = "ccfe7497d635ab4df3e6943b05adbd9b" +VERSION = "25837aadc266393928ddbc44bc806763" diff --git a/homeassistant/components/frontend/www_static/frontend.html b/homeassistant/components/frontend/www_static/frontend.html index c8cabd1b0a3..1589d181df5 100644 --- a/homeassistant/components/frontend/www_static/frontend.html +++ b/homeassistant/components/frontend/www_static/frontend.html @@ -1,6 +1,6 @@ - \ No newline at end of file + } \ No newline at end of file diff --git a/homeassistant/components/frontend/www_static/home-assistant-polymer b/homeassistant/components/frontend/www_static/home-assistant-polymer index 576c04efb49..0035b14304c 160000 --- a/homeassistant/components/frontend/www_static/home-assistant-polymer +++ b/homeassistant/components/frontend/www_static/home-assistant-polymer @@ -1 +1 @@ -Subproject commit 576c04efb49a8a5f7f35734458ffc93f874dd68d +Subproject commit 0035b14304c310e101c95671bd89b44e62513e7e diff --git a/homeassistant/components/frontend/www_static/webcomponents-lite.min.js b/homeassistant/components/frontend/www_static/webcomponents-lite.min.js index a5749ca2592..ec6063b7a58 100644 --- a/homeassistant/components/frontend/www_static/webcomponents-lite.min.js +++ b/homeassistant/components/frontend/www_static/webcomponents-lite.min.js @@ -7,6 +7,6 @@ * 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 */ -// @version 0.7.10 -window.WebComponents=window.WebComponents||{},function(e){var t=e.flags||{},n="webcomponents-lite.js",r=document.querySelector('script[src*="'+n+'"]');if(!t.noOpts){if(location.search.slice(1).split("&").forEach(function(e){var n,r=e.split("=");r[0]&&(n=r[0].match(/wc-(.+)/))&&(t[n[1]]=r[1]||!0)}),r)for(var o,i=0;o=r.attributes[i];i++)"src"!==o.name&&(t[o.name]=o.value||!0);if(t.log){var a=t.log.split(",");t.log={},a.forEach(function(e){t.log[e]=!0})}else t.log={}}t.shadow=t.shadow||t.shadowdom||t.polyfill,t.shadow="native"===t.shadow?!1:t.shadow||!HTMLElement.prototype.createShadowRoot,t.register&&(window.CustomElements=window.CustomElements||{flags:{}},window.CustomElements.flags.register=t.register),e.flags=t}(window.WebComponents),function(e){"use strict";function t(e){return void 0!==h[e]}function n(){s.call(this),this._isInvalid=!0}function r(e){return""==e&&n.call(this),e.toLowerCase()}function o(e){var t=e.charCodeAt(0);return t>32&&127>t&&-1==[34,35,60,62,63,96].indexOf(t)?e:encodeURIComponent(e)}function i(e){var t=e.charCodeAt(0);return t>32&&127>t&&-1==[34,35,60,62,96].indexOf(t)?e:encodeURIComponent(e)}function a(e,a,s){function c(e){g.push(e)}var d=a||"scheme start",u=0,l="",_=!1,w=!1,g=[];e:for(;(e[u-1]!=f||0==u)&&!this._isInvalid;){var b=e[u];switch(d){case"scheme start":if(!b||!m.test(b)){if(a){c("Invalid scheme.");break e}l="",d="no scheme";continue}l+=b.toLowerCase(),d="scheme";break;case"scheme":if(b&&v.test(b))l+=b.toLowerCase();else{if(":"!=b){if(a){if(f==b)break e;c("Code point not allowed in scheme: "+b);break e}l="",u=0,d="no scheme";continue}if(this._scheme=l,l="",a)break e;t(this._scheme)&&(this._isRelative=!0),d="file"==this._scheme?"relative":this._isRelative&&s&&s._scheme==this._scheme?"relative or authority":this._isRelative?"authority first slash":"scheme data"}break;case"scheme data":"?"==b?(this._query="?",d="query"):"#"==b?(this._fragment="#",d="fragment"):f!=b&&" "!=b&&"\n"!=b&&"\r"!=b&&(this._schemeData+=o(b));break;case"no scheme":if(s&&t(s._scheme)){d="relative";continue}c("Missing scheme."),n.call(this);break;case"relative or authority":if("/"!=b||"/"!=e[u+1]){c("Expected /, got: "+b),d="relative";continue}d="authority ignore slashes";break;case"relative":if(this._isRelative=!0,"file"!=this._scheme&&(this._scheme=s._scheme),f==b){this._host=s._host,this._port=s._port,this._path=s._path.slice(),this._query=s._query,this._username=s._username,this._password=s._password;break e}if("/"==b||"\\"==b)"\\"==b&&c("\\ is an invalid code point."),d="relative slash";else if("?"==b)this._host=s._host,this._port=s._port,this._path=s._path.slice(),this._query="?",this._username=s._username,this._password=s._password,d="query";else{if("#"!=b){var y=e[u+1],E=e[u+2];("file"!=this._scheme||!m.test(b)||":"!=y&&"|"!=y||f!=E&&"/"!=E&&"\\"!=E&&"?"!=E&&"#"!=E)&&(this._host=s._host,this._port=s._port,this._username=s._username,this._password=s._password,this._path=s._path.slice(),this._path.pop()),d="relative path";continue}this._host=s._host,this._port=s._port,this._path=s._path.slice(),this._query=s._query,this._fragment="#",this._username=s._username,this._password=s._password,d="fragment"}break;case"relative slash":if("/"!=b&&"\\"!=b){"file"!=this._scheme&&(this._host=s._host,this._port=s._port,this._username=s._username,this._password=s._password),d="relative path";continue}"\\"==b&&c("\\ is an invalid code point."),d="file"==this._scheme?"file host":"authority ignore slashes";break;case"authority first slash":if("/"!=b){c("Expected '/', got: "+b),d="authority ignore slashes";continue}d="authority second slash";break;case"authority second slash":if(d="authority ignore slashes","/"!=b){c("Expected '/', got: "+b);continue}break;case"authority ignore slashes":if("/"!=b&&"\\"!=b){d="authority";continue}c("Expected authority, got: "+b);break;case"authority":if("@"==b){_&&(c("@ already seen."),l+="%40"),_=!0;for(var L=0;L>>0)+(t++ +"__")};n.prototype={set:function(t,n){var r=t[this.name];return r&&r[0]===t?r[1]=n:e(t,this.name,{value:[t,n],writable:!0}),this},get:function(e){var t;return(t=e[this.name])&&t[0]===e?t[1]:void 0},"delete":function(e){var t=e[this.name];return t&&t[0]===e?(t[0]=t[1]=void 0,!0):!1},has:function(e){var t=e[this.name];return t?t[0]===e:!1}},window.WeakMap=n}(),function(e){function t(e){b.push(e),g||(g=!0,m(r))}function n(e){return window.ShadowDOMPolyfill&&window.ShadowDOMPolyfill.wrapIfNeeded(e)||e}function r(){g=!1;var e=b;b=[],e.sort(function(e,t){return e.uid_-t.uid_});var t=!1;e.forEach(function(e){var n=e.takeRecords();o(e),n.length&&(e.callback_(n,e),t=!0)}),t&&r()}function o(e){e.nodes_.forEach(function(t){var n=v.get(t);n&&n.forEach(function(t){t.observer===e&&t.removeTransientObservers()})})}function i(e,t){for(var n=e;n;n=n.parentNode){var r=v.get(n);if(r)for(var o=0;o0){var o=n[r-1],i=p(o,e);if(i)return void(n[r-1]=i)}else t(this.observer);n[r]=e},addListeners:function(){this.addListeners_(this.target)},addListeners_:function(e){var t=this.options;t.attributes&&e.addEventListener("DOMAttrModified",this,!0),t.characterData&&e.addEventListener("DOMCharacterDataModified",this,!0),t.childList&&e.addEventListener("DOMNodeInserted",this,!0),(t.childList||t.subtree)&&e.addEventListener("DOMNodeRemoved",this,!0)},removeListeners:function(){this.removeListeners_(this.target)},removeListeners_:function(e){var t=this.options;t.attributes&&e.removeEventListener("DOMAttrModified",this,!0),t.characterData&&e.removeEventListener("DOMCharacterDataModified",this,!0),t.childList&&e.removeEventListener("DOMNodeInserted",this,!0),(t.childList||t.subtree)&&e.removeEventListener("DOMNodeRemoved",this,!0)},addTransientObserver:function(e){if(e!==this.target){this.addListeners_(e),this.transientObservedNodes.push(e);var t=v.get(e);t||v.set(e,t=[]),t.push(this)}},removeTransientObservers:function(){var e=this.transientObservedNodes;this.transientObservedNodes=[],e.forEach(function(e){this.removeListeners_(e);for(var t=v.get(e),n=0;np&&(h=s[p]);p++)a(h)?(c++,n()):(h.addEventListener("load",r),h.addEventListener("error",i));else n()}function a(e){return l?e.__loaded||e["import"]&&"loading"!==e["import"].readyState:e.__importParsed}function s(e){for(var t,n=0,r=e.length;r>n&&(t=e[n]);n++)c(t)&&d(t)}function c(e){return"link"===e.localName&&"import"===e.rel}function d(e){var t=e["import"];t?o({target:e}):(e.addEventListener("load",o),e.addEventListener("error",o))}var u="import",l=Boolean(u in document.createElement("link")),h=Boolean(window.ShadowDOMPolyfill),p=function(e){return h?window.ShadowDOMPolyfill.wrapIfNeeded(e):e},f=p(document),m={get:function(){var e=window.HTMLImports.currentScript||document.currentScript||("complete"!==document.readyState?document.scripts[document.scripts.length-1]:null);return p(e)},configurable:!0};Object.defineProperty(document,"_currentScript",m),Object.defineProperty(f,"_currentScript",m);var v=/Trident/.test(navigator.userAgent),_=v?"complete":"interactive",w="readystatechange";l&&(new MutationObserver(function(e){for(var t,n=0,r=e.length;r>n&&(t=e[n]);n++)t.addedNodes&&s(t.addedNodes)}).observe(document.head,{childList:!0}),function(){if("loading"===document.readyState)for(var e,t=document.querySelectorAll("link[rel=import]"),n=0,r=t.length;r>n&&(e=t[n]);n++)d(e)}()),t(function(e){window.HTMLImports.ready=!0,window.HTMLImports.readyTime=(new Date).getTime();var t=f.createEvent("CustomEvent");t.initCustomEvent("HTMLImportsLoaded",!0,!0,e),f.dispatchEvent(t)}),e.IMPORT_LINK_TYPE=u,e.useNative=l,e.rootDocument=f,e.whenReady=t,e.isIE=v}(window.HTMLImports),function(e){var t=[],n=function(e){t.push(e)},r=function(){t.forEach(function(t){t(e)})};e.addModule=n,e.initializeModules=r}(window.HTMLImports),window.HTMLImports.addModule(function(e){var t=/(url\()([^)]*)(\))/g,n=/(@import[\s]+(?!url\())([^;]*)(;)/g,r={resolveUrlsInStyle:function(e,t){var n=e.ownerDocument,r=n.createElement("a");return e.textContent=this.resolveUrlsInCssText(e.textContent,t,r),e},resolveUrlsInCssText:function(e,r,o){var i=this.replaceUrls(e,o,r,t);return i=this.replaceUrls(i,o,r,n)},replaceUrls:function(e,t,n,r){return e.replace(r,function(e,r,o,i){var a=o.replace(/["']/g,"");return n&&(a=new URL(a,n).href),t.href=a,a=t.href,r+"'"+a+"'"+i})}};e.path=r}),window.HTMLImports.addModule(function(e){var t={async:!0,ok:function(e){return e.status>=200&&e.status<300||304===e.status||0===e.status},load:function(n,r,o){var i=new XMLHttpRequest;return(e.flags.debug||e.flags.bust)&&(n+="?"+Math.random()),i.open("GET",n,t.async),i.addEventListener("readystatechange",function(e){if(4===i.readyState){var n=i.getResponseHeader("Location"),a=null;if(n)var a="/"===n.substr(0,1)?location.origin+n:n;r.call(o,!t.ok(i)&&i,i.response||i.responseText,a)}}),i.send(),i},loadDocument:function(e,t,n){this.load(e,t,n).responseType="document"}};e.xhr=t}),window.HTMLImports.addModule(function(e){var t=e.xhr,n=e.flags,r=function(e,t){this.cache={},this.onload=e,this.oncomplete=t,this.inflight=0,this.pending={}};r.prototype={addNodes:function(e){this.inflight+=e.length;for(var t,n=0,r=e.length;r>n&&(t=e[n]);n++)this.require(t);this.checkDone()},addNode:function(e){this.inflight++,this.require(e),this.checkDone()},require:function(e){var t=e.src||e.href;e.__nodeUrl=t,this.dedupe(t,e)||this.fetch(t,e)},dedupe:function(e,t){if(this.pending[e])return this.pending[e].push(t),!0;return this.cache[e]?(this.onload(e,t,this.cache[e]),this.tail(),!0):(this.pending[e]=[t],!1)},fetch:function(e,r){if(n.load&&console.log("fetch",e,r),e)if(e.match(/^data:/)){var o=e.split(","),i=o[0],a=o[1];a=i.indexOf(";base64")>-1?atob(a):decodeURIComponent(a),setTimeout(function(){this.receive(e,r,null,a)}.bind(this),0)}else{var s=function(t,n,o){this.receive(e,r,t,n,o)}.bind(this);t.load(e,s)}else setTimeout(function(){this.receive(e,r,{error:"href must be specified"},null)}.bind(this),0)},receive:function(e,t,n,r,o){this.cache[e]=r;for(var i,a=this.pending[e],s=0,c=a.length;c>s&&(i=a[s]);s++)this.onload(e,i,r,n,o),this.tail();this.pending[e]=null},tail:function(){--this.inflight,this.checkDone()},checkDone:function(){this.inflight||this.oncomplete()}},e.Loader=r}),window.HTMLImports.addModule(function(e){var t=function(e){this.addCallback=e,this.mo=new MutationObserver(this.handler.bind(this))};t.prototype={handler:function(e){for(var t,n=0,r=e.length;r>n&&(t=e[n]);n++)"childList"===t.type&&t.addedNodes.length&&this.addedNodes(t.addedNodes)},addedNodes:function(e){this.addCallback&&this.addCallback(e);for(var t,n=0,r=e.length;r>n&&(t=e[n]);n++)t.children&&t.children.length&&this.addedNodes(t.children)},observe:function(e){this.mo.observe(e,{childList:!0,subtree:!0})}},e.Observer=t}),window.HTMLImports.addModule(function(e){function t(e){return"link"===e.localName&&e.rel===u}function n(e){var t=r(e);return"data:text/javascript;charset=utf-8,"+encodeURIComponent(t)}function r(e){return e.textContent+o(e)}function o(e){var t=e.ownerDocument;t.__importedScripts=t.__importedScripts||0;var n=e.ownerDocument.baseURI,r=t.__importedScripts?"-"+t.__importedScripts:"";return t.__importedScripts++,"\n//# sourceURL="+n+r+".js\n"}function i(e){var t=e.ownerDocument.createElement("style");return t.textContent=e.textContent,a.resolveUrlsInStyle(t),t}var a=e.path,s=e.rootDocument,c=e.flags,d=e.isIE,u=e.IMPORT_LINK_TYPE,l="link[rel="+u+"]",h={documentSelectors:l,importsSelectors:[l,"link[rel=stylesheet]:not([type])","style:not([type])","script:not([type])",'script[type="application/javascript"]','script[type="text/javascript"]'].join(","),map:{link:"parseLink",script:"parseScript",style:"parseStyle"},dynamicElements:[],parseNext:function(){var e=this.nextToParse();e&&this.parse(e)},parse:function(e){if(this.isParsed(e))return void(c.parse&&console.log("[%s] is already parsed",e.localName));var t=this[this.map[e.localName]];t&&(this.markParsing(e),t.call(this,e))},parseDynamic:function(e,t){this.dynamicElements.push(e),t||this.parseNext()},markParsing:function(e){c.parse&&console.log("parsing",e),this.parsingElement=e},markParsingComplete:function(e){e.__importParsed=!0,this.markDynamicParsingComplete(e),e.__importElement&&(e.__importElement.__importParsed=!0,this.markDynamicParsingComplete(e.__importElement)),this.parsingElement=null,c.parse&&console.log("completed",e)},markDynamicParsingComplete:function(e){var t=this.dynamicElements.indexOf(e);t>=0&&this.dynamicElements.splice(t,1)},parseImport:function(e){if(e["import"]=e.__doc,window.HTMLImports.__importsParsingHook&&window.HTMLImports.__importsParsingHook(e),e["import"]&&(e["import"].__importParsed=!0),this.markParsingComplete(e),e.dispatchEvent(e.__resource&&!e.__error?new CustomEvent("load",{bubbles:!1}):new CustomEvent("error",{bubbles:!1})),e.__pending)for(var t;e.__pending.length;)t=e.__pending.shift(),t&&t({target:e});this.parseNext()},parseLink:function(e){t(e)?this.parseImport(e):(e.href=e.href,this.parseGeneric(e))},parseStyle:function(e){var t=e;e=i(e),t.__appliedElement=e,e.__importElement=t,this.parseGeneric(e)},parseGeneric:function(e){this.trackElement(e),this.addElementToDocument(e)},rootImportForElement:function(e){for(var t=e;t.ownerDocument.__importLink;)t=t.ownerDocument.__importLink;return t},addElementToDocument:function(e){var t=this.rootImportForElement(e.__importElement||e);t.parentNode.insertBefore(e,t)},trackElement:function(e,t){var n=this,r=function(o){e.removeEventListener("load",r),e.removeEventListener("error",r),t&&t(o),n.markParsingComplete(e),n.parseNext()};if(e.addEventListener("load",r),e.addEventListener("error",r),d&&"style"===e.localName){var o=!1;if(-1==e.textContent.indexOf("@import"))o=!0;else if(e.sheet){o=!0;for(var i,a=e.sheet.cssRules,s=a?a.length:0,c=0;s>c&&(i=a[c]);c++)i.type===CSSRule.IMPORT_RULE&&(o=o&&Boolean(i.styleSheet))}o&&setTimeout(function(){e.dispatchEvent(new CustomEvent("load",{bubbles:!1}))})}},parseScript:function(t){var r=document.createElement("script");r.__importElement=t,r.src=t.src?t.src:n(t),e.currentScript=t,this.trackElement(r,function(t){r.parentNode&&r.parentNode.removeChild(r),e.currentScript=null}),this.addElementToDocument(r)},nextToParse:function(){return this._mayParse=[],!this.parsingElement&&(this.nextToParseInDoc(s)||this.nextToParseDynamic())},nextToParseInDoc:function(e,n){if(e&&this._mayParse.indexOf(e)<0){this._mayParse.push(e);for(var r,o=e.querySelectorAll(this.parseSelectorsForNode(e)),i=0,a=o.length;a>i&&(r=o[i]);i++)if(!this.isParsed(r))return this.hasResource(r)?t(r)?this.nextToParseInDoc(r.__doc,r):r:void 0}return n},nextToParseDynamic:function(){return this.dynamicElements[0]},parseSelectorsForNode:function(e){var t=e.ownerDocument||e;return t===s?this.documentSelectors:this.importsSelectors},isParsed:function(e){return e.__importParsed},needsDynamicParsing:function(e){return this.dynamicElements.indexOf(e)>=0},hasResource:function(e){return t(e)&&void 0===e.__doc?!1:!0}};e.parser=h,e.IMPORT_SELECTOR=l}),window.HTMLImports.addModule(function(e){function t(e){return n(e,a)}function n(e,t){return"link"===e.localName&&e.getAttribute("rel")===t}function r(e){return!!Object.getOwnPropertyDescriptor(e,"baseURI")}function o(e,t){var n=document.implementation.createHTMLDocument(a);n._URL=t;var o=n.createElement("base");o.setAttribute("href",t),n.baseURI||r(n)||Object.defineProperty(n,"baseURI",{value:t});var i=n.createElement("meta");return i.setAttribute("charset","utf-8"),n.head.appendChild(i),n.head.appendChild(o),n.body.innerHTML=e,window.HTMLTemplateElement&&HTMLTemplateElement.bootstrap&&HTMLTemplateElement.bootstrap(n),n}var i=e.flags,a=e.IMPORT_LINK_TYPE,s=e.IMPORT_SELECTOR,c=e.rootDocument,d=e.Loader,u=e.Observer,l=e.parser,h={documents:{},documentPreloadSelectors:s,importsPreloadSelectors:[s].join(","),loadNode:function(e){p.addNode(e)},loadSubtree:function(e){var t=this.marshalNodes(e);p.addNodes(t)},marshalNodes:function(e){return e.querySelectorAll(this.loadSelectorsForNode(e))},loadSelectorsForNode:function(e){var t=e.ownerDocument||e;return t===c?this.documentPreloadSelectors:this.importsPreloadSelectors},loaded:function(e,n,r,a,s){if(i.load&&console.log("loaded",e,n),n.__resource=r,n.__error=a,t(n)){var c=this.documents[e];void 0===c&&(c=a?null:o(r,s||e),c&&(c.__importLink=n,this.bootDocument(c)),this.documents[e]=c),n.__doc=c}l.parseNext()},bootDocument:function(e){this.loadSubtree(e),this.observer.observe(e),l.parseNext()},loadedAll:function(){l.parseNext()}},p=new d(h.loaded.bind(h),h.loadedAll.bind(h));if(h.observer=new u,!document.baseURI){var f={get:function(){var e=document.querySelector("base");return e?e.href:window.location.href},configurable:!0};Object.defineProperty(document,"baseURI",f),Object.defineProperty(c,"baseURI",f)}e.importer=h,e.importLoader=p}),window.HTMLImports.addModule(function(e){var t=e.parser,n=e.importer,r={added:function(e){for(var r,o,i,a,s=0,c=e.length;c>s&&(a=e[s]);s++)r||(r=a.ownerDocument,o=t.isParsed(r)),i=this.shouldLoadNode(a),i&&n.loadNode(a),this.shouldParseNode(a)&&o&&t.parseDynamic(a,i)},shouldLoadNode:function(e){return 1===e.nodeType&&o.call(e,n.loadSelectorsForNode(e))},shouldParseNode:function(e){return 1===e.nodeType&&o.call(e,t.parseSelectorsForNode(e))}};n.observer.addCallback=r.added.bind(r);var o=HTMLElement.prototype.matches||HTMLElement.prototype.matchesSelector||HTMLElement.prototype.webkitMatchesSelector||HTMLElement.prototype.mozMatchesSelector||HTMLElement.prototype.msMatchesSelector}),function(e){function t(){window.HTMLImports.importer.bootDocument(o)}var n=e.initializeModules,r=e.isIE;if(!e.useNative){r&&"function"!=typeof window.CustomEvent&&(window.CustomEvent=function(e,t){t=t||{};var n=document.createEvent("CustomEvent");return n.initCustomEvent(e,Boolean(t.bubbles),Boolean(t.cancelable),t.detail),n.preventDefault=function(){Object.defineProperty(this,"defaultPrevented",{get:function(){return!0}})},n},window.CustomEvent.prototype=window.Event.prototype),n();var o=e.rootDocument;"complete"===document.readyState||"interactive"===document.readyState&&!window.attachEvent?t():document.addEventListener("DOMContentLoaded",t)}}(window.HTMLImports),window.CustomElements=window.CustomElements||{flags:{}},function(e){var t=e.flags,n=[],r=function(e){n.push(e)},o=function(){n.forEach(function(t){t(e)})};e.addModule=r,e.initializeModules=o,e.hasNative=Boolean(document.registerElement),e.useNative=!t.register&&e.hasNative&&!window.ShadowDOMPolyfill&&(!window.HTMLImports||window.HTMLImports.useNative)}(window.CustomElements),window.CustomElements.addModule(function(e){function t(e,t){n(e,function(e){return t(e)?!0:void r(e,t)}),r(e,t)}function n(e,t,r){var o=e.firstElementChild;if(!o)for(o=e.firstChild;o&&o.nodeType!==Node.ELEMENT_NODE;)o=o.nextSibling;for(;o;)t(o,r)!==!0&&n(o,t,r),o=o.nextElementSibling;return null}function r(e,n){for(var r=e.shadowRoot;r;)t(r,n),r=r.olderShadowRoot}function o(e,t){i(e,t,[])}function i(e,t,n){if(e=window.wrap(e),!(n.indexOf(e)>=0)){n.push(e);for(var r,o=e.querySelectorAll("link[rel="+a+"]"),s=0,c=o.length;c>s&&(r=o[s]);s++)r["import"]&&i(r["import"],t,n);t(e)}}var a=window.HTMLImports?window.HTMLImports.IMPORT_LINK_TYPE:"none";e.forDocumentTree=o,e.forSubtree=t}),window.CustomElements.addModule(function(e){function t(e,t){return n(e,t)||r(e,t)}function n(t,n){return e.upgrade(t,n)?!0:void(n&&a(t))}function r(e,t){g(e,function(e){return n(e,t)?!0:void 0})}function o(e){L.push(e),E||(E=!0,setTimeout(i))}function i(){E=!1;for(var e,t=L,n=0,r=t.length;r>n&&(e=t[n]);n++)e();L=[]}function a(e){y?o(function(){s(e)}):s(e)}function s(e){e.__upgraded__&&!e.__attached&&(e.__attached=!0,e.attachedCallback&&e.attachedCallback())}function c(e){d(e),g(e,function(e){d(e)})}function d(e){y?o(function(){u(e)}):u(e)}function u(e){e.__upgraded__&&e.__attached&&(e.__attached=!1,e.detachedCallback&&e.detachedCallback())}function l(e){for(var t=e,n=window.wrap(document);t;){if(t==n)return!0;t=t.parentNode||t.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&t.host}}function h(e){if(e.shadowRoot&&!e.shadowRoot.__watched){w.dom&&console.log("watching shadow-root for: ",e.localName);for(var t=e.shadowRoot;t;)m(t),t=t.olderShadowRoot}}function p(e,n){if(w.dom){var r=n[0];if(r&&"childList"===r.type&&r.addedNodes&&r.addedNodes){for(var o=r.addedNodes[0];o&&o!==document&&!o.host;)o=o.parentNode;var i=o&&(o.URL||o._URL||o.host&&o.host.localName)||"";i=i.split("/?").shift().split("/").pop()}console.group("mutations (%d) [%s]",n.length,i||"")}var a=l(e);n.forEach(function(e){"childList"===e.type&&(M(e.addedNodes,function(e){e.localName&&t(e,a)}),M(e.removedNodes,function(e){e.localName&&c(e)}))}),w.dom&&console.groupEnd()}function f(e){for(e=window.wrap(e),e||(e=window.wrap(document));e.parentNode;)e=e.parentNode;var t=e.__observer;t&&(p(e,t.takeRecords()),i())}function m(e){if(!e.__observer){var t=new MutationObserver(p.bind(this,e));t.observe(e,{childList:!0,subtree:!0}),e.__observer=t}}function v(e){e=window.wrap(e),w.dom&&console.group("upgradeDocument: ",e.baseURI.split("/").pop());var n=e===window.wrap(document);t(e,n),m(e),w.dom&&console.groupEnd()}function _(e){b(e,v)}var w=e.flags,g=e.forSubtree,b=e.forDocumentTree,y=!window.MutationObserver||window.MutationObserver===window.JsMutationObserver;e.hasPolyfillMutations=y;var E=!1,L=[],M=Array.prototype.forEach.call.bind(Array.prototype.forEach),T=Element.prototype.createShadowRoot;T&&(Element.prototype.createShadowRoot=function(){var e=T.call(this);return window.CustomElements.watchShadow(this),e}),e.watchShadow=h,e.upgradeDocumentTree=_,e.upgradeDocument=v,e.upgradeSubtree=r,e.upgradeAll=t,e.attached=a,e.takeRecords=f}),window.CustomElements.addModule(function(e){function t(t,r){if(!t.__upgraded__&&t.nodeType===Node.ELEMENT_NODE){var o=t.getAttribute("is"),i=e.getRegisteredDefinition(t.localName)||e.getRegisteredDefinition(o);if(i&&(o&&i.tag==t.localName||!o&&!i["extends"]))return n(t,i,r)}}function n(t,n,o){return a.upgrade&&console.group("upgrade:",t.localName),n.is&&t.setAttribute("is",n.is),r(t,n),t.__upgraded__=!0,i(t),o&&e.attached(t),e.upgradeSubtree(t,o),a.upgrade&&console.groupEnd(),t}function r(e,t){Object.__proto__?e.__proto__=t.prototype:(o(e,t.prototype,t["native"]),e.__proto__=t.prototype)}function o(e,t,n){for(var r={},o=t;o!==n&&o!==HTMLElement.prototype;){for(var i,a=Object.getOwnPropertyNames(o),s=0;i=a[s];s++)r[i]||(Object.defineProperty(e,i,Object.getOwnPropertyDescriptor(o,i)),r[i]=1);o=Object.getPrototypeOf(o)}}function i(e){e.createdCallback&&e.createdCallback()}var a=e.flags;e.upgrade=t,e.upgradeWithDefinition=n,e.implementPrototype=r}),window.CustomElements.addModule(function(e){function t(t,r){var c=r||{};if(!t)throw new Error("document.registerElement: first argument `name` must not be empty");if(t.indexOf("-")<0)throw new Error("document.registerElement: first argument ('name') must contain a dash ('-'). Argument provided was '"+String(t)+"'.");if(o(t))throw new Error("Failed to execute 'registerElement' on 'Document': Registration failed for type '"+String(t)+"'. The type name is invalid.");if(d(t))throw new Error("DuplicateDefinitionError: a type with name '"+String(t)+"' is already registered");return c.prototype||(c.prototype=Object.create(HTMLElement.prototype)),c.__name=t.toLowerCase(),c.lifecycle=c.lifecycle||{},c.ancestry=i(c["extends"]),a(c),s(c),n(c.prototype),u(c.__name,c),c.ctor=l(c),c.ctor.prototype=c.prototype,c.prototype.constructor=c.ctor,e.ready&&_(document),c.ctor}function n(e){if(!e.setAttribute._polyfilled){var t=e.setAttribute;e.setAttribute=function(e,n){ -r.call(this,e,n,t)};var n=e.removeAttribute;e.removeAttribute=function(e){r.call(this,e,null,n)},e.setAttribute._polyfilled=!0}}function r(e,t,n){e=e.toLowerCase();var r=this.getAttribute(e);n.apply(this,arguments);var o=this.getAttribute(e);this.attributeChangedCallback&&o!==r&&this.attributeChangedCallback(e,r,o)}function o(e){for(var t=0;t=0&&b(r,HTMLElement),r)}function f(e,t){var n=e[t];e[t]=function(){var e=n.apply(this,arguments);return w(e),e}}var m,v=e.isIE11OrOlder,_=e.upgradeDocumentTree,w=e.upgradeAll,g=e.upgradeWithDefinition,b=e.implementPrototype,y=e.useNative,E=["annotation-xml","color-profile","font-face","font-face-src","font-face-uri","font-face-format","font-face-name","missing-glyph"],L={},M="http://www.w3.org/1999/xhtml",T=document.createElement.bind(document),O=document.createElementNS.bind(document);m=Object.__proto__||y?function(e,t){return e instanceof t}:function(e,t){if(e instanceof t)return!0;for(var n=e;n;){if(n===t.prototype)return!0;n=n.__proto__}return!1},f(Node.prototype,"cloneNode"),f(document,"importNode"),v&&!function(){var e=document.importNode;document.importNode=function(){var t=e.apply(document,arguments);if(t.nodeType==t.DOCUMENT_FRAGMENT_NODE){var n=document.createDocumentFragment();return n.appendChild(t),n}return t}}(),document.registerElement=t,document.createElement=p,document.createElementNS=h,e.registry=L,e["instanceof"]=m,e.reservedTagList=E,e.getRegisteredDefinition=d,document.register=document.registerElement}),function(e){function t(){a(window.wrap(document)),window.CustomElements.ready=!0,requestAnimationFrame(function(){setTimeout(function(){window.CustomElements.readyTime=Date.now(),window.HTMLImports&&(window.CustomElements.elapsed=window.CustomElements.readyTime-window.HTMLImports.readyTime),document.dispatchEvent(new CustomEvent("WebComponentsReady",{bubbles:!0}))})})}var n=e.useNative,r=e.initializeModules,o=/Trident/.test(navigator.userAgent);if(n){var i=function(){};e.watchShadow=i,e.upgrade=i,e.upgradeAll=i,e.upgradeDocumentTree=i,e.upgradeSubtree=i,e.takeRecords=i,e["instanceof"]=function(e,t){return e instanceof t}}else r();var a=e.upgradeDocumentTree,s=e.upgradeDocument;if(window.wrap||(window.ShadowDOMPolyfill?(window.wrap=window.ShadowDOMPolyfill.wrapIfNeeded,window.unwrap=window.ShadowDOMPolyfill.unwrapIfNeeded):window.wrap=window.unwrap=function(e){return e}),window.HTMLImports&&(window.HTMLImports.__importsParsingHook=function(e){e["import"]&&s(wrap(e["import"]))}),o&&"function"!=typeof window.CustomEvent&&(window.CustomEvent=function(e,t){t=t||{};var n=document.createEvent("CustomEvent");return n.initCustomEvent(e,Boolean(t.bubbles),Boolean(t.cancelable),t.detail),n.preventDefault=function(){Object.defineProperty(this,"defaultPrevented",{get:function(){return!0}})},n},window.CustomEvent.prototype=window.Event.prototype),"complete"===document.readyState||e.flags.eager)t();else if("interactive"!==document.readyState||window.attachEvent||window.HTMLImports&&!window.HTMLImports.ready){var c=window.HTMLImports&&!window.HTMLImports.ready?"HTMLImportsLoaded":"DOMContentLoaded";window.addEventListener(c,t)}else t();e.isIE11OrOlder=o}(window.CustomElements),"undefined"==typeof HTMLTemplateElement&&!function(){function e(e){switch(e){case"&":return"&";case"<":return"<";case">":return">";case" ":return" "}}function t(t){return t.replace(a,e)}var n="template",r=document.implementation.createHTMLDocument("template"),o=!0;HTMLTemplateElement=function(){},HTMLTemplateElement.prototype=Object.create(HTMLElement.prototype),HTMLTemplateElement.decorate=function(e){e.content||(e.content=e.ownerDocument.createDocumentFragment());for(var n;n=e.firstChild;)e.content.appendChild(n);if(o)try{Object.defineProperty(e,"innerHTML",{get:function(){for(var e="",n=this.content.firstChild;n;n=n.nextSibling)e+=n.outerHTML||t(n.data);return e},set:function(e){for(r.body.innerHTML=e;this.content.firstChild;)this.content.removeChild(this.content.firstChild);for(;r.body.firstChild;)this.content.appendChild(r.body.firstChild)},configurable:!0})}catch(i){o=!1}},HTMLTemplateElement.bootstrap=function(e){for(var t,r=e.querySelectorAll(n),o=0,i=r.length;i>o&&(t=r[o]);o++)HTMLTemplateElement.decorate(t)},window.addEventListener("DOMContentLoaded",function(){HTMLTemplateElement.bootstrap(document)});var i=document.createElement;document.createElement=function(){"use strict";var e=i.apply(document,arguments);return"template"==e.localName&&HTMLTemplateElement.decorate(e),e};var a=/[&\u00A0<>]/g}(),function(e){var t=document.createElement("style");t.textContent="body {transition: opacity ease-in 0.2s; } \nbody[unresolved] {opacity: 0; display: block; overflow: hidden; position: relative; } \n";var n=document.querySelector("head");n.insertBefore(t,n.firstChild)}(window.WebComponents); \ No newline at end of file +// @version 0.7.12 +window.WebComponents=window.WebComponents||{},function(e){var t=e.flags||{},n="webcomponents-lite.js",r=document.querySelector('script[src*="'+n+'"]');if(!t.noOpts){if(location.search.slice(1).split("&").forEach(function(e){var n,r=e.split("=");r[0]&&(n=r[0].match(/wc-(.+)/))&&(t[n[1]]=r[1]||!0)}),r)for(var o,i=0;o=r.attributes[i];i++)"src"!==o.name&&(t[o.name]=o.value||!0);if(t.log){var a=t.log.split(",");t.log={},a.forEach(function(e){t.log[e]=!0})}else t.log={}}t.shadow=t.shadow||t.shadowdom||t.polyfill,t.shadow="native"===t.shadow?!1:t.shadow||!HTMLElement.prototype.createShadowRoot,t.register&&(window.CustomElements=window.CustomElements||{flags:{}},window.CustomElements.flags.register=t.register),e.flags=t}(window.WebComponents),function(e){"use strict";function t(e){return void 0!==h[e]}function n(){s.call(this),this._isInvalid=!0}function r(e){return""==e&&n.call(this),e.toLowerCase()}function o(e){var t=e.charCodeAt(0);return t>32&&127>t&&-1==[34,35,60,62,63,96].indexOf(t)?e:encodeURIComponent(e)}function i(e){var t=e.charCodeAt(0);return t>32&&127>t&&-1==[34,35,60,62,96].indexOf(t)?e:encodeURIComponent(e)}function a(e,a,s){function c(e){g.push(e)}var d=a||"scheme start",u=0,l="",_=!1,w=!1,g=[];e:for(;(e[u-1]!=f||0==u)&&!this._isInvalid;){var b=e[u];switch(d){case"scheme start":if(!b||!m.test(b)){if(a){c("Invalid scheme.");break e}l="",d="no scheme";continue}l+=b.toLowerCase(),d="scheme";break;case"scheme":if(b&&v.test(b))l+=b.toLowerCase();else{if(":"!=b){if(a){if(f==b)break e;c("Code point not allowed in scheme: "+b);break e}l="",u=0,d="no scheme";continue}if(this._scheme=l,l="",a)break e;t(this._scheme)&&(this._isRelative=!0),d="file"==this._scheme?"relative":this._isRelative&&s&&s._scheme==this._scheme?"relative or authority":this._isRelative?"authority first slash":"scheme data"}break;case"scheme data":"?"==b?(this._query="?",d="query"):"#"==b?(this._fragment="#",d="fragment"):f!=b&&" "!=b&&"\n"!=b&&"\r"!=b&&(this._schemeData+=o(b));break;case"no scheme":if(s&&t(s._scheme)){d="relative";continue}c("Missing scheme."),n.call(this);break;case"relative or authority":if("/"!=b||"/"!=e[u+1]){c("Expected /, got: "+b),d="relative";continue}d="authority ignore slashes";break;case"relative":if(this._isRelative=!0,"file"!=this._scheme&&(this._scheme=s._scheme),f==b){this._host=s._host,this._port=s._port,this._path=s._path.slice(),this._query=s._query,this._username=s._username,this._password=s._password;break e}if("/"==b||"\\"==b)"\\"==b&&c("\\ is an invalid code point."),d="relative slash";else if("?"==b)this._host=s._host,this._port=s._port,this._path=s._path.slice(),this._query="?",this._username=s._username,this._password=s._password,d="query";else{if("#"!=b){var y=e[u+1],E=e[u+2];("file"!=this._scheme||!m.test(b)||":"!=y&&"|"!=y||f!=E&&"/"!=E&&"\\"!=E&&"?"!=E&&"#"!=E)&&(this._host=s._host,this._port=s._port,this._username=s._username,this._password=s._password,this._path=s._path.slice(),this._path.pop()),d="relative path";continue}this._host=s._host,this._port=s._port,this._path=s._path.slice(),this._query=s._query,this._fragment="#",this._username=s._username,this._password=s._password,d="fragment"}break;case"relative slash":if("/"!=b&&"\\"!=b){"file"!=this._scheme&&(this._host=s._host,this._port=s._port,this._username=s._username,this._password=s._password),d="relative path";continue}"\\"==b&&c("\\ is an invalid code point."),d="file"==this._scheme?"file host":"authority ignore slashes";break;case"authority first slash":if("/"!=b){c("Expected '/', got: "+b),d="authority ignore slashes";continue}d="authority second slash";break;case"authority second slash":if(d="authority ignore slashes","/"!=b){c("Expected '/', got: "+b);continue}break;case"authority ignore slashes":if("/"!=b&&"\\"!=b){d="authority";continue}c("Expected authority, got: "+b);break;case"authority":if("@"==b){_&&(c("@ already seen."),l+="%40"),_=!0;for(var L=0;L>>0)+(t++ +"__")};n.prototype={set:function(t,n){var r=t[this.name];return r&&r[0]===t?r[1]=n:e(t,this.name,{value:[t,n],writable:!0}),this},get:function(e){var t;return(t=e[this.name])&&t[0]===e?t[1]:void 0},"delete":function(e){var t=e[this.name];return t&&t[0]===e?(t[0]=t[1]=void 0,!0):!1},has:function(e){var t=e[this.name];return t?t[0]===e:!1}},window.WeakMap=n}(),function(e){function t(e){b.push(e),g||(g=!0,m(r))}function n(e){return window.ShadowDOMPolyfill&&window.ShadowDOMPolyfill.wrapIfNeeded(e)||e}function r(){g=!1;var e=b;b=[],e.sort(function(e,t){return e.uid_-t.uid_});var t=!1;e.forEach(function(e){var n=e.takeRecords();o(e),n.length&&(e.callback_(n,e),t=!0)}),t&&r()}function o(e){e.nodes_.forEach(function(t){var n=v.get(t);n&&n.forEach(function(t){t.observer===e&&t.removeTransientObservers()})})}function i(e,t){for(var n=e;n;n=n.parentNode){var r=v.get(n);if(r)for(var o=0;o0){var o=n[r-1],i=p(o,e);if(i)return void(n[r-1]=i)}else t(this.observer);n[r]=e},addListeners:function(){this.addListeners_(this.target)},addListeners_:function(e){var t=this.options;t.attributes&&e.addEventListener("DOMAttrModified",this,!0),t.characterData&&e.addEventListener("DOMCharacterDataModified",this,!0),t.childList&&e.addEventListener("DOMNodeInserted",this,!0),(t.childList||t.subtree)&&e.addEventListener("DOMNodeRemoved",this,!0)},removeListeners:function(){this.removeListeners_(this.target)},removeListeners_:function(e){var t=this.options;t.attributes&&e.removeEventListener("DOMAttrModified",this,!0),t.characterData&&e.removeEventListener("DOMCharacterDataModified",this,!0),t.childList&&e.removeEventListener("DOMNodeInserted",this,!0),(t.childList||t.subtree)&&e.removeEventListener("DOMNodeRemoved",this,!0)},addTransientObserver:function(e){if(e!==this.target){this.addListeners_(e),this.transientObservedNodes.push(e);var t=v.get(e);t||v.set(e,t=[]),t.push(this)}},removeTransientObservers:function(){var e=this.transientObservedNodes;this.transientObservedNodes=[],e.forEach(function(e){this.removeListeners_(e);for(var t=v.get(e),n=0;np&&(h=s[p]);p++)a(h)?(c++,n()):(h.addEventListener("load",r),h.addEventListener("error",i));else n()}function a(e){return l?e.__loaded||e["import"]&&"loading"!==e["import"].readyState:e.__importParsed}function s(e){for(var t,n=0,r=e.length;r>n&&(t=e[n]);n++)c(t)&&d(t)}function c(e){return"link"===e.localName&&"import"===e.rel}function d(e){var t=e["import"];t?o({target:e}):(e.addEventListener("load",o),e.addEventListener("error",o))}var u="import",l=Boolean(u in document.createElement("link")),h=Boolean(window.ShadowDOMPolyfill),p=function(e){return h?window.ShadowDOMPolyfill.wrapIfNeeded(e):e},f=p(document),m={get:function(){var e=window.HTMLImports.currentScript||document.currentScript||("complete"!==document.readyState?document.scripts[document.scripts.length-1]:null);return p(e)},configurable:!0};Object.defineProperty(document,"_currentScript",m),Object.defineProperty(f,"_currentScript",m);var v=/Trident/.test(navigator.userAgent),_=v?"complete":"interactive",w="readystatechange";l&&(new MutationObserver(function(e){for(var t,n=0,r=e.length;r>n&&(t=e[n]);n++)t.addedNodes&&s(t.addedNodes)}).observe(document.head,{childList:!0}),function(){if("loading"===document.readyState)for(var e,t=document.querySelectorAll("link[rel=import]"),n=0,r=t.length;r>n&&(e=t[n]);n++)d(e)}()),t(function(e){window.HTMLImports.ready=!0,window.HTMLImports.readyTime=(new Date).getTime();var t=f.createEvent("CustomEvent");t.initCustomEvent("HTMLImportsLoaded",!0,!0,e),f.dispatchEvent(t)}),e.IMPORT_LINK_TYPE=u,e.useNative=l,e.rootDocument=f,e.whenReady=t,e.isIE=v}(window.HTMLImports),function(e){var t=[],n=function(e){t.push(e)},r=function(){t.forEach(function(t){t(e)})};e.addModule=n,e.initializeModules=r}(window.HTMLImports),window.HTMLImports.addModule(function(e){var t=/(url\()([^)]*)(\))/g,n=/(@import[\s]+(?!url\())([^;]*)(;)/g,r={resolveUrlsInStyle:function(e,t){var n=e.ownerDocument,r=n.createElement("a");return e.textContent=this.resolveUrlsInCssText(e.textContent,t,r),e},resolveUrlsInCssText:function(e,r,o){var i=this.replaceUrls(e,o,r,t);return i=this.replaceUrls(i,o,r,n)},replaceUrls:function(e,t,n,r){return e.replace(r,function(e,r,o,i){var a=o.replace(/["']/g,"");return n&&(a=new URL(a,n).href),t.href=a,a=t.href,r+"'"+a+"'"+i})}};e.path=r}),window.HTMLImports.addModule(function(e){var t={async:!0,ok:function(e){return e.status>=200&&e.status<300||304===e.status||0===e.status},load:function(n,r,o){var i=new XMLHttpRequest;return(e.flags.debug||e.flags.bust)&&(n+="?"+Math.random()),i.open("GET",n,t.async),i.addEventListener("readystatechange",function(e){if(4===i.readyState){var n=i.getResponseHeader("Location"),a=null;if(n)var a="/"===n.substr(0,1)?location.origin+n:n;r.call(o,!t.ok(i)&&i,i.response||i.responseText,a)}}),i.send(),i},loadDocument:function(e,t,n){this.load(e,t,n).responseType="document"}};e.xhr=t}),window.HTMLImports.addModule(function(e){var t=e.xhr,n=e.flags,r=function(e,t){this.cache={},this.onload=e,this.oncomplete=t,this.inflight=0,this.pending={}};r.prototype={addNodes:function(e){this.inflight+=e.length;for(var t,n=0,r=e.length;r>n&&(t=e[n]);n++)this.require(t);this.checkDone()},addNode:function(e){this.inflight++,this.require(e),this.checkDone()},require:function(e){var t=e.src||e.href;e.__nodeUrl=t,this.dedupe(t,e)||this.fetch(t,e)},dedupe:function(e,t){if(this.pending[e])return this.pending[e].push(t),!0;return this.cache[e]?(this.onload(e,t,this.cache[e]),this.tail(),!0):(this.pending[e]=[t],!1)},fetch:function(e,r){if(n.load&&console.log("fetch",e,r),e)if(e.match(/^data:/)){var o=e.split(","),i=o[0],a=o[1];a=i.indexOf(";base64")>-1?atob(a):decodeURIComponent(a),setTimeout(function(){this.receive(e,r,null,a)}.bind(this),0)}else{var s=function(t,n,o){this.receive(e,r,t,n,o)}.bind(this);t.load(e,s)}else setTimeout(function(){this.receive(e,r,{error:"href must be specified"},null)}.bind(this),0)},receive:function(e,t,n,r,o){this.cache[e]=r;for(var i,a=this.pending[e],s=0,c=a.length;c>s&&(i=a[s]);s++)this.onload(e,i,r,n,o),this.tail();this.pending[e]=null},tail:function(){--this.inflight,this.checkDone()},checkDone:function(){this.inflight||this.oncomplete()}},e.Loader=r}),window.HTMLImports.addModule(function(e){var t=function(e){this.addCallback=e,this.mo=new MutationObserver(this.handler.bind(this))};t.prototype={handler:function(e){for(var t,n=0,r=e.length;r>n&&(t=e[n]);n++)"childList"===t.type&&t.addedNodes.length&&this.addedNodes(t.addedNodes)},addedNodes:function(e){this.addCallback&&this.addCallback(e);for(var t,n=0,r=e.length;r>n&&(t=e[n]);n++)t.children&&t.children.length&&this.addedNodes(t.children)},observe:function(e){this.mo.observe(e,{childList:!0,subtree:!0})}},e.Observer=t}),window.HTMLImports.addModule(function(e){function t(e){return"link"===e.localName&&e.rel===u}function n(e){var t=r(e);return"data:text/javascript;charset=utf-8,"+encodeURIComponent(t)}function r(e){return e.textContent+o(e)}function o(e){var t=e.ownerDocument;t.__importedScripts=t.__importedScripts||0;var n=e.ownerDocument.baseURI,r=t.__importedScripts?"-"+t.__importedScripts:"";return t.__importedScripts++,"\n//# sourceURL="+n+r+".js\n"}function i(e){var t=e.ownerDocument.createElement("style");return t.textContent=e.textContent,a.resolveUrlsInStyle(t),t}var a=e.path,s=e.rootDocument,c=e.flags,d=e.isIE,u=e.IMPORT_LINK_TYPE,l="link[rel="+u+"]",h={documentSelectors:l,importsSelectors:[l,"link[rel=stylesheet]:not([type])","style:not([type])","script:not([type])",'script[type="application/javascript"]','script[type="text/javascript"]'].join(","),map:{link:"parseLink",script:"parseScript",style:"parseStyle"},dynamicElements:[],parseNext:function(){var e=this.nextToParse();e&&this.parse(e)},parse:function(e){if(this.isParsed(e))return void(c.parse&&console.log("[%s] is already parsed",e.localName));var t=this[this.map[e.localName]];t&&(this.markParsing(e),t.call(this,e))},parseDynamic:function(e,t){this.dynamicElements.push(e),t||this.parseNext()},markParsing:function(e){c.parse&&console.log("parsing",e),this.parsingElement=e},markParsingComplete:function(e){e.__importParsed=!0,this.markDynamicParsingComplete(e),e.__importElement&&(e.__importElement.__importParsed=!0,this.markDynamicParsingComplete(e.__importElement)),this.parsingElement=null,c.parse&&console.log("completed",e)},markDynamicParsingComplete:function(e){var t=this.dynamicElements.indexOf(e);t>=0&&this.dynamicElements.splice(t,1)},parseImport:function(e){if(e["import"]=e.__doc,window.HTMLImports.__importsParsingHook&&window.HTMLImports.__importsParsingHook(e),e["import"]&&(e["import"].__importParsed=!0),this.markParsingComplete(e),e.dispatchEvent(e.__resource&&!e.__error?new CustomEvent("load",{bubbles:!1}):new CustomEvent("error",{bubbles:!1})),e.__pending)for(var t;e.__pending.length;)t=e.__pending.shift(),t&&t({target:e});this.parseNext()},parseLink:function(e){t(e)?this.parseImport(e):(e.href=e.href,this.parseGeneric(e))},parseStyle:function(e){var t=e;e=i(e),t.__appliedElement=e,e.__importElement=t,this.parseGeneric(e)},parseGeneric:function(e){this.trackElement(e),this.addElementToDocument(e)},rootImportForElement:function(e){for(var t=e;t.ownerDocument.__importLink;)t=t.ownerDocument.__importLink;return t},addElementToDocument:function(e){var t=this.rootImportForElement(e.__importElement||e);t.parentNode.insertBefore(e,t)},trackElement:function(e,t){var n=this,r=function(o){e.removeEventListener("load",r),e.removeEventListener("error",r),t&&t(o),n.markParsingComplete(e),n.parseNext()};if(e.addEventListener("load",r),e.addEventListener("error",r),d&&"style"===e.localName){var o=!1;if(-1==e.textContent.indexOf("@import"))o=!0;else if(e.sheet){o=!0;for(var i,a=e.sheet.cssRules,s=a?a.length:0,c=0;s>c&&(i=a[c]);c++)i.type===CSSRule.IMPORT_RULE&&(o=o&&Boolean(i.styleSheet))}o&&setTimeout(function(){e.dispatchEvent(new CustomEvent("load",{bubbles:!1}))})}},parseScript:function(t){var r=document.createElement("script");r.__importElement=t,r.src=t.src?t.src:n(t),e.currentScript=t,this.trackElement(r,function(t){r.parentNode&&r.parentNode.removeChild(r),e.currentScript=null}),this.addElementToDocument(r)},nextToParse:function(){return this._mayParse=[],!this.parsingElement&&(this.nextToParseInDoc(s)||this.nextToParseDynamic())},nextToParseInDoc:function(e,n){if(e&&this._mayParse.indexOf(e)<0){this._mayParse.push(e);for(var r,o=e.querySelectorAll(this.parseSelectorsForNode(e)),i=0,a=o.length;a>i&&(r=o[i]);i++)if(!this.isParsed(r))return this.hasResource(r)?t(r)?this.nextToParseInDoc(r.__doc,r):r:void 0}return n},nextToParseDynamic:function(){return this.dynamicElements[0]},parseSelectorsForNode:function(e){var t=e.ownerDocument||e;return t===s?this.documentSelectors:this.importsSelectors},isParsed:function(e){return e.__importParsed},needsDynamicParsing:function(e){return this.dynamicElements.indexOf(e)>=0},hasResource:function(e){return t(e)&&void 0===e.__doc?!1:!0}};e.parser=h,e.IMPORT_SELECTOR=l}),window.HTMLImports.addModule(function(e){function t(e){return n(e,a)}function n(e,t){return"link"===e.localName&&e.getAttribute("rel")===t}function r(e){return!!Object.getOwnPropertyDescriptor(e,"baseURI")}function o(e,t){var n=document.implementation.createHTMLDocument(a);n._URL=t;var o=n.createElement("base");o.setAttribute("href",t),n.baseURI||r(n)||Object.defineProperty(n,"baseURI",{value:t});var i=n.createElement("meta");return i.setAttribute("charset","utf-8"),n.head.appendChild(i),n.head.appendChild(o),n.body.innerHTML=e,window.HTMLTemplateElement&&HTMLTemplateElement.bootstrap&&HTMLTemplateElement.bootstrap(n),n}var i=e.flags,a=e.IMPORT_LINK_TYPE,s=e.IMPORT_SELECTOR,c=e.rootDocument,d=e.Loader,u=e.Observer,l=e.parser,h={documents:{},documentPreloadSelectors:s,importsPreloadSelectors:[s].join(","),loadNode:function(e){p.addNode(e)},loadSubtree:function(e){var t=this.marshalNodes(e);p.addNodes(t)},marshalNodes:function(e){return e.querySelectorAll(this.loadSelectorsForNode(e))},loadSelectorsForNode:function(e){var t=e.ownerDocument||e;return t===c?this.documentPreloadSelectors:this.importsPreloadSelectors},loaded:function(e,n,r,a,s){if(i.load&&console.log("loaded",e,n),n.__resource=r,n.__error=a,t(n)){var c=this.documents[e];void 0===c&&(c=a?null:o(r,s||e),c&&(c.__importLink=n,this.bootDocument(c)),this.documents[e]=c),n.__doc=c}l.parseNext()},bootDocument:function(e){this.loadSubtree(e),this.observer.observe(e),l.parseNext()},loadedAll:function(){l.parseNext()}},p=new d(h.loaded.bind(h),h.loadedAll.bind(h));if(h.observer=new u,!document.baseURI){var f={get:function(){var e=document.querySelector("base");return e?e.href:window.location.href},configurable:!0};Object.defineProperty(document,"baseURI",f),Object.defineProperty(c,"baseURI",f)}e.importer=h,e.importLoader=p}),window.HTMLImports.addModule(function(e){var t=e.parser,n=e.importer,r={added:function(e){for(var r,o,i,a,s=0,c=e.length;c>s&&(a=e[s]);s++)r||(r=a.ownerDocument,o=t.isParsed(r)),i=this.shouldLoadNode(a),i&&n.loadNode(a),this.shouldParseNode(a)&&o&&t.parseDynamic(a,i)},shouldLoadNode:function(e){return 1===e.nodeType&&o.call(e,n.loadSelectorsForNode(e))},shouldParseNode:function(e){return 1===e.nodeType&&o.call(e,t.parseSelectorsForNode(e))}};n.observer.addCallback=r.added.bind(r);var o=HTMLElement.prototype.matches||HTMLElement.prototype.matchesSelector||HTMLElement.prototype.webkitMatchesSelector||HTMLElement.prototype.mozMatchesSelector||HTMLElement.prototype.msMatchesSelector}),function(e){function t(){window.HTMLImports.importer.bootDocument(o)}var n=e.initializeModules,r=e.isIE;if(!e.useNative){r&&"function"!=typeof window.CustomEvent&&(window.CustomEvent=function(e,t){t=t||{};var n=document.createEvent("CustomEvent");return n.initCustomEvent(e,Boolean(t.bubbles),Boolean(t.cancelable),t.detail),n.preventDefault=function(){Object.defineProperty(this,"defaultPrevented",{get:function(){return!0}})},n},window.CustomEvent.prototype=window.Event.prototype),n();var o=e.rootDocument;"complete"===document.readyState||"interactive"===document.readyState&&!window.attachEvent?t():document.addEventListener("DOMContentLoaded",t)}}(window.HTMLImports),window.CustomElements=window.CustomElements||{flags:{}},function(e){var t=e.flags,n=[],r=function(e){n.push(e)},o=function(){n.forEach(function(t){t(e)})};e.addModule=r,e.initializeModules=o,e.hasNative=Boolean(document.registerElement),e.isIE=/Trident/.test(navigator.userAgent),e.useNative=!t.register&&e.hasNative&&!window.ShadowDOMPolyfill&&(!window.HTMLImports||window.HTMLImports.useNative)}(window.CustomElements),window.CustomElements.addModule(function(e){function t(e,t){n(e,function(e){return t(e)?!0:void r(e,t)}),r(e,t)}function n(e,t,r){var o=e.firstElementChild;if(!o)for(o=e.firstChild;o&&o.nodeType!==Node.ELEMENT_NODE;)o=o.nextSibling;for(;o;)t(o,r)!==!0&&n(o,t,r),o=o.nextElementSibling;return null}function r(e,n){for(var r=e.shadowRoot;r;)t(r,n),r=r.olderShadowRoot}function o(e,t){i(e,t,[])}function i(e,t,n){if(e=window.wrap(e),!(n.indexOf(e)>=0)){n.push(e);for(var r,o=e.querySelectorAll("link[rel="+a+"]"),s=0,c=o.length;c>s&&(r=o[s]);s++)r["import"]&&i(r["import"],t,n);t(e)}}var a=window.HTMLImports?window.HTMLImports.IMPORT_LINK_TYPE:"none";e.forDocumentTree=o,e.forSubtree=t}),window.CustomElements.addModule(function(e){function t(e,t){return n(e,t)||r(e,t)}function n(t,n){return e.upgrade(t,n)?!0:void(n&&a(t))}function r(e,t){g(e,function(e){return n(e,t)?!0:void 0})}function o(e){L.push(e),E||(E=!0,setTimeout(i))}function i(){E=!1;for(var e,t=L,n=0,r=t.length;r>n&&(e=t[n]);n++)e();L=[]}function a(e){y?o(function(){s(e)}):s(e)}function s(e){e.__upgraded__&&!e.__attached&&(e.__attached=!0,e.attachedCallback&&e.attachedCallback())}function c(e){d(e),g(e,function(e){d(e)})}function d(e){y?o(function(){u(e)}):u(e)}function u(e){e.__upgraded__&&e.__attached&&(e.__attached=!1,e.detachedCallback&&e.detachedCallback())}function l(e){for(var t=e,n=window.wrap(document);t;){if(t==n)return!0;t=t.parentNode||t.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&t.host}}function h(e){if(e.shadowRoot&&!e.shadowRoot.__watched){w.dom&&console.log("watching shadow-root for: ",e.localName);for(var t=e.shadowRoot;t;)m(t),t=t.olderShadowRoot}}function p(e,n){if(w.dom){var r=n[0];if(r&&"childList"===r.type&&r.addedNodes&&r.addedNodes){for(var o=r.addedNodes[0];o&&o!==document&&!o.host;)o=o.parentNode;var i=o&&(o.URL||o._URL||o.host&&o.host.localName)||"";i=i.split("/?").shift().split("/").pop()}console.group("mutations (%d) [%s]",n.length,i||"")}var a=l(e);n.forEach(function(e){"childList"===e.type&&(M(e.addedNodes,function(e){e.localName&&t(e,a)}),M(e.removedNodes,function(e){e.localName&&c(e)}))}),w.dom&&console.groupEnd()}function f(e){for(e=window.wrap(e),e||(e=window.wrap(document));e.parentNode;)e=e.parentNode;var t=e.__observer;t&&(p(e,t.takeRecords()),i())}function m(e){if(!e.__observer){var t=new MutationObserver(p.bind(this,e));t.observe(e,{childList:!0,subtree:!0}),e.__observer=t}}function v(e){e=window.wrap(e),w.dom&&console.group("upgradeDocument: ",e.baseURI.split("/").pop());var n=e===window.wrap(document);t(e,n),m(e),w.dom&&console.groupEnd()}function _(e){b(e,v)}var w=e.flags,g=e.forSubtree,b=e.forDocumentTree,y=!window.MutationObserver||window.MutationObserver===window.JsMutationObserver;e.hasPolyfillMutations=y;var E=!1,L=[],M=Array.prototype.forEach.call.bind(Array.prototype.forEach),T=Element.prototype.createShadowRoot;T&&(Element.prototype.createShadowRoot=function(){var e=T.call(this);return window.CustomElements.watchShadow(this),e}),e.watchShadow=h,e.upgradeDocumentTree=_,e.upgradeDocument=v,e.upgradeSubtree=r,e.upgradeAll=t,e.attached=a,e.takeRecords=f}),window.CustomElements.addModule(function(e){function t(t,r){if(!t.__upgraded__&&t.nodeType===Node.ELEMENT_NODE){var o=t.getAttribute("is"),i=e.getRegisteredDefinition(t.localName)||e.getRegisteredDefinition(o);if(i&&(o&&i.tag==t.localName||!o&&!i["extends"]))return n(t,i,r)}}function n(t,n,o){return a.upgrade&&console.group("upgrade:",t.localName),n.is&&t.setAttribute("is",n.is),r(t,n),t.__upgraded__=!0,i(t),o&&e.attached(t),e.upgradeSubtree(t,o),a.upgrade&&console.groupEnd(),t}function r(e,t){Object.__proto__?e.__proto__=t.prototype:(o(e,t.prototype,t["native"]),e.__proto__=t.prototype)}function o(e,t,n){for(var r={},o=t;o!==n&&o!==HTMLElement.prototype;){for(var i,a=Object.getOwnPropertyNames(o),s=0;i=a[s];s++)r[i]||(Object.defineProperty(e,i,Object.getOwnPropertyDescriptor(o,i)),r[i]=1);o=Object.getPrototypeOf(o)}}function i(e){e.createdCallback&&e.createdCallback()}var a=e.flags;e.upgrade=t,e.upgradeWithDefinition=n,e.implementPrototype=r}),window.CustomElements.addModule(function(e){function t(t,r){var c=r||{};if(!t)throw new Error("document.registerElement: first argument `name` must not be empty");if(t.indexOf("-")<0)throw new Error("document.registerElement: first argument ('name') must contain a dash ('-'). Argument provided was '"+String(t)+"'.");if(o(t))throw new Error("Failed to execute 'registerElement' on 'Document': Registration failed for type '"+String(t)+"'. The type name is invalid.");if(d(t))throw new Error("DuplicateDefinitionError: a type with name '"+String(t)+"' is already registered");return c.prototype||(c.prototype=Object.create(HTMLElement.prototype)),c.__name=t.toLowerCase(),c.lifecycle=c.lifecycle||{},c.ancestry=i(c["extends"]),a(c),s(c),n(c.prototype),u(c.__name,c),c.ctor=l(c),c.ctor.prototype=c.prototype,c.prototype.constructor=c.ctor,e.ready&&_(document),c.ctor}function n(e){if(!e.setAttribute._polyfilled){ +var t=e.setAttribute;e.setAttribute=function(e,n){r.call(this,e,n,t)};var n=e.removeAttribute;e.removeAttribute=function(e){r.call(this,e,null,n)},e.setAttribute._polyfilled=!0}}function r(e,t,n){e=e.toLowerCase();var r=this.getAttribute(e);n.apply(this,arguments);var o=this.getAttribute(e);this.attributeChangedCallback&&o!==r&&this.attributeChangedCallback(e,r,o)}function o(e){for(var t=0;t=0&&b(r,HTMLElement),r)}function f(e,t){var n=e[t];e[t]=function(){var e=n.apply(this,arguments);return w(e),e}}var m,v=e.isIE,_=e.upgradeDocumentTree,w=e.upgradeAll,g=e.upgradeWithDefinition,b=e.implementPrototype,y=e.useNative,E=["annotation-xml","color-profile","font-face","font-face-src","font-face-uri","font-face-format","font-face-name","missing-glyph"],L={},M="http://www.w3.org/1999/xhtml",T=document.createElement.bind(document),N=document.createElementNS.bind(document);m=Object.__proto__||y?function(e,t){return e instanceof t}:function(e,t){if(e instanceof t)return!0;for(var n=e;n;){if(n===t.prototype)return!0;n=n.__proto__}return!1},f(Node.prototype,"cloneNode"),f(document,"importNode"),v&&!function(){var e=document.importNode;document.importNode=function(){var t=e.apply(document,arguments);if(t.nodeType==t.DOCUMENT_FRAGMENT_NODE){var n=document.createDocumentFragment();return n.appendChild(t),n}return t}}(),document.registerElement=t,document.createElement=p,document.createElementNS=h,e.registry=L,e["instanceof"]=m,e.reservedTagList=E,e.getRegisteredDefinition=d,document.register=document.registerElement}),function(e){function t(){a(window.wrap(document)),window.CustomElements.ready=!0;var e=window.requestAnimationFrame||function(e){setTimeout(e,16)};e(function(){setTimeout(function(){window.CustomElements.readyTime=Date.now(),window.HTMLImports&&(window.CustomElements.elapsed=window.CustomElements.readyTime-window.HTMLImports.readyTime),document.dispatchEvent(new CustomEvent("WebComponentsReady",{bubbles:!0}))})})}var n=e.useNative,r=e.initializeModules,o=e.isIE;if(n){var i=function(){};e.watchShadow=i,e.upgrade=i,e.upgradeAll=i,e.upgradeDocumentTree=i,e.upgradeSubtree=i,e.takeRecords=i,e["instanceof"]=function(e,t){return e instanceof t}}else r();var a=e.upgradeDocumentTree,s=e.upgradeDocument;if(window.wrap||(window.ShadowDOMPolyfill?(window.wrap=window.ShadowDOMPolyfill.wrapIfNeeded,window.unwrap=window.ShadowDOMPolyfill.unwrapIfNeeded):window.wrap=window.unwrap=function(e){return e}),window.HTMLImports&&(window.HTMLImports.__importsParsingHook=function(e){e["import"]&&s(wrap(e["import"]))}),o&&"function"!=typeof window.CustomEvent&&(window.CustomEvent=function(e,t){t=t||{};var n=document.createEvent("CustomEvent");return n.initCustomEvent(e,Boolean(t.bubbles),Boolean(t.cancelable),t.detail),n.preventDefault=function(){Object.defineProperty(this,"defaultPrevented",{get:function(){return!0}})},n},window.CustomEvent.prototype=window.Event.prototype),"complete"===document.readyState||e.flags.eager)t();else if("interactive"!==document.readyState||window.attachEvent||window.HTMLImports&&!window.HTMLImports.ready){var c=window.HTMLImports&&!window.HTMLImports.ready?"HTMLImportsLoaded":"DOMContentLoaded";window.addEventListener(c,t)}else t()}(window.CustomElements),"undefined"==typeof HTMLTemplateElement&&!function(){function e(e){switch(e){case"&":return"&";case"<":return"<";case">":return">";case" ":return" "}}function t(t){return t.replace(a,e)}var n="template",r=document.implementation.createHTMLDocument("template"),o=!0;HTMLTemplateElement=function(){},HTMLTemplateElement.prototype=Object.create(HTMLElement.prototype),HTMLTemplateElement.decorate=function(e){e.content||(e.content=r.createDocumentFragment());for(var n;n=e.firstChild;)e.content.appendChild(n);if(o)try{Object.defineProperty(e,"innerHTML",{get:function(){for(var e="",n=this.content.firstChild;n;n=n.nextSibling)e+=n.outerHTML||t(n.data);return e},set:function(e){for(r.body.innerHTML=e,HTMLTemplateElement.bootstrap(r);this.content.firstChild;)this.content.removeChild(this.content.firstChild);for(;r.body.firstChild;)this.content.appendChild(r.body.firstChild)},configurable:!0})}catch(i){o=!1}},HTMLTemplateElement.bootstrap=function(e){for(var t,r=e.querySelectorAll(n),o=0,i=r.length;i>o&&(t=r[o]);o++)HTMLTemplateElement.decorate(t)},window.addEventListener("DOMContentLoaded",function(){HTMLTemplateElement.bootstrap(document)});var i=document.createElement;document.createElement=function(){"use strict";var e=i.apply(document,arguments);return"template"==e.localName&&HTMLTemplateElement.decorate(e),e};var a=/[&\u00A0<>]/g}(),function(e){var t=document.createElement("style");t.textContent="body {transition: opacity ease-in 0.2s; } \nbody[unresolved] {opacity: 0; display: block; overflow: hidden; position: relative; } \n";var n=document.querySelector("head");n.insertBefore(t,n.firstChild)}(window.WebComponents); \ No newline at end of file From b79d0f5404b3fcd587d2767019f8c690ad8d553a Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 23 Aug 2015 10:06:54 -0700 Subject: [PATCH 159/224] Fix discovery and wemo --- homeassistant/components/discovery.py | 2 +- homeassistant/components/switch/wemo.py | 2 +- requirements.txt | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/discovery.py b/homeassistant/components/discovery.py index 0aa7312bfd7..7ba66f9aba6 100644 --- a/homeassistant/components/discovery.py +++ b/homeassistant/components/discovery.py @@ -19,7 +19,7 @@ from homeassistant.const import ( DOMAIN = "discovery" DEPENDENCIES = [] -REQUIREMENTS = ['netdisco>=0.1'] +REQUIREMENTS = ['netdisco>=0.2'] SCAN_INTERVAL = 300 # seconds diff --git a/homeassistant/components/switch/wemo.py b/homeassistant/components/switch/wemo.py index 3020f638dd3..1614db02653 100644 --- a/homeassistant/components/switch/wemo.py +++ b/homeassistant/components/switch/wemo.py @@ -8,7 +8,7 @@ import logging from homeassistant.components.switch import SwitchDevice -REQUIREMENTS = ['pywemo>=0.1'] +REQUIREMENTS = ['pywemo>=0.2'] # pylint: disable=unused-argument diff --git a/requirements.txt b/requirements.txt index b294793f24a..03b0d882e8b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -84,10 +84,10 @@ https://github.com/theolind/pymysensors/archive/master.zip#egg=pymysensors-0.1 pynetgear>=0.1 # Netdisco (discovery) -netdisco>=0.1 +netdisco>=0.2 # Wemo (switch.wemo) -pywemo>=0.1 +pywemo>=0.2 # Wink (*.wink) https://github.com/balloob/python-wink/archive/master.zip#pywink>=0.1 From d69bb8db6e4e06f0f451ffb6eb17d4f8c7d9ee55 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 23 Aug 2015 10:20:54 -0700 Subject: [PATCH 160/224] Frontend: fix sensor label and menu button --- homeassistant/components/frontend/version.py | 2 +- .../frontend/www_static/frontend.html | 20 +++++++++---------- .../www_static/home-assistant-polymer | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py index 8aab54a4d7a..dd3a749907f 100644 --- a/homeassistant/components/frontend/version.py +++ b/homeassistant/components/frontend/version.py @@ -1,2 +1,2 @@ """ DO NOT MODIFY. Auto-generated by build_frontend script """ -VERSION = "2e836a9f75c620da8183ced8776358e0" +VERSION = "0d1e053fd7cc4ae9909843ab656317f9" diff --git a/homeassistant/components/frontend/www_static/frontend.html b/homeassistant/components/frontend/www_static/frontend.html index a94b12f061a..01759535947 100644 --- a/homeassistant/components/frontend/www_static/frontend.html +++ b/homeassistant/components/frontend/www_static/frontend.html @@ -3736,7 +3736,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN background-color: rgba(255, 255, 255, 0.95); line-height: 2em; cursor: pointer; - } \ No newline at end of file + } \ No newline at end of file diff --git a/homeassistant/components/frontend/www_static/home-assistant-polymer b/homeassistant/components/frontend/www_static/home-assistant-polymer index 0035b14304c..360564758fe 160000 --- a/homeassistant/components/frontend/www_static/home-assistant-polymer +++ b/homeassistant/components/frontend/www_static/home-assistant-polymer @@ -1 +1 @@ -Subproject commit 0035b14304c310e101c95671bd89b44e62513e7e +Subproject commit 360564758fe4da56b7ff71aae12b7913cc1c06c2 From 517d4b35ede91d125decb82d831d4e957d47712a Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 23 Aug 2015 17:18:21 -0700 Subject: [PATCH 161/224] Update Cast dependency to latest version --- homeassistant/components/media_player/cast.py | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/media_player/cast.py b/homeassistant/components/media_player/cast.py index cd5a29a2e5f..5fca233013a 100644 --- a/homeassistant/components/media_player/cast.py +++ b/homeassistant/components/media_player/cast.py @@ -19,7 +19,7 @@ from homeassistant.components.media_player import ( SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK, MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO) -REQUIREMENTS = ['pychromecast>=0.6.9'] +REQUIREMENTS = ['pychromecast>=0.6.10'] CONF_IGNORE_CEC = 'ignore_cec' CAST_SPLASH = 'https://home-assistant.io/images/cast/splash.png' SUPPORT_CAST = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \ diff --git a/requirements.txt b/requirements.txt index 03b0d882e8b..88552844823 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,7 +15,7 @@ phue>=0.8 ledcontroller>=1.0.7 # Chromecast bindings (media_player.cast) -pychromecast>=0.6.9 +pychromecast>=0.6.10 # Keyboard (keyboard) pyuserinput>=0.1.9 From e917479fbad25cf71f11a47205201f21d094f6d0 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 23 Aug 2015 17:18:52 -0700 Subject: [PATCH 162/224] Update netdisco to latest version --- homeassistant/components/discovery.py | 2 +- homeassistant/components/light/hue.py | 2 +- homeassistant/components/switch/wemo.py | 2 +- requirements.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/discovery.py b/homeassistant/components/discovery.py index 7ba66f9aba6..cb2e2d3c780 100644 --- a/homeassistant/components/discovery.py +++ b/homeassistant/components/discovery.py @@ -19,7 +19,7 @@ from homeassistant.const import ( DOMAIN = "discovery" DEPENDENCIES = [] -REQUIREMENTS = ['netdisco>=0.2'] +REQUIREMENTS = ['netdisco>=0.3'] SCAN_INTERVAL = 300 # seconds diff --git a/homeassistant/components/light/hue.py b/homeassistant/components/light/hue.py index f012d160b7f..c3b28ec1dd6 100644 --- a/homeassistant/components/light/hue.py +++ b/homeassistant/components/light/hue.py @@ -39,7 +39,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): return if discovery_info is not None: - host = urlparse(discovery_info).hostname + host = urlparse(discovery_info[1]).hostname else: host = config.get(CONF_HOST, None) diff --git a/homeassistant/components/switch/wemo.py b/homeassistant/components/switch/wemo.py index 1614db02653..d133191e6db 100644 --- a/homeassistant/components/switch/wemo.py +++ b/homeassistant/components/switch/wemo.py @@ -18,7 +18,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): import pywemo.discovery as discovery if discovery_info is not None: - device = discovery.device_from_description(discovery_info) + device = discovery.device_from_description(discovery_info[2]) if device: add_devices_callback([WemoSwitch(device)]) diff --git a/requirements.txt b/requirements.txt index 88552844823..20d1d72b47c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -84,7 +84,7 @@ https://github.com/theolind/pymysensors/archive/master.zip#egg=pymysensors-0.1 pynetgear>=0.1 # Netdisco (discovery) -netdisco>=0.2 +netdisco>=0.3 # Wemo (switch.wemo) pywemo>=0.2 From 35489998dfeff583067e14896a9e27ed7636089d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 23 Aug 2015 17:20:09 -0700 Subject: [PATCH 163/224] Allow for Netgear router discovery --- .../components/device_tracker/__init__.py | 5 +-- .../components/device_tracker/netgear.py | 34 ++++++++++++------- homeassistant/components/discovery.py | 9 +++++ requirements.txt | 2 +- 4 files changed, 34 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/device_tracker/__init__.py b/homeassistant/components/device_tracker/__init__.py index 452480b12c9..e529327f505 100644 --- a/homeassistant/components/device_tracker/__init__.py +++ b/homeassistant/components/device_tracker/__init__.py @@ -66,14 +66,15 @@ def setup(hass, config): 'device_tracker.{}'.format(tracker_type)) if tracker_implementation is None: - _LOGGER.error("Unknown device_tracker type specified.") + _LOGGER.error("Unknown device_tracker type specified: %s.", + tracker_type) return False device_scanner = tracker_implementation.get_scanner(hass, config) if device_scanner is None: - _LOGGER.error("Failed to initialize device scanner for %s", + _LOGGER.error("Failed to initialize device scanner: %s", tracker_type) return False diff --git a/homeassistant/components/device_tracker/netgear.py b/homeassistant/components/device_tracker/netgear.py index 3fe11f99fe6..269b6eb1eab 100644 --- a/homeassistant/components/device_tracker/netgear.py +++ b/homeassistant/components/device_tracker/netgear.py @@ -35,7 +35,6 @@ from datetime import timedelta import threading from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD -from homeassistant.helpers import validate_config from homeassistant.util import Throttle from homeassistant.components.device_tracker import DOMAIN @@ -43,20 +42,21 @@ from homeassistant.components.device_tracker import DOMAIN MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['pynetgear>=0.1'] +REQUIREMENTS = ['pynetgear>=0.3'] def get_scanner(hass, config): """ Validates config and returns a Netgear scanner. """ - if not validate_config(config, - {DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]}, - _LOGGER): + info = config[DOMAIN] + host = info.get(CONF_HOST) + username = info.get(CONF_USERNAME) + password = info.get(CONF_PASSWORD) + + if password is not None and host is None: + _LOGGER.warning('Found username or password but no host') return None - info = config[DOMAIN] - - scanner = NetgearDeviceScanner( - info[CONF_HOST], info[CONF_USERNAME], info[CONF_PASSWORD]) + scanner = NetgearDeviceScanner(host, password, username) return scanner if scanner.success_init else None @@ -68,16 +68,24 @@ class NetgearDeviceScanner(object): import pynetgear self.last_results = [] - - self._api = pynetgear.Netgear(host, username, password) self.lock = threading.Lock() + if host is None: + print("BIER") + self._api = pynetgear.Netgear() + elif username is None: + self._api = pynetgear.Netgear(password, host) + else: + self._api = pynetgear.Netgear(password, host, username) + _LOGGER.info("Logging in") - self.success_init = self._api.login() + results = self._api.get_attached_devices() + + self.success_init = results is not None if self.success_init: - self._update_info() + self.last_results = results else: _LOGGER.error("Failed to Login") diff --git a/homeassistant/components/discovery.py b/homeassistant/components/discovery.py index cb2e2d3c780..4ad0299cc8f 100644 --- a/homeassistant/components/discovery.py +++ b/homeassistant/components/discovery.py @@ -28,11 +28,13 @@ SCAN_INTERVAL = 300 # seconds SERVICE_WEMO = 'belkin_wemo' SERVICE_HUE = 'philips_hue' SERVICE_CAST = 'google_cast' +SERVICE_NETGEAR = 'netgear_router' SERVICE_HANDLERS = { SERVICE_WEMO: "switch", SERVICE_CAST: "media_player", SERVICE_HUE: "light", + SERVICE_NETGEAR: 'device_tracker', } @@ -77,6 +79,13 @@ def setup(hass, config): if not component: return + # Hack - fix when device_tracker supports discovery + if service == SERVICE_NETGEAR: + bootstrap.setup_component(hass, component, { + 'device_tracker': {'platform': 'netgear'} + }) + return + # This component cannot be setup. if not bootstrap.setup_component(hass, component, config): return diff --git a/requirements.txt b/requirements.txt index 20d1d72b47c..e284cad72b9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -81,7 +81,7 @@ https://github.com/Danielhiversen/pyRFXtrx/archive/master.zip https://github.com/theolind/pymysensors/archive/master.zip#egg=pymysensors-0.1 # Netgear (device_tracker.netgear) -pynetgear>=0.1 +pynetgear>=0.3 # Netdisco (discovery) netdisco>=0.3 From 6338f387d29a663c546c653e9f11f257ee6bd1b0 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 23 Aug 2015 17:20:23 -0700 Subject: [PATCH 164/224] Bugfixes for frontend --- homeassistant/components/frontend/version.py | 2 +- .../frontend/www_static/frontend.html | 84 +++++++++---------- .../www_static/home-assistant-polymer | 2 +- 3 files changed, 42 insertions(+), 46 deletions(-) diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py index dd3a749907f..622adf2fd2d 100644 --- a/homeassistant/components/frontend/version.py +++ b/homeassistant/components/frontend/version.py @@ -1,2 +1,2 @@ """ DO NOT MODIFY. Auto-generated by build_frontend script """ -VERSION = "0d1e053fd7cc4ae9909843ab656317f9" +VERSION = "03ced84dfc0cb243ab407ca953021e60" diff --git a/homeassistant/components/frontend/www_static/frontend.html b/homeassistant/components/frontend/www_static/frontend.html index 01759535947..52deee54205 100644 --- a/homeassistant/components/frontend/www_static/frontend.html +++ b/homeassistant/components/frontend/www_static/frontend.html @@ -1,6 +1,6 @@ - \ No newline at end of file + } \ No newline at end of file diff --git a/homeassistant/components/frontend/www_static/home-assistant-polymer b/homeassistant/components/frontend/www_static/home-assistant-polymer index 360564758fe..82aca23a671 160000 --- a/homeassistant/components/frontend/www_static/home-assistant-polymer +++ b/homeassistant/components/frontend/www_static/home-assistant-polymer @@ -1 +1 @@ -Subproject commit 360564758fe4da56b7ff71aae12b7913cc1c06c2 +Subproject commit 82aca23a671d021cd2647b7709f58bf6b9e2eb04 From d45a7e2ba4b64fc7130c10f75a0ec3c581742ed2 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 23 Aug 2015 19:46:18 -0700 Subject: [PATCH 165/224] Update demo data to show more features --- homeassistant/components/demo.py | 15 +++++++++------ homeassistant/components/sensor/demo.py | 1 + 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/demo.py b/homeassistant/components/demo.py index 17d20571f62..2dab6a12659 100644 --- a/homeassistant/components/demo.py +++ b/homeassistant/components/demo.py @@ -10,7 +10,7 @@ import homeassistant.core as ha import homeassistant.bootstrap as bootstrap import homeassistant.loader as loader from homeassistant.const import ( - CONF_PLATFORM, ATTR_ENTITY_PICTURE, ATTR_ENTITY_ID) + CONF_PLATFORM, ATTR_ENTITY_PICTURE, ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME) DOMAIN = "demo" @@ -48,8 +48,11 @@ def setup(hass, config): # Setup room groups lights = hass.states.entity_ids('light') switches = hass.states.entity_ids('switch') - group.setup_group(hass, 'living room', [lights[0], lights[1], switches[0]]) - group.setup_group(hass, 'bedroom', [lights[2], switches[1]]) + media_players = sorted(hass.states.entity_ids('media_player')) + group.setup_group(hass, 'living room', [lights[0], lights[1], switches[0], + media_players[1]]) + group.setup_group(hass, 'bedroom', [lights[2], switches[1], + media_players[0]]) # Setup IP Camera bootstrap.setup_component( @@ -102,10 +105,10 @@ def setup(hass, config): # Setup fake device tracker hass.states.set("device_tracker.paulus", "home", {ATTR_ENTITY_PICTURE: - "http://graph.facebook.com/297400035/picture"}) + "http://graph.facebook.com/297400035/picture", + ATTR_FRIENDLY_NAME: 'Paulus'}) hass.states.set("device_tracker.anne_therese", "not_home", - {ATTR_ENTITY_PICTURE: - "http://graph.facebook.com/621994601/picture"}) + {ATTR_FRIENDLY_NAME: 'Anne Therese'}) hass.states.set("group.all_devices", "home", { diff --git a/homeassistant/components/sensor/demo.py b/homeassistant/components/sensor/demo.py index 37e2555136a..218860290b0 100644 --- a/homeassistant/components/sensor/demo.py +++ b/homeassistant/components/sensor/demo.py @@ -15,6 +15,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): add_devices([ DemoSensor('Outside Temperature', 15.6, TEMP_CELCIUS, 12), DemoSensor('Outside Humidity', 54, '%', None), + DemoSensor('Alarm back', 'Armed', None, None), ]) From a419509893be002886f37aa564325693b18d27ca Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 23 Aug 2015 19:47:51 -0700 Subject: [PATCH 166/224] More bugfixes frontend --- homeassistant/components/frontend/version.py | 2 +- .../frontend/www_static/frontend.html | 299 +++++++++--------- .../www_static/home-assistant-polymer | 2 +- 3 files changed, 148 insertions(+), 155 deletions(-) diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py index 622adf2fd2d..1caad17c3b3 100644 --- a/homeassistant/components/frontend/version.py +++ b/homeassistant/components/frontend/version.py @@ -1,2 +1,2 @@ """ DO NOT MODIFY. Auto-generated by build_frontend script """ -VERSION = "03ced84dfc0cb243ab407ca953021e60" +VERSION = "e202d8b48a2b501c031920cc9d57999d" diff --git a/homeassistant/components/frontend/www_static/frontend.html b/homeassistant/components/frontend/www_static/frontend.html index 52deee54205..94d2b93b35a 100644 --- a/homeassistant/components/frontend/www_static/frontend.html +++ b/homeassistant/components/frontend/www_static/frontend.html @@ -3312,7 +3312,137 @@ iron-selector > #main { iron-selector:not(.narrow-layout) #main ::content [paper-drawer-toggle] { display: none; -} \ No newline at end of file + } \ No newline at end of file diff --git a/homeassistant/components/frontend/www_static/home-assistant-polymer b/homeassistant/components/frontend/www_static/home-assistant-polymer index 82aca23a671..c027f213466 160000 --- a/homeassistant/components/frontend/www_static/home-assistant-polymer +++ b/homeassistant/components/frontend/www_static/home-assistant-polymer @@ -1 +1 @@ -Subproject commit 82aca23a671d021cd2647b7709f58bf6b9e2eb04 +Subproject commit c027f213466a9d1bfb7bc6e8b5f0545d88a43715 From 93bd238be5c1754e431360435d09aa7b845e7542 Mon Sep 17 00:00:00 2001 From: Nolan Gilley Date: Mon, 24 Aug 2015 00:27:26 -0400 Subject: [PATCH 167/224] add return None for get_actiontec_data --- homeassistant/components/device_tracker/actiontec.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/device_tracker/actiontec.py b/homeassistant/components/device_tracker/actiontec.py index 41e209ac506..d9e89ab7157 100644 --- a/homeassistant/components/device_tracker/actiontec.py +++ b/homeassistant/components/device_tracker/actiontec.py @@ -130,6 +130,7 @@ class ActiontecDeviceScanner(object): telnet.read_until(prompt) leases_result = telnet.read_until(prompt).split(b'\n')[1:-1] telnet.write('exit\n'.encode('ascii')) + return None except EOFError: _LOGGER.exception("Unexpected response from router") return From 05df84d04f8fc77570c8c794854b72b6b15b1a9a Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 23 Aug 2015 22:45:00 -0700 Subject: [PATCH 168/224] Frontend bugfix url-sync --- homeassistant/components/frontend/version.py | 2 +- homeassistant/components/frontend/www_static/frontend.html | 2 +- .../components/frontend/www_static/home-assistant-polymer | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py index 1caad17c3b3..930a564063e 100644 --- a/homeassistant/components/frontend/version.py +++ b/homeassistant/components/frontend/version.py @@ -1,2 +1,2 @@ """ DO NOT MODIFY. Auto-generated by build_frontend script """ -VERSION = "e202d8b48a2b501c031920cc9d57999d" +VERSION = "59fdef87aec53cbcb5568e9c4cdc0f0f" diff --git a/homeassistant/components/frontend/www_static/frontend.html b/homeassistant/components/frontend/www_static/frontend.html index 94d2b93b35a..541e17f8cc9 100644 --- a/homeassistant/components/frontend/www_static/frontend.html +++ b/homeassistant/components/frontend/www_static/frontend.html @@ -5068,5 +5068,5 @@ return null!=t?t:null!=e?e:n}function gt(t){var e=new Date;return t._useUTC?[e.g default:return"bookmark"}}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=n,t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){return u["default"](t).format("LT")}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=i;var o=n(55),u=r(o);t.exports=e["default"]},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(2);e["default"]=function(t,e){r.authActions.validate(t,{rememberAuth:e,useStreaming:r.localStoragePreferences.useStreaming})},t.exports=e["default"]},function(t,e,n){"use strict";function r(t,e){var n=null==t?void 0:t[e];return i(n)?n:void 0}var i=n(182);t.exports=r},function(t,e){"use strict";function n(t){return!!t&&"object"==typeof t}t.exports=n},function(t,e,n){"use strict";function r(t){return i(t)&&a.call(t)==o}var i=n(67),o="[object Function]",u=Object.prototype,a=u.toString;t.exports=r},function(t,e){"use strict";function n(t){var e=typeof t;return!!t&&("object"==e||"function"==e)}t.exports=n},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(3),i=["isLoadingEntityHistory"];e.isLoadingEntityHistory=i;var o=["currentEntityHistoryDate"];e.currentDate=o;var u=["entityHistory"];e.entityHistoryMap=u;var a=[o,u,function(t,e){return e.get(t)||r.toImmutable({})}];e.entityHistoryForCurrentDate=a;var s=[o,u,function(t,e){return!!e.get(t)}];e.hasDataForCurrentDate=s;var c=["recentEntityHistory"];e.recentEntityHistoryMap=c;var l=["recentEntityHistory"];e.recentEntityHistoryUpdatedMap=l},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}function o(t){t.registerStores({currentEntityHistoryDate:a["default"],entityHistory:c["default"],isLoadingEntityHistory:f["default"],recentEntityHistory:p["default"],recentEntityHistoryUpdated:v["default"]})}Object.defineProperty(e,"__esModule",{value:!0}),e.register=o;var u=n(194),a=i(u),s=n(195),c=i(s),l=n(196),f=i(l),d=n(197),p=i(d),h=n(198),v=i(h),_=n(193),y=r(_),m=n(68),g=r(m),b=y;e.actions=b;var O=g;e.getters=O},function(t,e,n){"use strict";function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function i(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}Object.defineProperty(e,"__esModule",{value:!0});var o=function(){function t(t,e){for(var n=0;n6e4}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=n,t.exports=e["default"]},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}function o(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(e,"__esModule",{value:!0});var u=n(220),a=n(240),s=i(a),c=n(242),l=i(c),f=n(244),d=i(f),p=n(23),h=r(p),v=n(37),_=r(v),y=n(10),m=r(y),g=n(69),b=r(g),O=n(38),w=r(O),S=n(205),T=r(S),M=n(72),j=r(M),E=n(214),I=r(E),P=n(39),D=r(P),C=n(19),A=r(C),x=n(40),k=r(x),L=n(42),N=r(L),R=n(237),z=r(R),Y=n(11),H=r(Y),G=function F(){o(this,F);var t=s["default"]();Object.defineProperties(this,{demo:{value:!1,enumerable:!0},localStoragePreferences:{value:u.localStoragePreferences,enumerable:!0},reactor:{value:t,enumerable:!0},util:{value:d["default"],enumerable:!0},startLocalStoragePreferencesSync:{value:u.localStoragePreferences.startSync.bind(u.localStoragePreferences,t)},startUrlSync:{value:I.urlSync.startSync.bind(null,t)},stopUrlSync:{value:I.urlSync.stopSync.bind(null,t)}}),l["default"](this,t,{auth:h,config:_,entity:m,entityHistory:b,event:w,logbook:T,moreInfo:j,navigation:I,notification:D,service:A,stream:k,sync:N,voice:z,restApi:H})};e["default"]=G,t.exports=e["default"]},function(t,e,n){"use strict";function r(t,e,n){var r=t?t.length:0;return n&&o(t,e,n)&&(e=!1),r?i(t,e):[]}var i=n(97),o=n(27);t.exports=r},function(t,e){"use strict";function n(t){var e=t?t.length:0;return e?t[e-1]:void 0}t.exports=n},function(t,e,n){"use strict";function r(t,e,n,r){var s=t?t.length:0;return s?(null!=e&&"boolean"!=typeof e&&(r=n,n=u(t,e,r)?void 0:e,e=!1),n=null==n?n:i(n,r,3),e?a(t,n):o(t,n)):[]}var i=n(25),o=n(109),u=n(27),a=n(123);t.exports=r},function(t,e,n){"use strict";function r(t,e,n){var r=a(t)?i:u;return e=o(e,n,3),r(t,e)}var i=n(92),o=n(25),u=n(47),a=n(9);t.exports=r},function(t,e,n){"use strict";function r(t,e){return i(t,o(e))}var i=n(88),o=n(54);t.exports=r},function(t,e,n){"use strict";function r(t,e,n){if(null==t)return[];n&&s(t,e,n)&&(e=void 0);var r=-1;e=i(e,n,3);var c=o(t,function(t,n,i){return{criteria:e(t,n,i),index:++r,value:t}});return u(c,a)}var i=n(25),o=n(47),u=n(107),a=n(113),s=n(27);t.exports=r},function(t,e,n){(function(e){"use strict";function r(t){var e=t?t.length:0;for(this.data={hash:a(null),set:new u};e--;)this.push(t[e])}var i=n(112),o=n(20),u=o(e,"Set"),a=o(Object,"create");r.prototype.push=i,t.exports=r}).call(e,function(){return this}())},function(t,e){"use strict";function n(t,e){for(var n=-1,r=t.length,i=Array(r);++ne&&!o||!i||n&&!u&&a||r&&a)return 1;if(e>t&&!n||!a||o&&!r&&i||u&&i)return-1}return 0}t.exports=n},function(t,e,n){"use strict";var r=n(99),i=n(114),o=i(r);t.exports=o},function(t,e,n){"use strict";function r(t,e,n,c){c||(c=[]);for(var l=-1,f=t.length;++le&&(e=-e>i?0:i+e),n=void 0===n||n>i?i:+n||0,0>n&&(n+=i),i=e>n?0:n-e>>>0,e>>>=0;for(var o=Array(i);++r=a,f=l?u():null,d=[];f?(r=o,c=!1):(l=!1,f=e?[]:d);t:for(;++nc))return!1;for(;++s0;++rc;c++)a._columns[c]=[];var l=0;return r.keySeq().sortBy(function(t){return i(t)}).forEach(function(t){"a"===t?a._demo=!0:i(t)<10?a._badges.push.apply(a._badges,r.get(t).sortBy(function(t){return t.entityDisplay}).toArray()):"group"===t?r.get(t).filter(function(t){return!t.attributes.auto}).forEach(function(t){var r=s.util.expandGroup(t,e);r.forEach(function(t){return u[t.entityId]=!0}),n(t.entityDisplay,r,!1)}):n(t,r.get(t).sortBy(function(t){return t.entityDisplay; }).toArray())}),a},computeStatesOfCard:function(t,e){return t[e]}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o);n(30),n(132),n(57),e["default"]=new u["default"]({is:"logbook-entry",entityClicked:function(t){t.preventDefault(),i.moreInfoActions.selectEntity(this.entryObj.entityId)}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o),a=n(4),s=r(a);n(30),e["default"]=new u["default"]({is:"services-list",behaviors:[s["default"]],properties:{serviceDomains:{type:Array,bindNuclear:[i.serviceGetters.entityMap,function(t){return t.valueSeq().sortBy(function(t){return t.domain}).toJS()}]}},computeServices:function(t){return this.services.get(t).toArray()},serviceClicked:function(t){t.preventDefault(),this.fire("service-selected",{domain:t.model.domain.domain,service:t.model.service})}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(89),o=r(i),u=n(85),a=r(u),s=n(87),c=r(s),l=n(90),f=r(l),d=n(1),p=r(d);e["default"]=new p["default"]({is:"state-history-chart-line",properties:{data:{type:Object,observer:"dataChanged"},unit:{type:String},isSingleDevice:{type:Boolean,value:!1},isAttached:{type:Boolean,value:!1,observer:"dataChanged"}},created:function(){this.style.display="block"},attached:function(){this.isAttached=!0},dataChanged:function(){this.drawChart()},drawChart:function(){if(this.isAttached){for(var t=p["default"].dom(this),e=this.unit,n=this.data;t.lastChild;)t.removeChild(t.lastChild);if(0!==n.length){var r=new google.visualization.LineChart(this),i=new google.visualization.DataTable;i.addColumn({type:"datetime",id:"Time"});var u={legend:{position:"top"},titlePosition:"none",vAxes:{0:{title:e}},hAxis:{format:"H:mm"},lineWidth:1,chartArea:{left:"60",width:"95%"},explorer:{actions:["dragToZoom","rightClickToReset","dragToPan"],keepInBounds:!0,axis:"horizontal",maxZoomIn:.1}};this.isSingleDevice&&(u.legend.position="none",u.vAxes[0].title=null,u.chartArea.left=40,u.chartArea.height="80%",u.chartArea.top=5,u.enableInteractivity=!1);var s=o["default"](a["default"](n),"lastChangedAsDate");s=f["default"](c["default"](s,function(t){return t.getTime()}));for(var l=[],d=new Array(n.length),h=0;hnew Date&&(a=new Date);var s=0;n.forEach(function(e){if(0!==e.length){var n=e[0].entityDisplay,r=void 0,i=null,o=null;e.forEach(function(e){null!==i&&e.state!==i?(r=e.lastChangedAsDate,t(n,i,o,r),i=e.state,o=r):null===i&&(i=e.state,o=e.lastChangedAsDate)}),t(n,i,o,a),s++}}),r.draw(i,{height:55+42*s,timeline:{showRowLabels:n.length>1},hAxis:{format:"H:mm"}})}}}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o),a=n(4),s=r(a);e["default"]=new u["default"]({is:"stream-status",behaviors:[s["default"]],properties:{isStreaming:{type:Boolean,bindNuclear:i.streamGetters.isStreamingEvents},hasError:{type:Boolean,bindNuclear:i.streamGetters.hasStreamingEventsError}},toggleChanged:function(){this.isStreaming?i.streamActions.stop():i.streamActions.start()}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o),a=n(4),s=r(a);n(32),n(58),n(161);var c=["camera"];e["default"]=new u["default"]({is:"more-info-dialog",behaviors:[s["default"]],properties:{stateObj:{type:Object,bindNuclear:i.moreInfoGetters.currentEntity,observer:"stateObjChanged"},stateHistory:{type:Object,bindNuclear:[i.moreInfoGetters.currentEntityHistory,function(t){return t?[t]:!1}]},isLoadingHistoryData:{type:Boolean,bindNuclear:i.entityHistoryGetters.isLoadingEntityHistory},hasHistoryComponent:{type:Boolean,bindNuclear:i.configGetters.isComponentLoaded("history"),observer:"fetchHistoryData"},shouldFetchHistory:{type:Boolean,bindNuclear:i.moreInfoGetters.isCurrentEntityHistoryStale,observer:"fetchHistoryData"},showHistoryComponent:{type:Boolean,value:!1},dialogOpen:{type:Boolean,value:!1,observer:"dialogOpenChanged"}},fetchHistoryData:function(){this.stateObj&&this.hasHistoryComponent&&this.shouldFetchHistory&&i.entityHistoryActions.fetchRecent(this.stateObj.entityId)},stateObjChanged:function(t){var e=this;return t?(this.showHistoryComponent=-1===c.indexOf(this.stateObj.domain)&&this.hasHistoryComponent,void this.async(function(){e.fetchHistoryData(),e.dialogOpen=!0},10)):void(this.dialogOpen=!1)},dialogOpenChanged:function(t){!t&&this.stateObj&&i.moreInfoActions.deselectEntity()}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(4),u=r(o);n(141),n(157),n(156),n(155),n(152),n(153),n(154),n(158),n(149),e["default"]=new Polymer({is:"home-assistant-main",behaviors:[u["default"]],properties:{narrow:{type:Boolean},activePane:{type:String,bindNuclear:i.navigationGetters.activePane,observer:"activePaneChanged"},isSelectedStates:{type:Boolean,bindNuclear:i.navigationGetters.isActivePane("states")},isSelectedHistory:{type:Boolean,bindNuclear:i.navigationGetters.isActivePane("history")},isSelectedLogbook:{type:Boolean,bindNuclear:i.navigationGetters.isActivePane("logbook")},isSelectedDevEvent:{type:Boolean,bindNuclear:i.navigationGetters.isActivePane("devEvent")},isSelectedDevState:{type:Boolean,bindNuclear:i.navigationGetters.isActivePane("devState")},isSelectedDevService:{type:Boolean,bindNuclear:i.navigationGetters.isActivePane("devService")}},listeners:{"open-menu":"openDrawer"},openDrawer:function(){this.$.drawer.openDrawer()},activePaneChanged:function(){this.$.drawer.closeDrawer()},attached:function(){i.startUrlSync()},detached:function(){i.stopUrlSync()}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i),u=n(2),a=n(4),s=r(a),c=n(63),l=r(c);e["default"]=new o["default"]({is:"login-form",behaviors:[s["default"]],properties:{isValidating:{type:Boolean,observer:"isValidatingChanged",bindNuclear:u.authGetters.isValidating},isInvalid:{type:Boolean,bindNuclear:u.authGetters.isInvalidAttempt},errorMessage:{type:String,bindNuclear:u.authGetters.attemptErrorMessage}},listeners:{keydown:"passwordKeyDown","loginButton.click":"validatePassword"},observers:["validatingChanged(isValidating, isInvalid)"],validatingChanged:function(t,e){t||e||(this.$.passwordInput.value="")},isValidatingChanged:function(t){var e=this;t||this.async(function(){return e.$.passwordInput.focus()},10)},passwordKeyDown:function(t){13===t.keyCode?(this.validatePassword(),t.preventDefault()):this.isInvalid&&(this.isInvalid=!1)},validatePassword:function(){this.$.hideKeyboardOnFocus.focus(),l["default"](this.$.passwordInput.value,this.$.rememberLogin.checked)}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o);n(14),n(145),e["default"]=new u["default"]({is:"partial-dev-call-service",properties:{narrow:{type:Boolean,value:!1},domain:{type:String,value:""},service:{type:String,value:""},serviceData:{type:String,value:""}},serviceSelected:function(t){this.domain=t.detail.domain,this.service=t.detail.service},callService:function(){var t=void 0;try{t=this.serviceData?JSON.parse(this.serviceData):{}}catch(e){return void alert("Error parsing JSON: "+e)}i.serviceActions.callService(this.domain,this.service,t)},computeFormClasses:function(t){return"layout "+(t?"vertical":"horizontal")}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o);n(14),n(136),e["default"]=new u["default"]({is:"partial-dev-fire-event",properties:{eventType:{type:String,value:""},eventData:{type:String,value:""}},eventSelected:function(t){this.eventType=t.detail.eventType},fireEvent:function(){var t=void 0;try{t=this.eventData?JSON.parse(this.eventData):{}}catch(e){return void alert("Error parsing JSON: "+e)}i.eventActions.fireEvent(this.eventType,t)},computeFormClasses:function(t){return"layout "+(t?"vertical":"horizontal")}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o);n(14),n(133),e["default"]=new u["default"]({is:"partial-dev-set-state",properties:{entityId:{type:String,value:""},state:{type:String,value:""},stateAttributes:{type:String,value:""}},setStateData:function(t){var e=t?JSON.stringify(t,null," "):"";this.$.inputData.value=e,this.$.inputDataWrapper.update(this.$.inputData)},entitySelected:function(t){var e=i.reactor.evaluate(i.entityGetters.byId(t.detail.entityId));this.entityId=e.entityId,this.state=e.state,this.stateAttributes=JSON.stringify(e.attributes,null," ")},handleSetState:function(){var t=void 0;try{t=this.stateAttributes?JSON.parse(this.stateAttributes):{}}catch(e){return void alert("Error parsing JSON: "+e)}i.entityActions.save({entityId:this.entityId,state:this.state,attributes:t})},computeFormClasses:function(t){return"layout "+(t?"vertical":"horizontal")}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o),a=n(4),s=r(a);n(14),n(58),e["default"]=new u["default"]({is:"partial-history",behaviors:[s["default"]],properties:{narrow:{type:Boolean},isDataLoaded:{type:Boolean,bindNuclear:i.entityHistoryGetters.hasDataForCurrentDate,observer:"isDataLoadedChanged"},stateHistory:{type:Object,bindNuclear:i.entityHistoryGetters.entityHistoryForCurrentDate},isLoadingData:{type:Boolean,bindNuclear:i.entityHistoryGetters.isLoadingEntityHistory},selectedDate:{type:String,value:null,bindNuclear:i.entityHistoryGetters.currentDate}},isDataLoadedChanged:function(t){t||this.async(function(){return i.entityHistoryActions.fetchSelectedDate()},1)},handleRefreshClick:function(){i.entityHistoryActions.fetchSelectedDate()},datepickerFocus:function(){this.datePicker.adjustPosition()},attached:function(){this.datePicker=new Pikaday({field:this.$.datePicker.inputElement,onSelect:i.entityHistoryActions.changeCurrentDate})},detached:function(){this.datePicker.destroy()},computeContentClasses:function(t){return"flex content "+(t?"narrow":"wide")}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o),a=n(4),s=r(a);n(14),n(140),n(31),e["default"]=new u["default"]({is:"partial-logbook",behaviors:[s["default"]],properties:{narrow:{type:Boolean,value:!1},selectedDate:{type:String,bindNuclear:i.logbookGetters.currentDate},isLoading:{type:Boolean,bindNuclear:i.logbookGetters.isLoadingEntries},isStale:{type:Boolean,bindNuclear:i.logbookGetters.isCurrentStale,observer:"isStaleChanged"},entries:{type:Array,bindNuclear:[i.logbookGetters.currentEntries,function(t){return t.toArray()}]},datePicker:{type:Object}},isStaleChanged:function(t){var e=this;t&&this.async(function(){return i.logbookActions.fetchDate(e.selectedDate)},1)},handleRefresh:function(){i.logbookActions.fetchDate(this.selectedDate)},datepickerFocus:function(){this.datePicker.adjustPosition()},attached:function(){this.datePicker=new Pikaday({field:this.$.datePicker.inputElement,onSelect:i.logbookActions.changeCurrentDate})},detached:function(){this.datePicker.destroy()}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o),a=n(4),s=r(a);n(14),n(142),n(143),e["default"]=new u["default"]({is:"partial-zone",behaviors:[s["default"]],properties:{narrow:{type:Boolean,value:!1},isFetching:{type:Boolean,bindNuclear:i.syncGetters.isFetching},isStreaming:{type:Boolean,bindNuclear:i.streamGetters.isStreamingEvents},canListen:{type:Boolean,bindNuclear:[i.voiceGetters.isVoiceSupported,i.configGetters.isComponentLoaded("conversation"),function(t,e){return t&&e}]},isListening:{type:Boolean,bindNuclear:i.voiceGetters.isListening},showListenInterface:{type:Boolean,bindNuclear:[i.voiceGetters.isListening,i.voiceGetters.isTransmitting,function(t,e){return t||e}]},states:{type:Object,bindNuclear:i.entityGetters.visibleEntityMap},columns:{type:Number}},created:function(){this.windowChange=this.windowChange.bind(this)},attached:function(){for(var t=this,e=[],n=0;4>n;n++)e.push(940+350*n);this.mqls=e.map(function(e){var n=window.matchMedia("(min-width: "+e+"px)");return n.addListener(t.windowChange),n}),this.windowChange()},detached:function(){var t=this;this.mqls.forEach(function(e){return e.removeListener(t.windowChange)})},windowChange:function(){this.columns=this.mqls.reduce(function(t,e){return t+e.matches},1)},handleRefresh:function(){i.syncActions.fetchAll()},handleListenClick:function(){this.isListening?i.voiceActions.stop():i.voiceActions.listen()},computeDomains:function(t){return t.keySeq().toArray()},computeStatesOfDomain:function(t,e){return t.get(e).toArray()},computeListenButtonIcon:function(t){return t?"av:mic-off":"av:mic"},computeRefreshButtonClass:function(t){return t?"ha-spin":void 0},toggleMenu:function(){this.fire("open-menu")}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o),a=n(4),s=r(a);e["default"]=new u["default"]({is:"notification-manager",behaviors:[s["default"]],properties:{text:{type:String,bindNuclear:i.notificationGetters.lastNotificationMessage,observer:"showNotification"}},showNotification:function(t){t&&this.$.toast.show()}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);e["default"]=new o["default"]({is:"more-info-camera",properties:{stateObj:{type:Object},dialogOpen:{type:Boolean}},imageLoaded:function(){this.fire("iron-resize")},computeCameraImageUrl:function(t){return t?"/api/camera_proxy_stream/"+this.stateObj.entityId:"data:image/gif;base64,R0lGODlhAQABAAAAACw="}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o),a=n(4),s=r(a);n(31),e["default"]=new u["default"]({is:"more-info-configurator",behaviors:[s["default"]],properties:{stateObj:{type:Object},action:{type:String,value:"display"},isStreaming:{type:Boolean,bindNuclear:i.streamGetters.isStreamingEvents},isConfigurable:{type:Boolean,computed:"computeIsConfigurable(stateObj)"},isConfiguring:{type:Boolean,value:!1},submitCaption:{type:String,computed:"computeSubmitCaption(stateObj)"}},computeIsConfigurable:function(t){return"configure"===t.state},computeSubmitCaption:function(t){return t.attributes.submit_caption||"Set configuration"},submitClicked:function(){var t=this;this.isConfiguring=!0;var e={configure_id:this.stateObj.attributes.configure_id};i.serviceActions.callService("configurator","configure",e).then(function(){t.isConfiguring=!1,t.isStreaming||i.syncActions.fetchAll()},function(){t.isConfiguring=!1})}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i),u=n(176),a=r(u);n(162),n(163),n(167),n(160),n(168),n(166),n(164),n(165),n(159),n(169),e["default"]=new o["default"]({is:"more-info-content",properties:{stateObj:{type:Object,observer:"stateObjChanged"},dialogOpen:{type:Boolean,value:!1,observer:"dialogOpenChanged"}},dialogOpenChanged:function(t){var e=o["default"].dom(this);e.lastChild&&(e.lastChild.dialogOpen=t)},stateObjChanged:function(t,e){var n=o["default"].dom(this);if(!t)return void(n.lastChild&&n.removeChild(n.lastChild));var r=a["default"](t);if(e&&a["default"](e)===r)n.lastChild.dialogOpen=this.dialogOpen,n.lastChild.stateObj=t;else{n.lastChild&&n.removeChild(n.lastChild);var i=document.createElement("more-info-"+r);i.stateObj=t,i.dialogOpen=this.dialogOpen,n.appendChild(i)}}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i),u=["entity_picture","friendly_name","unit_of_measurement"];e["default"]=new o["default"]({is:"more-info-default",properties:{stateObj:{type:Object}},computeDisplayAttributes:function(t){return t?Object.keys(t.attributes).filter(function(t){return-1===u.indexOf(t)}):[]},getAttributeValue:function(t,e){return t.attributes[e]}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o),a=n(4),s=r(a);n(32),e["default"]=new u["default"]({is:"more-info-group",behaviors:[s["default"]],properties:{stateObj:{type:Object},states:{type:Array,bindNuclear:[i.moreInfoGetters.currentEntity,i.entityGetters.entityMap,function(t,e){return t?t.attributes.entity_id.map(e.get.bind(e)):[]}]}},updateStates:function(){this.states=this.stateObj&&this.stateObj.attributes.entity_id?stateStore.gets(this.stateObj.attributes.entity_id).toArray():[]}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o),a=n(34),s=r(a);n(138);var c=["brightness","xy_color"];e["default"]=new u["default"]({is:"more-info-light",properties:{stateObj:{type:Object,observer:"stateObjChanged"},brightnessSliderValue:{type:Number,value:0}},stateObjChanged:function(t){var e=this;t&&"on"===t.state&&(this.brightnessSliderValue=t.attributes.brightness),this.async(function(){return e.fire("iron-resize")},500)},computeClassNames:function(t){return s["default"](t,c)},brightnessSliderChanged:function(t){var e=parseInt(t.target.value,10);isNaN(e)||(0===e?i.serviceActions.callTurnOff(this.stateObj.entityId):i.serviceActions.callService("light","turn_on",{entity_id:this.stateObj.entityId,brightness:e}))},colorPicked:function(t){var e=t.detail.rgb;i.serviceActions.callService("light","turn_on",{entity_id:this.stateObj.entityId,rgb_color:[e.r,e.g,e.b]})}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o),a=n(34),s=r(a),c=["volume_level"];e["default"]=new u["default"]({is:"more-info-media_player",properties:{stateObj:{type:Object,observer:"stateObjChanged"},isOff:{type:Boolean,value:!1},isPlaying:{type:Boolean,value:!1},isMuted:{type:Boolean,value:!1},volumeSliderValue:{type:Number,value:0},supportsPause:{type:Boolean,value:!1},supportsVolumeSet:{type:Boolean,value:!1},supportsVolumeMute:{type:Boolean,value:!1},supportsPreviousTrack:{type:Boolean,value:!1},supportsNextTrack:{type:Boolean,value:!1},supportsTurnOn:{type:Boolean,value:!1},supportsTurnOff:{type:Boolean,value:!1}},stateObjChanged:function(t){var e=this;t&&(this.isOff="off"===t.state,this.isPlaying="playing"===t.state,this.volumeSliderValue=100*t.attributes.volume_level,this.isMuted=t.attributes.is_volume_muted,this.supportsPause=0!==(1&t.attributes.supported_media_commands),this.supportsVolumeSet=0!==(4&t.attributes.supported_media_commands),this.supportsVolumeMute=0!==(8&t.attributes.supported_media_commands),this.supportsPreviousTrack=0!==(16&t.attributes.supported_media_commands),this.supportsNextTrack=0!==(32&t.attributes.supported_media_commands),this.supportsTurnOn=0!==(128&t.attributes.supported_media_commands),this.supportsTurnOff=0!==(256&t.attributes.supported_media_commands)),this.async(function(){return e.fire("iron-resize")},500)},computeClassNames:function(t){return s["default"](t,c)},computeIsOff:function(t){return"off"===t.state},computeMuteVolumeIcon:function(t){return t?"av:volume-off":"av:volume-up"},computePlaybackControlIcon:function(){return this.isPlaying?this.supportsPause?"av:pause":"av:stop":"av:play-arrow"},computeHidePowerButton:function(t,e,n){return t?!e:!n},handleTogglePower:function(){this.callService(this.isOff?"turn_on":"turn_off")},handlePrevious:function(){this.callService("media_previous_track")},handlePlaybackControl:function(){this.callService("media_play_pause")},handleNext:function(){this.callService("media_next_track")},handleVolumeTap:function(){this.supportsVolumeMute&&this.callService("volume_mute",{is_volume_muted:!this.isMuted})},volumeSliderChanged:function(t){var e=parseFloat(t.target.value),n=e>0?e/100:0;this.callService("volume_set",{volume_level:n})},callService:function(t,e){var n=e||{};n.entity_id=this.stateObj.entityId,i.serviceActions.callService("media_player",t,n)}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);e["default"]=new o["default"]({is:"more-info-script",properties:{stateObj:{type:Object}}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(62),u=r(o),a=i.util.parseDateTime;e["default"]=new Polymer({is:"more-info-sun",properties:{stateObj:{type:Object},risingDate:{type:Object,computed:"computeRising(stateObj)"},settingDate:{type:Object,computed:"computeSetting(stateObj)"}},computeRising:function(t){return a(t.attributes.next_rising)},computeSetting:function(t){return a(t.attributes.next_setting)},computeOrder:function(t,e){return t>e?["set","ris"]:["ris","set"]},itemCaption:function(t){return"ris"===t?"Rising ":"Setting "},itemDate:function(t){return"ris"===t?this.risingDate:this.settingDate},itemValue:function(t){return u["default"](this.itemDate(t))}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o),a=n(34),s=r(a),c=["away_mode"];e["default"]=new u["default"]({is:"more-info-thermostat",properties:{stateObj:{type:Object,observer:"stateObjChanged"},tempMin:{type:Number},tempMax:{type:Number},targetTemperatureSliderValue:{type:Number},awayToggleChecked:{type:Boolean}},stateObjChanged:function(t){this.targetTemperatureSliderValue=t.state,this.awayToggleChecked="on"===t.attributes.away_mode,this.tempMin=t.attributes.min_temp,this.tempMax=t.attributes.max_temp},computeClassNames:function(t){return s["default"](t,c)},targetTemperatureSliderChanged:function(t){var e=parseInt(t.target.value,10);isNaN(e)||i.serviceActions.callService("thermostat","set_temperature",{entity_id:this.stateObj.entityId,temperature:e})},toggleChanged:function(t){var e=t.target.checked;e&&"off"===this.stateObj.attributes.away_mode?this.service_set_away(!0):e||"on"!==this.stateObj.attributes.away_mode||this.service_set_away(!1)},service_set_away:function(t){var e=this;i.serviceActions.callService("thermostat","set_away_mode",{away_mode:t,entity_id:this.stateObj.entityId}).then(function(){return e.stateObjChanged(e.stateObj)})}}),t.exports=e["default"]},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(2);e["default"]=new Polymer({is:"more-info-updater",properties:{stateObj:{type:Object}},updateTapped:function(){r.serviceActions.callService("updater","update",{})},linkTapped:function(){window.open(this.stateObj.attributes.link,"_blank")}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(17),n(33),e["default"]=new o["default"]({is:"state-card-configurator",properties:{stateObj:{type:Object}}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(17);var u=["playing","paused"];e["default"]=new o["default"]({is:"state-card-media_player",properties:{stateObj:{type:Object},isPlaying:{type:Boolean,computed:"computeIsPlaying(stateObj)"}},computeIsPlaying:function(t){return-1!==u.indexOf(t.state)},computePrimaryText:function(t,e){return e?t.attributes.media_title:t.stateDisplay},computeSecondaryText:function(t){var e=void 0;return"music"===t.attributes.media_content_type?t.attributes.media_artist:"tvshow"===t.attributes.media_content_type?(e=t.attributes.media_series_title,t.attributes.media_season&&t.attributes.media_episode&&(e+=" S"+t.attributes.media_season+"E"+t.attributes.media_episode),e):t.attributes.app_name?t.attributes.app_name:""}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(33),n(59),e["default"]=new o["default"]({is:"state-card-scene",properties:{stateObj:{type:Object},allowToggle:{type:Boolean,value:!1,computed:"computeAllowToggle(stateObj)"}},computeAllowToggle:function(t){return"off"===t.state||t.attributes.active_requested}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(17),e["default"]=new o["default"]({is:"state-card-thermostat",properties:{stateObj:{type:Object}}}),t.exports=e["default"]},function(t,e){"use strict";function n(t){return{attached:function(){var e=this;this.__unwatchFns=Object.keys(this.properties).reduce(function(n,r){if(!("bindNuclear"in e.properties[r]))return n;var i=e.properties[r].bindNuclear;if(!i)throw new Error("Undefined getter specified for key "+r);return e[r]=t.evaluate(i),n.concat(t.observe(i,function(t){e[r]=t}))},[])},detached:function(){for(;this.__unwatchFns.length;)this.__unwatchFns.shift()()}}}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=n,t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){return-1!==a.indexOf(t.domain)?t.domain:u["default"](t.entityId)?"toggle":"display"}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=i;var o=n(60),u=r(o),a=["thermostat","configurator","scene","media_player"];t.exports=e["default"]},function(t,e){"use strict";function n(t){return-1!==r.indexOf(t.domain)?t.domain:"default"}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=n;var r=["light","group","sun","configurator","thermostat","script","media_player","camera","updater"];t.exports=e["default"]},function(t,e){"use strict";function n(t,e,n){var r=1-t-e,i=n/255,o=i/e*t,u=i/e*r,a=1.612*o-.203*i-.302*u,s=.509*-o+1.412*i+.066*u,c=.026*o-.072*i+.962*u;a=.0031308>=a?12.92*a:1.055*Math.pow(a,1/2.4)-.055,s=.0031308>=s?12.92*s:1.055*Math.pow(s,1/2.4)-.055,c=.0031308>=c?12.92*c:1.055*Math.pow(c,1/2.4)-.055;var l=Math.max(a,s,c);return a/=l,s/=l,c/=l,a=255*a,0>a&&(a=255),s=255*s,0>s&&(s=255),c=255*c,0>c&&(c=255),[a,s,c]}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=n,t.exports=e["default"]},function(t,e,n){var r;(function(t,i,o,u){"use strict";(function(){function a(t){return"function"==typeof t||"object"==typeof t&&null!==t}function s(t){return"function"==typeof t}function c(t){return"object"==typeof t&&null!==t}function l(t){K=t}function f(t){X=t}function d(){var e=t.nextTick,n=t.versions.node.match(/^(?:(\d+)\.)?(?:(\d+)\.)?(\*|\d+)$/);return Array.isArray(n)&&"0"===n[1]&&"10"===n[2]&&(e=i),function(){e(y)}}function p(){return function(){W(y)}}function h(){var t=0,e=new et(y),n=document.createTextNode("");return e.observe(n,{characterData:!0}),function(){n.data=t=++t%2}}function v(){var t=new MessageChannel;return t.port1.onmessage=y,function(){t.port2.postMessage(0)}}function _(){return function(){setTimeout(y,1)}}function y(){for(var t=0;Z>t;t+=2){var e=it[t],n=it[t+1];e(n),it[t]=void 0,it[t+1]=void 0}Z=0}function m(){try{var t=n(250);return W=t.runOnLoop||t.runOnContext,p()}catch(e){return _()}}function g(){}function b(){return new TypeError("You cannot resolve a promise with itself")}function O(){return new TypeError("A promises callback cannot return that same promise.")}function w(t){try{return t.then}catch(e){return st.error=e,st}}function S(t,e,n,r){try{t.call(e,n,r)}catch(i){return i}}function T(t,e,n){X(function(t){var r=!1,i=S(n,e,function(n){r||(r=!0,e!==n?E(t,n):P(t,n))},function(e){r||(r=!0,D(t,e))},"Settle: "+(t._label||" unknown promise"));!r&&i&&(r=!0,D(t,i))},t)}function M(t,e){e._state===ut?P(t,e._result):e._state===at?D(t,e._result):C(e,void 0,function(e){E(t,e)},function(e){D(t,e)})}function j(t,e){if(e.constructor===t.constructor)M(t,e);else{var n=w(e);n===st?D(t,st.error):void 0===n?P(t,e):s(n)?T(t,e,n):P(t,e)}}function E(t,e){t===e?D(t,b()):a(e)?j(t,e):P(t,e)}function I(t){t._onerror&&t._onerror(t._result),A(t)}function P(t,e){t._state===ot&&(t._result=e,t._state=ut,0!==t._subscribers.length&&X(A,t))}function D(t,e){t._state===ot&&(t._state=at,t._result=e,X(I,t))}function C(t,e,n,r){var i=t._subscribers,o=i.length;t._onerror=null,i[o]=e,i[o+ut]=n,i[o+at]=r,0===o&&t._state&&X(A,t)}function A(t){var e=t._subscribers,n=t._state;if(0!==e.length){for(var r,i,o=t._result,u=0;uu;u++)C(r.resolve(t[u]),void 0,e,n);return i}function H(t){var e=this;if(t&&"object"==typeof t&&t.constructor===e)return t;var n=new e(g);return E(n,t),n}function G(t){var e=this,n=new e(g);return D(n,t),n}function F(){throw new TypeError("You must pass a resolver function as the first argument to the promise constructor")}function U(){throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.")}function B(t){this._id=vt++,this._state=void 0,this._result=void 0,this._subscribers=[],g!==t&&(s(t)||F(),this instanceof B||U(),N(this,t))}function V(){var t;if("undefined"!=typeof o)t=o;else if("undefined"!=typeof self)t=self;else try{t=Function("return this")()}catch(e){throw new Error("polyfill failed because global object is unavailable in this environment")}var n=t.Promise;(!n||"[object Promise]"!==Object.prototype.toString.call(n.resolve())||n.cast)&&(t.Promise=_t)}var q;q=Array.isArray?Array.isArray:function(t){return"[object Array]"===Object.prototype.toString.call(t)};var W,K,J,$=q,Z=0,X=({}.toString,function(t,e){it[Z]=t,it[Z+1]=e,Z+=2,2===Z&&(K?K(y):J())}),Q="undefined"!=typeof window?window:void 0,tt=Q||{},et=tt.MutationObserver||tt.WebKitMutationObserver,nt="undefined"!=typeof t&&"[object process]"==={}.toString.call(t),rt="undefined"!=typeof Uint8ClampedArray&&"undefined"!=typeof importScripts&&"undefined"!=typeof MessageChannel,it=new Array(1e3);J=nt?d():et?h():rt?v():void 0===Q?m():_();var ot=void 0,ut=1,at=2,st=new x,ct=new x;R.prototype._validateInput=function(t){return $(t)},R.prototype._validationError=function(){return new Error("Array Methods must be provided an Array")},R.prototype._init=function(){this._result=new Array(this.length)};var lt=R;R.prototype._enumerate=function(){for(var t=this,e=t.length,n=t.promise,r=t._input,i=0;n._state===ot&&e>i;i++)t._eachEntry(r[i],i)},R.prototype._eachEntry=function(t,e){var n=this,r=n._instanceConstructor;c(t)?t.constructor===r&&t._state!==ot?(t._onerror=null,n._settledAt(t._state,e,t._result)):n._willSettleAt(r.resolve(t),e):(n._remaining--,n._result[e]=t)},R.prototype._settledAt=function(t,e,n){var r=this,i=r.promise;i._state===ot&&(r._remaining--,t===at?D(i,n):r._result[e]=n),0===r._remaining&&P(i,r._result)},R.prototype._willSettleAt=function(t,e){var n=this;C(t,void 0,function(t){n._settledAt(ut,e,t)},function(t){n._settledAt(at,e,t)})};var ft=z,dt=Y,pt=H,ht=G,vt=0,_t=B;B.all=ft,B.race=dt,B.resolve=pt,B.reject=ht,B._setScheduler=l,B._setAsap=f,B._asap=X,B.prototype={constructor:B,then:function(t,e){var n=this,r=n._state;if(r===ut&&!t||r===at&&!e)return this;var i=new this.constructor(g),o=n._result;if(r){var u=arguments[r-1];X(function(){L(r,i,u,o)})}else C(n,i,t,e);return i},"catch":function(t){return this.then(null,t)}};var yt=V,mt={Promise:_t,polyfill:yt};n(249).amd?(r=function(){return mt}.call(e,n,e,u),!(void 0!==r&&(u.exports=r))):"undefined"!=typeof u&&u.exports?u.exports=mt:"undefined"!=typeof this&&(this.ES6Promise=mt),yt()}).call(void 0)}).call(e,n(245),n(246).setImmediate,function(){return this}(),n(247)(t))},function(t,e,n){"use strict";var r=n(64),i=r(Date,"now"),o=i||function(){return(new Date).getTime()};t.exports=o},function(t,e){"use strict";function n(t){return"number"==typeof t&&t>-1&&t%1==0&&r>=t}var r=9007199254740991;t.exports=n},function(t,e,n){"use strict";var r=n(64),i=n(180),o=n(65),u="[object Array]",a=Object.prototype,s=a.toString,c=r(Array,"isArray"),l=c||function(t){return o(t)&&i(t.length)&&s.call(t)==u};t.exports=l},function(t,e,n){"use strict";function r(t){return null==t?!1:i(t)?l.test(s.call(t)):o(t)&&u.test(t)}var i=n(66),o=n(65),u=/^\[object .+?Constructor\]$/,a=Object.prototype,s=Function.prototype.toString,c=a.hasOwnProperty,l=RegExp("^"+s.call(c).replace(/[\\^$.*+?()[\]{}|]/g,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$");t.exports=r},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(178),i=n(23),o=function(t,e,n){var o=arguments.length<=3||void 0===arguments[3]?null:arguments[3],u=t.evaluate(i.getters.authInfo),a=u.host+"/api/"+n;return new r.Promise(function(t,n){var r=new XMLHttpRequest;r.open(e,a,!0),r.setRequestHeader("X-HA-access",u.authToken),r.onload=function(){if(r.status>199&&r.status<300)t(JSON.parse(r.responseText));else try{n(JSON.parse(r.responseText))}catch(e){n({})}},r.onerror=function(){return n({})},o?r.send(JSON.stringify(o)):r.send()})};e["default"]=o,t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){var n=arguments.length<=2||void 0===arguments[2]?{}:arguments[2],r=n.useStreaming,i=void 0===r?t.evaluate(s.getters.isSupported):r,o=n.rememberAuth,u=void 0===o?!1:o,f=n.host,d=void 0===f?"":f;t.dispatch(a["default"].VALIDATING_AUTH_TOKEN,{authToken:e,host:d}),c.actions.fetchAll(t).then(function(){t.dispatch(a["default"].VALID_AUTH_TOKEN,{authToken:e,host:d,rememberAuth:u}),i?s.actions.start(t,{syncOnInitialConnect:!1}):c.actions.start(t,{skipInitialSync:!0})},function(){var e=arguments.length<=0||void 0===arguments[0]?{}:arguments[0],n=e.message,r=void 0===n?l:n;t.dispatch(a["default"].INVALID_AUTH_TOKEN,{errorMessage:r})})}function o(t){t.dispatch(a["default"].LOG_OUT,{})}Object.defineProperty(e,"__esModule",{value:!0}),e.validate=i,e.logOut=o;var u=n(22),a=r(u),s=n(40),c=n(42),l="Unexpected result from API"},function(t,e){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var n=[["authAttempt","isValidating"],function(t){return!!t}];e.isValidating=n;var r=[["authAttempt","isInvalid"],function(t){return!!t}];e.isInvalidAttempt=r;var i=["authAttempt","errorMessage"];e.attemptErrorMessage=i;var o=["rememberAuth"];e.rememberAuth=o;var u=[["authAttempt","authToken"],["authAttempt","host"],function(t,e){return{authToken:t,host:e}}];e.attemptAuthInfo=u;var a=["authCurrent","authToken"];e.currentAuthToken=a;var s=[a,["authCurrent","host"],function(t,e){return{authToken:t,host:e}}];e.currentAuthInfo=s;var c=[n,["authAttempt","authToken"],["authCurrent","authToken"],function(t,e,n){return t?e:n}];e.authToken=c;var l=[n,u,s,function(t,e,n){return t?e:n}];e.authInfo=l},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){if(null==t)throw new TypeError("Cannot destructure undefined")}function o(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function u(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}function a(t,e){var n=e.authToken,r=e.host;return d.toImmutable({authToken:n,host:r,isValidating:"true",isInvalid:!1,errorMessage:""})}function s(t,e){return i(e),_.getInitialState()}function c(t,e){var n=e.errorMessage;return t.withMutations(function(t){return t.set("isValidating",!1).set("isInvalid","true").set("errorMessage",n)})}Object.defineProperty(e,"__esModule",{value:!0});var l=function(){function t(t,e){for(var n=0;n1&&t.set(p,r)})}function a(){return v.getInitialState()}Object.defineProperty(e,"__esModule",{value:!0});var s=function(){function t(t,e){for(var n=0;no}Object.defineProperty(e,"__esModule",{value:!0});var i=n(3),o=6e4,u=["currentLogbookDate"];e.currentDate=u;var a=[u,["logbookEntriesUpdated"],function(t,e){return r(e.get(t))}];e.isCurrentStale=a;var s=[u,["logbookEntries"],function(t,e){return e.get(t)||i.toImmutable([])}];e.currentEntries=s;var c=["isLoadingLogbookEntries"];e.isLoadingEntries=c},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}function o(t){t.registerStores({currentLogbookDate:a["default"],isLoadingLogbookEntries:c["default"],logbookEntries:f["default"],logbookEntriesUpdated:p["default"]})}Object.defineProperty(e,"__esModule",{value:!0}),e.register=o;var u=n(207),a=i(u),s=n(208),c=i(s),l=n(209),f=i(l),d=n(210),p=i(d),h=n(203),v=r(h),_=n(204),y=r(_),m=v;e.actions=m;var g=y;e.getters=g},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function o(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}Object.defineProperty(e,"__esModule",{value:!0});var u=function(){function t(t,e){for(var n=0;n1)for(var n=1;n=0&&(t._idleTimeoutId=setTimeout(function(){t._onTimeout&&t._onTimeout()},e))},e.setImmediate="function"==typeof t?t:function(t){var n=c++,r=arguments.length<2?!1:a.call(arguments,1);return s[n]=!0,o(function(){s[n]&&(r?t.apply(null,r):t.call(null),e.clearImmediate(n))}),n},e.clearImmediate="function"==typeof r?r:function(t){delete s[t]}}).call(e,n(21).setImmediate,n(21).clearImmediate)},function(t,e){"use strict";t.exports=function(t){return t.webpackPolyfill||(t.deprecate=function(){},t.paths=[],t.children=[],t.webpackPolyfill=1),t}},function(t,e){function n(t){throw new Error("Cannot find module '"+t+"'.")}n.keys=function(){return[]},n.resolve=n,t.exports=n,n.id=248},function(t,e){t.exports=function(){throw new Error("define cannot be used indirect")}},function(t,e){}]); \ No newline at end of file diff --git a/homeassistant/components/frontend/www_static/home-assistant-polymer b/homeassistant/components/frontend/www_static/home-assistant-polymer index c027f213466..b26ee9b9a65 160000 --- a/homeassistant/components/frontend/www_static/home-assistant-polymer +++ b/homeassistant/components/frontend/www_static/home-assistant-polymer @@ -1 +1 @@ -Subproject commit c027f213466a9d1bfb7bc6e8b5f0545d88a43715 +Subproject commit b26ee9b9a652ce5af84b1d179477da01f04cfeb0 From a7889ef628e7b435346c81403ea026cffbc6d11d Mon Sep 17 00:00:00 2001 From: theolind Date: Mon, 24 Aug 2015 21:19:47 +0200 Subject: [PATCH 169/224] Automation component now supports YAML lists for config key service_entity_id --- homeassistant/components/automation/__init__.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 54d16bcca37..8dcb158dea4 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -62,8 +62,12 @@ def _get_action(hass, config): service_data = {} if CONF_SERVICE_ENTITY_ID in config: - service_data[ATTR_ENTITY_ID] = \ - config[CONF_SERVICE_ENTITY_ID].split(",") + try: + service_data[ATTR_ENTITY_ID] = \ + config[CONF_SERVICE_ENTITY_ID].split(",") + except AttributeError: + service_data[ATTR_ENTITY_ID] = \ + config[CONF_SERVICE_ENTITY_ID] hass.services.call(domain, service, service_data) From 089cd0ff8ae26cccf46e127dd1c18e4ffd69b612 Mon Sep 17 00:00:00 2001 From: theolind Date: Tue, 25 Aug 2015 06:50:20 +0200 Subject: [PATCH 170/224] Added test for Automation component service id list --- tests/components/automation/test_init.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index 2af17ea405c..507c37dc20a 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -78,3 +78,18 @@ class TestAutomationEvent(unittest.TestCase): self.hass.pool.block_till_done() self.assertEqual(1, len(self.calls)) self.assertEqual(['hello.world'], self.calls[0].data[ATTR_ENTITY_ID]) + + def test_service_specify_entity_id_list(self): + automation.setup(self.hass, { + automation.DOMAIN: { + CONF_PLATFORM: 'event', + event.CONF_EVENT_TYPE: 'test_event', + automation.CONF_SERVICE: 'test.automation', + automation.CONF_SERVICE_ENTITY_ID: ['hello.world', 'hello.world2'] + } + }) + + self.hass.bus.fire('test_event') + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) + self.assertEqual(['hello.world', 'hello.world2'], self.calls[0].data[ATTR_ENTITY_ID]) From 917db18b29e37685517bde78b827c41729f3512d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 24 Aug 2015 23:31:54 -0700 Subject: [PATCH 171/224] Fix netgear username/password combo --- homeassistant/components/device_tracker/netgear.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/device_tracker/netgear.py b/homeassistant/components/device_tracker/netgear.py index 269b6eb1eab..c04a1d07b1f 100644 --- a/homeassistant/components/device_tracker/netgear.py +++ b/homeassistant/components/device_tracker/netgear.py @@ -56,7 +56,7 @@ def get_scanner(hass, config): _LOGGER.warning('Found username or password but no host') return None - scanner = NetgearDeviceScanner(host, password, username) + scanner = NetgearDeviceScanner(host, username, password) return scanner if scanner.success_init else None From 047b4abd822e8ad65b45d2fa66d531bcc254d221 Mon Sep 17 00:00:00 2001 From: Nolan Gilley Date: Tue, 25 Aug 2015 09:39:00 -0400 Subject: [PATCH 172/224] Fix get_device_name and get_actiontec_data --- homeassistant/components/device_tracker/actiontec.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/device_tracker/actiontec.py b/homeassistant/components/device_tracker/actiontec.py index d9e89ab7157..bbf20b09232 100644 --- a/homeassistant/components/device_tracker/actiontec.py +++ b/homeassistant/components/device_tracker/actiontec.py @@ -95,8 +95,8 @@ class ActiontecDeviceScanner(object): if not self.last_results: return None for client in self.last_results: - if client == device: - return client + if client['mac'] == device: + return client['ip'] return None @Throttle(MIN_TIME_BETWEEN_SCANS) @@ -130,14 +130,13 @@ class ActiontecDeviceScanner(object): telnet.read_until(prompt) leases_result = telnet.read_until(prompt).split(b'\n')[1:-1] telnet.write('exit\n'.encode('ascii')) - return None except EOFError: _LOGGER.exception("Unexpected response from router") return except ConnectionRefusedError: _LOGGER.exception("Connection refused by router," + " is telnet enabled?") - return + return None devices = {} for lease in leases_result: From bc5a7564b16d09beb0a5cd7b8bb3afddf8662a0e Mon Sep 17 00:00:00 2001 From: Nolan Gilley Date: Tue, 25 Aug 2015 10:09:47 -0400 Subject: [PATCH 173/224] fix formatting --- homeassistant/components/device_tracker/actiontec.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/device_tracker/actiontec.py b/homeassistant/components/device_tracker/actiontec.py index bbf20b09232..06956475ba0 100644 --- a/homeassistant/components/device_tracker/actiontec.py +++ b/homeassistant/components/device_tracker/actiontec.py @@ -123,8 +123,8 @@ class ActiontecDeviceScanner(object): telnet.write((self.username + '\n').encode('ascii')) telnet.read_until(b'Password: ') telnet.write((self.password + '\n').encode('ascii')) - prompt = telnet.read_until(b'Wireless Broadband Router> ', - '').split(b'\n')[-1] + prompt = telnet.read_until( + b'Wireless Broadband Router> ').split(b'\n')[-1] telnet.write('firewall mac_cache_dump\n'.encode('ascii')) telnet.write('\n'.encode('ascii')) telnet.read_until(prompt) From 0a9d82fe6f8def0deea2498f4189b6e275033281 Mon Sep 17 00:00:00 2001 From: sfam Date: Tue, 25 Aug 2015 23:23:51 +0100 Subject: [PATCH 174/224] Fix RPi_GPIO switch and add extra parameters --- homeassistant/components/switch/rpi_gpio.py | 23 ++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/switch/rpi_gpio.py b/homeassistant/components/switch/rpi_gpio.py index 127d02a121d..9c1caaea0b1 100644 --- a/homeassistant/components/switch/rpi_gpio.py +++ b/homeassistant/components/switch/rpi_gpio.py @@ -2,21 +2,28 @@ homeassistant.components.switch.rpi_gpio ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Allows to control the GPIO pins of a Raspberry Pi. +Note: To use RPi GPIO, Home Assistant must be run as root. Configuration: switch: platform: rpi_gpio + active_state: "HIGH" ports: 11: Fan Office 12: Light Desk Variables: +active_state +*Optional +Defines which GPIO state corresponds to a ACTIVE switch. Default is HIGH. + ports *Required An array specifying the GPIO ports to use and the name to use in the frontend. """ + import logging try: import RPi.GPIO as GPIO @@ -27,6 +34,8 @@ from homeassistant.const import (DEVICE_DEFAULT_NAME, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) +DEFAULT_ACTIVE_STATE = "HIGH" + REQUIREMENTS = ['RPi.GPIO>=0.5.11'] _LOGGER = logging.getLogger(__name__) @@ -38,10 +47,13 @@ def setup_platform(hass, config, add_devices, discovery_info=None): _LOGGER.error('RPi.GPIO not available. rpi_gpio ports ignored.') return + GPIO.setmode(GPIO.BCM) + switches = [] + active_state = config.get('active_state', DEFAULT_ACTIVE_STATE) ports = config.get('ports') for port_num, port_name in ports.items(): - switches.append(RPiGPIOSwitch(port_name, port_num)) + switches.append(RPiGPIOSwitch(port_name, port_num, active_state)) add_devices(switches) def cleanup_gpio(event): @@ -59,10 +71,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class RPiGPIOSwitch(ToggleEntity): """ Represents a port that can be toggled using Raspberry Pi GPIO. """ - def __init__(self, name, gpio): + def __init__(self, name, gpio, active_state): self._name = name or DEVICE_DEFAULT_NAME - self._state = False + self._state = False if self._active_state == "HIGH" else True self._gpio = gpio + self._active_state = active_state # pylint: disable=no-member GPIO.setup(gpio, GPIO.OUT) @@ -83,13 +96,13 @@ class RPiGPIOSwitch(ToggleEntity): def turn_on(self, **kwargs): """ Turn the device on. """ - if self._switch(True): + if self._switch(True if self._active_state == "HIGH" else False): self._state = True self.update_ha_state() def turn_off(self, **kwargs): """ Turn the device off. """ - if self._switch(False): + if self._switch(False if self._active_state == "HIGH" else True): self._state = False self.update_ha_state() From ab5a5699223dd795e9bd01e27bae4df0e74169a9 Mon Sep 17 00:00:00 2001 From: sfam Date: Tue, 25 Aug 2015 23:24:36 +0100 Subject: [PATCH 175/224] Add RPi GPIO sensor --- homeassistant/components/sensor/rpi_gpio.py | 130 ++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 homeassistant/components/sensor/rpi_gpio.py diff --git a/homeassistant/components/sensor/rpi_gpio.py b/homeassistant/components/sensor/rpi_gpio.py new file mode 100644 index 00000000000..1f5cb072ca6 --- /dev/null +++ b/homeassistant/components/sensor/rpi_gpio.py @@ -0,0 +1,130 @@ +# -*- coding: utf-8 -*- +""" +homeassistant.components.sensor.rpi_gpio +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Allows to configure a binary state sensor using RPi GPIO. +Note: To use RPi GPIO, Home Assistant must be run as root. + +sensor: + platform: rpi_gpio + pull_mode: "UP" + value_high: "Active" + value_low: "Inactive" + ports: + 11: PIR Office + 12: PIR Bedroom + +Variables: + +pull_mode +*Optional +The internal pull to use (UP or DOWN). Default is UP. + +value_high +*Optional +The value of the sensor when the port is HIGH. Default is "HIGH". + +value_low +*Optional +The value of the sensor when the port is LOW. Default is "LOW". + +bouncetime +*Optional +The time in milliseconds for port debouncing. Default is 50ms. + +ports +*Required +An array specifying the GPIO ports to use and the name to use in the frontend. + +""" + +import logging +from homeassistant.helpers.entity import Entity +try: + import RPi.GPIO as GPIO +except ImportError: + GPIO = None +from homeassistant.const import (DEVICE_DEFAULT_NAME, + EVENT_HOMEASSISTANT_START, + EVENT_HOMEASSISTANT_STOP) + +DEFAULT_PULL_MODE = "UP" +DEFAULT_VALUE_HIGH = "HIGH" +DEFAULT_VALUE_LOW = "LOW" +DEFAULT_BOUNCETIME = 50 + +REQUIREMENTS = ['RPi.GPIO>=0.5.11'] +_LOGGER = logging.getLogger(__name__) + + +# pylint: disable=unused-argument +def setup_platform(hass, config, add_devices, discovery_info=None): + """ Sets up the Raspberry PI GPIO ports. """ + if GPIO is None: + _LOGGER.error('RPi.GPIO not available. rpi_gpio ports ignored.') + return + + GPIO.setmode(GPIO.BCM) + + sensors = [] + pull_mode = config.get('pull_mode', DEFAULT_PULL_MODE) + value_high = config.get('value_high', DEFAULT_VALUE_HIGH) + value_low = config.get('value_low', DEFAULT_VALUE_LOW) + bouncetime = config.get('bouncetime', DEFAULT_BOUNCETIME) + ports = config.get('ports') + for port_num, port_name in ports.items(): + sensors.append(RPiGPIOSensor( + port_name, port_num, pull_mode, + value_high, value_low, bouncetime)) + add_devices(sensors) + + def cleanup_gpio(event): + """ Stuff to do before stop home assistant. """ + # pylint: disable=no-member + GPIO.cleanup() + + def prepare_gpio(event): + """ Stuff to do when home assistant starts. """ + hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, cleanup_gpio) + + hass.bus.listen_once(EVENT_HOMEASSISTANT_START, prepare_gpio) + + +class RPiGPIOSensor(Entity): + """ Sets up the Raspberry PI GPIO ports. """ + def __init__(self, port_name, port_num, pull_mode, + value_high, value_low, bouncetime): + self._name = port_name + self._port = port_num + self._pull = GPIO.PUD_DOWN if pull_mode=="DOWN" else GPIO.PUD_UP + self._vhigh = value_high + self._vlow = value_low + self._bouncetime = bouncetime + GPIO.setup(self._port, GPIO.IN, pull_up_down=self._pull) + self._state = self._vhigh if GPIO.input(self._port) else self._vlow + + def edge_callback(channel): + """ port changed state """ + self._state = self._vhigh if GPIO.input(channel) else self._vlow + self.update_ha_state() + + GPIO.add_event_detect( + self._port, + GPIO.BOTH, + callback=edge_callback, + bouncetime=self._bouncetime) + + @property + def should_poll(self): + """ No polling needed """ + return False + + @property + def name(self): + """ The name of the sensor """ + return self._name + + @property + def state(self): + """ Returns the state of the entity. """ + return self._state From cfc2232c22f51c7aadfc258ba6a3adced62c668c Mon Sep 17 00:00:00 2001 From: sfam Date: Wed, 26 Aug 2015 00:02:52 +0100 Subject: [PATCH 176/224] fix pylint warnings --- homeassistant/components/switch/rpi_gpio.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/switch/rpi_gpio.py b/homeassistant/components/switch/rpi_gpio.py index 9c1caaea0b1..a1f9f61192b 100644 --- a/homeassistant/components/switch/rpi_gpio.py +++ b/homeassistant/components/switch/rpi_gpio.py @@ -46,7 +46,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): if GPIO is None: _LOGGER.error('RPi.GPIO not available. rpi_gpio ports ignored.') return - + # pylint: disable=no-member GPIO.setmode(GPIO.BCM) switches = [] @@ -73,9 +73,9 @@ class RPiGPIOSwitch(ToggleEntity): def __init__(self, name, gpio, active_state): self._name = name or DEVICE_DEFAULT_NAME - self._state = False if self._active_state == "HIGH" else True self._gpio = gpio self._active_state = active_state + self._state = False if self._active_state == "HIGH" else True # pylint: disable=no-member GPIO.setup(gpio, GPIO.OUT) From c194121da693ba2dbf39ce1af196a96bb8f3452f Mon Sep 17 00:00:00 2001 From: sfam Date: Wed, 26 Aug 2015 00:11:07 +0100 Subject: [PATCH 177/224] fix pylint warnings --- homeassistant/components/sensor/rpi_gpio.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sensor/rpi_gpio.py b/homeassistant/components/sensor/rpi_gpio.py index 1f5cb072ca6..ee410be6ed4 100644 --- a/homeassistant/components/sensor/rpi_gpio.py +++ b/homeassistant/components/sensor/rpi_gpio.py @@ -63,7 +63,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): if GPIO is None: _LOGGER.error('RPi.GPIO not available. rpi_gpio ports ignored.') return - + # pylint: disable=no-member GPIO.setmode(GPIO.BCM) sensors = [] @@ -94,9 +94,10 @@ class RPiGPIOSensor(Entity): """ Sets up the Raspberry PI GPIO ports. """ def __init__(self, port_name, port_num, pull_mode, value_high, value_low, bouncetime): - self._name = port_name + # pylint: disable=no-member + self._name = port_name or DEVICE_DEFAULT_NAME self._port = port_num - self._pull = GPIO.PUD_DOWN if pull_mode=="DOWN" else GPIO.PUD_UP + self._pull = GPIO.PUD_DOWN if pull_mode == "DOWN" else GPIO.PUD_UP self._vhigh = value_high self._vlow = value_low self._bouncetime = bouncetime @@ -105,6 +106,7 @@ class RPiGPIOSensor(Entity): def edge_callback(channel): """ port changed state """ + # pylint: disable=no-member self._state = self._vhigh if GPIO.input(channel) else self._vlow self.update_ha_state() From 930036272bbfbb1e29466e58e08e2395dd8434f7 Mon Sep 17 00:00:00 2001 From: sfam Date: Wed, 26 Aug 2015 00:15:57 +0100 Subject: [PATCH 178/224] fix pylint warnings --- homeassistant/components/sensor/rpi_gpio.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/sensor/rpi_gpio.py b/homeassistant/components/sensor/rpi_gpio.py index ee410be6ed4..c57cf31b397 100644 --- a/homeassistant/components/sensor/rpi_gpio.py +++ b/homeassistant/components/sensor/rpi_gpio.py @@ -90,6 +90,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): hass.bus.listen_once(EVENT_HOMEASSISTANT_START, prepare_gpio) +# pylint: disable=too-many-arguments, too-many-instance-attributes class RPiGPIOSensor(Entity): """ Sets up the Raspberry PI GPIO ports. """ def __init__(self, port_name, port_num, pull_mode, From c12b7e70d956b67df259b672530b0aa8d0d2456c Mon Sep 17 00:00:00 2001 From: zyell Date: Thu, 13 Aug 2015 11:38:57 -0700 Subject: [PATCH 179/224] Updated to support Nest API changes and fix set_temp and away_mode and pull in new required python-nest version --- homeassistant/components/thermostat/nest.py | 42 +++++++++++++++++++-- requirements.txt | 2 +- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/thermostat/nest.py b/homeassistant/components/thermostat/nest.py index b2e48b96bcd..5b50c444dc7 100644 --- a/homeassistant/components/thermostat/nest.py +++ b/homeassistant/components/thermostat/nest.py @@ -34,7 +34,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): napi = nest.Nest(username, password) add_devices([ - NestThermostat(structure, device) + NestThermostat(nest.utils, structure, device) for structure in napi.structures for device in structure.devices ]) @@ -43,14 +43,23 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class NestThermostat(ThermostatDevice): """ Represents a Nest thermostat within Home Assistant. """ - def __init__(self, structure, device): + def __init__(self, nest_utils, structure, device): self.structure = structure self.device = device + self.nest_utils = nest_utils @property def name(self): """ Returns the name of the nest, if any. """ - return self.device.name + location = self.device.where + name = self.device.name + if location is None: + return name + else: + if name == '': + return location.capitalize() + else: + return location.capitalize() + '(' + name + ')' @property def unit_of_measurement(self): @@ -97,9 +106,16 @@ class NestThermostat(ThermostatDevice): """ Returns if away mode is on. """ return self.structure.away + def enforce_temp_units(self, temperature): + """ Enforces temp units in C for nest API, returns temp """ + if self.hass.config.temperature_unit == self.unit_of_measurement: + return temperature + else: + return self.nest_utils.f_to_c(temperature) + def set_temperature(self, temperature): """ Set new target temperature """ - self.device.target = temperature + self.device.target = self.enforce_temp_units(temperature) def turn_away_mode_on(self): """ Turns away on. """ @@ -109,6 +125,24 @@ class NestThermostat(ThermostatDevice): """ Turns away off. """ self.structure.away = False + @property + def min_temp(self): + """ Identifies min_temp in Nest API or defaults if not available. """ + temp = self.device.away_temperature.low + if temp is None: + return super().min_temp + else: + return round(self.hass.config.temperature(temp, TEMP_CELCIUS)[0]) + + @property + def max_temp(self): + """ Identifies mxn_temp in Nest API or defaults if not available. """ + temp = self.device.away_temperature.high + if temp is None: + return super().max_temp + else: + return round(self.hass.config.temperature(temp, TEMP_CELCIUS)[0]) + def update(self): """ Python-nest has its own mechanism for staying up to date. """ pass diff --git a/requirements.txt b/requirements.txt index e284cad72b9..8bcd5470034 100644 --- a/requirements.txt +++ b/requirements.txt @@ -30,7 +30,7 @@ python-libnmap>=0.6.3 pushbullet.py>=0.7.1 # Nest Thermostat bindings (thermostat.nest) -python-nest>=2.3.1 +python-nest>=2.4.0 # Z-Wave (*.zwave) pydispatcher>=2.0.5 From 6647894c36aed92f81690d7fdc247019f79b9d89 Mon Sep 17 00:00:00 2001 From: zyell Date: Thu, 20 Aug 2015 10:44:46 -0700 Subject: [PATCH 180/224] updated nest to use helper method and updated requirements --- homeassistant/components/thermostat/nest.py | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/thermostat/nest.py b/homeassistant/components/thermostat/nest.py index 5b50c444dc7..503483ee6c1 100644 --- a/homeassistant/components/thermostat/nest.py +++ b/homeassistant/components/thermostat/nest.py @@ -4,9 +4,10 @@ Adds support for Nest thermostats. import logging from homeassistant.components.thermostat import ThermostatDevice +from homeassistant.helpers.temperature import convert from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD, TEMP_CELCIUS) -REQUIREMENTS = ['python-nest>=2.3.1'] +REQUIREMENTS = ['python-nest>=2.4.0'] # pylint: disable=unused-argument @@ -34,7 +35,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): napi = nest.Nest(username, password) add_devices([ - NestThermostat(nest.utils, structure, device) + NestThermostat(structure, device) for structure in napi.structures for device in structure.devices ]) @@ -43,10 +44,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class NestThermostat(ThermostatDevice): """ Represents a Nest thermostat within Home Assistant. """ - def __init__(self, nest_utils, structure, device): + def __init__(self, structure, device): self.structure = structure self.device = device - self.nest_utils = nest_utils @property def name(self): @@ -106,16 +106,11 @@ class NestThermostat(ThermostatDevice): """ Returns if away mode is on. """ return self.structure.away - def enforce_temp_units(self, temperature): - """ Enforces temp units in C for nest API, returns temp """ - if self.hass.config.temperature_unit == self.unit_of_measurement: - return temperature - else: - return self.nest_utils.f_to_c(temperature) - def set_temperature(self, temperature): - """ Set new target temperature """ - self.device.target = self.enforce_temp_units(temperature) + """ Set new target temperature ensuring it is in proper units """ + temperature = convert(temperature, self.hass.config.temperature_unit, + self.unit_of_measurement) + self.device.target = temperature def turn_away_mode_on(self): """ Turns away on. """ From e15eb90b33c228e6ca8ec8aafa4cdc0c63f2dc04 Mon Sep 17 00:00:00 2001 From: zyell Date: Mon, 24 Aug 2015 16:50:22 -0700 Subject: [PATCH 181/224] Have thermostat class make all necessary unit conversions --- .../components/thermostat/__init__.py | 32 ++++++++++++++----- homeassistant/components/thermostat/nest.py | 11 +++---- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/thermostat/__init__.py b/homeassistant/components/thermostat/__init__.py index 40e392709f2..bbc0979e38c 100644 --- a/homeassistant/components/thermostat/__init__.py +++ b/homeassistant/components/thermostat/__init__.py @@ -10,6 +10,7 @@ from homeassistant.helpers.entity_component import EntityComponent import homeassistant.util as util from homeassistant.helpers.entity import Entity +from homeassistant.helpers.temperature import convert from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_TEMPERATURE, STATE_ON, STATE_OFF, TEMP_CELCIUS) @@ -86,7 +87,9 @@ def setup(hass, config): return for thermostat in target_thermostats: - thermostat.set_temperature(temperature) + thermostat.set_temperature(convert( + temperature, hass.config.temperature_unit, + thermostat.unit_of_measurement)) for thermostat in target_thermostats: thermostat.update_ha_state(True) @@ -118,9 +121,20 @@ class ThermostatDevice(Entity): @property def state_attributes(self): """ Returns optional state attributes. """ + + thermostat_unit = self.unit_of_measurement + user_unit = self.hass.config.temperature_unit + data = { - ATTR_CURRENT_TEMPERATURE: self.hass.config.temperature( - self.current_temperature, self.unit_of_measurement)[0] + ATTR_CURRENT_TEMPERATURE: round(convert(self.current_temperature, + thermostat_unit, + user_unit), 1), + ATTR_MIN_TEMP: round(convert(self.min_temp, + thermostat_unit, + user_unit), 0), + ATTR_MAX_TEMP: round(convert(self.max_temp, + thermostat_unit, + user_unit), 0) } is_away = self.is_away_mode_on @@ -133,11 +147,13 @@ 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 + def unit_of_measurement(self): + """ Unit of measurement this thermostat expresses itself in. """ + return NotImplementedError + @property def current_temperature(self): """ Returns the current temperature. """ @@ -171,9 +187,9 @@ class ThermostatDevice(Entity): @property def min_temp(self): """ Return minimum temperature. """ - return self.hass.config.temperature(7, TEMP_CELCIUS)[0] + return convert(7, TEMP_CELCIUS, self.unit_of_measurement) @property def max_temp(self): """ Return maxmum temperature. """ - return self.hass.config.temperature(35, TEMP_CELCIUS)[0] + return convert(35, TEMP_CELCIUS, self.unit_of_measurement) diff --git a/homeassistant/components/thermostat/nest.py b/homeassistant/components/thermostat/nest.py index 503483ee6c1..cb74fa091ff 100644 --- a/homeassistant/components/thermostat/nest.py +++ b/homeassistant/components/thermostat/nest.py @@ -4,7 +4,6 @@ Adds support for Nest thermostats. import logging from homeassistant.components.thermostat import ThermostatDevice -from homeassistant.helpers.temperature import convert from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD, TEMP_CELCIUS) REQUIREMENTS = ['python-nest>=2.4.0'] @@ -63,7 +62,7 @@ class NestThermostat(ThermostatDevice): @property def unit_of_measurement(self): - """ Returns the unit of measurement. """ + """ Unit of measurement this thermostat expresses itself in. """ return TEMP_CELCIUS @property @@ -107,9 +106,7 @@ class NestThermostat(ThermostatDevice): return self.structure.away def set_temperature(self, temperature): - """ Set new target temperature ensuring it is in proper units """ - temperature = convert(temperature, self.hass.config.temperature_unit, - self.unit_of_measurement) + """ Set new target temperature """ self.device.target = temperature def turn_away_mode_on(self): @@ -127,7 +124,7 @@ class NestThermostat(ThermostatDevice): if temp is None: return super().min_temp else: - return round(self.hass.config.temperature(temp, TEMP_CELCIUS)[0]) + return temp @property def max_temp(self): @@ -136,7 +133,7 @@ class NestThermostat(ThermostatDevice): if temp is None: return super().max_temp else: - return round(self.hass.config.temperature(temp, TEMP_CELCIUS)[0]) + return temp def update(self): """ Python-nest has its own mechanism for staying up to date. """ From c8b88219b7d923e6d404efdff9047b24a94ac3bc Mon Sep 17 00:00:00 2001 From: sfam Date: Wed, 26 Aug 2015 11:03:06 +0100 Subject: [PATCH 182/224] rename config parameter to "invert_logic" --- homeassistant/components/switch/rpi_gpio.py | 22 ++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/switch/rpi_gpio.py b/homeassistant/components/switch/rpi_gpio.py index a1f9f61192b..298168ed65a 100644 --- a/homeassistant/components/switch/rpi_gpio.py +++ b/homeassistant/components/switch/rpi_gpio.py @@ -8,16 +8,16 @@ Configuration: switch: platform: rpi_gpio - active_state: "HIGH" + invert_logic: false ports: 11: Fan Office 12: Light Desk Variables: -active_state +invert_logic *Optional -Defines which GPIO state corresponds to a ACTIVE switch. Default is HIGH. +If true, inverts the output logic to ACTIVE LOW. Default is false (ACTIVE HIGH). ports *Required @@ -34,7 +34,7 @@ from homeassistant.const import (DEVICE_DEFAULT_NAME, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) -DEFAULT_ACTIVE_STATE = "HIGH" +DEFAULT_INVERT_LOGIC = False REQUIREMENTS = ['RPi.GPIO>=0.5.11'] _LOGGER = logging.getLogger(__name__) @@ -50,10 +50,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None): GPIO.setmode(GPIO.BCM) switches = [] - active_state = config.get('active_state', DEFAULT_ACTIVE_STATE) + invert_logic = config.get('invert_logic', DEFAULT_INVERT_LOGIC) ports = config.get('ports') for port_num, port_name in ports.items(): - switches.append(RPiGPIOSwitch(port_name, port_num, active_state)) + switches.append(RPiGPIOSwitch(port_name, port_num, invert_logic)) add_devices(switches) def cleanup_gpio(event): @@ -71,11 +71,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class RPiGPIOSwitch(ToggleEntity): """ Represents a port that can be toggled using Raspberry Pi GPIO. """ - def __init__(self, name, gpio, active_state): + def __init__(self, name, gpio, invert_logic): self._name = name or DEVICE_DEFAULT_NAME self._gpio = gpio - self._active_state = active_state - self._state = False if self._active_state == "HIGH" else True + self._active_state = !invert_logic + self._state = !self._active_state # pylint: disable=no-member GPIO.setup(gpio, GPIO.OUT) @@ -96,13 +96,13 @@ class RPiGPIOSwitch(ToggleEntity): def turn_on(self, **kwargs): """ Turn the device on. """ - if self._switch(True if self._active_state == "HIGH" else False): + if self._switch(self._active_state): self._state = True self.update_ha_state() def turn_off(self, **kwargs): """ Turn the device off. """ - if self._switch(False if self._active_state == "HIGH" else True): + if self._switch(!self._active_state): self._state = False self.update_ha_state() From 6b3b000822dd5306da590bed173ab4364c9a958e Mon Sep 17 00:00:00 2001 From: sfam Date: Wed, 26 Aug 2015 11:22:06 +0100 Subject: [PATCH 183/224] quick fix --- homeassistant/components/switch/rpi_gpio.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/switch/rpi_gpio.py b/homeassistant/components/switch/rpi_gpio.py index 298168ed65a..c21280a2fa8 100644 --- a/homeassistant/components/switch/rpi_gpio.py +++ b/homeassistant/components/switch/rpi_gpio.py @@ -74,8 +74,8 @@ class RPiGPIOSwitch(ToggleEntity): def __init__(self, name, gpio, invert_logic): self._name = name or DEVICE_DEFAULT_NAME self._gpio = gpio - self._active_state = !invert_logic - self._state = !self._active_state + self._active_state = not invert_logic + self._state = not self._active_state # pylint: disable=no-member GPIO.setup(gpio, GPIO.OUT) @@ -102,7 +102,7 @@ class RPiGPIOSwitch(ToggleEntity): def turn_off(self, **kwargs): """ Turn the device off. """ - if self._switch(!self._active_state): + if self._switch(not self._active_state): self._state = False self.update_ha_state() From 9811869111e7830f96fb7db08307e8a895193dc4 Mon Sep 17 00:00:00 2001 From: sfam Date: Wed, 26 Aug 2015 11:27:27 +0100 Subject: [PATCH 184/224] fix line too long --- homeassistant/components/switch/rpi_gpio.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/switch/rpi_gpio.py b/homeassistant/components/switch/rpi_gpio.py index c21280a2fa8..bb9cf13e3ed 100644 --- a/homeassistant/components/switch/rpi_gpio.py +++ b/homeassistant/components/switch/rpi_gpio.py @@ -17,7 +17,7 @@ Variables: invert_logic *Optional -If true, inverts the output logic to ACTIVE LOW. Default is false (ACTIVE HIGH). +If true, inverts the output logic to ACTIVE LOW. Default is false (ACTIVE HIGH) ports *Required From 47998cff97399512546676f1957053fa8a435f9e Mon Sep 17 00:00:00 2001 From: sfam Date: Wed, 26 Aug 2015 15:45:39 +0100 Subject: [PATCH 185/224] Update .coveragerc --- .coveragerc | 1 + 1 file changed, 1 insertion(+) diff --git a/.coveragerc b/.coveragerc index 7c0421c384a..65292aa9130 100644 --- a/.coveragerc +++ b/.coveragerc @@ -61,6 +61,7 @@ omit = homeassistant/components/sensor/mysensors.py homeassistant/components/sensor/openweathermap.py homeassistant/components/sensor/rfxtrx.py + homeassistant/components/sensor/rpi_gpio.py homeassistant/components/sensor/sabnzbd.py homeassistant/components/sensor/swiss_public_transport.py homeassistant/components/sensor/systemmonitor.py From 1cb6077e36c0432bdf457c80d2ed69c06ca3fbda Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 26 Aug 2015 08:39:12 -0700 Subject: [PATCH 186/224] Bugfixes for frontend --- homeassistant/components/frontend/version.py | 2 +- .../frontend/www_static/frontend.html | 23 +++++++++++-------- .../www_static/home-assistant-polymer | 2 +- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py index 930a564063e..354516eba7b 100644 --- a/homeassistant/components/frontend/version.py +++ b/homeassistant/components/frontend/version.py @@ -1,2 +1,2 @@ """ DO NOT MODIFY. Auto-generated by build_frontend script """ -VERSION = "59fdef87aec53cbcb5568e9c4cdc0f0f" +VERSION = "e92fffeec57aa088306ca9387bdb9968" diff --git a/homeassistant/components/frontend/www_static/frontend.html b/homeassistant/components/frontend/www_static/frontend.html index 541e17f8cc9..43ed3c371d3 100644 --- a/homeassistant/components/frontend/www_static/frontend.html +++ b/homeassistant/components/frontend/www_static/frontend.html @@ -3527,6 +3527,9 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN overflow: hidden; text-overflow: ellipsis; } + .label-badge .value.big { + font-size: 70%; + } .label-badge .label { position: absolute; bottom: -1em; @@ -3560,7 +3563,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN iron-image { border-radius: 50%; - } \ No newline at end of file + } \ No newline at end of file diff --git a/homeassistant/components/frontend/www_static/home-assistant-polymer b/homeassistant/components/frontend/www_static/home-assistant-polymer index b26ee9b9a65..6c0bb8a47dd 160000 --- a/homeassistant/components/frontend/www_static/home-assistant-polymer +++ b/homeassistant/components/frontend/www_static/home-assistant-polymer @@ -1 +1 @@ -Subproject commit b26ee9b9a652ce5af84b1d179477da01f04cfeb0 +Subproject commit 6c0bb8a47ddc296c79c0ea47c033e3d7d7a742fd From ba7b9c625e239620320c69c0e2d42ea85689f231 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 27 Aug 2015 01:06:07 -0700 Subject: [PATCH 187/224] Add an introduction component to support first usage --- homeassistant/components/introduction.py | 41 ++++++++++++++++++++++++ homeassistant/config.py | 14 ++++++-- homeassistant/loader.py | 7 ++-- 3 files changed, 56 insertions(+), 6 deletions(-) create mode 100644 homeassistant/components/introduction.py diff --git a/homeassistant/components/introduction.py b/homeassistant/components/introduction.py new file mode 100644 index 00000000000..b84a02d5fa5 --- /dev/null +++ b/homeassistant/components/introduction.py @@ -0,0 +1,41 @@ +""" +homeassistant.components.introduction +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Component that will help guide the user taking its first steps. +""" +import logging + +DOMAIN = 'introduction' +DEPENDENCIES = [] + + +def setup(hass, config=None): + """ Setup the introduction component. """ + log = logging.getLogger(__name__) + log.info(""" + + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Hello, and welcome to Home Assistant! + + We'll hope that we can make all your dreams come true. + + Here are some resources to get started: + + - Configuring Home Assistant: + https://home-assistant.io/getting-started/configuration.html + + - Available components: + https://home-assistant.io/components/ + + - Chat room: + https://gitter.im/balloob/home-assistant + + This message is generated by the introduction component. You can + disable it in configuration.yaml. + + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + """) + + return True diff --git a/homeassistant/config.py b/homeassistant/config.py index 6ae40e9e7c7..54ad297e62a 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -28,8 +28,15 @@ DEFAULT_CONFIG = ( (CONF_TIME_ZONE, 'UTC', 'time_zone', 'Pick yours from here: http://en.wiki' 'pedia.org/wiki/List_of_tz_database_time_zones'), ) -DEFAULT_COMPONENTS = ( - 'discovery', 'frontend', 'conversation', 'history', 'logbook', 'sun') +DEFAULT_COMPONENTS = { + 'introduction': 'Show links to resources in log and frontend', + 'frontend': 'Enables the frontend', + 'discovery': 'Discover some devices automatically', + 'conversation': 'Allows you to issue voice commands from the frontend', + 'history': 'Enables support for tracking state changes over time.', + 'logbook': 'View all events in a logbook', + 'sun': 'Track the sun', +} def ensure_config_exists(config_dir, detect_location=True): @@ -78,7 +85,8 @@ def create_default_config(config_dir, detect_location=True): config_file.write("\n") - for component in DEFAULT_COMPONENTS: + for component, description in DEFAULT_COMPONENTS.items(): + config_file.write("# {}\n".format(description)) config_file.write("{}:\n\n".format(component)) return config_path diff --git a/homeassistant/loader.py b/homeassistant/loader.py index b0eb8d287db..7b755214252 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -166,9 +166,10 @@ def load_order_components(components): key=lambda order: 'group' in order): load_order.update(comp_load_order) - # Push recorder to first place in load order - if 'recorder' in load_order: - load_order.promote('recorder') + # Push some to first place in load order + for comp in ('recorder', 'introduction'): + if comp in load_order: + load_order.promote(comp) return load_order From a98ecb6bcc8524f0757354f8ad234309a64705d3 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 27 Aug 2015 01:06:28 -0700 Subject: [PATCH 188/224] Frontend: material design + introduction card --- homeassistant/components/frontend/version.py | 2 +- .../frontend/www_static/frontend.html | 109 ++++++++++-------- .../www_static/home-assistant-polymer | 2 +- 3 files changed, 62 insertions(+), 51 deletions(-) diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py index 354516eba7b..3b00afea13b 100644 --- a/homeassistant/components/frontend/version.py +++ b/homeassistant/components/frontend/version.py @@ -1,2 +1,2 @@ """ DO NOT MODIFY. Auto-generated by build_frontend script """ -VERSION = "e92fffeec57aa088306ca9387bdb9968" +VERSION = "e9a79e5367c298e2e5201c35c9b9de8b" diff --git a/homeassistant/components/frontend/www_static/frontend.html b/homeassistant/components/frontend/www_static/frontend.html index 43ed3c371d3..26c38c2a31b 100644 --- a/homeassistant/components/frontend/www_static/frontend.html +++ b/homeassistant/components/frontend/www_static/frontend.html @@ -533,6 +533,8 @@ if(n>=0){var s=e.getKey(t);return this.splice("selected",n,1),this.unlinkPaths(" --paper-toggle-button-checked-bar-color: #039be5; --paper-toolbar-background: #03A9F4; + + font-size: 14px; } @-webkit-keyframes ha-spin { @@ -3592,19 +3594,18 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN .header { font-size: 24px; padding: 24px 16px 16px; - border-bottom: 2px solid #E8E8E8; text-transform: capitalize; } \ No newline at end of file +console.debug(e),console.groupEnd())},e.dispatchError=function(t){console.group&&(console.debug("Dispatch error: "+t),console.groupEnd())},e.storeHandled=function(t,e,n){console.group&&e!==n&&console.debug("Store "+t+" handled action")},e.dispatchEnd=function(t){console.group&&(console.debug("Dispatch done, new state: ",t.toJS()),console.groupEnd())}},function(t,e,n){function r(t,e){this.__prevState=t,this.__evaluator=e,this.__prevValues=i.Map(),this.__observers=[]}var i=n(2),o=n(7),u=n(8);Object.defineProperty(r.prototype,"notifyObservers",{writable:!0,configurable:!0,value:function(t){if(this.__observers.length>0){var e=i.Map();this.__observers.forEach(function(n){var r,i=n.getter,a=o(i),s=this.__prevState;this.__prevValues.has(a)?r=this.__prevValues.get(a):(r=this.__evaluator.evaluate(s,i),this.__prevValues=this.__prevValues.set(a,r));var c=this.__evaluator.evaluate(t,i);u(r,c)||(n.handler.call(null,c),e=e.set(a,c))}.bind(this)),this.__prevValues=e}this.__prevState=t}}),Object.defineProperty(r.prototype,"onChange",{writable:!0,configurable:!0,value:function(t,e){var n={getter:t,handler:e};return this.__observers.push(n),function(){var t=this.__observers.indexOf(n);t>-1&&this.__observers.splice(t,1)}.bind(this)}}),Object.defineProperty(r.prototype,"reset",{writable:!0,configurable:!0,value:function(t){this.__prevState=t,this.__prevValues=i.Map(),this.__observers=[]}}),t.exports=r},function(t,e,n){var r=n(2);t.exports=function(t,e){if(t.hasOwnProperty("__hashCode"))return t.__hashCode;var n=r.fromJS(t).hashCode();return e||(Object.defineProperty(t,"__hashCode",{enumerable:!1,configurable:!1,writable:!1,value:n}),Object.freeze(t)),n}},function(t,e,n){var r=n(2);t.exports=function(t,e){return r.is(t,e)}},function(t,e,n){function r(t){return s(t)&&a(t[t.length-1])}function i(t){return t[t.length-1]}function o(t){return t.slice(0,t.length-1)}function u(t){if(!c(t))throw new Error("Cannot create Getter from KeyPath: "+t);return[t,l]}var a=n(3).isFunction,s=n(3).isArray,c=n(10).isKeyPath,l=function(t){return t};t.exports={isGetter:r,getComputeFn:i,getDeps:o,fromKeyPath:u}},function(t,e,n){var r=n(3).isArray,i=n(3).isFunction;e.isKeyPath=function(t){return r(t)&&!i(t[t.length-1])}},function(t,e,n){function r(){this.__cachedGetters=i.Map({})}var i=n(2),o=n(1).toImmutable,u=n(7),a=n(8),s=n(9).getComputeFn,c=n(9).getDeps,l=n(10).isKeyPath,f=n(9).isGetter,d=!1;Object.defineProperty(r.prototype,"evaluate",{writable:!0,configurable:!0,value:function(t,e){if(l(e))return t.getIn(e);if(!f(e))throw new Error("evaluate must be passed a keyPath or Getter");var n=u(e);if(this.__isCached(t,e))return this.__cachedGetters.getIn([n,"value"]);var r=c(e).map(function(e){return this.evaluate(t,e)}.bind(this));if(this.__hasStaleValue(t,e)){var i=this.__cachedGetters.getIn([n,"args"]);if(a(i,o(r))){var p=this.__cachedGetters.getIn([n,"value"]);return this.__cacheValue(t,e,i,p),p}}if(d===!0)throw d=!1,new Error("Evaluate may not be called within a Getters computeFn");var h;d=!0;try{h=s(e).apply(null,r),d=!1}catch(v){throw d=!1,v}return this.__cacheValue(t,e,r,h),h}}),Object.defineProperty(r.prototype,"__hasStaleValue",{writable:!0,configurable:!0,value:function(t,e){var n=u(e),r=this.__cachedGetters;return r.has(n)&&r.getIn([n,"stateHashCode"])!==t.hashCode()}}),Object.defineProperty(r.prototype,"__cacheValue",{writable:!0,configurable:!0,value:function(t,e,n,r){var a=u(e);this.__cachedGetters=this.__cachedGetters.set(a,i.Map({value:r,args:o(n),stateHashCode:t.hashCode()}))}}),Object.defineProperty(r.prototype,"__isCached",{writable:!0,configurable:!0,value:function(t,e){var n=u(e);return this.__cachedGetters.hasIn([n,"value"])&&this.__cachedGetters.getIn([n,"stateHashCode"])===t.hashCode()}}),Object.defineProperty(r.prototype,"untrack",{writable:!0,configurable:!0,value:function(t){}}),Object.defineProperty(r.prototype,"reset",{writable:!0,configurable:!0,value:function(){this.__cachedGetters=i.Map({})}}),t.exports=r},function(t,e,n){function r(t,e){var n={};return i(e,function(e,r){n[r]=t.evaluate(e)}),n}var i=n(3).each;t.exports=function(t){return{getInitialState:function(){return r(t,this.getDataBindings())},componentDidMount:function(){var e=this;e.__unwatchFns=[],i(this.getDataBindings(),function(n,r){var i=t.observe(n,function(t){var n={};n[r]=t,e.setState(n)});e.__unwatchFns.push(i)})},componentWillUnmount:function(){for(;this.__unwatchFns.length;)this.__unwatchFns.shift()()}}}},function(t,e,n){function r(t){return this instanceof r?(this.__handlers=o({}),t&&u(this,t),void this.initialize()):new r(t)}function i(t){return t instanceof r}var o=n(2).Map,u=n(3).extend,a=n(1).toJS,s=n(1).toImmutable;Object.defineProperty(r.prototype,"initialize",{writable:!0,configurable:!0,value:function(){}}),Object.defineProperty(r.prototype,"getInitialState",{writable:!0,configurable:!0,value:function(){return o()}}),Object.defineProperty(r.prototype,"handle",{writable:!0,configurable:!0,value:function(t,e,n){var r=this.__handlers.get(e);return"function"==typeof r?r.call(this,t,n,e):t}}),Object.defineProperty(r.prototype,"handleReset",{writable:!0,configurable:!0,value:function(t){return this.getInitialState()}}),Object.defineProperty(r.prototype,"on",{writable:!0,configurable:!0,value:function(t,e){this.__handlers=this.__handlers.set(t,e)}}),Object.defineProperty(r.prototype,"serialize",{writable:!0,configurable:!0,value:function(t){return a(t)}}),Object.defineProperty(r.prototype,"deserialize",{writable:!0,configurable:!0,value:function(t){return s(t)}}),t.exports=r,t.exports.isStore=i}])})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(173),u=r(o);e["default"]=u["default"](i.reactor),t.exports=e["default"]},function(t,e){"use strict";var n=function(t){var e,n={};if(!(t instanceof Object)||Array.isArray(t))throw new Error("keyMirror(...): Argument must be an object.");for(e in t)t.hasOwnProperty(e)&&(n[e]=e);return n};t.exports=n},function(t,e){"use strict";function n(t){var e=typeof t;return!!t&&("object"==e||"function"==e)}t.exports=n},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(182),o=r(i);e.callApi=o["default"]},function(t,e,n){"use strict";function r(t){return i(t)?t:Object(t)}var i=n(6);t.exports=r},function(t,e,n){"use strict";var r=n(20),i=n(12),o=n(13),u="[object Array]",a=Object.prototype,s=a.toString,c=r(Array,"isArray"),l=c||function(t){return o(t)&&i(t.length)&&s.call(t)==u};t.exports=l},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var o=n(198),u=i(o),a=n(199),s=r(a),c=u["default"];e.actions=c;var l=s;e.getters=l},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){t.registerStores({restApiCache:l["default"]})}function o(t){return[["restApiCache",t.entity],function(t){return!!t}]}function u(t){return[["restApiCache",t.entity],function(t){return t||s.toImmutable({})}]}function a(t){return function(e){return["restApiCache",t.entity,e]}}Object.defineProperty(e,"__esModule",{value:!0}),e.register=i,e.createHasDataGetter=o,e.createEntityMapGetter=u,e.createByIdGetter=a;var s=n(3),c=n(222),l=r(c),f=n(221),d=r(f);e.createApiActions=d["default"]},function(t,e){"use strict";function n(t){return"number"==typeof t&&t>-1&&t%1==0&&r>=t}var r=9007199254740991;t.exports=n},function(t,e){"use strict";function n(t){return!!t&&"object"==typeof t}t.exports=n},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);e["default"]=new o["default"]({is:"partial-base",properties:{narrow:{type:Boolean,value:!1}},toggleMenu:function(){this.fire("open-menu")}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(5),o=r(i);e["default"]=o["default"]({ENTITY_HISTORY_DATE_SELECTED:null,ENTITY_HISTORY_FETCH_START:null,ENTITY_HISTORY_FETCH_ERROR:null,ENTITY_HISTORY_FETCH_SUCCESS:null,RECENT_ENTITY_HISTORY_FETCH_START:null,RECENT_ENTITY_HISTORY_FETCH_ERROR:null,RECENT_ENTITY_HISTORY_FETCH_SUCCESS:null,LOG_OUT:null}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return null!=t&&o(i(t))}var i=n(48),o=n(12);t.exports=r},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(135),n(56),e["default"]=new o["default"]({is:"state-info",properties:{stateObj:{type:Object}}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(5),o=r(i);e["default"]=o["default"]({LOGBOOK_DATE_SELECTED:null,LOGBOOK_ENTRIES_FETCH_START:null,LOGBOOK_ENTRIES_FETCH_ERROR:null,LOGBOOK_ENTRIES_FETCH_SUCCESS:null}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var o=n(223),u=i(o),a=n(77),s=r(a),c=u["default"];e.actions=c;var l=s;e.getters=l},function(t,e,n){"use strict";function r(t,e){var n=null==t?void 0:t[e];return i(n)?n:void 0}var i=n(124);t.exports=r},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(5),o=r(i);e["default"]=o["default"]({VALIDATING_AUTH_TOKEN:null,VALID_AUTH_TOKEN:null,INVALID_AUTH_TOKEN:null,LOG_OUT:null}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}function o(t){t.registerStores({authAttempt:a["default"],authCurrent:c["default"],rememberAuth:f["default"]})}Object.defineProperty(e,"__esModule",{value:!0}),e.register=o;var u=n(185),a=i(u),s=n(186),c=i(s),l=n(187),f=i(l),d=n(183),p=r(d),h=n(184),v=r(h),_=p;e.actions=_;var y=v;e.getters=y},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function o(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}Object.defineProperty(e,"__esModule",{value:!0});var u=function(){function t(t,e){var n=[],r=!0,i=!1,o=void 0;try{for(var u,a=t[Symbol.iterator]();!(r=(u=a.next()).done)&&(n.push(u.value),!e||n.length!==e);r=!0);}catch(s){i=!0,o=s}finally{try{!r&&a["return"]&&a["return"]()}finally{if(i)throw o}}return n}return function(e,n){if(Array.isArray(e))return e;if(Symbol.iterator in Object(e))return t(e,n);throw new TypeError("Invalid attempt to destructure non-iterable instance")}}(),a=function(){function t(t,e){for(var n=0;n-1&&t%1==0&&e>t}var r=/^\d+$/,i=9007199254740991;t.exports=n},function(t,e,n){"use strict";function r(t,e,n){if(!u(n))return!1;var r=typeof e;if("number"==r?i(n)&&o(e,n.length):"string"==r&&e in n){var a=n[e];return t===t?t===a:a!==a}return!1}var i=n(16),o=n(25),u=n(6);t.exports=r},function(t,e,n){"use strict";function r(t){return o(t)&&i(t)&&a.call(t,"callee")&&!s.call(t,"callee")}var i=n(16),o=n(13),u=Object.prototype,a=u.hasOwnProperty,s=u.propertyIsEnumerable;t.exports=r},function(t,e,n){"use strict";var r=n(20),i=n(16),o=n(6),u=n(121),a=r(Object,"keys"),s=a?function(t){var e=null==t?void 0:t.constructor;return"function"==typeof e&&e.prototype===t||"function"!=typeof t&&i(t)?u(t):o(t)?a(t):[]}:u;t.exports=s},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i),u=n(60),a=r(u);e["default"]=new o["default"]({is:"domain-icon",properties:{domain:{type:String,value:""},state:{type:String,value:""}},computeIcon:function(t,e){return a["default"](t,e)}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);e["default"]=new o["default"]({is:"loading-box"}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i),u=n(174),a=r(u);n(32),n(58),n(172),n(169),n(171),n(170),e["default"]=new o["default"]({is:"state-card-content",properties:{stateObj:{type:Object,observer:"stateObjChanged"}},stateObjChanged:function(t,e){var n=o["default"].dom(this);if(!t)return void(n.lastChild&&n.removeChild(n.lastChild));var r=a["default"](t);if(e&&a["default"](e)===r)n.lastChild.stateObj=t;else{n.lastChild&&n.removeChild(n.lastChild);var i=document.createElement("state-card-"+r);i.stateObj=t,n.appendChild(i)}}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(17),e["default"]=new o["default"]({is:"state-card-display",properties:{stateObj:{type:Object}}}),t.exports=e["default"]},function(t,e){"use strict";function n(t,e){return t?e.map(function(e){return e in t.attributes?"has-"+e:""}).join(" "):""}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=n,t.exports=e["default"]},function(t,e,n){"use strict";function r(t,e,n){function r(){y&&clearTimeout(y),p&&clearTimeout(p),g=0,p=y=m=void 0}function s(e,n){n&&clearTimeout(n),p=y=m=void 0,e&&(g=o(),h=t.apply(_,d),y||p||(d=_=void 0))}function c(){var t=e-(o()-v);0>=t||t>e?s(m,p):y=setTimeout(c,t)}function l(){s(O,y)}function f(){if(d=arguments,v=o(),_=this,m=O&&(y||!w),b===!1)var n=w&&!y;else{p||w||(g=v);var r=b-(v-g),i=0>=r||r>b;i?(p&&(p=clearTimeout(p)),g=v,h=t.apply(_,d)):p||(p=setTimeout(l,r))}return i&&y?y=clearTimeout(y):y||e===b||(y=setTimeout(c,e)),n&&(i=!0,h=t.apply(_,d)),!i||y||p||(d=_=void 0),h}var d,p,h,v,_,y,m,g=0,b=!1,O=!0;if("function"!=typeof t)throw new TypeError(u);if(e=0>e?0:+e||0,n===!0){var w=!0;O=!1}else i(n)&&(w=!!n.leading,b="maxWait"in n&&a(+n.maxWait||0,e),O="trailing"in n?!!n.trailing:O);return f.cancel=r,f}var i=n(66),o=n(178),u="Expected a function",a=Math.max;t.exports=r},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(5),o=r(i);e["default"]=o["default"]({SERVER_CONFIG_LOADED:null,COMPONENT_LOADED:null,LOG_OUT:null}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}function o(t){t.registerStores({serverComponent:a["default"],serverConfig:c["default"]})}Object.defineProperty(e,"__esModule",{value:!0}),e.register=o;var u=n(190),a=i(u),s=n(191),c=i(s),l=n(188),f=r(l),d=n(189),p=r(d),h=f;e.actions=h;var v=p;e.getters=v},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var o=n(200),u=i(o),a=n(201),s=r(a),c=u["default"];e.actions=c;var l=s;e.getters=l},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}function o(t){t.registerStores({notifications:a["default"]})}Object.defineProperty(e,"__esModule",{value:!0}),e.register=o;var u=n(218),a=i(u),s=n(216),c=r(s),l=n(217),f=r(l),d=c;e.actions=d;var p=f;e.getters=p},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}function o(t){t.registerStores({streamStatus:a["default"]})}Object.defineProperty(e,"__esModule",{value:!0}),e.register=o;var u=n(230),a=i(u),s=n(226),c=r(s),l=n(227),f=r(l),d=c;e.actions=d;var p=f;e.getters=p},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(5),o=r(i);e["default"]=o["default"]({API_FETCH_ALL_START:null,API_FETCH_ALL_SUCCESS:null,API_FETCH_ALL_FAIL:null,SYNC_SCHEDULED:null,SYNC_SCHEDULE_CANCELLED:null}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}function o(t){t.registerStores({isFetchingData:a["default"],isSyncScheduled:c["default"]})}Object.defineProperty(e,"__esModule",{value:!0}),e.register=o;var u=n(232),a=i(u),s=n(233),c=i(s),l=n(231),f=r(l),d=n(80),p=r(d),h=f;e.actions=h;var v=p;e.getters=v},function(t,e){"use strict";function n(t){return t.getFullYear()+"-"+(t.getMonth()+1)+"-"+t.getDate()}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=n,t.exports=e["default"]},function(t,e){"use strict";function n(t){var e=t.split(" "),n=r(e,2),i=n[0],o=n[1],u=i.split(":"),a=r(u,3),s=a[0],c=a[1],l=a[2],f=o.split("-"),d=r(f,3),p=d[0],h=d[1],v=d[2];return new Date(Date.UTC(v,parseInt(h,10)-1,p,s,c,l))}Object.defineProperty(e,"__esModule",{value:!0});var r=function(){function t(t,e){var n=[],r=!0,i=!1,o=void 0;try{for(var u,a=t[Symbol.iterator]();!(r=(u=a.next()).done)&&(n.push(u.value),!e||n.length!==e);r=!0);}catch(s){i=!0,o=s}finally{try{!r&&a["return"]&&a["return"]()}finally{if(i)throw o}}return n}return function(e,n){if(Array.isArray(e))return e;if(Symbol.iterator in Object(e))return t(e,n);throw new TypeError("Invalid attempt to destructure non-iterable instance")}}();e["default"]=n,t.exports=e["default"]},function(t,e,n){"use strict";function r(t,e,n){if(null!=t){void 0!==n&&n in i(t)&&(e=[n]);for(var r=0,o=e.length;null!=t&&o>r;)t=t[e[r++]];return r&&r==o?t:void 0}}var i=n(8);t.exports=r},function(t,e,n){"use strict";function r(t,e,n,a,s,c){return t===e?!0:null==t||null==e||!o(t)&&!u(e)?t!==t&&e!==e:i(t,e,r,n,a,s,c)}var i=n(100),o=n(6),u=n(13);t.exports=r},function(t,e,n){"use strict";function r(t,e){var n=-1,r=o(t)?Array(t.length):[];return i(t,function(t,i,o){r[++n]=e(t,i,o)}),r}var i=n(95),o=n(16);t.exports=r},function(t,e){"use strict";function n(t){return function(e){return null==e?void 0:e[t]}}t.exports=n},function(t,e,n){"use strict";var r=n(47),i=r("length");t.exports=i},function(t,e,n){"use strict";function r(t,e){var n=typeof t;if("string"==n&&a.test(t)||"number"==n)return!0;if(i(t))return!1;var r=!u.test(t);return r||null!=e&&t in o(e)}var i=n(9),o=n(8),u=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\n\\]|\\.)*?\1)\]/,a=/^\w*$/;t.exports=r},function(t,e,n){"use strict";function r(t){return t===t&&!i(t)}var i=n(6);t.exports=r},function(t,e,n){"use strict";function r(t){if(o(t))return t;var e=[];return i(t).replace(u,function(t,n,r,i){e.push(r?i.replace(a,"$1"):n||t)}),e}var i=n(107),o=n(9),u=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\n\\]|\\.)*?)\2)\]/g,a=/\\(\\)?/g;t.exports=r},function(t,e){"use strict";function n(t){return t}t.exports=n},function(t,e,n){"use strict";function r(t){return u(t)?i(t):o(t)}var i=n(47),o=n(104),u=n(49);t.exports=r},function(t,e,n){(function(t){"use strict";!function(e,n){t.exports=n()}(void 0,function(){function e(){return Ln.apply(null,arguments)}function n(t){Ln=t}function r(t){return"[object Array]"===Object.prototype.toString.call(t)}function i(t){return t instanceof Date||"[object Date]"===Object.prototype.toString.call(t)}function o(t,e){var n,r=[];for(n=0;n0)for(n in Rn)r=Rn[n],i=e[r],"undefined"!=typeof i&&(t[r]=i);return t}function h(t){p(this,t),this._d=new Date(null!=t._d?t._d.getTime():NaN),zn===!1&&(zn=!0,e.updateOffset(this),zn=!1)}function v(t){return t instanceof h||null!=t&&null!=t._isAMomentObject}function _(t){return 0>t?Math.ceil(t):Math.floor(t)}function y(t){var e=+t,n=0;return 0!==e&&isFinite(e)&&(n=_(e)),n}function m(t,e,n){var r,i=Math.min(t.length,e.length),o=Math.abs(t.length-e.length),u=0;for(r=0;i>r;r++)(n&&t[r]!==e[r]||!n&&y(t[r])!==y(e[r]))&&u++;return u+o}function g(){}function b(t){return t?t.toLowerCase().replace("_","-"):t}function O(t){for(var e,n,r,i,o=0;o0;){if(r=w(i.slice(0,e).join("-")))return r;if(n&&n.length>=e&&m(i,n,!0)>=e-1)break;e--}o++}return null}function w(e){var n=null;if(!Yn[e]&&"undefined"!=typeof t&&t&&t.exports)try{n=Nn._abbr,!function(){var t=new Error('Cannot find module "./locale"');throw t.code="MODULE_NOT_FOUND",t}(),S(n)}catch(r){}return Yn[e]}function S(t,e){var n;return t&&(n="undefined"==typeof e?M(t):T(t,e),n&&(Nn=n)),Nn._abbr}function T(t,e){return null!==e?(e.abbr=t,Yn[t]=Yn[t]||new g,Yn[t].set(e),S(t),Yn[t]):(delete Yn[t],null)}function M(t){var e;if(t&&t._locale&&t._locale._abbr&&(t=t._locale._abbr),!t)return Nn;if(!r(t)){if(e=w(t))return e;t=[t]}return O(t)}function j(t,e){var n=t.toLowerCase();Hn[n]=Hn[n+"s"]=Hn[e]=t}function E(t){return"string"==typeof t?Hn[t]||Hn[t.toLowerCase()]:void 0}function I(t){var e,n,r={};for(n in t)u(t,n)&&(e=E(n),e&&(r[e]=t[n]));return r}function P(t,n){return function(r){return null!=r?(C(this,t,r),e.updateOffset(this,n),this):D(this,t)}}function D(t,e){return t._d["get"+(t._isUTC?"UTC":"")+e]()}function C(t,e,n){return t._d["set"+(t._isUTC?"UTC":"")+e](n)}function A(t,e){var n;if("object"==typeof t)for(n in t)this.set(n,t[n]);else if(t=E(t),"function"==typeof this[t])return this[t](e);return this}function x(t,e,n){var r=""+Math.abs(t),i=e-r.length,o=t>=0;return(o?n?"+":"":"-")+Math.pow(10,Math.max(0,i)).toString().substr(1)+r}function k(t,e,n,r){var i=r;"string"==typeof r&&(i=function(){return this[r]()}),t&&(Bn[t]=i),e&&(Bn[e[0]]=function(){return x(i.apply(this,arguments),e[1],e[2])}),n&&(Bn[n]=function(){return this.localeData().ordinal(i.apply(this,arguments),t)})}function L(t){return t.match(/\[[\s\S]/)?t.replace(/^\[|\]$/g,""):t.replace(/\\/g,"")}function N(t){var e,n,r=t.match(Gn);for(e=0,n=r.length;n>e;e++)Bn[r[e]]?r[e]=Bn[r[e]]:r[e]=L(r[e]);return function(i){var o="";for(e=0;n>e;e++)o+=r[e]instanceof Function?r[e].call(i,t):r[e];return o}}function R(t,e){return t.isValid()?(e=z(e,t.localeData()),Fn[e]=Fn[e]||N(e),Fn[e](t)):t.localeData().invalidDate()}function z(t,e){function n(t){return e.longDateFormat(t)||t}var r=5;for(Un.lastIndex=0;r>=0&&Un.test(t);)t=t.replace(Un,n),Un.lastIndex=0,r-=1;return t}function Y(t){return"function"==typeof t&&"[object Function]"===Object.prototype.toString.call(t)}function H(t,e,n){or[t]=Y(e)?e:function(t){return t&&n?n:e}}function G(t,e){return u(or,t)?or[t](e._strict,e._locale):new RegExp(U(t))}function U(t){return t.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(t,e,n,r,i){return e||n||r||i}).replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function F(t,e){var n,r=e;for("string"==typeof t&&(t=[t]),"number"==typeof e&&(r=function(t,n){n[e]=y(t)}),n=0;nr;r++){if(i=s([2e3,r]),n&&!this._longMonthsParse[r]&&(this._longMonthsParse[r]=new RegExp("^"+this.months(i,"").replace(".","")+"$","i"),this._shortMonthsParse[r]=new RegExp("^"+this.monthsShort(i,"").replace(".","")+"$","i")),n||this._monthsParse[r]||(o="^"+this.months(i,"")+"|^"+this.monthsShort(i,""),this._monthsParse[r]=new RegExp(o.replace(".",""),"i")),n&&"MMMM"===e&&this._longMonthsParse[r].test(t))return r;if(n&&"MMM"===e&&this._shortMonthsParse[r].test(t))return r;if(!n&&this._monthsParse[r].test(t))return r}}function $(t,e){var n;return"string"==typeof e&&(e=t.localeData().monthsParse(e),"number"!=typeof e)?t:(n=Math.min(t.date(),q(t.year(),e)),t._d["set"+(t._isUTC?"UTC":"")+"Month"](e,n),t)}function Z(t){return null!=t?($(this,t),e.updateOffset(this,!0),this):D(this,"Month")}function X(){return q(this.year(),this.month())}function Q(t){var e,n=t._a;return n&&-2===l(t).overflow&&(e=n[sr]<0||n[sr]>11?sr:n[cr]<1||n[cr]>q(n[ar],n[sr])?cr:n[lr]<0||n[lr]>24||24===n[lr]&&(0!==n[fr]||0!==n[dr]||0!==n[pr])?lr:n[fr]<0||n[fr]>59?fr:n[dr]<0||n[dr]>59?dr:n[pr]<0||n[pr]>999?pr:-1,l(t)._overflowDayOfYear&&(ar>e||e>cr)&&(e=cr),l(t).overflow=e),t}function tt(t){e.suppressDeprecationWarnings===!1&&"undefined"!=typeof console&&console.warn&&console.warn("Deprecation warning: "+t)}function et(t,e){var n=!0;return a(function(){return n&&(tt(t+"\n"+(new Error).stack),n=!1),e.apply(this,arguments)},e)}function nt(t,e){_r[t]||(tt(e),_r[t]=!0)}function rt(t){var e,n,r=t._i,i=yr.exec(r);if(i){for(l(t).iso=!0,e=0,n=mr.length;n>e;e++)if(mr[e][1].exec(r)){t._f=mr[e][0];break}for(e=0,n=gr.length;n>e;e++)if(gr[e][1].exec(r)){t._f+=(i[6]||" ")+gr[e][0];break}r.match(nr)&&(t._f+="Z"),wt(t)}else t._isValid=!1}function it(t){var n=br.exec(t._i);return null!==n?void(t._d=new Date(+n[1])):(rt(t),void(t._isValid===!1&&(delete t._isValid,e.createFromInputFallback(t))))}function ot(t,e,n,r,i,o,u){var a=new Date(t,e,n,r,i,o,u);return 1970>t&&a.setFullYear(t),a}function ut(t){var e=new Date(Date.UTC.apply(null,arguments));return 1970>t&&e.setUTCFullYear(t),e}function at(t){return st(t)?366:365}function st(t){return t%4===0&&t%100!==0||t%400===0}function ct(){return st(this.year())}function lt(t,e,n){var r,i=n-e,o=n-t.day();return o>i&&(o-=7),i-7>o&&(o+=7),r=Dt(t).add(o,"d"),{week:Math.ceil(r.dayOfYear()/7),year:r.year()}}function ft(t){return lt(t,this._week.dow,this._week.doy).week}function dt(){return this._week.dow}function pt(){return this._week.doy}function ht(t){var e=this.localeData().week(this);return null==t?e:this.add(7*(t-e),"d")}function vt(t){var e=lt(this,1,4).week;return null==t?e:this.add(7*(t-e),"d")}function _t(t,e,n,r,i){var o,u=6+i-r,a=ut(t,0,1+u),s=a.getUTCDay();return i>s&&(s+=7),n=null!=n?1*n:i,o=1+u+7*(e-1)-s+n,{year:o>0?t:t-1,dayOfYear:o>0?o:at(t-1)+o}}function yt(t){var e=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==t?e:this.add(t-e,"d")}function mt(t,e,n){return null!=t?t:null!=e?e:n}function gt(t){var e=new Date;return t._useUTC?[e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate()]:[e.getFullYear(),e.getMonth(),e.getDate()]}function bt(t){var e,n,r,i,o=[];if(!t._d){for(r=gt(t),t._w&&null==t._a[cr]&&null==t._a[sr]&&Ot(t),t._dayOfYear&&(i=mt(t._a[ar],r[ar]),t._dayOfYear>at(i)&&(l(t)._overflowDayOfYear=!0),n=ut(i,0,t._dayOfYear),t._a[sr]=n.getUTCMonth(),t._a[cr]=n.getUTCDate()),e=0;3>e&&null==t._a[e];++e)t._a[e]=o[e]=r[e];for(;7>e;e++)t._a[e]=o[e]=null==t._a[e]?2===e?1:0:t._a[e];24===t._a[lr]&&0===t._a[fr]&&0===t._a[dr]&&0===t._a[pr]&&(t._nextDay=!0,t._a[lr]=0),t._d=(t._useUTC?ut:ot).apply(null,o),null!=t._tzm&&t._d.setUTCMinutes(t._d.getUTCMinutes()-t._tzm),t._nextDay&&(t._a[lr]=24)}}function Ot(t){var e,n,r,i,o,u,a;e=t._w,null!=e.GG||null!=e.W||null!=e.E?(o=1,u=4,n=mt(e.GG,t._a[ar],lt(Dt(),1,4).year),r=mt(e.W,1),i=mt(e.E,1)):(o=t._locale._week.dow,u=t._locale._week.doy,n=mt(e.gg,t._a[ar],lt(Dt(),o,u).year),r=mt(e.w,1),null!=e.d?(i=e.d,o>i&&++r):i=null!=e.e?e.e+o:o),a=_t(n,r,i,u,o),t._a[ar]=a.year,t._dayOfYear=a.dayOfYear}function wt(t){if(t._f===e.ISO_8601)return void rt(t); +t._a=[],l(t).empty=!0;var n,r,i,o,u,a=""+t._i,s=a.length,c=0;for(i=z(t._f,t._locale).match(Gn)||[],n=0;n0&&l(t).unusedInput.push(u),a=a.slice(a.indexOf(r)+r.length),c+=r.length),Bn[o]?(r?l(t).empty=!1:l(t).unusedTokens.push(o),V(o,r,t)):t._strict&&!r&&l(t).unusedTokens.push(o);l(t).charsLeftOver=s-c,a.length>0&&l(t).unusedInput.push(a),l(t).bigHour===!0&&t._a[lr]<=12&&t._a[lr]>0&&(l(t).bigHour=void 0),t._a[lr]=St(t._locale,t._a[lr],t._meridiem),bt(t),Q(t)}function St(t,e,n){var r;return null==n?e:null!=t.meridiemHour?t.meridiemHour(e,n):null!=t.isPM?(r=t.isPM(n),r&&12>e&&(e+=12),r||12!==e||(e=0),e):e}function Tt(t){var e,n,r,i,o;if(0===t._f.length)return l(t).invalidFormat=!0,void(t._d=new Date(NaN));for(i=0;io)&&(r=o,n=e));a(t,n||e)}function Mt(t){if(!t._d){var e=I(t._i);t._a=[e.year,e.month,e.day||e.date,e.hour,e.minute,e.second,e.millisecond],bt(t)}}function jt(t){var e=new h(Q(Et(t)));return e._nextDay&&(e.add(1,"d"),e._nextDay=void 0),e}function Et(t){var e=t._i,n=t._f;return t._locale=t._locale||M(t._l),null===e||void 0===n&&""===e?d({nullInput:!0}):("string"==typeof e&&(t._i=e=t._locale.preparse(e)),v(e)?new h(Q(e)):(r(n)?Tt(t):n?wt(t):i(e)?t._d=e:It(t),t))}function It(t){var n=t._i;void 0===n?t._d=new Date:i(n)?t._d=new Date(+n):"string"==typeof n?it(t):r(n)?(t._a=o(n.slice(0),function(t){return parseInt(t,10)}),bt(t)):"object"==typeof n?Mt(t):"number"==typeof n?t._d=new Date(n):e.createFromInputFallback(t)}function Pt(t,e,n,r,i){var o={};return"boolean"==typeof n&&(r=n,n=void 0),o._isAMomentObject=!0,o._useUTC=o._isUTC=i,o._l=n,o._i=t,o._f=e,o._strict=r,jt(o)}function Dt(t,e,n,r){return Pt(t,e,n,r,!1)}function Ct(t,e){var n,i;if(1===e.length&&r(e[0])&&(e=e[0]),!e.length)return Dt();for(n=e[0],i=1;it&&(t=-t,n="-"),n+x(~~(t/60),2)+e+x(~~t%60,2)})}function Rt(t){var e=(t||"").match(nr)||[],n=e[e.length-1]||[],r=(n+"").match(Mr)||["-",0,0],i=+(60*r[1])+y(r[2]);return"+"===r[0]?i:-i}function zt(t,n){var r,o;return n._isUTC?(r=n.clone(),o=(v(t)||i(t)?+t:+Dt(t))-+r,r._d.setTime(+r._d+o),e.updateOffset(r,!1),r):Dt(t).local()}function Yt(t){return 15*-Math.round(t._d.getTimezoneOffset()/15)}function Ht(t,n){var r,i=this._offset||0;return null!=t?("string"==typeof t&&(t=Rt(t)),Math.abs(t)<16&&(t=60*t),!this._isUTC&&n&&(r=Yt(this)),this._offset=t,this._isUTC=!0,null!=r&&this.add(r,"m"),i!==t&&(!n||this._changeInProgress?ne(this,Zt(t-i,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,e.updateOffset(this,!0),this._changeInProgress=null)),this):this._isUTC?i:Yt(this)}function Gt(t,e){return null!=t?("string"!=typeof t&&(t=-t),this.utcOffset(t,e),this):-this.utcOffset()}function Ut(t){return this.utcOffset(0,t)}function Ft(t){return this._isUTC&&(this.utcOffset(0,t),this._isUTC=!1,t&&this.subtract(Yt(this),"m")),this}function Bt(){return this._tzm?this.utcOffset(this._tzm):"string"==typeof this._i&&this.utcOffset(Rt(this._i)),this}function Vt(t){return t=t?Dt(t).utcOffset():0,(this.utcOffset()-t)%60===0}function qt(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()}function Wt(){if("undefined"!=typeof this._isDSTShifted)return this._isDSTShifted;var t={};if(p(t,this),t=Et(t),t._a){var e=t._isUTC?s(t._a):Dt(t._a);this._isDSTShifted=this.isValid()&&m(t._a,e.toArray())>0}else this._isDSTShifted=!1;return this._isDSTShifted}function Kt(){return!this._isUTC}function Jt(){return this._isUTC}function $t(){return this._isUTC&&0===this._offset}function Zt(t,e){var n,r,i,o=t,a=null;return Lt(t)?o={ms:t._milliseconds,d:t._days,M:t._months}:"number"==typeof t?(o={},e?o[e]=t:o.milliseconds=t):(a=jr.exec(t))?(n="-"===a[1]?-1:1,o={y:0,d:y(a[cr])*n,h:y(a[lr])*n,m:y(a[fr])*n,s:y(a[dr])*n,ms:y(a[pr])*n}):(a=Er.exec(t))?(n="-"===a[1]?-1:1,o={y:Xt(a[2],n),M:Xt(a[3],n),d:Xt(a[4],n),h:Xt(a[5],n),m:Xt(a[6],n),s:Xt(a[7],n),w:Xt(a[8],n)}):null==o?o={}:"object"==typeof o&&("from"in o||"to"in o)&&(i=te(Dt(o.from),Dt(o.to)),o={},o.ms=i.milliseconds,o.M=i.months),r=new kt(o),Lt(t)&&u(t,"_locale")&&(r._locale=t._locale),r}function Xt(t,e){var n=t&&parseFloat(t.replace(",","."));return(isNaN(n)?0:n)*e}function Qt(t,e){var n={milliseconds:0,months:0};return n.months=e.month()-t.month()+12*(e.year()-t.year()),t.clone().add(n.months,"M").isAfter(e)&&--n.months,n.milliseconds=+e-+t.clone().add(n.months,"M"),n}function te(t,e){var n;return e=zt(e,t),t.isBefore(e)?n=Qt(t,e):(n=Qt(e,t),n.milliseconds=-n.milliseconds,n.months=-n.months),n}function ee(t,e){return function(n,r){var i,o;return null===r||isNaN(+r)||(nt(e,"moment()."+e+"(period, number) is deprecated. Please use moment()."+e+"(number, period)."),o=n,n=r,r=o),n="string"==typeof n?+n:n,i=Zt(n,r),ne(this,i,t),this}}function ne(t,n,r,i){var o=n._milliseconds,u=n._days,a=n._months;i=null==i?!0:i,o&&t._d.setTime(+t._d+o*r),u&&C(t,"Date",D(t,"Date")+u*r),a&&$(t,D(t,"Month")+a*r),i&&e.updateOffset(t,u||a)}function re(t,e){var n=t||Dt(),r=zt(n,this).startOf("day"),i=this.diff(r,"days",!0),o=-6>i?"sameElse":-1>i?"lastWeek":0>i?"lastDay":1>i?"sameDay":2>i?"nextDay":7>i?"nextWeek":"sameElse";return this.format(e&&e[o]||this.localeData().calendar(o,this,Dt(n)))}function ie(){return new h(this)}function oe(t,e){var n;return e=E("undefined"!=typeof e?e:"millisecond"),"millisecond"===e?(t=v(t)?t:Dt(t),+this>+t):(n=v(t)?+t:+Dt(t),n<+this.clone().startOf(e))}function ue(t,e){var n;return e=E("undefined"!=typeof e?e:"millisecond"),"millisecond"===e?(t=v(t)?t:Dt(t),+t>+this):(n=v(t)?+t:+Dt(t),+this.clone().endOf(e)e-o?(n=t.clone().add(i-1,"months"),r=(e-o)/(o-n)):(n=t.clone().add(i+1,"months"),r=(e-o)/(n-o)),-(i+r)}function fe(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")}function de(){var t=this.clone().utc();return 0e;e++)if(this._weekdaysParse[e]||(n=Dt([2e3,1]).day(e),r="^"+this.weekdays(n,"")+"|^"+this.weekdaysShort(n,"")+"|^"+this.weekdaysMin(n,""),this._weekdaysParse[e]=new RegExp(r.replace(".",""),"i")),this._weekdaysParse[e].test(t))return e}function Ue(t){var e=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=t?(t=Re(t,this.localeData()),this.add(t-e,"d")):e}function Fe(t){var e=(this.day()+7-this.localeData()._week.dow)%7;return null==t?e:this.add(t-e,"d")}function Be(t){return null==t?this.day()||7:this.day(this.day()%7?t:t-7)}function Ve(t,e){k(t,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),e)})}function qe(t,e){return e._meridiemParse}function We(t){return"p"===(t+"").toLowerCase().charAt(0)}function Ke(t,e,n){return t>11?n?"pm":"PM":n?"am":"AM"}function Je(t,e){e[pr]=y(1e3*("0."+t))}function $e(){return this._isUTC?"UTC":""}function Ze(){return this._isUTC?"Coordinated Universal Time":""}function Xe(t){return Dt(1e3*t)}function Qe(){return Dt.apply(null,arguments).parseZone()}function tn(t,e,n){var r=this._calendar[t];return"function"==typeof r?r.call(e,n):r}function en(t){var e=this._longDateFormat[t],n=this._longDateFormat[t.toUpperCase()];return e||!n?e:(this._longDateFormat[t]=n.replace(/MMMM|MM|DD|dddd/g,function(t){return t.slice(1)}),this._longDateFormat[t])}function nn(){return this._invalidDate}function rn(t){return this._ordinal.replace("%d",t)}function on(t){return t}function un(t,e,n,r){var i=this._relativeTime[n];return"function"==typeof i?i(t,e,n,r):i.replace(/%d/i,t)}function an(t,e){var n=this._relativeTime[t>0?"future":"past"];return"function"==typeof n?n(e):n.replace(/%s/i,e)}function sn(t){var e,n;for(n in t)e=t[n],"function"==typeof e?this[n]=e:this["_"+n]=e;this._ordinalParseLenient=new RegExp(this._ordinalParse.source+"|"+/\d{1,2}/.source)}function cn(t,e,n,r){var i=M(),o=s().set(r,e);return i[n](o,t)}function ln(t,e,n,r,i){if("number"==typeof t&&(e=t,t=void 0),t=t||"",null!=e)return cn(t,e,n,i);var o,u=[];for(o=0;r>o;o++)u[o]=cn(t,o,n,i);return u}function fn(t,e){return ln(t,e,"months",12,"month")}function dn(t,e){return ln(t,e,"monthsShort",12,"month")}function pn(t,e){return ln(t,e,"weekdays",7,"day")}function hn(t,e){return ln(t,e,"weekdaysShort",7,"day")}function vn(t,e){return ln(t,e,"weekdaysMin",7,"day")}function _n(){var t=this._data;return this._milliseconds=$r(this._milliseconds),this._days=$r(this._days),this._months=$r(this._months),t.milliseconds=$r(t.milliseconds),t.seconds=$r(t.seconds),t.minutes=$r(t.minutes),t.hours=$r(t.hours),t.months=$r(t.months),t.years=$r(t.years),this}function yn(t,e,n,r){var i=Zt(e,n);return t._milliseconds+=r*i._milliseconds,t._days+=r*i._days,t._months+=r*i._months,t._bubble()}function mn(t,e){return yn(this,t,e,1)}function gn(t,e){return yn(this,t,e,-1)}function bn(t){return 0>t?Math.floor(t):Math.ceil(t)}function On(){var t,e,n,r,i,o=this._milliseconds,u=this._days,a=this._months,s=this._data;return o>=0&&u>=0&&a>=0||0>=o&&0>=u&&0>=a||(o+=864e5*bn(Sn(a)+u),u=0,a=0),s.milliseconds=o%1e3,t=_(o/1e3),s.seconds=t%60,e=_(t/60),s.minutes=e%60,n=_(e/60),s.hours=n%24,u+=_(n/24),i=_(wn(u)),a+=i,u-=bn(Sn(i)),r=_(a/12),a%=12,s.days=u,s.months=a,s.years=r,this}function wn(t){return 4800*t/146097}function Sn(t){return 146097*t/4800}function Tn(t){var e,n,r=this._milliseconds;if(t=E(t),"month"===t||"year"===t)return e=this._days+r/864e5,n=this._months+wn(e),"month"===t?n:n/12;switch(e=this._days+Math.round(Sn(this._months)),t){case"week":return e/7+r/6048e5;case"day":return e+r/864e5;case"hour":return 24*e+r/36e5;case"minute":return 1440*e+r/6e4;case"second":return 86400*e+r/1e3;case"millisecond":return Math.floor(864e5*e)+r;default:throw new Error("Unknown unit "+t)}}function Mn(){return this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*y(this._months/12)}function jn(t){return function(){return this.as(t)}}function En(t){return t=E(t),this[t+"s"]()}function In(t){return function(){return this._data[t]}}function Pn(){return _(this.days()/7)}function Dn(t,e,n,r,i){return i.relativeTime(e||1,!!n,t,r)}function Cn(t,e,n){var r=Zt(t).abs(),i=di(r.as("s")),o=di(r.as("m")),u=di(r.as("h")),a=di(r.as("d")),s=di(r.as("M")),c=di(r.as("y")),l=i0,l[4]=n,Dn.apply(null,l)}function An(t,e){return void 0===pi[t]?!1:void 0===e?pi[t]:(pi[t]=e,!0)}function xn(t){var e=this.localeData(),n=Cn(this,!t,e);return t&&(n=e.pastFuture(+this,n)),e.postformat(n)}function kn(){var t,e,n,r=hi(this._milliseconds)/1e3,i=hi(this._days),o=hi(this._months);t=_(r/60),e=_(t/60),r%=60,t%=60,n=_(o/12),o%=12;var u=n,a=o,s=i,c=e,l=t,f=r,d=this.asSeconds();return d?(0>d?"-":"")+"P"+(u?u+"Y":"")+(a?a+"M":"")+(s?s+"D":"")+(c||l||f?"T":"")+(c?c+"H":"")+(l?l+"M":"")+(f?f+"S":""):"P0D"}var Ln,Nn,Rn=e.momentProperties=[],zn=!1,Yn={},Hn={},Gn=/(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Q|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,Un=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,Fn={},Bn={},Vn=/\d/,qn=/\d\d/,Wn=/\d{3}/,Kn=/\d{4}/,Jn=/[+-]?\d{6}/,$n=/\d\d?/,Zn=/\d{1,3}/,Xn=/\d{1,4}/,Qn=/[+-]?\d{1,6}/,tr=/\d+/,er=/[+-]?\d+/,nr=/Z|[+-]\d\d:?\d\d/gi,rr=/[+-]?\d+(\.\d{1,3})?/,ir=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,or={},ur={},ar=0,sr=1,cr=2,lr=3,fr=4,dr=5,pr=6;k("M",["MM",2],"Mo",function(){return this.month()+1}),k("MMM",0,0,function(t){return this.localeData().monthsShort(this,t)}),k("MMMM",0,0,function(t){return this.localeData().months(this,t)}),j("month","M"),H("M",$n),H("MM",$n,qn),H("MMM",ir),H("MMMM",ir),F(["M","MM"],function(t,e){e[sr]=y(t)-1}),F(["MMM","MMMM"],function(t,e,n,r){var i=n._locale.monthsParse(t,r,n._strict);null!=i?e[sr]=i:l(n).invalidMonth=t});var hr="January_February_March_April_May_June_July_August_September_October_November_December".split("_"),vr="Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),_r={};e.suppressDeprecationWarnings=!1;var yr=/^\s*(?:[+-]\d{6}|\d{4})-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,mr=[["YYYYYY-MM-DD",/[+-]\d{6}-\d{2}-\d{2}/],["YYYY-MM-DD",/\d{4}-\d{2}-\d{2}/],["GGGG-[W]WW-E",/\d{4}-W\d{2}-\d/],["GGGG-[W]WW",/\d{4}-W\d{2}/],["YYYY-DDD",/\d{4}-\d{3}/]],gr=[["HH:mm:ss.SSSS",/(T| )\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss",/(T| )\d\d:\d\d:\d\d/],["HH:mm",/(T| )\d\d:\d\d/],["HH",/(T| )\d\d/]],br=/^\/?Date\((\-?\d+)/i;e.createFromInputFallback=et("moment construction falls back to js Date. This is discouraged and will be removed in upcoming major release. Please refer to https://github.com/moment/moment/issues/1407 for more info.",function(t){t._d=new Date(t._i+(t._useUTC?" UTC":""))}),k(0,["YY",2],0,function(){return this.year()%100}),k(0,["YYYY",4],0,"year"),k(0,["YYYYY",5],0,"year"),k(0,["YYYYYY",6,!0],0,"year"),j("year","y"),H("Y",er),H("YY",$n,qn),H("YYYY",Xn,Kn),H("YYYYY",Qn,Jn),H("YYYYYY",Qn,Jn),F(["YYYYY","YYYYYY"],ar),F("YYYY",function(t,n){n[ar]=2===t.length?e.parseTwoDigitYear(t):y(t)}),F("YY",function(t,n){n[ar]=e.parseTwoDigitYear(t)}),e.parseTwoDigitYear=function(t){return y(t)+(y(t)>68?1900:2e3)};var Or=P("FullYear",!1);k("w",["ww",2],"wo","week"),k("W",["WW",2],"Wo","isoWeek"),j("week","w"),j("isoWeek","W"),H("w",$n),H("ww",$n,qn),H("W",$n),H("WW",$n,qn),B(["w","ww","W","WW"],function(t,e,n,r){e[r.substr(0,1)]=y(t)});var wr={dow:0,doy:6};k("DDD",["DDDD",3],"DDDo","dayOfYear"),j("dayOfYear","DDD"),H("DDD",Zn),H("DDDD",Wn),F(["DDD","DDDD"],function(t,e,n){n._dayOfYear=y(t)}),e.ISO_8601=function(){};var Sr=et("moment().min is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548",function(){var t=Dt.apply(null,arguments);return this>t?this:t}),Tr=et("moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548",function(){var t=Dt.apply(null,arguments);return t>this?this:t});Nt("Z",":"),Nt("ZZ",""),H("Z",nr),H("ZZ",nr),F(["Z","ZZ"],function(t,e,n){n._useUTC=!0,n._tzm=Rt(t)});var Mr=/([\+\-]|\d\d)/gi;e.updateOffset=function(){};var jr=/(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/,Er=/^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/;Zt.fn=kt.prototype;var Ir=ee(1,"add"),Pr=ee(-1,"subtract");e.defaultFormat="YYYY-MM-DDTHH:mm:ssZ";var Dr=et("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(t){return void 0===t?this.localeData():this.locale(t)});k(0,["gg",2],0,function(){return this.weekYear()%100}),k(0,["GG",2],0,function(){return this.isoWeekYear()%100}),De("gggg","weekYear"),De("ggggg","weekYear"),De("GGGG","isoWeekYear"),De("GGGGG","isoWeekYear"),j("weekYear","gg"),j("isoWeekYear","GG"),H("G",er),H("g",er),H("GG",$n,qn),H("gg",$n,qn),H("GGGG",Xn,Kn),H("gggg",Xn,Kn),H("GGGGG",Qn,Jn),H("ggggg",Qn,Jn),B(["gggg","ggggg","GGGG","GGGGG"],function(t,e,n,r){e[r.substr(0,2)]=y(t)}),B(["gg","GG"],function(t,n,r,i){n[i]=e.parseTwoDigitYear(t)}),k("Q",0,0,"quarter"),j("quarter","Q"),H("Q",Vn),F("Q",function(t,e){e[sr]=3*(y(t)-1)}),k("D",["DD",2],"Do","date"),j("date","D"),H("D",$n),H("DD",$n,qn),H("Do",function(t,e){return t?e._ordinalParse:e._ordinalParseLenient}),F(["D","DD"],cr),F("Do",function(t,e){e[cr]=y(t.match($n)[0],10)});var Cr=P("Date",!0);k("d",0,"do","day"),k("dd",0,0,function(t){return this.localeData().weekdaysMin(this,t)}),k("ddd",0,0,function(t){return this.localeData().weekdaysShort(this,t)}),k("dddd",0,0,function(t){return this.localeData().weekdays(this,t)}),k("e",0,0,"weekday"),k("E",0,0,"isoWeekday"),j("day","d"),j("weekday","e"),j("isoWeekday","E"),H("d",$n),H("e",$n),H("E",$n),H("dd",ir),H("ddd",ir),H("dddd",ir),B(["dd","ddd","dddd"],function(t,e,n){var r=n._locale.weekdaysParse(t);null!=r?e.d=r:l(n).invalidWeekday=t}),B(["d","e","E"],function(t,e,n,r){e[r]=y(t)});var Ar="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),xr="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),kr="Su_Mo_Tu_We_Th_Fr_Sa".split("_");k("H",["HH",2],0,"hour"),k("h",["hh",2],0,function(){return this.hours()%12||12}),Ve("a",!0),Ve("A",!1),j("hour","h"),H("a",qe),H("A",qe),H("H",$n),H("h",$n),H("HH",$n,qn),H("hh",$n,qn),F(["H","HH"],lr),F(["a","A"],function(t,e,n){n._isPm=n._locale.isPM(t),n._meridiem=t}),F(["h","hh"],function(t,e,n){e[lr]=y(t),l(n).bigHour=!0});var Lr=/[ap]\.?m?\.?/i,Nr=P("Hours",!0);k("m",["mm",2],0,"minute"),j("minute","m"),H("m",$n),H("mm",$n,qn),F(["m","mm"],fr);var Rr=P("Minutes",!1);k("s",["ss",2],0,"second"),j("second","s"),H("s",$n),H("ss",$n,qn),F(["s","ss"],dr);var zr=P("Seconds",!1);k("S",0,0,function(){return~~(this.millisecond()/100)}),k(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),k(0,["SSS",3],0,"millisecond"),k(0,["SSSS",4],0,function(){return 10*this.millisecond()}),k(0,["SSSSS",5],0,function(){return 100*this.millisecond()}),k(0,["SSSSSS",6],0,function(){return 1e3*this.millisecond()}),k(0,["SSSSSSS",7],0,function(){return 1e4*this.millisecond()}),k(0,["SSSSSSSS",8],0,function(){return 1e5*this.millisecond()}),k(0,["SSSSSSSSS",9],0,function(){return 1e6*this.millisecond()}),j("millisecond","ms"),H("S",Zn,Vn),H("SS",Zn,qn),H("SSS",Zn,Wn);var Yr;for(Yr="SSSS";Yr.length<=9;Yr+="S")H(Yr,tr);for(Yr="S";Yr.length<=9;Yr+="S")F(Yr,Je);var Hr=P("Milliseconds",!1);k("z",0,0,"zoneAbbr"),k("zz",0,0,"zoneName");var Gr=h.prototype;Gr.add=Ir,Gr.calendar=re,Gr.clone=ie,Gr.diff=ce,Gr.endOf=Oe,Gr.format=pe,Gr.from=he,Gr.fromNow=ve,Gr.to=_e,Gr.toNow=ye,Gr.get=A,Gr.invalidAt=Pe,Gr.isAfter=oe,Gr.isBefore=ue,Gr.isBetween=ae,Gr.isSame=se,Gr.isValid=Ee,Gr.lang=Dr,Gr.locale=me,Gr.localeData=ge,Gr.max=Tr,Gr.min=Sr,Gr.parsingFlags=Ie,Gr.set=A,Gr.startOf=be,Gr.subtract=Pr,Gr.toArray=Me,Gr.toObject=je,Gr.toDate=Te,Gr.toISOString=de,Gr.toJSON=de,Gr.toString=fe,Gr.unix=Se,Gr.valueOf=we,Gr.year=Or,Gr.isLeapYear=ct,Gr.weekYear=Ae,Gr.isoWeekYear=xe,Gr.quarter=Gr.quarters=Ne,Gr.month=Z,Gr.daysInMonth=X,Gr.week=Gr.weeks=ht,Gr.isoWeek=Gr.isoWeeks=vt,Gr.weeksInYear=Le,Gr.isoWeeksInYear=ke,Gr.date=Cr,Gr.day=Gr.days=Ue,Gr.weekday=Fe,Gr.isoWeekday=Be,Gr.dayOfYear=yt,Gr.hour=Gr.hours=Nr,Gr.minute=Gr.minutes=Rr,Gr.second=Gr.seconds=zr,Gr.millisecond=Gr.milliseconds=Hr,Gr.utcOffset=Ht,Gr.utc=Ut,Gr.local=Ft,Gr.parseZone=Bt,Gr.hasAlignedHourOffset=Vt,Gr.isDST=qt,Gr.isDSTShifted=Wt,Gr.isLocal=Kt,Gr.isUtcOffset=Jt,Gr.isUtc=$t,Gr.isUTC=$t,Gr.zoneAbbr=$e,Gr.zoneName=Ze,Gr.dates=et("dates accessor is deprecated. Use date instead.",Cr),Gr.months=et("months accessor is deprecated. Use month instead",Z),Gr.years=et("years accessor is deprecated. Use year instead",Or),Gr.zone=et("moment().zone is deprecated, use moment().utcOffset instead. https://github.com/moment/moment/issues/1779",Gt);var Ur=Gr,Fr={sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},Br={LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},Vr="Invalid date",qr="%d",Wr=/\d{1,2}/,Kr={future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},Jr=g.prototype;Jr._calendar=Fr,Jr.calendar=tn,Jr._longDateFormat=Br,Jr.longDateFormat=en,Jr._invalidDate=Vr,Jr.invalidDate=nn,Jr._ordinal=qr,Jr.ordinal=rn,Jr._ordinalParse=Wr,Jr.preparse=on,Jr.postformat=on,Jr._relativeTime=Kr,Jr.relativeTime=un,Jr.pastFuture=an,Jr.set=sn,Jr.months=W,Jr._months=hr,Jr.monthsShort=K,Jr._monthsShort=vr,Jr.monthsParse=J,Jr.week=ft,Jr._week=wr,Jr.firstDayOfYear=pt,Jr.firstDayOfWeek=dt,Jr.weekdays=ze,Jr._weekdays=Ar,Jr.weekdaysMin=He,Jr._weekdaysMin=kr,Jr.weekdaysShort=Ye,Jr._weekdaysShort=xr,Jr.weekdaysParse=Ge,Jr.isPM=We,Jr._meridiemParse=Lr,Jr.meridiem=Ke,S("en",{ordinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(t){var e=t%10,n=1===y(t%100/10)?"th":1===e?"st":2===e?"nd":3===e?"rd":"th";return t+n}}),e.lang=et("moment.lang is deprecated. Use moment.locale instead.",S),e.langData=et("moment.langData is deprecated. Use moment.localeData instead.",M);var $r=Math.abs,Zr=jn("ms"),Xr=jn("s"),Qr=jn("m"),ti=jn("h"),ei=jn("d"),ni=jn("w"),ri=jn("M"),ii=jn("y"),oi=In("milliseconds"),ui=In("seconds"),ai=In("minutes"),si=In("hours"),ci=In("days"),li=In("months"),fi=In("years"),di=Math.round,pi={s:45,m:45,h:22,d:26,M:11},hi=Math.abs,vi=kt.prototype;vi.abs=_n,vi.add=mn,vi.subtract=gn,vi.as=Tn,vi.asMilliseconds=Zr,vi.asSeconds=Xr,vi.asMinutes=Qr,vi.asHours=ti,vi.asDays=ei,vi.asWeeks=ni,vi.asMonths=ri,vi.asYears=ii,vi.valueOf=Mn,vi._bubble=On,vi.get=En,vi.milliseconds=oi,vi.seconds=ui,vi.minutes=ai,vi.hours=si,vi.days=ci,vi.weeks=Pn,vi.months=li,vi.years=fi,vi.humanize=xn,vi.toISOString=kn,vi.toString=kn,vi.toJSON=kn,vi.locale=me,vi.localeData=ge,vi.toIsoString=et("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",kn),vi.lang=Dr,k("X",0,0,"unix"),k("x",0,0,"valueOf"),H("x",er),H("X",rr),F("X",function(t,e,n){n._d=new Date(1e3*parseFloat(t,10))}),F("x",function(t,e,n){n._d=new Date(y(t))}),e.version="2.10.6",n(Dt),e.fn=Ur,e.min=At,e.max=xt,e.utc=s,e.unix=Xe,e.months=fn,e.isDate=i,e.locale=S,e.invalid=d,e.duration=Zt,e.isMoment=v,e.weekdays=pn,e.parseZone=Qe,e.localeData=M,e.isDuration=Lt,e.monthsShort=dn,e.weekdaysMin=vn,e.defineLocale=T,e.weekdaysShort=hn,e.normalizeUnits=E,e.relativeTimeThreshold=An;var _i=e;return _i})}).call(e,n(128)(t))},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);e["default"]=new o["default"]({is:"ha-card",properties:{title:{type:String},header:{type:String}}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(54),o=r(i),u=n(2),a=n(1),s=r(a),c=6e4,l=u.util.parseDateTime;e["default"]=new s["default"]({is:"relative-ha-datetime",properties:{datetime:{type:String,observer:"datetimeChanged"},datetimeObj:{type:Object,observer:"datetimeObjChanged"},parsedDateTime:{type:Object},relativeTime:{type:String,value:"not set"}},created:function(){this.updateRelative=this.updateRelative.bind(this)},attached:function(){this._interval=setInterval(this.updateRelative,c)},detached:function(){clearInterval(this._interval)},datetimeChanged:function(t){this.parsedDateTime=t?l(t):null,this.updateRelative()},datetimeObjChanged:function(t){this.parsedDateTime=t,this.updateRelative()},updateRelative:function(){this.relativeTime=this.parsedDateTime?o["default"](this.parsedDateTime).fromNow():""}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(30),n(146),n(145),e["default"]=new o["default"]({is:"state-history-charts",properties:{stateHistory:{type:Object},isLoadingData:{type:Boolean,value:!1},apiLoaded:{type:Boolean,value:!1},isLoading:{type:Boolean,computed:"computeIsLoading(isLoadingData, apiLoaded)"},groupedStateHistory:{type:Object,computed:"computeGroupedStateHistory(isLoading, stateHistory)"},isSingleDevice:{type:Boolean,computed:"computeIsSingleDevice(stateHistory)"}},computeIsSingleDevice:function(t){return t&&1===t.size},computeGroupedStateHistory:function(t,e){if(t||!e)return{line:[],timeline:[]};var n={},r=[];e.forEach(function(t){if(t&&0!==t.size){var e=t.find(function(t){return"unit_of_measurement"in t.attributes}),i=e?e.attributes.unit_of_measurement:!1;i?i in n?n[i].push(t.toArray()):n[i]=[t.toArray()]:r.push(t.toArray())}}),r=r.length>0&&r;var i=Object.keys(n).map(function(t){return[t,n[t]]});return{line:i,timeline:r}},googleApiLoaded:function(){var t=this;google.load("visualization","1",{packages:["timeline","corechart"],callback:function(){return t.apiLoaded=!0}})},computeContentClasses:function(t){return t?"loading":""},computeIsLoading:function(t,e){return t||!e},computeIsEmpty:function(t){return t&&0===t.size},extractUnit:function(t){return t[0]},extractData:function(t){return t[1]}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o);n(17),e["default"]=new u["default"]({is:"state-card-toggle",properties:{stateObj:{type:Object,observer:"stateObjChanged"},toggleChecked:{type:Boolean,value:!1}},ready:function(){this.forceStateChange()},toggleTapped:function(t){t.stopPropagation()},toggleChanged:function(t){var e=t.target.checked;e&&"off"===this.stateObj.state?this.turn_on():e||"on"!==this.stateObj.state||this.turn_off()},stateObjChanged:function(t){t&&this.updateToggle(t)},updateToggle:function(t){this.toggleChecked=t&&"on"===t.state},forceStateChange:function(){this.updateToggle(this.stateObj)},turn_on:function(){var t=this;i.serviceActions.callTurnOn(this.stateObj.entityId).then(function(){return t.forceStateChange()})},turn_off:function(){var t=this;i.serviceActions.callTurnOff(this.stateObj.entityId).then(function(){return t.forceStateChange()})}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return i.reactor.evaluate(i.serviceGetters.canToggleEntity(t))}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=r;var i=n(2);t.exports=e["default"]},function(t,e){"use strict";function n(t,e){switch(t){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 n="hardware:cast";return e&&"off"!==e&&"idle"!==e&&(n+="-connected"),n;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";case"updater":return"update_available"===e?"icons:cloud-download":"icons:cloud-done";default:return"bookmark"}}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=n,t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){return u["default"](t).format("LT")}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=i;var o=n(54),u=r(o);t.exports=e["default"]},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(2);e["default"]=function(t,e){r.authActions.validate(t,{rememberAuth:e,useStreaming:r.localStoragePreferences.useStreaming})},t.exports=e["default"]},function(t,e,n){"use strict";function r(t,e){var n=null==t?void 0:t[e];return i(n)?n:void 0}var i=n(181);t.exports=r},function(t,e){"use strict";function n(t){return!!t&&"object"==typeof t}t.exports=n},function(t,e,n){"use strict";function r(t){return i(t)&&a.call(t)==o}var i=n(66),o="[object Function]",u=Object.prototype,a=u.toString;t.exports=r},function(t,e){"use strict";function n(t){var e=typeof t;return!!t&&("object"==e||"function"==e)}t.exports=n},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(3),i=["isLoadingEntityHistory"];e.isLoadingEntityHistory=i;var o=["currentEntityHistoryDate"];e.currentDate=o;var u=["entityHistory"];e.entityHistoryMap=u;var a=[o,u,function(t,e){return e.get(t)||r.toImmutable({})}];e.entityHistoryForCurrentDate=a;var s=[o,u,function(t,e){return!!e.get(t)}];e.hasDataForCurrentDate=s;var c=["recentEntityHistory"];e.recentEntityHistoryMap=c;var l=["recentEntityHistory"];e.recentEntityHistoryUpdatedMap=l},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){ +return t&&t.__esModule?t:{"default":t}}function o(t){t.registerStores({currentEntityHistoryDate:a["default"],entityHistory:c["default"],isLoadingEntityHistory:f["default"],recentEntityHistory:p["default"],recentEntityHistoryUpdated:v["default"]})}Object.defineProperty(e,"__esModule",{value:!0}),e.register=o;var u=n(193),a=i(u),s=n(194),c=i(s),l=n(195),f=i(l),d=n(196),p=i(d),h=n(197),v=i(h),_=n(192),y=r(_),m=n(67),g=r(m),b=y;e.actions=b;var O=g;e.getters=O},function(t,e,n){"use strict";function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function i(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}Object.defineProperty(e,"__esModule",{value:!0});var o=function(){function t(t,e){for(var n=0;n6e4}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=n,t.exports=e["default"]},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}function o(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(e,"__esModule",{value:!0});var u=n(219),a=n(239),s=i(a),c=n(241),l=i(c),f=n(243),d=i(f),p=n(22),h=r(p),v=n(36),_=r(v),y=n(10),m=r(y),g=n(68),b=r(g),O=n(37),w=r(O),S=n(204),T=r(S),M=n(71),j=r(M),E=n(213),I=r(E),P=n(38),D=r(P),C=n(19),A=r(C),x=n(39),k=r(x),L=n(41),N=r(L),R=n(236),z=r(R),Y=n(11),H=r(Y),G=function U(){o(this,U);var t=s["default"]();Object.defineProperties(this,{demo:{value:!1,enumerable:!0},localStoragePreferences:{value:u.localStoragePreferences,enumerable:!0},reactor:{value:t,enumerable:!0},util:{value:d["default"],enumerable:!0},startLocalStoragePreferencesSync:{value:u.localStoragePreferences.startSync.bind(u.localStoragePreferences,t)},startUrlSync:{value:I.urlSync.startSync.bind(null,t)},stopUrlSync:{value:I.urlSync.stopSync.bind(null,t)}}),l["default"](this,t,{auth:h,config:_,entity:m,entityHistory:b,event:w,logbook:T,moreInfo:j,navigation:I,notification:D,service:A,stream:k,sync:N,voice:z,restApi:H})};e["default"]=G,t.exports=e["default"]},function(t,e,n){"use strict";function r(t,e,n){var r=t?t.length:0;return n&&o(t,e,n)&&(e=!1),r?i(t,e):[]}var i=n(96),o=n(26);t.exports=r},function(t,e){"use strict";function n(t){var e=t?t.length:0;return e?t[e-1]:void 0}t.exports=n},function(t,e,n){"use strict";function r(t,e,n,r){var s=t?t.length:0;return s?(null!=e&&"boolean"!=typeof e&&(r=n,n=u(t,e,r)?void 0:e,e=!1),n=null==n?n:i(n,r,3),e?a(t,n):o(t,n)):[]}var i=n(24),o=n(108),u=n(26),a=n(122);t.exports=r},function(t,e,n){"use strict";function r(t,e,n){var r=a(t)?i:u;return e=o(e,n,3),r(t,e)}var i=n(91),o=n(24),u=n(46),a=n(9);t.exports=r},function(t,e,n){"use strict";function r(t,e){return i(t,o(e))}var i=n(87),o=n(53);t.exports=r},function(t,e,n){"use strict";function r(t,e,n){if(null==t)return[];n&&s(t,e,n)&&(e=void 0);var r=-1;e=i(e,n,3);var c=o(t,function(t,n,i){return{criteria:e(t,n,i),index:++r,value:t}});return u(c,a)}var i=n(24),o=n(46),u=n(106),a=n(112),s=n(26);t.exports=r},function(t,e,n){(function(e){"use strict";function r(t){var e=t?t.length:0;for(this.data={hash:a(null),set:new u};e--;)this.push(t[e])}var i=n(111),o=n(20),u=o(e,"Set"),a=o(Object,"create");r.prototype.push=i,t.exports=r}).call(e,function(){return this}())},function(t,e){"use strict";function n(t,e){for(var n=-1,r=t.length,i=Array(r);++ne&&!o||!i||n&&!u&&a||r&&a)return 1;if(e>t&&!n||!a||o&&!r&&i||u&&i)return-1}return 0}t.exports=n},function(t,e,n){"use strict";var r=n(98),i=n(113),o=i(r);t.exports=o},function(t,e,n){"use strict";function r(t,e,n,c){c||(c=[]);for(var l=-1,f=t.length;++le&&(e=-e>i?0:i+e),n=void 0===n||n>i?i:+n||0,0>n&&(n+=i),i=e>n?0:n-e>>>0,e>>>=0;for(var o=Array(i);++r=a,f=l?u():null,d=[];f?(r=o,c=!1):(l=!1,f=e?[]:d);t:for(;++nc))return!1;for(;++s0;++r5?"value big":"value"}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(143),e["default"]=new o["default"]({is:"ha-logbook",properties:{entries:{type:Object,value:[]}},noEntries:function(t){return!t.length}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o),a=n(4),s=r(a);n(147),e["default"]=new u["default"]({is:"ha-sidebar",behaviors:[s["default"]],properties:{menuSelected:{type:String},selected:{type:String,bindNuclear:i.navigationGetters.activePane,observer:"selectedChanged"},hasHistoryComponent:{type:Boolean,bindNuclear:i.configGetters.isComponentLoaded("history")},hasLogbookComponent:{type:Boolean,bindNuclear:i.configGetters.isComponentLoaded("logbook")}},selectedChanged:function(t){for(var e=this.querySelectorAll(".menu [data-panel]"),n=0;nc;c++)a._columns[c]=[];var l=0;return r.keySeq().sortBy(function(t){return i(t)}).forEach(function(t){"a"===t?a._demo=!0:i(t)<10?a._badges.push.apply(a._badges,r.get(t).sortBy(function(t){return t.entityDisplay}).toArray()):"group"===t?r.get(t).filter(function(t){return!t.attributes.auto}).forEach(function(t){var r=s.util.expandGroup(t,e);r.forEach(function(t){return u[t.entityId]=!0}),n(t.entityDisplay,r.toArray(),!1)}):n(t,r.get(t).sortBy(function(t){return t.entityDisplay}).toArray())}),a},computeShowHideInstruction:function(t){return t.size>0&&!0},computeStatesOfCard:function(t,e){return t[e]}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o);n(29),n(132),n(56),e["default"]=new u["default"]({is:"logbook-entry",entityClicked:function(t){t.preventDefault(),i.moreInfoActions.selectEntity(this.entryObj.entityId)}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o),a=n(4),s=r(a);n(29),e["default"]=new u["default"]({is:"services-list",behaviors:[s["default"]],properties:{serviceDomains:{type:Array,bindNuclear:[i.serviceGetters.entityMap,function(t){return t.valueSeq().sortBy(function(t){return t.domain}).toJS()}]}},computeServices:function(t){return this.services.get(t).toArray()},serviceClicked:function(t){t.preventDefault(),this.fire("service-selected",{domain:t.model.domain.domain,service:t.model.service})}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(88),o=r(i),u=n(84),a=r(u),s=n(86),c=r(s),l=n(89),f=r(l),d=n(1),p=r(d);e["default"]=new p["default"]({is:"state-history-chart-line",properties:{data:{type:Object,observer:"dataChanged"},unit:{type:String},isSingleDevice:{type:Boolean,value:!1},isAttached:{type:Boolean,value:!1,observer:"dataChanged"}},created:function(){this.style.display="block"; +},attached:function(){this.isAttached=!0},dataChanged:function(){this.drawChart()},drawChart:function(){if(this.isAttached){for(var t=p["default"].dom(this),e=this.unit,n=this.data;t.lastChild;)t.removeChild(t.lastChild);if(0!==n.length){var r=new google.visualization.LineChart(this),i=new google.visualization.DataTable;i.addColumn({type:"datetime",id:"Time"});var u={legend:{position:"top"},titlePosition:"none",vAxes:{0:{title:e}},hAxis:{format:"H:mm"},lineWidth:1,chartArea:{left:"60",width:"95%"},explorer:{actions:["dragToZoom","rightClickToReset","dragToPan"],keepInBounds:!0,axis:"horizontal",maxZoomIn:.1}};this.isSingleDevice&&(u.legend.position="none",u.vAxes[0].title=null,u.chartArea.left=40,u.chartArea.height="80%",u.chartArea.top=5,u.enableInteractivity=!1);var s=o["default"](a["default"](n),"lastChangedAsDate");s=f["default"](c["default"](s,function(t){return t.getTime()}));for(var l=[],d=new Array(n.length),h=0;hnew Date&&(a=new Date);var s=0;n.forEach(function(e){if(0!==e.length){var n=e[0].entityDisplay,r=void 0,i=null,o=null;e.forEach(function(e){null!==i&&e.state!==i?(r=e.lastChangedAsDate,t(n,i,o,r),i=e.state,o=r):null===i&&(i=e.state,o=e.lastChangedAsDate)}),t(n,i,o,a),s++}}),r.draw(i,{height:55+42*s,timeline:{showRowLabels:n.length>1},hAxis:{format:"H:mm"}})}}}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o),a=n(4),s=r(a);e["default"]=new u["default"]({is:"stream-status",behaviors:[s["default"]],properties:{isStreaming:{type:Boolean,bindNuclear:i.streamGetters.isStreamingEvents},hasError:{type:Boolean,bindNuclear:i.streamGetters.hasStreamingEventsError}},toggleChanged:function(){this.isStreaming?i.streamActions.stop():i.streamActions.start()}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o),a=n(4),s=r(a);n(31),n(57),n(160);var c=["camera"];e["default"]=new u["default"]({is:"more-info-dialog",behaviors:[s["default"]],properties:{stateObj:{type:Object,bindNuclear:i.moreInfoGetters.currentEntity,observer:"stateObjChanged"},stateHistory:{type:Object,bindNuclear:[i.moreInfoGetters.currentEntityHistory,function(t){return t?[t]:!1}]},isLoadingHistoryData:{type:Boolean,bindNuclear:i.entityHistoryGetters.isLoadingEntityHistory},hasHistoryComponent:{type:Boolean,bindNuclear:i.configGetters.isComponentLoaded("history"),observer:"fetchHistoryData"},shouldFetchHistory:{type:Boolean,bindNuclear:i.moreInfoGetters.isCurrentEntityHistoryStale,observer:"fetchHistoryData"},showHistoryComponent:{type:Boolean,value:!1},dialogOpen:{type:Boolean,value:!1,observer:"dialogOpenChanged"}},fetchHistoryData:function(){this.stateObj&&this.hasHistoryComponent&&this.shouldFetchHistory&&i.entityHistoryActions.fetchRecent(this.stateObj.entityId)},stateObjChanged:function(t){var e=this;return t?(this.showHistoryComponent=-1===c.indexOf(this.stateObj.domain)&&this.hasHistoryComponent,void this.async(function(){e.fetchHistoryData(),e.dialogOpen=!0},10)):void(this.dialogOpen=!1)},dialogOpenChanged:function(t){!t&&this.stateObj&&i.moreInfoActions.deselectEntity()}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(4),u=r(o);n(140),n(156),n(155),n(154),n(151),n(152),n(153),n(157),n(148),e["default"]=new Polymer({is:"home-assistant-main",behaviors:[u["default"]],properties:{narrow:{type:Boolean},activePane:{type:String,bindNuclear:i.navigationGetters.activePane,observer:"activePaneChanged"},isSelectedStates:{type:Boolean,bindNuclear:i.navigationGetters.isActivePane("states")},isSelectedHistory:{type:Boolean,bindNuclear:i.navigationGetters.isActivePane("history")},isSelectedLogbook:{type:Boolean,bindNuclear:i.navigationGetters.isActivePane("logbook")},isSelectedDevEvent:{type:Boolean,bindNuclear:i.navigationGetters.isActivePane("devEvent")},isSelectedDevState:{type:Boolean,bindNuclear:i.navigationGetters.isActivePane("devState")},isSelectedDevService:{type:Boolean,bindNuclear:i.navigationGetters.isActivePane("devService")}},listeners:{"open-menu":"openDrawer"},openDrawer:function(){this.$.drawer.openDrawer()},activePaneChanged:function(){this.$.drawer.closeDrawer()},attached:function(){i.startUrlSync()},detached:function(){i.stopUrlSync()}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i),u=n(2),a=n(4),s=r(a),c=n(62),l=r(c);e["default"]=new o["default"]({is:"login-form",behaviors:[s["default"]],properties:{isValidating:{type:Boolean,observer:"isValidatingChanged",bindNuclear:u.authGetters.isValidating},isInvalid:{type:Boolean,bindNuclear:u.authGetters.isInvalidAttempt},errorMessage:{type:String,bindNuclear:u.authGetters.attemptErrorMessage}},listeners:{keydown:"passwordKeyDown","loginButton.click":"validatePassword"},observers:["validatingChanged(isValidating, isInvalid)"],validatingChanged:function(t,e){t||e||(this.$.passwordInput.value="")},isValidatingChanged:function(t){var e=this;t||this.async(function(){return e.$.passwordInput.focus()},10)},passwordKeyDown:function(t){13===t.keyCode?(this.validatePassword(),t.preventDefault()):this.isInvalid&&(this.isInvalid=!1)},validatePassword:function(){this.$.hideKeyboardOnFocus.focus(),l["default"](this.$.passwordInput.value,this.$.rememberLogin.checked)}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o);n(14),n(144),e["default"]=new u["default"]({is:"partial-dev-call-service",properties:{narrow:{type:Boolean,value:!1},domain:{type:String,value:""},service:{type:String,value:""},serviceData:{type:String,value:""}},serviceSelected:function(t){this.domain=t.detail.domain,this.service=t.detail.service},callService:function(){var t=void 0;try{t=this.serviceData?JSON.parse(this.serviceData):{}}catch(e){return void alert("Error parsing JSON: "+e)}i.serviceActions.callService(this.domain,this.service,t)},computeFormClasses:function(t){return"layout "+(t?"vertical":"horizontal")}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o);n(14),n(136),e["default"]=new u["default"]({is:"partial-dev-fire-event",properties:{eventType:{type:String,value:""},eventData:{type:String,value:""}},eventSelected:function(t){this.eventType=t.detail.eventType},fireEvent:function(){var t=void 0;try{t=this.eventData?JSON.parse(this.eventData):{}}catch(e){return void alert("Error parsing JSON: "+e)}i.eventActions.fireEvent(this.eventType,t)},computeFormClasses:function(t){return"layout "+(t?"vertical":"horizontal")}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o);n(14),n(133),e["default"]=new u["default"]({is:"partial-dev-set-state",properties:{entityId:{type:String,value:""},state:{type:String,value:""},stateAttributes:{type:String,value:""}},setStateData:function(t){var e=t?JSON.stringify(t,null," "):"";this.$.inputData.value=e,this.$.inputDataWrapper.update(this.$.inputData)},entitySelected:function(t){var e=i.reactor.evaluate(i.entityGetters.byId(t.detail.entityId));this.entityId=e.entityId,this.state=e.state,this.stateAttributes=JSON.stringify(e.attributes,null," ")},handleSetState:function(){var t=void 0;try{t=this.stateAttributes?JSON.parse(this.stateAttributes):{}}catch(e){return void alert("Error parsing JSON: "+e)}i.entityActions.save({entityId:this.entityId,state:this.state,attributes:t})},computeFormClasses:function(t){return"layout "+(t?"vertical":"horizontal")}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o),a=n(4),s=r(a);n(14),n(57),e["default"]=new u["default"]({is:"partial-history",behaviors:[s["default"]],properties:{narrow:{type:Boolean},isDataLoaded:{type:Boolean,bindNuclear:i.entityHistoryGetters.hasDataForCurrentDate,observer:"isDataLoadedChanged"},stateHistory:{type:Object,bindNuclear:i.entityHistoryGetters.entityHistoryForCurrentDate},isLoadingData:{type:Boolean,bindNuclear:i.entityHistoryGetters.isLoadingEntityHistory},selectedDate:{type:String,value:null,bindNuclear:i.entityHistoryGetters.currentDate}},isDataLoadedChanged:function(t){t||this.async(function(){return i.entityHistoryActions.fetchSelectedDate()},1)},handleRefreshClick:function(){i.entityHistoryActions.fetchSelectedDate()},datepickerFocus:function(){this.datePicker.adjustPosition()},attached:function(){this.datePicker=new Pikaday({field:this.$.datePicker.inputElement,onSelect:i.entityHistoryActions.changeCurrentDate})},detached:function(){this.datePicker.destroy()},computeContentClasses:function(t){return"flex content "+(t?"narrow":"wide")}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o),a=n(4),s=r(a);n(14),n(139),n(30),e["default"]=new u["default"]({is:"partial-logbook",behaviors:[s["default"]],properties:{narrow:{type:Boolean,value:!1},selectedDate:{type:String,bindNuclear:i.logbookGetters.currentDate},isLoading:{type:Boolean,bindNuclear:i.logbookGetters.isLoadingEntries},isStale:{type:Boolean,bindNuclear:i.logbookGetters.isCurrentStale,observer:"isStaleChanged"},entries:{type:Array,bindNuclear:[i.logbookGetters.currentEntries,function(t){return t.toArray()}]},datePicker:{type:Object}},isStaleChanged:function(t){var e=this;t&&this.async(function(){return i.logbookActions.fetchDate(e.selectedDate)},1)},handleRefresh:function(){i.logbookActions.fetchDate(this.selectedDate)},datepickerFocus:function(){this.datePicker.adjustPosition()},attached:function(){this.datePicker=new Pikaday({field:this.$.datePicker.inputElement,onSelect:i.logbookActions.changeCurrentDate})},detached:function(){this.datePicker.destroy()}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o),a=n(4),s=r(a);n(14),n(141),n(142),e["default"]=new u["default"]({is:"partial-zone",behaviors:[s["default"]],properties:{narrow:{type:Boolean,value:!1},isFetching:{type:Boolean,bindNuclear:i.syncGetters.isFetching},isStreaming:{type:Boolean,bindNuclear:i.streamGetters.isStreamingEvents},canListen:{type:Boolean,bindNuclear:[i.voiceGetters.isVoiceSupported,i.configGetters.isComponentLoaded("conversation"),function(t,e){return t&&e}]},isListening:{type:Boolean,bindNuclear:i.voiceGetters.isListening},showListenInterface:{type:Boolean,bindNuclear:[i.voiceGetters.isListening,i.voiceGetters.isTransmitting,function(t,e){return t||e}]},introductionLoaded:{type:Boolean,bindNuclear:i.configGetters.isComponentLoaded("introduction")},states:{type:Object,bindNuclear:i.entityGetters.visibleEntityMap},columns:{type:Number}},created:function(){this.windowChange=this.windowChange.bind(this)},attached:function(){for(var t=this,e=[],n=0;4>n;n++)e.push(940+350*n);this.mqls=e.map(function(e){var n=window.matchMedia("(min-width: "+e+"px)");return n.addListener(t.windowChange),n}),this.windowChange()},detached:function(){var t=this;this.mqls.forEach(function(e){return e.removeListener(t.windowChange)})},windowChange:function(){this.columns=this.mqls.reduce(function(t,e){return t+e.matches},1)},handleRefresh:function(){i.syncActions.fetchAll()},handleListenClick:function(){this.isListening?i.voiceActions.stop():i.voiceActions.listen()},computeDomains:function(t){return t.keySeq().toArray()},computeStatesOfDomain:function(t,e){return t.get(e).toArray()},computeListenButtonIcon:function(t){return t?"av:mic-off":"av:mic"},computeRefreshButtonClass:function(t){return t?"ha-spin":void 0},computeShowIntroduction:function(t,e){return t||0===e.size},toggleMenu:function(){this.fire("open-menu")}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o),a=n(4),s=r(a);e["default"]=new u["default"]({is:"notification-manager",behaviors:[s["default"]],properties:{text:{type:String,bindNuclear:i.notificationGetters.lastNotificationMessage,observer:"showNotification"}},showNotification:function(t){t&&this.$.toast.show()}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);e["default"]=new o["default"]({is:"more-info-camera",properties:{stateObj:{type:Object},dialogOpen:{type:Boolean}},imageLoaded:function(){this.fire("iron-resize")},computeCameraImageUrl:function(t){return t?"/api/camera_proxy_stream/"+this.stateObj.entityId:"data:image/gif;base64,R0lGODlhAQABAAAAACw="}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o),a=n(4),s=r(a);n(30),e["default"]=new u["default"]({is:"more-info-configurator",behaviors:[s["default"]],properties:{stateObj:{type:Object},action:{type:String,value:"display"},isStreaming:{type:Boolean,bindNuclear:i.streamGetters.isStreamingEvents},isConfigurable:{type:Boolean,computed:"computeIsConfigurable(stateObj)"},isConfiguring:{type:Boolean,value:!1},submitCaption:{type:String,computed:"computeSubmitCaption(stateObj)"}},computeIsConfigurable:function(t){return"configure"===t.state},computeSubmitCaption:function(t){return t.attributes.submit_caption||"Set configuration"},submitClicked:function(){var t=this;this.isConfiguring=!0;var e={configure_id:this.stateObj.attributes.configure_id};i.serviceActions.callService("configurator","configure",e).then(function(){t.isConfiguring=!1,t.isStreaming||i.syncActions.fetchAll()},function(){t.isConfiguring=!1})}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i),u=n(175),a=r(u);n(161),n(162),n(166),n(159),n(167),n(165),n(163),n(164),n(158),n(168),e["default"]=new o["default"]({is:"more-info-content",properties:{stateObj:{type:Object,observer:"stateObjChanged"},dialogOpen:{type:Boolean,value:!1,observer:"dialogOpenChanged"}},dialogOpenChanged:function(t){var e=o["default"].dom(this);e.lastChild&&(e.lastChild.dialogOpen=t)},stateObjChanged:function(t,e){var n=o["default"].dom(this);if(!t)return void(n.lastChild&&n.removeChild(n.lastChild));var r=a["default"](t);if(e&&a["default"](e)===r)n.lastChild.dialogOpen=this.dialogOpen,n.lastChild.stateObj=t;else{n.lastChild&&n.removeChild(n.lastChild);var i=document.createElement("more-info-"+r);i.stateObj=t,i.dialogOpen=this.dialogOpen,n.appendChild(i)}}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i),u=["entity_picture","friendly_name","unit_of_measurement"];e["default"]=new o["default"]({is:"more-info-default",properties:{stateObj:{type:Object}},computeDisplayAttributes:function(t){return t?Object.keys(t.attributes).filter(function(t){return-1===u.indexOf(t)}):[]},getAttributeValue:function(t,e){return t.attributes[e]}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o),a=n(4),s=r(a);n(31),e["default"]=new u["default"]({is:"more-info-group",behaviors:[s["default"]],properties:{stateObj:{type:Object},states:{type:Array,bindNuclear:[i.moreInfoGetters.currentEntity,i.entityGetters.entityMap,function(t,e){return t?t.attributes.entity_id.map(e.get.bind(e)):[]}]}},updateStates:function(){this.states=this.stateObj&&this.stateObj.attributes.entity_id?stateStore.gets(this.stateObj.attributes.entity_id).toArray():[]}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o),a=n(33),s=r(a);n(137);var c=["brightness","xy_color"];e["default"]=new u["default"]({is:"more-info-light",properties:{stateObj:{type:Object,observer:"stateObjChanged"},brightnessSliderValue:{type:Number,value:0}},stateObjChanged:function(t){var e=this;t&&"on"===t.state&&(this.brightnessSliderValue=t.attributes.brightness),this.async(function(){return e.fire("iron-resize")},500)},computeClassNames:function(t){return s["default"](t,c)},brightnessSliderChanged:function(t){var e=parseInt(t.target.value,10);isNaN(e)||(0===e?i.serviceActions.callTurnOff(this.stateObj.entityId):i.serviceActions.callService("light","turn_on",{entity_id:this.stateObj.entityId,brightness:e}))},colorPicked:function(t){var e=t.detail.rgb;i.serviceActions.callService("light","turn_on",{entity_id:this.stateObj.entityId,rgb_color:[e.r,e.g,e.b]})}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o),a=n(33),s=r(a),c=["volume_level"];e["default"]=new u["default"]({is:"more-info-media_player",properties:{stateObj:{type:Object,observer:"stateObjChanged"},isOff:{type:Boolean,value:!1},isPlaying:{type:Boolean,value:!1},isMuted:{type:Boolean,value:!1},volumeSliderValue:{type:Number,value:0},supportsPause:{type:Boolean,value:!1},supportsVolumeSet:{type:Boolean,value:!1},supportsVolumeMute:{type:Boolean,value:!1},supportsPreviousTrack:{type:Boolean,value:!1},supportsNextTrack:{type:Boolean,value:!1},supportsTurnOn:{type:Boolean,value:!1},supportsTurnOff:{type:Boolean,value:!1}},stateObjChanged:function(t){var e=this;t&&(this.isOff="off"===t.state,this.isPlaying="playing"===t.state,this.volumeSliderValue=100*t.attributes.volume_level,this.isMuted=t.attributes.is_volume_muted,this.supportsPause=0!==(1&t.attributes.supported_media_commands),this.supportsVolumeSet=0!==(4&t.attributes.supported_media_commands),this.supportsVolumeMute=0!==(8&t.attributes.supported_media_commands),this.supportsPreviousTrack=0!==(16&t.attributes.supported_media_commands),this.supportsNextTrack=0!==(32&t.attributes.supported_media_commands),this.supportsTurnOn=0!==(128&t.attributes.supported_media_commands),this.supportsTurnOff=0!==(256&t.attributes.supported_media_commands)),this.async(function(){return e.fire("iron-resize")},500)},computeClassNames:function(t){return s["default"](t,c)},computeIsOff:function(t){return"off"===t.state},computeMuteVolumeIcon:function(t){return t?"av:volume-off":"av:volume-up"},computePlaybackControlIcon:function(){return this.isPlaying?this.supportsPause?"av:pause":"av:stop":"av:play-arrow"},computeHidePowerButton:function(t,e,n){return t?!e:!n},handleTogglePower:function(){this.callService(this.isOff?"turn_on":"turn_off")},handlePrevious:function(){this.callService("media_previous_track")},handlePlaybackControl:function(){this.callService("media_play_pause")},handleNext:function(){this.callService("media_next_track")},handleVolumeTap:function(){this.supportsVolumeMute&&this.callService("volume_mute",{is_volume_muted:!this.isMuted})},volumeSliderChanged:function(t){var e=parseFloat(t.target.value),n=e>0?e/100:0;this.callService("volume_set",{volume_level:n})},callService:function(t,e){var n=e||{};n.entity_id=this.stateObj.entityId,i.serviceActions.callService("media_player",t,n)}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);e["default"]=new o["default"]({is:"more-info-script",properties:{stateObj:{type:Object}}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(61),u=r(o),a=i.util.parseDateTime;e["default"]=new Polymer({is:"more-info-sun",properties:{stateObj:{type:Object},risingDate:{type:Object,computed:"computeRising(stateObj)"},settingDate:{type:Object,computed:"computeSetting(stateObj)"}},computeRising:function(t){return a(t.attributes.next_rising)},computeSetting:function(t){return a(t.attributes.next_setting)},computeOrder:function(t,e){return t>e?["set","ris"]:["ris","set"]},itemCaption:function(t){return"ris"===t?"Rising ":"Setting "},itemDate:function(t){return"ris"===t?this.risingDate:this.settingDate},itemValue:function(t){return u["default"](this.itemDate(t))}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o),a=n(33),s=r(a),c=["away_mode"];e["default"]=new u["default"]({is:"more-info-thermostat",properties:{stateObj:{type:Object,observer:"stateObjChanged"},tempMin:{type:Number},tempMax:{type:Number},targetTemperatureSliderValue:{type:Number},awayToggleChecked:{type:Boolean}},stateObjChanged:function(t){this.targetTemperatureSliderValue=t.state,this.awayToggleChecked="on"===t.attributes.away_mode,this.tempMin=t.attributes.min_temp,this.tempMax=t.attributes.max_temp},computeClassNames:function(t){return s["default"](t,c)},targetTemperatureSliderChanged:function(t){var e=parseInt(t.target.value,10);isNaN(e)||i.serviceActions.callService("thermostat","set_temperature",{entity_id:this.stateObj.entityId,temperature:e})},toggleChanged:function(t){var e=t.target.checked;e&&"off"===this.stateObj.attributes.away_mode?this.service_set_away(!0):e||"on"!==this.stateObj.attributes.away_mode||this.service_set_away(!1)},service_set_away:function(t){var e=this;i.serviceActions.callService("thermostat","set_away_mode",{away_mode:t,entity_id:this.stateObj.entityId}).then(function(){return e.stateObjChanged(e.stateObj)})}}),t.exports=e["default"]},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(2);e["default"]=new Polymer({is:"more-info-updater",properties:{stateObj:{type:Object}},updateTapped:function(){r.serviceActions.callService("updater","update",{})},linkTapped:function(){window.open(this.stateObj.attributes.link,"_blank")}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(17),n(32),e["default"]=new o["default"]({is:"state-card-configurator",properties:{stateObj:{type:Object}}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(17);var u=["playing","paused"];e["default"]=new o["default"]({is:"state-card-media_player",properties:{stateObj:{type:Object},isPlaying:{type:Boolean,computed:"computeIsPlaying(stateObj)"}},computeIsPlaying:function(t){return-1!==u.indexOf(t.state)},computePrimaryText:function(t,e){return e?t.attributes.media_title:t.stateDisplay},computeSecondaryText:function(t){var e=void 0;return"music"===t.attributes.media_content_type?t.attributes.media_artist:"tvshow"===t.attributes.media_content_type?(e=t.attributes.media_series_title,t.attributes.media_season&&t.attributes.media_episode&&(e+=" S"+t.attributes.media_season+"E"+t.attributes.media_episode),e):t.attributes.app_name?t.attributes.app_name:""}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(32),n(58),e["default"]=new o["default"]({is:"state-card-scene",properties:{stateObj:{type:Object},allowToggle:{type:Boolean,value:!1,computed:"computeAllowToggle(stateObj)"}},computeAllowToggle:function(t){return"off"===t.state||t.attributes.active_requested}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(17),e["default"]=new o["default"]({is:"state-card-thermostat",properties:{stateObj:{type:Object}}}),t.exports=e["default"]},function(t,e){"use strict";function n(t){return{attached:function(){var e=this;this.__unwatchFns=Object.keys(this.properties).reduce(function(n,r){if(!("bindNuclear"in e.properties[r]))return n;var i=e.properties[r].bindNuclear;if(!i)throw new Error("Undefined getter specified for key "+r);return e[r]=t.evaluate(i),n.concat(t.observe(i,function(t){e[r]=t}))},[])},detached:function(){for(;this.__unwatchFns.length;)this.__unwatchFns.shift()()}}}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=n,t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){return-1!==a.indexOf(t.domain)?t.domain:u["default"](t.entityId)?"toggle":"display"}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=i;var o=n(59),u=r(o),a=["thermostat","configurator","scene","media_player"];t.exports=e["default"]},function(t,e){"use strict";function n(t){return-1!==r.indexOf(t.domain)?t.domain:"default"}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=n;var r=["light","group","sun","configurator","thermostat","script","media_player","camera","updater"];t.exports=e["default"]},function(t,e){"use strict";function n(t,e,n){var r=1-t-e,i=n/255,o=i/e*t,u=i/e*r,a=1.612*o-.203*i-.302*u,s=.509*-o+1.412*i+.066*u,c=.026*o-.072*i+.962*u;a=.0031308>=a?12.92*a:1.055*Math.pow(a,1/2.4)-.055,s=.0031308>=s?12.92*s:1.055*Math.pow(s,1/2.4)-.055,c=.0031308>=c?12.92*c:1.055*Math.pow(c,1/2.4)-.055;var l=Math.max(a,s,c);return a/=l,s/=l,c/=l,a=255*a,0>a&&(a=255),s=255*s,0>s&&(s=255),c=255*c,0>c&&(c=255),[a,s,c]}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=n,t.exports=e["default"]},function(t,e,n){var r;(function(t,i,o){"use strict";(function(){function u(t){return"function"==typeof t||"object"==typeof t&&null!==t}function a(t){return"function"==typeof t}function s(t){return"object"==typeof t&&null!==t}function c(t){W=t}function l(t){Z=t}function f(){return function(){t.nextTick(_)}}function d(){return function(){q(_)}}function p(){var t=0,e=new tt(_),n=document.createTextNode("");return e.observe(n,{characterData:!0}),function(){n.data=t=++t%2}}function h(){var t=new MessageChannel;return t.port1.onmessage=_,function(){t.port2.postMessage(0)}}function v(){return function(){setTimeout(_,1)}}function _(){for(var t=0;$>t;t+=2){var e=rt[t],n=rt[t+1];e(n),rt[t]=void 0,rt[t+1]=void 0}$=0}function y(){try{var t=n(248);return q=t.runOnLoop||t.runOnContext,d()}catch(e){return v()}}function m(){}function g(){return new TypeError("You cannot resolve a promise with itself")}function b(){return new TypeError("A promises callback cannot return that same promise.")}function O(t){try{return t.then}catch(e){return at.error=e,at}}function w(t,e,n,r){try{t.call(e,n,r)}catch(i){return i}}function S(t,e,n){Z(function(t){var r=!1,i=w(n,e,function(n){r||(r=!0,e!==n?j(t,n):I(t,n))},function(e){r||(r=!0,P(t,e))},"Settle: "+(t._label||" unknown promise"));!r&&i&&(r=!0,P(t,i))},t)}function T(t,e){e._state===ot?I(t,e._result):e._state===ut?P(t,e._result):D(e,void 0,function(e){j(t,e)},function(e){P(t,e)})}function M(t,e){if(e.constructor===t.constructor)T(t,e);else{var n=O(e);n===at?P(t,at.error):void 0===n?I(t,e):a(n)?S(t,e,n):I(t,e)}}function j(t,e){t===e?P(t,g()):u(e)?M(t,e):I(t,e)}function E(t){t._onerror&&t._onerror(t._result),C(t)}function I(t,e){t._state===it&&(t._result=e,t._state=ot,0!==t._subscribers.length&&Z(C,t))}function P(t,e){t._state===it&&(t._state=ut,t._result=e,Z(E,t))}function D(t,e,n,r){var i=t._subscribers,o=i.length;t._onerror=null,i[o]=e,i[o+ot]=n,i[o+ut]=r,0===o&&t._state&&Z(C,t)}function C(t){var e=t._subscribers,n=t._state;if(0!==e.length){for(var r,i,o=t._result,u=0;uu;u++)D(r.resolve(t[u]),void 0,e,n);return i}function Y(t){var e=this;if(t&&"object"==typeof t&&t.constructor===e)return t;var n=new e(m);return j(n,t),n}function H(t){var e=this,n=new e(m);return P(n,t),n}function G(){throw new TypeError("You must pass a resolver function as the first argument to the promise constructor")}function U(){throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.")}function F(t){this._id=ht++,this._state=void 0,this._result=void 0,this._subscribers=[],m!==t&&(a(t)||G(),this instanceof F||U(),L(this,t))}function B(){var t;if("undefined"!=typeof i)t=i;else if("undefined"!=typeof self)t=self;else try{t=Function("return this")()}catch(e){throw new Error("polyfill failed because global object is unavailable in this environment")}var n=t.Promise;(!n||"[object Promise]"!==Object.prototype.toString.call(n.resolve())||n.cast)&&(t.Promise=vt)}var V;V=Array.isArray?Array.isArray:function(t){return"[object Array]"===Object.prototype.toString.call(t)};var q,W,K,J=V,$=0,Z=({}.toString,function(t,e){rt[$]=t,rt[$+1]=e,$+=2,2===$&&(W?W(_):K())}),X="undefined"!=typeof window?window:void 0,Q=X||{},tt=Q.MutationObserver||Q.WebKitMutationObserver,et="undefined"!=typeof t&&"[object process]"==={}.toString.call(t),nt="undefined"!=typeof Uint8ClampedArray&&"undefined"!=typeof importScripts&&"undefined"!=typeof MessageChannel,rt=new Array(1e3); +K=et?f():tt?p():nt?h():void 0===X?y():v();var it=void 0,ot=1,ut=2,at=new A,st=new A;N.prototype._validateInput=function(t){return J(t)},N.prototype._validationError=function(){return new Error("Array Methods must be provided an Array")},N.prototype._init=function(){this._result=new Array(this.length)};var ct=N;N.prototype._enumerate=function(){for(var t=this,e=t.length,n=t.promise,r=t._input,i=0;n._state===it&&e>i;i++)t._eachEntry(r[i],i)},N.prototype._eachEntry=function(t,e){var n=this,r=n._instanceConstructor;s(t)?t.constructor===r&&t._state!==it?(t._onerror=null,n._settledAt(t._state,e,t._result)):n._willSettleAt(r.resolve(t),e):(n._remaining--,n._result[e]=t)},N.prototype._settledAt=function(t,e,n){var r=this,i=r.promise;i._state===it&&(r._remaining--,t===ut?P(i,n):r._result[e]=n),0===r._remaining&&I(i,r._result)},N.prototype._willSettleAt=function(t,e){var n=this;D(t,void 0,function(t){n._settledAt(ot,e,t)},function(t){n._settledAt(ut,e,t)})};var lt=R,ft=z,dt=Y,pt=H,ht=0,vt=F;F.all=lt,F.race=ft,F.resolve=dt,F.reject=pt,F._setScheduler=c,F._setAsap=l,F._asap=Z,F.prototype={constructor:F,then:function(t,e){var n=this,r=n._state;if(r===ot&&!t||r===ut&&!e)return this;var i=new this.constructor(m),o=n._result;if(r){var u=arguments[r-1];Z(function(){k(r,i,u,o)})}else D(n,i,t,e);return i},"catch":function(t){return this.then(null,t)}};var _t=B,yt={Promise:vt,polyfill:_t};n(247).amd?(r=function(){return yt}.call(e,n,e,o),!(void 0!==r&&(o.exports=r))):"undefined"!=typeof o&&o.exports?o.exports=yt:"undefined"!=typeof this&&(this.ES6Promise=yt),_t()}).call(void 0)}).call(e,n(244),function(){return this}(),n(245)(t))},function(t,e,n){"use strict";var r=n(63),i=r(Date,"now"),o=i||function(){return(new Date).getTime()};t.exports=o},function(t,e){"use strict";function n(t){return"number"==typeof t&&t>-1&&t%1==0&&r>=t}var r=9007199254740991;t.exports=n},function(t,e,n){"use strict";var r=n(63),i=n(179),o=n(64),u="[object Array]",a=Object.prototype,s=a.toString,c=r(Array,"isArray"),l=c||function(t){return o(t)&&i(t.length)&&s.call(t)==u};t.exports=l},function(t,e,n){"use strict";function r(t){return null==t?!1:i(t)?l.test(s.call(t)):o(t)&&u.test(t)}var i=n(65),o=n(64),u=/^\[object .+?Constructor\]$/,a=Object.prototype,s=Function.prototype.toString,c=a.hasOwnProperty,l=RegExp("^"+s.call(c).replace(/[\\^$.*+?()[\]{}|]/g,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$");t.exports=r},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(177),i=n(22),o=function(t,e,n){var o=arguments.length<=3||void 0===arguments[3]?null:arguments[3],u=t.evaluate(i.getters.authInfo),a=u.host+"/api/"+n;return new r.Promise(function(t,n){var r=new XMLHttpRequest;r.open(e,a,!0),r.setRequestHeader("X-HA-access",u.authToken),r.onload=function(){if(r.status>199&&r.status<300)t(JSON.parse(r.responseText));else try{n(JSON.parse(r.responseText))}catch(e){n({})}},r.onerror=function(){return n({})},o?r.send(JSON.stringify(o)):r.send()})};e["default"]=o,t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){var n=arguments.length<=2||void 0===arguments[2]?{}:arguments[2],r=n.useStreaming,i=void 0===r?t.evaluate(s.getters.isSupported):r,o=n.rememberAuth,u=void 0===o?!1:o,f=n.host,d=void 0===f?"":f;t.dispatch(a["default"].VALIDATING_AUTH_TOKEN,{authToken:e,host:d}),c.actions.fetchAll(t).then(function(){t.dispatch(a["default"].VALID_AUTH_TOKEN,{authToken:e,host:d,rememberAuth:u}),i?s.actions.start(t,{syncOnInitialConnect:!1}):c.actions.start(t,{skipInitialSync:!0})},function(){var e=arguments.length<=0||void 0===arguments[0]?{}:arguments[0],n=e.message,r=void 0===n?l:n;t.dispatch(a["default"].INVALID_AUTH_TOKEN,{errorMessage:r})})}function o(t){t.dispatch(a["default"].LOG_OUT,{})}Object.defineProperty(e,"__esModule",{value:!0}),e.validate=i,e.logOut=o;var u=n(21),a=r(u),s=n(39),c=n(41),l="Unexpected result from API"},function(t,e){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var n=[["authAttempt","isValidating"],function(t){return!!t}];e.isValidating=n;var r=[["authAttempt","isInvalid"],function(t){return!!t}];e.isInvalidAttempt=r;var i=["authAttempt","errorMessage"];e.attemptErrorMessage=i;var o=["rememberAuth"];e.rememberAuth=o;var u=[["authAttempt","authToken"],["authAttempt","host"],function(t,e){return{authToken:t,host:e}}];e.attemptAuthInfo=u;var a=["authCurrent","authToken"];e.currentAuthToken=a;var s=[a,["authCurrent","host"],function(t,e){return{authToken:t,host:e}}];e.currentAuthInfo=s;var c=[n,["authAttempt","authToken"],["authCurrent","authToken"],function(t,e,n){return t?e:n}];e.authToken=c;var l=[n,u,s,function(t,e,n){return t?e:n}];e.authInfo=l},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){if(null==t)throw new TypeError("Cannot destructure undefined")}function o(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function u(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}function a(t,e){var n=e.authToken,r=e.host;return d.toImmutable({authToken:n,host:r,isValidating:"true",isInvalid:!1,errorMessage:""})}function s(t,e){return i(e),_.getInitialState()}function c(t,e){var n=e.errorMessage;return t.withMutations(function(t){return t.set("isValidating",!1).set("isInvalid","true").set("errorMessage",n)})}Object.defineProperty(e,"__esModule",{value:!0});var l=function(){function t(t,e){for(var n=0;n1&&t.set(p,r)})}function a(){return v.getInitialState()}Object.defineProperty(e,"__esModule",{value:!0});var s=function(){function t(t,e){for(var n=0;no}Object.defineProperty(e,"__esModule",{value:!0});var i=n(3),o=6e4,u=["currentLogbookDate"];e.currentDate=u;var a=[u,["logbookEntriesUpdated"],function(t,e){return r(e.get(t))}];e.isCurrentStale=a;var s=[u,["logbookEntries"],function(t,e){return e.get(t)||i.toImmutable([])}];e.currentEntries=s;var c=["isLoadingLogbookEntries"];e.isLoadingEntries=c},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}function o(t){t.registerStores({currentLogbookDate:a["default"],isLoadingLogbookEntries:c["default"],logbookEntries:f["default"],logbookEntriesUpdated:p["default"]})}Object.defineProperty(e,"__esModule",{value:!0}),e.register=o;var u=n(206),a=i(u),s=n(207),c=i(s),l=n(208),f=i(l),d=n(209),p=i(d),h=n(202),v=r(h),_=n(203),y=r(_),m=v;e.actions=m;var g=y;e.getters=g},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function o(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}Object.defineProperty(e,"__esModule",{value:!0});var u=function(){function t(t,e){for(var n=0;n1)for(var n=1;n \ No newline at end of file diff --git a/homeassistant/components/frontend/www_static/home-assistant-polymer b/homeassistant/components/frontend/www_static/home-assistant-polymer index 6c0bb8a47dd..1c82a536312 160000 --- a/homeassistant/components/frontend/www_static/home-assistant-polymer +++ b/homeassistant/components/frontend/www_static/home-assistant-polymer @@ -1 +1 @@ -Subproject commit 6c0bb8a47ddc296c79c0ea47c033e3d7d7a742fd +Subproject commit 1c82a536312e8321716ab7d80a5d17045d20d77f From f3868ea744d1fcd41cb9d2d04f4545d6ecabade6 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Thu, 27 Aug 2015 14:14:50 +0200 Subject: [PATCH 189/224] update comments --- requirements.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/requirements.txt b/requirements.txt index 8bcd5470034..351c2c07ae4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -74,7 +74,7 @@ python-forecastio>=1.3.3 # Firmata Bindings (*.arduino) PyMata==2.07a -#Rfxtrx sensor +# Rfxtrx sensor (sensor.rfxtrx) https://github.com/Danielhiversen/pyRFXtrx/archive/master.zip # Mysensors @@ -92,23 +92,23 @@ pywemo>=0.2 # Wink (*.wink) https://github.com/balloob/python-wink/archive/master.zip#pywink>=0.1 -# Slack notifier +# Slack notifier (notify.slack) slacker>=0.6.8 -# Temper sensors +# Temper sensors (sensor.temper) https://github.com/rkabadi/temper-python/archive/master.zip # PyEdimax https://github.com/rkabadi/pyedimax/archive/master.zip -# RPI-GPIO platform +# RPI-GPIO platform (*.rpi_gpio) RPi.GPIO >=0.5.11 -# PAHO MQTT Binding (protocol.mqtt) +# PAHO MQTT Binding (mqtt) paho-mqtt>=1.1 # PyModbus (modbus) https://github.com/bashwork/pymodbus/archive/python3.zip#pymodbus>=1.2.0 -# Verisure +# Verisure (verisure) https://github.com/persandstrom/python-verisure/archive/master.zip From 6582067f66baafb2b96ee8f49d9bcaf2c1d7ec0d Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Thu, 27 Aug 2015 14:15:57 +0200 Subject: [PATCH 190/224] add dnspython to enable xmpp to do srv lookups --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 351c2c07ae4..a28088c94b8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -52,6 +52,7 @@ pyowm>=2.2.1 # XMPP Bindings (notify.xmpp) sleekxmpp>=1.3.1 +dnspython>=1.11.1 # Blockchain (sensor.bitcoin) blockchain>=1.1.2 From 387769edff3faa894e23ed24660eeea207b415cd Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Thu, 27 Aug 2015 14:16:29 +0200 Subject: [PATCH 191/224] add dnspython --- homeassistant/components/notify/xmpp.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/homeassistant/components/notify/xmpp.py b/homeassistant/components/notify/xmpp.py index 25099921f45..74269907c06 100644 --- a/homeassistant/components/notify/xmpp.py +++ b/homeassistant/components/notify/xmpp.py @@ -1,7 +1,6 @@ """ homeassistant.components.notify.xmpp ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Jabber (XMPP) notification service. Configuration: @@ -29,7 +28,6 @@ The password for your given Jabber account. recipient *Required The Jabber ID (JID) that will receive the messages. - """ import logging @@ -47,7 +45,7 @@ from homeassistant.helpers import validate_config from homeassistant.components.notify import ( DOMAIN, ATTR_TITLE, BaseNotificationService) -REQUIREMENTS = ['sleekxmpp>=1.3.1'] +REQUIREMENTS = ['sleekxmpp>=1.3.1', 'dnspython>=1.11.1'] def get_service(hass, config): From 17bf27474a92233d3737e38ff3f784e49c498f7b Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Thu, 27 Aug 2015 14:25:21 +0200 Subject: [PATCH 192/224] fix dnspython version --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index a28088c94b8..d27815e13cd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -52,7 +52,7 @@ pyowm>=2.2.1 # XMPP Bindings (notify.xmpp) sleekxmpp>=1.3.1 -dnspython>=1.11.1 +dnspython3>=1.12.0 # Blockchain (sensor.bitcoin) blockchain>=1.1.2 From e0db473294178498589e06c81de6f9caa08fea5e Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Thu, 27 Aug 2015 14:26:06 +0200 Subject: [PATCH 193/224] update dnspython --- homeassistant/components/notify/xmpp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/notify/xmpp.py b/homeassistant/components/notify/xmpp.py index 74269907c06..e756be82014 100644 --- a/homeassistant/components/notify/xmpp.py +++ b/homeassistant/components/notify/xmpp.py @@ -45,7 +45,7 @@ from homeassistant.helpers import validate_config from homeassistant.components.notify import ( DOMAIN, ATTR_TITLE, BaseNotificationService) -REQUIREMENTS = ['sleekxmpp>=1.3.1', 'dnspython>=1.11.1'] +REQUIREMENTS = ['sleekxmpp>=1.3.1', 'dnspython3>=1.12.0'] def get_service(hass, config): From 1aef768ff08f19d2fbc24c95276a0325bb179f82 Mon Sep 17 00:00:00 2001 From: sfam Date: Fri, 28 Aug 2015 00:02:26 +0100 Subject: [PATCH 194/224] Initial support for THOMSON routers --- .../components/device_tracker/thomson.py | 156 ++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 homeassistant/components/device_tracker/thomson.py diff --git a/homeassistant/components/device_tracker/thomson.py b/homeassistant/components/device_tracker/thomson.py new file mode 100644 index 00000000000..a1ed9ff5f4b --- /dev/null +++ b/homeassistant/components/device_tracker/thomson.py @@ -0,0 +1,156 @@ +""" +homeassistant.components.device_tracker.thomson +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Device tracker platform that supports scanning a THOMSON router for device +presence. + +This device tracker needs telnet to be enabled on the router. + +Configuration: + +To use the THOMSON tracker you will need to add something like the following +to your config/configuration.yaml + +device_tracker: + platform: thomson + host: YOUR_ROUTER_IP + username: YOUR_ADMIN_USERNAME + password: YOUR_ADMIN_PASSWORD + +Variables: + +host +*Required +The IP address of your router, e.g. 192.168.1.1. + +username +*Required +The username of an user with administrative privileges, usually 'admin'. + +password +*Required +The password for your given admin account. +""" +import logging +from datetime import timedelta +import re +import threading +import telnetlib + +from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD +from homeassistant.helpers import validate_config +from homeassistant.util import Throttle +from homeassistant.components.device_tracker import DOMAIN + +# Return cached results if last scan was less then this time ago +MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) + +_LOGGER = logging.getLogger(__name__) + +_DEVICES_REGEX = re.compile( + r'(?P(([0-9a-f]{2}[:-]){5}([0-9a-f]{2})))\s' + + r'(?P([0-9]{1,3}[\.]){3}[0-9]{1,3})\s+' + + r'(?P([^\s]+))\s+' + + r'(?P([^\s]+))\s+' + + r'(?P([^\s]+))\s+' + + r'(?P([^\s]+))\s+' + + r'(?P([^\s]+))') + +# pylint: disable=unused-argument +def get_scanner(hass, config): + """ Validates config and returns a THOMSON scanner. """ + if not validate_config(config, + {DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]}, + _LOGGER): + return None + + scanner = ThomsonDeviceScanner(config[DOMAIN]) + + return scanner if scanner.success_init else None + + +class ThomsonDeviceScanner(object): + """ This class queries a router running THOMSON firmware + for connected devices. Adapted from ASUSWRT scanner. + """ + + def __init__(self, config): + self.host = config[CONF_HOST] + self.username = config[CONF_USERNAME] + self.password = config[CONF_PASSWORD] + + self.lock = threading.Lock() + + self.last_results = {} + + # Test the router is accessible + data = self.get_thomson_data() + self.success_init = data is not None + + def scan_devices(self): + """ Scans for new devices and return a + list containing found device ids. """ + + self._update_info() + return [client['mac'] for client in self.last_results] + + def get_device_name(self, device): + """ Returns the name of the given device + or None if we don't know. """ + if not self.last_results: + return None + for client in self.last_results: + if client['mac'] == device: + return client['host'] + return None + + @Throttle(MIN_TIME_BETWEEN_SCANS) + def _update_info(self): + """ Ensures the information from the THOMSON router is up to date. + Returns boolean if scanning successful. """ + if not self.success_init: + return False + + with self.lock: + _LOGGER.info("Checking ARP") + data = self.get_thomson_data() + if not data: + return False + + # flag C stands for CONNECTED + active_clients = [client for client in data.values() if + client['status'].find('C') != -1] + self.last_results = active_clients + return True + + def get_thomson_data(self): + """ Retrieve data from THOMSON and return parsed result. """ + try: + telnet = telnetlib.Telnet(self.host) + telnet.read_until(b'Username : ') + telnet.write((self.username + '\r\n').encode('ascii')) + telnet.read_until(b'Password : ') + telnet.write((self.password + '\r\n').encode('ascii')) + telnet.read_until(b'=>') + telnet.write(('hostmgr list\r\n').encode('ascii')) + devices_result = telnet.read_until(b'=>').split(b'\r\n') + telnet.write('exit\r\n'.encode('ascii')) + except EOFError: + _LOGGER.exception("Unexpected response from router") + return + except ConnectionRefusedError: + _LOGGER.exception("Connection refused by router," + + " is telnet enabled?") + return + + devices = {} + for device in devices_result: + match = _DEVICES_REGEX.search(device.decode('utf-8')) + if match: + devices[match.group('ip')] = { + 'ip': match.group('ip'), + 'mac': match.group('mac').upper(), + 'host': match.group('host'), + 'status': match.group('status') + } + return devices From 1311e00e901c7c651ac3c2cde9ebd4a12ffe0e1e Mon Sep 17 00:00:00 2001 From: sfam Date: Fri, 28 Aug 2015 00:03:05 +0100 Subject: [PATCH 195/224] Initial support for THOMSON routers --- .coveragerc | 1 + 1 file changed, 1 insertion(+) diff --git a/.coveragerc b/.coveragerc index 63763cb9231..28a8fd310ef 100644 --- a/.coveragerc +++ b/.coveragerc @@ -36,6 +36,7 @@ omit = homeassistant/components/device_tracker/luci.py homeassistant/components/device_tracker/netgear.py homeassistant/components/device_tracker/nmap_tracker.py + homeassistant/components/device_tracker/thomson.py homeassistant/components/device_tracker/tomato.py homeassistant/components/device_tracker/tplink.py homeassistant/components/discovery.py From 4ef4aa2095db1a361d2d6070cf57aa20522ae9ae Mon Sep 17 00:00:00 2001 From: sfam Date: Fri, 28 Aug 2015 00:09:24 +0100 Subject: [PATCH 196/224] fix pylint warnings --- homeassistant/components/device_tracker/thomson.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/device_tracker/thomson.py b/homeassistant/components/device_tracker/thomson.py index a1ed9ff5f4b..ffe1a7f64c2 100644 --- a/homeassistant/components/device_tracker/thomson.py +++ b/homeassistant/components/device_tracker/thomson.py @@ -50,12 +50,13 @@ _LOGGER = logging.getLogger(__name__) _DEVICES_REGEX = re.compile( r'(?P(([0-9a-f]{2}[:-]){5}([0-9a-f]{2})))\s' + r'(?P([0-9]{1,3}[\.]){3}[0-9]{1,3})\s+' + - r'(?P([^\s]+))\s+' + - r'(?P([^\s]+))\s+' + - r'(?P([^\s]+))\s+' + - r'(?P([^\s]+))\s+' + + r'(?P([^\s]+))\s+' + + r'(?P([^\s]+))\s+' + + r'(?P([^\s]+))\s+' + + r'(?P([^\s]+))\s+' + r'(?P([^\s]+))') + # pylint: disable=unused-argument def get_scanner(hass, config): """ Validates config and returns a THOMSON scanner. """ @@ -95,7 +96,7 @@ class ThomsonDeviceScanner(object): return [client['mac'] for client in self.last_results] def get_device_name(self, device): - """ Returns the name of the given device + """ Returns the name of the given device or None if we don't know. """ if not self.last_results: return None @@ -116,7 +117,7 @@ class ThomsonDeviceScanner(object): data = self.get_thomson_data() if not data: return False - + # flag C stands for CONNECTED active_clients = [client for client in data.values() if client['status'].find('C') != -1] From 71e60dcfe9c738cda6ebe9d80309e855a2a0defa Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Thu, 27 Aug 2015 22:41:12 -0400 Subject: [PATCH 197/224] Fixed Issue #280 --- scripts/homeassistant.daemon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/homeassistant.daemon b/scripts/homeassistant.daemon index bef4cd90f4a..4dd6b37a9c5 100755 --- a/scripts/homeassistant.daemon +++ b/scripts/homeassistant.daemon @@ -79,7 +79,7 @@ case "$1" in uninstall) uninstall ;; - retart) + restart) stop start ;; From 76a8bd396906e425a9cbc27c35768ff45a0f1e6c Mon Sep 17 00:00:00 2001 From: MakeMeASandwich Date: Fri, 28 Aug 2015 14:42:41 +0200 Subject: [PATCH 198/224] Add support for Adafruit temperature/humidity sensors --- .coveragerc | 1 + homeassistant/components/sensor/dht.py | 160 +++++++++++++++++++++++++ requirements.txt | 3 + 3 files changed, 164 insertions(+) create mode 100644 homeassistant/components/sensor/dht.py diff --git a/.coveragerc b/.coveragerc index 63763cb9231..54c1b234351 100644 --- a/.coveragerc +++ b/.coveragerc @@ -57,6 +57,7 @@ omit = homeassistant/components/notify/syslog.py homeassistant/components/notify/xmpp.py homeassistant/components/sensor/bitcoin.py + homeassistant/components/sensor/dht.py homeassistant/components/sensor/efergy.py homeassistant/components/sensor/forecast.py homeassistant/components/sensor/mysensors.py diff --git a/homeassistant/components/sensor/dht.py b/homeassistant/components/sensor/dht.py new file mode 100644 index 00000000000..1334bdc59b7 --- /dev/null +++ b/homeassistant/components/sensor/dht.py @@ -0,0 +1,160 @@ +""" +homeassistant.components.sensor.dht +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Adafruit DHT temperature and humidity sensor. +As this requires access to the GPIO, you will need to run home-assistant +as root (FIXME). + +Configuration: + +To use the Adafruit DHT sensor you will need to +add something like the following to your config/configuration.yaml: + +sensor: + platform: dht + sensor: DHT22 + pin: 23 + monitored_conditions: + - temperature + - humidity + +Variables: + +sensor +*Required +The sensor type, DHT11, DHT22 or AM2302 + +pin +*Required +The pin the sensor is connected to, something like +'P8_11' for Beaglebone, '23' for Raspberry Pi + +monitored_conditions +*Optional +Conditions to monitor. Available conditions are temperature and humidity. +""" +import logging +from datetime import timedelta + +from homeassistant.util import Throttle +from homeassistant.const import TEMP_FAHRENHEIT +from homeassistant.helpers.entity import Entity + +# TODO: update this requirement to upstream as soon as it supports python3 +REQUIREMENTS = ['git+git://github.com/mala-zaba/Adafruit_Python_DHT'] +_LOGGER = logging.getLogger(__name__) +SENSOR_TYPES = { + 'temperature': ['Temperature', ''], + 'humidity': ['Humidity', '%'] +} +# Return cached results if last scan was less then this time ago +# DHT11 is able to deliver data once per second, DHT22 once every two +MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """ Get the DHT sensor. """ + + try: + import Adafruit_DHT + + except ImportError: + _LOGGER.exception( + "Unable to import Adafruit_DHT. " + "Did you maybe not install the 'Adafruit_DHT' package?") + + return False + + SENSOR_TYPES['temperature'][1] = hass.config.temperature_unit + unit = hass.config.temperature_unit + available_sensors = { + "DHT11": Adafruit_DHT.DHT11, + "DHT22": Adafruit_DHT.DHT22, + "AM2302": Adafruit_DHT.AM2302 + } + sensor = available_sensors[config['sensor']] + + pin = config['pin'] + + if not sensor or not pin: + _LOGGER.error( + "Config error " + "Please check your settings for DHT, sensor not supported.") + return None + + data = DHTClient(Adafruit_DHT, sensor, pin) + dev = [] + try: + for variable in config['monitored_conditions']: + if variable not in SENSOR_TYPES: + _LOGGER.error('Sensor type: "%s" does not exist', variable) + else: + dev.append(DHTSensor(data, variable, unit)) + except KeyError: + pass + + add_devices(dev) + + +# pylint: disable=too-few-public-methods +class DHTSensor(Entity): + + """ Implements an DHT sensor. """ + + def __init__(self, dht_client, sensor_type, temp_unit): + self.client_name = 'DHT sensor' + self._name = SENSOR_TYPES[sensor_type][0] + self.dht_client = dht_client + self.temp_unit = temp_unit + self.type = sensor_type + self._state = None + self._unit_of_measurement = SENSOR_TYPES[sensor_type][1] + self.update() + + @property + def name(self): + return '{} {}'.format(self.client_name, self._name) + + @property + def state(self): + """ Returns the state of the device. """ + return self._state + + @property + def unit_of_measurement(self): + """ Unit of measurement of this entity, if any. """ + return self._unit_of_measurement + + def update(self): + """ Gets the latest data from the DHT and updates the states. """ + + self.dht_client.update() + data = self.dht_client.data + + if self.type == 'temperature': + self._state = round(data['temperature'], 1) + if self.temp_unit == TEMP_FAHRENHEIT: + self._state = round(data['temperature'] * 1.8 + 32, 1) + elif self.type == 'humidity': + self._state = round(data['humidity'], 1) + + +class DHTClient(object): + + """ Gets the latest data from the DHT sensor. """ + + def __init__(self, adafruit_dht, sensor, pin): + self.adafruit_dht = adafruit_dht + self.sensor = sensor + self.pin = pin + self.data = dict() + + @Throttle(MIN_TIME_BETWEEN_UPDATES) + def update(self): + """ Gets the latest data the DHT sensor. """ + humidity, temperature = self.adafruit_dht.read_retry(self.sensor, + self.pin) + if temperature: + self.data['temperature'] = temperature + if humidity: + self.data['humidity'] = humidity diff --git a/requirements.txt b/requirements.txt index d27815e13cd..394d038de13 100644 --- a/requirements.txt +++ b/requirements.txt @@ -105,6 +105,9 @@ https://github.com/rkabadi/pyedimax/archive/master.zip # RPI-GPIO platform (*.rpi_gpio) RPi.GPIO >=0.5.11 +# Adafruit temperature/humidity sensor +git+git://github.com/mala-zaba/Adafruit_Python_DHT + # PAHO MQTT Binding (mqtt) paho-mqtt>=1.1 From 3f4d5eae1c224c382e0d1bad262139de570a4d1a Mon Sep 17 00:00:00 2001 From: MakeMeASandwich Date: Fri, 28 Aug 2015 18:14:13 +0200 Subject: [PATCH 199/224] work around build failures also on non-RPi/BB-machines --- homeassistant/components/sensor/dht.py | 7 +++++-- requirements.txt | 3 ++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sensor/dht.py b/homeassistant/components/sensor/dht.py index 1334bdc59b7..c7df4b4a8d3 100644 --- a/homeassistant/components/sensor/dht.py +++ b/homeassistant/components/sensor/dht.py @@ -2,8 +2,11 @@ homeassistant.components.sensor.dht ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Adafruit DHT temperature and humidity sensor. +You need a Python3 compatible version of the Adafruit_Python_DHT library +(e.g. https://github.com/mala-zaba/Adafruit_Python_DHT, +also see requirements.txt). As this requires access to the GPIO, you will need to run home-assistant -as root (FIXME). +as root. Configuration: @@ -40,7 +43,7 @@ from homeassistant.util import Throttle from homeassistant.const import TEMP_FAHRENHEIT from homeassistant.helpers.entity import Entity -# TODO: update this requirement to upstream as soon as it supports python3 +# update this requirement to upstream as soon as it supports python3 REQUIREMENTS = ['git+git://github.com/mala-zaba/Adafruit_Python_DHT'] _LOGGER = logging.getLogger(__name__) SENSOR_TYPES = { diff --git a/requirements.txt b/requirements.txt index 394d038de13..f851f70e86f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -106,7 +106,8 @@ https://github.com/rkabadi/pyedimax/archive/master.zip RPi.GPIO >=0.5.11 # Adafruit temperature/humidity sensor -git+git://github.com/mala-zaba/Adafruit_Python_DHT +# uncomment on a Raspberry Pi / Beaglebone +#git+git://github.com/mala-zaba/Adafruit_Python_DHT # PAHO MQTT Binding (mqtt) paho-mqtt>=1.1 From 936e20bdf7a82bd364edf5617e5488312aaaab06 Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Fri, 28 Aug 2015 20:17:07 -0400 Subject: [PATCH 200/224] Cleaned up some entities. 1) Modified device tracker entities to allow for attributes to be overwritten with configuration data. 2) Modified ISY lights to hide brightness when off. --- homeassistant/components/device_tracker/__init__.py | 8 ++++++-- homeassistant/components/isy994.py | 5 +++++ homeassistant/components/light/isy994.py | 6 ++++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/device_tracker/__init__.py b/homeassistant/components/device_tracker/__init__.py index e529327f505..099c23973f0 100644 --- a/homeassistant/components/device_tracker/__init__.py +++ b/homeassistant/components/device_tracker/__init__.py @@ -12,6 +12,7 @@ from datetime import timedelta from homeassistant.loader import get_component from homeassistant.helpers import validate_config +from homeassistant.helpers.entity import _OVERWRITE import homeassistant.util as util import homeassistant.util.dt as dt_util @@ -162,9 +163,12 @@ class DeviceTracker(object): state = STATE_HOME if is_home else STATE_NOT_HOME + # overwrite properties that have been set in the config file + attr = dict(dev_info['state_attr']) + attr.update(_OVERWRITE.get(dev_info['entity_id'], {})) + self.hass.states.set( - dev_info['entity_id'], state, - dev_info['state_attr']) + dev_info['entity_id'], state, attr) def update_devices(self, now): """ Update device states based on the found devices. """ diff --git a/homeassistant/components/isy994.py b/homeassistant/components/isy994.py index 5cdfbe1b277..ad165d64d6c 100644 --- a/homeassistant/components/isy994.py +++ b/homeassistant/components/isy994.py @@ -156,6 +156,11 @@ class ISYDeviceABC(ToggleEntity): attr = {ATTR_FRIENDLY_NAME: self.name} for name, prop in self._attrs.items(): attr[name] = getattr(self, prop) + attr = self._attr_filter(attr) + return attr + + def _attr_filter(self, attr): + """ Placeholder for attribute filters. """ return attr @property diff --git a/homeassistant/components/light/isy994.py b/homeassistant/components/light/isy994.py index b231fe3e441..5b62120ee98 100644 --- a/homeassistant/components/light/isy994.py +++ b/homeassistant/components/light/isy994.py @@ -38,3 +38,9 @@ class ISYLightDevice(ISYDeviceABC): _attrs = {ATTR_BRIGHTNESS: 'value'} _onattrs = [ATTR_BRIGHTNESS] _states = [STATE_ON, STATE_OFF] + + def _attr_filter(self, attr): + """ Filter brightness out of entity while off. """ + if ATTR_BRIGHTNESS in attr and not self.is_on: + del attr[ATTR_BRIGHTNESS] + return attr From c49cdf7ffd966160d189973c973ac77b9090eb2d Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Fri, 28 Aug 2015 20:18:54 -0400 Subject: [PATCH 201/224] Pylint fixes to ISY component. --- homeassistant/components/isy994.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/isy994.py b/homeassistant/components/isy994.py index ad165d64d6c..f5998faeaf8 100644 --- a/homeassistant/components/isy994.py +++ b/homeassistant/components/isy994.py @@ -161,6 +161,7 @@ class ISYDeviceABC(ToggleEntity): def _attr_filter(self, attr): """ Placeholder for attribute filters. """ + # pylint: disable=no-self-use return attr @property From 0a2652630f6f2e6e86889283c2614f10a2c08308 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 29 Aug 2015 11:50:43 -0700 Subject: [PATCH 202/224] Frontend: Style loading page like login form --- .../components/frontend/index.html.template | 48 +++++++++++++----- .../components/frontend/www_static/splash.png | Bin 0 -> 11636 bytes 2 files changed, 35 insertions(+), 13 deletions(-) create mode 100644 homeassistant/components/frontend/www_static/splash.png diff --git a/homeassistant/components/frontend/index.html.template b/homeassistant/components/frontend/index.html.template index f84c8653b31..556fe4e67c8 100644 --- a/homeassistant/components/frontend/index.html.template +++ b/homeassistant/components/frontend/index.html.template @@ -5,24 +5,46 @@ Home Assistant - - - - - - + + + - - -

Initializing Home Assistant

- - - + + + +
+ +

Initializing

+
+ + + diff --git a/homeassistant/components/frontend/www_static/splash.png b/homeassistant/components/frontend/www_static/splash.png new file mode 100644 index 0000000000000000000000000000000000000000..3d5411ecb4b2612011c33b5dcbf593864c775857 GIT binary patch literal 11636 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 Date: Sat, 29 Aug 2015 18:59:05 -0400 Subject: [PATCH 203/224] Created setup.py for standard installs. --- .../frontend/www_static/__init__.py | 0 .../frontend/www_static/images/__init__.py | 0 homeassistant/const.py | 3 ++ pylintrc | 2 +- setup.py | 48 +++++++++++++++++++ 5 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/frontend/www_static/__init__.py create mode 100644 homeassistant/components/frontend/www_static/images/__init__.py create mode 100755 setup.py diff --git a/homeassistant/components/frontend/www_static/__init__.py b/homeassistant/components/frontend/www_static/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/homeassistant/components/frontend/www_static/images/__init__.py b/homeassistant/components/frontend/www_static/images/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/homeassistant/const.py b/homeassistant/const.py index 7d58dbb01d2..a3b9cc8d396 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,4 +1,7 @@ """ Constants used by Home Assistant components. """ + +__version__ = "0.7.0" + # Can be used to specify a catch all when registering state or event listeners. MATCH_ALL = '*' diff --git a/pylintrc b/pylintrc index 54b1f80cdc5..888fb50ee0f 100644 --- a/pylintrc +++ b/pylintrc @@ -1,5 +1,5 @@ [MASTER] -ignore=external +ignore=external,setup.py reports=no # Reasons disabled: diff --git a/setup.py b/setup.py new file mode 100755 index 00000000000..fb6f88ac115 --- /dev/null +++ b/setup.py @@ -0,0 +1,48 @@ +import os +import re +from setuptools import setup, find_packages + +PACKAGE_NAME = 'homeassistant' +HERE = os.path.abspath(os.path.dirname(__file__)) +with open(os.path.join(HERE, PACKAGE_NAME, 'const.py')) as fp: + VERSION = re.search("__version__ = ['\"]([^']+)['\"]\n", fp.read()).group(1) + +PACKAGES = find_packages() + \ + ['homeassistant.external', 'homeassistant.external.noop', + 'homeassistant.external.nzbclients', 'homeassistant.external.vera'] + +PACKAGE_DATA = \ + {'homeassistant.components.frontend': ['index.html.template'], + 'homeassistant.components.frontend.www_static': ['*.*'], + 'homeassistant.components.frontend.www_static.images': ['*.*']} + +setup( + name=PACKAGE_NAME, + version=VERSION, + license='MIT License', + url='https://home-assistant.io/', + download_url='https://github.com/automicus/pyisy/tarball/0.7.0', + author='Paulus Schoutsen', + author_email='paulus@paulusschoutsen.nl', + description='Open-source home automation platform running on Python 3.', + packages=PACKAGES, + include_package_data=True, + package_data=PACKAGE_DATA, + zip_safe=False, + platforms='any', + install_requires=['requests==2.7.0', 'pyyaml==3.11', 'pytz==2015.4', + 'netdisco==0.3', 'astral==0.8.1'], + keywords=['home', 'automation'], + entry_points={ + 'console_scripts': [ + 'hass = homeassistant.__main__:main' + ] + }, + classifiers=[ + 'Intended Audience :: End Users/Desktop', + 'License :: OSI Approved :: MIT License', + 'Operating System :: OS Independent', + 'Programming Language :: Python :: 3.4', + 'Topic :: Home Automation' + ] +) From 18e32165a49f2eb354b07167345668163d537344 Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Sat, 29 Aug 2015 20:06:54 -0400 Subject: [PATCH 204/224] Cleaned up main file to remove dependency management. --- homeassistant/__main__.py | 91 +++++++++++---------------------------- setup.py | 3 +- 2 files changed, 25 insertions(+), 69 deletions(-) diff --git a/homeassistant/__main__.py b/homeassistant/__main__.py index 2514b35587f..40cde95751f 100644 --- a/homeassistant/__main__.py +++ b/homeassistant/__main__.py @@ -4,12 +4,14 @@ from __future__ import print_function 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')) +from homeassistant import bootstrap +import homeassistant.config as config_util +from homeassistant.components import frontend, demo + +USER_DATA_DIR = os.getenv('APPDATA') if os.name == "nt" \ + else os.path.expanduser('~') def validate_python(): @@ -31,54 +33,6 @@ def ensure_pip(): 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 = [sys.executable, '-m', 'pip', 'install', '--quiet', package] - if not IS_VIRTUAL: - args.append('--user') - 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 - - for requirement in DEPENDENCIES: - if not install_package(requirement): - import_fail = True - print('Fatal Error: Unable to install dependency', requirement) - - if import_fail: - print(("Install dependencies by running: " - "python3 -m pip install -r requirements.txt")) - sys.exit() - - -def ensure_path_and_load_bootstrap(): - """ Ensure sys load path is correct and load Home Assistant bootstrap. """ - try: - from homeassistant import bootstrap - - except ImportError: - # This is to add support to load Home Assistant using - # `python3 homeassistant` instead of `python3 -m homeassistant` - - # Insert the parent directory of this file into the module search path - sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) - - from homeassistant import bootstrap - - return bootstrap - - def validate_git_submodules(): """ Validate the git submodules are cloned. """ try: @@ -94,13 +48,25 @@ def ensure_config_path(config_dir): """ Gets the path to the configuration file. Creates one if it not exists. """ + lib_dir = os.path.join(config_dir, 'lib') + # Test if configuration directory exists if not os.path.isdir(config_dir): - print(('Fatal Error: Unable to find specified configuration ' - 'directory {} ').format(config_dir)) - sys.exit() + try: + os.mkdir(config_dir) + except OSError: + print(('Fatal Error: Unable to create specified configuration ' + 'directory {} ').format(config_dir)) + sys.exit() - import homeassistant.config as config_util + # Test if library directory exists + if not os.path.isdir(lib_dir): + try: + os.mkdir(lib_dir) + except OSError: + print(('Fatal Error: Unable to create library ' + 'directory {} ').format(lib_dir)) + sys.exit() config_path = config_util.ensure_config_exists(config_dir) @@ -117,7 +83,7 @@ def get_arguments(): parser.add_argument( '-c', '--config', metavar='path_to_config_dir', - default="config", + default=os.path.join(USER_DATA_DIR, '.homeassistant'), help="Directory that contains the Home Assistant configuration") parser.add_argument( '--demo-mode', @@ -134,12 +100,6 @@ def get_arguments(): def main(): """ Starts Home Assistant. """ validate_python() - validate_dependencies() - - # Windows needs this to pick up new modules - importlib.invalidate_caches() - - bootstrap = ensure_path_and_load_bootstrap() validate_git_submodules() @@ -149,8 +109,6 @@ def main(): config_path = ensure_config_path(config_dir) if args.demo_mode: - from homeassistant.components import frontend, demo - hass = bootstrap.from_config_dict({ frontend.DOMAIN: {}, demo.DOMAIN: {} @@ -159,11 +117,10 @@ def main(): hass = bootstrap.from_config_file(config_path) if args.open_ui: - from homeassistant.const import EVENT_HOMEASSISTANT_START - def open_browser(event): """ Open the webinterface in a browser. """ if hass.config.api is not None: + from homeassistant.const import EVENT_HOMEASSISTANT_START import webbrowser webbrowser.open(hass.config.api.base_url) diff --git a/setup.py b/setup.py index fb6f88ac115..f922459e509 100755 --- a/setup.py +++ b/setup.py @@ -30,8 +30,7 @@ setup( package_data=PACKAGE_DATA, zip_safe=False, platforms='any', - install_requires=['requests==2.7.0', 'pyyaml==3.11', 'pytz==2015.4', - 'netdisco==0.3', 'astral==0.8.1'], + install_requires=['requests==2.7.0', 'pyyaml==3.11', 'pytz==2015.4'], keywords=['home', 'automation'], entry_points={ 'console_scripts': [ From 6fdf9b8d7cf53f667c11f94c1b80e4a6321bb987 Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Sat, 29 Aug 2015 21:11:24 -0400 Subject: [PATCH 205/224] Many changes to cleanup config directory and lib installations. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cleaned up default config directory determination. Made bootstrap creators for HA always set config directory. Made bootstrap creators set the local library in the Python Path. Moved all exceptions to their own file to make imports easier. Moved default configuration directory be in the users’ profile. Moved pip installs to be done to a lib folder in the config directory. Reduced requirements.txt to only the barebones reqs. --- homeassistant/__main__.py | 7 +- homeassistant/bootstrap.py | 14 ++-- homeassistant/config.py | 10 ++- homeassistant/core.py | 25 +++---- homeassistant/exceptions.py | 15 +++++ homeassistant/util/package.py | 10 +-- requirements.txt | 122 +--------------------------------- 7 files changed, 53 insertions(+), 150 deletions(-) create mode 100644 homeassistant/exceptions.py diff --git a/homeassistant/__main__.py b/homeassistant/__main__.py index 40cde95751f..59c2cd11abb 100644 --- a/homeassistant/__main__.py +++ b/homeassistant/__main__.py @@ -10,9 +10,6 @@ from homeassistant import bootstrap import homeassistant.config as config_util from homeassistant.components import frontend, demo -USER_DATA_DIR = os.getenv('APPDATA') if os.name == "nt" \ - else os.path.expanduser('~') - def validate_python(): """ Validate we're running the right Python version. """ @@ -83,7 +80,7 @@ def get_arguments(): parser.add_argument( '-c', '--config', metavar='path_to_config_dir', - default=os.path.join(USER_DATA_DIR, '.homeassistant'), + default=config_util.get_default_config_dir(), help="Directory that contains the Home Assistant configuration") parser.add_argument( '--demo-mode', @@ -112,7 +109,7 @@ def main(): hass = bootstrap.from_config_dict({ frontend.DOMAIN: {}, demo.DOMAIN: {} - }) + }, config_dir=config_dir) else: hass = bootstrap.from_config_file(config_path) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index e5f6d2b9672..e4cd307019e 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -61,13 +61,13 @@ def setup_component(hass, domain, config=None): return True -def _handle_requirements(component, name): +def _handle_requirements(hass, component, name): """ Installs requirements for component. """ if not hasattr(component, 'REQUIREMENTS'): return True for req in component.REQUIREMENTS: - if not pkg_util.install_package(req): + if not pkg_util.install_package(req, target=hass.config.path('lib')): _LOGGER.error('Not initializing %s because could not install ' 'dependency %s', name, req) return False @@ -88,7 +88,7 @@ def _setup_component(hass, domain, config): domain, ", ".join(missing_deps)) return False - if not _handle_requirements(component, domain): + if not _handle_requirements(hass, component, domain): return False try: @@ -138,14 +138,14 @@ def prepare_setup_platform(hass, config, domain, platform_name): component) return None - if not _handle_requirements(platform, platform_path): + if not _handle_requirements(hass, platform, platform_path): return None return platform # pylint: disable=too-many-branches, too-many-statements -def from_config_dict(config, hass=None): +def from_config_dict(config, hass=None, config_dir=None): """ Tries to configure Home Assistant from a config dict. @@ -153,6 +153,9 @@ def from_config_dict(config, hass=None): """ if hass is None: hass = core.HomeAssistant() + if config_dir is not None: + hass.config.config_dir = os.path.abspath(config_dir) + hass.config.mount_local_path() process_ha_core_config(hass, config.get(core.DOMAIN, {})) @@ -196,6 +199,7 @@ def from_config_file(config_path, hass=None): # Set config dir to directory holding config file hass.config.config_dir = os.path.abspath(os.path.dirname(config_path)) + hass.config.mount_local_path() config_dict = config_util.load_config_file(config_path) diff --git a/homeassistant/config.py b/homeassistant/config.py index 54ad297e62a..ca2d43eeb40 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -7,7 +7,7 @@ Module to help with parsing and generating configuration files. import logging import os -from homeassistant.core import HomeAssistantError +from homeassistant.exceptions import HomeAssistantError from homeassistant.const import ( CONF_LATITUDE, CONF_LONGITUDE, CONF_TEMPERATURE_UNIT, CONF_NAME, CONF_TIME_ZONE) @@ -16,6 +16,7 @@ import homeassistant.util.location as loc_util _LOGGER = logging.getLogger(__name__) YAML_CONFIG_FILE = 'configuration.yaml' +CONFIG_DIR_NAME = '.homeassistant' DEFAULT_CONFIG = ( # Tuples (attribute, default, auto detect property, description) @@ -39,6 +40,13 @@ DEFAULT_COMPONENTS = { } +def get_default_config_dir(): + """ Put together the default configuration directory based on OS. """ + data_dir = os.getenv('APPDATA') if os.name == "nt" \ + else os.path.expanduser('~') + return os.path.join(data_dir, CONFIG_DIR_NAME) + + def ensure_config_exists(config_dir, detect_location=True): """ Ensures a config file exists in given config dir. Creating a default one if needed. diff --git a/homeassistant/core.py b/homeassistant/core.py index 76b4b38f3fc..309c9336706 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -7,6 +7,7 @@ of entities and react to changes. """ import os +import sys import time import logging import threading @@ -21,9 +22,12 @@ from homeassistant.const import ( EVENT_CALL_SERVICE, ATTR_NOW, ATTR_DOMAIN, ATTR_SERVICE, MATCH_ALL, EVENT_SERVICE_EXECUTED, ATTR_SERVICE_CALL_ID, EVENT_SERVICE_REGISTERED, TEMP_CELCIUS, TEMP_FAHRENHEIT, ATTR_FRIENDLY_NAME) +from homeassistant.exceptions import ( + HomeAssistantError, InvalidEntityFormatError, NoEntitySpecifiedError) import homeassistant.util as util import homeassistant.util.dt as date_util import homeassistant.helpers.temperature as temp_helper +from homeassistant.config import get_default_config_dir DOMAIN = "homeassistant" @@ -660,7 +664,11 @@ class Config(object): self.api = None # Directory that holds the configuration - self.config_dir = os.path.join(os.getcwd(), 'config') + self.config_dir = get_default_config_dir() + + def mount_local_path(self): + """ Add local library to Python Path """ + sys.path.insert(0, self.path('lib')) def path(self, *path): """ Returns path to the file within the config dir. """ @@ -695,21 +703,6 @@ class Config(object): } -class HomeAssistantError(Exception): - """ General Home Assistant exception occured. """ - pass - - -class InvalidEntityFormatError(HomeAssistantError): - """ When an invalid formatted entity is encountered. """ - pass - - -class NoEntitySpecifiedError(HomeAssistantError): - """ When no entity is specified. """ - pass - - def create_timer(hass, interval=TIMER_INTERVAL): """ Creates a timer. Timer will start on HOMEASSISTANT_START. """ # We want to be able to fire every time a minute starts (seconds=0). diff --git a/homeassistant/exceptions.py b/homeassistant/exceptions.py new file mode 100644 index 00000000000..4ecd22f9e43 --- /dev/null +++ b/homeassistant/exceptions.py @@ -0,0 +1,15 @@ +""" Exceptions used by Home Assistant """ + +class HomeAssistantError(Exception): + """ General Home Assistant exception occured. """ + pass + + +class InvalidEntityFormatError(HomeAssistantError): + """ When an invalid formatted entity is encountered. """ + pass + + +class NoEntitySpecifiedError(HomeAssistantError): + """ When no entity is specified. """ + pass diff --git a/homeassistant/util/package.py b/homeassistant/util/package.py index d220a5a7e61..3719fecb9ff 100644 --- a/homeassistant/util/package.py +++ b/homeassistant/util/package.py @@ -1,4 +1,5 @@ """Helpers to install PyPi packages.""" +import os import subprocess import sys @@ -8,15 +9,16 @@ from . import environment as env INSTALL_USER = not env.is_virtual() -def install_package(package, upgrade=False, user=INSTALL_USER): +def install_package(package, upgrade=False, target=None): """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 = [sys.executable, '-m', 'pip', 'install', '--quiet', package] + args = [sys.executable, '-m', 'pip', 'install', '--quiet', + '--isolated', '-I', package] if upgrade: args.append('--upgrade') - if user: - args.append('--user') + if target: + args += ['--target', os.path.abspath(target)] try: return 0 == subprocess.call(args) except subprocess.SubprocessError: diff --git a/requirements.txt b/requirements.txt index f851f70e86f..14c276aa5be 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,119 +1,3 @@ -# Required for Home Assistant core -requests>=2.0 -pyyaml>=3.11 -pytz>=2015.2 - -# Optional, needed for specific components - -# Sun (sun) -astral>=0.8.1 - -# Philips Hue library (lights.hue) -phue>=0.8 - -# Limitlessled/Easybulb/Milight library (lights.limitlessled) -ledcontroller>=1.0.7 - -# Chromecast bindings (media_player.cast) -pychromecast>=0.6.10 - -# Keyboard (keyboard) -pyuserinput>=0.1.9 - -# Tellstick bindings (*.tellstick) -tellcore-py>=1.0.4 - -# Nmap bindings (device_tracker.nmap) -python-libnmap>=0.6.3 - -# PushBullet bindings (notify.pushbullet) -pushbullet.py>=0.7.1 - -# Nest Thermostat bindings (thermostat.nest) -python-nest>=2.4.0 - -# Z-Wave (*.zwave) -pydispatcher>=2.0.5 - -# ISY994 bindings (*.isy994) -PyISY>=1.0.5 - -# PSutil (sensor.systemmonitor) -psutil>=3.0.0 - -# Pushover bindings (notify.pushover) -python-pushover>=0.2 - -# Transmission Torrent Client (*.transmission) -transmissionrpc>=0.11 - -# OpenWeatherMap Web API (sensor.openweathermap) -pyowm>=2.2.1 - -# XMPP Bindings (notify.xmpp) -sleekxmpp>=1.3.1 -dnspython3>=1.12.0 - -# Blockchain (sensor.bitcoin) -blockchain>=1.1.2 - -# MPD Bindings (media_player.mpd) -python-mpd2>=0.5.4 - -# Hikvision (switch.hikvisioncam) -hikvision>=0.4 - -# console log coloring -colorlog>=2.6.0 - -# JSON-RPC interface (media_player.kodi) -jsonrpc-requests>=0.1 - -# Forecast.io Bindings (sensor.forecast) -python-forecastio>=1.3.3 - -# Firmata Bindings (*.arduino) -PyMata==2.07a - -# Rfxtrx sensor (sensor.rfxtrx) -https://github.com/Danielhiversen/pyRFXtrx/archive/master.zip - -# Mysensors -https://github.com/theolind/pymysensors/archive/master.zip#egg=pymysensors-0.1 - -# Netgear (device_tracker.netgear) -pynetgear>=0.3 - -# Netdisco (discovery) -netdisco>=0.3 - -# Wemo (switch.wemo) -pywemo>=0.2 - -# Wink (*.wink) -https://github.com/balloob/python-wink/archive/master.zip#pywink>=0.1 - -# Slack notifier (notify.slack) -slacker>=0.6.8 - -# Temper sensors (sensor.temper) -https://github.com/rkabadi/temper-python/archive/master.zip - -# PyEdimax -https://github.com/rkabadi/pyedimax/archive/master.zip - -# RPI-GPIO platform (*.rpi_gpio) -RPi.GPIO >=0.5.11 - -# Adafruit temperature/humidity sensor -# uncomment on a Raspberry Pi / Beaglebone -#git+git://github.com/mala-zaba/Adafruit_Python_DHT - -# PAHO MQTT Binding (mqtt) -paho-mqtt>=1.1 - -# PyModbus (modbus) -https://github.com/bashwork/pymodbus/archive/python3.zip#pymodbus>=1.2.0 - -# Verisure (verisure) -https://github.com/persandstrom/python-verisure/archive/master.zip +requests==2.7.0 +pyyaml==3.11 +pytz==2015.4 From 75b3cc046dc7499757401381369188fbbf71c882 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 29 Aug 2015 18:17:21 -0700 Subject: [PATCH 206/224] Update frontend with latest changes --- homeassistant/components/frontend/version.py | 2 +- .../frontend/www_static/frontend.html | 163 +++++++++--------- .../www_static/home-assistant-polymer | 2 +- 3 files changed, 82 insertions(+), 85 deletions(-) diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py index 3b00afea13b..defce54a73a 100644 --- a/homeassistant/components/frontend/version.py +++ b/homeassistant/components/frontend/version.py @@ -1,2 +1,2 @@ """ DO NOT MODIFY. Auto-generated by build_frontend script """ -VERSION = "e9a79e5367c298e2e5201c35c9b9de8b" +VERSION = "25d26e3bbcae0f4493f787a5673a84fe" diff --git a/homeassistant/components/frontend/www_static/frontend.html b/homeassistant/components/frontend/www_static/frontend.html index 26c38c2a31b..82b0dba526a 100644 --- a/homeassistant/components/frontend/www_static/frontend.html +++ b/homeassistant/components/frontend/www_static/frontend.html @@ -516,51 +516,6 @@ if(n>=0){var s=e.getKey(t);return this.splice("selected",n,1),this.unlinkPaths(" left: 0; }; - } \ No newline at end of file + } \ No newline at end of file diff --git a/homeassistant/components/frontend/www_static/home-assistant-polymer b/homeassistant/components/frontend/www_static/home-assistant-polymer index 1c82a536312..c3b9a55d683 160000 --- a/homeassistant/components/frontend/www_static/home-assistant-polymer +++ b/homeassistant/components/frontend/www_static/home-assistant-polymer @@ -1 +1 @@ -Subproject commit 1c82a536312e8321716ab7d80a5d17045d20d77f +Subproject commit c3b9a55d6839b933042da14d578c43f9a47499dc From 893ae15042675ef10d255bbc6e7e1554288143e8 Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Sat, 29 Aug 2015 21:39:50 -0400 Subject: [PATCH 207/224] Changed component REQUIREMENTS to absolute versions. --- homeassistant/components/device_tracker/netgear.py | 2 +- homeassistant/components/device_tracker/nmap_tracker.py | 2 +- homeassistant/components/discovery.py | 2 +- homeassistant/components/isy994.py | 2 +- homeassistant/components/keyboard.py | 2 +- homeassistant/components/light/hue.py | 2 +- homeassistant/components/light/limitlessled.py | 2 +- homeassistant/components/light/tellstick.py | 2 +- homeassistant/components/light/wink.py | 4 ++-- homeassistant/components/media_player/cast.py | 2 +- homeassistant/components/media_player/kodi.py | 2 +- homeassistant/components/media_player/mpd.py | 2 +- homeassistant/components/modbus.py | 4 ++-- homeassistant/components/mqtt.py | 2 +- homeassistant/components/notify/pushbullet.py | 2 +- homeassistant/components/notify/pushover.py | 2 +- homeassistant/components/notify/slack.py | 2 +- homeassistant/components/notify/xmpp.py | 2 +- homeassistant/components/sensor/bitcoin.py | 2 +- homeassistant/components/sensor/dht.py | 3 ++- homeassistant/components/sensor/forecast.py | 2 +- homeassistant/components/sensor/mysensors.py | 4 ++-- homeassistant/components/sensor/openweathermap.py | 2 +- homeassistant/components/sensor/rfxtrx.py | 4 ++-- homeassistant/components/sensor/rpi_gpio.py | 2 +- homeassistant/components/sensor/systemmonitor.py | 2 +- homeassistant/components/sensor/tellstick.py | 2 +- homeassistant/components/sensor/temper.py | 3 ++- homeassistant/components/sensor/transmission.py | 2 +- homeassistant/components/sensor/wink.py | 4 ++-- homeassistant/components/sun.py | 2 +- homeassistant/components/switch/edimax.py | 3 ++- homeassistant/components/switch/hikvisioncam.py | 2 +- homeassistant/components/switch/rpi_gpio.py | 2 +- homeassistant/components/switch/tellstick.py | 2 +- homeassistant/components/switch/transmission.py | 2 +- homeassistant/components/switch/wemo.py | 2 +- homeassistant/components/switch/wink.py | 4 ++-- homeassistant/components/thermostat/nest.py | 2 +- homeassistant/components/verisure.py | 3 ++- homeassistant/components/wink.py | 4 ++-- homeassistant/components/zwave.py | 2 +- 42 files changed, 53 insertions(+), 49 deletions(-) diff --git a/homeassistant/components/device_tracker/netgear.py b/homeassistant/components/device_tracker/netgear.py index c04a1d07b1f..346fbb37d37 100644 --- a/homeassistant/components/device_tracker/netgear.py +++ b/homeassistant/components/device_tracker/netgear.py @@ -42,7 +42,7 @@ from homeassistant.components.device_tracker import DOMAIN MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['pynetgear>=0.3'] +REQUIREMENTS = ['pynetgear==0.3'] def get_scanner(hass, config): diff --git a/homeassistant/components/device_tracker/nmap_tracker.py b/homeassistant/components/device_tracker/nmap_tracker.py index 00fc8fd12b4..dc12a53f539 100644 --- a/homeassistant/components/device_tracker/nmap_tracker.py +++ b/homeassistant/components/device_tracker/nmap_tracker.py @@ -43,7 +43,7 @@ _LOGGER = logging.getLogger(__name__) # interval in minutes to exclude devices from a scan while they are home CONF_HOME_INTERVAL = "home_interval" -REQUIREMENTS = ['python-libnmap>=0.6.3'] +REQUIREMENTS = ['python-libnmap==0.6.1'] def get_scanner(hass, config): diff --git a/homeassistant/components/discovery.py b/homeassistant/components/discovery.py index 4ad0299cc8f..c21249fbc60 100644 --- a/homeassistant/components/discovery.py +++ b/homeassistant/components/discovery.py @@ -19,7 +19,7 @@ from homeassistant.const import ( DOMAIN = "discovery" DEPENDENCIES = [] -REQUIREMENTS = ['netdisco>=0.3'] +REQUIREMENTS = ['netdisco==0.3'] SCAN_INTERVAL = 300 # seconds diff --git a/homeassistant/components/isy994.py b/homeassistant/components/isy994.py index f5998faeaf8..63c7b6c4af6 100644 --- a/homeassistant/components/isy994.py +++ b/homeassistant/components/isy994.py @@ -21,7 +21,7 @@ from homeassistant.const import ( DOMAIN = "isy994" DEPENDENCIES = [] -REQUIREMENTS = ['PyISY>=1.0.5'] +REQUIREMENTS = ['PyISY==1.0.5'] DISCOVER_LIGHTS = "isy994.lights" DISCOVER_SWITCHES = "isy994.switches" DISCOVER_SENSORS = "isy994.sensors" diff --git a/homeassistant/components/keyboard.py b/homeassistant/components/keyboard.py index 5359791087e..3629fce31bf 100644 --- a/homeassistant/components/keyboard.py +++ b/homeassistant/components/keyboard.py @@ -14,7 +14,7 @@ from homeassistant.const import ( DOMAIN = "keyboard" DEPENDENCIES = [] -REQUIREMENTS = ['pyuserinput>=0.1.9'] +REQUIREMENTS = ['pyuserinput==0.1.9'] def volume_up(hass): diff --git a/homeassistant/components/light/hue.py b/homeassistant/components/light/hue.py index c3b28ec1dd6..b438d7b92b1 100644 --- a/homeassistant/components/light/hue.py +++ b/homeassistant/components/light/hue.py @@ -16,7 +16,7 @@ from homeassistant.components.light import ( ATTR_FLASH, FLASH_LONG, FLASH_SHORT, ATTR_EFFECT, EFFECT_COLORLOOP) -REQUIREMENTS = ['phue>=0.8'] +REQUIREMENTS = ['phue==0.8'] MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(milliseconds=100) diff --git a/homeassistant/components/light/limitlessled.py b/homeassistant/components/light/limitlessled.py index b3e0858ffe2..8fdb525d4e0 100644 --- a/homeassistant/components/light/limitlessled.py +++ b/homeassistant/components/light/limitlessled.py @@ -34,7 +34,7 @@ from homeassistant.components.light import (Light, ATTR_BRIGHTNESS, from homeassistant.util.color import color_RGB_to_xy _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['ledcontroller>=1.0.7'] +REQUIREMENTS = ['ledcontroller==1.0.7'] def setup_platform(hass, config, add_devices_callback, discovery_info=None): diff --git a/homeassistant/components/light/tellstick.py b/homeassistant/components/light/tellstick.py index 9132604b294..8068d20bb74 100644 --- a/homeassistant/components/light/tellstick.py +++ b/homeassistant/components/light/tellstick.py @@ -9,7 +9,7 @@ from homeassistant.components.light import Light, ATTR_BRIGHTNESS from homeassistant.const import ATTR_FRIENDLY_NAME import tellcore.constants as tellcore_constants -REQUIREMENTS = ['tellcore-py>=1.0.4'] +REQUIREMENTS = ['tellcore-py==1.0.4'] def setup_platform(hass, config, add_devices_callback, discovery_info=None): diff --git a/homeassistant/components/light/wink.py b/homeassistant/components/light/wink.py index e8c8eb7a224..4b5af0c3250 100644 --- a/homeassistant/components/light/wink.py +++ b/homeassistant/components/light/wink.py @@ -9,8 +9,8 @@ from homeassistant.components.light import ATTR_BRIGHTNESS from homeassistant.components.wink import WinkToggleDevice from homeassistant.const import CONF_ACCESS_TOKEN -REQUIREMENTS = ['https://github.com/balloob/python-wink/archive/master.zip' - '#pywink>=0.1'] +REQUIREMENTS = ['https://github.com/balloob/python-wink/archive/' + + 'c2b700e8ca866159566ecf5e644d9c297f69f257.zip'] def setup_platform(hass, config, add_devices_callback, discovery_info=None): diff --git a/homeassistant/components/media_player/cast.py b/homeassistant/components/media_player/cast.py index 5fca233013a..d19e4166c1c 100644 --- a/homeassistant/components/media_player/cast.py +++ b/homeassistant/components/media_player/cast.py @@ -19,7 +19,7 @@ from homeassistant.components.media_player import ( SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK, MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO) -REQUIREMENTS = ['pychromecast>=0.6.10'] +REQUIREMENTS = ['pychromecast==0.6.10'] CONF_IGNORE_CEC = 'ignore_cec' CAST_SPLASH = 'https://home-assistant.io/images/cast/splash.png' SUPPORT_CAST = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \ diff --git a/homeassistant/components/media_player/kodi.py b/homeassistant/components/media_player/kodi.py index 4b24f8694ed..dfc2f64a2a8 100644 --- a/homeassistant/components/media_player/kodi.py +++ b/homeassistant/components/media_player/kodi.py @@ -48,7 +48,7 @@ except ImportError: jsonrpc_requests = None _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['jsonrpc-requests>=0.1'] +REQUIREMENTS = ['jsonrpc-requests==0.1'] SUPPORT_KODI = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \ SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_SEEK diff --git a/homeassistant/components/media_player/mpd.py b/homeassistant/components/media_player/mpd.py index 0239173f7cc..aca2413d3e4 100644 --- a/homeassistant/components/media_player/mpd.py +++ b/homeassistant/components/media_player/mpd.py @@ -48,7 +48,7 @@ from homeassistant.components.media_player import ( MEDIA_TYPE_MUSIC) _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['python-mpd2>=0.5.4'] +REQUIREMENTS = ['python-mpd2==0.5.4'] SUPPORT_MPD = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_TURN_OFF | \ SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK diff --git a/homeassistant/components/modbus.py b/homeassistant/components/modbus.py index 0bd3b23c2f9..e6c3f1cfcee 100644 --- a/homeassistant/components/modbus.py +++ b/homeassistant/components/modbus.py @@ -38,8 +38,8 @@ from homeassistant.const import (EVENT_HOMEASSISTANT_START, DOMAIN = "modbus" DEPENDENCIES = [] -REQUIREMENTS = ['https://github.com/bashwork/pymodbus/archive/python3.zip' - '#pymodbus>=1.2.0'] +REQUIREMENTS = ['https://github.com/bashwork/pymodbus/archive/' + + 'd7fc4f1cc975631e0a9011390e8017f64b612661.zip'] # Type of network MEDIUM = "type" diff --git a/homeassistant/components/mqtt.py b/homeassistant/components/mqtt.py index aa1a3167029..073406e9700 100644 --- a/homeassistant/components/mqtt.py +++ b/homeassistant/components/mqtt.py @@ -66,7 +66,7 @@ SERVICE_PUBLISH = 'publish' EVENT_MQTT_MESSAGE_RECEIVED = 'MQTT_MESSAGE_RECEIVED' DEPENDENCIES = [] -REQUIREMENTS = ['paho-mqtt>=1.1'] +REQUIREMENTS = ['paho-mqtt==1.1'] CONF_BROKER = 'broker' CONF_PORT = 'port' diff --git a/homeassistant/components/notify/pushbullet.py b/homeassistant/components/notify/pushbullet.py index 5e322cfc3b5..58462954d2e 100644 --- a/homeassistant/components/notify/pushbullet.py +++ b/homeassistant/components/notify/pushbullet.py @@ -28,7 +28,7 @@ from homeassistant.components.notify import ( from homeassistant.const import CONF_API_KEY _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['pushbullet.py>=0.7.1'] +REQUIREMENTS = ['pushbullet.py==0.7.1'] def get_service(hass, config): diff --git a/homeassistant/components/notify/pushover.py b/homeassistant/components/notify/pushover.py index 1bc5e9ac9a3..0df035a4a6e 100644 --- a/homeassistant/components/notify/pushover.py +++ b/homeassistant/components/notify/pushover.py @@ -42,7 +42,7 @@ from homeassistant.components.notify import ( DOMAIN, ATTR_TITLE, BaseNotificationService) from homeassistant.const import CONF_API_KEY -REQUIREMENTS = ['python-pushover>=0.2'] +REQUIREMENTS = ['python-pushover==0.2'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/notify/slack.py b/homeassistant/components/notify/slack.py index 859b5b0388a..d604cffb754 100644 --- a/homeassistant/components/notify/slack.py +++ b/homeassistant/components/notify/slack.py @@ -32,7 +32,7 @@ from homeassistant.components.notify import ( DOMAIN, BaseNotificationService) from homeassistant.const import CONF_API_KEY -REQUIREMENTS = ['slacker>=0.6.8'] +REQUIREMENTS = ['slacker==0.6.8'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/notify/xmpp.py b/homeassistant/components/notify/xmpp.py index e756be82014..81268c734b7 100644 --- a/homeassistant/components/notify/xmpp.py +++ b/homeassistant/components/notify/xmpp.py @@ -45,7 +45,7 @@ from homeassistant.helpers import validate_config from homeassistant.components.notify import ( DOMAIN, ATTR_TITLE, BaseNotificationService) -REQUIREMENTS = ['sleekxmpp>=1.3.1', 'dnspython3>=1.12.0'] +REQUIREMENTS = ['sleekxmpp==1.3.1', 'dnspython3==1.12.0'] def get_service(hass, config): diff --git a/homeassistant/components/sensor/bitcoin.py b/homeassistant/components/sensor/bitcoin.py index e0ecbab6db5..b30886448ad 100644 --- a/homeassistant/components/sensor/bitcoin.py +++ b/homeassistant/components/sensor/bitcoin.py @@ -71,7 +71,7 @@ from homeassistant.util import Throttle from homeassistant.helpers.entity import Entity -REQUIREMENTS = ['blockchain>=1.1.2'] +REQUIREMENTS = ['blockchain==1.1.2'] _LOGGER = logging.getLogger(__name__) OPTION_TYPES = { 'wallet': ['Wallet balance', 'BTC'], diff --git a/homeassistant/components/sensor/dht.py b/homeassistant/components/sensor/dht.py index c7df4b4a8d3..7949a7a44fa 100644 --- a/homeassistant/components/sensor/dht.py +++ b/homeassistant/components/sensor/dht.py @@ -44,7 +44,8 @@ from homeassistant.const import TEMP_FAHRENHEIT from homeassistant.helpers.entity import Entity # update this requirement to upstream as soon as it supports python3 -REQUIREMENTS = ['git+git://github.com/mala-zaba/Adafruit_Python_DHT'] +REQUIREMENTS = ['http://github.com/mala-zaba/Adafruit_Python_DHT/archive/' + + '4101340de8d2457dd194bca1e8d11cbfc237e919.zip'] _LOGGER = logging.getLogger(__name__) SENSOR_TYPES = { 'temperature': ['Temperature', ''], diff --git a/homeassistant/components/sensor/forecast.py b/homeassistant/components/sensor/forecast.py index a9783104cd8..b56432ab89b 100644 --- a/homeassistant/components/sensor/forecast.py +++ b/homeassistant/components/sensor/forecast.py @@ -49,7 +49,7 @@ Details for the API : https://developer.forecast.io/docs/v2 import logging from datetime import timedelta -REQUIREMENTS = ['python-forecastio>=1.3.3'] +REQUIREMENTS = ['python-forecastio==1.3.3'] try: import forecastio diff --git a/homeassistant/components/sensor/mysensors.py b/homeassistant/components/sensor/mysensors.py index a626858db31..994b110a585 100644 --- a/homeassistant/components/sensor/mysensors.py +++ b/homeassistant/components/sensor/mysensors.py @@ -36,8 +36,8 @@ ATTR_NODE_ID = "node_id" ATTR_CHILD_ID = "child_id" _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['https://github.com/theolind/pymysensors/archive/master.zip' - '#egg=pymysensors-0.1'] +REQUIREMENTS = ['https://github.com/theolind/pymysensors/archive/' + + '35b87d880147a34107da0d40cb815d75e6cb4af7.zip'] def setup_platform(hass, config, add_devices, discovery_info=None): diff --git a/homeassistant/components/sensor/openweathermap.py b/homeassistant/components/sensor/openweathermap.py index f4635cd13ca..537fc9f59b5 100644 --- a/homeassistant/components/sensor/openweathermap.py +++ b/homeassistant/components/sensor/openweathermap.py @@ -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 = ['pyowm>=2.2.1'] +REQUIREMENTS = ['pyowm==2.2.1'] _LOGGER = logging.getLogger(__name__) SENSOR_TYPES = { 'weather': ['Condition', ''], diff --git a/homeassistant/components/sensor/rfxtrx.py b/homeassistant/components/sensor/rfxtrx.py index ffc688804ef..8e5a1ad3dca 100644 --- a/homeassistant/components/sensor/rfxtrx.py +++ b/homeassistant/components/sensor/rfxtrx.py @@ -26,8 +26,8 @@ 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'] +REQUIREMENTS = ['https://github.com/Danielhiversen/pyRFXtrx/archive/' + + 'ec7a1aaddf8270db6e5da1c13d58c1547effd7cf.zip'] DATA_TYPES = OrderedDict([ ('Temperature', TEMP_CELCIUS), diff --git a/homeassistant/components/sensor/rpi_gpio.py b/homeassistant/components/sensor/rpi_gpio.py index c57cf31b397..f973b24a301 100644 --- a/homeassistant/components/sensor/rpi_gpio.py +++ b/homeassistant/components/sensor/rpi_gpio.py @@ -53,7 +53,7 @@ DEFAULT_VALUE_HIGH = "HIGH" DEFAULT_VALUE_LOW = "LOW" DEFAULT_BOUNCETIME = 50 -REQUIREMENTS = ['RPi.GPIO>=0.5.11'] +REQUIREMENTS = ['RPi.GPIO==0.5.11'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/sensor/systemmonitor.py b/homeassistant/components/sensor/systemmonitor.py index 1d1bdb1f3b5..b473cc27283 100644 --- a/homeassistant/components/sensor/systemmonitor.py +++ b/homeassistant/components/sensor/systemmonitor.py @@ -66,7 +66,7 @@ import homeassistant.util.dt as dt_util from homeassistant.helpers.entity import Entity from homeassistant.const import STATE_ON, STATE_OFF -REQUIREMENTS = ['psutil>=3.0.0'] +REQUIREMENTS = ['psutil==3.0.0'] SENSOR_TYPES = { 'disk_use_percent': ['Disk Use', '%'], 'disk_use': ['Disk Use', 'GiB'], diff --git a/homeassistant/components/sensor/tellstick.py b/homeassistant/components/sensor/tellstick.py index 7d024333023..e93c6e4c97f 100644 --- a/homeassistant/components/sensor/tellstick.py +++ b/homeassistant/components/sensor/tellstick.py @@ -35,7 +35,7 @@ import homeassistant.util as util DatatypeDescription = namedtuple("DatatypeDescription", ['name', 'unit']) -REQUIREMENTS = ['tellcore-py>=1.0.4'] +REQUIREMENTS = ['tellcore-py==1.0.4'] # pylint: disable=unused-argument diff --git a/homeassistant/components/sensor/temper.py b/homeassistant/components/sensor/temper.py index e443e81b93f..8579a922661 100644 --- a/homeassistant/components/sensor/temper.py +++ b/homeassistant/components/sensor/temper.py @@ -18,7 +18,8 @@ from homeassistant.const import CONF_NAME, DEVICE_DEFAULT_NAME _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['https://github.com/rkabadi/temper-python/archive/master.zip'] +REQUIREMENTS = ['https://github.com/rkabadi/temper-python/archive/' + + '3dbdaf2d87b8db9a3cd6e5585fc704537dd2d09b.zip'] # pylint: disable=unused-argument diff --git a/homeassistant/components/sensor/transmission.py b/homeassistant/components/sensor/transmission.py index b9ed3ea4e9f..587f5131d9d 100644 --- a/homeassistant/components/sensor/transmission.py +++ b/homeassistant/components/sensor/transmission.py @@ -67,7 +67,7 @@ from transmissionrpc.error import TransmissionError import logging -REQUIREMENTS = ['transmissionrpc>=0.11'] +REQUIREMENTS = ['transmissionrpc==0.11'] SENSOR_TYPES = { 'current_status': ['Status', ''], 'download_speed': ['Down Speed', 'MB/s'], diff --git a/homeassistant/components/sensor/wink.py b/homeassistant/components/sensor/wink.py index 4056bbd7733..0b3d33cea24 100644 --- a/homeassistant/components/sensor/wink.py +++ b/homeassistant/components/sensor/wink.py @@ -8,8 +8,8 @@ import logging from homeassistant.helpers.entity import Entity from homeassistant.const import CONF_ACCESS_TOKEN, STATE_OPEN, STATE_CLOSED -REQUIREMENTS = ['https://github.com/balloob/python-wink/archive/master.zip' - '#pywink>=0.1'] +REQUIREMENTS = ['https://github.com/balloob/python-wink/archive/' + + 'c2b700e8ca866159566ecf5e644d9c297f69f257.zip'] def setup_platform(hass, config, add_devices, discovery_info=None): diff --git a/homeassistant/components/sun.py b/homeassistant/components/sun.py index 507c4a2b63b..802eddb4a3a 100644 --- a/homeassistant/components/sun.py +++ b/homeassistant/components/sun.py @@ -31,7 +31,7 @@ from homeassistant.helpers.entity import Entity from homeassistant.components.scheduler import ServiceEventListener DEPENDENCIES = [] -REQUIREMENTS = ['astral>=0.8.1'] +REQUIREMENTS = ['astral==0.8.1'] DOMAIN = "sun" ENTITY_ID = "sun.sun" diff --git a/homeassistant/components/switch/edimax.py b/homeassistant/components/switch/edimax.py index 17fe6d61735..200c5746e27 100644 --- a/homeassistant/components/switch/edimax.py +++ b/homeassistant/components/switch/edimax.py @@ -44,7 +44,8 @@ from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD,\ DEFAULT_USERNAME = 'admin' DEFAULT_PASSWORD = '1234' DEVICE_DEFAULT_NAME = 'Edimax Smart Plug' -REQUIREMENTS = ['https://github.com/rkabadi/pyedimax/archive/master.zip'] +REQUIREMENTS = ['https://github.com/rkabadi/pyedimax/archive/' + + '365301ce3ff26129a7910c501ead09ea625f3700.zip'] # setup logger _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/switch/hikvisioncam.py b/homeassistant/components/switch/hikvisioncam.py index 5ab084319fc..6ab82df482a 100644 --- a/homeassistant/components/switch/hikvisioncam.py +++ b/homeassistant/components/switch/hikvisioncam.py @@ -49,7 +49,7 @@ except ImportError: hikvision.api = None _LOGGING = logging.getLogger(__name__) -REQUIREMENTS = ['hikvision>=0.4'] +REQUIREMENTS = ['hikvision==0.4'] # pylint: disable=too-many-arguments # pylint: disable=too-many-instance-attributes diff --git a/homeassistant/components/switch/rpi_gpio.py b/homeassistant/components/switch/rpi_gpio.py index bb9cf13e3ed..4afa38aa80a 100644 --- a/homeassistant/components/switch/rpi_gpio.py +++ b/homeassistant/components/switch/rpi_gpio.py @@ -36,7 +36,7 @@ from homeassistant.const import (DEVICE_DEFAULT_NAME, DEFAULT_INVERT_LOGIC = False -REQUIREMENTS = ['RPi.GPIO>=0.5.11'] +REQUIREMENTS = ['RPi.GPIO==0.5.11'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/switch/tellstick.py b/homeassistant/components/switch/tellstick.py index 4fde4babf9e..230151382e7 100644 --- a/homeassistant/components/switch/tellstick.py +++ b/homeassistant/components/switch/tellstick.py @@ -19,7 +19,7 @@ import tellcore.constants as tellcore_constants SINGAL_REPETITIONS = 1 -REQUIREMENTS = ['tellcore-py>=1.0.4'] +REQUIREMENTS = ['tellcore-py==1.0.4'] # pylint: disable=unused-argument diff --git a/homeassistant/components/switch/transmission.py b/homeassistant/components/switch/transmission.py index 7575951f53b..d5cf716c770 100644 --- a/homeassistant/components/switch/transmission.py +++ b/homeassistant/components/switch/transmission.py @@ -48,7 +48,7 @@ from transmissionrpc.error import TransmissionError import logging _LOGGING = logging.getLogger(__name__) -REQUIREMENTS = ['transmissionrpc>=0.11'] +REQUIREMENTS = ['transmissionrpc==0.11'] # pylint: disable=unused-argument diff --git a/homeassistant/components/switch/wemo.py b/homeassistant/components/switch/wemo.py index d133191e6db..2d6e25b296b 100644 --- a/homeassistant/components/switch/wemo.py +++ b/homeassistant/components/switch/wemo.py @@ -8,7 +8,7 @@ import logging from homeassistant.components.switch import SwitchDevice -REQUIREMENTS = ['pywemo>=0.2'] +REQUIREMENTS = ['pywemo==0.2'] # pylint: disable=unused-argument diff --git a/homeassistant/components/switch/wink.py b/homeassistant/components/switch/wink.py index 556a40b181f..c9fb045d9c0 100644 --- a/homeassistant/components/switch/wink.py +++ b/homeassistant/components/switch/wink.py @@ -9,8 +9,8 @@ import logging from homeassistant.components.wink import WinkToggleDevice from homeassistant.const import CONF_ACCESS_TOKEN -REQUIREMENTS = ['https://github.com/balloob/python-wink/archive/master.zip' - '#pywink>=0.1'] +REQUIREMENTS = ['https://github.com/balloob/python-wink/archive/' + + 'c2b700e8ca866159566ecf5e644d9c297f69f257.zip'] def setup_platform(hass, config, add_devices, discovery_info=None): diff --git a/homeassistant/components/thermostat/nest.py b/homeassistant/components/thermostat/nest.py index cb74fa091ff..1de729b590d 100644 --- a/homeassistant/components/thermostat/nest.py +++ b/homeassistant/components/thermostat/nest.py @@ -6,7 +6,7 @@ import logging from homeassistant.components.thermostat import ThermostatDevice from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD, TEMP_CELCIUS) -REQUIREMENTS = ['python-nest>=2.4.0'] +REQUIREMENTS = ['python-nest==2.4.0'] # pylint: disable=unused-argument diff --git a/homeassistant/components/verisure.py b/homeassistant/components/verisure.py index f084ce9874c..d716c8c46ad 100644 --- a/homeassistant/components/verisure.py +++ b/homeassistant/components/verisure.py @@ -61,7 +61,8 @@ DISCOVER_SWITCHES = 'verisure.switches' DEPENDENCIES = [] REQUIREMENTS = [ - 'https://github.com/persandstrom/python-verisure/archive/master.zip' + 'https://github.com/persandstrom/python-verisure/archive/' + + '9873c4527f01b1ba1f72ae60f7f35854390d59be.zip' ] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/wink.py b/homeassistant/components/wink.py index d56a244b84c..eb2beac508a 100644 --- a/homeassistant/components/wink.py +++ b/homeassistant/components/wink.py @@ -16,8 +16,8 @@ from homeassistant.const import ( DOMAIN = "wink" DEPENDENCIES = [] -REQUIREMENTS = ['https://github.com/balloob/python-wink/archive/master.zip' - '#pywink>=0.1'] +REQUIREMENTS = ['https://github.com/balloob/python-wink/archive/' + + 'c2b700e8ca866159566ecf5e644d9c297f69f257.zip'] DISCOVER_LIGHTS = "wink.lights" DISCOVER_SWITCHES = "wink.switches" diff --git a/homeassistant/components/zwave.py b/homeassistant/components/zwave.py index ce189a242b4..ef7e7308959 100644 --- a/homeassistant/components/zwave.py +++ b/homeassistant/components/zwave.py @@ -12,7 +12,7 @@ from homeassistant.const import ( DOMAIN = "zwave" DEPENDENCIES = [] -REQUIREMENTS = ['pydispatcher>=2.0.5'] +REQUIREMENTS = ['pydispatcher==2.0.5'] CONF_USB_STICK_PATH = "usb_path" DEFAULT_CONF_USB_STICK_PATH = "/zwaveusbstick" From 0b6358e759263d50429130766fa2be81a2e26197 Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Sat, 29 Aug 2015 22:19:52 -0400 Subject: [PATCH 208/224] Implemented comments from Paulus. Revised main to use frontend and demo strings rather than importing their domains. Removed submodule validation. Moved local library mounting to the bootstrap module and out of core. Added requirements_all.txt for all dependencies. Made core dependencies looser. Small updates to setup.py. --- .travis.yml | 2 +- homeassistant/__main__.py | 33 ++++------ homeassistant/bootstrap.py | 15 +++-- homeassistant/core.py | 4 -- requirements.txt | 6 +- requirements_all.txt | 120 +++++++++++++++++++++++++++++++++++++ setup.py | 10 +++- 7 files changed, 155 insertions(+), 35 deletions(-) create mode 100644 requirements_all.txt diff --git a/.travis.yml b/.travis.yml index 7af8ce86dcd..65e417fffb6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ language: python python: - "3.4" install: - - pip install -r requirements.txt + - pip install -r requirements_all.txt - pip install flake8 pylint coveralls script: - flake8 homeassistant --exclude bower_components,external diff --git a/homeassistant/__main__.py b/homeassistant/__main__.py index 59c2cd11abb..02ef2c401f3 100644 --- a/homeassistant/__main__.py +++ b/homeassistant/__main__.py @@ -8,7 +8,6 @@ import importlib from homeassistant import bootstrap import homeassistant.config as config_util -from homeassistant.components import frontend, demo def validate_python(): @@ -30,17 +29,6 @@ def ensure_pip(): sys.exit() -def validate_git_submodules(): - """ Validate the git submodules are cloned. """ - try: - # pylint: disable=no-name-in-module, unused-variable - from homeassistant.external.noop import WORKING # noqa - except ImportError: - print("Repository submodules have not been initialized") - print("Please run: git submodule update --init --recursive") - sys.exit() - - def ensure_config_path(config_dir): """ Gets the path to the configuration file. Creates one if it not exists. """ @@ -49,11 +37,16 @@ def ensure_config_path(config_dir): # Test if configuration directory exists if not os.path.isdir(config_dir): - try: - os.mkdir(config_dir) - except OSError: - print(('Fatal Error: Unable to create specified configuration ' - 'directory {} ').format(config_dir)) + if config_dir == config_util.get_default_config_dir(): + try: + os.mkdir(config_dir) + except OSError: + print(('Fatal Error: Unable to create default configuration ' + 'directory {} ').format(config_dir)) + sys.exit() + else: + print(('Fatal Error: Specified configuration directory does ' + 'not exist {} ').format(config_dir)) sys.exit() # Test if library directory exists @@ -98,8 +91,6 @@ def main(): """ Starts Home Assistant. """ validate_python() - validate_git_submodules() - args = get_arguments() config_dir = os.path.join(os.getcwd(), args.config) @@ -107,8 +98,8 @@ def main(): if args.demo_mode: hass = bootstrap.from_config_dict({ - frontend.DOMAIN: {}, - demo.DOMAIN: {} + 'frontend': {}, + 'demo': {} }, config_dir=config_dir) else: hass = bootstrap.from_config_file(config_path) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index e4cd307019e..61685dbf2fe 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -144,6 +144,11 @@ def prepare_setup_platform(hass, config, domain, platform_name): return platform +def mount_local_lib_path(config_dir): + """ Add local library to Python Path """ + sys.path.insert(0, os.path.join(config_dir, 'lib')) + + # pylint: disable=too-many-branches, too-many-statements def from_config_dict(config, hass=None, config_dir=None): """ @@ -154,8 +159,9 @@ def from_config_dict(config, hass=None, config_dir=None): if hass is None: hass = core.HomeAssistant() if config_dir is not None: - hass.config.config_dir = os.path.abspath(config_dir) - hass.config.mount_local_path() + config_dir = os.path.abspath(config_dir) + hass.config.config_dir = config_dir + mount_local_lib_path(config_dir) process_ha_core_config(hass, config.get(core.DOMAIN, {})) @@ -198,8 +204,9 @@ def from_config_file(config_path, hass=None): hass = core.HomeAssistant() # Set config dir to directory holding config file - hass.config.config_dir = os.path.abspath(os.path.dirname(config_path)) - hass.config.mount_local_path() + config_dir = os.path.abspath(os.path.dirname(config_path)) + hass.config.config_dir = config_dir + mount_local_lib_path(config_dir) config_dict = config_util.load_config_file(config_path) diff --git a/homeassistant/core.py b/homeassistant/core.py index 309c9336706..0ad05694d65 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -666,10 +666,6 @@ class Config(object): # Directory that holds the configuration self.config_dir = get_default_config_dir() - def mount_local_path(self): - """ Add local library to Python Path """ - sys.path.insert(0, self.path('lib')) - def path(self, *path): """ Returns path to the file within the config dir. """ return os.path.join(self.config_dir, *path) diff --git a/requirements.txt b/requirements.txt index 14c276aa5be..a21c2db6208 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -requests==2.7.0 -pyyaml==3.11 -pytz==2015.4 +requests>=2,<3 +pyyaml>=3.11,<4 +pytz>=2015.4 diff --git a/requirements_all.txt b/requirements_all.txt new file mode 100644 index 00000000000..f9f02b49740 --- /dev/null +++ b/requirements_all.txt @@ -0,0 +1,120 @@ +# Required for Home Assistant core +requests>=2,<3 +pyyaml>=3.11,<4 +pytz>=2015.4 + +# Optional, needed for specific components + +# Sun (sun) +astral==0.8.1 + +# Philips Hue library (lights.hue) +phue==0.8 + +# Limitlessled/Easybulb/Milight library (lights.limitlessled) +ledcontroller==1.0.7 + +# Chromecast bindings (media_player.cast) +pychromecast==0.6.10 + +# Keyboard (keyboard) +pyuserinput==0.1.9 + +# Tellstick bindings (*.tellstick) +tellcore-py==1.0.4 + +# Nmap bindings (device_tracker.nmap) +python-libnmap==0.6.3 + +# PushBullet bindings (notify.pushbullet) +pushbullet.py==0.7.1 + +# Nest Thermostat bindings (thermostat.nest) +python-nest==2.4.0 + +# Z-Wave (*.zwave) +pydispatcher==2.0.5 + +# ISY994 bindings (*.isy994) +PyISY==1.0.5 + +# PSutil (sensor.systemmonitor) +psutil==3.0.0 + +# Pushover bindings (notify.pushover) +python-pushover==0.2 + +# Transmission Torrent Client (*.transmission) +transmissionrpc==0.11 + +# OpenWeatherMap Web API (sensor.openweathermap) +pyowm==2.2.1 + +# XMPP Bindings (notify.xmpp) +sleekxmpp==1.3.1 +dnspython3==1.12.0 + +# Blockchain (sensor.bitcoin) +blockchain==1.1.2 + +# MPD Bindings (media_player.mpd) +python-mpd2==0.5.4 + +# Hikvision (switch.hikvisioncam) +hikvision==0.4 + +# console log coloring +colorlog==2.6.0 + +# JSON-RPC interface (media_player.kodi) +jsonrpc-requests==0.1 + +# Forecast.io Bindings (sensor.forecast) +python-forecastio==1.3.3 + +# Firmata Bindings (*.arduino) +PyMata==2.07a + +# Rfxtrx sensor (sensor.rfxtrx) +https://github.com/Danielhiversen/pyRFXtrx/archive/ec7a1aaddf8270db6e5da1c13d58c1547effd7cf.zip + +# Mysensors +https://github.com/theolind/pymysensors/archive/35b87d880147a34107da0d40cb815d75e6cb4af7.zip + +# Netgear (device_tracker.netgear) +pynetgear==0.3 + +# Netdisco (discovery) +netdisco==0.3 + +# Wemo (switch.wemo) +pywemo==0.2 + +# Wink (*.wink) +https://github.com/balloob/python-wink/archive/c2b700e8ca866159566ecf5e644d9c297f69f257.zip + +# Slack notifier (notify.slack) +slacker==0.6.8 + +# Temper sensors (sensor.temper) +https://github.com/rkabadi/temper-python/archive/3dbdaf2d87b8db9a3cd6e5585fc704537dd2d09b.zip + +# PyEdimax +https://github.com/rkabadi/pyedimax/archive/365301ce3ff26129a7910c501ead09ea625f3700.zip + +# RPI-GPIO platform (*.rpi_gpio) +# Uncomment for Raspberry Pi +# RPi.GPIO ==0.5.11 + +# Adafruit temperature/humidity sensor +# uncomment on a Raspberry Pi / Beaglebone +# http://github.com/mala-zaba/Adafruit_Python_DHT/archive/4101340de8d2457dd194bca1e8d11cbfc237e919.zip + +# PAHO MQTT Binding (mqtt) +paho-mqtt==1.1 + +# PyModbus (modbus) +https://github.com/bashwork/pymodbus/archive/d7fc4f1cc975631e0a9011390e8017f64b612661.zip + +# Verisure (verisure) +https://github.com/persandstrom/python-verisure/archive/9873c4527f01b1ba1f72ae60f7f35854390d59be.zip diff --git a/setup.py b/setup.py index f922459e509..610a7398735 100755 --- a/setup.py +++ b/setup.py @@ -6,6 +6,8 @@ PACKAGE_NAME = 'homeassistant' HERE = os.path.abspath(os.path.dirname(__file__)) with open(os.path.join(HERE, PACKAGE_NAME, 'const.py')) as fp: VERSION = re.search("__version__ = ['\"]([^']+)['\"]\n", fp.read()).group(1) +DOWNLOAD_URL = \ + 'https://github.com/balloob/home-assistant/tarball/{}'.format(VERSION) PACKAGES = find_packages() + \ ['homeassistant.external', 'homeassistant.external.noop', @@ -16,12 +18,15 @@ PACKAGE_DATA = \ 'homeassistant.components.frontend.www_static': ['*.*'], 'homeassistant.components.frontend.www_static.images': ['*.*']} +REQUIRES = \ + [line.strip() for line in open('requirements.txt', 'r')] + setup( name=PACKAGE_NAME, version=VERSION, license='MIT License', url='https://home-assistant.io/', - download_url='https://github.com/automicus/pyisy/tarball/0.7.0', + download_url=DOWNLOAD_URL, author='Paulus Schoutsen', author_email='paulus@paulusschoutsen.nl', description='Open-source home automation platform running on Python 3.', @@ -30,7 +35,7 @@ setup( package_data=PACKAGE_DATA, zip_safe=False, platforms='any', - install_requires=['requests==2.7.0', 'pyyaml==3.11', 'pytz==2015.4'], + install_requires=REQUIRES, keywords=['home', 'automation'], entry_points={ 'console_scripts': [ @@ -39,6 +44,7 @@ setup( }, classifiers=[ 'Intended Audience :: End Users/Desktop', + 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Programming Language :: Python :: 3.4', From f5b98c86f0d87f3c4261aaf586067b79ed9df936 Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Sat, 29 Aug 2015 22:34:35 -0400 Subject: [PATCH 209/224] Mostly PyLint and Flake8 updates. Rewrote imports of exceptions to be from the exceptions module. Made nmap scanner check for libnmap dependency without crashing. Various flake8 and pylint updates. --- homeassistant/__main__.py | 3 +- homeassistant/bootstrap.py | 1 + .../components/device_tracker/nmap_tracker.py | 12 +++++-- homeassistant/components/mqtt.py | 2 +- homeassistant/core.py | 3 +- homeassistant/exceptions.py | 1 + homeassistant/helpers/entity.py | 2 +- homeassistant/remote.py | 31 ++++++++++--------- 8 files changed, 32 insertions(+), 23 deletions(-) diff --git a/homeassistant/__main__.py b/homeassistant/__main__.py index 02ef2c401f3..c6f0fd97b98 100644 --- a/homeassistant/__main__.py +++ b/homeassistant/__main__.py @@ -108,10 +108,9 @@ def main(): def open_browser(event): """ Open the webinterface in a browser. """ if hass.config.api is not None: - from homeassistant.const import EVENT_HOMEASSISTANT_START import webbrowser webbrowser.open(hass.config.api.base_url) - + from homeassistant.const import EVENT_HOMEASSISTANT_START hass.bus.listen_once(EVENT_HOMEASSISTANT_START, open_browser) hass.start() diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 61685dbf2fe..e9f04d9ab71 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -10,6 +10,7 @@ start by calling homeassistant.start_home_assistant(bus) """ import os +import sys import logging from collections import defaultdict diff --git a/homeassistant/components/device_tracker/nmap_tracker.py b/homeassistant/components/device_tracker/nmap_tracker.py index dc12a53f539..ee1650594ee 100644 --- a/homeassistant/components/device_tracker/nmap_tracker.py +++ b/homeassistant/components/device_tracker/nmap_tracker.py @@ -26,8 +26,12 @@ from collections import namedtuple import subprocess import re -from libnmap.process import NmapProcess -from libnmap.parser import NmapParser, NmapParserException +try: + from libnmap.process import NmapProcess + from libnmap.parser import NmapParser, NmapParserException + LIB_LOADED = True +except ImportError: + LIB_LOADED = False import homeassistant.util.dt as dt_util from homeassistant.const import CONF_HOSTS @@ -52,6 +56,10 @@ def get_scanner(hass, config): _LOGGER): return None + if not LIB_LOADED: + _LOGGER.error("Error while importing dependency python-libnmap.") + return False + scanner = NmapDeviceScanner(config[DOMAIN]) return scanner if scanner.success_init else None diff --git a/homeassistant/components/mqtt.py b/homeassistant/components/mqtt.py index 073406e9700..474b5ebb53e 100644 --- a/homeassistant/components/mqtt.py +++ b/homeassistant/components/mqtt.py @@ -46,7 +46,7 @@ The keep alive in seconds for this client. Default is 60. import logging import socket -from homeassistant.core import HomeAssistantError +from homeassistant.exceptions import HomeAssistantError import homeassistant.util as util from homeassistant.helpers import validate_config from homeassistant.const import ( diff --git a/homeassistant/core.py b/homeassistant/core.py index 0ad05694d65..c04e9a9ab63 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -7,7 +7,6 @@ of entities and react to changes. """ import os -import sys import time import logging import threading @@ -23,7 +22,7 @@ from homeassistant.const import ( EVENT_SERVICE_EXECUTED, ATTR_SERVICE_CALL_ID, EVENT_SERVICE_REGISTERED, TEMP_CELCIUS, TEMP_FAHRENHEIT, ATTR_FRIENDLY_NAME) from homeassistant.exceptions import ( - HomeAssistantError, InvalidEntityFormatError, NoEntitySpecifiedError) + HomeAssistantError, InvalidEntityFormatError) import homeassistant.util as util import homeassistant.util.dt as date_util import homeassistant.helpers.temperature as temp_helper diff --git a/homeassistant/exceptions.py b/homeassistant/exceptions.py index 4ecd22f9e43..bd32d356670 100644 --- a/homeassistant/exceptions.py +++ b/homeassistant/exceptions.py @@ -1,5 +1,6 @@ """ Exceptions used by Home Assistant """ + class HomeAssistantError(Exception): """ General Home Assistant exception occured. """ pass diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index 0ca63856c27..b29379049d3 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -7,7 +7,7 @@ Provides ABC for entities in HA. from collections import defaultdict -from homeassistant.core import NoEntitySpecifiedError +from homeassistant.exceptions import NoEntitySpecifiedError from homeassistant.const import ( ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT, ATTR_HIDDEN, diff --git a/homeassistant/remote.py b/homeassistant/remote.py index 2488f0a9c46..2193ede86e7 100644 --- a/homeassistant/remote.py +++ b/homeassistant/remote.py @@ -18,6 +18,7 @@ import urllib.parse import requests import homeassistant.core as ha +from homeassistant.exceptions import HomeAssistantError import homeassistant.bootstrap as bootstrap from homeassistant.const import ( @@ -84,12 +85,12 @@ class API(object): except requests.exceptions.ConnectionError: _LOGGER.exception("Error connecting to server") - raise ha.HomeAssistantError("Error connecting to server") + raise HomeAssistantError("Error connecting to server") except requests.exceptions.Timeout: error = "Timeout when talking to {}".format(self.host) _LOGGER.exception(error) - raise ha.HomeAssistantError(error) + raise HomeAssistantError(error) def __repr__(self): return "API({}, {}, {})".format( @@ -102,7 +103,7 @@ class HomeAssistant(ha.HomeAssistant): def __init__(self, remote_api, local_api=None): if not remote_api.validate_api(): - raise ha.HomeAssistantError( + raise HomeAssistantError( "Remote API at {}:{} not valid: {}".format( remote_api.host, remote_api.port, remote_api.status)) @@ -121,7 +122,7 @@ class HomeAssistant(ha.HomeAssistant): # Ensure a local API exists to connect with remote if self.config.api is None: if not bootstrap.setup_component(self, 'api'): - raise ha.HomeAssistantError( + raise HomeAssistantError( 'Unable to setup local API to receive events') ha.create_timer(self) @@ -132,7 +133,7 @@ class HomeAssistant(ha.HomeAssistant): # Setup that events from remote_api get forwarded to local_api # Do this after we fire START, otherwise HTTP is not started if not connect_remote_events(self.remote_api, self.config.api): - raise ha.HomeAssistantError(( + raise HomeAssistantError(( 'Could not setup event forwarding from api {} to ' 'local api {}').format(self.remote_api, self.config.api)) @@ -293,7 +294,7 @@ def validate_api(api): else: return APIStatus.UNKNOWN - except ha.HomeAssistantError: + except HomeAssistantError: return APIStatus.CANNOT_CONNECT @@ -318,7 +319,7 @@ def connect_remote_events(from_api, to_api): return False - except ha.HomeAssistantError: + except HomeAssistantError: _LOGGER.exception("Error setting up event forwarding") return False @@ -342,7 +343,7 @@ def disconnect_remote_events(from_api, to_api): return False - except ha.HomeAssistantError: + except HomeAssistantError: _LOGGER.exception("Error removing an event forwarder") return False @@ -354,7 +355,7 @@ def get_event_listeners(api): return req.json() if req.status_code == 200 else {} - except (ha.HomeAssistantError, ValueError): + except (HomeAssistantError, ValueError): # ValueError if req.json() can't parse the json _LOGGER.exception("Unexpected result retrieving event listeners") @@ -371,7 +372,7 @@ def fire_event(api, event_type, data=None): _LOGGER.error("Error firing event: %d - %d", req.status_code, req.text) - except ha.HomeAssistantError: + except HomeAssistantError: _LOGGER.exception("Error firing event") @@ -387,7 +388,7 @@ def get_state(api, entity_id): return ha.State.from_dict(req.json()) \ if req.status_code == 200 else None - except (ha.HomeAssistantError, ValueError): + except (HomeAssistantError, ValueError): # ValueError if req.json() can't parse the json _LOGGER.exception("Error fetching state") @@ -404,7 +405,7 @@ def get_states(api): return [ha.State.from_dict(item) for item in req.json()] - except (ha.HomeAssistantError, ValueError, AttributeError): + except (HomeAssistantError, ValueError, AttributeError): # ValueError if req.json() can't parse the json _LOGGER.exception("Error fetching states") @@ -434,7 +435,7 @@ def set_state(api, entity_id, new_state, attributes=None): else: return True - except ha.HomeAssistantError: + except HomeAssistantError: _LOGGER.exception("Error setting state") return False @@ -457,7 +458,7 @@ def get_services(api): return req.json() if req.status_code == 200 else {} - except (ha.HomeAssistantError, ValueError): + except (HomeAssistantError, ValueError): # ValueError if req.json() can't parse the json _LOGGER.exception("Got unexpected services result") @@ -475,5 +476,5 @@ def call_service(api, domain, service, service_data=None): _LOGGER.error("Error calling service: %d - %s", req.status_code, req.text) - except ha.HomeAssistantError: + except HomeAssistantError: _LOGGER.exception("Error calling service") From bfa3900e6a3027d889916bf28a5accb265f16887 Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Sat, 29 Aug 2015 22:44:59 -0400 Subject: [PATCH 210/224] Updated core config directory tests --- tests/test_core.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/tests/test_core.py b/tests/test_core.py index 6e7b52795b2..1aab679805a 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -16,6 +16,8 @@ from datetime import datetime import pytz import homeassistant.core as ha +from homeassistant.exceptions import ( + HomeAssistantError, InvalidEntityFormatError) import homeassistant.util.dt as dt_util from homeassistant.helpers.event import track_state_change from homeassistant.const import ( @@ -41,7 +43,7 @@ class TestHomeAssistant(unittest.TestCase): """ Stop down stuff we started. """ try: self.hass.stop() - except ha.HomeAssistantError: + except HomeAssistantError: # Already stopped after the block till stopped test pass @@ -250,7 +252,7 @@ class TestState(unittest.TestCase): def test_init(self): """ Test state.init """ self.assertRaises( - ha.InvalidEntityFormatError, ha.State, + InvalidEntityFormatError, ha.State, 'invalid_entity_format', 'test_state') def test_domain(self): @@ -489,18 +491,24 @@ class TestConfig(unittest.TestCase): def test_config_dir_set_correct(self): """ Test config dir set correct. """ - self.assertEqual(os.path.join(os.getcwd(), "config"), + data_dir = os.getenv('APPDATA') if os.name == "nt" \ + else os.path.expanduser('~') + self.assertEqual(os.path.join(data_dir, ".homeassistant"), self.config.config_dir) def test_path_with_file(self): """ Test get_config_path method. """ - self.assertEqual(os.path.join(os.getcwd(), "config", "test.conf"), + data_dir = os.getenv('APPDATA') if os.name == "nt" \ + else os.path.expanduser('~') + self.assertEqual(os.path.join(data_dir, ".homeassistant", "test.conf"), self.config.path("test.conf")) def test_path_with_dir_and_file(self): """ Test get_config_path method. """ + data_dir = os.getenv('APPDATA') if os.name == "nt" \ + else os.path.expanduser('~') self.assertEqual( - os.path.join(os.getcwd(), "config", "dir", "test.conf"), + os.path.join(data_dir, ".homeassistant", "dir", "test.conf"), self.config.path("dir", "test.conf")) def test_temperature_not_convert_if_no_preference(self): From b750457afa8d7b9c00fe428d90f9c586961fa37e Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 29 Aug 2015 20:11:27 -0700 Subject: [PATCH 211/224] Bugfixes frontend --- homeassistant/components/frontend/version.py | 2 +- .../frontend/www_static/frontend.html | 24 ++++++++++-------- .../www_static/home-assistant-polymer | 2 +- .../components/frontend/www_static/splash.png | Bin 11636 -> 51901 bytes 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py index defce54a73a..6eea0024c30 100644 --- a/homeassistant/components/frontend/version.py +++ b/homeassistant/components/frontend/version.py @@ -1,2 +1,2 @@ """ DO NOT MODIFY. Auto-generated by build_frontend script """ -VERSION = "25d26e3bbcae0f4493f787a5673a84fe" +VERSION = "441ca74ed89c1db053bc2f55d57a48c6" diff --git a/homeassistant/components/frontend/www_static/frontend.html b/homeassistant/components/frontend/www_static/frontend.html index 82b0dba526a..06f43dec560 100644 --- a/homeassistant/components/frontend/www_static/frontend.html +++ b/homeassistant/components/frontend/www_static/frontend.html @@ -3549,7 +3549,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN color: white; border-radius: 1em; padding: 4px 8px; - font-weight: 400; + font-weight: 500; text-transform: uppercase; overflow: hidden; text-overflow: ellipsis; @@ -3566,7 +3566,9 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN iron-image { border-radius: 50%; - } \ No newline at end of file +console.group&&(console.groupCollapsed("Dispatch: %s",t),console.group("payload"),console.debug(e),console.groupEnd())},e.dispatchError=function(t){console.group&&(console.debug("Dispatch error: "+t),console.groupEnd())},e.storeHandled=function(t,e,n){console.group&&e!==n&&console.debug("Store "+t+" handled action")},e.dispatchEnd=function(t){console.group&&(console.debug("Dispatch done, new state: ",t.toJS()),console.groupEnd())}},function(t,e,n){function r(t,e){this.__prevState=t,this.__evaluator=e,this.__prevValues=i.Map(),this.__observers=[]}var i=n(2),o=n(7),u=n(8);Object.defineProperty(r.prototype,"notifyObservers",{writable:!0,configurable:!0,value:function(t){if(this.__observers.length>0){var e=i.Map();this.__observers.forEach(function(n){var r,i=n.getter,a=o(i),s=this.__prevState;this.__prevValues.has(a)?r=this.__prevValues.get(a):(r=this.__evaluator.evaluate(s,i),this.__prevValues=this.__prevValues.set(a,r));var c=this.__evaluator.evaluate(t,i);u(r,c)||(n.handler.call(null,c),e=e.set(a,c))}.bind(this)),this.__prevValues=e}this.__prevState=t}}),Object.defineProperty(r.prototype,"onChange",{writable:!0,configurable:!0,value:function(t,e){var n={getter:t,handler:e};return this.__observers.push(n),function(){var t=this.__observers.indexOf(n);t>-1&&this.__observers.splice(t,1)}.bind(this)}}),Object.defineProperty(r.prototype,"reset",{writable:!0,configurable:!0,value:function(t){this.__prevState=t,this.__prevValues=i.Map(),this.__observers=[]}}),t.exports=r},function(t,e,n){var r=n(2);t.exports=function(t,e){if(t.hasOwnProperty("__hashCode"))return t.__hashCode;var n=r.fromJS(t).hashCode();return e||(Object.defineProperty(t,"__hashCode",{enumerable:!1,configurable:!1,writable:!1,value:n}),Object.freeze(t)),n}},function(t,e,n){var r=n(2);t.exports=function(t,e){return r.is(t,e)}},function(t,e,n){function r(t){return s(t)&&a(t[t.length-1])}function i(t){return t[t.length-1]}function o(t){return t.slice(0,t.length-1)}function u(t){if(!c(t))throw new Error("Cannot create Getter from KeyPath: "+t);return[t,l]}var a=n(3).isFunction,s=n(3).isArray,c=n(10).isKeyPath,l=function(t){return t};t.exports={isGetter:r,getComputeFn:i,getDeps:o,fromKeyPath:u}},function(t,e,n){var r=n(3).isArray,i=n(3).isFunction;e.isKeyPath=function(t){return r(t)&&!i(t[t.length-1])}},function(t,e,n){function r(){this.__cachedGetters=i.Map({})}var i=n(2),o=n(1).toImmutable,u=n(7),a=n(8),s=n(9).getComputeFn,c=n(9).getDeps,l=n(10).isKeyPath,f=n(9).isGetter,d=!1;Object.defineProperty(r.prototype,"evaluate",{writable:!0,configurable:!0,value:function(t,e){if(l(e))return t.getIn(e);if(!f(e))throw new Error("evaluate must be passed a keyPath or Getter");var n=u(e);if(this.__isCached(t,e))return this.__cachedGetters.getIn([n,"value"]);var r=c(e).map(function(e){return this.evaluate(t,e)}.bind(this));if(this.__hasStaleValue(t,e)){var i=this.__cachedGetters.getIn([n,"args"]);if(a(i,o(r))){var p=this.__cachedGetters.getIn([n,"value"]);return this.__cacheValue(t,e,i,p),p}}if(d===!0)throw d=!1,new Error("Evaluate may not be called within a Getters computeFn");var h;d=!0;try{h=s(e).apply(null,r),d=!1}catch(v){throw d=!1,v}return this.__cacheValue(t,e,r,h),h}}),Object.defineProperty(r.prototype,"__hasStaleValue",{writable:!0,configurable:!0,value:function(t,e){var n=u(e),r=this.__cachedGetters;return r.has(n)&&r.getIn([n,"stateHashCode"])!==t.hashCode()}}),Object.defineProperty(r.prototype,"__cacheValue",{writable:!0,configurable:!0,value:function(t,e,n,r){var a=u(e);this.__cachedGetters=this.__cachedGetters.set(a,i.Map({value:r,args:o(n),stateHashCode:t.hashCode()}))}}),Object.defineProperty(r.prototype,"__isCached",{writable:!0,configurable:!0,value:function(t,e){var n=u(e);return this.__cachedGetters.hasIn([n,"value"])&&this.__cachedGetters.getIn([n,"stateHashCode"])===t.hashCode()}}),Object.defineProperty(r.prototype,"untrack",{writable:!0,configurable:!0,value:function(t){}}),Object.defineProperty(r.prototype,"reset",{writable:!0,configurable:!0,value:function(){this.__cachedGetters=i.Map({})}}),t.exports=r},function(t,e,n){function r(t,e){var n={};return i(e,function(e,r){n[r]=t.evaluate(e)}),n}var i=n(3).each;t.exports=function(t){return{getInitialState:function(){return r(t,this.getDataBindings())},componentDidMount:function(){var e=this;e.__unwatchFns=[],i(this.getDataBindings(),function(n,r){var i=t.observe(n,function(t){var n={};n[r]=t,e.setState(n)});e.__unwatchFns.push(i)})},componentWillUnmount:function(){for(;this.__unwatchFns.length;)this.__unwatchFns.shift()()}}}},function(t,e,n){function r(t){return this instanceof r?(this.__handlers=o({}),t&&u(this,t),void this.initialize()):new r(t)}function i(t){return t instanceof r}var o=n(2).Map,u=n(3).extend,a=n(1).toJS,s=n(1).toImmutable;Object.defineProperty(r.prototype,"initialize",{writable:!0,configurable:!0,value:function(){}}),Object.defineProperty(r.prototype,"getInitialState",{writable:!0,configurable:!0,value:function(){return o()}}),Object.defineProperty(r.prototype,"handle",{writable:!0,configurable:!0,value:function(t,e,n){var r=this.__handlers.get(e);return"function"==typeof r?r.call(this,t,n,e):t}}),Object.defineProperty(r.prototype,"handleReset",{writable:!0,configurable:!0,value:function(t){return this.getInitialState()}}),Object.defineProperty(r.prototype,"on",{writable:!0,configurable:!0,value:function(t,e){this.__handlers=this.__handlers.set(t,e)}}),Object.defineProperty(r.prototype,"serialize",{writable:!0,configurable:!0,value:function(t){return a(t)}}),Object.defineProperty(r.prototype,"deserialize",{writable:!0,configurable:!0,value:function(t){return s(t)}}),t.exports=r,t.exports.isStore=i}])})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(175),u=r(o);e["default"]=u["default"](i.reactor),t.exports=e["default"]},function(t,e){"use strict";var n=function(t){var e,n={};if(!(t instanceof Object)||Array.isArray(t))throw new Error("keyMirror(...): Argument must be an object.");for(e in t)t.hasOwnProperty(e)&&(n[e]=e);return n};t.exports=n},function(t,e){"use strict";function n(t){var e=typeof t;return!!t&&("object"==e||"function"==e)}t.exports=n},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(184),o=r(i);e.callApi=o["default"]},function(t,e,n){"use strict";function r(t){return i(t)?t:Object(t)}var i=n(6);t.exports=r},function(t,e,n){"use strict";var r=n(20),i=n(12),o=n(13),u="[object Array]",a=Object.prototype,s=a.toString,c=r(Array,"isArray"),l=c||function(t){return o(t)&&i(t.length)&&s.call(t)==u};t.exports=l},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var o=n(200),u=i(o),a=n(201),s=r(a),c=u["default"];e.actions=c;var l=s;e.getters=l},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){t.registerStores({restApiCache:l["default"]})}function o(t){return[["restApiCache",t.entity],function(t){return!!t}]}function u(t){return[["restApiCache",t.entity],function(t){return t||s.toImmutable({})}]}function a(t){return function(e){return["restApiCache",t.entity,e]}}Object.defineProperty(e,"__esModule",{value:!0}),e.register=i,e.createHasDataGetter=o,e.createEntityMapGetter=u,e.createByIdGetter=a;var s=n(3),c=n(224),l=r(c),f=n(223),d=r(f);e.createApiActions=d["default"]},function(t,e){"use strict";function n(t){return"number"==typeof t&&t>-1&&t%1==0&&r>=t}var r=9007199254740991;t.exports=n},function(t,e){"use strict";function n(t){return!!t&&"object"==typeof t}t.exports=n},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);e["default"]=new o["default"]({is:"partial-base",properties:{narrow:{type:Boolean,value:!1},showMenu:{type:Boolean,value:!1}},computeMenuButtonClass:function(t,e){return!t&&e?"invisible":""},toggleMenu:function(){this.fire("open-menu")}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(5),o=r(i);e["default"]=o["default"]({ENTITY_HISTORY_DATE_SELECTED:null,ENTITY_HISTORY_FETCH_START:null,ENTITY_HISTORY_FETCH_ERROR:null,ENTITY_HISTORY_FETCH_SUCCESS:null,RECENT_ENTITY_HISTORY_FETCH_START:null,RECENT_ENTITY_HISTORY_FETCH_ERROR:null,RECENT_ENTITY_HISTORY_FETCH_SUCCESS:null,LOG_OUT:null}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return null!=t&&o(i(t))}var i=n(49),o=n(12);t.exports=r},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(137),n(58),e["default"]=new o["default"]({is:"state-info",properties:{stateObj:{type:Object}}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(5),o=r(i);e["default"]=o["default"]({LOGBOOK_DATE_SELECTED:null,LOGBOOK_ENTRIES_FETCH_START:null,LOGBOOK_ENTRIES_FETCH_ERROR:null,LOGBOOK_ENTRIES_FETCH_SUCCESS:null}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var o=n(225),u=i(o),a=n(79),s=r(a),c=u["default"];e.actions=c;var l=s;e.getters=l},function(t,e,n){"use strict";function r(t,e){var n=null==t?void 0:t[e];return i(n)?n:void 0}var i=n(126);t.exports=r},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(5),o=r(i);e["default"]=o["default"]({VALIDATING_AUTH_TOKEN:null,VALID_AUTH_TOKEN:null,INVALID_AUTH_TOKEN:null,LOG_OUT:null}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}function o(t){t.registerStores({authAttempt:a["default"],authCurrent:c["default"],rememberAuth:f["default"]})}Object.defineProperty(e,"__esModule",{value:!0}),e.register=o;var u=n(187),a=i(u),s=n(188),c=i(s),l=n(189),f=i(l),d=n(185),p=r(d),h=n(186),v=r(h),_=p;e.actions=_;var y=v;e.getters=y},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function o(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}Object.defineProperty(e,"__esModule",{value:!0});var u=function(){function t(t,e){var n=[],r=!0,i=!1,o=void 0;try{for(var u,a=t[Symbol.iterator]();!(r=(u=a.next()).done)&&(n.push(u.value),!e||n.length!==e);r=!0);}catch(s){i=!0,o=s}finally{try{!r&&a["return"]&&a["return"]()}finally{if(i)throw o}}return n}return function(e,n){if(Array.isArray(e))return e;if(Symbol.iterator in Object(e))return t(e,n);throw new TypeError("Invalid attempt to destructure non-iterable instance")}}(),a=function(){function t(t,e){for(var n=0;n-1&&t%1==0&&e>t}var r=/^\d+$/,i=9007199254740991;t.exports=n},function(t,e,n){"use strict";function r(t,e,n){if(!u(n))return!1;var r=typeof e;if("number"==r?i(n)&&o(e,n.length):"string"==r&&e in n){var a=n[e];return t===t?t===a:a!==a}return!1}var i=n(16),o=n(25),u=n(6);t.exports=r},function(t,e,n){"use strict";function r(t){return o(t)&&i(t)&&a.call(t,"callee")&&!s.call(t,"callee")}var i=n(16),o=n(13),u=Object.prototype,a=u.hasOwnProperty,s=u.propertyIsEnumerable;t.exports=r},function(t,e,n){"use strict";var r=n(20),i=n(16),o=n(6),u=n(123),a=r(Object,"keys"),s=a?function(t){var e=null==t?void 0:t.constructor;return"function"==typeof e&&e.prototype===t||"function"!=typeof t&&i(t)?u(t):o(t)?a(t):[]}:u;t.exports=s},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i),u=n(62),a=r(u);e["default"]=new o["default"]({is:"domain-icon",properties:{domain:{type:String,value:""},state:{type:String,value:""}},computeIcon:function(t,e){return a["default"](t,e)}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);e["default"]=new o["default"]({is:"loading-box"}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i),u=n(176),a=r(u);n(32),n(60),n(174),n(171),n(173),n(172),e["default"]=new o["default"]({is:"state-card-content",properties:{stateObj:{type:Object,observer:"stateObjChanged"}},stateObjChanged:function(t,e){var n=o["default"].dom(this);if(!t)return void(n.lastChild&&n.removeChild(n.lastChild));var r=a["default"](t);if(e&&a["default"](e)===r)n.lastChild.stateObj=t;else{n.lastChild&&n.removeChild(n.lastChild);var i=document.createElement("state-card-"+r);i.stateObj=t,n.appendChild(i)}}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(17),e["default"]=new o["default"]({is:"state-card-display",properties:{stateObj:{type:Object}}}),t.exports=e["default"]},function(t,e){"use strict";function n(t,e){return t?e.map(function(e){return e in t.attributes?"has-"+e:""}).join(" "):""}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=n,t.exports=e["default"]},function(t,e,n){"use strict";function r(t,e,n){function r(){y&&clearTimeout(y),p&&clearTimeout(p),g=0,p=y=m=void 0}function s(e,n){n&&clearTimeout(n),p=y=m=void 0,e&&(g=o(),h=t.apply(_,d),y||p||(d=_=void 0))}function c(){var t=e-(o()-v);0>=t||t>e?s(m,p):y=setTimeout(c,t)}function l(){s(O,y)}function f(){if(d=arguments,v=o(),_=this,m=O&&(y||!w),b===!1)var n=w&&!y;else{p||w||(g=v);var r=b-(v-g),i=0>=r||r>b;i?(p&&(p=clearTimeout(p)),g=v,h=t.apply(_,d)):p||(p=setTimeout(l,r))}return i&&y?y=clearTimeout(y):y||e===b||(y=setTimeout(c,e)),n&&(i=!0,h=t.apply(_,d)),!i||y||p||(d=_=void 0),h}var d,p,h,v,_,y,m,g=0,b=!1,O=!0;if("function"!=typeof t)throw new TypeError(u);if(e=0>e?0:+e||0,n===!0){var w=!0;O=!1}else i(n)&&(w=!!n.leading,b="maxWait"in n&&a(+n.maxWait||0,e),O="trailing"in n?!!n.trailing:O);return f.cancel=r,f}var i=n(68),o=n(180),u="Expected a function",a=Math.max;t.exports=r},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(5),o=r(i);e["default"]=o["default"]({SERVER_CONFIG_LOADED:null,COMPONENT_LOADED:null,LOG_OUT:null}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}function o(t){t.registerStores({serverComponent:a["default"],serverConfig:c["default"]})}Object.defineProperty(e,"__esModule",{value:!0}),e.register=o;var u=n(192),a=i(u),s=n(193),c=i(s),l=n(190),f=r(l),d=n(191),p=r(d),h=f;e.actions=h;var v=p;e.getters=v},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var o=n(202),u=i(o),a=n(203),s=r(a),c=u["default"];e.actions=c;var l=s;e.getters=l},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(5),o=r(i);e["default"]=o["default"]({NAVIGATE:null,SHOW_SIDEBAR:null,LOG_OUT:null}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}function o(t){t.registerStores({notifications:a["default"]})}Object.defineProperty(e,"__esModule",{value:!0}),e.register=o;var u=n(220),a=i(u),s=n(218),c=r(s),l=n(219),f=r(l),d=c;e.actions=d;var p=f;e.getters=p},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}function o(t){t.registerStores({streamStatus:a["default"]})}Object.defineProperty(e,"__esModule",{value:!0}),e.register=o;var u=n(232),a=i(u),s=n(228),c=r(s),l=n(229),f=r(l),d=c;e.actions=d;var p=f;e.getters=p},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(5),o=r(i);e["default"]=o["default"]({API_FETCH_ALL_START:null,API_FETCH_ALL_SUCCESS:null,API_FETCH_ALL_FAIL:null,SYNC_SCHEDULED:null,SYNC_SCHEDULE_CANCELLED:null}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}function o(t){t.registerStores({isFetchingData:a["default"],isSyncScheduled:c["default"]})}Object.defineProperty(e,"__esModule",{value:!0}),e.register=o;var u=n(234),a=i(u),s=n(235),c=i(s),l=n(233),f=r(l),d=n(82),p=r(d),h=f;e.actions=h;var v=p;e.getters=v},function(t,e){"use strict";function n(t){return t.getFullYear()+"-"+(t.getMonth()+1)+"-"+t.getDate()}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=n,t.exports=e["default"]},function(t,e){"use strict";function n(t){var e=t.split(" "),n=r(e,2),i=n[0],o=n[1],u=i.split(":"),a=r(u,3),s=a[0],c=a[1],l=a[2],f=o.split("-"),d=r(f,3),p=d[0],h=d[1],v=d[2];return new Date(Date.UTC(v,parseInt(h,10)-1,p,s,c,l))}Object.defineProperty(e,"__esModule",{value:!0});var r=function(){function t(t,e){var n=[],r=!0,i=!1,o=void 0;try{for(var u,a=t[Symbol.iterator]();!(r=(u=a.next()).done)&&(n.push(u.value),!e||n.length!==e);r=!0);}catch(s){i=!0,o=s}finally{try{!r&&a["return"]&&a["return"]()}finally{if(i)throw o}}return n}return function(e,n){if(Array.isArray(e))return e;if(Symbol.iterator in Object(e))return t(e,n);throw new TypeError("Invalid attempt to destructure non-iterable instance")}}();e["default"]=n,t.exports=e["default"]},function(t,e,n){"use strict";function r(t,e,n){if(null!=t){void 0!==n&&n in i(t)&&(e=[n]);for(var r=0,o=e.length;null!=t&&o>r;)t=t[e[r++]];return r&&r==o?t:void 0}}var i=n(8);t.exports=r},function(t,e,n){"use strict";function r(t,e,n,a,s,c){return t===e?!0:null==t||null==e||!o(t)&&!u(e)?t!==t&&e!==e:i(t,e,r,n,a,s,c)}var i=n(102),o=n(6),u=n(13);t.exports=r},function(t,e,n){"use strict";function r(t,e){var n=-1,r=o(t)?Array(t.length):[];return i(t,function(t,i,o){r[++n]=e(t,i,o)}),r}var i=n(97),o=n(16);t.exports=r},function(t,e){"use strict";function n(t){return function(e){return null==e?void 0:e[t]}}t.exports=n},function(t,e,n){"use strict";var r=n(48),i=r("length");t.exports=i},function(t,e,n){"use strict";function r(t,e){var n=typeof t;if("string"==n&&a.test(t)||"number"==n)return!0;if(i(t))return!1;var r=!u.test(t);return r||null!=e&&t in o(e)}var i=n(9),o=n(8),u=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\n\\]|\\.)*?\1)\]/,a=/^\w*$/;t.exports=r},function(t,e,n){"use strict";function r(t){return t===t&&!i(t)}var i=n(6);t.exports=r},function(t,e,n){"use strict";function r(t){if(o(t))return t;var e=[];return i(t).replace(u,function(t,n,r,i){e.push(r?i.replace(a,"$1"):n||t)}),e}var i=n(109),o=n(9),u=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\n\\]|\\.)*?)\2)\]/g,a=/\\(\\)?/g;t.exports=r},function(t,e){"use strict";function n(t){return t}t.exports=n},function(t,e,n){"use strict";function r(t){return u(t)?i(t):o(t)}var i=n(48),o=n(106),u=n(50);t.exports=r},function(t,e,n){(function(t){"use strict";!function(e,n){t.exports=n()}(void 0,function(){function e(){return Ln.apply(null,arguments)}function n(t){Ln=t}function r(t){return"[object Array]"===Object.prototype.toString.call(t)}function i(t){return t instanceof Date||"[object Date]"===Object.prototype.toString.call(t)}function o(t,e){var n,r=[];for(n=0;n0)for(n in Rn)r=Rn[n],i=e[r],"undefined"!=typeof i&&(t[r]=i);return t}function h(t){p(this,t),this._d=new Date(null!=t._d?t._d.getTime():NaN),zn===!1&&(zn=!0,e.updateOffset(this),zn=!1)}function v(t){return t instanceof h||null!=t&&null!=t._isAMomentObject}function _(t){return 0>t?Math.ceil(t):Math.floor(t)}function y(t){var e=+t,n=0;return 0!==e&&isFinite(e)&&(n=_(e)),n}function m(t,e,n){var r,i=Math.min(t.length,e.length),o=Math.abs(t.length-e.length),u=0;for(r=0;i>r;r++)(n&&t[r]!==e[r]||!n&&y(t[r])!==y(e[r]))&&u++;return u+o}function g(){}function b(t){return t?t.toLowerCase().replace("_","-"):t}function O(t){for(var e,n,r,i,o=0;o0;){if(r=w(i.slice(0,e).join("-")))return r;if(n&&n.length>=e&&m(i,n,!0)>=e-1)break;e--}o++}return null}function w(e){var n=null;if(!Hn[e]&&"undefined"!=typeof t&&t&&t.exports)try{n=Nn._abbr,!function(){var t=new Error('Cannot find module "./locale"');throw t.code="MODULE_NOT_FOUND",t}(),S(n)}catch(r){}return Hn[e]}function S(t,e){var n;return t&&(n="undefined"==typeof e?T(t):M(t,e),n&&(Nn=n)),Nn._abbr}function M(t,e){return null!==e?(e.abbr=t,Hn[t]=Hn[t]||new g,Hn[t].set(e),S(t),Hn[t]):(delete Hn[t],null)}function T(t){var e;if(t&&t._locale&&t._locale._abbr&&(t=t._locale._abbr),!t)return Nn;if(!r(t)){if(e=w(t))return e;t=[t]}return O(t)}function j(t,e){var n=t.toLowerCase();Yn[n]=Yn[n+"s"]=Yn[e]=t}function E(t){return"string"==typeof t?Yn[t]||Yn[t.toLowerCase()]:void 0}function I(t){var e,n,r={};for(n in t)u(t,n)&&(e=E(n),e&&(r[e]=t[n]));return r}function P(t,n){return function(r){return null!=r?(C(this,t,r),e.updateOffset(this,n),this):D(this,t)}}function D(t,e){return t._d["get"+(t._isUTC?"UTC":"")+e]()}function C(t,e,n){return t._d["set"+(t._isUTC?"UTC":"")+e](n)}function A(t,e){var n;if("object"==typeof t)for(n in t)this.set(n,t[n]);else if(t=E(t),"function"==typeof this[t])return this[t](e);return this}function x(t,e,n){var r=""+Math.abs(t),i=e-r.length,o=t>=0;return(o?n?"+":"":"-")+Math.pow(10,Math.max(0,i)).toString().substr(1)+r}function k(t,e,n,r){var i=r;"string"==typeof r&&(i=function(){return this[r]()}),t&&(Bn[t]=i),e&&(Bn[e[0]]=function(){return x(i.apply(this,arguments),e[1],e[2])}),n&&(Bn[n]=function(){return this.localeData().ordinal(i.apply(this,arguments),t)})}function L(t){return t.match(/\[[\s\S]/)?t.replace(/^\[|\]$/g,""):t.replace(/\\/g,"")}function N(t){var e,n,r=t.match(Gn);for(e=0,n=r.length;n>e;e++)Bn[r[e]]?r[e]=Bn[r[e]]:r[e]=L(r[e]);return function(i){var o="";for(e=0;n>e;e++)o+=r[e]instanceof Function?r[e].call(i,t):r[e];return o}}function R(t,e){return t.isValid()?(e=z(e,t.localeData()),Fn[e]=Fn[e]||N(e),Fn[e](t)):t.localeData().invalidDate()}function z(t,e){function n(t){return e.longDateFormat(t)||t}var r=5;for(Un.lastIndex=0;r>=0&&Un.test(t);)t=t.replace(Un,n),Un.lastIndex=0,r-=1;return t}function H(t){return"function"==typeof t&&"[object Function]"===Object.prototype.toString.call(t)}function Y(t,e,n){or[t]=H(e)?e:function(t){return t&&n?n:e}}function G(t,e){return u(or,t)?or[t](e._strict,e._locale):new RegExp(U(t))}function U(t){return t.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(t,e,n,r,i){return e||n||r||i}).replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function F(t,e){var n,r=e;for("string"==typeof t&&(t=[t]),"number"==typeof e&&(r=function(t,n){n[e]=y(t)}),n=0;nr;r++){if(i=s([2e3,r]),n&&!this._longMonthsParse[r]&&(this._longMonthsParse[r]=new RegExp("^"+this.months(i,"").replace(".","")+"$","i"),this._shortMonthsParse[r]=new RegExp("^"+this.monthsShort(i,"").replace(".","")+"$","i")),n||this._monthsParse[r]||(o="^"+this.months(i,"")+"|^"+this.monthsShort(i,""),this._monthsParse[r]=new RegExp(o.replace(".",""),"i")),n&&"MMMM"===e&&this._longMonthsParse[r].test(t))return r;if(n&&"MMM"===e&&this._shortMonthsParse[r].test(t))return r;if(!n&&this._monthsParse[r].test(t))return r}}function $(t,e){var n;return"string"==typeof e&&(e=t.localeData().monthsParse(e),"number"!=typeof e)?t:(n=Math.min(t.date(),q(t.year(),e)),t._d["set"+(t._isUTC?"UTC":"")+"Month"](e,n),t)}function Z(t){return null!=t?($(this,t),e.updateOffset(this,!0),this):D(this,"Month")}function X(){return q(this.year(),this.month())}function Q(t){var e,n=t._a;return n&&-2===l(t).overflow&&(e=n[sr]<0||n[sr]>11?sr:n[cr]<1||n[cr]>q(n[ar],n[sr])?cr:n[lr]<0||n[lr]>24||24===n[lr]&&(0!==n[fr]||0!==n[dr]||0!==n[pr])?lr:n[fr]<0||n[fr]>59?fr:n[dr]<0||n[dr]>59?dr:n[pr]<0||n[pr]>999?pr:-1,l(t)._overflowDayOfYear&&(ar>e||e>cr)&&(e=cr),l(t).overflow=e),t}function tt(t){e.suppressDeprecationWarnings===!1&&"undefined"!=typeof console&&console.warn&&console.warn("Deprecation warning: "+t)}function et(t,e){var n=!0;return a(function(){return n&&(tt(t+"\n"+(new Error).stack),n=!1),e.apply(this,arguments)},e)}function nt(t,e){_r[t]||(tt(e),_r[t]=!0)}function rt(t){var e,n,r=t._i,i=yr.exec(r);if(i){for(l(t).iso=!0,e=0,n=mr.length;n>e;e++)if(mr[e][1].exec(r)){t._f=mr[e][0];break}for(e=0,n=gr.length;n>e;e++)if(gr[e][1].exec(r)){t._f+=(i[6]||" ")+gr[e][0];break}r.match(nr)&&(t._f+="Z"),wt(t)}else t._isValid=!1}function it(t){var n=br.exec(t._i);return null!==n?void(t._d=new Date(+n[1])):(rt(t),void(t._isValid===!1&&(delete t._isValid,e.createFromInputFallback(t))))}function ot(t,e,n,r,i,o,u){var a=new Date(t,e,n,r,i,o,u);return 1970>t&&a.setFullYear(t),a}function ut(t){var e=new Date(Date.UTC.apply(null,arguments));return 1970>t&&e.setUTCFullYear(t),e}function at(t){return st(t)?366:365}function st(t){return t%4===0&&t%100!==0||t%400===0}function ct(){return st(this.year())}function lt(t,e,n){var r,i=n-e,o=n-t.day();return o>i&&(o-=7),i-7>o&&(o+=7),r=Dt(t).add(o,"d"),{week:Math.ceil(r.dayOfYear()/7),year:r.year()}}function ft(t){return lt(t,this._week.dow,this._week.doy).week}function dt(){return this._week.dow}function pt(){return this._week.doy}function ht(t){var e=this.localeData().week(this);return null==t?e:this.add(7*(t-e),"d")}function vt(t){var e=lt(this,1,4).week;return null==t?e:this.add(7*(t-e),"d")}function _t(t,e,n,r,i){var o,u=6+i-r,a=ut(t,0,1+u),s=a.getUTCDay();return i>s&&(s+=7),n=null!=n?1*n:i,o=1+u+7*(e-1)-s+n,{year:o>0?t:t-1,dayOfYear:o>0?o:at(t-1)+o}}function yt(t){var e=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==t?e:this.add(t-e,"d")}function mt(t,e,n){return null!=t?t:null!=e?e:n}function gt(t){var e=new Date;return t._useUTC?[e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate()]:[e.getFullYear(),e.getMonth(),e.getDate()]}function bt(t){var e,n,r,i,o=[];if(!t._d){for(r=gt(t),t._w&&null==t._a[cr]&&null==t._a[sr]&&Ot(t),t._dayOfYear&&(i=mt(t._a[ar],r[ar]),t._dayOfYear>at(i)&&(l(t)._overflowDayOfYear=!0),n=ut(i,0,t._dayOfYear),t._a[sr]=n.getUTCMonth(),t._a[cr]=n.getUTCDate()),e=0;3>e&&null==t._a[e];++e)t._a[e]=o[e]=r[e];for(;7>e;e++)t._a[e]=o[e]=null==t._a[e]?2===e?1:0:t._a[e];24===t._a[lr]&&0===t._a[fr]&&0===t._a[dr]&&0===t._a[pr]&&(t._nextDay=!0,t._a[lr]=0),t._d=(t._useUTC?ut:ot).apply(null,o),null!=t._tzm&&t._d.setUTCMinutes(t._d.getUTCMinutes()-t._tzm), +t._nextDay&&(t._a[lr]=24)}}function Ot(t){var e,n,r,i,o,u,a;e=t._w,null!=e.GG||null!=e.W||null!=e.E?(o=1,u=4,n=mt(e.GG,t._a[ar],lt(Dt(),1,4).year),r=mt(e.W,1),i=mt(e.E,1)):(o=t._locale._week.dow,u=t._locale._week.doy,n=mt(e.gg,t._a[ar],lt(Dt(),o,u).year),r=mt(e.w,1),null!=e.d?(i=e.d,o>i&&++r):i=null!=e.e?e.e+o:o),a=_t(n,r,i,u,o),t._a[ar]=a.year,t._dayOfYear=a.dayOfYear}function wt(t){if(t._f===e.ISO_8601)return void rt(t);t._a=[],l(t).empty=!0;var n,r,i,o,u,a=""+t._i,s=a.length,c=0;for(i=z(t._f,t._locale).match(Gn)||[],n=0;n0&&l(t).unusedInput.push(u),a=a.slice(a.indexOf(r)+r.length),c+=r.length),Bn[o]?(r?l(t).empty=!1:l(t).unusedTokens.push(o),V(o,r,t)):t._strict&&!r&&l(t).unusedTokens.push(o);l(t).charsLeftOver=s-c,a.length>0&&l(t).unusedInput.push(a),l(t).bigHour===!0&&t._a[lr]<=12&&t._a[lr]>0&&(l(t).bigHour=void 0),t._a[lr]=St(t._locale,t._a[lr],t._meridiem),bt(t),Q(t)}function St(t,e,n){var r;return null==n?e:null!=t.meridiemHour?t.meridiemHour(e,n):null!=t.isPM?(r=t.isPM(n),r&&12>e&&(e+=12),r||12!==e||(e=0),e):e}function Mt(t){var e,n,r,i,o;if(0===t._f.length)return l(t).invalidFormat=!0,void(t._d=new Date(NaN));for(i=0;io)&&(r=o,n=e));a(t,n||e)}function Tt(t){if(!t._d){var e=I(t._i);t._a=[e.year,e.month,e.day||e.date,e.hour,e.minute,e.second,e.millisecond],bt(t)}}function jt(t){var e=new h(Q(Et(t)));return e._nextDay&&(e.add(1,"d"),e._nextDay=void 0),e}function Et(t){var e=t._i,n=t._f;return t._locale=t._locale||T(t._l),null===e||void 0===n&&""===e?d({nullInput:!0}):("string"==typeof e&&(t._i=e=t._locale.preparse(e)),v(e)?new h(Q(e)):(r(n)?Mt(t):n?wt(t):i(e)?t._d=e:It(t),t))}function It(t){var n=t._i;void 0===n?t._d=new Date:i(n)?t._d=new Date(+n):"string"==typeof n?it(t):r(n)?(t._a=o(n.slice(0),function(t){return parseInt(t,10)}),bt(t)):"object"==typeof n?Tt(t):"number"==typeof n?t._d=new Date(n):e.createFromInputFallback(t)}function Pt(t,e,n,r,i){var o={};return"boolean"==typeof n&&(r=n,n=void 0),o._isAMomentObject=!0,o._useUTC=o._isUTC=i,o._l=n,o._i=t,o._f=e,o._strict=r,jt(o)}function Dt(t,e,n,r){return Pt(t,e,n,r,!1)}function Ct(t,e){var n,i;if(1===e.length&&r(e[0])&&(e=e[0]),!e.length)return Dt();for(n=e[0],i=1;it&&(t=-t,n="-"),n+x(~~(t/60),2)+e+x(~~t%60,2)})}function Rt(t){var e=(t||"").match(nr)||[],n=e[e.length-1]||[],r=(n+"").match(Tr)||["-",0,0],i=+(60*r[1])+y(r[2]);return"+"===r[0]?i:-i}function zt(t,n){var r,o;return n._isUTC?(r=n.clone(),o=(v(t)||i(t)?+t:+Dt(t))-+r,r._d.setTime(+r._d+o),e.updateOffset(r,!1),r):Dt(t).local()}function Ht(t){return 15*-Math.round(t._d.getTimezoneOffset()/15)}function Yt(t,n){var r,i=this._offset||0;return null!=t?("string"==typeof t&&(t=Rt(t)),Math.abs(t)<16&&(t=60*t),!this._isUTC&&n&&(r=Ht(this)),this._offset=t,this._isUTC=!0,null!=r&&this.add(r,"m"),i!==t&&(!n||this._changeInProgress?ne(this,Zt(t-i,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,e.updateOffset(this,!0),this._changeInProgress=null)),this):this._isUTC?i:Ht(this)}function Gt(t,e){return null!=t?("string"!=typeof t&&(t=-t),this.utcOffset(t,e),this):-this.utcOffset()}function Ut(t){return this.utcOffset(0,t)}function Ft(t){return this._isUTC&&(this.utcOffset(0,t),this._isUTC=!1,t&&this.subtract(Ht(this),"m")),this}function Bt(){return this._tzm?this.utcOffset(this._tzm):"string"==typeof this._i&&this.utcOffset(Rt(this._i)),this}function Vt(t){return t=t?Dt(t).utcOffset():0,(this.utcOffset()-t)%60===0}function qt(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()}function Wt(){if("undefined"!=typeof this._isDSTShifted)return this._isDSTShifted;var t={};if(p(t,this),t=Et(t),t._a){var e=t._isUTC?s(t._a):Dt(t._a);this._isDSTShifted=this.isValid()&&m(t._a,e.toArray())>0}else this._isDSTShifted=!1;return this._isDSTShifted}function Kt(){return!this._isUTC}function Jt(){return this._isUTC}function $t(){return this._isUTC&&0===this._offset}function Zt(t,e){var n,r,i,o=t,a=null;return Lt(t)?o={ms:t._milliseconds,d:t._days,M:t._months}:"number"==typeof t?(o={},e?o[e]=t:o.milliseconds=t):(a=jr.exec(t))?(n="-"===a[1]?-1:1,o={y:0,d:y(a[cr])*n,h:y(a[lr])*n,m:y(a[fr])*n,s:y(a[dr])*n,ms:y(a[pr])*n}):(a=Er.exec(t))?(n="-"===a[1]?-1:1,o={y:Xt(a[2],n),M:Xt(a[3],n),d:Xt(a[4],n),h:Xt(a[5],n),m:Xt(a[6],n),s:Xt(a[7],n),w:Xt(a[8],n)}):null==o?o={}:"object"==typeof o&&("from"in o||"to"in o)&&(i=te(Dt(o.from),Dt(o.to)),o={},o.ms=i.milliseconds,o.M=i.months),r=new kt(o),Lt(t)&&u(t,"_locale")&&(r._locale=t._locale),r}function Xt(t,e){var n=t&&parseFloat(t.replace(",","."));return(isNaN(n)?0:n)*e}function Qt(t,e){var n={milliseconds:0,months:0};return n.months=e.month()-t.month()+12*(e.year()-t.year()),t.clone().add(n.months,"M").isAfter(e)&&--n.months,n.milliseconds=+e-+t.clone().add(n.months,"M"),n}function te(t,e){var n;return e=zt(e,t),t.isBefore(e)?n=Qt(t,e):(n=Qt(e,t),n.milliseconds=-n.milliseconds,n.months=-n.months),n}function ee(t,e){return function(n,r){var i,o;return null===r||isNaN(+r)||(nt(e,"moment()."+e+"(period, number) is deprecated. Please use moment()."+e+"(number, period)."),o=n,n=r,r=o),n="string"==typeof n?+n:n,i=Zt(n,r),ne(this,i,t),this}}function ne(t,n,r,i){var o=n._milliseconds,u=n._days,a=n._months;i=null==i?!0:i,o&&t._d.setTime(+t._d+o*r),u&&C(t,"Date",D(t,"Date")+u*r),a&&$(t,D(t,"Month")+a*r),i&&e.updateOffset(t,u||a)}function re(t,e){var n=t||Dt(),r=zt(n,this).startOf("day"),i=this.diff(r,"days",!0),o=-6>i?"sameElse":-1>i?"lastWeek":0>i?"lastDay":1>i?"sameDay":2>i?"nextDay":7>i?"nextWeek":"sameElse";return this.format(e&&e[o]||this.localeData().calendar(o,this,Dt(n)))}function ie(){return new h(this)}function oe(t,e){var n;return e=E("undefined"!=typeof e?e:"millisecond"),"millisecond"===e?(t=v(t)?t:Dt(t),+this>+t):(n=v(t)?+t:+Dt(t),n<+this.clone().startOf(e))}function ue(t,e){var n;return e=E("undefined"!=typeof e?e:"millisecond"),"millisecond"===e?(t=v(t)?t:Dt(t),+t>+this):(n=v(t)?+t:+Dt(t),+this.clone().endOf(e)e-o?(n=t.clone().add(i-1,"months"),r=(e-o)/(o-n)):(n=t.clone().add(i+1,"months"),r=(e-o)/(n-o)),-(i+r)}function fe(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")}function de(){var t=this.clone().utc();return 0e;e++)if(this._weekdaysParse[e]||(n=Dt([2e3,1]).day(e),r="^"+this.weekdays(n,"")+"|^"+this.weekdaysShort(n,"")+"|^"+this.weekdaysMin(n,""),this._weekdaysParse[e]=new RegExp(r.replace(".",""),"i")),this._weekdaysParse[e].test(t))return e}function Ue(t){var e=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=t?(t=Re(t,this.localeData()),this.add(t-e,"d")):e}function Fe(t){var e=(this.day()+7-this.localeData()._week.dow)%7;return null==t?e:this.add(t-e,"d")}function Be(t){return null==t?this.day()||7:this.day(this.day()%7?t:t-7)}function Ve(t,e){k(t,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),e)})}function qe(t,e){return e._meridiemParse}function We(t){return"p"===(t+"").toLowerCase().charAt(0)}function Ke(t,e,n){return t>11?n?"pm":"PM":n?"am":"AM"}function Je(t,e){e[pr]=y(1e3*("0."+t))}function $e(){return this._isUTC?"UTC":""}function Ze(){return this._isUTC?"Coordinated Universal Time":""}function Xe(t){return Dt(1e3*t)}function Qe(){return Dt.apply(null,arguments).parseZone()}function tn(t,e,n){var r=this._calendar[t];return"function"==typeof r?r.call(e,n):r}function en(t){var e=this._longDateFormat[t],n=this._longDateFormat[t.toUpperCase()];return e||!n?e:(this._longDateFormat[t]=n.replace(/MMMM|MM|DD|dddd/g,function(t){return t.slice(1)}),this._longDateFormat[t])}function nn(){return this._invalidDate}function rn(t){return this._ordinal.replace("%d",t)}function on(t){return t}function un(t,e,n,r){var i=this._relativeTime[n];return"function"==typeof i?i(t,e,n,r):i.replace(/%d/i,t)}function an(t,e){var n=this._relativeTime[t>0?"future":"past"];return"function"==typeof n?n(e):n.replace(/%s/i,e)}function sn(t){var e,n;for(n in t)e=t[n],"function"==typeof e?this[n]=e:this["_"+n]=e;this._ordinalParseLenient=new RegExp(this._ordinalParse.source+"|"+/\d{1,2}/.source)}function cn(t,e,n,r){var i=T(),o=s().set(r,e);return i[n](o,t)}function ln(t,e,n,r,i){if("number"==typeof t&&(e=t,t=void 0),t=t||"",null!=e)return cn(t,e,n,i);var o,u=[];for(o=0;r>o;o++)u[o]=cn(t,o,n,i);return u}function fn(t,e){return ln(t,e,"months",12,"month")}function dn(t,e){return ln(t,e,"monthsShort",12,"month")}function pn(t,e){return ln(t,e,"weekdays",7,"day")}function hn(t,e){return ln(t,e,"weekdaysShort",7,"day")}function vn(t,e){return ln(t,e,"weekdaysMin",7,"day")}function _n(){var t=this._data;return this._milliseconds=$r(this._milliseconds),this._days=$r(this._days),this._months=$r(this._months),t.milliseconds=$r(t.milliseconds),t.seconds=$r(t.seconds),t.minutes=$r(t.minutes),t.hours=$r(t.hours),t.months=$r(t.months),t.years=$r(t.years),this}function yn(t,e,n,r){var i=Zt(e,n);return t._milliseconds+=r*i._milliseconds,t._days+=r*i._days,t._months+=r*i._months,t._bubble()}function mn(t,e){return yn(this,t,e,1)}function gn(t,e){return yn(this,t,e,-1)}function bn(t){return 0>t?Math.floor(t):Math.ceil(t)}function On(){var t,e,n,r,i,o=this._milliseconds,u=this._days,a=this._months,s=this._data;return o>=0&&u>=0&&a>=0||0>=o&&0>=u&&0>=a||(o+=864e5*bn(Sn(a)+u),u=0,a=0),s.milliseconds=o%1e3,t=_(o/1e3),s.seconds=t%60,e=_(t/60),s.minutes=e%60,n=_(e/60),s.hours=n%24,u+=_(n/24),i=_(wn(u)),a+=i,u-=bn(Sn(i)),r=_(a/12),a%=12,s.days=u,s.months=a,s.years=r,this}function wn(t){return 4800*t/146097}function Sn(t){return 146097*t/4800}function Mn(t){var e,n,r=this._milliseconds;if(t=E(t),"month"===t||"year"===t)return e=this._days+r/864e5,n=this._months+wn(e),"month"===t?n:n/12;switch(e=this._days+Math.round(Sn(this._months)),t){case"week":return e/7+r/6048e5;case"day":return e+r/864e5;case"hour":return 24*e+r/36e5;case"minute":return 1440*e+r/6e4;case"second":return 86400*e+r/1e3;case"millisecond":return Math.floor(864e5*e)+r;default:throw new Error("Unknown unit "+t)}}function Tn(){return this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*y(this._months/12)}function jn(t){return function(){return this.as(t)}}function En(t){return t=E(t),this[t+"s"]()}function In(t){return function(){return this._data[t]}}function Pn(){return _(this.days()/7)}function Dn(t,e,n,r,i){return i.relativeTime(e||1,!!n,t,r)}function Cn(t,e,n){var r=Zt(t).abs(),i=di(r.as("s")),o=di(r.as("m")),u=di(r.as("h")),a=di(r.as("d")),s=di(r.as("M")),c=di(r.as("y")),l=i0,l[4]=n,Dn.apply(null,l)}function An(t,e){return void 0===pi[t]?!1:void 0===e?pi[t]:(pi[t]=e,!0)}function xn(t){var e=this.localeData(),n=Cn(this,!t,e);return t&&(n=e.pastFuture(+this,n)),e.postformat(n)}function kn(){var t,e,n,r=hi(this._milliseconds)/1e3,i=hi(this._days),o=hi(this._months);t=_(r/60),e=_(t/60),r%=60,t%=60,n=_(o/12),o%=12;var u=n,a=o,s=i,c=e,l=t,f=r,d=this.asSeconds();return d?(0>d?"-":"")+"P"+(u?u+"Y":"")+(a?a+"M":"")+(s?s+"D":"")+(c||l||f?"T":"")+(c?c+"H":"")+(l?l+"M":"")+(f?f+"S":""):"P0D"}var Ln,Nn,Rn=e.momentProperties=[],zn=!1,Hn={},Yn={},Gn=/(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Q|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,Un=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,Fn={},Bn={},Vn=/\d/,qn=/\d\d/,Wn=/\d{3}/,Kn=/\d{4}/,Jn=/[+-]?\d{6}/,$n=/\d\d?/,Zn=/\d{1,3}/,Xn=/\d{1,4}/,Qn=/[+-]?\d{1,6}/,tr=/\d+/,er=/[+-]?\d+/,nr=/Z|[+-]\d\d:?\d\d/gi,rr=/[+-]?\d+(\.\d{1,3})?/,ir=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,or={},ur={},ar=0,sr=1,cr=2,lr=3,fr=4,dr=5,pr=6;k("M",["MM",2],"Mo",function(){return this.month()+1}),k("MMM",0,0,function(t){return this.localeData().monthsShort(this,t)}),k("MMMM",0,0,function(t){return this.localeData().months(this,t)}),j("month","M"),Y("M",$n),Y("MM",$n,qn),Y("MMM",ir),Y("MMMM",ir),F(["M","MM"],function(t,e){e[sr]=y(t)-1}),F(["MMM","MMMM"],function(t,e,n,r){var i=n._locale.monthsParse(t,r,n._strict);null!=i?e[sr]=i:l(n).invalidMonth=t});var hr="January_February_March_April_May_June_July_August_September_October_November_December".split("_"),vr="Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),_r={};e.suppressDeprecationWarnings=!1;var yr=/^\s*(?:[+-]\d{6}|\d{4})-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,mr=[["YYYYYY-MM-DD",/[+-]\d{6}-\d{2}-\d{2}/],["YYYY-MM-DD",/\d{4}-\d{2}-\d{2}/],["GGGG-[W]WW-E",/\d{4}-W\d{2}-\d/],["GGGG-[W]WW",/\d{4}-W\d{2}/],["YYYY-DDD",/\d{4}-\d{3}/]],gr=[["HH:mm:ss.SSSS",/(T| )\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss",/(T| )\d\d:\d\d:\d\d/],["HH:mm",/(T| )\d\d:\d\d/],["HH",/(T| )\d\d/]],br=/^\/?Date\((\-?\d+)/i;e.createFromInputFallback=et("moment construction falls back to js Date. This is discouraged and will be removed in upcoming major release. Please refer to https://github.com/moment/moment/issues/1407 for more info.",function(t){t._d=new Date(t._i+(t._useUTC?" UTC":""))}),k(0,["YY",2],0,function(){return this.year()%100}),k(0,["YYYY",4],0,"year"),k(0,["YYYYY",5],0,"year"),k(0,["YYYYYY",6,!0],0,"year"),j("year","y"),Y("Y",er),Y("YY",$n,qn),Y("YYYY",Xn,Kn),Y("YYYYY",Qn,Jn),Y("YYYYYY",Qn,Jn),F(["YYYYY","YYYYYY"],ar),F("YYYY",function(t,n){n[ar]=2===t.length?e.parseTwoDigitYear(t):y(t)}),F("YY",function(t,n){n[ar]=e.parseTwoDigitYear(t)}),e.parseTwoDigitYear=function(t){return y(t)+(y(t)>68?1900:2e3)};var Or=P("FullYear",!1);k("w",["ww",2],"wo","week"),k("W",["WW",2],"Wo","isoWeek"),j("week","w"),j("isoWeek","W"),Y("w",$n),Y("ww",$n,qn),Y("W",$n),Y("WW",$n,qn),B(["w","ww","W","WW"],function(t,e,n,r){e[r.substr(0,1)]=y(t)});var wr={dow:0,doy:6};k("DDD",["DDDD",3],"DDDo","dayOfYear"),j("dayOfYear","DDD"),Y("DDD",Zn),Y("DDDD",Wn),F(["DDD","DDDD"],function(t,e,n){n._dayOfYear=y(t)}),e.ISO_8601=function(){};var Sr=et("moment().min is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548",function(){var t=Dt.apply(null,arguments);return this>t?this:t}),Mr=et("moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548",function(){var t=Dt.apply(null,arguments);return t>this?this:t});Nt("Z",":"),Nt("ZZ",""),Y("Z",nr),Y("ZZ",nr),F(["Z","ZZ"],function(t,e,n){n._useUTC=!0,n._tzm=Rt(t)});var Tr=/([\+\-]|\d\d)/gi;e.updateOffset=function(){};var jr=/(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/,Er=/^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/;Zt.fn=kt.prototype;var Ir=ee(1,"add"),Pr=ee(-1,"subtract");e.defaultFormat="YYYY-MM-DDTHH:mm:ssZ";var Dr=et("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(t){return void 0===t?this.localeData():this.locale(t)});k(0,["gg",2],0,function(){return this.weekYear()%100}),k(0,["GG",2],0,function(){return this.isoWeekYear()%100}),De("gggg","weekYear"),De("ggggg","weekYear"),De("GGGG","isoWeekYear"),De("GGGGG","isoWeekYear"),j("weekYear","gg"),j("isoWeekYear","GG"),Y("G",er),Y("g",er),Y("GG",$n,qn),Y("gg",$n,qn),Y("GGGG",Xn,Kn),Y("gggg",Xn,Kn),Y("GGGGG",Qn,Jn),Y("ggggg",Qn,Jn),B(["gggg","ggggg","GGGG","GGGGG"],function(t,e,n,r){e[r.substr(0,2)]=y(t)}),B(["gg","GG"],function(t,n,r,i){n[i]=e.parseTwoDigitYear(t)}),k("Q",0,0,"quarter"),j("quarter","Q"),Y("Q",Vn),F("Q",function(t,e){e[sr]=3*(y(t)-1)}),k("D",["DD",2],"Do","date"),j("date","D"),Y("D",$n),Y("DD",$n,qn),Y("Do",function(t,e){return t?e._ordinalParse:e._ordinalParseLenient}),F(["D","DD"],cr),F("Do",function(t,e){e[cr]=y(t.match($n)[0],10)});var Cr=P("Date",!0);k("d",0,"do","day"),k("dd",0,0,function(t){return this.localeData().weekdaysMin(this,t)}),k("ddd",0,0,function(t){return this.localeData().weekdaysShort(this,t)}),k("dddd",0,0,function(t){return this.localeData().weekdays(this,t)}),k("e",0,0,"weekday"),k("E",0,0,"isoWeekday"),j("day","d"),j("weekday","e"),j("isoWeekday","E"),Y("d",$n),Y("e",$n),Y("E",$n),Y("dd",ir),Y("ddd",ir),Y("dddd",ir),B(["dd","ddd","dddd"],function(t,e,n){var r=n._locale.weekdaysParse(t);null!=r?e.d=r:l(n).invalidWeekday=t}),B(["d","e","E"],function(t,e,n,r){e[r]=y(t)});var Ar="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),xr="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),kr="Su_Mo_Tu_We_Th_Fr_Sa".split("_");k("H",["HH",2],0,"hour"),k("h",["hh",2],0,function(){return this.hours()%12||12}),Ve("a",!0),Ve("A",!1),j("hour","h"),Y("a",qe),Y("A",qe),Y("H",$n),Y("h",$n),Y("HH",$n,qn),Y("hh",$n,qn),F(["H","HH"],lr),F(["a","A"],function(t,e,n){n._isPm=n._locale.isPM(t),n._meridiem=t}),F(["h","hh"],function(t,e,n){e[lr]=y(t),l(n).bigHour=!0});var Lr=/[ap]\.?m?\.?/i,Nr=P("Hours",!0);k("m",["mm",2],0,"minute"),j("minute","m"),Y("m",$n),Y("mm",$n,qn),F(["m","mm"],fr);var Rr=P("Minutes",!1);k("s",["ss",2],0,"second"),j("second","s"),Y("s",$n),Y("ss",$n,qn),F(["s","ss"],dr);var zr=P("Seconds",!1);k("S",0,0,function(){return~~(this.millisecond()/100)}),k(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),k(0,["SSS",3],0,"millisecond"),k(0,["SSSS",4],0,function(){return 10*this.millisecond()}),k(0,["SSSSS",5],0,function(){return 100*this.millisecond()}),k(0,["SSSSSS",6],0,function(){return 1e3*this.millisecond()}),k(0,["SSSSSSS",7],0,function(){return 1e4*this.millisecond()}),k(0,["SSSSSSSS",8],0,function(){return 1e5*this.millisecond()}),k(0,["SSSSSSSSS",9],0,function(){return 1e6*this.millisecond()}),j("millisecond","ms"),Y("S",Zn,Vn),Y("SS",Zn,qn),Y("SSS",Zn,Wn);var Hr;for(Hr="SSSS";Hr.length<=9;Hr+="S")Y(Hr,tr);for(Hr="S";Hr.length<=9;Hr+="S")F(Hr,Je);var Yr=P("Milliseconds",!1);k("z",0,0,"zoneAbbr"),k("zz",0,0,"zoneName");var Gr=h.prototype;Gr.add=Ir,Gr.calendar=re,Gr.clone=ie,Gr.diff=ce,Gr.endOf=Oe,Gr.format=pe,Gr.from=he,Gr.fromNow=ve,Gr.to=_e,Gr.toNow=ye,Gr.get=A,Gr.invalidAt=Pe,Gr.isAfter=oe,Gr.isBefore=ue,Gr.isBetween=ae,Gr.isSame=se,Gr.isValid=Ee,Gr.lang=Dr,Gr.locale=me,Gr.localeData=ge,Gr.max=Mr,Gr.min=Sr,Gr.parsingFlags=Ie,Gr.set=A,Gr.startOf=be,Gr.subtract=Pr,Gr.toArray=Te,Gr.toObject=je,Gr.toDate=Me,Gr.toISOString=de,Gr.toJSON=de,Gr.toString=fe,Gr.unix=Se,Gr.valueOf=we,Gr.year=Or,Gr.isLeapYear=ct,Gr.weekYear=Ae,Gr.isoWeekYear=xe,Gr.quarter=Gr.quarters=Ne,Gr.month=Z,Gr.daysInMonth=X,Gr.week=Gr.weeks=ht,Gr.isoWeek=Gr.isoWeeks=vt,Gr.weeksInYear=Le,Gr.isoWeeksInYear=ke,Gr.date=Cr,Gr.day=Gr.days=Ue,Gr.weekday=Fe,Gr.isoWeekday=Be,Gr.dayOfYear=yt,Gr.hour=Gr.hours=Nr,Gr.minute=Gr.minutes=Rr,Gr.second=Gr.seconds=zr,Gr.millisecond=Gr.milliseconds=Yr,Gr.utcOffset=Yt,Gr.utc=Ut,Gr.local=Ft,Gr.parseZone=Bt,Gr.hasAlignedHourOffset=Vt,Gr.isDST=qt,Gr.isDSTShifted=Wt,Gr.isLocal=Kt,Gr.isUtcOffset=Jt,Gr.isUtc=$t,Gr.isUTC=$t,Gr.zoneAbbr=$e,Gr.zoneName=Ze,Gr.dates=et("dates accessor is deprecated. Use date instead.",Cr),Gr.months=et("months accessor is deprecated. Use month instead",Z),Gr.years=et("years accessor is deprecated. Use year instead",Or),Gr.zone=et("moment().zone is deprecated, use moment().utcOffset instead. https://github.com/moment/moment/issues/1779",Gt);var Ur=Gr,Fr={sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},Br={LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},Vr="Invalid date",qr="%d",Wr=/\d{1,2}/,Kr={future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},Jr=g.prototype;Jr._calendar=Fr,Jr.calendar=tn,Jr._longDateFormat=Br,Jr.longDateFormat=en,Jr._invalidDate=Vr,Jr.invalidDate=nn,Jr._ordinal=qr,Jr.ordinal=rn,Jr._ordinalParse=Wr,Jr.preparse=on,Jr.postformat=on,Jr._relativeTime=Kr,Jr.relativeTime=un,Jr.pastFuture=an,Jr.set=sn,Jr.months=W,Jr._months=hr,Jr.monthsShort=K,Jr._monthsShort=vr,Jr.monthsParse=J,Jr.week=ft,Jr._week=wr,Jr.firstDayOfYear=pt,Jr.firstDayOfWeek=dt,Jr.weekdays=ze,Jr._weekdays=Ar,Jr.weekdaysMin=Ye,Jr._weekdaysMin=kr,Jr.weekdaysShort=He,Jr._weekdaysShort=xr,Jr.weekdaysParse=Ge,Jr.isPM=We,Jr._meridiemParse=Lr,Jr.meridiem=Ke,S("en",{ordinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(t){var e=t%10,n=1===y(t%100/10)?"th":1===e?"st":2===e?"nd":3===e?"rd":"th";return t+n}}),e.lang=et("moment.lang is deprecated. Use moment.locale instead.",S),e.langData=et("moment.langData is deprecated. Use moment.localeData instead.",T);var $r=Math.abs,Zr=jn("ms"),Xr=jn("s"),Qr=jn("m"),ti=jn("h"),ei=jn("d"),ni=jn("w"),ri=jn("M"),ii=jn("y"),oi=In("milliseconds"),ui=In("seconds"),ai=In("minutes"),si=In("hours"),ci=In("days"),li=In("months"),fi=In("years"),di=Math.round,pi={s:45,m:45,h:22,d:26,M:11},hi=Math.abs,vi=kt.prototype;vi.abs=_n,vi.add=mn,vi.subtract=gn,vi.as=Mn,vi.asMilliseconds=Zr,vi.asSeconds=Xr,vi.asMinutes=Qr,vi.asHours=ti,vi.asDays=ei,vi.asWeeks=ni,vi.asMonths=ri,vi.asYears=ii,vi.valueOf=Tn,vi._bubble=On,vi.get=En,vi.milliseconds=oi,vi.seconds=ui,vi.minutes=ai,vi.hours=si,vi.days=ci,vi.weeks=Pn,vi.months=li,vi.years=fi,vi.humanize=xn,vi.toISOString=kn,vi.toString=kn,vi.toJSON=kn,vi.locale=me,vi.localeData=ge,vi.toIsoString=et("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",kn),vi.lang=Dr,k("X",0,0,"unix"),k("x",0,0,"valueOf"),Y("x",er),Y("X",rr),F("X",function(t,e,n){n._d=new Date(1e3*parseFloat(t,10))}),F("x",function(t,e,n){n._d=new Date(y(t))}),e.version="2.10.6",n(Dt),e.fn=Ur,e.min=At,e.max=xt,e.utc=s,e.unix=Xe,e.months=fn,e.isDate=i,e.locale=S,e.invalid=d,e.duration=Zt,e.isMoment=v,e.weekdays=pn,e.parseZone=Qe,e.localeData=T,e.isDuration=Lt,e.monthsShort=dn,e.weekdaysMin=vn,e.defineLocale=M,e.weekdaysShort=hn,e.normalizeUnits=E,e.relativeTimeThreshold=An;var _i=e;return _i})}).call(e,n(130)(t))},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);e["default"]=new o["default"]({is:"ha-card",properties:{title:{type:String},header:{type:String}}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);e["default"]=new o["default"]({is:"ha-label-badge",properties:{value:{type:String},icon:{type:String},label:{type:String},description:{type:String},image:{type:String,observe:"imageChanged"}},computeClasses:function(t){return t&&t.length>5?"value big":"value"}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(55),o=r(i),u=n(2),a=n(1),s=r(a),c=6e4,l=u.util.parseDateTime;e["default"]=new s["default"]({is:"relative-ha-datetime",properties:{datetime:{type:String,observer:"datetimeChanged"},datetimeObj:{type:Object,observer:"datetimeObjChanged"},parsedDateTime:{type:Object},relativeTime:{type:String,value:"not set"}},created:function(){this.updateRelative=this.updateRelative.bind(this)},attached:function(){this._interval=setInterval(this.updateRelative,c)},detached:function(){clearInterval(this._interval)},datetimeChanged:function(t){this.parsedDateTime=t?l(t):null,this.updateRelative()},datetimeObjChanged:function(t){this.parsedDateTime=t,this.updateRelative()},updateRelative:function(){this.relativeTime=this.parsedDateTime?o["default"](this.parsedDateTime).fromNow():""}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(30),n(148),n(147),e["default"]=new o["default"]({is:"state-history-charts",properties:{stateHistory:{type:Object},isLoadingData:{type:Boolean,value:!1},apiLoaded:{type:Boolean,value:!1},isLoading:{type:Boolean,computed:"computeIsLoading(isLoadingData, apiLoaded)"},groupedStateHistory:{type:Object,computed:"computeGroupedStateHistory(isLoading, stateHistory)"},isSingleDevice:{type:Boolean,computed:"computeIsSingleDevice(stateHistory)"}},computeIsSingleDevice:function(t){return t&&1===t.size},computeGroupedStateHistory:function(t,e){if(t||!e)return{line:[],timeline:[]};var n={},r=[];e.forEach(function(t){if(t&&0!==t.size){var e=t.find(function(t){return"unit_of_measurement"in t.attributes}),i=e?e.attributes.unit_of_measurement:!1;i?i in n?n[i].push(t.toArray()):n[i]=[t.toArray()]:r.push(t.toArray())}}),r=r.length>0&&r;var i=Object.keys(n).map(function(t){return[t,n[t]]});return{line:i,timeline:r}},googleApiLoaded:function(){var t=this;google.load("visualization","1",{packages:["timeline","corechart"],callback:function(){return t.apiLoaded=!0}})},computeContentClasses:function(t){return t?"loading":""},computeIsLoading:function(t,e){return t||!e},computeIsEmpty:function(t){return t&&0===t.size},extractUnit:function(t){return t[0]},extractData:function(t){return t[1]}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o);n(17),e["default"]=new u["default"]({is:"state-card-toggle",properties:{stateObj:{type:Object,observer:"stateObjChanged"},toggleChecked:{type:Boolean,value:!1}},ready:function(){this.forceStateChange()},toggleTapped:function(t){t.stopPropagation()},toggleChanged:function(t){var e=t.target.checked;e&&"off"===this.stateObj.state?this.turn_on():e||"on"!==this.stateObj.state||this.turn_off()},stateObjChanged:function(t){t&&this.updateToggle(t)},updateToggle:function(t){this.toggleChecked=t&&"on"===t.state},forceStateChange:function(){this.updateToggle(this.stateObj)},turn_on:function(){var t=this;i.serviceActions.callTurnOn(this.stateObj.entityId).then(function(){return t.forceStateChange()})},turn_off:function(){var t=this;i.serviceActions.callTurnOff(this.stateObj.entityId).then(function(){return t.forceStateChange()})}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return i.reactor.evaluate(i.serviceGetters.canToggleEntity(t))}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=r;var i=n(2);t.exports=e["default"]},function(t,e){"use strict";function n(t,e){switch(t){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 n="hardware:cast";return e&&"off"!==e&&"idle"!==e&&(n+="-connected"),n;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";case"updater":return"update_available"===e?"icons:cloud-download":"icons:cloud-done";default:return"bookmark"}}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=n,t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){return u["default"](t).format("LT")}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=i;var o=n(55),u=r(o);t.exports=e["default"]},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(2);e["default"]=function(t,e){r.authActions.validate(t,{rememberAuth:e,useStreaming:r.localStoragePreferences.useStreaming})},t.exports=e["default"]},function(t,e,n){"use strict";function r(t,e){var n=null==t?void 0:t[e];return i(n)?n:void 0}var i=n(183);t.exports=r},function(t,e){"use strict";function n(t){return!!t&&"object"==typeof t}t.exports=n},function(t,e,n){"use strict";function r(t){return i(t)&&a.call(t)==o}var i=n(68),o="[object Function]",u=Object.prototype,a=u.toString; +t.exports=r},function(t,e){"use strict";function n(t){var e=typeof t;return!!t&&("object"==e||"function"==e)}t.exports=n},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(3),i=["isLoadingEntityHistory"];e.isLoadingEntityHistory=i;var o=["currentEntityHistoryDate"];e.currentDate=o;var u=["entityHistory"];e.entityHistoryMap=u;var a=[o,u,function(t,e){return e.get(t)||r.toImmutable({})}];e.entityHistoryForCurrentDate=a;var s=[o,u,function(t,e){return!!e.get(t)}];e.hasDataForCurrentDate=s;var c=["recentEntityHistory"];e.recentEntityHistoryMap=c;var l=["recentEntityHistory"];e.recentEntityHistoryUpdatedMap=l},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}function o(t){t.registerStores({currentEntityHistoryDate:a["default"],entityHistory:c["default"],isLoadingEntityHistory:f["default"],recentEntityHistory:p["default"],recentEntityHistoryUpdated:v["default"]})}Object.defineProperty(e,"__esModule",{value:!0}),e.register=o;var u=n(195),a=i(u),s=n(196),c=i(s),l=n(197),f=i(l),d=n(198),p=i(d),h=n(199),v=i(h),_=n(194),y=r(_),m=n(69),g=r(m),b=y;e.actions=b;var O=g;e.getters=O},function(t,e,n){"use strict";function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function i(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}Object.defineProperty(e,"__esModule",{value:!0});var o=function(){function t(t,e){for(var n=0;n6e4}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=n,t.exports=e["default"]},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}function o(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(e,"__esModule",{value:!0});var u=n(221),a=n(241),s=i(a),c=n(243),l=i(c),f=n(245),d=i(f),p=n(22),h=r(p),v=n(36),_=r(v),y=n(10),m=r(y),g=n(70),b=r(g),O=n(37),w=r(O),S=n(206),M=r(S),T=n(73),j=r(T),E=n(76),I=r(E),P=n(39),D=r(P),C=n(19),A=r(C),x=n(40),k=r(x),L=n(42),N=r(L),R=n(238),z=r(R),H=n(11),Y=r(H),G=function U(){o(this,U);var t=s["default"]();Object.defineProperties(this,{demo:{value:!1,enumerable:!0},localStoragePreferences:{value:u.localStoragePreferences,enumerable:!0},reactor:{value:t,enumerable:!0},util:{value:d["default"],enumerable:!0},startLocalStoragePreferencesSync:{value:u.localStoragePreferences.startSync.bind(u.localStoragePreferences,t)},startUrlSync:{value:I.urlSync.startSync.bind(null,t)},stopUrlSync:{value:I.urlSync.stopSync.bind(null,t)}}),l["default"](this,t,{auth:h,config:_,entity:m,entityHistory:b,event:w,logbook:M,moreInfo:j,navigation:I,notification:D,service:A,stream:k,sync:N,voice:z,restApi:Y})};e["default"]=G,t.exports=e["default"]},function(t,e,n){"use strict";function r(t,e,n){var r=t?t.length:0;return n&&o(t,e,n)&&(e=!1),r?i(t,e):[]}var i=n(98),o=n(26);t.exports=r},function(t,e){"use strict";function n(t){var e=t?t.length:0;return e?t[e-1]:void 0}t.exports=n},function(t,e,n){"use strict";function r(t,e,n,r){var s=t?t.length:0;return s?(null!=e&&"boolean"!=typeof e&&(r=n,n=u(t,e,r)?void 0:e,e=!1),n=null==n?n:i(n,r,3),e?a(t,n):o(t,n)):[]}var i=n(24),o=n(110),u=n(26),a=n(124);t.exports=r},function(t,e,n){"use strict";function r(t,e,n){var r=a(t)?i:u;return e=o(e,n,3),r(t,e)}var i=n(93),o=n(24),u=n(47),a=n(9);t.exports=r},function(t,e,n){"use strict";function r(t,e){return i(t,o(e))}var i=n(89),o=n(54);t.exports=r},function(t,e,n){"use strict";function r(t,e,n){if(null==t)return[];n&&s(t,e,n)&&(e=void 0);var r=-1;e=i(e,n,3);var c=o(t,function(t,n,i){return{criteria:e(t,n,i),index:++r,value:t}});return u(c,a)}var i=n(24),o=n(47),u=n(108),a=n(114),s=n(26);t.exports=r},function(t,e,n){(function(e){"use strict";function r(t){var e=t?t.length:0;for(this.data={hash:a(null),set:new u};e--;)this.push(t[e])}var i=n(113),o=n(20),u=o(e,"Set"),a=o(Object,"create");r.prototype.push=i,t.exports=r}).call(e,function(){return this}())},function(t,e){"use strict";function n(t,e){for(var n=-1,r=t.length,i=Array(r);++ne&&!o||!i||n&&!u&&a||r&&a)return 1;if(e>t&&!n||!a||o&&!r&&i||u&&i)return-1}return 0}t.exports=n},function(t,e,n){"use strict";var r=n(100),i=n(115),o=i(r);t.exports=o},function(t,e,n){"use strict";function r(t,e,n,c){c||(c=[]);for(var l=-1,f=t.length;++le&&(e=-e>i?0:i+e),n=void 0===n||n>i?i:+n||0,0>n&&(n+=i),i=e>n?0:n-e>>>0,e>>>=0;for(var o=Array(i);++r=a,f=l?u():null,d=[];f?(r=o,c=!1):(l=!1,f=e?[]:d);t:for(;++nc))return!1;for(;++s0;++rd;d++)f._columns[d]=[];var p=0;return n&&u(),c.keySeq().sortBy(function(t){return i(t)}).forEach(function(t){if("a"===t)return void(f._demo=!0);var n=i(t);n>=0&&10>n?f._badges.push.apply(f._badges,r(c.get(t)).sortBy(o).toArray()):"group"===t?c.get(t).filter(function(t){return!t.attributes.auto}).sortBy(o).forEach(function(t){var n=s.util.expandGroup(t,e);n.forEach(function(t){return l[t.entityId]=!0}),a(t.entityDisplay,n.toArray())}):a(t,r(c.get(t)).sortBy(o).toArray())}),f},computeShowIntroduction:function(t,e){return t||e._demo},computeShowHideInstruction:function(t,e){return t.size>0&&!0&&!e._demo},computeStatesOfCard:function(t,e){return t[e]}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o);n(29),n(134),n(58),e["default"]=new u["default"]({is:"logbook-entry", +entityClicked:function(t){t.preventDefault(),i.moreInfoActions.selectEntity(this.entryObj.entityId)}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o),a=n(4),s=r(a);n(29),e["default"]=new u["default"]({is:"services-list",behaviors:[s["default"]],properties:{serviceDomains:{type:Array,bindNuclear:[i.serviceGetters.entityMap,function(t){return t.valueSeq().sortBy(function(t){return t.domain}).toJS()}]}},computeServices:function(t){return this.services.get(t).toArray()},serviceClicked:function(t){t.preventDefault(),this.fire("service-selected",{domain:t.model.domain.domain,service:t.model.service})}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(90),o=r(i),u=n(86),a=r(u),s=n(88),c=r(s),l=n(91),f=r(l),d=n(1),p=r(d);e["default"]=new p["default"]({is:"state-history-chart-line",properties:{data:{type:Object,observer:"dataChanged"},unit:{type:String},isSingleDevice:{type:Boolean,value:!1},isAttached:{type:Boolean,value:!1,observer:"dataChanged"}},created:function(){this.style.display="block"},attached:function(){this.isAttached=!0},dataChanged:function(){this.drawChart()},drawChart:function(){if(this.isAttached){for(var t=p["default"].dom(this),e=this.unit,n=this.data;t.lastChild;)t.removeChild(t.lastChild);if(0!==n.length){var r=new google.visualization.LineChart(this),i=new google.visualization.DataTable;i.addColumn({type:"datetime",id:"Time"});var u={legend:{position:"top"},titlePosition:"none",vAxes:{0:{title:e}},hAxis:{format:"H:mm"},lineWidth:1,chartArea:{left:"60",width:"95%"},explorer:{actions:["dragToZoom","rightClickToReset","dragToPan"],keepInBounds:!0,axis:"horizontal",maxZoomIn:.1}};this.isSingleDevice&&(u.legend.position="none",u.vAxes[0].title=null,u.chartArea.left=40,u.chartArea.height="80%",u.chartArea.top=5,u.enableInteractivity=!1);var s=o["default"](a["default"](n),"lastChangedAsDate");s=f["default"](c["default"](s,function(t){return t.getTime()}));for(var l=[],d=new Array(n.length),h=0;hnew Date&&(a=new Date);var s=0;n.forEach(function(e){if(0!==e.length){var n=e[0].entityDisplay,r=void 0,i=null,o=null;e.forEach(function(e){null!==i&&e.state!==i?(r=e.lastChangedAsDate,t(n,i,o,r),i=e.state,o=r):null===i&&(i=e.state,o=e.lastChangedAsDate)}),t(n,i,o,a),s++}}),r.draw(i,{height:55+42*s,timeline:{showRowLabels:n.length>1},hAxis:{format:"H:mm"}})}}}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o),a=n(4),s=r(a);e["default"]=new u["default"]({is:"stream-status",behaviors:[s["default"]],properties:{isStreaming:{type:Boolean,bindNuclear:i.streamGetters.isStreamingEvents},hasError:{type:Boolean,bindNuclear:i.streamGetters.hasStreamingEventsError}},toggleChanged:function(){this.isStreaming?i.streamActions.stop():i.streamActions.start()}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o),a=n(4),s=r(a);n(31),n(59),n(162);var c=["camera","configurator"];e["default"]=new u["default"]({is:"more-info-dialog",behaviors:[s["default"]],properties:{stateObj:{type:Object,bindNuclear:i.moreInfoGetters.currentEntity,observer:"stateObjChanged"},stateHistory:{type:Object,bindNuclear:[i.moreInfoGetters.currentEntityHistory,function(t){return t?[t]:!1}]},isLoadingHistoryData:{type:Boolean,computed:"computeIsLoadingHistoryData(_delayedDialogOpen, _isLoadingHistoryData)"},_isLoadingHistoryData:{type:Boolean,bindNuclear:i.entityHistoryGetters.isLoadingEntityHistory},hasHistoryComponent:{type:Boolean,bindNuclear:i.configGetters.isComponentLoaded("history"),observer:"fetchHistoryData"},shouldFetchHistory:{type:Boolean,bindNuclear:i.moreInfoGetters.isCurrentEntityHistoryStale,observer:"fetchHistoryData"},showHistoryComponent:{type:Boolean,value:!1},dialogOpen:{type:Boolean,value:!1,observer:"dialogOpenChanged"},_delayedDialogOpen:{type:Boolean,value:!1},_boundOnBackdropTap:{type:Function,value:function(){return this._onBackdropTap.bind(this)}}},computeIsLoadingHistoryData:function(t,e){return!t||e},fetchHistoryData:function(){this.stateObj&&this.hasHistoryComponent&&this.shouldFetchHistory&&i.entityHistoryActions.fetchRecent(this.stateObj.entityId)},stateObjChanged:function(t){var e=this;return t?(this.showHistoryComponent=-1===c.indexOf(this.stateObj.domain)&&this.hasHistoryComponent,void this.async(function(){e.fetchHistoryData(),e.dialogOpen=!0},10)):void(this.dialogOpen=!1)},dialogOpenChanged:function(t){var e=this;t?(this.$.dialog.backdropElement.addEventListener("click",this._boundOnBackdropTap),this.async(function(){return e._delayedDialogOpen=!0},10)):!t&&this.stateObj&&(i.moreInfoActions.deselectEntity(),this._delayedDialogOpen=!1)},_onBackdropTap:function(){this.$.dialog.backdropElement.removeEventListener("click",this._boundOnBackdropTap),this.dialogOpen=!1}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(4),u=r(o);n(142),n(158),n(157),n(156),n(153),n(154),n(155),n(159),n(150),e["default"]=new Polymer({is:"home-assistant-main",behaviors:[u["default"]],properties:{narrow:{type:Boolean,value:!1},activePane:{type:String,bindNuclear:i.navigationGetters.activePane,observer:"activePaneChanged"},isSelectedStates:{type:Boolean,bindNuclear:i.navigationGetters.isActivePane("states")},isSelectedHistory:{type:Boolean,bindNuclear:i.navigationGetters.isActivePane("history")},isSelectedLogbook:{type:Boolean,bindNuclear:i.navigationGetters.isActivePane("logbook")},isSelectedDevEvent:{type:Boolean,bindNuclear:i.navigationGetters.isActivePane("devEvent")},isSelectedDevState:{type:Boolean,bindNuclear:i.navigationGetters.isActivePane("devState")},isSelectedDevService:{type:Boolean,bindNuclear:i.navigationGetters.isActivePane("devService")},showSidebar:{type:Boolean,bindNuclear:i.navigationGetters.showSidebar}},listeners:{"open-menu":"openMenu","close-menu":"closeMenu"},openMenu:function(){this.narrow?this.$.drawer.openDrawer():i.navigationActions.showSidebar(!0)},closeMenu:function(){this.$.drawer.closeDrawer(),this.showSidebar&&i.navigationActions.showSidebar(!1)},activePaneChanged:function(){this.narrow&&this.$.drawer.closeDrawer()},attached:function(){i.startUrlSync()},computeForceNarrow:function(t,e){return t||!e},detached:function(){i.stopUrlSync()}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i),u=n(2),a=n(4),s=r(a),c=n(64),l=r(c);e["default"]=new o["default"]({is:"login-form",behaviors:[s["default"]],properties:{isValidating:{type:Boolean,observer:"isValidatingChanged",bindNuclear:u.authGetters.isValidating},isInvalid:{type:Boolean,bindNuclear:u.authGetters.isInvalidAttempt},errorMessage:{type:String,bindNuclear:u.authGetters.attemptErrorMessage}},listeners:{keydown:"passwordKeyDown","loginButton.click":"validatePassword"},observers:["validatingChanged(isValidating, isInvalid)"],validatingChanged:function(t,e){t||e||(this.$.passwordInput.value="")},isValidatingChanged:function(t){var e=this;t||this.async(function(){return e.$.passwordInput.focus()},10)},passwordKeyDown:function(t){13===t.keyCode?(this.validatePassword(),t.preventDefault()):this.isInvalid&&(this.isInvalid=!1)},validatePassword:function(){this.$.hideKeyboardOnFocus.focus(),l["default"](this.$.passwordInput.value,this.$.rememberLogin.checked)}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o);n(14),n(146),e["default"]=new u["default"]({is:"partial-dev-call-service",properties:{narrow:{type:Boolean,value:!1},showMenu:{type:Boolean,value:!1},domain:{type:String,value:""},service:{type:String,value:""},serviceData:{type:String,value:""}},serviceSelected:function(t){this.domain=t.detail.domain,this.service=t.detail.service},callService:function(){var t=void 0;try{t=this.serviceData?JSON.parse(this.serviceData):{}}catch(e){return void alert("Error parsing JSON: "+e)}i.serviceActions.callService(this.domain,this.service,t)},computeFormClasses:function(t){return"layout "+(t?"vertical":"horizontal")}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o);n(14),n(138),e["default"]=new u["default"]({is:"partial-dev-fire-event",properties:{narrow:{type:Boolean,value:!1},showMenu:{type:Boolean,value:!1},eventType:{type:String,value:""},eventData:{type:String,value:""}},eventSelected:function(t){this.eventType=t.detail.eventType},fireEvent:function(){var t=void 0;try{t=this.eventData?JSON.parse(this.eventData):{}}catch(e){return void alert("Error parsing JSON: "+e)}i.eventActions.fireEvent(this.eventType,t)},computeFormClasses:function(t){return"layout "+(t?"vertical":"horizontal")}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o);n(14),n(135),e["default"]=new u["default"]({is:"partial-dev-set-state",properties:{narrow:{type:Boolean,value:!1},showMenu:{type:Boolean,value:!1},entityId:{type:String,value:""},state:{type:String,value:""},stateAttributes:{type:String,value:""}},setStateData:function(t){var e=t?JSON.stringify(t,null," "):"";this.$.inputData.value=e,this.$.inputDataWrapper.update(this.$.inputData)},entitySelected:function(t){var e=i.reactor.evaluate(i.entityGetters.byId(t.detail.entityId));this.entityId=e.entityId,this.state=e.state,this.stateAttributes=JSON.stringify(e.attributes,null," ")},handleSetState:function(){var t=void 0;try{t=this.stateAttributes?JSON.parse(this.stateAttributes):{}}catch(e){return void alert("Error parsing JSON: "+e)}i.entityActions.save({entityId:this.entityId,state:this.state,attributes:t})},computeFormClasses:function(t){return"layout "+(t?"vertical":"horizontal")}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o),a=n(4),s=r(a);n(14),n(59),e["default"]=new u["default"]({is:"partial-history",behaviors:[s["default"]],properties:{narrow:{type:Boolean},showMenu:{type:Boolean,value:!1},isDataLoaded:{type:Boolean,bindNuclear:i.entityHistoryGetters.hasDataForCurrentDate,observer:"isDataLoadedChanged"},stateHistory:{type:Object,bindNuclear:i.entityHistoryGetters.entityHistoryForCurrentDate},isLoadingData:{type:Boolean,bindNuclear:i.entityHistoryGetters.isLoadingEntityHistory},selectedDate:{type:String,value:null,bindNuclear:i.entityHistoryGetters.currentDate}},isDataLoadedChanged:function(t){t||this.async(function(){return i.entityHistoryActions.fetchSelectedDate()},1)},handleRefreshClick:function(){i.entityHistoryActions.fetchSelectedDate()},datepickerFocus:function(){this.datePicker.adjustPosition()},attached:function(){this.datePicker=new Pikaday({field:this.$.datePicker.inputElement,onSelect:i.entityHistoryActions.changeCurrentDate})},detached:function(){this.datePicker.destroy()},computeContentClasses:function(t){return"flex content "+(t?"narrow":"wide")}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o),a=n(4),s=r(a);n(14),n(141),n(30),e["default"]=new u["default"]({is:"partial-logbook",behaviors:[s["default"]],properties:{narrow:{type:Boolean,value:!1},showMenu:{type:Boolean,value:!1},selectedDate:{type:String,bindNuclear:i.logbookGetters.currentDate},isLoading:{type:Boolean,bindNuclear:i.logbookGetters.isLoadingEntries},isStale:{type:Boolean,bindNuclear:i.logbookGetters.isCurrentStale,observer:"isStaleChanged"},entries:{type:Array,bindNuclear:[i.logbookGetters.currentEntries,function(t){return t.toArray()}]},datePicker:{type:Object}},isStaleChanged:function(t){var e=this;t&&this.async(function(){return i.logbookActions.fetchDate(e.selectedDate)},1)},handleRefresh:function(){i.logbookActions.fetchDate(this.selectedDate)},datepickerFocus:function(){this.datePicker.adjustPosition()},attached:function(){this.datePicker=new Pikaday({field:this.$.datePicker.inputElement,onSelect:i.logbookActions.changeCurrentDate})},detached:function(){this.datePicker.destroy()}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o),a=n(4),s=r(a);n(14),n(143),n(144),e["default"]=new u["default"]({is:"partial-zone",behaviors:[s["default"]],properties:{narrow:{type:Boolean,value:!1},isFetching:{type:Boolean,bindNuclear:i.syncGetters.isFetching},isStreaming:{type:Boolean,bindNuclear:i.streamGetters.isStreamingEvents},canListen:{type:Boolean,bindNuclear:[i.voiceGetters.isVoiceSupported,i.configGetters.isComponentLoaded("conversation"),function(t,e){return t&&e}]},isListening:{type:Boolean,bindNuclear:i.voiceGetters.isListening},showListenInterface:{type:Boolean,bindNuclear:[i.voiceGetters.isListening,i.voiceGetters.isTransmitting,function(t,e){return t||e}]},introductionLoaded:{type:Boolean,bindNuclear:i.configGetters.isComponentLoaded("introduction")},locationName:{type:String,bindNuclear:i.configGetters.locationName},showMenu:{type:Boolean,value:!1},states:{type:Object,bindNuclear:i.entityGetters.visibleEntityMap},columns:{type:Number}},created:function(){this.windowChange=this.windowChange.bind(this)},attached:function(){for(var t=this,e=[],n=0;4>n;n++)e.push(940+350*n);this.mqls=e.map(function(e){var n=window.matchMedia("(min-width: "+e+"px)");return n.addListener(t.windowChange),n}),this.windowChange()},detached:function(){var t=this;this.mqls.forEach(function(e){return e.removeListener(t.windowChange)})},windowChange:function(){this.columns=this.mqls.reduce(function(t,e){return t+e.matches},1)},handleRefresh:function(){i.syncActions.fetchAll()},handleListenClick:function(){this.isListening?i.voiceActions.stop():i.voiceActions.listen()},computeDomains:function(t){return t.keySeq().toArray()},computeMenuButtonClass:function(t,e){return!t&&e?"invisible":""},computeStatesOfDomain:function(t,e){return t.get(e).toArray()},computeListenButtonIcon:function(t){return t?"av:mic-off":"av:mic"},computeRefreshButtonClass:function(t){return t?"ha-spin":void 0},computeShowIntroduction:function(t,e){return t||0===e.size},toggleMenu:function(){this.fire("open-menu")}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o),a=n(4),s=r(a);e["default"]=new u["default"]({is:"notification-manager",behaviors:[s["default"]],properties:{text:{type:String,bindNuclear:i.notificationGetters.lastNotificationMessage,observer:"showNotification"}},showNotification:function(t){t&&this.$.toast.show()}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);e["default"]=new o["default"]({is:"more-info-camera",properties:{stateObj:{type:Object},dialogOpen:{type:Boolean}},imageLoaded:function(){this.fire("iron-resize")},computeCameraImageUrl:function(t){return t?"/api/camera_proxy_stream/"+this.stateObj.entityId:"data:image/gif;base64,R0lGODlhAQABAAAAACw="}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o),a=n(4),s=r(a);n(30),e["default"]=new u["default"]({is:"more-info-configurator",behaviors:[s["default"]],properties:{stateObj:{type:Object},action:{type:String,value:"display"},isStreaming:{type:Boolean,bindNuclear:i.streamGetters.isStreamingEvents},isConfigurable:{type:Boolean,computed:"computeIsConfigurable(stateObj)"},isConfiguring:{type:Boolean,value:!1},submitCaption:{type:String,computed:"computeSubmitCaption(stateObj)"}},computeIsConfigurable:function(t){return"configure"===t.state},computeSubmitCaption:function(t){return t.attributes.submit_caption||"Set configuration"},submitClicked:function(){var t=this;this.isConfiguring=!0;var e={configure_id:this.stateObj.attributes.configure_id};i.serviceActions.callService("configurator","configure",e).then(function(){t.isConfiguring=!1,t.isStreaming||i.syncActions.fetchAll()},function(){t.isConfiguring=!1})}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i),u=n(177),a=r(u);n(163),n(164),n(168),n(161),n(169),n(167),n(165),n(166),n(160),n(170),e["default"]=new o["default"]({is:"more-info-content",properties:{stateObj:{type:Object,observer:"stateObjChanged"},dialogOpen:{type:Boolean,value:!1,observer:"dialogOpenChanged"}},dialogOpenChanged:function(t){var e=o["default"].dom(this);e.lastChild&&(e.lastChild.dialogOpen=t)},stateObjChanged:function(t,e){var n=o["default"].dom(this);if(!t)return void(n.lastChild&&n.removeChild(n.lastChild));var r=a["default"](t);if(e&&a["default"](e)===r)n.lastChild.dialogOpen=this.dialogOpen,n.lastChild.stateObj=t;else{n.lastChild&&n.removeChild(n.lastChild);var i=document.createElement("more-info-"+r);i.stateObj=t,i.dialogOpen=this.dialogOpen,n.appendChild(i)}}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i),u=["entity_picture","friendly_name","unit_of_measurement"];e["default"]=new o["default"]({is:"more-info-default",properties:{stateObj:{type:Object}},computeDisplayAttributes:function(t){return t?Object.keys(t.attributes).filter(function(t){return-1===u.indexOf(t)}):[]},getAttributeValue:function(t,e){return t.attributes[e]}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o),a=n(4),s=r(a);n(31),e["default"]=new u["default"]({is:"more-info-group",behaviors:[s["default"]],properties:{stateObj:{type:Object},states:{type:Array,bindNuclear:[i.moreInfoGetters.currentEntity,i.entityGetters.entityMap,function(t,e){return t?t.attributes.entity_id.map(e.get.bind(e)):[]}]}},updateStates:function(){this.states=this.stateObj&&this.stateObj.attributes.entity_id?stateStore.gets(this.stateObj.attributes.entity_id).toArray():[]}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o),a=n(33),s=r(a);n(139);var c=["brightness","xy_color"];e["default"]=new u["default"]({is:"more-info-light",properties:{stateObj:{type:Object,observer:"stateObjChanged"},brightnessSliderValue:{type:Number,value:0}},stateObjChanged:function(t){var e=this;t&&"on"===t.state&&(this.brightnessSliderValue=t.attributes.brightness),this.async(function(){return e.fire("iron-resize")},500)},computeClassNames:function(t){return s["default"](t,c)},brightnessSliderChanged:function(t){var e=parseInt(t.target.value,10);isNaN(e)||(0===e?i.serviceActions.callTurnOff(this.stateObj.entityId):i.serviceActions.callService("light","turn_on",{entity_id:this.stateObj.entityId,brightness:e}))},colorPicked:function(t){var e=t.detail.rgb;i.serviceActions.callService("light","turn_on",{entity_id:this.stateObj.entityId,rgb_color:[e.r,e.g,e.b]})}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o),a=n(33),s=r(a),c=["volume_level"];e["default"]=new u["default"]({is:"more-info-media_player",properties:{stateObj:{type:Object,observer:"stateObjChanged"},isOff:{type:Boolean,value:!1},isPlaying:{type:Boolean,value:!1},isMuted:{type:Boolean,value:!1},volumeSliderValue:{type:Number,value:0},supportsPause:{type:Boolean,value:!1},supportsVolumeSet:{type:Boolean,value:!1},supportsVolumeMute:{type:Boolean,value:!1},supportsPreviousTrack:{type:Boolean,value:!1},supportsNextTrack:{type:Boolean,value:!1},supportsTurnOn:{type:Boolean,value:!1},supportsTurnOff:{type:Boolean,value:!1}},stateObjChanged:function(t){var e=this;t&&(this.isOff="off"===t.state,this.isPlaying="playing"===t.state,this.volumeSliderValue=100*t.attributes.volume_level,this.isMuted=t.attributes.is_volume_muted,this.supportsPause=0!==(1&t.attributes.supported_media_commands),this.supportsVolumeSet=0!==(4&t.attributes.supported_media_commands),this.supportsVolumeMute=0!==(8&t.attributes.supported_media_commands),this.supportsPreviousTrack=0!==(16&t.attributes.supported_media_commands),this.supportsNextTrack=0!==(32&t.attributes.supported_media_commands),this.supportsTurnOn=0!==(128&t.attributes.supported_media_commands),this.supportsTurnOff=0!==(256&t.attributes.supported_media_commands)),this.async(function(){return e.fire("iron-resize")},500)},computeClassNames:function(t){return s["default"](t,c)},computeIsOff:function(t){return"off"===t.state},computeMuteVolumeIcon:function(t){return t?"av:volume-off":"av:volume-up"},computePlaybackControlIcon:function(){return this.isPlaying?this.supportsPause?"av:pause":"av:stop":"av:play-arrow"},computeHidePowerButton:function(t,e,n){return t?!e:!n},handleTogglePower:function(){this.callService(this.isOff?"turn_on":"turn_off")},handlePrevious:function(){this.callService("media_previous_track")},handlePlaybackControl:function(){this.callService("media_play_pause")},handleNext:function(){this.callService("media_next_track")},handleVolumeTap:function(){this.supportsVolumeMute&&this.callService("volume_mute",{is_volume_muted:!this.isMuted})},volumeSliderChanged:function(t){var e=parseFloat(t.target.value),n=e>0?e/100:0;this.callService("volume_set",{volume_level:n})},callService:function(t,e){var n=e||{};n.entity_id=this.stateObj.entityId,i.serviceActions.callService("media_player",t,n)}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);e["default"]=new o["default"]({is:"more-info-script",properties:{stateObj:{type:Object}}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(63),u=r(o),a=i.util.parseDateTime;e["default"]=new Polymer({is:"more-info-sun",properties:{stateObj:{type:Object},risingDate:{type:Object,computed:"computeRising(stateObj)"},settingDate:{type:Object,computed:"computeSetting(stateObj)"}},computeRising:function(t){return a(t.attributes.next_rising)},computeSetting:function(t){return a(t.attributes.next_setting)},computeOrder:function(t,e){return t>e?["set","ris"]:["ris","set"]},itemCaption:function(t){return"ris"===t?"Rising ":"Setting "},itemDate:function(t){return"ris"===t?this.risingDate:this.settingDate},itemValue:function(t){return u["default"](this.itemDate(t))}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),o=n(1),u=r(o),a=n(33),s=r(a),c=["away_mode"];e["default"]=new u["default"]({is:"more-info-thermostat",properties:{stateObj:{type:Object,observer:"stateObjChanged"},tempMin:{type:Number},tempMax:{type:Number},targetTemperatureSliderValue:{type:Number},awayToggleChecked:{type:Boolean}},stateObjChanged:function(t){this.targetTemperatureSliderValue=t.state,this.awayToggleChecked="on"===t.attributes.away_mode,this.tempMin=t.attributes.min_temp,this.tempMax=t.attributes.max_temp},computeClassNames:function(t){return s["default"](t,c)},targetTemperatureSliderChanged:function(t){var e=parseInt(t.target.value,10);isNaN(e)||i.serviceActions.callService("thermostat","set_temperature",{entity_id:this.stateObj.entityId,temperature:e})},toggleChanged:function(t){var e=t.target.checked;e&&"off"===this.stateObj.attributes.away_mode?this.service_set_away(!0):e||"on"!==this.stateObj.attributes.away_mode||this.service_set_away(!1)},service_set_away:function(t){var e=this;i.serviceActions.callService("thermostat","set_away_mode",{away_mode:t,entity_id:this.stateObj.entityId}).then(function(){return e.stateObjChanged(e.stateObj)})}}),t.exports=e["default"]},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(2);e["default"]=new Polymer({is:"more-info-updater",properties:{stateObj:{type:Object}},updateTapped:function(){r.serviceActions.callService("updater","update",{})},linkTapped:function(){window.open(this.stateObj.attributes.link,"_blank")}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(17),n(32),e["default"]=new o["default"]({is:"state-card-configurator",properties:{stateObj:{type:Object}}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(17);var u=["playing","paused"];e["default"]=new o["default"]({is:"state-card-media_player",properties:{stateObj:{type:Object},isPlaying:{type:Boolean,computed:"computeIsPlaying(stateObj)"}},computeIsPlaying:function(t){return-1!==u.indexOf(t.state)},computePrimaryText:function(t,e){return e?t.attributes.media_title:t.stateDisplay},computeSecondaryText:function(t){var e=void 0;return"music"===t.attributes.media_content_type?t.attributes.media_artist:"tvshow"===t.attributes.media_content_type?(e=t.attributes.media_series_title,t.attributes.media_season&&t.attributes.media_episode&&(e+=" S"+t.attributes.media_season+"E"+t.attributes.media_episode),e):t.attributes.app_name?t.attributes.app_name:""}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(32),n(60),e["default"]=new o["default"]({is:"state-card-scene",properties:{stateObj:{type:Object},allowToggle:{type:Boolean,value:!1,computed:"computeAllowToggle(stateObj)"}},computeAllowToggle:function(t){return"off"===t.state||t.attributes.active_requested}}),t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=r(i);n(17),e["default"]=new o["default"]({is:"state-card-thermostat",properties:{stateObj:{type:Object}}}),t.exports=e["default"]},function(t,e){"use strict";function n(t){return{attached:function(){var e=this;this.__unwatchFns=Object.keys(this.properties).reduce(function(n,r){if(!("bindNuclear"in e.properties[r]))return n;var i=e.properties[r].bindNuclear;if(!i)throw new Error("Undefined getter specified for key "+r);return e[r]=t.evaluate(i),n.concat(t.observe(i,function(t){e[r]=t}))},[])},detached:function(){for(;this.__unwatchFns.length;)this.__unwatchFns.shift()()}}}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=n,t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){return-1!==a.indexOf(t.domain)?t.domain:u["default"](t.entityId)?"toggle":"display"}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=i;var o=n(61),u=r(o),a=["thermostat","configurator","scene","media_player"];t.exports=e["default"]},function(t,e){"use strict";function n(t){return-1!==r.indexOf(t.domain)?t.domain:"default"}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=n;var r=["light","group","sun","configurator","thermostat","script","media_player","camera","updater"];t.exports=e["default"]},function(t,e){"use strict";function n(t,e,n){var r=1-t-e,i=n/255,o=i/e*t,u=i/e*r,a=1.612*o-.203*i-.302*u,s=.509*-o+1.412*i+.066*u,c=.026*o-.072*i+.962*u;a=.0031308>=a?12.92*a:1.055*Math.pow(a,1/2.4)-.055,s=.0031308>=s?12.92*s:1.055*Math.pow(s,1/2.4)-.055,c=.0031308>=c?12.92*c:1.055*Math.pow(c,1/2.4)-.055;var l=Math.max(a,s,c);return a/=l,s/=l,c/=l,a=255*a,0>a&&(a=255),s=255*s,0>s&&(s=255),c=255*c,0>c&&(c=255),[a,s,c]}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=n,t.exports=e["default"]},function(t,e,n){var r;(function(t,i,o){"use strict";(function(){function u(t){return"function"==typeof t||"object"==typeof t&&null!==t}function a(t){return"function"==typeof t}function s(t){return"object"==typeof t&&null!==t}function c(t){W=t}function l(t){Z=t}function f(){return function(){t.nextTick(_)}}function d(){return function(){q(_)}}function p(){var t=0,e=new tt(_),n=document.createTextNode("");return e.observe(n,{characterData:!0}),function(){n.data=t=++t%2}}function h(){var t=new MessageChannel;return t.port1.onmessage=_,function(){t.port2.postMessage(0)}}function v(){return function(){setTimeout(_,1)}}function _(){for(var t=0;$>t;t+=2){var e=rt[t],n=rt[t+1];e(n),rt[t]=void 0,rt[t+1]=void 0}$=0}function y(){try{var t=n(250);return q=t.runOnLoop||t.runOnContext,d()}catch(e){return v()}}function m(){}function g(){return new TypeError("You cannot resolve a promise with itself")}function b(){return new TypeError("A promises callback cannot return that same promise.")}function O(t){try{return t.then}catch(e){return at.error=e,at}}function w(t,e,n,r){try{t.call(e,n,r)}catch(i){return i}}function S(t,e,n){Z(function(t){var r=!1,i=w(n,e,function(n){r||(r=!0,e!==n?j(t,n):I(t,n))},function(e){r||(r=!0,P(t,e))},"Settle: "+(t._label||" unknown promise"));!r&&i&&(r=!0,P(t,i))},t)}function M(t,e){e._state===ot?I(t,e._result):e._state===ut?P(t,e._result):D(e,void 0,function(e){j(t,e)},function(e){P(t,e)})}function T(t,e){if(e.constructor===t.constructor)M(t,e);else{var n=O(e);n===at?P(t,at.error):void 0===n?I(t,e):a(n)?S(t,e,n):I(t,e)}}function j(t,e){t===e?P(t,g()):u(e)?T(t,e):I(t,e); +}function E(t){t._onerror&&t._onerror(t._result),C(t)}function I(t,e){t._state===it&&(t._result=e,t._state=ot,0!==t._subscribers.length&&Z(C,t))}function P(t,e){t._state===it&&(t._state=ut,t._result=e,Z(E,t))}function D(t,e,n,r){var i=t._subscribers,o=i.length;t._onerror=null,i[o]=e,i[o+ot]=n,i[o+ut]=r,0===o&&t._state&&Z(C,t)}function C(t){var e=t._subscribers,n=t._state;if(0!==e.length){for(var r,i,o=t._result,u=0;uu;u++)D(r.resolve(t[u]),void 0,e,n);return i}function H(t){var e=this;if(t&&"object"==typeof t&&t.constructor===e)return t;var n=new e(m);return j(n,t),n}function Y(t){var e=this,n=new e(m);return P(n,t),n}function G(){throw new TypeError("You must pass a resolver function as the first argument to the promise constructor")}function U(){throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.")}function F(t){this._id=ht++,this._state=void 0,this._result=void 0,this._subscribers=[],m!==t&&(a(t)||G(),this instanceof F||U(),L(this,t))}function B(){var t;if("undefined"!=typeof i)t=i;else if("undefined"!=typeof self)t=self;else try{t=Function("return this")()}catch(e){throw new Error("polyfill failed because global object is unavailable in this environment")}var n=t.Promise;(!n||"[object Promise]"!==Object.prototype.toString.call(n.resolve())||n.cast)&&(t.Promise=vt)}var V;V=Array.isArray?Array.isArray:function(t){return"[object Array]"===Object.prototype.toString.call(t)};var q,W,K,J=V,$=0,Z=({}.toString,function(t,e){rt[$]=t,rt[$+1]=e,$+=2,2===$&&(W?W(_):K())}),X="undefined"!=typeof window?window:void 0,Q=X||{},tt=Q.MutationObserver||Q.WebKitMutationObserver,et="undefined"!=typeof t&&"[object process]"==={}.toString.call(t),nt="undefined"!=typeof Uint8ClampedArray&&"undefined"!=typeof importScripts&&"undefined"!=typeof MessageChannel,rt=new Array(1e3);K=et?f():tt?p():nt?h():void 0===X?y():v();var it=void 0,ot=1,ut=2,at=new A,st=new A;N.prototype._validateInput=function(t){return J(t)},N.prototype._validationError=function(){return new Error("Array Methods must be provided an Array")},N.prototype._init=function(){this._result=new Array(this.length)};var ct=N;N.prototype._enumerate=function(){for(var t=this,e=t.length,n=t.promise,r=t._input,i=0;n._state===it&&e>i;i++)t._eachEntry(r[i],i)},N.prototype._eachEntry=function(t,e){var n=this,r=n._instanceConstructor;s(t)?t.constructor===r&&t._state!==it?(t._onerror=null,n._settledAt(t._state,e,t._result)):n._willSettleAt(r.resolve(t),e):(n._remaining--,n._result[e]=t)},N.prototype._settledAt=function(t,e,n){var r=this,i=r.promise;i._state===it&&(r._remaining--,t===ut?P(i,n):r._result[e]=n),0===r._remaining&&I(i,r._result)},N.prototype._willSettleAt=function(t,e){var n=this;D(t,void 0,function(t){n._settledAt(ot,e,t)},function(t){n._settledAt(ut,e,t)})};var lt=R,ft=z,dt=H,pt=Y,ht=0,vt=F;F.all=lt,F.race=ft,F.resolve=dt,F.reject=pt,F._setScheduler=c,F._setAsap=l,F._asap=Z,F.prototype={constructor:F,then:function(t,e){var n=this,r=n._state;if(r===ot&&!t||r===ut&&!e)return this;var i=new this.constructor(m),o=n._result;if(r){var u=arguments[r-1];Z(function(){k(r,i,u,o)})}else D(n,i,t,e);return i},"catch":function(t){return this.then(null,t)}};var _t=B,yt={Promise:vt,polyfill:_t};n(249).amd?(r=function(){return yt}.call(e,n,e,o),!(void 0!==r&&(o.exports=r))):"undefined"!=typeof o&&o.exports?o.exports=yt:"undefined"!=typeof this&&(this.ES6Promise=yt),_t()}).call(void 0)}).call(e,n(246),function(){return this}(),n(247)(t))},function(t,e,n){"use strict";var r=n(65),i=r(Date,"now"),o=i||function(){return(new Date).getTime()};t.exports=o},function(t,e){"use strict";function n(t){return"number"==typeof t&&t>-1&&t%1==0&&r>=t}var r=9007199254740991;t.exports=n},function(t,e,n){"use strict";var r=n(65),i=n(181),o=n(66),u="[object Array]",a=Object.prototype,s=a.toString,c=r(Array,"isArray"),l=c||function(t){return o(t)&&i(t.length)&&s.call(t)==u};t.exports=l},function(t,e,n){"use strict";function r(t){return null==t?!1:i(t)?l.test(s.call(t)):o(t)&&u.test(t)}var i=n(67),o=n(66),u=/^\[object .+?Constructor\]$/,a=Object.prototype,s=Function.prototype.toString,c=a.hasOwnProperty,l=RegExp("^"+s.call(c).replace(/[\\^$.*+?()[\]{}|]/g,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$");t.exports=r},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(179),i=n(22),o=function(t,e,n){var o=arguments.length<=3||void 0===arguments[3]?null:arguments[3],u=t.evaluate(i.getters.authInfo),a=u.host+"/api/"+n;return new r.Promise(function(t,n){var r=new XMLHttpRequest;r.open(e,a,!0),r.setRequestHeader("X-HA-access",u.authToken),r.onload=function(){if(r.status>199&&r.status<300)t(JSON.parse(r.responseText));else try{n(JSON.parse(r.responseText))}catch(e){n({})}},r.onerror=function(){return n({})},o?r.send(JSON.stringify(o)):r.send()})};e["default"]=o,t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){var n=arguments.length<=2||void 0===arguments[2]?{}:arguments[2],r=n.useStreaming,i=void 0===r?t.evaluate(s.getters.isSupported):r,o=n.rememberAuth,u=void 0===o?!1:o,f=n.host,d=void 0===f?"":f;t.dispatch(a["default"].VALIDATING_AUTH_TOKEN,{authToken:e,host:d}),c.actions.fetchAll(t).then(function(){t.dispatch(a["default"].VALID_AUTH_TOKEN,{authToken:e,host:d,rememberAuth:u}),i?s.actions.start(t,{syncOnInitialConnect:!1}):c.actions.start(t,{skipInitialSync:!0})},function(){var e=arguments.length<=0||void 0===arguments[0]?{}:arguments[0],n=e.message,r=void 0===n?l:n;t.dispatch(a["default"].INVALID_AUTH_TOKEN,{errorMessage:r})})}function o(t){t.dispatch(a["default"].LOG_OUT,{})}Object.defineProperty(e,"__esModule",{value:!0}),e.validate=i,e.logOut=o;var u=n(21),a=r(u),s=n(40),c=n(42),l="Unexpected result from API"},function(t,e){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var n=[["authAttempt","isValidating"],function(t){return!!t}];e.isValidating=n;var r=[["authAttempt","isInvalid"],function(t){return!!t}];e.isInvalidAttempt=r;var i=["authAttempt","errorMessage"];e.attemptErrorMessage=i;var o=["rememberAuth"];e.rememberAuth=o;var u=[["authAttempt","authToken"],["authAttempt","host"],function(t,e){return{authToken:t,host:e}}];e.attemptAuthInfo=u;var a=["authCurrent","authToken"];e.currentAuthToken=a;var s=[a,["authCurrent","host"],function(t,e){return{authToken:t,host:e}}];e.currentAuthInfo=s;var c=[n,["authAttempt","authToken"],["authCurrent","authToken"],function(t,e,n){return t?e:n}];e.authToken=c;var l=[n,u,s,function(t,e,n){return t?e:n}];e.authInfo=l},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){if(null==t)throw new TypeError("Cannot destructure undefined")}function o(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function u(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}function a(t,e){var n=e.authToken,r=e.host;return d.toImmutable({authToken:n,host:r,isValidating:"true",isInvalid:!1,errorMessage:""})}function s(t,e){return i(e),_.getInitialState()}function c(t,e){var n=e.errorMessage;return t.withMutations(function(t){return t.set("isValidating",!1).set("isInvalid","true").set("errorMessage",n)})}Object.defineProperty(e,"__esModule",{value:!0});var l=function(){function t(t,e){for(var n=0;n1&&t.set(p,r)})}function a(){return v.getInitialState()}Object.defineProperty(e,"__esModule",{value:!0});var s=function(){function t(t,e){for(var n=0;no}Object.defineProperty(e,"__esModule",{value:!0});var i=n(3),o=6e4,u=["currentLogbookDate"];e.currentDate=u;var a=[u,["logbookEntriesUpdated"],function(t,e){return r(e.get(t))}];e.isCurrentStale=a;var s=[u,["logbookEntries"],function(t,e){return e.get(t)||i.toImmutable([])}];e.currentEntries=s;var c=["isLoadingLogbookEntries"];e.isLoadingEntries=c},function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function i(t){return t&&t.__esModule?t:{"default":t}}function o(t){t.registerStores({currentLogbookDate:a["default"],isLoadingLogbookEntries:c["default"],logbookEntries:f["default"],logbookEntriesUpdated:p["default"]})}Object.defineProperty(e,"__esModule",{value:!0}),e.register=o;var u=n(208),a=i(u),s=n(209),c=i(s),l=n(210),f=i(l),d=n(211),p=i(d),h=n(204),v=r(h),_=n(205),y=r(_),m=v;e.actions=m;var g=y;e.getters=g},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function o(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}Object.defineProperty(e,"__esModule",{value:!0});var u=function(){function t(t,e){for(var n=0;n1)for(var n=1;n \ No newline at end of file diff --git a/homeassistant/components/frontend/www_static/home-assistant-polymer b/homeassistant/components/frontend/www_static/home-assistant-polymer index c3b9a55d683..46dbd30fbae 160000 --- a/homeassistant/components/frontend/www_static/home-assistant-polymer +++ b/homeassistant/components/frontend/www_static/home-assistant-polymer @@ -1 +1 @@ -Subproject commit c3b9a55d6839b933042da14d578c43f9a47499dc +Subproject commit 46dbd30fbae80a5421392a8db5fa98de32b89f21 diff --git a/homeassistant/components/frontend/www_static/splash.png b/homeassistant/components/frontend/www_static/splash.png index 3d5411ecb4b2612011c33b5dcbf593864c775857..582140a2bc35f598fc227782ff06039759b39fe5 100644 GIT binary patch literal 51901 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 X4*bsH#oYQwnxd$@(NwIHw~YKRWF8~N From 4e01e7ca9b7c02893808d603c9d4dcaa27718fd5 Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Sat, 29 Aug 2015 23:17:47 -0400 Subject: [PATCH 212/224] Pip requirements and fixes. 1) Reduced the flags being sent to pip. 2) Required a minimum of pip 7.0.0 for Home Assistant. --- homeassistant/util/package.py | 3 +-- requirements.txt | 1 + requirements_all.txt | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/util/package.py b/homeassistant/util/package.py index 3719fecb9ff..75d59970bfb 100644 --- a/homeassistant/util/package.py +++ b/homeassistant/util/package.py @@ -13,8 +13,7 @@ def install_package(package, upgrade=False, target=None): """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 = [sys.executable, '-m', 'pip', 'install', '--quiet', - '--isolated', '-I', package] + args = [sys.executable, '-m', 'pip', 'install', '--quiet', package] if upgrade: args.append('--upgrade') if target: diff --git a/requirements.txt b/requirements.txt index a21c2db6208..1b7d2396971 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ requests>=2,<3 pyyaml>=3.11,<4 pytz>=2015.4 +pip>=7.0.0 diff --git a/requirements_all.txt b/requirements_all.txt index f9f02b49740..a900846e30d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2,6 +2,7 @@ requests>=2,<3 pyyaml>=3.11,<4 pytz>=2015.4 +pip>=7.0.0 # Optional, needed for specific components From bea81ddd926c560463aac8f26d4d36b2cf53b457 Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Sat, 29 Aug 2015 23:31:33 -0400 Subject: [PATCH 213/224] Minor tweaks Moved another import to the top of main. Forced an exit code of 1 when there is an error initializing. --- homeassistant/__main__.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/homeassistant/__main__.py b/homeassistant/__main__.py index c6f0fd97b98..815404b1de1 100644 --- a/homeassistant/__main__.py +++ b/homeassistant/__main__.py @@ -8,6 +8,7 @@ import importlib from homeassistant import bootstrap import homeassistant.config as config_util +from homeassistant.const import EVENT_HOMEASSISTANT_START def validate_python(): @@ -16,7 +17,7 @@ def validate_python(): if major < 3 or (major == 3 and minor < 4): print("Home Assistant requires atleast Python 3.4") - sys.exit() + sys.exit(1) def ensure_pip(): @@ -26,7 +27,7 @@ def ensure_pip(): print("Home Assistant requires 'pip' to be installed.") print("Please install pip: " "https://pip.pypa.io/en/latest/installing.html") - sys.exit() + sys.exit(1) def ensure_config_path(config_dir): @@ -37,17 +38,17 @@ def ensure_config_path(config_dir): # Test if configuration directory exists if not os.path.isdir(config_dir): - if config_dir == config_util.get_default_config_dir(): - try: - os.mkdir(config_dir) - except OSError: - print(('Fatal Error: Unable to create default configuration ' - 'directory {} ').format(config_dir)) - sys.exit() - else: + if config_dir != config_util.get_default_config_dir(): print(('Fatal Error: Specified configuration directory does ' 'not exist {} ').format(config_dir)) - sys.exit() + sys.exit(1) + + try: + os.mkdir(config_dir) + except OSError: + print(('Fatal Error: Unable to create default configuration ' + 'directory {} ').format(config_dir)) + sys.exit(1) # Test if library directory exists if not os.path.isdir(lib_dir): @@ -56,13 +57,13 @@ def ensure_config_path(config_dir): except OSError: print(('Fatal Error: Unable to create library ' 'directory {} ').format(lib_dir)) - sys.exit() + sys.exit(1) config_path = config_util.ensure_config_exists(config_dir) if config_path is None: print('Error getting configuration path') - sys.exit() + sys.exit(1) return config_path @@ -110,7 +111,7 @@ def main(): if hass.config.api is not None: import webbrowser webbrowser.open(hass.config.api.base_url) - from homeassistant.const import EVENT_HOMEASSISTANT_START + hass.bus.listen_once(EVENT_HOMEASSISTANT_START, open_browser) hass.start() From 7343e33063d561dbc5ec2ef7930b7d081a9c7f4a Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 29 Aug 2015 22:02:19 -0700 Subject: [PATCH 214/224] Some more frontend bug fixes --- homeassistant/components/frontend/version.py | 2 +- .../components/frontend/www_static/frontend.html | 16 ++++++++-------- .../frontend/www_static/home-assistant-polymer | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py index 6eea0024c30..f4a1ad184e7 100644 --- a/homeassistant/components/frontend/version.py +++ b/homeassistant/components/frontend/version.py @@ -1,2 +1,2 @@ """ DO NOT MODIFY. Auto-generated by build_frontend script """ -VERSION = "441ca74ed89c1db053bc2f55d57a48c6" +VERSION = "e9060d58fc9034468cfefa9794026d0c" diff --git a/homeassistant/components/frontend/www_static/frontend.html b/homeassistant/components/frontend/www_static/frontend.html index 06f43dec560..e1b3b84dfc3 100644 --- a/homeassistant/components/frontend/www_static/frontend.html +++ b/homeassistant/components/frontend/www_static/frontend.html @@ -3825,7 +3825,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN } .content { padding: 0 16px 16px; - }