From b596fa33d6b03bb858cf6599c1d37cb4a182af33 Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Tue, 26 Jan 2016 22:39:59 -0500 Subject: [PATCH 001/232] Implemented restart service Implemented an OS and environment safe restart service. This works by running Home Assistant in a child process. If the child process terminates with an exit code > 0, HASS is restarted. SIGTERM and KeyboardInterrupts to the parent process are forwarded to the child process. KeyboardInterrupts will only be forwarded once. The second KeyboardInterrupt will be handled by the parent. --- homeassistant/__main__.py | 89 +++++++++++++++++++++++++++------------ homeassistant/const.py | 2 +- homeassistant/core.py | 12 +++++- 3 files changed, 75 insertions(+), 28 deletions(-) diff --git a/homeassistant/__main__.py b/homeassistant/__main__.py index e97ed0c6386..7da1e6658e1 100644 --- a/homeassistant/__main__.py +++ b/homeassistant/__main__.py @@ -1,7 +1,10 @@ """ Starts home assistant. """ from __future__ import print_function +from multiprocessing import Process +import signal import sys +import threading import os import argparse @@ -204,6 +207,61 @@ def uninstall_osx(): print("Home Assistant has been uninstalled.") +def setup_and_run_hass(config_dir, args): + """ Setup HASS and run. Block until stopped. """ + if args.demo_mode: + config = { + 'frontend': {}, + 'demo': {} + } + hass = bootstrap.from_config_dict( + config, config_dir=config_dir, daemon=args.daemon, + verbose=args.verbose, skip_pip=args.skip_pip, + log_rotate_days=args.log_rotate_days) + else: + config_file = ensure_config_file(config_dir) + print('Config directory:', config_dir) + hass = bootstrap.from_config_file( + config_file, daemon=args.daemon, verbose=args.verbose, + skip_pip=args.skip_pip, log_rotate_days=args.log_rotate_days) + + if args.open_ui: + def open_browser(event): + """ Open the webinterface in a browser. """ + if hass.config.api is not None: + import webbrowser + webbrowser.open(hass.config.api.base_url) + + hass.bus.listen_once(EVENT_HOMEASSISTANT_START, open_browser) + + hass.start() + sys.exit(int(hass.block_till_stopped())) + + +def run_hass_process(hass_proc): + """ Runs a child hass process. Returns True if it should be restarted. """ + requested_stop = threading.Event() + hass_proc.daemon = True + + def request_stop(): + """ request hass stop """ + requested_stop.set() + hass_proc.terminate() + + try: + signal.signal(signal.SIGTERM, request_stop) + except ValueError: + print('Could not bind to SIGQUIT. Are you running in a thread?') + + hass_proc.start() + try: + hass_proc.join() + except KeyboardInterrupt: + request_stop() + hass_proc.join() + return not requested_stop.isSet() and hass_proc.exitcode > 0 + + def main(): """ Starts Home Assistant. """ validate_python() @@ -233,33 +291,12 @@ def main(): if args.pid_file: write_pid(args.pid_file) - if args.demo_mode: - config = { - 'frontend': {}, - 'demo': {} - } - hass = bootstrap.from_config_dict( - config, config_dir=config_dir, daemon=args.daemon, - verbose=args.verbose, skip_pip=args.skip_pip, - log_rotate_days=args.log_rotate_days) - else: - config_file = ensure_config_file(config_dir) - print('Config directory:', config_dir) - hass = bootstrap.from_config_file( - config_file, daemon=args.daemon, verbose=args.verbose, - skip_pip=args.skip_pip, log_rotate_days=args.log_rotate_days) + # Run hass is child process. Restart if necessary. + keep_running = True + while keep_running: + hass_proc = Process(target=setup_and_run_hass, args=(config_dir, args)) + keep_running = run_hass_process(hass_proc) - if args.open_ui: - def open_browser(event): - """ Open the webinterface in a browser. """ - if hass.config.api is not None: - import webbrowser - webbrowser.open(hass.config.api.base_url) - - hass.bus.listen_once(EVENT_HOMEASSISTANT_START, open_browser) - - hass.start() - hass.block_till_stopped() if __name__ == "__main__": main() diff --git a/homeassistant/const.py b/homeassistant/const.py index 143704e1968..2d605a7ee71 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -10,7 +10,6 @@ MATCH_ALL = '*' DEVICE_DEFAULT_NAME = "Unnamed Device" # #### CONFIG #### -CONF_ICON = "icon" CONF_LATITUDE = "latitude" CONF_LONGITUDE = "longitude" CONF_TEMPERATURE_UNIT = "temperature_unit" @@ -124,6 +123,7 @@ ATTR_GPS_ACCURACY = 'gps_accuracy' # #### SERVICES #### SERVICE_HOMEASSISTANT_STOP = "stop" +SERVICE_HOMEASSISTANT_RESTART = "restart" SERVICE_TURN_ON = 'turn_on' SERVICE_TURN_OFF = 'turn_off' diff --git a/homeassistant/core.py b/homeassistant/core.py index 853d09020ce..e6b0a6ec722 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -16,7 +16,8 @@ from collections import namedtuple from homeassistant.const import ( __version__, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, - SERVICE_HOMEASSISTANT_STOP, EVENT_TIME_CHANGED, EVENT_STATE_CHANGED, + SERVICE_HOMEASSISTANT_STOP, SERVICE_HOMEASSISTANT_RESTART, + EVENT_TIME_CHANGED, EVENT_STATE_CHANGED, 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) @@ -70,13 +71,21 @@ class HomeAssistant(object): def block_till_stopped(self): """Register service homeassistant/stop and will block until called.""" request_shutdown = threading.Event() + request_restart = threading.Event() def stop_homeassistant(*args): """Stop Home Assistant.""" request_shutdown.set() + def restart_homeassistant(*args): + """Reset Home Assistant.""" + request_restart.set() + request_shutdown.set() + self.services.register( DOMAIN, SERVICE_HOMEASSISTANT_STOP, stop_homeassistant) + self.services.register( + DOMAIN, SERVICE_HOMEASSISTANT_RESTART, restart_homeassistant) if os.name != "nt": try: @@ -92,6 +101,7 @@ class HomeAssistant(object): break self.stop() + return request_restart.isSet() def stop(self): """Stop Home Assistant and shuts down all threads.""" From 519abbbfa2b7bffdcb5c075ac7ec7bed535b2682 Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Tue, 26 Jan 2016 22:41:57 -0500 Subject: [PATCH 002/232] Better handling of second KeyboardInterrupt Now the second KeyboardInterrupt will be cleanly handled by the parent process. --- homeassistant/__main__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/homeassistant/__main__.py b/homeassistant/__main__.py index 7da1e6658e1..ac9d5eabd70 100644 --- a/homeassistant/__main__.py +++ b/homeassistant/__main__.py @@ -258,7 +258,10 @@ def run_hass_process(hass_proc): hass_proc.join() except KeyboardInterrupt: request_stop() - hass_proc.join() + try: + hass_proc.join() + except KeyboardInterrupt: + return False return not requested_stop.isSet() and hass_proc.exitcode > 0 From 3534c975f371a91b00cd39d1679dea4890a97276 Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Tue, 26 Jan 2016 22:46:01 -0500 Subject: [PATCH 003/232] Added missing CONF_ICON constant --- homeassistant/const.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/const.py b/homeassistant/const.py index 2d605a7ee71..e59eb0fa64a 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -10,6 +10,7 @@ MATCH_ALL = '*' DEVICE_DEFAULT_NAME = "Unnamed Device" # #### CONFIG #### +CONF_ICON = "icon" CONF_LATITUDE = "latitude" CONF_LONGITUDE = "longitude" CONF_TEMPERATURE_UNIT = "temperature_unit" From a41b66bb94ee8be5e8f02b2910a4221f535d7acb Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Fri, 29 Jan 2016 22:02:39 -0500 Subject: [PATCH 004/232] Cleaned up block_till_stop in core.py 1. Removed handling of KeyboardInterrupt. This will no longer happen now that HASS is run in a subprocess. The KeyboardInterrupt will not be sent to the parent process which will send a SIGTERM to the HASS process. 2. Fixed logger warning about not being able to bind to SIGTERM. 3. Removed check for Windows OSs when binding to SIGTERM. This check was originally put in place when HASS was binding to SIGQUIT. SIGTERM exists in NT OSs, so the check is no longer required. 3. Now returning exit code of 100 when requesting a restart. This will allow the parent process to only restart HASS if it is specifically requested and not just on any encountered crash. --- homeassistant/core.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/homeassistant/core.py b/homeassistant/core.py index e6b0a6ec722..eaaecfe87ee 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -48,6 +48,9 @@ _LOGGER = logging.getLogger(__name__) # Temporary to support deprecated methods _MockHA = namedtuple("MockHomeAssistant", ['bus']) +# The exit code to send to request a restart +RESTART_EXIT_CODE = 100 + class HomeAssistant(object): """Root object of the Home Assistant home automation.""" @@ -87,21 +90,17 @@ class HomeAssistant(object): self.services.register( DOMAIN, SERVICE_HOMEASSISTANT_RESTART, restart_homeassistant) - if os.name != "nt": - try: - signal.signal(signal.SIGTERM, stop_homeassistant) - except ValueError: - _LOGGER.warning( - 'Could not bind to SIGQUIT. Are you running in a thread?') + try: + signal.signal(signal.SIGTERM, stop_homeassistant) + except ValueError: + _LOGGER.warning( + 'Could not bind to SIGTERM. Are you running in a thread?') while not request_shutdown.isSet(): - try: - time.sleep(1) - except KeyboardInterrupt: - break + time.sleep(1) self.stop() - return request_restart.isSet() + return RESTART_EXIT_CODE if request_restart.isSet() else 0 def stop(self): """Stop Home Assistant and shuts down all threads.""" From b56369855af490442f81c37ef12e5c7348e4ae88 Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Fri, 29 Jan 2016 22:11:11 -0500 Subject: [PATCH 005/232] Cleaned up restart handling in __main__.py 1. Fixed logged message about SIGTERM binding failure. 2. Set to only restart HASS with an exit code of 100. 3. Fixed typo in comment. --- homeassistant/__main__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/__main__.py b/homeassistant/__main__.py index ac9d5eabd70..d7cfd0a2f00 100644 --- a/homeassistant/__main__.py +++ b/homeassistant/__main__.py @@ -251,7 +251,7 @@ def run_hass_process(hass_proc): try: signal.signal(signal.SIGTERM, request_stop) except ValueError: - print('Could not bind to SIGQUIT. Are you running in a thread?') + print('Could not bind to SIGTERM. Are you running in a thread?') hass_proc.start() try: @@ -262,7 +262,7 @@ def run_hass_process(hass_proc): hass_proc.join() except KeyboardInterrupt: return False - return not requested_stop.isSet() and hass_proc.exitcode > 0 + return not requested_stop.isSet() and hass_proc.exitcode == 100 def main(): @@ -294,7 +294,7 @@ def main(): if args.pid_file: write_pid(args.pid_file) - # Run hass is child process. Restart if necessary. + # Run hass as child process. Restart if necessary. keep_running = True while keep_running: hass_proc = Process(target=setup_and_run_hass, args=(config_dir, args)) From 106c53abf1414a8bc3dc0e570f8afe4ba0243db8 Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Fri, 29 Jan 2016 22:42:39 -0500 Subject: [PATCH 006/232] Revised HASS Core test Changed the HASS Core test that tested KeyboardInterrupt handling to now test SIGTERM handling. KeyboardInterrupts are no longer handled in the HASS application process as they are handled in the HASS parent process. SIGTERM is the proper way to now stop HASS. --- tests/test_core.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/test_core.py b/tests/test_core.py index ca935e2d106..4a0096809c8 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -7,6 +7,7 @@ Provides tests to verify that Home Assistant core works. # pylint: disable=protected-access,too-many-public-methods # pylint: disable=too-few-public-methods import os +import signal import unittest from unittest.mock import patch import time @@ -79,15 +80,15 @@ class TestHomeAssistant(unittest.TestCase): self.assertFalse(blocking_thread.is_alive()) - def test_stopping_with_keyboardinterrupt(self): + def test_stopping_with_sigterm(self): calls = [] self.hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, lambda event: calls.append(1)) - def raise_keyboardinterrupt(length): - raise KeyboardInterrupt + def send_sigterm(length): + os.kill(os.getpid(), signal.SIGTERM) - with patch('homeassistant.core.time.sleep', raise_keyboardinterrupt): + with patch('homeassistant.core.time.sleep', send_sigterm): self.hass.block_till_stopped() self.assertEqual(1, len(calls)) From 10f9c049bb3d383f5ae575a1362cb56faad628b3 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 29 Jan 2016 22:38:01 -0800 Subject: [PATCH 007/232] Version bump to 0.13.0.dev0 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 3688aa0dc17..4109b32a263 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ # coding: utf-8 """ Constants used by Home Assistant components. """ -__version__ = "0.12.0" +__version__ = "0.13.0.dev0" # Can be used to specify a catch all when registering state or event listeners. MATCH_ALL = '*' From 0631f5c59df0ee7ffdb627832022cbbcf6b46bee Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Sat, 30 Jan 2016 06:44:22 -0500 Subject: [PATCH 008/232] Added tests for package utilities --- tests/util/test_package.py | 58 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 tests/util/test_package.py diff --git a/tests/util/test_package.py b/tests/util/test_package.py new file mode 100644 index 00000000000..1ffd66e1bb4 --- /dev/null +++ b/tests/util/test_package.py @@ -0,0 +1,58 @@ +""" +Tests Home Assistant package util methods. +""" +import unittest +import sys +import tempfile +import homeassistant.util.package as package + +TEST_EXIST_REQ = "pip>=7.0.0" +TEST_NEW_REQ = "pyhelloworld3==1.0.0" +TEST_ZIP_REQ = \ + "https://github.com/rmkraus/pyhelloworld3/archive/" \ + "5ba878316d68ea164e2cf5bd085d0cf1fd76bd15.zip#pyhelloworld3==1.0.0" + + +class TestPackageUtil(unittest.TestCase): + """ Tests for homeassistant.util.package module """ + + def setUp(self): + """ Create local library for testing """ + self.lib_dir = tempfile.TemporaryDirectory() + + def tearDown(self): + """ Remove local library """ + del self.lib_dir + + def test_install_existing_package(self): + """ Test an install attempt on an existing package """ + self.assertTrue(package.check_package_exists( + TEST_EXIST_REQ, self.lib_dir.name)) + + self.assertTrue(package.install_package(TEST_EXIST_REQ)) + + def test_install_package_locally(self): + """ Test an install attempt to the local library """ + self.assertFalse(package.check_package_exists( + TEST_NEW_REQ, self.lib_dir.name)) + + self.assertTrue(package.install_package( + TEST_NEW_REQ, True, self.lib_dir.name)) + + sys.path.insert(0, self.lib_dir.name) + import pyhelloworld3 + + self.assertEqual(pyhelloworld3.__version__, '1.0.0') + + def test_install_package_zip(self): + """ Test an install attempt from a zip path """ + self.assertFalse(package.check_package_exists( + TEST_ZIP_REQ, self.lib_dir.name)) + + self.assertTrue(package.install_package( + TEST_ZIP_REQ, True, self.lib_dir.name)) + + sys.path.insert(0, self.lib_dir.name) + import pyhelloworld3 + + self.assertEqual(pyhelloworld3.__version__, '1.0.0') From c396dbb570d55b9f3c653f1bebdf5110559b724e Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Sat, 30 Jan 2016 07:18:37 -0500 Subject: [PATCH 009/232] Added tests to check setup and config of universal media player. --- .../components/media_player/test_universal.py | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/tests/components/media_player/test_universal.py b/tests/components/media_player/test_universal.py index eca863b935e..26e8b517936 100644 --- a/tests/components/media_player/test_universal.py +++ b/tests/components/media_player/test_universal.py @@ -135,6 +135,65 @@ class TestMediaPlayer(unittest.TestCase): self.assertTrue(response) self.assertEqual(config_start, self.config_children_and_attr) + def test_check_config_no_name(self): + """ Check config with no Name entry """ + response = universal.validate_config({'platform': 'universal'}) + + self.assertFalse(response) + + def test_check_config_bad_children(self): + """ Check config with bad children entry """ + config_no_children = {'name': 'test', 'platform': 'universal'} + config_bad_children = {'name': 'test', 'children': {}, + 'platform': 'universal'} + + response = universal.validate_config(config_no_children) + self.assertTrue(response) + self.assertEqual(config_no_children['children'], []) + + response = universal.validate_config(config_bad_children) + self.assertTrue(response) + self.assertEqual(config_bad_children['children'], []) + + def test_check_config_bad_commands(self): + """ Check config with bad commands entry """ + config = {'name': 'test', 'commands': [], 'platform': 'universal'} + + response = universal.validate_config(config) + self.assertTrue(response) + self.assertEqual(config['commands'], {}) + + def test_check_config_bad_attributes(self): + """ Check config with bad attributes """ + config = {'name': 'test', 'atttributes': [], 'platform': 'universal'} + + response = universal.validate_config(config) + self.assertTrue(response) + self.assertEqual(config['attributes'], {}) + + def test_check_config_bad_key(self): + """ check config with bad key """ + config = {'name': 'test', 'asdf': 5, 'platform': 'universal'} + + response = universal.validate_config(config) + self.assertTrue(response) + self.assertFalse('asdf' in config) + + def test_platform_setup(self): + """ test platform setup """ + config = {'name': 'test', 'platform': 'universal'} + entities = [] + + def add_devices(new_entities): + """ add devices to list """ + for dev in new_entities: + entities.append(dev) + + universal.setup_platform(self.hass, config, add_devices) + + self.assertEqual(len(entities), 1) + self.assertEqual(entities[0].name, 'test') + def test_master_state(self): """ test master state property """ config = self.config_children_only From c1d057407b20496b09ed3903e65210030db792bd Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Sat, 30 Jan 2016 11:53:15 -0500 Subject: [PATCH 010/232] Fixed typo in universal media player test. --- tests/components/media_player/test_universal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/components/media_player/test_universal.py b/tests/components/media_player/test_universal.py index 26e8b517936..6600f9091ed 100644 --- a/tests/components/media_player/test_universal.py +++ b/tests/components/media_player/test_universal.py @@ -165,7 +165,7 @@ class TestMediaPlayer(unittest.TestCase): def test_check_config_bad_attributes(self): """ Check config with bad attributes """ - config = {'name': 'test', 'atttributes': [], 'platform': 'universal'} + config = {'name': 'test', 'attributes': [], 'platform': 'universal'} response = universal.validate_config(config) self.assertTrue(response) From 6a75b524cb66ca6064d7765cb6879afb2dc9abc4 Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Sat, 30 Jan 2016 11:57:46 -0500 Subject: [PATCH 011/232] Removed unused private method from universal media player MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The universal media player contained a private method that was replaced by the update method. It was meant to be removed and wasn’t. This commit removed that method. --- homeassistant/components/media_player/universal.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/homeassistant/components/media_player/universal.py b/homeassistant/components/media_player/universal.py index 09bb12ec332..359249f15e2 100644 --- a/homeassistant/components/media_player/universal.py +++ b/homeassistant/components/media_player/universal.py @@ -215,15 +215,6 @@ class UniversalMediaPlayer(MediaPlayerDevice): else: return None - def _cache_active_child_state(self): - """ The state of the active child or None """ - for child_name in self._children: - child_state = self.hass.states.get(child_name) - if child_state and child_state.state not in OFF_STATES: - self._child_state = child_state - return - self._child_state = None - @property def name(self): """ name of universal player """ From 8ac763c6f6f9b9fae2c9a849bd07cd2835d912de Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Sat, 30 Jan 2016 12:26:28 -0500 Subject: [PATCH 012/232] Added test for universal mp service routing. Added tests to ensure that the Universal Media Player is routing service calls correctly. --- .../components/media_player/test_universal.py | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/tests/components/media_player/test_universal.py b/tests/components/media_player/test_universal.py index 6600f9091ed..9ed0193959e 100644 --- a/tests/components/media_player/test_universal.py +++ b/tests/components/media_player/test_universal.py @@ -14,6 +14,8 @@ import homeassistant.components.switch as switch import homeassistant.components.media_player as media_player import homeassistant.components.media_player.universal as universal +from tests.common import mock_service + class MockMediaPlayer(media_player.MediaPlayerDevice): """ Mock media player for testing """ @@ -28,6 +30,9 @@ class MockMediaPlayer(media_player.MediaPlayerDevice): self._media_title = None self._supported_media_commands = 0 + self.turn_off_service_calls = mock_service( + hass, media_player.DOMAIN, media_player.SERVICE_TURN_OFF) + @property def name(self): """ name of player """ @@ -399,3 +404,38 @@ class TestMediaPlayer(unittest.TestCase): | universal.SUPPORT_VOLUME_STEP | universal.SUPPORT_VOLUME_MUTE self.assertEqual(check_flags, ump.supported_media_commands) + + def test_service_call_to_child(self): + """ test a service call that should be routed to a child """ + config = self.config_children_only + universal.validate_config(config) + + ump = universal.UniversalMediaPlayer(self.hass, **config) + ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config['name']) + ump.update() + + self.mock_mp_2._state = STATE_PLAYING + self.mock_mp_2.update_ha_state() + ump.update() + + ump.turn_off() + self.assertEqual(len(self.mock_mp_2.turn_off_service_calls), 1) + + def test_service_call_to_command(self): + config = self.config_children_only + config['commands'] = \ + {'turn_off': {'service': 'test.turn_off', 'data': {}}} + universal.validate_config(config) + + service = mock_service(self.hass, 'test', 'turn_off') + + ump = universal.UniversalMediaPlayer(self.hass, **config) + ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config['name']) + ump.update() + + self.mock_mp_2._state = STATE_PLAYING + self.mock_mp_2.update_ha_state() + ump.update() + + ump.turn_off() + self.assertEqual(len(service), 1) From 4cc9606bcc11f579437228ba25a9d90c073ebfbb Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Sat, 30 Jan 2016 13:03:46 -0500 Subject: [PATCH 013/232] Added test for logger component. --- tests/components/test_logger.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 tests/components/test_logger.py diff --git a/tests/components/test_logger.py b/tests/components/test_logger.py new file mode 100644 index 00000000000..96aecc73a0e --- /dev/null +++ b/tests/components/test_logger.py @@ -0,0 +1,31 @@ +""" +tests.test_logger +~~~~~~~~~~~~~~~~~~ + +Tests logger component. +""" +import logging +import unittest + +from homeassistant.components import logger + + +class TestUpdater(unittest.TestCase): + """ Test logger component. """ + + def test_logger(self): + """ Uses logger to create a logging filter """ + config = {'logger': + {'default': 'warning', + 'logs': {'test': 'info'}}} + + logger.setup(None, config) + + self.assertTrue(len(logging.root.handlers) > 0) + handler = logging.root.handlers[-1] + + self.assertEqual(len(handler.filters), 1) + log_filter = handler.filters[0].logfilter + + self.assertEqual(log_filter['default'], logging.WARNING) + self.assertEqual(log_filter['logs']['test'], logging.INFO) From de61bcb80ec4a930b34312f68e35fcba321754f4 Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Sat, 30 Jan 2016 13:23:35 -0500 Subject: [PATCH 014/232] Additional testing for logger component Added an additional test for the logger component the validates the filtering logic of the filters that were created during setup. --- tests/components/test_logger.py | 42 ++++++++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/tests/components/test_logger.py b/tests/components/test_logger.py index 96aecc73a0e..5e3aeda88d3 100644 --- a/tests/components/test_logger.py +++ b/tests/components/test_logger.py @@ -4,22 +4,30 @@ tests.test_logger Tests logger component. """ +from collections import namedtuple import logging import unittest from homeassistant.components import logger +RECORD = namedtuple('record', ('name', 'levelno')) + class TestUpdater(unittest.TestCase): """ Test logger component. """ - def test_logger(self): - """ Uses logger to create a logging filter """ - config = {'logger': - {'default': 'warning', - 'logs': {'test': 'info'}}} + def setUp(self): + """ Create default config """ + self.log_config = {'logger': + {'default': 'warning', 'logs': {'test': 'info'}}} - logger.setup(None, config) + def tearDown(self): + """ Reset logs """ + del logging.root.handlers[-1] + + def test_logger_setup(self): + """ Uses logger to create a logging filter """ + logger.setup(None, self.log_config) self.assertTrue(len(logging.root.handlers) > 0) handler = logging.root.handlers[-1] @@ -29,3 +37,25 @@ class TestUpdater(unittest.TestCase): self.assertEqual(log_filter['default'], logging.WARNING) self.assertEqual(log_filter['logs']['test'], logging.INFO) + + def test_logger_test_filters(self): + """ Tests resulting filter operation """ + logger.setup(None, self.log_config) + + log_filter = logging.root.handlers[-1].filters[0] + + # blocked default record + record = RECORD('asdf', logging.DEBUG) + self.assertFalse(log_filter.filter(record)) + + # allowed default record + record = RECORD('asdf', logging.WARNING) + self.assertTrue(log_filter.filter(record)) + + # blocked named record + record = RECORD('test', logging.DEBUG) + self.assertFalse(log_filter.filter(record)) + + # allowed named record + record = RECORD('test', logging.INFO) + self.assertTrue(log_filter.filter(record)) From 4a8f55e63043369fba32375fe53f0878e7da8b36 Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Sat, 30 Jan 2016 14:08:32 -0500 Subject: [PATCH 015/232] Revised package util tests The package util tests were revised to pull the external library pyhelloworld3 from an internal source rather than external. This speeds up tests, makes tests more reliable, and removes dependency on internet connection. --- tests/resources/pyhelloworld3.zip | Bin 0 -> 2100 bytes tests/util/test_package.py | 53 ++++++++++++++++-------------- 2 files changed, 28 insertions(+), 25 deletions(-) create mode 100644 tests/resources/pyhelloworld3.zip diff --git a/tests/resources/pyhelloworld3.zip b/tests/resources/pyhelloworld3.zip new file mode 100644 index 0000000000000000000000000000000000000000..f2a419e9f340dd870e1c729d06cbc9d622ef0e0f GIT binary patch literal 2100 zcmWIWW@h1H0D&)3b{=2`ln`a$WGJZ2NX^N~FV8Q^Nio*VO)M@+Ez%DSVP#;nox3J1 z07Y-^l&d{@K)oO=k4vv!dS*#xdR~4}D%_N$(#)I`E@YeZQq$9QGxO5&xwP~ODwDb5 zS0-R`&L7FK63T&IeYX+c)sMV4+h?48qcYTCxBZFlzdalcimxb*wiF^Ln$oz86Xe)y8B zpnu-XRY~8@PoK~E()TEnjaF-~p@(DcYtyqY%%{GXy6k@LmzBL9p8MUnSH)U7^Tqmg z@0i4T*{->m_j>50;)=r$liaQO7#4cC{LXL9J`*HjT_}5y9TrLuzkTxjvM&wjuL>YG zATE>;=^NR1!C@D!UJCU%=YR5y_x7L*3ln2skhGcd^Fa(r=WNofJHOKT_jX5TRoXnX%pG)X^i zVJH9F%!3znIZIw{<1F>!Q=Ac5ozwk-yEf;;{`VeX!ModEC7FCaWBtCzG(vZ#vwY9> zD2roDFJ9B1s1$$3W}(2Z(CCZxI$cw$!wsysRIWW$i?8#T$sKik>nih|ueY83xGi== zf@`ebJ?ZW1S55q_IcL4KuC{i(4oi6Msnu(AE80&5K5lNzbGr88;Ql7pvM3?R<_GGz zuRlHK>8kB6n(fjpWKp_&_Ow0R?>)V*RWkSKvE!Df)?Z}zcKdhZLRQZ8GwUWQ zuvp!aDA6}ia+WHe{@|{DyR$&fg=v*eJES6C@yPu)k84`g@A9*UR?Nb#b3! zN|nCcNL%;zp!44Z=4VBW^Q^qwlUG^(e#c^QoZYW}QJvSsic{6A-MNF@($xc!JKDZg zm)T$57P8Lw32(3JmQJ(C#|PQPcly8Ss&(Yg{=}}T+tGV(trTndWvShv;(1%Wnp|ui z&0Oo0{b_~Gs(EH*9JcOPRgb=De-^p;r=iP(b(Wi&4a4GBOnUdf-hAb+z9$NQm!_4; zJykl~cjb-?dvrAatq*!i`IBDD2L3s6UQEaRd->uwyWcVIoi_Dc@yC5Xcmuo{nSg~D zFx6t_c2F@40Rm7OC39jalu^w>FP1@u08<$d*Z?tBv#=D>2p3{6ctPetOx=LXRLo)+ zVJN1zL4_OyfV@2uXf{@-q6i`vd9XmhUg&{LhB)^zaVBFc0uj!~Uiu&`*$<3e6qmwc z4Mk8Nds&2T4SG3&u%-rBa-f(9vj(Ho2=HcQ1L Date: Sat, 30 Jan 2016 14:38:27 -0500 Subject: [PATCH 016/232] Cleaned up universal MP tests Changed all assertEquals in universal media player tests to have assumed value first. --- .../components/media_player/test_universal.py | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/components/media_player/test_universal.py b/tests/components/media_player/test_universal.py index 9ed0193959e..e359700f2fa 100644 --- a/tests/components/media_player/test_universal.py +++ b/tests/components/media_player/test_universal.py @@ -154,11 +154,11 @@ class TestMediaPlayer(unittest.TestCase): response = universal.validate_config(config_no_children) self.assertTrue(response) - self.assertEqual(config_no_children['children'], []) + self.assertEqual([], config_no_children['children']) response = universal.validate_config(config_bad_children) self.assertTrue(response) - self.assertEqual(config_bad_children['children'], []) + self.assertEqual([], config_bad_children['children']) def test_check_config_bad_commands(self): """ Check config with bad commands entry """ @@ -166,7 +166,7 @@ class TestMediaPlayer(unittest.TestCase): response = universal.validate_config(config) self.assertTrue(response) - self.assertEqual(config['commands'], {}) + self.assertEqual({}, config['commands']) def test_check_config_bad_attributes(self): """ Check config with bad attributes """ @@ -174,7 +174,7 @@ class TestMediaPlayer(unittest.TestCase): response = universal.validate_config(config) self.assertTrue(response) - self.assertEqual(config['attributes'], {}) + self.assertEqual({}, config['attributes']) def test_check_config_bad_key(self): """ check config with bad key """ @@ -196,8 +196,8 @@ class TestMediaPlayer(unittest.TestCase): universal.setup_platform(self.hass, config, add_devices) - self.assertEqual(len(entities), 1) - self.assertEqual(entities[0].name, 'test') + self.assertEqual(1, len(entities)) + self.assertEqual('test', entities[0].name) def test_master_state(self): """ test master state property """ @@ -294,20 +294,20 @@ class TestMediaPlayer(unittest.TestCase): ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config['name']) ump.update() - self.assertEqual(ump.state, STATE_OFF) + self.assertEqual(STATE_OFF, ump.state) self.hass.states.set(self.mock_state_switch_id, STATE_ON) ump.update() - self.assertEqual(ump.state, STATE_ON) + self.assertEqual(STATE_ON, ump.state) self.mock_mp_1._state = STATE_PLAYING self.mock_mp_1.update_ha_state() ump.update() - self.assertEqual(ump.state, STATE_PLAYING) + self.assertEqual(STATE_PLAYING, ump.state) self.hass.states.set(self.mock_state_switch_id, STATE_OFF) ump.update() - self.assertEqual(ump.state, STATE_OFF) + self.assertEqual(STATE_OFF, ump.state) def test_volume_level(self): """ test volume level property """ @@ -419,7 +419,7 @@ class TestMediaPlayer(unittest.TestCase): ump.update() ump.turn_off() - self.assertEqual(len(self.mock_mp_2.turn_off_service_calls), 1) + self.assertEqual(1, len(self.mock_mp_2.turn_off_service_calls)) def test_service_call_to_command(self): config = self.config_children_only @@ -438,4 +438,4 @@ class TestMediaPlayer(unittest.TestCase): ump.update() ump.turn_off() - self.assertEqual(len(service), 1) + self.assertEqual(1, len(service)) From 56ac4281c75bc450bc7ff182068b914d69d9202b Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Sat, 30 Jan 2016 14:39:17 -0500 Subject: [PATCH 017/232] Better tear down of util/package tests Explicitly removed temp directory at the end of util/package unit tests. --- tests/util/test_package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/util/test_package.py b/tests/util/test_package.py index 7c10134c60a..db5a8a88e94 100644 --- a/tests/util/test_package.py +++ b/tests/util/test_package.py @@ -27,7 +27,7 @@ class TestPackageUtil(unittest.TestCase): def tearDown(self): """ Remove local library """ - del self.tmp_dir + self.tmp_dir.cleanup() def test_install_existing_package(self): """ Test an install attempt on an existing package """ From f76dee8a05b89a89616b0b9fa284aae20de61ea8 Mon Sep 17 00:00:00 2001 From: Tim Date: Sat, 30 Jan 2016 20:48:57 +0000 Subject: [PATCH 018/232] Update to new release of liffylights --- homeassistant/components/light/lifx.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/light/lifx.py b/homeassistant/components/light/lifx.py index bc8b752f788..79c019c8417 100644 --- a/homeassistant/components/light/lifx.py +++ b/homeassistant/components/light/lifx.py @@ -28,7 +28,7 @@ from homeassistant.components.light import \ _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['liffylights==0.9.0'] +REQUIREMENTS = ['liffylights==0.9.2'] DEPENDENCIES = [] CONF_SERVER = "server" # server address configuration item diff --git a/requirements_all.txt b/requirements_all.txt index d38b7044ddd..b48088467c4 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -52,7 +52,7 @@ blinkstick==1.1.7 phue==0.8 # homeassistant.components.light.lifx -liffylights==0.9.0 +liffylights==0.9.2 # homeassistant.components.light.limitlessled limitlessled==1.0.0 From fd6086a5d61f0bec4dae9e3fd14bdc4254b0407a Mon Sep 17 00:00:00 2001 From: magnusknutas Date: Sat, 30 Jan 2016 22:14:10 +0100 Subject: [PATCH 019/232] Testing logbook service --- tests/components/test_logbook.py | 63 ++++++++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 8 deletions(-) diff --git a/tests/components/test_logbook.py b/tests/components/test_logbook.py index 8b394559512..d2879b1f308 100644 --- a/tests/components/test_logbook.py +++ b/tests/components/test_logbook.py @@ -14,20 +14,57 @@ from homeassistant.const import ( import homeassistant.util.dt as dt_util from homeassistant.components import logbook -from tests.common import get_test_home_assistant, mock_http_component +from tests.common import mock_http_component class TestComponentHistory(unittest.TestCase): """ Tests homeassistant.components.history module. """ - def test_setup(self): + def setUp(self): """ Test setup method. """ - try: - hass = get_test_home_assistant() - mock_http_component(hass) - self.assertTrue(logbook.setup(hass, {})) - finally: - hass.stop() + self.hass = ha.HomeAssistant() + mock_http_component(self.hass) + self.assertTrue(logbook.setup(self.hass, {})) + + def tearDown(self): + self.hass.stop() + + def test_service_call_create_logbook_entry(self): + calls = [] + + def event_listener(event): + calls.append(event) + + self.hass.bus.listen(logbook.EVENT_LOGBOOK_ENTRY, event_listener) + self.hass.services.call(logbook.DOMAIN, 'log', { + logbook.ATTR_NAME: 'Alarm', + logbook.ATTR_MESSAGE: 'is triggered', + logbook.ATTR_DOMAIN: 'switch', + logbook.ATTR_ENTITY_ID: 'test_switch' + }, True) + self.hass.pool.block_till_done() + + self.assertEqual(1, len(calls)) + last_call = calls[-1] + + self.assertEqual('Alarm', last_call.data.get(logbook.ATTR_NAME)) + self.assertEqual('is triggered', last_call.data.get( + logbook.ATTR_MESSAGE)) + self.assertEqual('switch', last_call.data.get(logbook.ATTR_DOMAIN)) + self.assertEqual('test_switch', last_call.data.get( + logbook.ATTR_ENTITY_ID)) + + def test_service_call_create_log_book_entry_no_message(self): + calls = [] + + def event_listener(event): + calls.append(event) + + self.hass.bus.listen(logbook.EVENT_LOGBOOK_ENTRY, event_listener) + self.hass.services.call(logbook.DOMAIN, 'log', {}, True) + self.hass.pool.block_till_done() + + self.assertEqual(0, len(calls)) def test_humanify_filter_sensor(self): """ Test humanify filter too frequent sensor values. """ @@ -50,6 +87,16 @@ class TestComponentHistory(unittest.TestCase): self.assert_entry( entries[1], pointC, 'bla', domain='sensor', entity_id=entity_id) + def test_entry_to_dict(self): + entry = logbook.Entry( + dt_util.utcnow(), 'Alarm', 'is triggered', 'switch', 'test_switch' + ) + data = entry.as_dict() + self.assertEqual('Alarm', data.get(logbook.ATTR_NAME)) + self.assertEqual('is triggered', data.get(logbook.ATTR_MESSAGE)) + self.assertEqual('switch', data.get(logbook.ATTR_DOMAIN)) + self.assertEqual('test_switch', data.get(logbook.ATTR_ENTITY_ID)) + def test_home_assistant_start_stop_grouped(self): """ Tests if home assistant start and stop events are grouped if occuring in the same minute. """ From b7722ec4523c3144c5bf98cc9d13311448c2c74c Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 30 Jan 2016 15:16:31 -0800 Subject: [PATCH 020/232] Allow usage of words domain, service, call_id in service data --- homeassistant/components/mqtt_eventstream.py | 3 +- homeassistant/const.py | 1 + homeassistant/core.py | 32 +++++++++++--------- tests/components/test_mqtt.py | 6 ++-- 4 files changed, 25 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/mqtt_eventstream.py b/homeassistant/components/mqtt_eventstream.py index 53573205378..e69639572ca 100644 --- a/homeassistant/components/mqtt_eventstream.py +++ b/homeassistant/components/mqtt_eventstream.py @@ -11,6 +11,7 @@ from homeassistant.core import EventOrigin, State from homeassistant.components.mqtt import DOMAIN as MQTT_DOMAIN from homeassistant.components.mqtt import SERVICE_PUBLISH as MQTT_SVC_PUBLISH from homeassistant.const import ( + ATTR_SERVICE_DATA, MATCH_ALL, EVENT_TIME_CHANGED, EVENT_CALL_SERVICE, @@ -46,7 +47,7 @@ def setup(hass, config): if ( event.data.get('domain') == MQTT_DOMAIN and event.data.get('service') == MQTT_SVC_PUBLISH and - event.data.get('topic') == pub_topic + event.data[ATTR_SERVICE_DATA].get('topic') == pub_topic ): return diff --git a/homeassistant/const.py b/homeassistant/const.py index 4109b32a263..e28c418d9e4 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -67,6 +67,7 @@ ATTR_NOW = "now" # Contains domain, service for a SERVICE_CALL event ATTR_DOMAIN = "domain" ATTR_SERVICE = "service" +ATTR_SERVICE_DATA = "service_data" # Data for a SERVICE_EXECUTED event ATTR_SERVICE_CALL_ID = "service_call_id" diff --git a/homeassistant/core.py b/homeassistant/core.py index 853d09020ce..6f95cedb9a9 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -19,7 +19,7 @@ from homeassistant.const import ( SERVICE_HOMEASSISTANT_STOP, EVENT_TIME_CHANGED, EVENT_STATE_CHANGED, 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) + TEMP_CELCIUS, TEMP_FAHRENHEIT, ATTR_FRIENDLY_NAME, ATTR_SERVICE_DATA) from homeassistant.exceptions import ( HomeAssistantError, InvalidEntityFormatError) import homeassistant.util as util @@ -555,13 +555,14 @@ class Service(object): class ServiceCall(object): """Represents a call to a service.""" - __slots__ = ['domain', 'service', 'data'] + __slots__ = ['domain', 'service', 'data', 'call_id'] - def __init__(self, domain, service, data=None): + def __init__(self, domain, service, data=None, call_id=None): """Initialize a service call.""" self.domain = domain self.service = service self.data = data or {} + self.call_id = call_id def __repr__(self): if self.data: @@ -633,10 +634,13 @@ class ServiceRegistry(object): the keys ATTR_DOMAIN and ATTR_SERVICE in your service_data. """ call_id = self._generate_unique_id() - event_data = service_data or {} - event_data[ATTR_DOMAIN] = domain - event_data[ATTR_SERVICE] = service - event_data[ATTR_SERVICE_CALL_ID] = call_id + + event_data = { + ATTR_DOMAIN: domain, + ATTR_SERVICE: service, + ATTR_SERVICE_DATA: service_data, + ATTR_SERVICE_CALL_ID: call_id, + } if blocking: executed_event = threading.Event() @@ -658,15 +662,16 @@ class ServiceRegistry(object): def _event_to_service_call(self, event): """Callback for SERVICE_CALLED events from the event bus.""" - service_data = dict(event.data) - domain = service_data.pop(ATTR_DOMAIN, None) - service = service_data.pop(ATTR_SERVICE, None) + service_data = event.data.get(ATTR_SERVICE_DATA) + domain = event.data.get(ATTR_DOMAIN) + service = event.data.get(ATTR_SERVICE) + call_id = event.data.get(ATTR_SERVICE_CALL_ID) if not self.has_service(domain, service): return service_handler = self._services[domain][service] - service_call = ServiceCall(domain, service, service_data) + service_call = ServiceCall(domain, service, service_data, call_id) # Add a job to the pool that calls _execute_service self._pool.add_job(JobPriority.EVENT_SERVICE, @@ -678,10 +683,9 @@ class ServiceRegistry(object): service, call = service_and_call service(call) - if ATTR_SERVICE_CALL_ID in call.data: + if call.call_id is not None: self._bus.fire( - EVENT_SERVICE_EXECUTED, - {ATTR_SERVICE_CALL_ID: call.data[ATTR_SERVICE_CALL_ID]}) + EVENT_SERVICE_EXECUTED, {ATTR_SERVICE_CALL_ID: call.call_id}) def _generate_unique_id(self): """Generate a unique service call id.""" diff --git a/tests/components/test_mqtt.py b/tests/components/test_mqtt.py index 40e473a3572..c36459e5500 100644 --- a/tests/components/test_mqtt.py +++ b/tests/components/test_mqtt.py @@ -63,8 +63,10 @@ class TestMQTT(unittest.TestCase): self.hass.pool.block_till_done() self.assertEqual(1, len(self.calls)) - self.assertEqual('test-topic', self.calls[0][0].data[mqtt.ATTR_TOPIC]) - self.assertEqual('test-payload', self.calls[0][0].data[mqtt.ATTR_PAYLOAD]) + self.assertEqual('test-topic', + self.calls[0][0].data['service_data'][mqtt.ATTR_TOPIC]) + self.assertEqual('test-payload', + self.calls[0][0].data['service_data'][mqtt.ATTR_PAYLOAD]) def test_service_call_without_topic_does_not_publush(self): self.hass.bus.fire(EVENT_CALL_SERVICE, { From 4b253d17badb08bea76432df9376be84a8e5626b Mon Sep 17 00:00:00 2001 From: Erik Date: Sun, 31 Jan 2016 00:46:08 +0100 Subject: [PATCH 021/232] use yaml safe loader --- homeassistant/util/yaml.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/util/yaml.py b/homeassistant/util/yaml.py index 26d7c6c316e..50355e43799 100644 --- a/homeassistant/util/yaml.py +++ b/homeassistant/util/yaml.py @@ -19,7 +19,7 @@ def load_yaml(fname): with open(fname, encoding='utf-8') as conf_file: # If configuration file is empty YAML returns None # We convert that to an empty dict - return yaml.load(conf_file) or {} + return yaml.safe_load(conf_file) or {} except yaml.YAMLError: error = 'Error reading YAML configuration file {}'.format(fname) _LOGGER.exception(error) @@ -45,6 +45,6 @@ def _ordered_dict(loader, node): return OrderedDict(loader.construct_pairs(node)) -yaml.add_constructor('!include', _include_yaml) -yaml.add_constructor(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, - _ordered_dict) +yaml.SafeLoader.add_constructor('!include', _include_yaml) +yaml.SafeLoader.add_constructor(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, + _ordered_dict) From 265102146123a0495989c165475a7dc7fc340b6b Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Sat, 30 Jan 2016 21:27:00 -0500 Subject: [PATCH 022/232] Added test for entity customization Added test for entity customization from configuration. Processes a sample configuration to hide an entity, creates the entity, updates ha state, and then verifies customization made it through. --- tests/test_bootstrap.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py index a0c4da894f0..688e5fb0b41 100644 --- a/tests/test_bootstrap.py +++ b/tests/test_bootstrap.py @@ -11,8 +11,10 @@ import unittest from unittest import mock from homeassistant import core, bootstrap -from homeassistant.const import __version__ +from homeassistant.const import (__version__, CONF_LATITUDE, CONF_LONGITUDE, + CONF_NAME, CONF_CUSTOMIZE) import homeassistant.util.dt as dt_util +from homeassistant.helpers.entity import Entity from tests.common import mock_detect_location_info @@ -83,3 +85,23 @@ class TestBootstrap(unittest.TestCase): bootstrap.process_ha_config_upgrade(hass) self.assertTrue(os.path.isfile(check_file)) + + def test_entity_customization(self): + """ Test entity customization through config """ + config = {CONF_LATITUDE: 50, + CONF_LONGITUDE: 50, + CONF_NAME: 'Test', + CONF_CUSTOMIZE: {'test.test': {'hidden': True}}} + + hass = core.HomeAssistant() + + bootstrap.process_ha_core_config(hass, config) + + entity = Entity() + entity.entity_id = 'test.test' + entity.hass = hass + entity.update_ha_state() + + state = hass.states.get('test.test') + + self.assertTrue(state.attributes['hidden']) From 97e867052d2b88dc487e473ac681aaef27303247 Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Sat, 30 Jan 2016 22:01:10 -0500 Subject: [PATCH 023/232] Added tests for command sensor Added tests to create and check basic functionality of command sensor. --- .../components/sensor/test_command_sensor.py | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 tests/components/sensor/test_command_sensor.py diff --git a/tests/components/sensor/test_command_sensor.py b/tests/components/sensor/test_command_sensor.py new file mode 100644 index 00000000000..7059e715ffb --- /dev/null +++ b/tests/components/sensor/test_command_sensor.py @@ -0,0 +1,45 @@ +""" +tests.components.sensor.command_sensor +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Tests command sensor. +""" + +import unittest + +import homeassistant.core as ha +from homeassistant.components.sensor import command_sensor + + +class TestCommandSensorSensor(unittest.TestCase): + """ Test the Template sensor. """ + + def setUp(self): + self.hass = ha.HomeAssistant() + + self.config = {'name': 'Test', + 'unit_of_measurement': 'in', + 'command': 'echo 5', + 'value_template': '{{ value }}'} + + def tearDown(self): + """ Stop down stuff we started. """ + self.hass.stop() + + def test_setup(self): + """ Test sensor setup """ + devices = [] + + def add_dev_callback(devs): + """ callback to add device """ + for dev in devs: + devices.append(dev) + + command_sensor.setup_platform( + self.hass, self.config, add_dev_callback) + + self.assertEqual(1, len(devices)) + entity = devices[0] + self.assertEqual('Test', entity.name) + self.assertEqual('in', entity.unit_of_measurement) + self.assertEqual('5', entity.state) From 6a08f1412048ad2578e71a60bc284b671de9eab4 Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Sat, 30 Jan 2016 22:13:42 -0500 Subject: [PATCH 024/232] Additional tests for Command Sensor. 1. Moved template testing out of main test. 2. Added test for bad command. --- .../components/sensor/test_command_sensor.py | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/tests/components/sensor/test_command_sensor.py b/tests/components/sensor/test_command_sensor.py index 7059e715ffb..21cd10a5e46 100644 --- a/tests/components/sensor/test_command_sensor.py +++ b/tests/components/sensor/test_command_sensor.py @@ -17,17 +17,15 @@ class TestCommandSensorSensor(unittest.TestCase): def setUp(self): self.hass = ha.HomeAssistant() - self.config = {'name': 'Test', - 'unit_of_measurement': 'in', - 'command': 'echo 5', - 'value_template': '{{ value }}'} - def tearDown(self): """ Stop down stuff we started. """ self.hass.stop() def test_setup(self): """ Test sensor setup """ + config = {'name': 'Test', + 'unit_of_measurement': 'in', + 'command': 'echo 5'} devices = [] def add_dev_callback(devs): @@ -36,10 +34,26 @@ class TestCommandSensorSensor(unittest.TestCase): devices.append(dev) command_sensor.setup_platform( - self.hass, self.config, add_dev_callback) + self.hass, config, add_dev_callback) self.assertEqual(1, len(devices)) entity = devices[0] self.assertEqual('Test', entity.name) self.assertEqual('in', entity.unit_of_measurement) self.assertEqual('5', entity.state) + + def test_template(self): + """ Test command sensor with template """ + data = command_sensor.CommandSensorData('echo 50') + + entity = command_sensor.CommandSensor( + self.hass, data, 'test', 'in', '{{ value | multiply(0.1) }}') + + self.assertEqual(5, float(entity.state)) + + def test_bad_command(self): + """ Test bad command """ + data = command_sensor.CommandSensorData('asdfasdf') + data.update() + + self.assertEqual(None, data.value) From 2d0004f46aa6844b1de7690c20b5da72fa98d760 Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Sat, 30 Jan 2016 22:16:22 -0500 Subject: [PATCH 025/232] Another test for for command sensor Added a test for command sensors with bad configurations. --- tests/components/sensor/test_command_sensor.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/components/sensor/test_command_sensor.py b/tests/components/sensor/test_command_sensor.py index 21cd10a5e46..ae6c9452d3f 100644 --- a/tests/components/sensor/test_command_sensor.py +++ b/tests/components/sensor/test_command_sensor.py @@ -42,6 +42,22 @@ class TestCommandSensorSensor(unittest.TestCase): self.assertEqual('in', entity.unit_of_measurement) self.assertEqual('5', entity.state) + def test_setup_bad_config(self): + """ Test setup with a bad config """ + config = {} + + devices = [] + + def add_dev_callback(devs): + """ callback to add device """ + for dev in devs: + devices.append(dev) + + self.assertFalse(command_sensor.setup_platform( + self.hass, config, add_dev_callback)) + + self.assertEqual(0, len(devices)) + def test_template(self): """ Test command sensor with template """ data = command_sensor.CommandSensorData('echo 50') From 283d621e90f2806487f50d01d094b0dc090d2dae Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Sat, 30 Jan 2016 22:32:25 -0500 Subject: [PATCH 026/232] Added tests for Binary Command Sensor --- .../binary_sensor/test_command_sensor.py | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 tests/components/binary_sensor/test_command_sensor.py diff --git a/tests/components/binary_sensor/test_command_sensor.py b/tests/components/binary_sensor/test_command_sensor.py new file mode 100644 index 00000000000..011946b1279 --- /dev/null +++ b/tests/components/binary_sensor/test_command_sensor.py @@ -0,0 +1,68 @@ +""" +tests.components.binary_sensor.command_sensor +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Tests command binary sensor. +""" + +import unittest + +import homeassistant.core as ha +from homeassistant.components.binary_sensor import command_sensor + + +class TestCommandSensorBinarySensor(unittest.TestCase): + """ Test the Template sensor. """ + + def setUp(self): + self.hass = ha.HomeAssistant() + + def tearDown(self): + """ Stop down stuff we started. """ + self.hass.stop() + + def test_setup(self): + """ Test sensor setup """ + config = {'name': 'Test', + 'command': 'echo 1', + 'payload_on': '1', + 'payload_off': '0'} + devices = [] + + def add_dev_callback(devs): + """ callback to add device """ + for dev in devs: + devices.append(dev) + + command_sensor.setup_platform( + self.hass, config, add_dev_callback) + + self.assertEqual(1, len(devices)) + entity = devices[0] + self.assertEqual('Test', entity.name) + self.assertTrue(entity.state) + + def test_setup_bad_config(self): + """ Test setup with a bad config """ + config = {} + + devices = [] + + def add_dev_callback(devs): + """ callback to add device """ + for dev in devs: + devices.append(dev) + + self.assertFalse(command_sensor.setup_platform( + self.hass, config, add_dev_callback)) + + self.assertEqual(0, len(devices)) + + def test_template(self): + """ Test command sensor with template """ + data = command_sensor.CommandSensorData('echo 10') + + entity = command_sensor.CommandBinarySensor( + self.hass, data, 'test', '1.0', '0', '{{ value | multiply(0.1) }}') + + self.assertTrue(entity.state) From 5fdbe5fd9ac8d99af306d6ac53342af41ee4170c Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Sat, 30 Jan 2016 22:41:29 -0500 Subject: [PATCH 027/232] More tests for Binary Command Sensor 1. Added a test for detecting STATE_OFF 2. Fixed tests for detecting STATE_ON --- .../binary_sensor/test_command_sensor.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/tests/components/binary_sensor/test_command_sensor.py b/tests/components/binary_sensor/test_command_sensor.py index 011946b1279..aa6a87c2061 100644 --- a/tests/components/binary_sensor/test_command_sensor.py +++ b/tests/components/binary_sensor/test_command_sensor.py @@ -8,6 +8,7 @@ Tests command binary sensor. import unittest import homeassistant.core as ha +from homeassistant.const import (STATE_ON, STATE_OFF) from homeassistant.components.binary_sensor import command_sensor @@ -40,7 +41,7 @@ class TestCommandSensorBinarySensor(unittest.TestCase): self.assertEqual(1, len(devices)) entity = devices[0] self.assertEqual('Test', entity.name) - self.assertTrue(entity.state) + self.assertEqual(STATE_ON, entity.state) def test_setup_bad_config(self): """ Test setup with a bad config """ @@ -65,4 +66,13 @@ class TestCommandSensorBinarySensor(unittest.TestCase): entity = command_sensor.CommandBinarySensor( self.hass, data, 'test', '1.0', '0', '{{ value | multiply(0.1) }}') - self.assertTrue(entity.state) + self.assertEqual(STATE_ON, entity.state) + + def test_sensor_off(self): + """ Test command sensor with template """ + data = command_sensor.CommandSensorData('echo 0') + + entity = command_sensor.CommandBinarySensor( + self.hass, data, 'test', '1', '0', None) + + self.assertEqual(STATE_OFF, entity.state) From a230d00ed02ac3044b5bd8c840b8ff8edd167ffc Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Sat, 30 Jan 2016 22:50:56 -0500 Subject: [PATCH 028/232] Added test for Introduction component MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This test may seem useless, but it is good to ensure that default components don’t ever crash HASS. --- tests/components/test_introduction.py | 28 +++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 tests/components/test_introduction.py diff --git a/tests/components/test_introduction.py b/tests/components/test_introduction.py new file mode 100644 index 00000000000..42c16081d1e --- /dev/null +++ b/tests/components/test_introduction.py @@ -0,0 +1,28 @@ +""" +tests.components.introduction +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Test introduction. + +This test is primarily to ensure that default components don't crash HASS. +""" + +import unittest + +import homeassistant.core as ha +from homeassistant.components import introduction + + +class TestIntroduction(unittest.TestCase): + """ Test Introduction. """ + + def setUp(self): + self.hass = ha.HomeAssistant() + + def tearDown(self): + """ Stop down stuff we started. """ + self.hass.stop() + + def test_setup(self): + """ Test Introduction setup """ + self.assertTrue(introduction.setup(self.hass, {})) From 90e17fc77f6fcabe84de8402bb8c62946d43126d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 30 Jan 2016 18:55:52 -0800 Subject: [PATCH 029/232] Add tests for entity component --- homeassistant/helpers/entity.py | 5 +- homeassistant/helpers/entity_component.py | 50 +++-- tests/common.py | 19 +- tests/helpers/test_entity_component.py | 236 ++++++++++++++++++++++ 4 files changed, 278 insertions(+), 32 deletions(-) create mode 100644 tests/helpers/test_entity_component.py diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index ab5707a0121..8b4e1be2d52 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -50,8 +50,6 @@ class Entity(object): """ ABC for Home Assistant entities. """ # pylint: disable=no-self-use - _hidden = False - # SAFE TO OVERWRITE # The properties and methods here are safe to overwrite when inherting this # class. These may be used to customize the behavior of the entity. @@ -103,13 +101,14 @@ class Entity(object): """ Retrieve latest state. """ pass + entity_id = None + # DO NOT OVERWRITE # These properties and methods are either managed by Home Assistant or they # are used to perform a very specific function. Overwriting these may # produce undesirable effects in the entity's operation. hass = None - entity_id = None def update_ha_state(self, force_refresh=False): """ diff --git a/homeassistant/helpers/entity_component.py b/homeassistant/helpers/entity_component.py index 0450a788809..3382d90b62b 100644 --- a/homeassistant/helpers/entity_component.py +++ b/homeassistant/helpers/entity_component.py @@ -1,9 +1,4 @@ -""" -homeassistant.helpers.entity_component -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Provides helpers for components that manage entities. -""" +"""Provides helpers for components that manage entities.""" from threading import Lock from homeassistant.bootstrap import prepare_setup_platform @@ -18,14 +13,14 @@ DEFAULT_SCAN_INTERVAL = 15 class EntityComponent(object): + """Helper class that will help a component manage its entities.""" + # pylint: disable=too-many-instance-attributes # pylint: disable=too-many-arguments - """ - Helper class that will help a component manage its entities. - """ def __init__(self, logger, domain, hass, scan_interval=DEFAULT_SCAN_INTERVAL, discovery_platforms=None, group_name=None): + """Initialize an entity component.""" self.logger = logger self.hass = hass @@ -44,9 +39,10 @@ class EntityComponent(object): def setup(self, config): """ - Sets up a full entity component: - - Loads the platforms from the config - - Will listen for supported discovered platforms + Set up a full entity component. + + Loads the platforms from the config and will listen for supported + discovered platforms. """ self.config = config @@ -57,13 +53,18 @@ class EntityComponent(object): self._setup_platform(p_type, p_config) if self.discovery_platforms: - discovery.listen(self.hass, self.discovery_platforms.keys(), - self._entity_discovered) + discovery.listen( + self.hass, self.discovery_platforms.keys(), + lambda service, info: + self._setup_platform(self.discovery_platforms[service], {}, + info)) def add_entities(self, new_entities): """ - Takes in a list of new entities. For each entity will see if it already - exists. If not, will add it, set it up and push the first state. + Add new entities to this component. + + For each entity will see if it already exists. If not, will add it, + set it up and push the first state. """ with self.lock: for entity in new_entities: @@ -101,8 +102,10 @@ class EntityComponent(object): def extract_from_service(self, service): """ - Takes a service and extracts all known entities. - Will return all if no entity IDs given in service. + Extract all known entities from a service call. + + Will return all entities if no entities specified in call. + Will return an empty list if entities specified but unknown. """ with self.lock: if ATTR_ENTITY_ID not in service.data: @@ -113,7 +116,7 @@ class EntityComponent(object): if entity_id in self.entities] def _update_entity_states(self, now): - """ Update the states of all the entities. """ + """Update the states of all the polling entities.""" with self.lock: # We copy the entities because new entities might be detected # during state update causing deadlocks. @@ -125,16 +128,9 @@ class EntityComponent(object): for entity in entities: entity.update_ha_state(True) - def _entity_discovered(self, service, info): - """ Called when a entity is discovered. """ - if service not in self.discovery_platforms: - return - - self._setup_platform(self.discovery_platforms[service], {}, info) - def _setup_platform(self, platform_type, platform_config, discovery_info=None): - """ Tries to setup a platform for this component. """ + """Setup a platform for this component.""" platform = prepare_setup_platform( self.hass, self.config, self.domain, platform_type) diff --git a/tests/common.py b/tests/common.py index b8108c673fd..350786c8e14 100644 --- a/tests/common.py +++ b/tests/common.py @@ -145,11 +145,26 @@ class MockHTTP(object): class MockModule(object): """ Provides a fake module. """ - def __init__(self, domain, dependencies=[], setup=None): + def __init__(self, domain=None, dependencies=[], setup=None): self.DOMAIN = domain self.DEPENDENCIES = dependencies # Setup a mock setup if none given. - self.setup = lambda hass, config: False if setup is None else setup + if setup is None: + self.setup = lambda hass, config: False + else: + self.setup = setup + + +class MockPlatform(object): + """ Provides a fake platform. """ + + def __init__(self, setup_platform=None, dependencies=[]): + self.DEPENDENCIES = dependencies + self._setup_platform = setup_platform + + def setup_platform(self, hass, config, add_devices, discovery_info=None): + if self._setup_platform is not None: + self._setup_platform(hass, config, add_devices, discovery_info) class MockToggleDevice(ToggleEntity): diff --git a/tests/helpers/test_entity_component.py b/tests/helpers/test_entity_component.py new file mode 100644 index 00000000000..5f21c2193ce --- /dev/null +++ b/tests/helpers/test_entity_component.py @@ -0,0 +1,236 @@ +""" +tests.test_helper_entity_component +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Tests the entity component helper. +""" +# pylint: disable=protected-access,too-many-public-methods +from collections import OrderedDict +import logging +import unittest +from unittest.mock import patch, Mock + +import homeassistant.core as ha +import homeassistant.loader as loader +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.entity_component import EntityComponent +from homeassistant.components import discovery + +from tests.common import get_test_home_assistant, MockPlatform, MockModule + +_LOGGER = logging.getLogger(__name__) +DOMAIN = "test_domain" + + +class EntityTest(Entity): + def __init__(self, **values): + self._values = values + + if 'entity_id' in values: + self.entity_id = values['entity_id'] + + @property + def name(self): + return self._handle('name') + + @property + def should_poll(self): + return self._handle('should_poll') + + @property + def unique_id(self): + return self._handle('unique_id') + + def _handle(self, attr): + if attr in self._values: + return self._values[attr] + return getattr(super(), attr) + + +class TestHelpersEntityComponent(unittest.TestCase): + """ Tests homeassistant.helpers.entity_component module. """ + + def setUp(self): # pylint: disable=invalid-name + """Initialize a test Home Assistant instance.""" + self.hass = get_test_home_assistant() + + def tearDown(self): # pylint: disable=invalid-name + """Clean up the test Home Assistant instance.""" + self.hass.stop() + + def test_setting_up_group(self): + component = EntityComponent(_LOGGER, DOMAIN, self.hass, + group_name='everyone') + + # No group after setup + assert 0 == len(self.hass.states.entity_ids()) + + component.add_entities([EntityTest(name='hello')]) + + # group exists + assert 2 == len(self.hass.states.entity_ids()) + assert ['group.everyone'] == self.hass.states.entity_ids('group') + + group = self.hass.states.get('group.everyone') + + assert ('test_domain.hello',) == group.attributes.get('entity_id') + + # group extended + component.add_entities([EntityTest(name='hello2')]) + + assert 3 == len(self.hass.states.entity_ids()) + group = self.hass.states.get('group.everyone') + + assert ['test_domain.hello', 'test_domain.hello2'] == \ + sorted(group.attributes.get('entity_id')) + + @patch('homeassistant.helpers.entity_component.track_utc_time_change') + def test_polling_only_updates_entities_it_should_poll(self, mock_track): + component = EntityComponent(_LOGGER, DOMAIN, self.hass, 20) + + no_poll_ent = EntityTest(should_poll=False) + no_poll_ent.update_ha_state = Mock() + poll_ent = EntityTest(should_poll=True) + poll_ent.update_ha_state = Mock() + + component.add_entities([no_poll_ent]) + assert not mock_track.called + + component.add_entities([poll_ent]) + assert mock_track.called + assert [0, 20, 40] == list(mock_track.call_args[1].get('second')) + + no_poll_ent.update_ha_state.reset_mock() + poll_ent.update_ha_state.reset_mock() + + component._update_entity_states(None) + + assert not no_poll_ent.update_ha_state.called + assert poll_ent.update_ha_state.called + + def test_update_state_adds_entities(self): + """Test if updating poll entities cause an entity to be added works.""" + component = EntityComponent(_LOGGER, DOMAIN, self.hass) + + ent1 = EntityTest() + ent2 = EntityTest(should_poll=True) + + component.add_entities([ent2]) + assert 1 == len(self.hass.states.entity_ids()) + ent2.update_ha_state = lambda *_: component.add_entities([ent1]) + component._update_entity_states(None) + assert 2 == len(self.hass.states.entity_ids()) + + def test_not_adding_duplicate_entities(self): + component = EntityComponent(_LOGGER, DOMAIN, self.hass) + + assert 0 == len(self.hass.states.entity_ids()) + + component.add_entities([None, EntityTest(unique_id='not_very_unique')]) + + assert 1 == len(self.hass.states.entity_ids()) + + component.add_entities([EntityTest(unique_id='not_very_unique')]) + + assert 1 == len(self.hass.states.entity_ids()) + + def test_not_assigning_entity_id_if_prescribes_one(self): + component = EntityComponent(_LOGGER, DOMAIN, self.hass) + + assert 'hello.world' not in self.hass.states.entity_ids() + + component.add_entities([EntityTest(entity_id='hello.world')]) + + assert 'hello.world' in self.hass.states.entity_ids() + + def test_extract_from_service_returns_all_if_no_entity_id(self): + component = EntityComponent(_LOGGER, DOMAIN, self.hass) + component.add_entities([ + EntityTest(name='test_1'), + EntityTest(name='test_2'), + ]) + + call = ha.ServiceCall('test', 'service') + + assert ['test_domain.test_1', 'test_domain.test_2'] == \ + sorted(ent.entity_id for ent in + component.extract_from_service(call)) + + def test_extract_from_service_filter_out_non_existing_entities(self): + component = EntityComponent(_LOGGER, DOMAIN, self.hass) + component.add_entities([ + EntityTest(name='test_1'), + EntityTest(name='test_2'), + ]) + + call = ha.ServiceCall('test', 'service', { + 'entity_id': ['test_domain.test_2', 'test_domain.non_exist'] + }) + + assert ['test_domain.test_2'] == \ + [ent.entity_id for ent in component.extract_from_service(call)] + + def test_setup_loads_platforms(self): + component_setup = Mock(return_value=True) + platform_setup = Mock(return_value=None) + loader.set_component( + 'test_component', + MockModule('test_component', setup=component_setup)) + loader.set_component('test_domain.mod2', + MockPlatform(platform_setup, ['test_component'])) + + component = EntityComponent(_LOGGER, DOMAIN, self.hass) + + assert not component_setup.called + assert not platform_setup.called + + component.setup({ + DOMAIN: { + 'platform': 'mod2', + } + }) + + assert component_setup.called + assert platform_setup.called + + def test_setup_recovers_when_setup_raises(self): + platform1_setup = Mock(side_effect=Exception('Broken')) + platform2_setup = Mock(return_value=None) + + loader.set_component('test_domain.mod1', MockPlatform(platform1_setup)) + loader.set_component('test_domain.mod2', MockPlatform(platform2_setup)) + + component = EntityComponent(_LOGGER, DOMAIN, self.hass) + + assert not platform1_setup.called + assert not platform2_setup.called + + component.setup(OrderedDict([ + (DOMAIN, {'platform': 'mod1'}), + ("{} 2".format(DOMAIN), {'platform': 'non_exist'}), + ("{} 3".format(DOMAIN), {'platform': 'mod2'}), + ])) + + assert platform1_setup.called + assert platform2_setup.called + + @patch('homeassistant.helpers.entity_component.EntityComponent' + '._setup_platform') + def test_setup_does_discovery(self, mock_setup): + component = EntityComponent( + _LOGGER, DOMAIN, self.hass, discovery_platforms={ + 'discovery.test': 'platform_test', + }) + + component.setup({}) + + self.hass.bus.fire(discovery.EVENT_PLATFORM_DISCOVERED, { + discovery.ATTR_SERVICE: 'discovery.test', + discovery.ATTR_DISCOVERED: 'discovery_info', + }) + + self.hass.pool.block_till_done() + + assert mock_setup.called + assert ('platform_test', {}, 'discovery_info') == \ + mock_setup.call_args[0] From fce8815ab47c562f07a582100d76f43ca60fc55e Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 31 Jan 2016 00:55:46 -0800 Subject: [PATCH 030/232] Support custom interval for platforms --- homeassistant/const.py | 2 +- homeassistant/helpers/entity_component.py | 137 +++++++++++++--------- tests/helpers/test_entity_component.py | 63 ++++++++-- 3 files changed, 133 insertions(+), 69 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index e28c418d9e4..87be5fa5f6f 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -26,7 +26,7 @@ CONF_PASSWORD = "password" CONF_API_KEY = "api_key" CONF_ACCESS_TOKEN = "access_token" CONF_FILENAME = "filename" - +CONF_SCAN_INTERVAL = "scan_interval" CONF_VALUE_TEMPLATE = "value_template" # #### EVENTS #### diff --git a/homeassistant/helpers/entity_component.py b/homeassistant/helpers/entity_component.py index 3382d90b62b..268e4e7b696 100644 --- a/homeassistant/helpers/entity_component.py +++ b/homeassistant/helpers/entity_component.py @@ -1,6 +1,7 @@ """Provides helpers for components that manage entities.""" from threading import Lock +from homeassistant.const import CONF_SCAN_INTERVAL from homeassistant.bootstrap import prepare_setup_platform from homeassistant.helpers import config_per_platform from homeassistant.helpers.entity import generate_entity_id @@ -37,6 +38,9 @@ class EntityComponent(object): self.config = None self.lock = Lock() + self.add_entities = EntityPlatform(self, + self.scan_interval).add_entities + def setup(self, config): """ Set up a full entity component. @@ -59,47 +63,6 @@ class EntityComponent(object): self._setup_platform(self.discovery_platforms[service], {}, info)) - def add_entities(self, new_entities): - """ - Add new entities to this component. - - For each entity will see if it already exists. If not, will add it, - set it up and push the first state. - """ - with self.lock: - for entity in new_entities: - if entity is None or entity in self.entities.values(): - continue - - entity.hass = self.hass - - if getattr(entity, 'entity_id', None) is None: - entity.entity_id = generate_entity_id( - self.entity_id_format, entity.name, - self.entities.keys()) - - self.entities[entity.entity_id] = entity - - entity.update_ha_state() - - if self.group is None and self.group_name is not None: - self.group = group.Group(self.hass, self.group_name, - user_defined=False) - - if self.group is not None: - self.group.update_tracked_entity_ids(self.entities.keys()) - - if self.is_polling or \ - not any(entity.should_poll for entity - in self.entities.values()): - return - - self.is_polling = True - - track_utc_time_change( - self.hass, self._update_entity_states, - second=range(0, 60, self.scan_interval)) - def extract_from_service(self, service): """ Extract all known entities from a service call. @@ -115,19 +78,6 @@ class EntityComponent(object): in extract_entity_ids(self.hass, service) if entity_id in self.entities] - def _update_entity_states(self, now): - """Update the states of all the polling entities.""" - with self.lock: - # We copy the entities because new entities might be detected - # during state update causing deadlocks. - entities = list(entity for entity in self.entities.values() - if entity.should_poll) - - self.logger.info("Updating %s entities", self.domain) - - for entity in entities: - entity.update_ha_state(True) - def _setup_platform(self, platform_type, platform_config, discovery_info=None): """Setup a platform for this component.""" @@ -138,12 +88,85 @@ class EntityComponent(object): return try: + # Config > Platform > Component + scan_interval = platform_config.get( + CONF_SCAN_INTERVAL, + getattr(platform, 'SCAN_INTERVAL', self.scan_interval)) platform.setup_platform( - self.hass, platform_config, self.add_entities, discovery_info) + self.hass, platform_config, + EntityPlatform(self, scan_interval).add_entities, + discovery_info) + platform_name = '{}.{}'.format(self.domain, platform_type) + self.hass.config.components.append(platform_name) except Exception: # pylint: disable=broad-except self.logger.exception( 'Error while setting up platform %s', platform_type) return - platform_name = '{}.{}'.format(self.domain, platform_type) - self.hass.config.components.append(platform_name) + def add_entity(self, entity): + """Add entity to component.""" + if entity is None or entity in self.entities.values(): + return False + + entity.hass = self.hass + + if getattr(entity, 'entity_id', None) is None: + entity.entity_id = generate_entity_id( + self.entity_id_format, entity.name, + self.entities.keys()) + + self.entities[entity.entity_id] = entity + entity.update_ha_state() + + return True + + def update_group(self): + """Set up and/or update component group.""" + if self.group is None and self.group_name is not None: + self.group = group.Group(self.hass, self.group_name, + user_defined=False) + + if self.group is not None: + self.group.update_tracked_entity_ids(self.entities.keys()) + + +class EntityPlatform(object): + """Keep track of entities for a single platform.""" + + # pylint: disable=too-few-public-methods + def __init__(self, component, scan_interval): + self.component = component + self.scan_interval = scan_interval + self.platform_entities = [] + self.is_polling = False + + def add_entities(self, new_entities): + """Add entities for a single platform.""" + with self.component.lock: + for entity in new_entities: + if self.component.add_entity(entity): + self.platform_entities.append(entity) + + self.component.update_group() + + if self.is_polling or \ + not any(entity.should_poll for entity + in self.platform_entities): + return + + self.is_polling = True + + track_utc_time_change( + self.component.hass, self._update_entity_states, + second=range(0, 60, self.scan_interval)) + + def _update_entity_states(self, now): + """Update the states of all the polling entities.""" + with self.component.lock: + # We copy the entities because new entities might be detected + # during state update causing deadlocks. + entities = list(entity for entity in self.platform_entities + if entity.should_poll) + + for entity in entities: + entity.update_ha_state(True) diff --git a/tests/helpers/test_entity_component.py b/tests/helpers/test_entity_component.py index 5f21c2193ce..68aecd32f5b 100644 --- a/tests/helpers/test_entity_component.py +++ b/tests/helpers/test_entity_component.py @@ -15,8 +15,10 @@ import homeassistant.loader as loader from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent from homeassistant.components import discovery +import homeassistant.util.dt as dt_util -from tests.common import get_test_home_assistant, MockPlatform, MockModule +from tests.common import ( + get_test_home_assistant, MockPlatform, MockModule, fire_time_changed) _LOGGER = logging.getLogger(__name__) DOMAIN = "test_domain" @@ -84,8 +86,7 @@ class TestHelpersEntityComponent(unittest.TestCase): assert ['test_domain.hello', 'test_domain.hello2'] == \ sorted(group.attributes.get('entity_id')) - @patch('homeassistant.helpers.entity_component.track_utc_time_change') - def test_polling_only_updates_entities_it_should_poll(self, mock_track): + def test_polling_only_updates_entities_it_should_poll(self): component = EntityComponent(_LOGGER, DOMAIN, self.hass, 20) no_poll_ent = EntityTest(should_poll=False) @@ -93,17 +94,13 @@ class TestHelpersEntityComponent(unittest.TestCase): poll_ent = EntityTest(should_poll=True) poll_ent.update_ha_state = Mock() - component.add_entities([no_poll_ent]) - assert not mock_track.called - - component.add_entities([poll_ent]) - assert mock_track.called - assert [0, 20, 40] == list(mock_track.call_args[1].get('second')) + component.add_entities([no_poll_ent, poll_ent]) no_poll_ent.update_ha_state.reset_mock() poll_ent.update_ha_state.reset_mock() - component._update_entity_states(None) + fire_time_changed(self.hass, dt_util.utcnow().replace(second=0)) + self.hass.pool.block_till_done() assert not no_poll_ent.update_ha_state.called assert poll_ent.update_ha_state.called @@ -118,7 +115,10 @@ class TestHelpersEntityComponent(unittest.TestCase): component.add_entities([ent2]) assert 1 == len(self.hass.states.entity_ids()) ent2.update_ha_state = lambda *_: component.add_entities([ent1]) - component._update_entity_states(None) + + fire_time_changed(self.hass, dt_util.utcnow().replace(second=0)) + self.hass.pool.block_till_done() + assert 2 == len(self.hass.states.entity_ids()) def test_not_adding_duplicate_entities(self): @@ -234,3 +234,44 @@ class TestHelpersEntityComponent(unittest.TestCase): assert mock_setup.called assert ('platform_test', {}, 'discovery_info') == \ mock_setup.call_args[0] + + @patch('homeassistant.helpers.entity_component.track_utc_time_change') + def test_set_scan_interval_via_config(self, mock_track): + def platform_setup(hass, config, add_devices, discovery_info=None): + add_devices([EntityTest(should_poll=True)]) + + loader.set_component('test_domain.platform', + MockPlatform(platform_setup)) + + component = EntityComponent(_LOGGER, DOMAIN, self.hass) + + component.setup({ + DOMAIN: { + 'platform': 'platform', + 'scan_interval': 30, + } + }) + + assert mock_track.called + assert [0, 30] == list(mock_track.call_args[1]['second']) + + @patch('homeassistant.helpers.entity_component.track_utc_time_change') + def test_set_scan_interval_via_platform(self, mock_track): + def platform_setup(hass, config, add_devices, discovery_info=None): + add_devices([EntityTest(should_poll=True)]) + + platform = MockPlatform(platform_setup) + platform.SCAN_INTERVAL = 30 + + loader.set_component('test_domain.platform', platform) + + component = EntityComponent(_LOGGER, DOMAIN, self.hass) + + component.setup({ + DOMAIN: { + 'platform': 'platform', + } + }) + + assert mock_track.called + assert [0, 30] == list(mock_track.call_args[1]['second']) From bd475f5db1b665452fd65e946b2cf296d8f6df79 Mon Sep 17 00:00:00 2001 From: Stefan Jonasson Date: Sun, 31 Jan 2016 10:06:39 +0100 Subject: [PATCH 031/232] Added a new media_player platform for controlling Samsung TVs with a lan interface. Configured like this media_player: platform: samsungtv host: name: --- .../components/media_player/samsungtv.py | 175 ++++++++++++++++++ requirements_all.txt | 3 + 2 files changed, 178 insertions(+) create mode 100644 homeassistant/components/media_player/samsungtv.py diff --git a/homeassistant/components/media_player/samsungtv.py b/homeassistant/components/media_player/samsungtv.py new file mode 100644 index 00000000000..7cf23a84d3d --- /dev/null +++ b/homeassistant/components/media_player/samsungtv.py @@ -0,0 +1,175 @@ +""" +homeassistant.components.media_player.denon +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Provides an interface to Samsung TV with a Laninterface. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/media_player.samsungtv/ +""" +import logging +import socket +from samsungctl import Remote + +from homeassistant.components.media_player import ( + MediaPlayerDevice, SUPPORT_PAUSE, SUPPORT_VOLUME_STEP, + SUPPORT_VOLUME_MUTE, SUPPORT_PREVIOUS_TRACK, + SUPPORT_NEXT_TRACK, SUPPORT_TURN_OFF, + DOMAIN) +from homeassistant.const import ( + CONF_HOST, STATE_OFF, + STATE_ON, STATE_UNKNOWN) + +_LOGGER = logging.getLogger(__name__) + +SUPPORT_SAMSUNGTV = SUPPORT_PAUSE | SUPPORT_VOLUME_STEP | \ + SUPPORT_VOLUME_MUTE | SUPPORT_PREVIOUS_TRACK | \ + SUPPORT_NEXT_TRACK | SUPPORT_TURN_OFF + + +# pylint: disable=unused-argument +def setup_platform(hass, config, add_devices, discovery_info=None): + """ Sets up the Denon platform. """ + if not config.get(CONF_HOST): + _LOGGER.error( + "Missing required configuration items in %s: %s", + DOMAIN, + CONF_HOST) + return False + name = config.get("name", 'Samsung TV Remote') + # Generate a config for the Samsung lib + remote_config = { + "name": "HomeAssistant", + "description": config.get("name", ''), + "id": "ha.component.samsung", + "port": config.get("port", 55000), + "host": config.get("host"), + "timeout": config.get("timeout", 0), + } + + add_devices([SamsungTVDevice(name, remote_config)]) + + +# pylint: disable=too-many-public-methods +class SamsungTVDevice(MediaPlayerDevice): + """ Represents a Denon device. """ + + def set_volume_level(self, volume): + pass + + # pylint: disable=too-many-public-methods + + def __init__(self, name, config): + self._name = name + # Assume that the TV is not muted + self._muted = False + # Assume that the TV is in Play mode + self._playing = True + self._state = STATE_UNKNOWN + self._remote = None + self._config = config + + def update(self): + # Send an empty key to see if we are still connected + return self.send_key('KEY_POWER') + + def get_remote(self): + """ Creates or Returns a remote control instance """ + if self._remote is None: + # We need to create a new instance to reconnect. + self._remote = Remote(self._config) + + return self._remote + + def send_key(self, key): + """ Sends a key to the tv and handles exceptions """ + try: + self.get_remote().control(key) + self._state = STATE_ON + except (Remote.UnhandledResponse, Remote.AccessDenied, + BrokenPipeError): + # We got a response so it's on. + # BrokenPipe can occur when the commands is sent to fast + self._state = STATE_ON + self._remote = None + return False + except (Remote.ConnectionClosed, Remote.ConnectionClosed, + socket.timeout, TimeoutError, OSError): + self._state = STATE_OFF + self._remote = None + return False + except Remote.AccessDenied: + self._state = STATE_ON + return False + + return True + + @property + def name(self): + """ Returns the name of the device. """ + return self._name + + @property + def state(self): + return self._state + + @property + def is_volume_muted(self): + """ Boolean if volume is currently muted. """ + return self._muted + + @property + def supported_media_commands(self): + """ Flags of media commands that are supported. """ + return SUPPORT_SAMSUNGTV + + def turn_off(self): + """ turn_off media player. """ + self.send_key("KEY_POWEROFF") + + def volume_up(self): + """ volume_up media player. """ + self.send_key("KEY_VOLUP") + + def volume_down(self): + """ volume_down media player. """ + self.send_key("KEY_VOLDOWN") + + def mute_volume(self, mute): + self.send_key("KEY_MUTE") + + def media_play_pause(self): + """ Simulate play pause media player. """ + if self._playing: + self.media_pause() + else: + self.media_play() + + def media_play(self): + """ media_play media player. """ + self._playing = True + self.send_key("KEY_PLAY") + + def media_pause(self): + """ media_pause media player. """ + self._playing = False + self.send_key("KEY_PAUSE") + + def media_next_track(self): + """ Send next track command. """ + self.send_key("KEY_FF") + + def media_previous_track(self): + self.send_key("KEY_REWIND") + + def media_seek(self, position): + raise NotImplementedError() + + def turn_on(self): + """ turn the media player on. """ + self.send_key("KEY_POWERON") + + def play_media(self, media_type, media_id): + pass + + def play_youtube(self, media_id): + pass diff --git a/requirements_all.txt b/requirements_all.txt index 80369444496..5dd8078e46e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -95,6 +95,9 @@ plexapi==1.1.0 # homeassistant.components.media_player.sonos SoCo==0.11.1 +# homeassistant.components.media_player.samsungtv +samsungctl==0.5.1 + # homeassistant.components.modbus https://github.com/bashwork/pymodbus/archive/d7fc4f1cc975631e0a9011390e8017f64b612661.zip#pymodbus==1.2.0 From 1974eda51d61c35a543ca8463b11ccea32300453 Mon Sep 17 00:00:00 2001 From: Tim Date: Sun, 31 Jan 2016 13:31:12 +0000 Subject: [PATCH 032/232] Update for new liffylights release Fix incorrect packet timeout/ack code causing flooding when no bulbs were online, which consumed all WorkerPool threads --- homeassistant/components/light/lifx.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/light/lifx.py b/homeassistant/components/light/lifx.py index 79c019c8417..cb7e8cbf647 100644 --- a/homeassistant/components/light/lifx.py +++ b/homeassistant/components/light/lifx.py @@ -28,7 +28,7 @@ from homeassistant.components.light import \ _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['liffylights==0.9.2'] +REQUIREMENTS = ['liffylights==0.9.3'] DEPENDENCIES = [] CONF_SERVER = "server" # server address configuration item @@ -70,6 +70,8 @@ class LIFX(): bulb = self.find_bulb(ipaddr) if bulb is None: + _LOGGER.debug("new bulb %s %s %d %d %d %d %d", + ipaddr, name, power, hue, sat, bri, kel) bulb = LIFXLight(self._liffylights, ipaddr, name, power, hue, sat, bri, kel) self._devices.append(bulb) From e6a8746dbad804d3845b059a6821978edacf3636 Mon Sep 17 00:00:00 2001 From: Tim Date: Sun, 31 Jan 2016 15:07:06 +0000 Subject: [PATCH 033/232] Update requirements_all --- requirements_all.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_all.txt b/requirements_all.txt index b48088467c4..8b2607f3e97 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -52,7 +52,7 @@ blinkstick==1.1.7 phue==0.8 # homeassistant.components.light.lifx -liffylights==0.9.2 +liffylights==0.9.3 # homeassistant.components.light.limitlessled limitlessled==1.0.0 From 0b8e0977051794e35e2cb7115daa27364c6b3ce9 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 31 Jan 2016 08:58:30 -0800 Subject: [PATCH 034/232] Remove unused environment util --- homeassistant/util/environment.py | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 homeassistant/util/environment.py diff --git a/homeassistant/util/environment.py b/homeassistant/util/environment.py deleted file mode 100644 index ea4c69e8f13..00000000000 --- a/homeassistant/util/environment.py +++ /dev/null @@ -1,13 +0,0 @@ -""" -homeassistant.util.environement -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Environement helpers. -""" -import sys - - -def is_virtual(): - """ Return if we run in a virtual environtment. """ - # Check supports venv && virtualenv - return (getattr(sys, 'base_prefix', sys.prefix) != sys.prefix or - hasattr(sys, 'real_prefix')) From de4dab74b145e152e318acf87f423dcc4b866505 Mon Sep 17 00:00:00 2001 From: magnusknutas Date: Sun, 31 Jan 2016 09:50:03 +0100 Subject: [PATCH 035/232] Adding weblink component Adding weblink component tests --- homeassistant/components/weblink.py | 70 +++++++++++++++++++++++++++++ tests/components/test_weblink.py | 29 ++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 homeassistant/components/weblink.py create mode 100644 tests/components/test_weblink.py diff --git a/homeassistant/components/weblink.py b/homeassistant/components/weblink.py new file mode 100644 index 00000000000..9a75d9683c1 --- /dev/null +++ b/homeassistant/components/weblink.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- +""" +homeassistant.components.weblink +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Adds links to external webpage + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/webpage/ +""" + +# The domain of your component. Should be equal to the name of your component +import logging + +from homeassistant.helpers.entity import Entity +from homeassistant.util import slugify + +DOMAIN = "weblink" + +# List of component names (string) your component depends upon +DEPENDENCIES = [] + +ATTR_NAME = 'name' +ATTR_URL = 'url' +ATTR_ICON = 'icon' + +_LOGGER = logging.getLogger(__name__) + + +def setup(hass, config): + """ Setup weblink component. """ + + # States are in the format DOMAIN.OBJECT_ID + + links = config.get(DOMAIN) + + for link in links.get('entities'): + if ATTR_NAME not in link or ATTR_URL not in link: + _LOGGER.error("You need to set both %s and %s to add a %s", + ATTR_NAME, ATTR_URL, DOMAIN) + continue + Link(hass, link.get(ATTR_NAME), link.get(ATTR_URL), + link.get(ATTR_ICON)) + + # return boolean to indicate that initialization was successful + return True + + +class Link(Entity): + """ Represent a link """ + + def __init__(self, hass, name, url, icon): + """ Represents a link. """ + self.hass = hass + self._name = name + self._url = url + self._icon = icon + self.entity_id = DOMAIN + '.%s' % slugify(name) + self.update_ha_state() + + @property + def icon(self): + return self._icon + + @property + def name(self): + return self._name + + @property + def state(self): + return self._url diff --git a/tests/components/test_weblink.py b/tests/components/test_weblink.py new file mode 100644 index 00000000000..6a8354c549c --- /dev/null +++ b/tests/components/test_weblink.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +import unittest + +import homeassistant.core as ha +from homeassistant.components import weblink + + +class TestComponentHistory(unittest.TestCase): + """ Tests homeassistant.components.history module. """ + + def setUp(self): + """ Test setup method. """ + self.hass = ha.HomeAssistant() + + def tearDown(self): + self.hass.stop() + + def test_setup(self): + self.assertTrue(weblink.setup(self.hass, { + weblink.DOMAIN: { + 'entities': [ + { + weblink.ATTR_NAME: 'My router', + weblink.ATTR_URL: 'http://127.0.0.1/' + }, + {} + ] + } + })) From 8eef978241229b74980ccfbec162c01a8a70456c Mon Sep 17 00:00:00 2001 From: Flavio Castelli Date: Sat, 9 Jan 2016 14:51:49 +0100 Subject: [PATCH 036/232] Add support for the SCSGate device Support the SCSGate device. This will allow home-assistant to interact with BTicino/Legrand MyHome system. Signed-off-by: Flavio Castelli --- .coveragerc | 3 + homeassistant/components/light/scsgate.py | 118 ++++++++++ .../components/rollershutter/scsgate.py | 98 +++++++++ homeassistant/components/scsgate.py | 162 ++++++++++++++ homeassistant/components/switch/scsgate.py | 202 ++++++++++++++++++ requirements_all.txt | 3 + 6 files changed, 586 insertions(+) create mode 100644 homeassistant/components/light/scsgate.py create mode 100644 homeassistant/components/rollershutter/scsgate.py create mode 100644 homeassistant/components/scsgate.py create mode 100644 homeassistant/components/switch/scsgate.py diff --git a/.coveragerc b/.coveragerc index 75f4cbd20fd..14bd1c68cb3 100644 --- a/.coveragerc +++ b/.coveragerc @@ -53,6 +53,9 @@ omit = homeassistant/components/rpi_gpio.py homeassistant/components/*/rpi_gpio.py + homeassistant/components/scsgate.py + homeassistant/components/*/scsgate.py + homeassistant/components/binary_sensor/arest.py homeassistant/components/binary_sensor/rest.py homeassistant/components/browser.py diff --git a/homeassistant/components/light/scsgate.py b/homeassistant/components/light/scsgate.py new file mode 100644 index 00000000000..275e9812ace --- /dev/null +++ b/homeassistant/components/light/scsgate.py @@ -0,0 +1,118 @@ +""" +homeassistant.components.light.scsgate +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Support for SCSGate lights. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/light.scsgate/ +""" +import logging +import homeassistant.components.scsgate as scsgate + +from homeassistant.components.light import Light + +from homeassistant.const import ATTR_ENTITY_ID + +DEPENDENCIES = ['scsgate'] + + +def setup_platform(hass, config, add_devices_callback, discovery_info=None): + """ Add the SCSGate swiches defined inside of the configuration file. """ + + devices = config.get('devices') + lights = [] + logger = logging.getLogger(__name__) + + if devices: + for _, entity_info in devices.items(): + if entity_info['scs_id'] in scsgate.SCSGATE.devices: + continue + + logger.info("Adding %s scsgate.light", entity_info['name']) + + name = entity_info['name'] + scs_id = entity_info['scs_id'] + light = SCSGateLight( + name=name, + scs_id=scs_id, + logger=logger) + lights.append(light) + + add_devices_callback(lights) + scsgate.SCSGATE.add_devices_to_register(lights) + + +class SCSGateLight(Light): + """ Provides a SCSGate light. """ + def __init__(self, scs_id, name, logger): + self._name = name + self._scs_id = scs_id + self._toggled = False + self._logger = logger + + @property + def scs_id(self): + """ SCS ID """ + return self._scs_id + + @property + def should_poll(self): + """ No polling needed for a SCSGate light. """ + return False + + @property + def name(self): + """ Returns the name of the device if any. """ + return self._name + + @property + def is_on(self): + """ True if light is on. """ + return self._toggled + + def turn_on(self, **kwargs): + """ Turn the device on. """ + from scsgate.tasks import ToggleStatusTask + + scsgate.SCSGATE.append_task( + ToggleStatusTask( + target=self._scs_id, + toggled=True)) + + self._toggled = True + self.update_ha_state() + + def turn_off(self, **kwargs): + """ Turn the device off. """ + from scsgate.tasks import ToggleStatusTask + + scsgate.SCSGATE.append_task( + ToggleStatusTask( + target=self._scs_id, + toggled=False)) + + self._toggled = False + self.update_ha_state() + + def process_event(self, message): + """ Handle a SCSGate message related with this light """ + if self._toggled == message.toggled: + self._logger.info( + "Light %s, ignoring message %s because state already active", + self._scs_id, message) + # Nothing changed, ignoring + return + + self._toggled = message.toggled + self.update_ha_state() + + command = "off" + if self._toggled: + command = "on" + + self.hass.bus.fire( + 'button_pressed', { + ATTR_ENTITY_ID: self._scs_id, + 'state': command + } + ) diff --git a/homeassistant/components/rollershutter/scsgate.py b/homeassistant/components/rollershutter/scsgate.py new file mode 100644 index 00000000000..e47d1574697 --- /dev/null +++ b/homeassistant/components/rollershutter/scsgate.py @@ -0,0 +1,98 @@ +""" +homeassistant.components.rollershutter.scsgate +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Allows to configure a SCSGate rollershutter. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/rollershutter.scsgate/ +""" +import logging +import homeassistant.components.scsgate as scsgate +from homeassistant.components.rollershutter import RollershutterDevice + + +DEPENDENCIES = ['scsgate'] + + +def setup_platform(hass, config, add_devices_callback, discovery_info=None): + """ Add the SCSGate swiches defined inside of the configuration file. """ + + devices = config.get('devices') + rollershutters = [] + logger = logging.getLogger(__name__) + + if devices: + for _, entity_info in devices.items(): + if entity_info['scs_id'] in scsgate.SCSGATE.devices: + continue + + logger.info("Adding %s scsgate.rollershutter", entity_info['name']) + + name = entity_info['name'] + scs_id = entity_info['scs_id'] + rollershutter = SCSGateRollerShutter( + name=name, + scs_id=scs_id, + logger=logger) + scsgate.SCSGATE.add_device(rollershutter) + rollershutters.append(rollershutter) + + add_devices_callback(rollershutters) + + +# pylint: disable=too-many-arguments, too-many-instance-attributes +class SCSGateRollerShutter(RollershutterDevice): + """ Represents a rollershutter that can be controlled using SCSGate. """ + def __init__(self, scs_id, name, logger): + self._scs_id = scs_id + self._name = name + self._logger = logger + + @property + def scs_id(self): + """ SCSGate ID """ + return self._scs_id + + @property + def should_poll(self): + """ No polling needed """ + return False + + @property + def name(self): + """ The name of the rollershutter. """ + return self._name + + @property + def current_position(self): + """ + Return current position of rollershutter. + None is unknown, 0 is closed, 100 is fully open. + """ + return None + + def move_up(self, **kwargs): + """ Move the rollershutter up. """ + from scsgate.tasks import RaiseRollerShutterTask + + scsgate.SCSGATE.append_task( + RaiseRollerShutterTask(target=self._scs_id)) + + def move_down(self, **kwargs): + """ Move the rollershutter down. """ + from scsgate.tasks import LowerRollerShutterTask + + scsgate.SCSGATE.append_task( + LowerRollerShutterTask(target=self._scs_id)) + + def stop(self, **kwargs): + """ Stop the device. """ + from scsgate.tasks import HaltRollerShutterTask + + scsgate.SCSGATE.append_task(HaltRollerShutterTask(target=self._scs_id)) + + def process_event(self, message): + """ Handle a SCSGate message related with this rollershutter """ + self._logger.debug( + "Rollershutter %s, got message %s", + self._scs_id, message.toggled) diff --git a/homeassistant/components/scsgate.py b/homeassistant/components/scsgate.py new file mode 100644 index 00000000000..e9b762f140c --- /dev/null +++ b/homeassistant/components/scsgate.py @@ -0,0 +1,162 @@ +""" +homeassistant.components.scsgate +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Provides support for SCSGate components. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/scsgate/ +""" +import logging +from threading import Lock +from homeassistant.core import EVENT_HOMEASSISTANT_STOP + +REQUIREMENTS = ['scsgate==0.1.0'] + + +DOMAIN = "scsgate" + +SCSGATE = None +_LOGGER = logging.getLogger(__name__) + + +class SCSGate: + """ Class dealing with the SCSGate device via scsgate.Reactor """ + + def __init__(self, device, logger): + self._logger = logger + self._devices = {} + self._devices_to_register = {} + self._devices_to_register_lock = Lock() + self._device_being_registered = None + self._device_being_registered_lock = Lock() + + from scsgate.connection import Connection + connection = Connection(device=device, logger=self._logger) + + from scsgate.reactor import Reactor + self._reactor = Reactor( + connection=connection, + logger=self._logger, + handle_message=self.handle_message) + + def handle_message(self, message): + """ Method called whenever a message is seen on the bus """ + from scsgate.messages import StateMessage, ScenarioTriggeredMessage + + self._logger.debug("Received message {}".format(message)) + if not isinstance(message, StateMessage) and \ + not isinstance(message, ScenarioTriggeredMessage): + msg = "Ignored message {} - not releavant type".format( + message) + self._logger.debug(msg) + return + + if message.entity in self._devices: + new_device_activated = False + with self._devices_to_register_lock: + if message.entity == self._device_being_registered: + self._device_being_registered = None + new_device_activated = True + if new_device_activated: + self._activate_next_device() + + # pylint: disable=broad-except + try: + self._devices[message.entity].process_event(message) + except Exception as exception: + msg = "Exception while processing event: {}".format( + exception) + self._logger.error(msg) + else: + self._logger.info( + "Ignoring state message for device {} because unknonw".format( + message.entity)) + + @property + def devices(self): + """ Dictionary with known devices. Key is device ID, + value is the device itself """ + return self._devices + + def add_device(self, device): + """ Adds the specified device to the list of the already registered ones. + + Beware: this is not what you usually want to do, take + a look at `add_devices_to_register` + """ + self._devices[device.scs_id] = device + + def add_devices_to_register(self, devices): + """ List of devices to be registered. + + Arguments: + * devices: list of devices to register + """ + with self._devices_to_register_lock: + for device in devices: + self._devices_to_register[device.scs_id] = device + self._activate_next_device() + + def _activate_next_device(self): + """ Starts the activation of the 1st device inside of self._devices """ + from scsgate.tasks import GetStatusTask + + with self._devices_to_register_lock: + if len(self._devices_to_register) == 0: + return + _, device = self._devices_to_register.popitem() + self._devices[device.scs_id] = device + self._device_being_registered = device.scs_id + self._reactor.append_task(GetStatusTask(target=device.scs_id)) + + def is_device_registered(self, device_id): + """ Checks whether a device is already registered or not + + Arguments: + device_id: the ID of the device to look for + """ + with self._devices_to_register_lock: + if device_id in self._devices_to_register.keys(): + return False + + with self._device_being_registered_lock: + if device_id == self._device_being_registered: + return False + + return True + + def start(self): + """ Start the scsgate.Reactor """ + self._reactor.start() + + def stop(self): + """ Stop the scsgate.Reactor """ + self._reactor.stop() + + def append_task(self, task): + """ Registers a new task to be executed """ + self._reactor.append_task(task) + + +def setup(hass, config): + """ Setup the SCSGate component. """ + device = config['scsgate']['device'] + global SCSGATE + + # pylint: disable=broad-except + try: + SCSGATE = SCSGate(device=device, logger=_LOGGER) + SCSGATE.start() + except Exception as exception: + _LOGGER.error("Cannot setup SCSGate component: %s", exception) + return False + + def stop_monitor(event): + """ Invoked when home-assistant is exiting. Performs the necessary + cleanups """ + _LOGGER.info("Stopping SCSGate monitor thread") + SCSGATE.stop() + + hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_monitor) + + return True diff --git a/homeassistant/components/switch/scsgate.py b/homeassistant/components/switch/scsgate.py new file mode 100644 index 00000000000..7755168585e --- /dev/null +++ b/homeassistant/components/switch/scsgate.py @@ -0,0 +1,202 @@ +""" +homeassistant.components.switch.scsgate +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Support for SCSGate switches. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/switch.scsgate/ +""" +import logging +import homeassistant.components.scsgate as scsgate + +from homeassistant.components.switch import SwitchDevice + +from homeassistant.const import ATTR_ENTITY_ID + + +DEPENDENCIES = ['scsgate'] + + +def setup_platform(hass, config, add_devices_callback, discovery_info=None): + """ Add the SCSGate swiches defined inside of the configuration file. """ + + logger = logging.getLogger(__name__) + + _setup_traditional_switches( + logger=logger, + config=config, + add_devices_callback=add_devices_callback) + + _setup_scenario_switches( + logger=logger, + config=config, + hass=hass) + + +def _setup_traditional_switches(logger, config, add_devices_callback): + """ Add traditional SCSGate switches """ + traditional = config.get('traditional') + switches = [] + + if traditional: + for _, entity_info in traditional.items(): + if entity_info['scs_id'] in scsgate.SCSGATE.devices: + continue + + logger.info( + "Adding %s scsgate.traditional_switch", entity_info['name']) + + name = entity_info['name'] + scs_id = entity_info['scs_id'] + + switch = SCSGateSwitch( + name=name, + scs_id=scs_id, + logger=logger) + switches.append(switch) + + add_devices_callback(switches) + scsgate.SCSGATE.add_devices_to_register(switches) + + +def _setup_scenario_switches(logger, config, hass): + """ Add only SCSGate scenario switches """ + scenario = config.get("scenario") + + if scenario: + for _, entity_info in scenario.items(): + if entity_info['scs_id'] in scsgate.SCSGATE.devices: + continue + + logger.info( + "Adding %s scsgate.scenario_switch", entity_info['name']) + + name = entity_info['name'] + scs_id = entity_info['scs_id'] + + switch = SCSGateScenarioSwitch( + name=name, + scs_id=scs_id, + logger=logger, + hass=hass) + scsgate.SCSGATE.add_device(switch) + + +class SCSGateSwitch(SwitchDevice): + """ Provides a SCSGate switch. """ + def __init__(self, scs_id, name, logger): + self._name = name + self._scs_id = scs_id + self._toggled = False + self._logger = logger + + @property + def scs_id(self): + """ SCS ID """ + return self._scs_id + + @property + def should_poll(self): + """ No polling needed for a SCSGate switch. """ + return False + + @property + def name(self): + """ Returns the name of the device if any. """ + return self._name + + @property + def is_on(self): + """ True if switch is on. """ + return self._toggled + + def turn_on(self, **kwargs): + """ Turn the device on. """ + from scsgate.tasks import ToggleStatusTask + + scsgate.SCSGATE.append_task( + ToggleStatusTask( + target=self._scs_id, + toggled=True)) + + self._toggled = True + self.update_ha_state() + + def turn_off(self, **kwargs): + """ Turn the device off. """ + from scsgate.tasks import ToggleStatusTask + + scsgate.SCSGATE.append_task( + ToggleStatusTask( + target=self._scs_id, + toggled=False)) + + self._toggled = False + self.update_ha_state() + + def process_event(self, message): + """ Handle a SCSGate message related with this switch""" + if self._toggled == message.toggled: + self._logger.info( + "Switch %s, ignoring message %s because state already active", + self._scs_id, message) + # Nothing changed, ignoring + return + + self._toggled = message.toggled + self.update_ha_state() + + command = "off" + if self._toggled: + command = "on" + + self.hass.bus.fire( + 'button_pressed', { + ATTR_ENTITY_ID: self._scs_id, + 'state': command + } + ) + + +class SCSGateScenarioSwitch: + """ Provides a SCSGate scenario switch. + + This switch is always in a 'off" state, when toggled + it's used to trigger events + """ + def __init__(self, scs_id, name, logger, hass): + self._name = name + self._scs_id = scs_id + self._logger = logger + self._hass = hass + + @property + def scs_id(self): + """ SCS ID """ + return self._scs_id + + @property + def name(self): + """ Returns the name of the device if any. """ + return self._name + + def process_event(self, message): + """ Handle a SCSGate message related with this switch""" + from scsgate.messages import StateMessage, ScenarioTriggeredMessage + + if isinstance(message, StateMessage): + scenario_id = message.bytes[4] + elif isinstance(message, ScenarioTriggeredMessage): + scenario_id = message.scenario + else: + self._logger.warn( + "Scenario switch: received unknown message %s", + message) + return + + self._hass.bus.fire( + 'scenario_switch_triggered', { + ATTR_ENTITY_ID: int(self._scs_id), + 'scenario_id': int(scenario_id, 16) + } + ) diff --git a/requirements_all.txt b/requirements_all.txt index 985bc324964..fa96f810e7b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -143,6 +143,9 @@ https://github.com/Danielhiversen/pyRFXtrx/archive/0.2.zip#RFXtrx==0.2 # homeassistant.components.rpi_gpio # RPi.GPIO==0.6.1 +# homeassistant.components.scsgate +scsgate==0.1.0 + # homeassistant.components.sensor.bitcoin blockchain==1.2.1 From 2dab815f90d96e833378bad5278718e12cc19d46 Mon Sep 17 00:00:00 2001 From: Stefan Jonasson Date: Sun, 31 Jan 2016 19:12:00 +0100 Subject: [PATCH 037/232] Fixes imports, styles and other misstates --- .../components/media_player/samsungtv.py | 47 ++++++++----------- 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/homeassistant/components/media_player/samsungtv.py b/homeassistant/components/media_player/samsungtv.py index 7cf23a84d3d..74ba5b5e526 100644 --- a/homeassistant/components/media_player/samsungtv.py +++ b/homeassistant/components/media_player/samsungtv.py @@ -8,7 +8,6 @@ https://home-assistant.io/components/media_player.samsungtv/ """ import logging import socket -from samsungctl import Remote from homeassistant.components.media_player import ( MediaPlayerDevice, SUPPORT_PAUSE, SUPPORT_VOLUME_STEP, @@ -16,11 +15,16 @@ from homeassistant.components.media_player import ( SUPPORT_NEXT_TRACK, SUPPORT_TURN_OFF, DOMAIN) from homeassistant.const import ( - CONF_HOST, STATE_OFF, + CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON, STATE_UNKNOWN) +CONF_PORT = "port" +CONF_TIMEOUT = "timeout" + _LOGGER = logging.getLogger(__name__) +REQUIREMENTS = ['samsungctl==0.5.1'] + SUPPORT_SAMSUNGTV = SUPPORT_PAUSE | SUPPORT_VOLUME_STEP | \ SUPPORT_VOLUME_MUTE | SUPPORT_PREVIOUS_TRACK | \ SUPPORT_NEXT_TRACK | SUPPORT_TURN_OFF @@ -28,7 +32,8 @@ SUPPORT_SAMSUNGTV = SUPPORT_PAUSE | SUPPORT_VOLUME_STEP | \ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up the Denon platform. """ + """ Sets up the Samsung TV platform. """ + if not config.get(CONF_HOST): _LOGGER.error( "Missing required configuration items in %s: %s", @@ -39,26 +44,23 @@ def setup_platform(hass, config, add_devices, discovery_info=None): # Generate a config for the Samsung lib remote_config = { "name": "HomeAssistant", - "description": config.get("name", ''), + "description": config.get(CONF_NAME, ''), "id": "ha.component.samsung", - "port": config.get("port", 55000), - "host": config.get("host"), - "timeout": config.get("timeout", 0), + "port": config.get(CONF_PORT, 55000), + "host": config.get(CONF_HOST), + "timeout": config.get(CONF_TIMEOUT, 0), } add_devices([SamsungTVDevice(name, remote_config)]) -# pylint: disable=too-many-public-methods +# pylint: disable=abstract-method class SamsungTVDevice(MediaPlayerDevice): - """ Represents a Denon device. """ - - def set_volume_level(self, volume): - pass + """ Represents a Samsung TV. """ # pylint: disable=too-many-public-methods - def __init__(self, name, config): + self._name = name # Assume that the TV is not muted self._muted = False @@ -74,6 +76,8 @@ class SamsungTVDevice(MediaPlayerDevice): def get_remote(self): """ Creates or Returns a remote control instance """ + from samsungctl import Remote + if self._remote is None: # We need to create a new instance to reconnect. self._remote = Remote(self._config) @@ -82,6 +86,7 @@ class SamsungTVDevice(MediaPlayerDevice): def send_key(self, key): """ Sends a key to the tv and handles exceptions """ + from samsungctl import Remote try: self.get_remote().control(key) self._state = STATE_ON @@ -92,14 +97,11 @@ class SamsungTVDevice(MediaPlayerDevice): self._state = STATE_ON self._remote = None return False - except (Remote.ConnectionClosed, Remote.ConnectionClosed, - socket.timeout, TimeoutError, OSError): + except (Remote.ConnectionClosed, socket.timeout, + TimeoutError, OSError): self._state = STATE_OFF self._remote = None return False - except Remote.AccessDenied: - self._state = STATE_ON - return False return True @@ -161,15 +163,6 @@ class SamsungTVDevice(MediaPlayerDevice): def media_previous_track(self): self.send_key("KEY_REWIND") - def media_seek(self, position): - raise NotImplementedError() - def turn_on(self): """ turn the media player on. """ self.send_key("KEY_POWERON") - - def play_media(self, media_type, media_id): - pass - - def play_youtube(self, media_id): - pass From 3f03fefd3547f0675971f60584ff71a55cbed319 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Per=20Sandstr=C3=B6m?= Date: Sun, 31 Jan 2016 19:23:53 +0100 Subject: [PATCH 038/232] vsure 0.5.0 --- homeassistant/components/verisure.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/verisure.py b/homeassistant/components/verisure.py index 0d635e861b3..e69321315eb 100644 --- a/homeassistant/components/verisure.py +++ b/homeassistant/components/verisure.py @@ -28,7 +28,7 @@ DISCOVER_SWITCHES = 'verisure.switches' DISCOVER_ALARMS = 'verisure.alarm_control_panel' DEPENDENCIES = ['alarm_control_panel'] -REQUIREMENTS = ['vsure==0.4.8'] +REQUIREMENTS = ['vsure==0.5.0'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index fed45f0a973..295518f367b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -223,7 +223,7 @@ proliphix==0.1.0 radiotherm==1.2 # homeassistant.components.verisure -vsure==0.4.8 +vsure==0.5.0 # homeassistant.components.zigbee xbee-helper==0.0.6 From 5719743ec7a14ef016306d8e69f87666c5d280be Mon Sep 17 00:00:00 2001 From: Stefan Jonasson Date: Sun, 31 Jan 2016 20:02:51 +0100 Subject: [PATCH 039/232] Fixed .coveragerc and requirements_all.txt --- .coveragerc | 1 + requirements_all.txt | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.coveragerc b/.coveragerc index 75f4cbd20fd..11e4edad8c1 100644 --- a/.coveragerc +++ b/.coveragerc @@ -91,6 +91,7 @@ omit = homeassistant/components/media_player/plex.py homeassistant/components/media_player/sonos.py homeassistant/components/media_player/squeezebox.py + homeassistant/components/media_player/samsungtv.py homeassistant/components/notify/free_mobile.py homeassistant/components/notify/instapush.py homeassistant/components/notify/nma.py diff --git a/requirements_all.txt b/requirements_all.txt index 5dd8078e46e..29379bd52c0 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -92,12 +92,12 @@ python-mpd2==0.5.4 # homeassistant.components.media_player.plex plexapi==1.1.0 -# homeassistant.components.media_player.sonos -SoCo==0.11.1 - # homeassistant.components.media_player.samsungtv samsungctl==0.5.1 +# homeassistant.components.media_player.sonos +SoCo==0.11.1 + # homeassistant.components.modbus https://github.com/bashwork/pymodbus/archive/d7fc4f1cc975631e0a9011390e8017f64b612661.zip#pymodbus==1.2.0 From c95c3d9198288e5562b4145f9c4a940b2bc86320 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 31 Jan 2016 12:00:14 -0800 Subject: [PATCH 040/232] Update frontend with weblink support --- homeassistant/components/frontend/version.py | 2 +- .../frontend/www_static/frontend.html | 87 +++++++++++-------- .../www_static/home-assistant-polymer | 2 +- 3 files changed, 51 insertions(+), 40 deletions(-) diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py index ed2461d04a6..9c7c3f49eb5 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 = "1e89871aaae43c91b2508f52bc161b69" +VERSION = "a6e64ec7d87014e4260c552a2560cb9f" diff --git a/homeassistant/components/frontend/www_static/frontend.html b/homeassistant/components/frontend/www_static/frontend.html index 0a2643facff..344ed82aae3 100644 --- a/homeassistant/components/frontend/www_static/frontend.html +++ b/homeassistant/components/frontend/www_static/frontend.html @@ -3797,37 +3797,7 @@ case"touchend":return this.addPointerListenerEnd(t,e,i,n);case"touchmove":return font-weight: 400; text-align: right; line-height: 1.5; - } \ No newline at end of file +value:function(t,e){if(0===this.__batchDepth){if(h.getOption(this.reactorState,"throwOnDispatchInDispatch")&&this.__isDispatching)throw this.__isDispatching=!1,new Error("Dispatch may not be called while a dispatch is in progress");this.__isDispatching=!0}try{this.reactorState=h.dispatch(this.reactorState,t,e)}catch(n){throw this.__isDispatching=!1,n}try{this.__notify()}finally{this.__isDispatching=!1}}},{key:"batch",value:function(t){this.batchStart(),t(),this.batchEnd()}},{key:"registerStore",value:function(t,e){console.warn("Deprecation warning: `registerStore` will no longer be supported in 1.1, use `registerStores` instead"),this.registerStores(o({},t,e))}},{key:"registerStores",value:function(t){this.reactorState=h.registerStores(this.reactorState,t),this.__notify()}},{key:"replaceStores",value:function(t){this.reactorState=h.replaceStores(this.reactorState,t)}},{key:"serialize",value:function(){return h.serialize(this.reactorState)}},{key:"loadState",value:function(t){this.reactorState=h.loadState(this.reactorState,t),this.__notify()}},{key:"reset",value:function(){var t=h.reset(this.reactorState);this.reactorState=t,this.prevReactorState=t,this.observerState=new m.ObserverState}},{key:"__notify",value:function(){var t=this;if(!(this.__batchDepth>0)){var e=this.reactorState.get("dirtyStores");if(0!==e.size){var n=c["default"].Set().withMutations(function(n){n.union(t.observerState.get("any")),e.forEach(function(e){var r=t.observerState.getIn(["stores",e]);r&&n.union(r)})});n.forEach(function(e){var n=t.observerState.getIn(["observersMap",e]);if(n){var r=n.get("getter"),i=n.get("handler"),o=h.evaluate(t.prevReactorState,r),a=h.evaluate(t.reactorState,r);t.prevReactorState=o.reactorState,t.reactorState=a.reactorState;var u=o.result,s=a.result;c["default"].is(u,s)||i.call(null,s)}});var r=h.resetDirtyStores(this.reactorState);this.prevReactorState=r,this.reactorState=r}}}},{key:"batchStart",value:function(){this.__batchDepth++}},{key:"batchEnd",value:function(){if(this.__batchDepth--,this.__batchDepth<=0){this.__isDispatching=!0;try{this.__notify()}catch(t){throw this.__isDispatching=!1,t}this.__isDispatching=!1}}}]),t}();e["default"]=(0,y.toFactory)(g),t.exports=e["default"]},function(t,e,n){"use strict";function r(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}function i(t,e){var n={};return(0,o.each)(e,function(e,r){n[r]=t.evaluate(e)}),n}Object.defineProperty(e,"__esModule",{value:!0});var o=n(4);e["default"]=function(t){return{getInitialState:function(){return i(t,this.getDataBindings())},componentDidMount:function(){var e=this;this.__unwatchFns=[],(0,o.each)(this.getDataBindings(),function(n,i){var o=t.observe(n,function(t){e.setState(r({},i,t))});e.__unwatchFns.push(o)})},componentWillUnmount:function(){for(;this.__unwatchFns.length;)this.__unwatchFns.shift()()}}},t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){return new A({result:t,reactorState:e})}function o(t,e){return t.withMutations(function(t){(0,P.each)(e,function(e,n){t.getIn(["stores",n])&&console.warn("Store already defined for id = "+n);var r=e.getInitialState();if(void 0===r&&l(t,"throwOnUndefinedStoreReturnValue"))throw new Error("Store getInitialState() must return a value, did you forget a return statement");if(l(t,"throwOnNonImmutableStore")&&!(0,D.isImmutableValue)(r))throw new Error("Store getInitialState() must return an immutable value, did you forget to call toImmutable");t.update("stores",function(t){return t.set(n,e)}).update("state",function(t){return t.set(n,r)}).update("dirtyStores",function(t){return t.add(n)}).update("storeStates",function(t){return O(t,[n])})}),w(t)})}function a(t,e){return t.withMutations(function(t){(0,P.each)(e,function(e,n){t.update("stores",function(t){return t.set(n,e)})})})}function u(t,e,n){if(void 0===e&&l(t,"throwOnUndefinedActionType"))throw new Error("`dispatch` cannot be called with an `undefined` action type.");var r=t.get("state"),i=t.get("dirtyStores"),o=r.withMutations(function(r){T["default"].dispatchStart(t,e,n),t.get("stores").forEach(function(o,a){var u=r.get(a),s=void 0;try{s=o.handle(u,e,n)}catch(c){throw T["default"].dispatchError(t,c.message),c}if(void 0===s&&l(t,"throwOnUndefinedStoreReturnValue")){var f="Store handler must return a value, did you forget a return statement";throw T["default"].dispatchError(t,f),new Error(f)}r.set(a,s),u!==s&&(i=i.add(a))}),T["default"].dispatchEnd(t,r,i)}),a=t.set("state",o).set("dirtyStores",i).update("storeStates",function(t){return O(t,i)});return w(a)}function s(t,e){var n=[],r=(0,D.toImmutable)({}).withMutations(function(r){(0,P.each)(e,function(e,i){var o=t.getIn(["stores",i]);if(o){var a=o.deserialize(e);void 0!==a&&(r.set(i,a),n.push(i))}})}),i=I["default"].Set(n);return t.update("state",function(t){return t.merge(r)}).update("dirtyStores",function(t){return t.union(i)}).update("storeStates",function(t){return O(t,n)})}function c(t,e,n){var r=e;(0,j.isKeyPath)(e)&&(e=(0,C.fromKeyPath)(e));var i=t.get("nextId"),o=(0,C.getStoreDeps)(e),a=I["default"].Map({id:i,storeDeps:o,getterKey:r,getter:e,handler:n}),u=void 0;return u=0===o.size?t.update("any",function(t){return t.add(i)}):t.withMutations(function(t){o.forEach(function(e){var n=["stores",e];t.hasIn(n)||t.setIn(n,I["default"].Set()),t.updateIn(["stores",e],function(t){return t.add(i)})})}),u=u.set("nextId",i+1).setIn(["observersMap",i],a),{observerState:u,entry:a}}function l(t,e){var n=t.getIn(["options",e]);if(void 0===n)throw new Error("Invalid option: "+e);return n}function f(t,e,n){var r=t.get("observersMap").filter(function(t){var r=t.get("getterKey"),i=!n||t.get("handler")===n;return i?(0,j.isKeyPath)(e)&&(0,j.isKeyPath)(r)?(0,j.isEqual)(e,r):e===r:!1});return t.withMutations(function(t){r.forEach(function(e){return d(t,e)})})}function d(t,e){return t.withMutations(function(t){var n=e.get("id"),r=e.get("storeDeps");0===r.size?t.update("any",function(t){return t.remove(n)}):r.forEach(function(e){t.updateIn(["stores",e],function(t){return t?t.remove(n):t})}),t.removeIn(["observersMap",n])})}function h(t){var e=t.get("state");return t.withMutations(function(t){var n=t.get("stores"),r=n.keySeq().toJS();n.forEach(function(n,r){var i=e.get(r),o=n.handleReset(i);if(void 0===o&&l(t,"throwOnUndefinedStoreReturnValue"))throw new Error("Store handleReset() must return a value, did you forget a return statement");if(l(t,"throwOnNonImmutableStore")&&!(0,D.isImmutableValue)(o))throw new Error("Store reset state must be an immutable value, did you forget to call toImmutable");t.setIn(["state",r],o)}),t.update("storeStates",function(t){return O(t,r)}),v(t)})}function p(t,e){var n=t.get("state");if((0,j.isKeyPath)(e))return i(n.getIn(e),t);if(!(0,C.isGetter)(e))throw new Error("evaluate must be passed a keyPath or Getter");if(g(t,e))return i(S(t,e),t);var r=(0,C.getDeps)(e).map(function(e){return p(t,e).result}),o=(0,C.getComputeFn)(e).apply(null,r);return i(o,b(t,e,o))}function _(t){var e={};return t.get("stores").forEach(function(n,r){var i=t.getIn(["state",r]),o=n.serialize(i);void 0!==o&&(e[r]=o)}),e}function v(t){return t.set("dirtyStores",I["default"].Set())}function y(t){return t}function m(t,e){var n=y(e);return t.getIn(["cache",n])}function g(t,e){var n=m(t,e);if(!n)return!1;var r=n.get("storeStates");return 0===r.size?!1:r.every(function(e,n){return t.getIn(["storeStates",n])===e})}function b(t,e,n){var r=y(e),i=t.get("dispatchId"),o=(0,C.getStoreDeps)(e),a=(0,D.toImmutable)({}).withMutations(function(e){o.forEach(function(n){var r=t.getIn(["storeStates",n]);e.set(n,r)})});return t.setIn(["cache",r],I["default"].Map({value:n,storeStates:a,dispatchId:i}))}function S(t,e){var n=y(e);return t.getIn(["cache",n,"value"])}function w(t){return t.update("dispatchId",function(t){return t+1})}function O(t,e){return t.withMutations(function(t){e.forEach(function(e){var n=t.has(e)?t.get(e)+1:1;t.set(e,n)})})}Object.defineProperty(e,"__esModule",{value:!0}),e.registerStores=o,e.replaceStores=a,e.dispatch=u,e.loadState=s,e.addObserver=c,e.getOption=l,e.removeObserver=f,e.removeObserverByEntry=d,e.reset=h,e.evaluate=p,e.serialize=_,e.resetDirtyStores=v;var M=n(3),I=r(M),E=n(9),T=r(E),D=n(5),C=n(10),j=n(11),P=n(4),A=I["default"].Record({result:null,reactorState:null})},function(t,e,n){"use strict";var r=n(8);e.dispatchStart=function(t,e,n){(0,r.getOption)(t,"logDispatches")&&console.group&&(console.groupCollapsed("Dispatch: %s",e),console.group("payload"),console.debug(n),console.groupEnd())},e.dispatchError=function(t,e){(0,r.getOption)(t,"logDispatches")&&console.group&&(console.debug("Dispatch error: "+e),console.groupEnd())},e.dispatchEnd=function(t,e,n){(0,r.getOption)(t,"logDispatches")&&console.group&&((0,r.getOption)(t,"logDirtyStores")&&console.log("Stores updated:",n.toList().toJS()),(0,r.getOption)(t,"logAppState")&&console.debug("Dispatch done, new state: ",e.toJS()),console.groupEnd())}},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){return(0,d.isArray)(t)&&(0,d.isFunction)(t[t.length-1])}function o(t){return t[t.length-1]}function a(t){return t.slice(0,t.length-1)}function u(t,e){e||(e=f["default"].Set());var n=f["default"].Set().withMutations(function(e){if(!i(t))throw new Error("getFlattenedDeps must be passed a Getter");a(t).forEach(function(t){if((0,h.isKeyPath)(t))e.add((0,l.List)(t));else{if(!i(t))throw new Error("Invalid getter, each dependency must be a KeyPath or Getter");e.union(u(t))}})});return e.union(n)}function s(t){if(!(0,h.isKeyPath)(t))throw new Error("Cannot create Getter from KeyPath: "+t);return[t,p]}function c(t){if(t.hasOwnProperty("__storeDeps"))return t.__storeDeps;var e=u(t).map(function(t){return t.first()}).filter(function(t){return!!t});return Object.defineProperty(t,"__storeDeps",{enumerable:!1,configurable:!1,writable:!1,value:e}),e}Object.defineProperty(e,"__esModule",{value:!0});var l=n(3),f=r(l),d=n(4),h=n(11),p=function(t){return t};e["default"]={isGetter:i,getComputeFn:o,getFlattenedDeps:u,getStoreDeps:c,getDeps:a,fromKeyPath:s},t.exports=e["default"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){return(0,s.isArray)(t)&&!(0,s.isFunction)(t[t.length-1])}function o(t,e){var n=u["default"].List(t),r=u["default"].List(e);return u["default"].is(n,r)}Object.defineProperty(e,"__esModule",{value:!0}),e.isKeyPath=i,e.isEqual=o;var a=n(3),u=r(a),s=n(4)},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(3),i=(0,r.Map)({logDispatches:!1,logAppState:!1,logDirtyStores:!1,throwOnUndefinedActionType:!1,throwOnUndefinedStoreReturnValue:!1,throwOnNonImmutableStore:!1,throwOnDispatchInDispatch:!1});e.PROD_OPTIONS=i;var o=(0,r.Map)({logDispatches:!0,logAppState:!0,logDirtyStores:!0,throwOnUndefinedActionType:!0,throwOnUndefinedStoreReturnValue:!0,throwOnNonImmutableStore:!0,throwOnDispatchInDispatch:!0});e.DEBUG_OPTIONS=o;var a=(0,r.Record)({dispatchId:0,state:(0,r.Map)(),stores:(0,r.Map)(),cache:(0,r.Map)(),storeStates:(0,r.Map)(),dirtyStores:(0,r.Set)(),debug:!1,options:i});e.ReactorState=a;var u=(0,r.Record)({any:(0,r.Set)(),stores:(0,r.Map)({}),observersMap:(0,r.Map)({}),nextId:1});e.ObserverState=u}])})},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=r(i),a=n(122),u=r(a);e["default"]=(0,u["default"])(o["default"].reactor)},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0}),e.callApi=void 0;var i=n(126),o=r(i);e.callApi=o["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,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(77),n(37),e["default"]=new o["default"]({is:"state-info",properties:{stateObj:{type:Object}}})},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")}})},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}),e.getters=e.actions=void 0;var o=n(142),a=i(o),u=n(143),s=r(u);e.actions=a["default"],e.getters=s},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 a(t){return[["restApiCache",t.entity],function(t){return t||(0,s.toImmutable)({})}]}function u(t){return function(e){return["restApiCache",t.entity,e]}}Object.defineProperty(e,"__esModule",{value:!0}),e.createApiActions=void 0,e.register=i,e.createHasDataGetter=o,e.createEntityMapGetter=a,e.createByIdGetter=u;var s=n(3),c=n(168),l=r(c),f=n(167),d=r(f);e.createApiActions=d["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(6),o=r(i);e["default"]=(0,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})},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(6),o=r(i);e["default"]=(0,o["default"])({LOGBOOK_DATE_SELECTED:null,LOGBOOK_ENTRIES_FETCH_START:null,LOGBOOK_ENTRIES_FETCH_ERROR:null,LOGBOOK_ENTRIES_FETCH_SUCCESS:null})},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}),e.getters=e.actions=void 0;var o=n(169),a=i(o),u=n(53),s=r(u);e.actions=a["default"],e.getters=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(6),o=r(i);e["default"]=(0,o["default"])({VALIDATING_AUTH_TOKEN:null,VALID_AUTH_TOKEN:null,INVALID_AUTH_TOKEN:null,LOG_OUT:null})},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:u["default"],authCurrent:c["default"],rememberAuth:f["default"]})}Object.defineProperty(e,"__esModule",{value:!0}),e.getters=e.actions=void 0,e.register=o;var a=n(129),u=i(a),s=n(130),c=i(s),l=n(131),f=i(l),d=n(127),h=r(d),p=n(128),_=r(p);e.actions=h,e.getters=_},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(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!=typeof e&&"function"!=typeof e?t:e}function a(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)}var u=function(){function t(t,e){var n=[],r=!0,i=!1,o=void 0;try{for(var a,u=t[Symbol.iterator]();!(r=(a=u.next()).done)&&(n.push(a.value),!e||n.length!==e);r=!0);}catch(s){i=!0,o=s}finally{try{!r&&u["return"]&&u["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")}}(),s=function(){function t(t,e){for(var n=0;n4?"value big":"value"}})},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"})},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),a=n(123),u=r(a);n(115),n(39),n(116),n(118),n(117),n(119),n(120),n(121),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=(0,u["default"])(t);if(e&&(0,u["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)}}})},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},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){return u.evaluate(s.canToggleEntity(t))}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=i;var o=n(2),a=r(o),u=a["default"].reactor,s=a["default"].serviceGetters},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){switch(t){case"alarm_control_panel":return e&&"disarmed"===e?"mdi:bell-outline":"mdi:bell";case"automation":return"mdi:playlist-play";case"binary_sensor":return e&&"off"===e?"mdi:radiobox-blank":"mdi:checkbox-marked-circle";case"camera":return"mdi:video";case"configurator":return"mdi:settings";case"conversation":return"mdi:text-to-speech";case"device_tracker":return"mdi:account";case"group":return"mdi:google-circles-communities";case"homeassistant":return"mdi:home";case"input_boolean":return"mdi:drawing";case"light":return"mdi:lightbulb";case"lock":return e&&"unlocked"===e?"mdi:lock-open":"mdi:lock";case"media_player":var n="mdi:cast";return e&&"off"!==e&&"idle"!==e&&(n+="-connected"),n;case"notify":return"mdi:comment-alert";case"rollershutter":return e&&"open"===e?"mdi:window-open":"mdi:window-closed";case"scene":return"mdi:google-pages";case"script":return"mdi:file-document";case"sensor":return"mdi:eye";case"simple_alarm":return"mdi:bell";case"sun":return"mdi:white-balance-sunny";case"switch":return"mdi:flash";case"thermostat":return"mdi:nest-thermostat";case"updater":return"mdi:cloud-upload";case"weblink":return"mdi:open-in-new";default:return console.warn("Unable to find icon for domain "+t+" ("+e+")"),a["default"]}}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=i;var o=n(40),a=r(o)},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(6),o=r(i);e["default"]=(0,o["default"])({SERVER_CONFIG_LOADED:null,COMPONENT_LOADED:null,LOG_OUT:null})},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:u["default"],serverConfig:c["default"]})}Object.defineProperty(e,"__esModule",{value:!0}),e.getters=e.actions=void 0,e.register=o;var a=n(134),u=i(a),s=n(135),c=i(s),l=n(132),f=r(l),d=n(133),h=r(d);e.actions=f,e.getters=h},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}),e.getters=e.actions=void 0;var o=n(146),a=i(o),u=n(147),s=r(u);e.actions=a["default"],e.getters=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(6),o=r(i);e["default"]=(0,o["default"])({NAVIGATE:null,SHOW_SIDEBAR:null,LOG_OUT:null})},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:u["default"]})}Object.defineProperty(e,"__esModule",{value:!0}),e.getters=e.actions=void 0,e.register=o;var a=n(164),u=i(a),s=n(162),c=r(s),l=n(163),f=r(l);e.actions=c,e.getters=f},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(6),o=r(i);e["default"]=(0,o["default"])({API_FETCH_SUCCESS:null,API_FETCH_START:null,API_FETCH_FAIL:null,API_SAVE_SUCCESS:null,API_SAVE_START:null,API_SAVE_FAIL:null,API_DELETE_SUCCESS:null,API_DELETE_START:null,API_DELETE_FAIL:null,LOG_OUT:null})},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:u["default"]})}Object.defineProperty(e,"__esModule",{value:!0}),e.getters=e.actions=void 0,e.register=o;var a=n(176),u=i(a),s=n(172),c=r(s),l=n(173),f=r(l);e.actions=c,e.getters=f},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(6),o=r(i);e["default"]=(0,o["default"])({API_FETCH_ALL_START:null,API_FETCH_ALL_SUCCESS:null,API_FETCH_ALL_FAIL:null,SYNC_SCHEDULED:null,SYNC_SCHEDULE_CANCELLED:null})},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:u["default"],isSyncScheduled:c["default"]})}Object.defineProperty(e,"__esModule",{value:!0}),e.getters=e.actions=void 0,e.register=o;var a=n(178),u=i(a),s=n(179),c=i(s),l=n(177),f=r(l),d=n(56),h=r(d);e.actions=f,e.getters=h},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},function(t,e){"use strict";function n(t){var e=t.split(" "),n=r(e,2),i=n[0],o=n[1],a=i.split(":"),u=r(a,3),s=u[0],c=u[1],l=u[2],f=o.split("-"),d=r(f,3),h=d[0],p=d[1],_=d[2];return new Date(Date.UTC(_,parseInt(p,10)-1,h,s,c,l))}var r=function(){function t(t,e){var n=[],r=!0,i=!1,o=void 0;try{for(var a,u=t[Symbol.iterator]();!(r=(a=u.next()).done)&&(n.push(a.value),!e||n.length!==e);r=!0);}catch(s){i=!0,o=s}finally{try{!r&&u["return"]&&u["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")}}();Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=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),a=n(22),u=r(a);e["default"]=new o["default"]({is:"domain-icon",properties:{domain:{type:String,value:""},state:{type:String,value:""}},computeIcon:function(t,e){return(0,u["default"])(t,e)}})},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=r(i),a=n(1),u=r(a),s=o["default"].serviceActions;e["default"]=new u["default"]({is:"ha-entity-toggle",properties:{stateObj:{type:Object,observer:"stateObjChanged"},toggleChecked:{type:Boolean,value:!1}},ready:function(){this.forceStateChange()},toggleChanged:function(t){var e=t.target.checked,n=this._checkToggle(this.stateObj);e&&!n?this._call_service(!0):!e&&n&&this._call_service(!1)},stateObjChanged:function(t){t&&this.updateToggle(t)},updateToggle:function(t){this.toggleChecked=this._checkToggle(t)},forceStateChange:function(){var t=this._checkToggle(this.stateObj);this.toggleChecked===t&&(this.toggleChecked=!this.toggleChecked),this.toggleChecked=t},_checkToggle:function(t){return t&&"off"!==t.state&&"unlocked"!==t.state},_call_service:function(t){var e=this,n=void 0,r=void 0;"lock"===this.stateObj.domain?(n="lock",r=t?"lock":"unlock"):(n="homeassistant",r=t?"turn_on":"turn_off"),s.callService(n,r,{entity_id:this.stateObj.entityId}).then(function(){return e.forceStateChange()})}})},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:{header:{type:String},elevation:{type:Number,value:1,reflectToAttribute:!0}}})},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(66),o=r(i),a=n(2),u=r(a),s=n(1),c=r(s),l=6e4,f=u["default"].util.parseDateTime;e["default"]=new c["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,l)},detached:function(){clearInterval(this._interval)},datetimeChanged:function(t){this.parsedDateTime=t?f(t):null,this.updateRelative()},datetimeObjChanged:function(t){this.parsedDateTime=t,this.updateRelative()},updateRelative:function(){this.relativeTime=this.parsedDateTime?(0,o["default"])(this.parsedDateTime).fromNow():""}})},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(18),n(87),n(86),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;window.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]}})},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(7),e["default"]=new o["default"]({is:"state-card-display",properties:{stateObj:{type:Object}}})},function(t,e){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e["default"]="bookmark"},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){return(0,a["default"])(t).format("LT")}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=i;var o=n(66),a=r(o)},function(t,e){"use strict";function n(){var t=document.getElementById("ha-init-skeleton");t&&t.parentElement.removeChild(t)}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=n},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=function(t,e){a.validate(t,{rememberAuth:e,useStreaming:u.useStreaming})};var i=n(2),o=r(i),a=o["default"].authActions,u=o["default"].localStoragePreferences},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.recentEntityHistoryUpdatedMap=e.recentEntityHistoryMap=e.hasDataForCurrentDate=e.entityHistoryForCurrentDate=e.entityHistoryMap=e.currentDate=e.isLoadingEntityHistory=void 0;var r=n(3),i=(e.isLoadingEntityHistory=["isLoadingEntityHistory"],e.currentDate=["currentEntityHistoryDate"]),o=e.entityHistoryMap=["entityHistory"];e.entityHistoryForCurrentDate=[i,o,function(t,e){return e.get(t)||(0,r.toImmutable)({})}],e.hasDataForCurrentDate=[i,o,function(t,e){return!!e.get(t)}],e.recentEntityHistoryMap=["recentEntityHistory"],e.recentEntityHistoryUpdatedMap=["recentEntityHistory"]},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:u["default"],entityHistory:c["default"],isLoadingEntityHistory:f["default"],recentEntityHistory:h["default"],recentEntityHistoryUpdated:_["default"]})}Object.defineProperty(e,"__esModule",{value:!0}),e.getters=e.actions=void 0,e.register=o;var a=n(137),u=i(a),s=n(138),c=i(s),l=n(139),f=i(l),d=n(140),h=i(d),p=n(141),_=i(p),v=n(136),y=r(v),m=n(44),g=r(m);e.actions=y,e.getters=g},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(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!=typeof e&&"function"!=typeof e?t:e}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)}var a=function(){function t(t,e){for(var n=0;n6e4}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=n},function(t,e,n){function r(t,e,n){function r(){y&&clearTimeout(y),h&&clearTimeout(h),g=0,h=y=m=void 0}function s(e,n){n&&clearTimeout(n),h=y=m=void 0,e&&(g=o(),p=t.apply(v,d),y||h||(d=v=void 0))}function c(){var t=e-(o()-_);0>=t||t>e?s(m,h):y=setTimeout(c,t)}function l(){s(S,y)}function f(){if(d=arguments,_=o(),v=this,m=S&&(y||!w),b===!1)var n=w&&!y;else{h||w||(g=_);var r=b-(_-g),i=0>=r||r>b;i?(h&&(h=clearTimeout(h)),g=_,p=t.apply(v,d)):h||(h=setTimeout(l,r))}return i&&y?y=clearTimeout(y):y||e===b||(y=setTimeout(c,e)),n&&(i=!0,p=t.apply(v,d)),!i||y||h||(d=v=void 0),p}var d,h,p,_,v,y,m,g=0,b=!1,S=!0;if("function"!=typeof t)throw new TypeError(a);if(e=0>e?0:+e||0,n===!0){var w=!0;S=!1}else i(n)&&(w=!!n.leading,b="maxWait"in n&&u(+n.maxWait||0,e),S="trailing"in n?!!n.trailing:S);return f.cancel=r,f}var i=n(65),o=n(196),a="Expected a function",u=Math.max;t.exports=r},function(t,e,n){function r(t,e){var n=null==t?void 0:t[e];return i(n)?n:void 0}var i=n(199);t.exports=r},function(t,e){function n(t){return!!t&&"object"==typeof t}t.exports=n},function(t,e,n){function r(t){return i(t)&&u.call(t)==o}var i=n(65),o="[object Function]",a=Object.prototype,u=a.toString;t.exports=r},function(t,e){function n(t){var e=typeof t;return!!t&&("object"==e||"function"==e)}t.exports=n},function(t,e,n){(function(t){!function(e,n){t.exports=n()}(this,function(){"use strict";function e(){return Kn.apply(null,arguments)}function n(t){Kn=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 $n)r=$n[n],i=e[r],h(i)||(t[r]=i);return t}function _(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 _||null!=t&&null!=t._isAMomentObject}function y(t){return 0>t?Math.ceil(t):Math.floor(t)}function m(t){var e=+t,n=0;return 0!==e&&isFinite(e)&&(n=y(e)),n}function g(t,e,n){var r,i=Math.min(t.length,e.length),o=Math.abs(t.length-e.length),a=0;for(r=0;i>r;r++)(n&&t[r]!==e[r]||!n&&m(t[r])!==m(e[r]))&&a++;return a+o}function b(){}function S(t){return t?t.toLowerCase().replace("_","-"):t}function w(t){for(var e,n,r,i,o=0;o0;){if(r=O(i.slice(0,e).join("-")))return r;if(n&&n.length>=e&&g(i,n,!0)>=e-1)break;e--}o++}return null}function O(e){var n=null;if(!Xn[e]&&"undefined"!=typeof t&&t&&t.exports)try{n=Jn._abbr,!function(){var t=new Error('Cannot find module "./locale"');throw t.code="MODULE_NOT_FOUND",t}(),M(n)}catch(r){}return Xn[e]}function M(t,e){var n;return t&&(n=h(e)?E(t):I(t,e),n&&(Jn=n)),Jn._abbr}function I(t,e){return null!==e?(e.abbr=t,Xn[t]=Xn[t]||new b,Xn[t].set(e),M(t),Xn[t]):(delete Xn[t],null)}function E(t){var e;if(t&&t._locale&&t._locale._abbr&&(t=t._locale._abbr),!t)return Jn;if(!r(t)){if(e=O(t))return e;t=[t]}return w(t)}function T(t,e){var n=t.toLowerCase();Qn[n]=Qn[n+"s"]=Qn[e]=t}function D(t){return"string"==typeof t?Qn[t]||Qn[t.toLowerCase()]:void 0}function C(t){var e,n,r={};for(n in t)a(t,n)&&(e=D(n),e&&(r[e]=t[n]));return r}function j(t){return t instanceof Function||"[object Function]"===Object.prototype.toString.call(t)}function P(t,n){return function(r){return null!=r?(k(this,t,r),e.updateOffset(this,n),this):A(this,t)}}function A(t,e){return t.isValid()?t._d["get"+(t._isUTC?"UTC":"")+e]():NaN}function k(t,e,n){t.isValid()&&t._d["set"+(t._isUTC?"UTC":"")+e](n)}function L(t,e){var n;if("object"==typeof t)for(n in t)this.set(n,t[n]);else if(t=D(t),j(this[t]))return this[t](e);return this}function N(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 R(t,e,n,r){var i=r;"string"==typeof r&&(i=function(){return this[r]()}),t&&(rr[t]=i),e&&(rr[e[0]]=function(){return N(i.apply(this,arguments),e[1],e[2])}),n&&(rr[n]=function(){return this.localeData().ordinal(i.apply(this,arguments),t)})}function x(t){return t.match(/\[[\s\S]/)?t.replace(/^\[|\]$/g,""):t.replace(/\\/g,"")}function H(t){var e,n,r=t.match(tr);for(e=0,n=r.length;n>e;e++)rr[r[e]]?r[e]=rr[r[e]]:r[e]=x(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 Y(t,e){return t.isValid()?(e=z(e,t.localeData()),nr[e]=nr[e]||H(e),nr[e](t)):t.localeData().invalidDate()}function z(t,e){function n(t){return e.longDateFormat(t)||t}var r=5;for(er.lastIndex=0;r>=0&&er.test(t);)t=t.replace(er,n),er.lastIndex=0,r-=1;return t}function U(t,e,n){Sr[t]=j(e)?e:function(t,r){return t&&n?n:e}}function V(t,e){return a(Sr,t)?Sr[t](e._strict,e._locale):new RegExp(G(t))}function G(t){return F(t.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(t,e,n,r,i){return e||n||r||i}))}function F(t){return t.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function B(t,e){var n,r=e;for("string"==typeof t&&(t=[t]),"number"==typeof e&&(r=function(t,n){n[e]=m(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 X(t,e){var n;return t.isValid()?"string"==typeof e&&(e=t.localeData().monthsParse(e),"number"!=typeof e)?t:(n=Math.min(t.date(),K(t.year(),e)),t._d["set"+(t._isUTC?"UTC":"")+"Month"](e,n),t):t}function Q(t){return null!=t?(X(this,t),e.updateOffset(this,!0),this):A(this,"Month")}function tt(){return K(this.year(),this.month())}function et(t){return this._monthsParseExact?(a(this,"_monthsRegex")||rt.call(this),t?this._monthsShortStrictRegex:this._monthsShortRegex):this._monthsShortStrictRegex&&t?this._monthsShortStrictRegex:this._monthsShortRegex}function nt(t){return this._monthsParseExact?(a(this,"_monthsRegex")||rt.call(this),t?this._monthsStrictRegex:this._monthsRegex):this._monthsStrictRegex&&t?this._monthsStrictRegex:this._monthsRegex}function rt(){function t(t,e){return e.length-t.length}var e,n,r=[],i=[],o=[];for(e=0;12>e;e++)n=s([2e3,e]),r.push(this.monthsShort(n,"")),i.push(this.months(n,"")),o.push(this.months(n,"")),o.push(this.monthsShort(n,""));for(r.sort(t),i.sort(t),o.sort(t),e=0;12>e;e++)r[e]=F(r[e]),i[e]=F(i[e]),o[e]=F(o[e]);this._monthsRegex=new RegExp("^("+o.join("|")+")","i"),this._monthsShortRegex=this._monthsRegex,this._monthsStrictRegex=new RegExp("^("+i.join("|")+")$","i"),this._monthsShortStrictRegex=new RegExp("^("+r.join("|")+")$","i")}function it(t){var e,n=t._a;return n&&-2===l(t).overflow&&(e=n[Mr]<0||n[Mr]>11?Mr:n[Ir]<1||n[Ir]>K(n[Or],n[Mr])?Ir:n[Er]<0||n[Er]>24||24===n[Er]&&(0!==n[Tr]||0!==n[Dr]||0!==n[Cr])?Er:n[Tr]<0||n[Tr]>59?Tr:n[Dr]<0||n[Dr]>59?Dr:n[Cr]<0||n[Cr]>999?Cr:-1,l(t)._overflowDayOfYear&&(Or>e||e>Ir)&&(e=Ir),l(t)._overflowWeeks&&-1===e&&(e=jr),l(t)._overflowWeekday&&-1===e&&(e=Pr),l(t).overflow=e),t}function ot(t){e.suppressDeprecationWarnings===!1&&"undefined"!=typeof console&&console.warn&&console.warn("Deprecation warning: "+t)}function at(t,e){var n=!0;return u(function(){return n&&(ot(t+"\nArguments: "+Array.prototype.slice.call(arguments).join(", ")+"\n"+(new Error).stack),n=!1),e.apply(this,arguments)},e)}function ut(t,e){xr[t]||(ot(e),xr[t]=!0)}function st(t){var e,n,r,i,o,a,u=t._i,s=Hr.exec(u)||Yr.exec(u);if(s){for(l(t).iso=!0,e=0,n=Ur.length;n>e;e++)if(Ur[e][1].exec(s[1])){i=Ur[e][0],r=Ur[e][2]!==!1;break}if(null==i)return void(t._isValid=!1);if(s[3]){for(e=0,n=Vr.length;n>e;e++)if(Vr[e][1].exec(s[3])){o=(s[2]||" ")+Vr[e][0];break}if(null==o)return void(t._isValid=!1)}if(!r&&null!=o)return void(t._isValid=!1);if(s[4]){if(!zr.exec(s[4]))return void(t._isValid=!1);a="Z"}t._f=i+(o||"")+(a||""),Ot(t)}else t._isValid=!1}function ct(t){var n=Gr.exec(t._i);return null!==n?void(t._d=new Date(+n[1])):(st(t),void(t._isValid===!1&&(delete t._isValid,e.createFromInputFallback(t))))}function lt(t,e,n,r,i,o,a){var u=new Date(t,e,n,r,i,o,a);return 100>t&&t>=0&&isFinite(u.getFullYear())&&u.setFullYear(t),u}function ft(t){var e=new Date(Date.UTC.apply(null,arguments));return 100>t&&t>=0&&isFinite(e.getUTCFullYear())&&e.setUTCFullYear(t),e}function dt(t){return ht(t)?366:365}function ht(t){return t%4===0&&t%100!==0||t%400===0}function pt(){return ht(this.year())}function _t(t,e,n){var r=7+e-n,i=(7+ft(t,0,r).getUTCDay()-e)%7;return-i+r-1}function vt(t,e,n,r,i){var o,a,u=(7+n-r)%7,s=_t(t,r,i),c=1+7*(e-1)+u+s;return 0>=c?(o=t-1,a=dt(o)+c):c>dt(t)?(o=t+1,a=c-dt(t)):(o=t,a=c),{year:o,dayOfYear:a}}function yt(t,e,n){var r,i,o=_t(t.year(),e,n),a=Math.floor((t.dayOfYear()-o-1)/7)+1;return 1>a?(i=t.year()-1,r=a+mt(i,e,n)):a>mt(t.year(),e,n)?(r=a-mt(t.year(),e,n),i=t.year()+1):(i=t.year(),r=a),{week:r,year:i}}function mt(t,e,n){var r=_t(t,e,n),i=_t(t+1,e,n);return(dt(t)-r+i)/7}function gt(t,e,n){return null!=t?t:null!=e?e:n}function bt(t){var n=new Date(e.now());return t._useUTC?[n.getUTCFullYear(),n.getUTCMonth(),n.getUTCDate()]:[n.getFullYear(),n.getMonth(),n.getDate()]}function St(t){var e,n,r,i,o=[];if(!t._d){for(r=bt(t),t._w&&null==t._a[Ir]&&null==t._a[Mr]&&wt(t),t._dayOfYear&&(i=gt(t._a[Or],r[Or]),t._dayOfYear>dt(i)&&(l(t)._overflowDayOfYear=!0),n=ft(i,0,t._dayOfYear),t._a[Mr]=n.getUTCMonth(),t._a[Ir]=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[Er]&&0===t._a[Tr]&&0===t._a[Dr]&&0===t._a[Cr]&&(t._nextDay=!0,t._a[Er]=0),t._d=(t._useUTC?ft:lt).apply(null,o),null!=t._tzm&&t._d.setUTCMinutes(t._d.getUTCMinutes()-t._tzm),t._nextDay&&(t._a[Er]=24)}}function wt(t){var e,n,r,i,o,a,u,s;e=t._w,null!=e.GG||null!=e.W||null!=e.E?(o=1,a=4,n=gt(e.GG,t._a[Or],yt(Pt(),1,4).year),r=gt(e.W,1),i=gt(e.E,1),(1>i||i>7)&&(s=!0)):(o=t._locale._week.dow,a=t._locale._week.doy,n=gt(e.gg,t._a[Or],yt(Pt(),o,a).year),r=gt(e.w,1),null!=e.d?(i=e.d,(0>i||i>6)&&(s=!0)):null!=e.e?(i=e.e+o,(e.e<0||e.e>6)&&(s=!0)):i=o),1>r||r>mt(n,o,a)?l(t)._overflowWeeks=!0:null!=s?l(t)._overflowWeekday=!0:(u=vt(n,r,i,o,a),t._a[Or]=u.year,t._dayOfYear=u.dayOfYear)}function Ot(t){if(t._f===e.ISO_8601)return void st(t);t._a=[],l(t).empty=!0;var n,r,i,o,a,u=""+t._i,s=u.length,c=0;for(i=z(t._f,t._locale).match(tr)||[],n=0;n0&&l(t).unusedInput.push(a),u=u.slice(u.indexOf(r)+r.length),c+=r.length),rr[o]?(r?l(t).empty=!1:l(t).unusedTokens.push(o),q(o,r,t)):t._strict&&!r&&l(t).unusedTokens.push(o);l(t).charsLeftOver=s-c,u.length>0&&l(t).unusedInput.push(u),l(t).bigHour===!0&&t._a[Er]<=12&&t._a[Er]>0&&(l(t).bigHour=void 0),t._a[Er]=Mt(t._locale,t._a[Er],t._meridiem),St(t),it(t)}function Mt(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 It(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));u(t,n||e)}function Et(t){if(!t._d){var e=C(t._i);t._a=o([e.year,e.month,e.day||e.date,e.hour,e.minute,e.second,e.millisecond],function(t){return t&&parseInt(t,10)}),St(t)}}function Tt(t){var e=new _(it(Dt(t)));return e._nextDay&&(e.add(1,"d"),e._nextDay=void 0),e}function Dt(t){var e=t._i,n=t._f;return t._locale=t._locale||E(t._l),null===e||void 0===n&&""===e?d({nullInput:!0}):("string"==typeof e&&(t._i=e=t._locale.preparse(e)),v(e)?new _(it(e)):(r(n)?It(t):n?Ot(t):i(e)?t._d=e:Ct(t),f(t)||(t._d=null),t))}function Ct(t){var n=t._i;void 0===n?t._d=new Date(e.now()):i(n)?t._d=new Date(+n):"string"==typeof n?ct(t):r(n)?(t._a=o(n.slice(0),function(t){return parseInt(t,10)}),St(t)):"object"==typeof n?Et(t):"number"==typeof n?t._d=new Date(n):e.createFromInputFallback(t)}function jt(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,Tt(o)}function Pt(t,e,n,r){return jt(t,e,n,r,!1)}function At(t,e){var n,i;if(1===e.length&&r(e[0])&&(e=e[0]),!e.length)return Pt();for(n=e[0],i=1;it&&(t=-t,n="-"),n+N(~~(t/60),2)+e+N(~~t%60,2)})}function Ht(t,e){var n=(e||"").match(t)||[],r=n[n.length-1]||[],i=(r+"").match(Kr)||["-",0,0],o=+(60*i[1])+m(i[2]);return"+"===i[0]?o:-o}function Yt(t,n){var r,o;return n._isUTC?(r=n.clone(),o=(v(t)||i(t)?+t:+Pt(t))-+r,r._d.setTime(+r._d+o),e.updateOffset(r,!1),r):Pt(t).local()}function zt(t){return 15*-Math.round(t._d.getTimezoneOffset()/15)}function Ut(t,n){var r,i=this._offset||0;return this.isValid()?null!=t?("string"==typeof t?t=Ht(mr,t):Math.abs(t)<16&&(t=60*t),!this._isUTC&&n&&(r=zt(this)),this._offset=t,this._isUTC=!0,null!=r&&this.add(r,"m"),i!==t&&(!n||this._changeInProgress?re(this,Xt(t-i,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,e.updateOffset(this,!0),this._changeInProgress=null)),this):this._isUTC?i:zt(this):null!=t?this:NaN}function Vt(t,e){return null!=t?("string"!=typeof t&&(t=-t),this.utcOffset(t,e),this):-this.utcOffset()}function Gt(t){return this.utcOffset(0,t)}function Ft(t){return this._isUTC&&(this.utcOffset(0,t),this._isUTC=!1,t&&this.subtract(zt(this),"m")),this}function Bt(){return this._tzm?this.utcOffset(this._tzm):"string"==typeof this._i&&this.utcOffset(Ht(yr,this._i)),this}function Wt(t){return this.isValid()?(t=t?Pt(t).utcOffset():0,(this.utcOffset()-t)%60===0):!1}function qt(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()}function Kt(){if(!h(this._isDSTShifted))return this._isDSTShifted;var t={};if(p(t,this),t=Dt(t),t._a){var e=t._isUTC?s(t._a):Pt(t._a);this._isDSTShifted=this.isValid()&&g(t._a,e.toArray())>0}else this._isDSTShifted=!1;return this._isDSTShifted}function Jt(){return this.isValid()?!this._isUTC:!1}function $t(){return this.isValid()?this._isUTC:!1}function Zt(){return this.isValid()?this._isUTC&&0===this._offset:!1}function Xt(t,e){var n,r,i,o=t,u=null;return Rt(t)?o={ms:t._milliseconds,d:t._days,M:t._months}:"number"==typeof t?(o={},e?o[e]=t:o.milliseconds=t):(u=Jr.exec(t))?(n="-"===u[1]?-1:1,o={y:0,d:m(u[Ir])*n,h:m(u[Er])*n,m:m(u[Tr])*n,s:m(u[Dr])*n,ms:m(u[Cr])*n}):(u=$r.exec(t))?(n="-"===u[1]?-1:1,o={y:Qt(u[2],n),M:Qt(u[3],n),d:Qt(u[4],n),h:Qt(u[5],n),m:Qt(u[6],n),s:Qt(u[7],n),w:Qt(u[8],n)}):null==o?o={}:"object"==typeof o&&("from"in o||"to"in o)&&(i=ee(Pt(o.from),Pt(o.to)),o={},o.ms=i.milliseconds,o.M=i.months),r=new Nt(o),Rt(t)&&a(t,"_locale")&&(r._locale=t._locale),r}function Qt(t,e){var n=t&&parseFloat(t.replace(",","."));return(isNaN(n)?0:n)*e}function te(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 ee(t,e){var n;return t.isValid()&&e.isValid()?(e=Yt(e,t),t.isBefore(e)?n=te(t,e):(n=te(e,t),n.milliseconds=-n.milliseconds,n.months=-n.months),n):{milliseconds:0,months:0}}function ne(t,e){return function(n,r){var i,o;return null===r||isNaN(+r)||(ut(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=Xt(n,r),re(this,i,t),this}}function re(t,n,r,i){var o=n._milliseconds,a=n._days,u=n._months;t.isValid()&&(i=null==i?!0:i,o&&t._d.setTime(+t._d+o*r),a&&k(t,"Date",A(t,"Date")+a*r),u&&X(t,A(t,"Month")+u*r),i&&e.updateOffset(t,a||u))}function ie(t,e){var n=t||Pt(),r=Yt(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",a=e&&(j(e[o])?e[o]():e[o]);return this.format(a||this.localeData().calendar(o,this,Pt(n)))}function oe(){return new _(this)}function ae(t,e){var n=v(t)?t:Pt(t);return this.isValid()&&n.isValid()?(e=D(h(e)?"millisecond":e),"millisecond"===e?+this>+n:+n<+this.clone().startOf(e)):!1}function ue(t,e){var n=v(t)?t:Pt(t);return this.isValid()&&n.isValid()?(e=D(h(e)?"millisecond":e),"millisecond"===e?+n>+this:+this.clone().endOf(e)<+n):!1}function se(t,e,n){return this.isAfter(t,n)&&this.isBefore(e,n)}function ce(t,e){var n,r=v(t)?t:Pt(t);return this.isValid()&&r.isValid()?(e=D(e||"millisecond"),"millisecond"===e?+this===+r:(n=+r,+this.clone().startOf(e)<=n&&n<=+this.clone().endOf(e))):!1}function le(t,e){return this.isSame(t,e)||this.isAfter(t,e)}function fe(t,e){return this.isSame(t,e)||this.isBefore(t,e)}function de(t,e,n){var r,i,o,a;return this.isValid()?(r=Yt(t,this),r.isValid()?(i=6e4*(r.utcOffset()-this.utcOffset()),e=D(e),"year"===e||"month"===e||"quarter"===e?(a=he(this,r),"quarter"===e?a/=3:"year"===e&&(a/=12)):(o=this-r,a="second"===e?o/1e3:"minute"===e?o/6e4:"hour"===e?o/36e5:"day"===e?(o-i)/864e5:"week"===e?(o-i)/6048e5:o),n?a:y(a)):NaN):NaN}function he(t,e){var n,r,i=12*(e.year()-t.year())+(e.month()-t.month()),o=t.clone().add(i,"months");return 0>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 pe(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")}function _e(){var t=this.clone().utc();return 0o&&(e=o),Ue.call(this,t,e,n,r,i))}function Ue(t,e,n,r,i){var o=vt(t,e,n,r,i),a=ft(o.year,0,o.dayOfYear);return this.year(a.getUTCFullYear()),this.month(a.getUTCMonth()),this.date(a.getUTCDate()),this}function Ve(t){return null==t?Math.ceil((this.month()+1)/3):this.month(3*(t-1)+this.month()%3)}function Ge(t){return yt(t,this._week.dow,this._week.doy).week}function Fe(){return this._week.dow}function Be(){return this._week.doy}function We(t){var e=this.localeData().week(this);return null==t?e:this.add(7*(t-e),"d")}function qe(t){var e=yt(this,1,4).week;return null==t?e:this.add(7*(t-e),"d")}function Ke(t,e){return"string"!=typeof t?t:isNaN(t)?(t=e.weekdaysParse(t),"number"==typeof t?t:null):parseInt(t,10)}function Je(t,e){return r(this._weekdays)?this._weekdays[t.day()]:this._weekdays[this._weekdays.isFormat.test(e)?"format":"standalone"][t.day()]}function $e(t){return this._weekdaysShort[t.day()]}function Ze(t){return this._weekdaysMin[t.day()]}function Xe(t,e,n){var r,i,o;for(this._weekdaysParse||(this._weekdaysParse=[],this._minWeekdaysParse=[],this._shortWeekdaysParse=[],this._fullWeekdaysParse=[]),r=0;7>r;r++){if(i=Pt([2e3,1]).day(r),n&&!this._fullWeekdaysParse[r]&&(this._fullWeekdaysParse[r]=new RegExp("^"+this.weekdays(i,"").replace(".",".?")+"$","i"),this._shortWeekdaysParse[r]=new RegExp("^"+this.weekdaysShort(i,"").replace(".",".?")+"$","i"),this._minWeekdaysParse[r]=new RegExp("^"+this.weekdaysMin(i,"").replace(".",".?")+"$","i")),this._weekdaysParse[r]||(o="^"+this.weekdays(i,"")+"|^"+this.weekdaysShort(i,"")+"|^"+this.weekdaysMin(i,""),this._weekdaysParse[r]=new RegExp(o.replace(".",""),"i")),n&&"dddd"===e&&this._fullWeekdaysParse[r].test(t))return r;if(n&&"ddd"===e&&this._shortWeekdaysParse[r].test(t))return r;if(n&&"dd"===e&&this._minWeekdaysParse[r].test(t))return r;if(!n&&this._weekdaysParse[r].test(t))return r}}function Qe(t){if(!this.isValid())return null!=t?this:NaN;var e=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=t?(t=Ke(t,this.localeData()),this.add(t-e,"d")):e}function tn(t){if(!this.isValid())return null!=t?this:NaN;var e=(this.day()+7-this.localeData()._week.dow)%7;return null==t?e:this.add(t-e,"d")}function en(t){return this.isValid()?null==t?this.day()||7:this.day(this.day()%7?t:t-7):null!=t?this:NaN; +}function nn(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 rn(){return this.hours()%12||12}function on(t,e){R(t,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),e)})}function an(t,e){return e._meridiemParse}function un(t){return"p"===(t+"").toLowerCase().charAt(0)}function sn(t,e,n){return t>11?n?"pm":"PM":n?"am":"AM"}function cn(t,e){e[Cr]=m(1e3*("0."+t))}function ln(){return this._isUTC?"UTC":""}function fn(){return this._isUTC?"Coordinated Universal Time":""}function dn(t){return Pt(1e3*t)}function hn(){return Pt.apply(null,arguments).parseZone()}function pn(t,e,n){var r=this._calendar[t];return j(r)?r.call(e,n):r}function _n(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 vn(){return this._invalidDate}function yn(t){return this._ordinal.replace("%d",t)}function mn(t){return t}function gn(t,e,n,r){var i=this._relativeTime[n];return j(i)?i(t,e,n,r):i.replace(/%d/i,t)}function bn(t,e){var n=this._relativeTime[t>0?"future":"past"];return j(n)?n(e):n.replace(/%s/i,e)}function Sn(t){var e,n;for(n in t)e=t[n],j(e)?this[n]=e:this["_"+n]=e;this._ordinalParseLenient=new RegExp(this._ordinalParse.source+"|"+/\d{1,2}/.source)}function wn(t,e,n,r){var i=E(),o=s().set(r,e);return i[n](o,t)}function On(t,e,n,r,i){if("number"==typeof t&&(e=t,t=void 0),t=t||"",null!=e)return wn(t,e,n,i);var o,a=[];for(o=0;r>o;o++)a[o]=wn(t,o,n,i);return a}function Mn(t,e){return On(t,e,"months",12,"month")}function In(t,e){return On(t,e,"monthsShort",12,"month")}function En(t,e){return On(t,e,"weekdays",7,"day")}function Tn(t,e){return On(t,e,"weekdaysShort",7,"day")}function Dn(t,e){return On(t,e,"weekdaysMin",7,"day")}function Cn(){var t=this._data;return this._milliseconds=bi(this._milliseconds),this._days=bi(this._days),this._months=bi(this._months),t.milliseconds=bi(t.milliseconds),t.seconds=bi(t.seconds),t.minutes=bi(t.minutes),t.hours=bi(t.hours),t.months=bi(t.months),t.years=bi(t.years),this}function jn(t,e,n,r){var i=Xt(e,n);return t._milliseconds+=r*i._milliseconds,t._days+=r*i._days,t._months+=r*i._months,t._bubble()}function Pn(t,e){return jn(this,t,e,1)}function An(t,e){return jn(this,t,e,-1)}function kn(t){return 0>t?Math.floor(t):Math.ceil(t)}function Ln(){var t,e,n,r,i,o=this._milliseconds,a=this._days,u=this._months,s=this._data;return o>=0&&a>=0&&u>=0||0>=o&&0>=a&&0>=u||(o+=864e5*kn(Rn(u)+a),a=0,u=0),s.milliseconds=o%1e3,t=y(o/1e3),s.seconds=t%60,e=y(t/60),s.minutes=e%60,n=y(e/60),s.hours=n%24,a+=y(n/24),i=y(Nn(a)),u+=i,a-=kn(Rn(i)),r=y(u/12),u%=12,s.days=a,s.months=u,s.years=r,this}function Nn(t){return 4800*t/146097}function Rn(t){return 146097*t/4800}function xn(t){var e,n,r=this._milliseconds;if(t=D(t),"month"===t||"year"===t)return e=this._days+r/864e5,n=this._months+Nn(e),"month"===t?n:n/12;switch(e=this._days+Math.round(Rn(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 Hn(){return this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*m(this._months/12)}function Yn(t){return function(){return this.as(t)}}function zn(t){return t=D(t),this[t+"s"]()}function Un(t){return function(){return this._data[t]}}function Vn(){return y(this.days()/7)}function Gn(t,e,n,r,i){return i.relativeTime(e||1,!!n,t,r)}function Fn(t,e,n){var r=Xt(t).abs(),i=Ri(r.as("s")),o=Ri(r.as("m")),a=Ri(r.as("h")),u=Ri(r.as("d")),s=Ri(r.as("M")),c=Ri(r.as("y")),l=i=o&&["m"]||o=a&&["h"]||a=u&&["d"]||u=s&&["M"]||s=c&&["y"]||["yy",c];return l[2]=e,l[3]=+t>0,l[4]=n,Gn.apply(null,l)}function Bn(t,e){return void 0===xi[t]?!1:void 0===e?xi[t]:(xi[t]=e,!0)}function Wn(t){var e=this.localeData(),n=Fn(this,!t,e);return t&&(n=e.pastFuture(+this,n)),e.postformat(n)}function qn(){var t,e,n,r=Hi(this._milliseconds)/1e3,i=Hi(this._days),o=Hi(this._months);t=y(r/60),e=y(t/60),r%=60,t%=60,n=y(o/12),o%=12;var a=n,u=o,s=i,c=e,l=t,f=r,d=this.asSeconds();return d?(0>d?"-":"")+"P"+(a?a+"Y":"")+(u?u+"M":"")+(s?s+"D":"")+(c||l||f?"T":"")+(c?c+"H":"")+(l?l+"M":"")+(f?f+"S":""):"P0D"}var Kn,Jn,$n=e.momentProperties=[],Zn=!1,Xn={},Qn={},tr=/(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,er=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,nr={},rr={},ir=/\d/,or=/\d\d/,ar=/\d{3}/,ur=/\d{4}/,sr=/[+-]?\d{6}/,cr=/\d\d?/,lr=/\d\d\d\d?/,fr=/\d\d\d\d\d\d?/,dr=/\d{1,3}/,hr=/\d{1,4}/,pr=/[+-]?\d{1,6}/,_r=/\d+/,vr=/[+-]?\d+/,yr=/Z|[+-]\d\d:?\d\d/gi,mr=/Z|[+-]\d\d(?::?\d\d)?/gi,gr=/[+-]?\d+(\.\d{1,3})?/,br=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,Sr={},wr={},Or=0,Mr=1,Ir=2,Er=3,Tr=4,Dr=5,Cr=6,jr=7,Pr=8;R("M",["MM",2],"Mo",function(){return this.month()+1}),R("MMM",0,0,function(t){return this.localeData().monthsShort(this,t)}),R("MMMM",0,0,function(t){return this.localeData().months(this,t)}),T("month","M"),U("M",cr),U("MM",cr,or),U("MMM",function(t,e){return e.monthsShortRegex(t)}),U("MMMM",function(t,e){return e.monthsRegex(t)}),B(["M","MM"],function(t,e){e[Mr]=m(t)-1}),B(["MMM","MMMM"],function(t,e,n,r){var i=n._locale.monthsParse(t,r,n._strict);null!=i?e[Mr]=i:l(n).invalidMonth=t});var Ar=/D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/,kr="January_February_March_April_May_June_July_August_September_October_November_December".split("_"),Lr="Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),Nr=br,Rr=br,xr={};e.suppressDeprecationWarnings=!1;var Hr=/^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?/,Yr=/^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?/,zr=/Z|[+-]\d\d(?::?\d\d)?/,Ur=[["YYYYYY-MM-DD",/[+-]\d{6}-\d\d-\d\d/],["YYYY-MM-DD",/\d{4}-\d\d-\d\d/],["GGGG-[W]WW-E",/\d{4}-W\d\d-\d/],["GGGG-[W]WW",/\d{4}-W\d\d/,!1],["YYYY-DDD",/\d{4}-\d{3}/],["YYYY-MM",/\d{4}-\d\d/,!1],["YYYYYYMMDD",/[+-]\d{10}/],["YYYYMMDD",/\d{8}/],["GGGG[W]WWE",/\d{4}W\d{3}/],["GGGG[W]WW",/\d{4}W\d{2}/,!1],["YYYYDDD",/\d{7}/]],Vr=[["HH:mm:ss.SSSS",/\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss,SSSS",/\d\d:\d\d:\d\d,\d+/],["HH:mm:ss",/\d\d:\d\d:\d\d/],["HH:mm",/\d\d:\d\d/],["HHmmss.SSSS",/\d\d\d\d\d\d\.\d+/],["HHmmss,SSSS",/\d\d\d\d\d\d,\d+/],["HHmmss",/\d\d\d\d\d\d/],["HHmm",/\d\d\d\d/],["HH",/\d\d/]],Gr=/^\/?Date\((\-?\d+)/i;e.createFromInputFallback=at("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":""))}),R("Y",0,0,function(){var t=this.year();return 9999>=t?""+t:"+"+t}),R(0,["YY",2],0,function(){return this.year()%100}),R(0,["YYYY",4],0,"year"),R(0,["YYYYY",5],0,"year"),R(0,["YYYYYY",6,!0],0,"year"),T("year","y"),U("Y",vr),U("YY",cr,or),U("YYYY",hr,ur),U("YYYYY",pr,sr),U("YYYYYY",pr,sr),B(["YYYYY","YYYYYY"],Or),B("YYYY",function(t,n){n[Or]=2===t.length?e.parseTwoDigitYear(t):m(t)}),B("YY",function(t,n){n[Or]=e.parseTwoDigitYear(t)}),B("Y",function(t,e){e[Or]=parseInt(t,10)}),e.parseTwoDigitYear=function(t){return m(t)+(m(t)>68?1900:2e3)};var Fr=P("FullYear",!1);e.ISO_8601=function(){};var Br=at("moment().min is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548",function(){var t=Pt.apply(null,arguments);return this.isValid()&&t.isValid()?this>t?this:t:d()}),Wr=at("moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548",function(){var t=Pt.apply(null,arguments);return this.isValid()&&t.isValid()?t>this?this:t:d()}),qr=function(){return Date.now?Date.now():+new Date};xt("Z",":"),xt("ZZ",""),U("Z",mr),U("ZZ",mr),B(["Z","ZZ"],function(t,e,n){n._useUTC=!0,n._tzm=Ht(mr,t)});var Kr=/([\+\-]|\d\d)/gi;e.updateOffset=function(){};var Jr=/(\-)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/,$r=/^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/;Xt.fn=Nt.prototype;var Zr=ne(1,"add"),Xr=ne(-1,"subtract");e.defaultFormat="YYYY-MM-DDTHH:mm:ssZ";var Qr=at("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)});R(0,["gg",2],0,function(){return this.weekYear()%100}),R(0,["GG",2],0,function(){return this.isoWeekYear()%100}),Ne("gggg","weekYear"),Ne("ggggg","weekYear"),Ne("GGGG","isoWeekYear"),Ne("GGGGG","isoWeekYear"),T("weekYear","gg"),T("isoWeekYear","GG"),U("G",vr),U("g",vr),U("GG",cr,or),U("gg",cr,or),U("GGGG",hr,ur),U("gggg",hr,ur),U("GGGGG",pr,sr),U("ggggg",pr,sr),W(["gggg","ggggg","GGGG","GGGGG"],function(t,e,n,r){e[r.substr(0,2)]=m(t)}),W(["gg","GG"],function(t,n,r,i){n[i]=e.parseTwoDigitYear(t)}),R("Q",0,"Qo","quarter"),T("quarter","Q"),U("Q",ir),B("Q",function(t,e){e[Mr]=3*(m(t)-1)}),R("w",["ww",2],"wo","week"),R("W",["WW",2],"Wo","isoWeek"),T("week","w"),T("isoWeek","W"),U("w",cr),U("ww",cr,or),U("W",cr),U("WW",cr,or),W(["w","ww","W","WW"],function(t,e,n,r){e[r.substr(0,1)]=m(t)});var ti={dow:0,doy:6};R("D",["DD",2],"Do","date"),T("date","D"),U("D",cr),U("DD",cr,or),U("Do",function(t,e){return t?e._ordinalParse:e._ordinalParseLenient}),B(["D","DD"],Ir),B("Do",function(t,e){e[Ir]=m(t.match(cr)[0],10)});var ei=P("Date",!0);R("d",0,"do","day"),R("dd",0,0,function(t){return this.localeData().weekdaysMin(this,t)}),R("ddd",0,0,function(t){return this.localeData().weekdaysShort(this,t)}),R("dddd",0,0,function(t){return this.localeData().weekdays(this,t)}),R("e",0,0,"weekday"),R("E",0,0,"isoWeekday"),T("day","d"),T("weekday","e"),T("isoWeekday","E"),U("d",cr),U("e",cr),U("E",cr),U("dd",br),U("ddd",br),U("dddd",br),W(["dd","ddd","dddd"],function(t,e,n,r){var i=n._locale.weekdaysParse(t,r,n._strict);null!=i?e.d=i:l(n).invalidWeekday=t}),W(["d","e","E"],function(t,e,n,r){e[r]=m(t)});var ni="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),ri="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),ii="Su_Mo_Tu_We_Th_Fr_Sa".split("_");R("DDD",["DDDD",3],"DDDo","dayOfYear"),T("dayOfYear","DDD"),U("DDD",dr),U("DDDD",ar),B(["DDD","DDDD"],function(t,e,n){n._dayOfYear=m(t)}),R("H",["HH",2],0,"hour"),R("h",["hh",2],0,rn),R("hmm",0,0,function(){return""+rn.apply(this)+N(this.minutes(),2)}),R("hmmss",0,0,function(){return""+rn.apply(this)+N(this.minutes(),2)+N(this.seconds(),2)}),R("Hmm",0,0,function(){return""+this.hours()+N(this.minutes(),2)}),R("Hmmss",0,0,function(){return""+this.hours()+N(this.minutes(),2)+N(this.seconds(),2)}),on("a",!0),on("A",!1),T("hour","h"),U("a",an),U("A",an),U("H",cr),U("h",cr),U("HH",cr,or),U("hh",cr,or),U("hmm",lr),U("hmmss",fr),U("Hmm",lr),U("Hmmss",fr),B(["H","HH"],Er),B(["a","A"],function(t,e,n){n._isPm=n._locale.isPM(t),n._meridiem=t}),B(["h","hh"],function(t,e,n){e[Er]=m(t),l(n).bigHour=!0}),B("hmm",function(t,e,n){var r=t.length-2;e[Er]=m(t.substr(0,r)),e[Tr]=m(t.substr(r)),l(n).bigHour=!0}),B("hmmss",function(t,e,n){var r=t.length-4,i=t.length-2;e[Er]=m(t.substr(0,r)),e[Tr]=m(t.substr(r,2)),e[Dr]=m(t.substr(i)),l(n).bigHour=!0}),B("Hmm",function(t,e,n){var r=t.length-2;e[Er]=m(t.substr(0,r)),e[Tr]=m(t.substr(r))}),B("Hmmss",function(t,e,n){var r=t.length-4,i=t.length-2;e[Er]=m(t.substr(0,r)),e[Tr]=m(t.substr(r,2)),e[Dr]=m(t.substr(i))});var oi=/[ap]\.?m?\.?/i,ai=P("Hours",!0);R("m",["mm",2],0,"minute"),T("minute","m"),U("m",cr),U("mm",cr,or),B(["m","mm"],Tr);var ui=P("Minutes",!1);R("s",["ss",2],0,"second"),T("second","s"),U("s",cr),U("ss",cr,or),B(["s","ss"],Dr);var si=P("Seconds",!1);R("S",0,0,function(){return~~(this.millisecond()/100)}),R(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),R(0,["SSS",3],0,"millisecond"),R(0,["SSSS",4],0,function(){return 10*this.millisecond()}),R(0,["SSSSS",5],0,function(){return 100*this.millisecond()}),R(0,["SSSSSS",6],0,function(){return 1e3*this.millisecond()}),R(0,["SSSSSSS",7],0,function(){return 1e4*this.millisecond()}),R(0,["SSSSSSSS",8],0,function(){return 1e5*this.millisecond()}),R(0,["SSSSSSSSS",9],0,function(){return 1e6*this.millisecond()}),T("millisecond","ms"),U("S",dr,ir),U("SS",dr,or),U("SSS",dr,ar);var ci;for(ci="SSSS";ci.length<=9;ci+="S")U(ci,_r);for(ci="S";ci.length<=9;ci+="S")B(ci,cn);var li=P("Milliseconds",!1);R("z",0,0,"zoneAbbr"),R("zz",0,0,"zoneName");var fi=_.prototype;fi.add=Zr,fi.calendar=ie,fi.clone=oe,fi.diff=de,fi.endOf=Me,fi.format=ve,fi.from=ye,fi.fromNow=me,fi.to=ge,fi.toNow=be,fi.get=L,fi.invalidAt=ke,fi.isAfter=ae,fi.isBefore=ue,fi.isBetween=se,fi.isSame=ce,fi.isSameOrAfter=le,fi.isSameOrBefore=fe,fi.isValid=Pe,fi.lang=Qr,fi.locale=Se,fi.localeData=we,fi.max=Wr,fi.min=Br,fi.parsingFlags=Ae,fi.set=L,fi.startOf=Oe,fi.subtract=Xr,fi.toArray=De,fi.toObject=Ce,fi.toDate=Te,fi.toISOString=_e,fi.toJSON=je,fi.toString=pe,fi.unix=Ee,fi.valueOf=Ie,fi.creationData=Le,fi.year=Fr,fi.isLeapYear=pt,fi.weekYear=Re,fi.isoWeekYear=xe,fi.quarter=fi.quarters=Ve,fi.month=Q,fi.daysInMonth=tt,fi.week=fi.weeks=We,fi.isoWeek=fi.isoWeeks=qe,fi.weeksInYear=Ye,fi.isoWeeksInYear=He,fi.date=ei,fi.day=fi.days=Qe,fi.weekday=tn,fi.isoWeekday=en,fi.dayOfYear=nn,fi.hour=fi.hours=ai,fi.minute=fi.minutes=ui,fi.second=fi.seconds=si,fi.millisecond=fi.milliseconds=li,fi.utcOffset=Ut,fi.utc=Gt,fi.local=Ft,fi.parseZone=Bt,fi.hasAlignedHourOffset=Wt,fi.isDST=qt,fi.isDSTShifted=Kt,fi.isLocal=Jt,fi.isUtcOffset=$t,fi.isUtc=Zt,fi.isUTC=Zt,fi.zoneAbbr=ln,fi.zoneName=fn,fi.dates=at("dates accessor is deprecated. Use date instead.",ei),fi.months=at("months accessor is deprecated. Use month instead",Q),fi.years=at("years accessor is deprecated. Use year instead",Fr),fi.zone=at("moment().zone is deprecated, use moment().utcOffset instead. https://github.com/moment/moment/issues/1779",Vt);var di=fi,hi={sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},pi={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"},_i="Invalid date",vi="%d",yi=/\d{1,2}/,mi={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"},gi=b.prototype;gi._calendar=hi,gi.calendar=pn,gi._longDateFormat=pi,gi.longDateFormat=_n,gi._invalidDate=_i,gi.invalidDate=vn,gi._ordinal=vi,gi.ordinal=yn,gi._ordinalParse=yi,gi.preparse=mn,gi.postformat=mn,gi._relativeTime=mi,gi.relativeTime=gn,gi.pastFuture=bn,gi.set=Sn,gi.months=J,gi._months=kr,gi.monthsShort=$,gi._monthsShort=Lr,gi.monthsParse=Z,gi._monthsRegex=Rr,gi.monthsRegex=nt,gi._monthsShortRegex=Nr,gi.monthsShortRegex=et,gi.week=Ge,gi._week=ti,gi.firstDayOfYear=Be,gi.firstDayOfWeek=Fe,gi.weekdays=Je,gi._weekdays=ni,gi.weekdaysMin=Ze,gi._weekdaysMin=ii,gi.weekdaysShort=$e,gi._weekdaysShort=ri,gi.weekdaysParse=Xe,gi.isPM=un,gi._meridiemParse=oi,gi.meridiem=sn,M("en",{ordinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(t){var e=t%10,n=1===m(t%100/10)?"th":1===e?"st":2===e?"nd":3===e?"rd":"th";return t+n}}),e.lang=at("moment.lang is deprecated. Use moment.locale instead.",M),e.langData=at("moment.langData is deprecated. Use moment.localeData instead.",E);var bi=Math.abs,Si=Yn("ms"),wi=Yn("s"),Oi=Yn("m"),Mi=Yn("h"),Ii=Yn("d"),Ei=Yn("w"),Ti=Yn("M"),Di=Yn("y"),Ci=Un("milliseconds"),ji=Un("seconds"),Pi=Un("minutes"),Ai=Un("hours"),ki=Un("days"),Li=Un("months"),Ni=Un("years"),Ri=Math.round,xi={s:45,m:45,h:22,d:26,M:11},Hi=Math.abs,Yi=Nt.prototype;Yi.abs=Cn,Yi.add=Pn,Yi.subtract=An,Yi.as=xn,Yi.asMilliseconds=Si,Yi.asSeconds=wi,Yi.asMinutes=Oi,Yi.asHours=Mi,Yi.asDays=Ii,Yi.asWeeks=Ei,Yi.asMonths=Ti,Yi.asYears=Di,Yi.valueOf=Hn,Yi._bubble=Ln,Yi.get=zn,Yi.milliseconds=Ci,Yi.seconds=ji,Yi.minutes=Pi,Yi.hours=Ai,Yi.days=ki,Yi.weeks=Vn,Yi.months=Li,Yi.years=Ni,Yi.humanize=Wn,Yi.toISOString=qn,Yi.toString=qn,Yi.toJSON=qn,Yi.locale=Se,Yi.localeData=we,Yi.toIsoString=at("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",qn),Yi.lang=Qr,R("X",0,0,"unix"),R("x",0,0,"valueOf"),U("x",vr),U("X",gr),B("X",function(t,e,n){n._d=new Date(1e3*parseFloat(t,10))}),B("x",function(t,e,n){n._d=new Date(m(t))}),e.version="2.11.1",n(Pt),e.fn=di,e.min=kt,e.max=Lt,e.now=qr,e.utc=s,e.unix=dn,e.months=Mn,e.isDate=i,e.locale=M,e.invalid=d,e.duration=Xt,e.isMoment=v,e.weekdays=En,e.parseZone=hn,e.localeData=E,e.isDuration=Rt,e.monthsShort=In,e.weekdaysMin=Dn,e.defineLocale=I,e.weekdaysShort=Tn,e.normalizeUnits=D,e.relativeTimeThreshold=Bn,e.prototype=di;var zi=e;return zi})}).call(e,n(67)(t))},function(t,e){t.exports=function(t){return t.webpackPolyfill||(t.deprecate=function(){},t.paths=[],t.children=[],t.webpackPolyfill=1),t}},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 a=n(165),u=n(190),s=i(u),c=n(192),l=i(c),f=n(194),d=i(f),h=n(15),p=r(h),_=n(24),v=r(_),y=n(9),m=r(y),g=n(45),b=r(g),S=n(145),w=r(S),O=n(25),M=r(O),I=n(150),E=r(I),T=n(48),D=r(T),C=n(51),j=r(C),P=n(27),A=r(P),k=n(58),L=r(k),N=n(13),R=r(N),x=n(29),H=r(x),Y=n(31),z=r(Y),U=n(181),V=r(U),G=n(187),F=r(G),B=n(10),W=r(B),q=function K(){o(this,K);var t=(0,s["default"])();Object.defineProperties(this,{demo:{value:!1,enumerable:!0},localStoragePreferences:{value:a.localStoragePreferences,enumerable:!0},reactor:{value:t,enumerable:!0},util:{value:d["default"],enumerable:!0},startLocalStoragePreferencesSync:{value:a.localStoragePreferences.startSync.bind(a.localStoragePreferences,t)},startUrlSync:{value:j.urlSync.startSync.bind(null,t)},stopUrlSync:{value:j.urlSync.stopSync.bind(null,t)}}),(0,l["default"])(this,t,{auth:p,config:v,entity:m,entityHistory:b,errorLog:w,event:M,logbook:E,moreInfo:D,navigation:j,notification:A,view:L,service:R,stream:H,sync:z,template:V,voice:F,restApi:W})};e["default"]=q},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(76),e["default"]=new o["default"]({is:"ha-badges-card",properties:{states:{type:Array}}})},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),a=n(2),u=r(a),s=n(21),c=r(s);n(36),n(35),n(19);var l=u["default"].moreInfoActions;e["default"]=new o["default"]({is:"ha-domain-card",properties:{domain:{type:String},states:{type:Array},groupEntity:{type:Object}},computeDomainTitle:function(t){return t.replace(/_/g," ")},entityTapped:function(t){if(!t.target.classList.contains("paper-toggle-button")&&!t.target.classList.contains("paper-icon-button")){t.stopPropagation();var e=t.model.item.entityId;this.async(function(){return l.selectEntity(e)},1)}},showGroupToggle:function(t,e){return!t||!e||"on"!==t.state&&"off"!==t.state?!1:e.reduce(function(t,e){return t+(0,c["default"])(e.entityId)},0)>1}})},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(36),e["default"]=new o["default"]({is:"ha-introduction-card",properties:{showInstallInstruction:{type:Boolean,value:!1},showHideInstruction:{type:Boolean,value:!0}}})},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),a=n(41),u=r(a);e["default"]=new o["default"]({is:"display-time",properties:{dateObj:{type:Object}},computeTime:function(t){return t?(0,u["default"])(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(2),o=r(i),a=n(1),u=r(a),s=n(4),c=r(s),l=o["default"].entityGetters;e["default"]=new u["default"]({is:"entity-list",behaviors:[c["default"]],properties:{entities:{type:Array,bindNuclear:[l.entityMap,function(t){return t.valueSeq().sortBy(function(t){return t.entityId}).toArray()}]}},entitySelected:function(t){t.preventDefault(),this.fire("entity-selected",{entityId:t.model.entity.entityId})}})},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),a=n(2),u=r(a);n(17);var s=u["default"].reactor,c=u["default"].entityGetters,l=u["default"].moreInfoActions;e["default"]=new o["default"]({is:"ha-entity-marker",properties:{entityId:{type:String,value:""},state:{type:Object,computed:"computeState(entityId)"},icon:{type:Object,computed:"computeIcon(state)"},image:{type:Object,computed:"computeImage(state)"},value:{type:String,computed:"computeValue(state)"}},listeners:{tap:"badgeTap"},badgeTap:function(t){var e=this;t.stopPropagation(),this.entityId&&this.async(function(){return l.selectEntity(e.entityId)},1)},computeState:function(t){return t&&s.evaluate(c.byId(t))},computeIcon:function(t){return!t&&"home"},computeImage:function(t){return t&&t.attributes.entity_picture},computeValue:function(t){return t&&t.entityDisplay.split(" ").map(function(t){return t.substr(0,1)}).join("")}})},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),a=n(124),u=r(a);e["default"]=new o["default"]({is:"ha-state-icon",properties:{stateObj:{type:Object}},computeIcon:function(t){return(0,u["default"])(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),a=n(2),u=r(a),s=n(22),c=r(s),l=n(21),f=r(l);n(17);var d=u["default"].moreInfoActions,h=u["default"].serviceActions;e["default"]=new o["default"]({is:"ha-state-label-badge",properties:{state:{type:Object,observer:"stateChanged"}},listeners:{tap:"badgeTap"},badgeTap:function(t){var e=this;return t.stopPropagation(),(0,f["default"])(this.state.entityId)?void("scene"===this.state.domain?h.callTurnOn(this.state.entityId):"off"===this.state.state?h.callTurnOn(this.state.entityId):h.callTurnOff(this.state.entityId)):void this.async(function(){return d.selectEntity(e.state.entityId)},1)},computeClasses:function(t){switch(t.domain){case"scene":return"green";case"binary_sensor":case"script":return"on"===t.state?"blue":"grey";case"updater":return"blue";default:return""}},computeValue:function(t){switch(t.domain){case"binary_sensor":case"device_tracker":case"updater":case"sun":case"scene":case"script":case"alarm_control_panel":return;case"sensor":return t.state;default:return t.state}},computeIcon:function(t){switch(t.domain){case"alarm_control_panel":return"pending"===t.state?"mdi:clock-fast":"armed_away"===t.state?"mdi:nature":"armed_home"===t.state?"mdi:home-variant":(0,c["default"])(t.domain,t.state);case"binary_sensor":case"device_tracker":case"scene":case"updater":case"script":return(0,c["default"])(t.domain,t.state);case"sun":return"above_horizon"===t.state?(0,c["default"])(t.domain):"mdi:brightness-3";default:return}},computeImage:function(t){return t.attributes.entity_picture},computeLabel:function(t){switch(t.domain){case"scene":case"script":return t.domain;case"device_tracker":return"not_home"===t.state?"Away":t.state;case"alarm_control_panel":return"pending"===t.state?"pend":"armed_away"===t.state||"armed_home"===t.state?"armed":"disarm";default:return t.attributes.unit_of_measurement}},computeDescription:function(t){return t.entityDisplay},stateChanged:function(){this.updateStyles()}})},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(75),e["default"]=new o["default"]({is:"state-badge",properties:{stateObj:{type:Object,observer:"updateIconColor"}},updateIconColor:function(t){"light"===t.domain&&"on"===t.state&&t.attributes.rgb_color&&t.attributes.rgb_color.reduce(function(t,e){return t+e},0)<730?this.$.icon.style.color="rgb("+t.attributes.rgb_color.join(",")+")":this.$.icon.style.color=null}})},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=r(i),a=n(1),u=r(a),s=n(4),c=r(s),l=o["default"].eventGetters;e["default"]=new u["default"]({is:"events-list",behaviors:[c["default"]],properties:{events:{type:Array,bindNuclear:[l.entityMap,function(t){return t.valueSeq().sortBy(function(t){return t.event}).toArray()}]}},eventSelected:function(t){t.preventDefault(),this.fire("event-selected",{eventType:t.model.event.event})}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){return t in f?f[t]:30}function o(t){return"group"===t.domain?t.attributes.order:t.entityDisplay.toLowerCase()}Object.defineProperty(e,"__esModule",{value:!0});var a=n(1),u=r(a),s=n(2),c=r(s);n(81),n(69),n(70),n(71);var l=c["default"].util,f={configurator:-20,group:-10,a:-1,updater:0,sun:1,device_tracker:2,alarm_control_panel:3,camera:4,sensor:5,binary_sensor:6,scene:7,script:8};e["default"]=new u["default"]({is:"ha-cards",properties:{showIntroduction:{type:Boolean,value:!1},columns:{type:Number,value:2},states:{type:Object},cards:{type:Object,computed:"computeCards(columns, states, showIntroduction)"}},computeCards:function(t,e,n){function r(t){return t.filter(function(t){return!(t.entityId in c)})}function a(){var e=h;return h=(h+1)%t,e}function u(t,e){var n=arguments.length<=2||void 0===arguments[2]?!1:arguments[2];0!==e.length&&(f._columns[a()].push(t),f[t]={entities:e,groupEntity:n})}for(var s=e.groupBy(function(t){return t.domain}),c={},f={_demo:!1,_badges:[],_columns:[]},d=0;t>d;d++)f._columns[d]=[];var h=0;return n&&a(),s.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(s.get(t)).sortBy(o).toArray()):"group"===t?s.get(t).sortBy(o).forEach(function(t){var n=l.expandGroup(t,e);n.forEach(function(t){return c[t.entityId]=!0}),u(t.entityDisplay,n.toArray(),t)}):u(t,r(s.get(t)).sortBy(o).toArray())}),f},computeShouldRenderColumn:function(t,e){return 0===t||e.length},computeShowIntroduction:function(t,e,n){return 0===t&&(e||n._demo)},computeShowHideInstruction:function(t,e){return t.size>0&&!0&&!e._demo},computeGroupEntityOfCard:function(t,e){return e in t&&t[e].groupEntity},computeStatesOfCard:function(t,e){return e in t&&t[e].entities}})},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-color-picker",properties:{color:{type:Object},width:{type:Number},height:{type:Number}},listeners:{mousedown:"onMouseDown",mouseup:"onMouseUp",touchstart:"onTouchStart",touchend:"onTouchEnd"},onMouseDown:function(t){this.onMouseMove(t),this.addEventListener("mousemove",this.onMouseMove)},onMouseUp:function(){this.removeEventListener("mousemove",this.onMouseMove)},onTouchStart:function(t){this.onTouchMove(t),this.addEventListener("touchmove",this.onTouchMove)},onTouchEnd:function(){this.removeEventListener("touchmove",this.onTouchMove)},onTouchMove:function(t){var e=this;this.mouseMoveIsThrottled&&(this.mouseMoveIsThrottled=!1,this.processColorSelect(t.touches[0]),this.async(function(){return e.mouseMoveIsThrottled=!0},100))},onMouseMove:function(t){var e=this;this.mouseMoveIsThrottled&&(this.mouseMoveIsThrottled=!1,this.processColorSelect(t),this.async(function(){return e.mouseMoveIsThrottled=!0},100))},processColorSelect:function(t){var e=this.canvas.getBoundingClientRect();t.clientX=e.left+e.width||t.clientY=e.top+e.height||this.onColorSelect(t.clientX-e.left,t.clientY-e.top)},onColorSelect:function(t,e){var n=this.context.getImageData(t,e,1,1).data;this.setColor({r:n[0],g:n[1],b:n[2]})},setColor:function(t){this.color=t,this.fire("colorselected",{rgb:this.color})},ready:function(){var t=this;this.setColor=this.setColor.bind(this),this.mouseMoveIsThrottled=!0,this.canvas=this.children[0],this.context=this.canvas.getContext("2d"),this.debounce("drawGradient",function(){var e=getComputedStyle(t),n=parseInt(e.width,10),r=parseInt(e.height,10);t.width=n,t.height=r;var i=t.context.createLinearGradient(0,0,n,0);i.addColorStop(0,"rgb(255,0,0)"),i.addColorStop(.16,"rgb(255,0,255)"),i.addColorStop(.32,"rgb(0,0,255)"),i.addColorStop(.48,"rgb(0,255,255)"),i.addColorStop(.64,"rgb(0,255,0)"),i.addColorStop(.8,"rgb(255,255,0)"),i.addColorStop(1,"rgb(255,0,0)"),t.context.fillStyle=i,t.context.fillRect(0,0,n,r);var o=t.context.createLinearGradient(0,0,0,r);o.addColorStop(0,"rgba(255,255,255,1)"),o.addColorStop(.5,"rgba(255,255,255,0)"),o.addColorStop(.5,"rgba(0,0,0,0)"),o.addColorStop(1,"rgba(0,0,0,1)"),t.context.fillStyle=o,t.context.fillRect(0,0,n,r)},100)}})},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:"ha-demo-badge"})},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(84),e["default"]=new o["default"]({is:"ha-logbook",properties:{entries:{type:Object,value:[]}},noEntries:function(t){return!t.length}})},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=r(i),a=n(1),u=r(a),s=n(4),c=r(s);n(88);var l=o["default"].configGetters,f=o["default"].navigationGetters,d=o["default"].authActions,h=o["default"].navigationActions;e["default"]=new u["default"]({is:"ha-sidebar",behaviors:[c["default"]],properties:{menuShown:{type:Boolean},menuSelected:{type:String},selected:{type:String,bindNuclear:f.activePane,observer:"selectedChanged"},hasHistoryComponent:{type:Boolean,bindNuclear:l.isComponentLoaded("history")},hasLogbookComponent:{type:Boolean,bindNuclear:l.isComponentLoaded("logbook")}},selectedChanged:function(t){for(var e=this.querySelectorAll(".menu [data-panel]"),n=0;nnew Date&&(o=new Date);var u=e.map(function(t){function e(t,e){c&&e&&s.push([t[0]].concat(c.slice(1).map(function(t,n){return e[n]?t:null}))),s.push(t),c=t}var n=t[t.length-1],r=n.domain,a=n.entityDisplay,u=new window.google.visualization.DataTable;u.addColumn({type:"datetime",id:"Time"});var s=[],c=void 0;if("thermostat"===r){var l=t.reduce(function(t,e){return t||e.attributes.target_temp_high!==e.attributes.target_temp_low},!1);u.addColumn("number",a+" current temperature");var f=void 0;l?!function(){u.addColumn("number",a+" target temperature high"),u.addColumn("number",a+" target temperature low");var t=[!1,!0,!0];f=function(n){var r=i(n.attributes.current_temperature),o=i(n.attributes.target_temp_high),a=i(n.attributes.target_temp_low);e([n.lastUpdatedAsDate,r,o,a],t)}}():!function(){u.addColumn("number",a+" target temperature");var t=[!1,!0];f=function(n){var r=i(n.attributes.current_temperature),o=i(n.attributes.temperature);e([n.lastUpdatedAsDate,r,o],t)}}(),t.forEach(f)}else!function(){u.addColumn("number",a);var n="sensor"!==r&&[!0];t.forEach(function(t){var r=i(t.state);e([t.lastChangedAsDate,r],n)})}();return e([o].concat(c.slice(1)),!1),u.addRows(s),u}),s=void 0;s=1===u.length?u[0]:u.slice(1).reduce(function(t,e){return window.google.visualization.data.join(t,e,"full",[[0,0]],(0,a["default"])(1,t.getNumberOfColumns()),(0,a["default"])(1,e.getNumberOfColumns()))},u[0]),this.chartEngine.draw(s,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:"state-history-chart-timeline",properties:{data:{type:Object,observer:"dataChanged"},isAttached:{type:Boolean,value:!1,observer:"dataChanged"}},attached:function(){this.isAttached=!0},dataChanged:function(){this.drawChart()},drawChart:function(){function t(t,e,n,r){var o=e.replace(/_/g," ");i.addRow([t,o,n,r])}if(this.isAttached){for(var e=o["default"].dom(this),n=this.data;e.node.lastChild;)e.node.removeChild(e.node.lastChild);if(n&&0!==n.length){var r=new window.google.visualization.Timeline(this),i=new window.google.visualization.DataTable;i.addColumn({type:"string",id:"Entity"}),i.addColumn({type:"string",id:"State"}),i.addColumn({type:"date",id:"Start"}),i.addColumn({type:"date",id:"End"});var a=new Date(n.reduce(function(t,e){return Math.min(t,e[0].lastChangedAsDate)},new Date)),u=new Date(a);u.setDate(u.getDate()+1),u>new Date&&(u=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,u),s++}}),r.draw(i,{height:55+42*s,timeline:{showRowLabels:n.length>1},hAxis:{format:"H:mm"}})}}}})},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=r(i),a=n(1),u=r(a),s=n(4),c=r(s),l=o["default"].streamGetters,f=o["default"].streamActions;e["default"]=new u["default"]({is:"stream-status",behaviors:[c["default"]],properties:{isStreaming:{type:Boolean,bindNuclear:l.isStreamingEvents},hasError:{type:Boolean,bindNuclear:l.hasStreamingEventsError}},toggleChanged:function(){this.isStreaming?f.stop():f.start()}})},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=r(i),a=n(1),u=r(a),s=n(4),c=r(s),l=o["default"].voiceActions,f=o["default"].voiceGetters;e["default"]=new u["default"]({is:"ha-voice-command-dialog",behaviors:[c["default"]],properties:{dialogOpen:{type:Boolean,value:!1,observer:"dialogOpenChanged"},finalTranscript:{type:String,bindNuclear:f.finalTranscript},interimTranscript:{type:String,bindNuclear:f.extraInterimTranscript},isTransmitting:{type:Boolean,bindNuclear:f.isTransmitting},isListening:{type:Boolean,bindNuclear:f.isListening},showListenInterface:{type:Boolean,computed:"computeShowListenInterface(isListening, isTransmitting)",observer:"showListenInterfaceChanged"}},computeShowListenInterface:function(t,e){return t||e},dialogOpenChanged:function(t){!t&&this.isListening&&l.stop()},showListenInterfaceChanged:function(t){!t&&this.dialogOpen?this.dialogOpen=!1:t&&(this.dialogOpen=!0)}})},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=r(i),a=n(1),u=r(a),s=n(4),c=r(s);n(19),n(38),n(106);var l=o["default"].configGetters,f=o["default"].entityHistoryGetters,d=o["default"].entityHistoryActions,h=o["default"].moreInfoGetters,p=o["default"].moreInfoActions,_=["camera","configurator","scene"];e["default"]=new u["default"]({is:"more-info-dialog",behaviors:[c["default"]],properties:{stateObj:{type:Object,bindNuclear:h.currentEntity,observer:"stateObjChanged"},stateHistory:{type:Object,bindNuclear:[h.currentEntityHistory,function(t){return t?[t]:!1}]},isLoadingHistoryData:{type:Boolean,computed:"computeIsLoadingHistoryData(_delayedDialogOpen, _isLoadingHistoryData)"},_isLoadingHistoryData:{type:Boolean,bindNuclear:f.isLoadingEntityHistory},hasHistoryComponent:{type:Boolean,bindNuclear:l.isComponentLoaded("history"),observer:"fetchHistoryData"},shouldFetchHistory:{type:Boolean,bindNuclear:h.isCurrentEntityHistoryStale,observer:"fetchHistoryData"},showHistoryComponent:{type:Boolean,value:!1},dialogOpen:{type:Boolean,value:!1,observer:"dialogOpenChanged"},_delayedDialogOpen:{type:Boolean,value:!1}},computeIsLoadingHistoryData:function(t,e){return!t||e},fetchHistoryData:function(){this.stateObj&&this.hasHistoryComponent&&this.shouldFetchHistory&&d.fetchRecent(this.stateObj.entityId)},stateObjChanged:function(t){var e=this;return t?(this.showHistoryComponent=this.hasHistoryComponent&&-1===_.indexOf(this.stateObj.domain),void this.async(function(){e.fetchHistoryData(),e.dialogOpen=!0},10)):void(this.dialogOpen=!1)},dialogOpenChanged:function(t){var e=this;t?this.async(function(){return e._delayedDialogOpen=!0},10):!t&&this.stateObj&&(p.deselectEntity(),this._delayedDialogOpen=!1)}})},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),a=n(2),u=r(a),s=n(4),c=r(s),l=n(42),f=r(l);n(83),n(93),n(100),n(99),n(101),n(94),n(95),n(97),n(98),n(96),n(102),n(90),n(89);var d=u["default"].navigationActions,h=u["default"].navigationGetters,p=u["default"].startUrlSync,_=u["default"].stopUrlSync;e["default"]=new o["default"]({is:"home-assistant-main",behaviors:[c["default"]],properties:{narrow:{type:Boolean,value:!1},activePane:{type:String,bindNuclear:h.activePane,observer:"activePaneChanged"},isSelectedStates:{type:Boolean,bindNuclear:h.isActivePane("states")},isSelectedHistory:{type:Boolean,bindNuclear:h.isActivePane("history")},isSelectedMap:{type:Boolean,bindNuclear:h.isActivePane("map")},isSelectedLogbook:{type:Boolean,bindNuclear:h.isActivePane("logbook")},isSelectedDevEvent:{type:Boolean,bindNuclear:h.isActivePane("devEvent")},isSelectedDevState:{type:Boolean,bindNuclear:h.isActivePane("devState")},isSelectedDevTemplate:{type:Boolean,bindNuclear:h.isActivePane("devTemplate")},isSelectedDevService:{type:Boolean,bindNuclear:h.isActivePane("devService")},isSelectedDevInfo:{type:Boolean,bindNuclear:h.isActivePane("devInfo")},showSidebar:{type:Boolean,bindNuclear:h.showSidebar}},listeners:{"open-menu":"openMenu","close-menu":"closeMenu"},openMenu:function(){this.narrow?this.$.drawer.openDrawer():d.showSidebar(!0)},closeMenu:function(){this.$.drawer.closeDrawer(),this.showSidebar&&d.showSidebar(!1)},activePaneChanged:function(){this.narrow&&this.$.drawer.closeDrawer()},attached:function(){(0,f["default"])(),p()},computeForceNarrow:function(t,e){return t||!e},detached:function(){_()}})},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),a=n(2),u=r(a),s=n(4),c=r(s),l=n(43),f=r(l),d=n(42),h=r(d),p=u["default"].authGetters;e["default"]=new o["default"]({is:"login-form",behaviors:[c["default"]],properties:{errorMessage:{type:String,bindNuclear:p.attemptErrorMessage},isInvalid:{type:Boolean,bindNuclear:p.isInvalidAttempt},isValidating:{type:Boolean,observer:"isValidatingChanged",bindNuclear:p.isValidating},loadingResources:{type:Boolean,value:!1},forceShowLoading:{type:Boolean,value:!1},showLoading:{type:Boolean,computed:"computeShowSpinner(forceShowLoading, isValidating)"}},listeners:{keydown:"passwordKeyDown","loginButton.tap":"validatePassword"},observers:["validatingChanged(isValidating, isInvalid)"],attached:function(){(0,h["default"])()},computeShowSpinner:function(t,e){return t||e},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(),(0,f["default"])(this.$.passwordInput.value,this.$.rememberLogin.checked)}})},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=r(i),a=n(1),u=r(a),s=n(4),c=r(s);n(8),n(79);var l=o["default"].configGetters,f=o["default"].viewActions,d=o["default"].viewGetters,h=o["default"].voiceGetters,p=o["default"].streamGetters,_=o["default"].syncGetters,v=o["default"].syncActions,y=o["default"].voiceActions;e["default"]=new u["default"]({is:"partial-cards",behaviors:[c["default"]],properties:{narrow:{type:Boolean,value:!1},isFetching:{type:Boolean,bindNuclear:_.isFetching},isStreaming:{type:Boolean,bindNuclear:p.isStreamingEvents},canListen:{type:Boolean,bindNuclear:[h.isVoiceSupported,l.isComponentLoaded("conversation"),function(t,e){return t&&e}]},introductionLoaded:{type:Boolean,bindNuclear:l.isComponentLoaded("introduction")},locationName:{type:String,bindNuclear:l.locationName},showMenu:{type:Boolean,value:!1,observer:"windowChange"},currentView:{type:String,bindNuclear:[d.currentView,function(t){return t||""}]},views:{type:Array,bindNuclear:[d.views,function(t){return t.valueSeq().sortBy(function(t){return t.attributes.order}).toArray()}]},hasViews:{type:Boolean,computed:"computeHasViews(views)"},states:{type:Object,bindNuclear:d.currentViewEntities},columns:{type:Number,value:1}},created:function(){var t=this;this.windowChange=this.windowChange.bind(this);for(var e=[],n=0;5>n;n++)e.push(300+300*n);this.mqls=e.map(function(e){var n=window.matchMedia("(min-width: "+e+"px)");return n.addListener(t.windowChange),n})},detached:function(){var t=this;this.mqls.forEach(function(e){return e.removeListener(t.windowChange)})},windowChange:function(){var t=this.mqls.reduce(function(t,e){return t+e.matches},0);this.columns=Math.max(1,t-this.showMenu)},handleRefresh:function(){v.fetchAll()},handleListenClick:function(){y.listen()},computeMenuButtonClass:function(t,e){return!t&&e?"invisible":""},computeRefreshButtonClass:function(t){return t?"ha-spin":void 0},computeShowIntroduction:function(t,e,n){return""===t&&(e||0===n.size)},computeHasViews:function(t){return t.length>0},toggleMenu:function(){this.fire("open-menu")},viewSelected:function(t){var e=t.detail.item.getAttribute("data-entity")||null,n=this.currentView||null;e!==n&&this.async(function(){return f.selectView(e)},0)}})},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=r(i),a=n(1),u=r(a);n(8),n(85);var s=o["default"].reactor,c=o["default"].serviceActions,l=o["default"].serviceGetters;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:""},description:{type:String,computed:"computeDescription(domain, service)"}},computeDescription:function(t,e){return s.evaluate([l.entityMap,function(n){return n.has(t)&&n.get(t).get("services").has(e)?JSON.stringify(n.get(t).get("services").get(e).toJS(),null,2):"No description available"}])},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)}c.callService(this.domain,this.service,t)},computeFormClasses:function(t){return"layout "+(t?"vertical":"horizontal")}})},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=r(i),a=n(1),u=r(a);n(8),n(78);var s=o["default"].eventActions;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)}s.fireEvent(this.eventType,t)},computeFormClasses:function(t){return"layout "+(t?"vertical":"horizontal")}})},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=r(i),a=n(1),u=r(a),s=n(4),c=r(s);n(8);var l=o["default"].configGetters,f=o["default"].errorLogActions;e["default"]=new u["default"]({is:"partial-dev-info",behaviors:[c["default"]],properties:{narrow:{type:Boolean,value:!1},showMenu:{type:Boolean,value:!1},hassVersion:{type:String,bindNuclear:l.serverVersion},polymerVersion:{type:String,value:u["default"].version},nuclearVersion:{type:String,value:"1.3.0"},errorLog:{type:String,value:""}},attached:function(){this.refreshErrorLog()},refreshErrorLog:function(t){var e=this;t&&t.preventDefault(),this.errorLog="Loading error log…",f.fetchErrorLog().then(function(t){return e.errorLog=t||"No errors have been reported."})}})},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=r(i),a=n(1),u=r(a);n(8),n(73);var s=o["default"].reactor,c=o["default"].entityGetters,l=o["default"].entityActions;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=s.evaluate(c.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)}l.save({entityId:this.entityId,state:this.state,attributes:t})},computeFormClasses:function(t){return"layout "+(t?"vertical":"horizontal")}})},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=r(i),a=n(1),u=r(a),s=n(4),c=r(s);n(8);var l=o["default"].templateActions;e["default"]=new u["default"]({is:"partial-dev-template",behaviors:[c["default"]],properties:{narrow:{type:Boolean,value:!1},showMenu:{type:Boolean,value:!1},error:{type:Boolean,value:!1},rendering:{type:Boolean,value:!1},template:{type:String,value:'{%- if is_state("device_tracker.paulus", "home") and \n is_state("device_tracker.anne_therese", "home") -%}\n\n You are both home, you silly\n\n{%- else -%}\n\n Anne Therese is at {{ states("device_tracker.anne_therese") }} and Paulus is at {{ states("device_tracker.paulus") }}\n\n{%- endif %}\n\nFor loop example:\n\n{% for state in states.sensor -%}\n {%- if loop.first %}The {% elif loop.last %} and the {% else %}, the {% endif -%}\n {{ state.name | lower }} is {{state.state}} {{- state.attributes.unit_of_measurement}}\n{%- endfor -%}.',observer:"templateChanged"},processed:{type:String,value:""}},computeFormClasses:function(t){return"content fit layout "+(t?"vertical":"horizontal")},computeRenderedClasses:function(t){return t?"error rendered":"rendered"},templateChanged:function(){this.error&&(this.error=!1),this.debounce("render-template",this.renderTemplate,500)},renderTemplate:function(){var t=this;this.rendering=!0,l.render(this.template).then(function(e){t.processed=e,t.rendering=!1},function(e){t.processed=e.message,t.error=!0,t.rendering=!1})}})},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=r(i),a=n(1),u=r(a),s=n(4),c=r(s);n(8),n(38);var l=o["default"].entityHistoryGetters,f=o["default"].entityHistoryActions;e["default"]=new u["default"]({is:"partial-history",behaviors:[c["default"]],properties:{narrow:{type:Boolean},showMenu:{type:Boolean,value:!1},isDataLoaded:{type:Boolean,bindNuclear:l.hasDataForCurrentDate,observer:"isDataLoadedChanged"},stateHistory:{type:Object,bindNuclear:l.entityHistoryForCurrentDate},isLoadingData:{type:Boolean,bindNuclear:l.isLoadingEntityHistory},selectedDate:{type:String,value:null,bindNuclear:l.currentDate}},isDataLoadedChanged:function(t){t||this.async(function(){return f.fetchSelectedDate()},1)},handleRefreshClick:function(){f.fetchSelectedDate()},datepickerFocus:function(){this.datePicker.adjustPosition()},attached:function(){this.datePicker=new window.Pikaday({field:this.$.datePicker.inputElement,onSelect:f.changeCurrentDate})},detached:function(){this.datePicker.destroy()},computeContentClasses:function(t){return"flex content "+(t?"narrow":"wide")}})},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=r(i),a=n(1),u=r(a),s=n(4),c=r(s);n(8),n(82),n(18);var l=o["default"].logbookGetters,f=o["default"].logbookActions;e["default"]=new u["default"]({is:"partial-logbook",behaviors:[c["default"]],properties:{narrow:{type:Boolean,value:!1},showMenu:{type:Boolean,value:!1},selectedDate:{type:String,bindNuclear:l.currentDate},isLoading:{type:Boolean,bindNuclear:l.isLoadingEntries},isStale:{type:Boolean,bindNuclear:l.isCurrentStale,observer:"isStaleChanged"},entries:{type:Array,bindNuclear:[l.currentEntries,function(t){return t.reverse().toArray()}]},datePicker:{type:Object}},isStaleChanged:function(t){var e=this;t&&this.async(function(){return f.fetchDate(e.selectedDate)},1)},handleRefresh:function(){f.fetchDate(this.selectedDate)},datepickerFocus:function(){this.datePicker.adjustPosition()},attached:function(){this.datePicker=new window.Pikaday({field:this.$.datePicker.inputElement,onSelect:f.changeCurrentDate})},detached:function(){this.datePicker.destroy()}})},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=r(i),a=n(1),u=r(a),s=n(4),c=r(s);n(74);var l=o["default"].configGetters,f=o["default"].entityGetters;window.L.Icon.Default.imagePath="/static/images/leaflet",e["default"]=new u["default"]({is:"partial-map",behaviors:[c["default"]],properties:{locationGPS:{type:Number,bindNuclear:l.locationGPS},locationName:{type:String,bindNuclear:l.locationName},locationEntities:{type:Array,bindNuclear:[f.visibleEntityMap,function(t){return t.valueSeq().filter(function(t){return t.attributes.latitude&&"home"!==t.state}).toArray()}]},zoneEntities:{type:Array,bindNuclear:[f.entityMap,function(t){return t.valueSeq().filter(function(t){return"zone"===t.domain&&!t.attributes.passive}).toArray()}]},narrow:{type:Boolean},showMenu:{type:Boolean,value:!1}},attached:function(){var t=this;window.L.Browser.mobileWebkit&&this.async(function(){var e=t.$.map,n=e.style.display;e.style.display="none",t.async(function(){e.style.display=n},1)},1)},computeMenuButtonClass:function(t,e){return!t&&e?"invisible":""},toggleMenu:function(){this.fire("open-menu")}})},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=r(i),a=n(1),u=r(a),s=n(4),c=r(s),l=o["default"].notificationGetters;e["default"]=new u["default"]({is:"notification-manager",behaviors:[c["default"]],properties:{text:{type:String,bindNuclear:l.lastNotificationMessage,observer:"showNotification"}},showNotification:function(t){t&&this.$.toast.show()}})},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=r(i),a=n(1),u=r(a),s=o["default"].serviceActions;e["default"]=new u["default"]({is:"more-info-alarm_control_panel",handleDisarmTap:function(){this.callService("alarm_disarm",{code:this.enteredCode})},handleHomeTap:function(){this.callService("alarm_arm_home",{code:this.enteredCode})},handleAwayTap:function(){this.callService("alarm_arm_away",{code:this.enteredCode})},properties:{stateObj:{type:Object,observer:"stateObjChanged"},enteredCode:{type:String,value:""},disarmButtonVisible:{type:Boolean,value:!1},armHomeButtonVisible:{type:Boolean,value:!1},armAwayButtonVisible:{type:Boolean,value:!1},codeInputVisible:{type:Boolean,value:!1},codeInputEnabled:{type:Boolean,value:!1},codeFormat:{type:String,value:""},codeValid:{type:Boolean,computed:"validateCode(enteredCode, codeFormat)"}},validateCode:function(t,e){var n=new RegExp(e);return null===e?!0:n.test(t)},stateObjChanged:function(t){var e=this;t&&(this.codeFormat=t.attributes.code_format,this.codeInputVisible=null!==this.codeFormat,this.codeInputEnabled="armed_home"===t.state||"armed_away"===t.state||"disarmed"===t.state||"pending"===t.state||"triggered"===t.state,this.disarmButtonVisible="armed_home"===t.state||"armed_away"===t.state||"pending"===t.state||"triggered"===t.state,this.armHomeButtonVisible="disarmed"===t.state,this.armAwayButtonVisible="disarmed"===t.state),this.async(function(){return e.fire("iron-resize")},500)},callService:function(t,e){var n=e||{};n.entity_id=this.stateObj.entityId,s.callService("alarm_control_panel",t,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:"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="}})},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=r(i),a=n(1),u=r(a),s=n(4),c=r(s);n(18);var l=o["default"].streamGetters,f=o["default"].syncActions,d=o["default"].serviceActions;e["default"]=new u["default"]({is:"more-info-configurator",behaviors:[c["default"]],properties:{stateObj:{type:Object},action:{type:String,value:"display"},isStreaming:{type:Boolean,bindNuclear:l.isStreamingEvents},isConfigurable:{type:Boolean,computed:"computeIsConfigurable(stateObj)"},isConfiguring:{type:Boolean,value:!1},submitCaption:{type:String,computed:"computeSubmitCaption(stateObj)"},fieldInput:{type:Object,value:{}}},computeIsConfigurable:function(t){return"configure"===t.state},computeSubmitCaption:function(t){return t.attributes.submit_caption||"Set configuration"},fieldChanged:function(t){var e=t.target;this.fieldInput[e.id]=e.value},submitClicked:function(){var t=this;this.isConfiguring=!0;var e={configure_id:this.stateObj.attributes.configure_id,fields:this.fieldInput};d.callService("configurator","configure",e).then(function(){t.isConfiguring=!1,t.isStreaming||f.fetchAll()},function(){t.isConfiguring=!1})}})},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),a=n(125),u=r(a);n(107),n(108),n(112),n(105),n(113),n(111),n(109),n(110),n(104),n(114),n(103),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=(0,u["default"])(t);if(e&&(0,u["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)}}})},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),a=["entity_picture","friendly_name","icon","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===a.indexOf(t)}):[]},getAttributeValue:function(t,e){return t.attributes[e]}})},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=r(i),a=n(1),u=r(a),s=n(4),c=r(s);n(19);var l=o["default"].entityGetters,f=o["default"].moreInfoGetters;e["default"]=new u["default"]({is:"more-info-group",behaviors:[c["default"]],properties:{stateObj:{type:Object},states:{type:Array,bindNuclear:[f.currentEntity,l.entityMap,function(t,e){return t?t.attributes.entity_id.map(e.get.bind(e)):[]}]}}})},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){f.callService("light","turn_on",{entity_id:t,rgb_color:[e.r,e.g,e.b]})}Object.defineProperty(e,"__esModule",{value:!0});var o=n(2),a=r(o),u=n(1),s=r(u),c=n(20),l=r(c);n(80);var f=a["default"].serviceActions,d=["brightness","rgb_color","color_temp"];e["default"]=new s["default"]({is:"more-info-light",properties:{stateObj:{type:Object,observer:"stateObjChanged"},brightnessSliderValue:{type:Number,value:0},ctSliderValue:{type:Number,value:0}},stateObjChanged:function(t){var e=this;t&&"on"===t.state&&(this.brightnessSliderValue=t.attributes.brightness,this.ctSliderValue=t.attributes.color_temp),this.async(function(){return e.fire("iron-resize")},500)},computeClassNames:function(t){return(0,l["default"])(t,d)},brightnessSliderChanged:function(t){var e=parseInt(t.target.value,10);isNaN(e)||(0===e?f.callTurnOff(this.stateObj.entityId):f.callService("light","turn_on",{entity_id:this.stateObj.entityId,brightness:e}))},ctSliderChanged:function(t){var e=parseInt(t.target.value,10);isNaN(e)||f.callService("light","turn_on",{entity_id:this.stateObj.entityId,color_temp:e})},colorPicked:function(t){var e=this;return this.skipColorPicked?void(this.colorChanged=!0):(this.color=t.detail.rgb,i(this.stateObj.entityId,this.color),this.colorChanged=!1,this.skipColorPicked=!0,void(this.colorDebounce=setTimeout(function(){e.colorChanged&&i(e.stateObj.entityId,e.color),e.skipColorPicked=!1},500)))}})},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=r(i),a=n(1),u=r(a),s=n(20),c=r(s),l=o["default"].serviceActions,f=["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},supportsVolumeButtons:{type:Boolean,value:!1},hasMediaControl:{type:Boolean,value:!1}},stateObjChanged:function(t){var e=this;if(t){var n=["playing","paused","unknown"];this.isOff="off"===t.state,this.isPlaying="playing"===t.state,this.hasMediaControl=-1!==n.indexOf(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.supportsVolumeButtons=0!==(1024&t.attributes.supported_media_commands)}this.async(function(){return e.fire("iron-resize")},500)},computeClassNames:function(t){return(0,c["default"])(t,f)},computeIsOff:function(t){return"off"===t.state},computeMuteVolumeIcon:function(t){return t?"mdi:volume-off":"mdi:volume-high"},computeHideVolumeButtons:function(t,e){return!e||t},computeShowPlaybackControls:function(t,e){return!t&&e},computePlaybackControlIcon:function(){return this.isPlaying?this.supportsPause?"mdi:pause":"mdi:stop":"mdi:play"},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})},handleVolumeUp:function(){var t=this.$.volumeUp;this.handleVolumeWorker("volume_up",t,!0)},handleVolumeDown:function(){ +var t=this.$.volumeDown;this.handleVolumeWorker("volume_down",t,!0)},handleVolumeWorker:function(t,e,n){var r=this;(n||void 0!==e&&e.pointerDown)&&(this.callService(t),this.async(function(){return r.handleVolumeWorker(t,e,!1)},500))},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,l.callService("media_player",t,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:"more-info-script",properties:{stateObj:{type:Object}}})},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),a=n(2),u=r(a),s=n(41),c=r(s),l=u["default"].util.parseDateTime;e["default"]=new o["default"]({is:"more-info-sun",properties:{stateObj:{type:Object},risingDate:{type:Object,computed:"computeRising(stateObj)"},settingDate:{type:Object,computed:"computeSetting(stateObj)"}},computeRising:function(t){return l(t.attributes.next_rising)},computeSetting:function(t){return l(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(0,c["default"])(this.itemDate(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(2),o=r(i),a=n(1),u=r(a),s=n(20),c=r(s),l=o["default"].serviceActions,f=["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.attributes.temperature,this.awayToggleChecked="on"===t.attributes.away_mode,this.tempMin=t.attributes.min_temp,this.tempMax=t.attributes.max_temp},computeClassNames:function(t){return(0,c["default"])(t,f)},targetTemperatureSliderChanged:function(t){l.callService("thermostat","set_temperature",{entity_id:this.stateObj.entityId,temperature:t.target.value})},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;l.callService("thermostat","set_away_mode",{away_mode:t,entity_id:this.stateObj.entityId}).then(function(){return e.stateObjChanged(e.stateObj)})}})},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-updater",properties:{}})},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(7),n(39),e["default"]=new o["default"]({is:"state-card-configurator",properties:{stateObj:{type:Object}}})},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(7);var a=["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!==a.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:""}})},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=r(i),a=n(1),u=r(a);n(7);var s=o["default"].serviceActions;e["default"]=new u["default"]({is:"state-card-rollershutter",properties:{stateObj:{type:Object}},computeIsFullyOpen:function(t){return 100===t.attributes.current_position},computeIsFullyClosed:function(t){return 0===t.attributes.current_position},onMoveUpTap:function(){s.callService("rollershutter","move_up",{entity_id:this.stateObj.entityId})},onMoveDownTap:function(){s.callService("rollershutter","move_down",{entity_id:this.stateObj.entityId})},onStopTap:function(){s.callService("rollershutter","stop",{entity_id:this.stateObj.entityId})}})},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),a=n(2),u=r(a);n(7);var s=u["default"].serviceActions;e["default"]=new o["default"]({is:"state-card-scene",properties:{stateObj:{type:Object}},activateScene:function(){s.callTurnOn(this.stateObj.entityId)}})},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(7),e["default"]=new o["default"]({is:"state-card-thermostat",properties:{stateObj:{type:Object}},computeTargetTemperature:function(t){return t.attributes.temperature+" "+t.attributes.unit_of_measurement}})},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(7),n(35),e["default"]=new o["default"]({is:"state-card-toggle"})},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(7),e["default"]=new o["default"]({is:"state-card-weblink",properties:{stateObj:{type:Object}},listeners:{tap:"onTap"},onTap:function(t){t.stopPropagation(),window.open(this.stateObj.state,"_blank")}})},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},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){return-1!==u.indexOf(t.domain)?t.domain:(0,a["default"])(t.entityId)?"toggle":"display"}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=i;var o=n(21),a=r(o),u=["configurator","media_player","rollershutter","scene","thermostat","weblink"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){if(!t)return a["default"];if(t.attributes.icon)return t.attributes.icon;var e=t.attributes.unit_of_measurement;return!e||"sensor"!==t.domain||e!==f.UNIT_TEMP_C&&e!==f.UNIT_TEMP_F?(0,s["default"])(t.domain,t.state):"mdi:thermometer"}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=i;var o=n(40),a=r(o),u=n(22),s=r(u),c=n(2),l=r(c),f=l["default"].util.temperatureUnits},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","alarm_control_panel"]},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(195),i=n(15),o=function(t,e,n){var o=arguments.length<=3||void 0===arguments[3]?null:arguments[3],a=t.evaluate(i.getters.authInfo),u=a.host+"/api/"+n;return new r.Promise(function(t,n){var r=new XMLHttpRequest;r.open(e,u,!0),r.setRequestHeader("X-HA-access",a.authToken),r.onload=function(){var e=void 0;try{e="application/json"===r.getResponseHeader("content-type")?JSON.parse(r.responseText):r.responseText}catch(i){e=r.responseText}r.status>199&&r.status<300?t(e):n(e)},r.onerror=function(){return n({})},o?r.send(JSON.stringify(o)):r.send()})};e["default"]=o},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(c.getters.isSupported):r,o=n.rememberAuth,a=void 0===o?!1:o,s=n.host,d=void 0===s?"":s;t.dispatch(u["default"].VALIDATING_AUTH_TOKEN,{authToken:e,host:d}),l.actions.fetchAll(t).then(function(){t.dispatch(u["default"].VALID_AUTH_TOKEN,{authToken:e,host:d,rememberAuth:a}),i?c.actions.start(t,{syncOnInitialConnect:!1}):l.actions.start(t,{skipInitialSync:!0})},function(){var e=arguments.length<=0||void 0===arguments[0]?{}:arguments[0],n=e.message,r=void 0===n?f:n;t.dispatch(u["default"].INVALID_AUTH_TOKEN,{errorMessage:r})})}function o(t){(0,s.callApi)(t,"POST","log_out"),t.dispatch(u["default"].LOG_OUT,{})}Object.defineProperty(e,"__esModule",{value:!0}),e.validate=i,e.logOut=o;var a=n(14),u=r(a),s=n(5),c=n(29),l=n(31),f="Unexpected result from API"},function(t,e){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var n=e.isValidating=["authAttempt","isValidating"],r=(e.isInvalidAttempt=["authAttempt","isInvalid"],e.attemptErrorMessage=["authAttempt","errorMessage"],e.rememberAuth=["rememberAuth"],e.attemptAuthInfo=[["authAttempt","authToken"],["authAttempt","host"],function(t,e){return{authToken:t,host:e}}]),i=e.currentAuthToken=["authCurrent","authToken"],o=e.currentAuthInfo=[i,["authCurrent","host"],function(t,e){return{authToken:t,host:e}}];e.authToken=[n,["authAttempt","authToken"],["authCurrent","authToken"],function(t,e,n){return t?e:n}],e.authInfo=[n,r,o,function(t,e,n){return t?e:n}]},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){var n=e.authToken,r=e.host;return(0,s.toImmutable)({authToken:n,host:r,isValidating:!0,isInvalid:!1,errorMessage:""})}function a(t,e){return i(e),f.getInitialState()}function u(t,e){var n=e.errorMessage;return t.withMutations(function(t){return t.set("isValidating",!1).set("isInvalid",!0).set("errorMessage",n)})}Object.defineProperty(e,"__esModule",{value:!0});var s=n(3),c=n(14),l=r(c),f=new s.Store({getInitialState:function(){return(0,s.toImmutable)({isValidating:!1,authToken:!1,host:null,isInvalid:!1,errorMessage:""})},initialize:function(){this.on(l["default"].VALIDATING_AUTH_TOKEN,o),this.on(l["default"].VALID_AUTH_TOKEN,a),this.on(l["default"].INVALID_AUTH_TOKEN,u)}});e["default"]=f},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){var n=e.authToken,r=e.host;return(0,a.toImmutable)({authToken:n,host:r})}function o(){return c.getInitialState()}Object.defineProperty(e,"__esModule",{value:!0});var a=n(3),u=n(14),s=r(u),c=new a.Store({getInitialState:function(){return(0,a.toImmutable)({authToken:null,host:""})},initialize:function(){this.on(s["default"].VALID_AUTH_TOKEN,i),this.on(s["default"].LOG_OUT,o)}});e["default"]=c},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){var n=e.rememberAuth;return n}Object.defineProperty(e,"__esModule",{value:!0});var o=n(3),a=n(14),u=r(a),s=new o.Store({getInitialState:function(){return!0},initialize:function(){this.on(u["default"].VALID_AUTH_TOKEN,i)}});e["default"]=s},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){t.dispatch(c["default"].SERVER_CONFIG_LOADED,e)}function o(t){(0,u.callApi)(t,"GET","config").then(function(e){return i(t,e)})}function a(t,e){t.dispatch(c["default"].COMPONENT_LOADED,{component:e})}Object.defineProperty(e,"__esModule",{value:!0}),e.configLoaded=i,e.fetchAll=o,e.componentLoaded=a;var u=n(5),s=n(23),c=r(s)},function(t,e){"use strict";function n(t){return[["serverComponent"],function(e){return e.contains(t)}]}Object.defineProperty(e,"__esModule",{value:!0}),e.isComponentLoaded=n,e.locationGPS=[["serverConfig","latitude"],["serverConfig","longitude"],function(t,e){return{latitude:t,longitude:e}}],e.locationName=["serverConfig","location_name"],e.serverVersion=["serverConfig","serverVersion"]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){var n=e.component;return t.push(n)}function o(t,e){var n=e.components;return(0,u.toImmutable)(n)}function a(){return l.getInitialState()}Object.defineProperty(e,"__esModule",{value:!0});var u=n(3),s=n(23),c=r(s),l=new u.Store({getInitialState:function(){return(0,u.toImmutable)([])},initialize:function(){this.on(c["default"].COMPONENT_LOADED,i),this.on(c["default"].SERVER_CONFIG_LOADED,o),this.on(c["default"].LOG_OUT,a)}});e["default"]=l},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){var n=e.latitude,r=e.longitude,i=e.location_name,o=e.temperature_unit,u=e.time_zone,s=e.version;return(0,a.toImmutable)({latitude:n,longitude:r,location_name:i,temperature_unit:o,time_zone:u,serverVersion:s})}function o(){return c.getInitialState()}Object.defineProperty(e,"__esModule",{value:!0});var a=n(3),u=n(23),s=r(u),c=new a.Store({getInitialState:function(){return(0,a.toImmutable)({latitude:null,longitude:null,location_name:"Home",temperature_unit:"°C",time_zone:"UTC",serverVersion:"unknown"})},initialize:function(){this.on(s["default"].SERVER_CONFIG_LOADED,i),this.on(s["default"].LOG_OUT,o)}});e["default"]=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,e){t.dispatch(f["default"].ENTITY_HISTORY_DATE_SELECTED,{date:e})}function a(t){var e=arguments.length<=1||void 0===arguments[1]?null:arguments[1];t.dispatch(f["default"].RECENT_ENTITY_HISTORY_FETCH_START,{});var n="history/period";return null!==e&&(n+="?filter_entity_id="+e),(0,c.callApi)(t,"GET",n).then(function(e){return t.dispatch(f["default"].RECENT_ENTITY_HISTORY_FETCH_SUCCESS,{stateHistory:e})},function(){return t.dispatch(f["default"].RECENT_ENTITY_HISTORY_FETCH_ERROR,{})})}function u(t,e){return t.dispatch(f["default"].ENTITY_HISTORY_FETCH_START,{date:e}),(0,c.callApi)(t,"GET","history/period/"+e).then(function(n){return t.dispatch(f["default"].ENTITY_HISTORY_FETCH_SUCCESS,{date:e,stateHistory:n})},function(){return t.dispatch(f["default"].ENTITY_HISTORY_FETCH_ERROR,{})})}function s(t){var e=t.evaluate(h.currentDate);return u(t,e)}Object.defineProperty(e,"__esModule",{value:!0}),e.changeCurrentDate=o,e.fetchRecent=a,e.fetchDate=u,e.fetchSelectedDate=s;var c=n(5),l=n(11),f=i(l),d=n(44),h=r(d)},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){var n=e.date;return(0,s["default"])(n)}function o(){return f.getInitialState()}Object.defineProperty(e,"__esModule",{value:!0});var a=n(3),u=n(32),s=r(u),c=n(11),l=r(c),f=new a.Store({getInitialState:function(){var t=new Date;return t.setDate(t.getDate()-1),(0,s["default"])(t)},initialize:function(){this.on(l["default"].ENTITY_HISTORY_DATE_SELECTED,i),this.on(l["default"].LOG_OUT,o)}});e["default"]=f},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){var n=e.date,r=e.stateHistory;return 0===r.length?t.set(n,(0,a.toImmutable)({})):t.withMutations(function(t){r.forEach(function(e){return t.setIn([n,e[0].entity_id],(0,a.toImmutable)(e.map(l["default"].fromJSON)))})})}function o(){return f.getInitialState()}Object.defineProperty(e,"__esModule",{value:!0});var a=n(3),u=n(11),s=r(u),c=n(16),l=r(c),f=new a.Store({getInitialState:function(){return(0,a.toImmutable)({})},initialize:function(){this.on(s["default"].ENTITY_HISTORY_FETCH_SUCCESS,i),this.on(s["default"].LOG_OUT,o)}});e["default"]=f},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(3),o=n(11),a=r(o),u=new i.Store({getInitialState:function(){return!1},initialize:function(){this.on(a["default"].ENTITY_HISTORY_FETCH_START,function(){return!0}),this.on(a["default"].ENTITY_HISTORY_FETCH_SUCCESS,function(){return!1}),this.on(a["default"].ENTITY_HISTORY_FETCH_ERROR,function(){return!1}),this.on(a["default"].RECENT_ENTITY_HISTORY_FETCH_START,function(){return!0}),this.on(a["default"].RECENT_ENTITY_HISTORY_FETCH_SUCCESS,function(){return!1}),this.on(a["default"].RECENT_ENTITY_HISTORY_FETCH_ERROR,function(){return!1}),this.on(a["default"].LOG_OUT,function(){return!1})}});e["default"]=u},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){var n=e.stateHistory;return t.withMutations(function(t){n.forEach(function(e){return t.set(e[0].entity_id,(0,a.toImmutable)(e.map(l["default"].fromJSON)))})})}function o(){return f.getInitialState()}Object.defineProperty(e,"__esModule",{value:!0});var a=n(3),u=n(11),s=r(u),c=n(16),l=r(c),f=new a.Store({getInitialState:function(){return(0,a.toImmutable)({})},initialize:function(){this.on(s["default"].RECENT_ENTITY_HISTORY_FETCH_SUCCESS,i),this.on(s["default"].LOG_OUT,o)}});e["default"]=f},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){var n=e.stateHistory,r=(new Date).getTime();return t.withMutations(function(t){n.forEach(function(e){return t.set(e[0].entity_id,r)}),history.length>1&&t.set(c,r)})}function o(){return l.getInitialState()}Object.defineProperty(e,"__esModule",{value:!0});var a=n(3),u=n(11),s=r(u),c="ALL_ENTRY_FETCH",l=new a.Store({getInitialState:function(){return(0,a.toImmutable)({})},initialize:function(){this.on(s["default"].RECENT_ENTITY_HISTORY_FETCH_SUCCESS,i),this.on(s["default"].LOG_OUT,o)}});e["default"]=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(10),o=n(16),a=r(o),u=(0,i.createApiActions)(a["default"]);e["default"]=u},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0}),e.visibleEntityMap=e.byId=e.entityMap=e.hasData=void 0;var i=n(10),o=n(16),a=r(o),u=(e.hasData=(0,i.createHasDataGetter)(a["default"]),e.entityMap=(0,i.createEntityMapGetter)(a["default"]));e.byId=(0,i.createByIdGetter)(a["default"]),e.visibleEntityMap=[u,function(t){return t.filter(function(t){return!t.attributes.hidden})}]},function(t,e,n){"use strict";function r(t){return(0,i.callApi)(t,"GET","error_log")}Object.defineProperty(e,"__esModule",{value:!0}),e.fetchErrorLog=r;var i=n(5)},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}Object.defineProperty(e,"__esModule",{value:!0}),e.actions=void 0;var i=n(144),o=r(i);e.actions=o},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=n(10),a=n(27),u=n(46),s=r(u),c=(0,o.createApiActions)(s["default"]);c.fireEvent=function(t,e){var n=arguments.length<=2||void 0===arguments[2]?{}:arguments[2];return(0,i.callApi)(t,"POST","events/"+e,n).then(function(){a.actions.createNotification(t,"Event "+e+" successful fired!")})},e["default"]=c},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0}),e.byId=e.entityMap=e.hasData=void 0;var i=n(10),o=n(46),a=r(o);e.hasData=(0,i.createHasDataGetter)(a["default"]),e.entityMap=(0,i.createEntityMapGetter)(a["default"]),e.byId=(0,i.createByIdGetter)(a["default"])},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){t.dispatch(s["default"].LOGBOOK_DATE_SELECTED,{date:e})}function o(t,e){t.dispatch(s["default"].LOGBOOK_ENTRIES_FETCH_START,{date:e}),(0,a.callApi)(t,"GET","logbook/"+e).then(function(n){return t.dispatch(s["default"].LOGBOOK_ENTRIES_FETCH_SUCCESS,{date:e,entries:n})},function(){return t.dispatch(s["default"].LOGBOOK_ENTRIES_FETCH_ERROR,{})})}Object.defineProperty(e,"__esModule",{value:!0}),e.changeCurrentDate=i,e.fetchDate=o;var a=n(5),u=n(12),s=r(u)},function(t,e,n){"use strict";function r(t){return!t||(new Date).getTime()-t>o}Object.defineProperty(e,"__esModule",{value:!0}),e.isLoadingEntries=e.currentEntries=e.isCurrentStale=e.currentDate=void 0;var i=n(3),o=6e4,a=e.currentDate=["currentLogbookDate"];e.isCurrentStale=[a,["logbookEntriesUpdated"],function(t,e){return r(e.get(t))}],e.currentEntries=[a,["logbookEntries"],function(t,e){return e.get(t)||(0,i.toImmutable)([])}],e.isLoadingEntries=["isLoadingLogbookEntries"]},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:u["default"],isLoadingLogbookEntries:c["default"],logbookEntries:f["default"],logbookEntriesUpdated:h["default"]})}Object.defineProperty(e,"__esModule",{value:!0}),e.getters=e.actions=void 0,e.register=o;var a=n(152),u=i(a),s=n(153),c=i(s),l=n(154),f=i(l),d=n(155),h=i(d),p=n(148),_=r(p),v=n(149),y=r(v);e.actions=_,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(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!=typeof e&&"function"!=typeof e?t:e}function a(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)}var u=function(){function t(t,e){for(var n=0;n1?r[1]:null,t.batch(function(){(0,h.navigate)(t,e),n&&f.actions.selectView(t,n)})}history.replaceState(i(e,n),_,o(e,n))}function u(t,e){var n=e.state,i=n.pane,o=n.view;t.evaluate(l.getters.hasCurrentEntityId)?(r(t).ignoreNextDeselectEntity=!0,l.actions.deselectEntity(t)):(i!==t.evaluate(d.activePane)||o!==t.evaluate(f.getters.currentView))&&t.batch(function(){(0,h.navigate)(t,i),void 0!==o&&f.actions.selectView(t,o)})}function s(t){if(p){a(t);var e={ignoreNextDeselectEntity:!1,popstateChangeListener:u.bind(null,t),unwatchNavigationObserver:t.observe(d.activePane,function(t){t!==history.state.pane&&history.pushState(i(t,history.state.view),_,o(t,history.state.view))}),unwatchViewObserver:t.observe(f.getters.currentView,function(t){t!==history.state.view&&history.pushState(i(history.state.pane,t),_,o(history.state.pane,t))}),unwatchMoreInfoObserver:t.observe(l.getters.hasCurrentEntityId,function(t){t?history.pushState(history.state,_,window.location.pathname):e.ignoreNextDeselectEntity?e.ignoreNextDeselectEntity=!1:history.back()})};v[t.hassId]=e,window.addEventListener("popstate",e.popstateChangeListener)}}function c(t){if(p){var e=r(t);e&&(e.unwatchNavigationObserver(),e.unwatchViewObserver(),e.unwatchMoreInfoObserver(),window.removeEventListener("popstate",e.popstateChangeListener),v[t.hassId]=!1)}}Object.defineProperty(e,"__esModule",{value:!0}),e.startSync=s,e.stopSync=c;var l=n(48),f=n(58),d=n(50),h=n(49),p=history.pushState&&!0,_="Home Assistant",v={}},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){t.dispatch(a["default"].NOTIFICATION_CREATED,{message:e})}Object.defineProperty(e,"__esModule",{value:!0}),e.createNotification=i;var o=n(52),a=r(o)},function(t,e){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var n=e.notificationMap=["notifications"];e.lastNotificationMessage=[n,function(t){return t.last()}]},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){var n=e.message;return t.set(t.size,n)}function o(){return c.getInitialState()}Object.defineProperty(e,"__esModule",{value:!0});var a=n(3),u=n(52),s=r(u),c=new a.Store({getInitialState:function(){return new a.Immutable.OrderedMap},initialize:function(){this.on(s["default"].NOTIFICATION_CREATED,i),this.on(s["default"].LOG_OUT,o)}});e["default"]=c},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0}),e.localStoragePreferences=void 0;var i=n(166),o=r(i);e.localStoragePreferences=o["default"]},function(t,e,n){"use strict";function r(){if(!("localStorage"in window))return{};var t=window.localStorage,e="___test";try{return t.setItem(e,e),t.removeItem(e),t}catch(n){return{}}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(15),o=n(29),a=n(51),u=r(),s={authToken:{getter:[i.getters.currentAuthToken,i.getters.rememberAuth,function(t,e){return e?t:null}],defaultValue:null},useStreaming:{getter:o.getters.useStreaming,defaultValue:!0},showSidebar:{getter:a.getters.showSidebar,defaultValue:!1}},c={};Object.keys(s).forEach(function(t){t in u||(u[t]=s[t].defaultValue),Object.defineProperty(c,t,{get:function(){try{return JSON.parse(u[t])}catch(e){return s[t].defaultValue}}})}),c.startSync=function(t){Object.keys(s).forEach(function(e){var n=s[e].getter,r=function(t){u[e]=JSON.stringify(t)};t.observe(n,r),r(t.evaluate(n))})},e["default"]=c},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){var e={};return e.incrementData=function(e,n){var r=arguments.length<=2||void 0===arguments[2]?{}:arguments[2];o(e,t,r,n)},e.replaceData=function(e,n){var r=arguments.length<=2||void 0===arguments[2]?{}:arguments[2];o(e,t,f({},r,{replace:!0}),n)},t.fetch&&(e.fetch=function(e){var n=arguments.length<=1||void 0===arguments[1]?{}:arguments[1];return e.dispatch(h["default"].API_FETCH_START,{model:t,params:n,method:"fetch"}),t.fetch(e,n).then(o.bind(null,e,t,n),a.bind(null,e,t,n))}),e.fetchAll=function(e){var n=arguments.length<=1||void 0===arguments[1]?{}:arguments[1];return e.dispatch(h["default"].API_FETCH_START,{model:t,params:n,method:"fetchAll"}),t.fetchAll(e,n).then(o.bind(null,e,t,f({},n,{replace:!0})),a.bind(null,e,t,n))},t.save&&(e.save=function(e){ +var n=arguments.length<=1||void 0===arguments[1]?{}:arguments[1];return e.dispatch(h["default"].API_SAVE_START,{params:n}),t.save(e,n).then(u.bind(null,e,t,n),s.bind(null,e,t,n))}),t["delete"]&&(e["delete"]=function(e){var n=arguments.length<=1||void 0===arguments[1]?{}:arguments[1];return e.dispatch(h["default"].API_DELETE_START,{params:n}),t["delete"](e,n).then(c.bind(null,e,t,n),l.bind(null,e,t,n))}),e}function o(t,e,n,r){return t.dispatch(h["default"].API_FETCH_SUCCESS,{model:e,params:n,result:r}),r}function a(t,e,n,r){return t.dispatch(h["default"].API_FETCH_FAIL,{model:e,params:n,reason:r}),Promise.reject(r)}function u(t,e,n,r){return t.dispatch(h["default"].API_SAVE_SUCCESS,{model:e,params:n,result:r}),r}function s(t,e,n,r){return t.dispatch(h["default"].API_SAVE_FAIL,{model:e,params:n,reason:r}),Promise.reject(r)}function c(t,e,n,r){return t.dispatch(h["default"].API_DELETE_SUCCESS,{model:e,params:n,result:r}),r}function l(t,e,n,r){return t.dispatch(h["default"].API_DELETE_FAIL,{model:e,params:n,reason:r}),Promise.reject(r)}var f=Object.assign||function(t){for(var e=1;et;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(211);return W=t.runOnLoop||t.runOnContext,d()}catch(e){return _()}}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 S(t){try{return t.then}catch(e){return ut.error=e,ut}}function w(t,e,n,r){try{t.call(e,n,r)}catch(i){return i}}function O(t,e,n){Z(function(t){var r=!1,i=w(n,e,function(n){r||(r=!0,e!==n?E(t,n):D(t,n))},function(e){r||(r=!0,C(t,e))},"Settle: "+(t._label||" unknown promise"));!r&&i&&(r=!0,C(t,i))},t)}function M(t,e){e._state===ot?D(t,e._result):e._state===at?C(t,e._result):j(e,void 0,function(e){E(t,e)},function(e){C(t,e)})}function I(t,e){if(e.constructor===t.constructor)M(t,e);else{var n=S(e);n===ut?C(t,ut.error):void 0===n?D(t,e):u(n)?O(t,e,n):D(t,e)}}function E(t,e){t===e?C(t,g()):a(e)?I(t,e):D(t,e)}function T(t){t._onerror&&t._onerror(t._result),P(t)}function D(t,e){t._state===it&&(t._result=e,t._state=ot,0!==t._subscribers.length&&Z(P,t))}function C(t,e){t._state===it&&(t._state=at,t._result=e,Z(T,t))}function j(t,e,n,r){var i=t._subscribers,o=i.length;t._onerror=null,i[o]=e,i[o+ot]=n,i[o+at]=r,0===o&&t._state&&Z(P,t)}function P(t){var e=t._subscribers,n=t._state;if(0!==e.length){for(var r,i,o=t._result,a=0;aa;a++)j(r.resolve(t[a]),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 E(n,t),n}function z(t){var e=this,n=new e(m);return C(n,t),n}function U(){throw new TypeError("You must pass a resolver function as the first argument to the promise constructor")}function V(){throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.")}function G(t){this._id=pt++,this._state=void 0,this._result=void 0,this._subscribers=[],m!==t&&(u(t)||U(),this instanceof G||V(),N(this,t))}function F(){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=_t)}var B;B=Array.isArray?Array.isArray:function(t){return"[object Array]"===Object.prototype.toString.call(t)};var W,q,K,J=B,$=0,Z=({}.toString,function(t,e){rt[$]=t,rt[$+1]=e,$+=2,2===$&&(q?q(v):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?h():nt?p():void 0===X?y():_();var it=void 0,ot=1,at=2,ut=new A,st=new A;R.prototype._validateInput=function(t){return J(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 ct=R;R.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)},R.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)},R.prototype._settledAt=function(t,e,n){var r=this,i=r.promise;i._state===it&&(r._remaining--,t===at?C(i,n):r._result[e]=n),0===r._remaining&&D(i,r._result)},R.prototype._willSettleAt=function(t,e){var n=this;j(t,void 0,function(t){n._settledAt(ot,e,t)},function(t){n._settledAt(at,e,t)})};var lt=x,ft=H,dt=Y,ht=z,pt=0,_t=G;G.all=lt,G.race=ft,G.resolve=dt,G.reject=ht,G._setScheduler=c,G._setAsap=l,G._asap=Z,G.prototype={constructor:G,then:function(t,e){var n=this,r=n._state;if(r===ot&&!t||r===at&&!e)return this;var i=new this.constructor(m),o=n._result;if(r){var a=arguments[r-1];Z(function(){L(r,i,a,o)})}else j(n,i,t,e);return i},"catch":function(t){return this.then(null,t)}};var vt=F,yt={Promise:_t,polyfill:vt};n(209).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),vt()}).call(this)}).call(e,n(210),function(){return this}(),n(67)(t))},function(t,e,n){var r=n(62),i=r(Date,"now"),o=i||function(){return(new Date).getTime()};t.exports=o},function(t,e){function n(t){return"number"==typeof t&&t>-1&&t%1==0&&r>=t}var r=9007199254740991;t.exports=n},function(t,e,n){var r=n(62),i=n(197),o=n(63),a="[object Array]",u=Object.prototype,s=u.toString,c=r(Array,"isArray"),l=c||function(t){return o(t)&&i(t.length)&&s.call(t)==a};t.exports=l},function(t,e,n){function r(t){return null==t?!1:i(t)?l.test(s.call(t)):o(t)&&a.test(t)}var i=n(64),o=n(63),a=/^\[object .+?Constructor\]$/,u=Object.prototype,s=Function.prototype.toString,c=u.hasOwnProperty,l=RegExp("^"+s.call(c).replace(/[\\^$.*+?()[\]{}|]/g,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$");t.exports=r},function(t,e){function n(t){return function(e){return null==e?void 0:e[t]}}t.exports=n},function(t,e,n){var r=n(200),i=r("length");t.exports=i},function(t,e,n){function r(t){return null!=t&&o(i(t))}var i=n(201),o=n(205);t.exports=r},function(t,e){function n(t,e){return t="number"==typeof t||r.test(t)?+t:-1,e=null==e?i:e,t>-1&&t%1==0&&e>t}var r=/^\d+$/,i=9007199254740991;t.exports=n},function(t,e,n){function r(t,e,n){if(!a(n))return!1;var r=typeof e;if("number"==r?i(n)&&o(e,n.length):"string"==r&&e in n){var u=n[e];return t===t?t===u:u!==u}return!1}var i=n(202),o=n(203),a=n(206);t.exports=r},function(t,e){function n(t){return"number"==typeof t&&t>-1&&t%1==0&&r>=t}var r=9007199254740991;t.exports=n},function(t,e){function n(t){var e=typeof t;return!!t&&("object"==e||"function"==e)}t.exports=n},function(t,e,n){function r(t,e,n){n&&i(t,e,n)&&(e=n=void 0),t=+t||0,n=null==n?1:+n||0,null==e?(e=t,t=0):e=+e||0;for(var r=-1,u=a(o((e-t)/(n||1)),0),s=Array(u);++r1)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 472f485a7e1..4a9cb961758 160000 --- a/homeassistant/components/frontend/www_static/home-assistant-polymer +++ b/homeassistant/components/frontend/www_static/home-assistant-polymer @@ -1 +1 @@ -Subproject commit 472f485a7e17d4ec4fc7cc6c17bcd6c41830d6fa +Subproject commit 4a9cb96175868292c42cdb12f2a1bec71f5ff320 From 96710ad410cc0b822347f8f0cbabc64f518ec359 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 31 Jan 2016 12:40:34 -0800 Subject: [PATCH 041/232] Add input_select component --- homeassistant/components/input_select.py | 142 +++++++++++++++++++++++ tests/components/test_input_select.py | 132 +++++++++++++++++++++ 2 files changed, 274 insertions(+) create mode 100644 homeassistant/components/input_select.py create mode 100644 tests/components/test_input_select.py diff --git a/homeassistant/components/input_select.py b/homeassistant/components/input_select.py new file mode 100644 index 00000000000..0019bca5a6a --- /dev/null +++ b/homeassistant/components/input_select.py @@ -0,0 +1,142 @@ +""" +homeassistant.components.input_select +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Component to offer a way to select an option from a list. + +For more details about this component, please refer to the documentation +at https://home-assistant.io/components/input_select/ +""" +import logging + +from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.helpers.entity_component import EntityComponent +from homeassistant.helpers.entity import Entity +from homeassistant.util import slugify + +DOMAIN = 'input_select' + +ENTITY_ID_FORMAT = DOMAIN + '.{}' + +_LOGGER = logging.getLogger(__name__) + +CONF_NAME = 'name' +CONF_INITIAL = 'initial' +CONF_ICON = 'icon' +CONF_OPTIONS = 'options' + +ATTR_OPTION = 'option' +ATTR_OPTIONS = 'options' + +SERVICE_SELECT_OPTION = 'select_option' + + +def select_option(hass, entity_id, option): + """Set input_boolean to False.""" + hass.services.call(DOMAIN, SERVICE_SELECT_OPTION, { + ATTR_ENTITY_ID: entity_id, + ATTR_OPTION: option, + }) + + +def setup(hass, config): + """Set up input booleans.""" + if not isinstance(config.get(DOMAIN), dict): + _LOGGER.error('Expected %s config to be a dictionary', DOMAIN) + return False + + component = EntityComponent(_LOGGER, DOMAIN, hass) + + entities = [] + + for object_id, cfg in config[DOMAIN].items(): + if object_id != slugify(object_id): + _LOGGER.warning("Found invalid key for boolean input: %s. " + "Use %s instead", object_id, slugify(object_id)) + continue + if not cfg: + _LOGGER.warning("No configuration specified for %s", object_id) + continue + + name = cfg.get(CONF_NAME) + options = cfg.get(CONF_OPTIONS) + + if not isinstance(options, list) or len(options) == 0: + _LOGGER.warning('Key %s should be a list of options', CONF_OPTIONS) + continue + + options = [str(val) for val in options] + + state = cfg.get(CONF_INITIAL) + + if state not in options: + state = options[0] + + icon = cfg.get(CONF_ICON) + + entities.append(InputSelect(object_id, name, state, options, icon)) + + if not entities: + return False + + def select_option_service(call): + """Handle a calls to the input boolean services.""" + target_inputs = component.extract_from_service(call) + + for input_select in target_inputs: + input_select.select_option(call.data.get(ATTR_OPTION)) + + hass.services.register(DOMAIN, SERVICE_SELECT_OPTION, + select_option_service) + + component.add_entities(entities) + + return True + + +class InputSelect(Entity): + """Represent a select input within Home Assistant.""" + + # pylint: disable=too-many-arguments + def __init__(self, object_id, name, state, options, icon): + """Initialize a boolean input.""" + self.entity_id = ENTITY_ID_FORMAT.format(object_id) + self._name = name + self._current_option = state + self._options = options + self._icon = icon + + @property + def should_poll(self): + """If entitiy should be polled.""" + return False + + @property + def name(self): + """Name of the boolean input.""" + return self._name + + @property + def icon(self): + """Icon to be used for this entity.""" + return self._icon + + @property + def state(self): + """State of the component.""" + return self._current_option + + @property + def state_attributes(self): + """State attributes.""" + return { + ATTR_OPTIONS: self._options, + } + + def select_option(self, option): + """Select new option.""" + if option not in self._options: + _LOGGER.warning('Invalid option: %s (possible options: %s)', + option, ', '.join(self._options)) + return + self._current_option = option + self.update_ha_state() diff --git a/tests/components/test_input_select.py b/tests/components/test_input_select.py new file mode 100644 index 00000000000..f7aa87d2a0d --- /dev/null +++ b/tests/components/test_input_select.py @@ -0,0 +1,132 @@ +""" +tests.components.test_input_select +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Tests input_select component. +""" +# pylint: disable=too-many-public-methods,protected-access +import unittest + +from homeassistant.components import input_select +from homeassistant.const import ( + ATTR_ICON, ATTR_FRIENDLY_NAME) + +from tests.common import get_test_home_assistant + + +class TestInputSelect(unittest.TestCase): + """ Test the input select module. """ + + def setUp(self): # pylint: disable=invalid-name + self.hass = get_test_home_assistant() + + def tearDown(self): # pylint: disable=invalid-name + """ Stop down stuff we started. """ + self.hass.stop() + + def test_config(self): + """Test config.""" + self.assertFalse(input_select.setup(self.hass, { + 'input_select': None + })) + + self.assertFalse(input_select.setup(self.hass, { + 'input_select': { + } + })) + + self.assertFalse(input_select.setup(self.hass, { + 'input_select': { + 'name with space': None + } + })) + + self.assertFalse(input_select.setup(self.hass, { + 'input_select': { + 'hello': { + 'options': None + } + } + })) + + self.assertFalse(input_select.setup(self.hass, { + 'input_select': { + 'hello': None + } + })) + + def test_select_option(self): + """ Test select_option methods. """ + self.assertTrue(input_select.setup(self.hass, { + 'input_select': { + 'test_1': { + 'options': [ + 'some option', + 'another option', + ], + }, + } + })) + entity_id = 'input_select.test_1' + + state = self.hass.states.get(entity_id) + self.assertEqual('some option', state.state) + + input_select.select_option(self.hass, entity_id, 'another option') + self.hass.pool.block_till_done() + + state = self.hass.states.get(entity_id) + self.assertEqual('another option', state.state) + + input_select.select_option(self.hass, entity_id, 'non existing option') + self.hass.pool.block_till_done() + + state = self.hass.states.get(entity_id) + self.assertEqual('another option', state.state) + + def test_config_options(self): + count_start = len(self.hass.states.entity_ids()) + + test_2_options = [ + 'Good Option', + 'Better Option', + 'Best Option', + ] + + self.assertTrue(input_select.setup(self.hass, { + 'input_select': { + 'test_1': { + 'options': [ + 1, + 2, + ], + }, + 'test_2': { + 'name': 'Hello World', + 'icon': 'work', + 'options': test_2_options, + 'initial': 'Better Option', + }, + }, + })) + + self.assertEqual(count_start + 2, len(self.hass.states.entity_ids())) + + state_1 = self.hass.states.get('input_select.test_1') + state_2 = self.hass.states.get('input_select.test_2') + + self.assertIsNotNone(state_1) + self.assertIsNotNone(state_2) + + self.assertEqual('1', state_1.state) + self.assertEqual(['1', '2'], + state_1.attributes.get(input_select.ATTR_OPTIONS)) + self.assertNotIn(ATTR_ICON, state_1.attributes) + self.assertNotIn(ATTR_FRIENDLY_NAME, state_1.attributes) + + self.assertEqual('Better Option', state_2.state) + self.assertEqual(test_2_options, + state_2.attributes.get(input_select.ATTR_OPTIONS)) + self.assertEqual('Hello World', + state_2.attributes.get(ATTR_FRIENDLY_NAME)) + self.assertEqual('work', state_2.attributes.get(ATTR_ICON)) From 1c10f218debe64f7ece4dbf855c66e0829c76174 Mon Sep 17 00:00:00 2001 From: Stefan Jonasson Date: Sun, 31 Jan 2016 22:17:00 +0100 Subject: [PATCH 042/232] Fixed duplicate import statements and made use of the config_helper --- .../components/media_player/samsungtv.py | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/media_player/samsungtv.py b/homeassistant/components/media_player/samsungtv.py index 74ba5b5e526..1d2703b0e2e 100644 --- a/homeassistant/components/media_player/samsungtv.py +++ b/homeassistant/components/media_player/samsungtv.py @@ -18,6 +18,8 @@ from homeassistant.const import ( CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON, STATE_UNKNOWN) +from homeassistant.helpers import validate_config + CONF_PORT = "port" CONF_TIMEOUT = "timeout" @@ -34,13 +36,13 @@ SUPPORT_SAMSUNGTV = SUPPORT_PAUSE | SUPPORT_VOLUME_STEP | \ def setup_platform(hass, config, add_devices, discovery_info=None): """ Sets up the Samsung TV platform. """ - if not config.get(CONF_HOST): - _LOGGER.error( - "Missing required configuration items in %s: %s", - DOMAIN, - CONF_HOST) + # Validate that all required config options are given + if not validate_config({DOMAIN: config}, {DOMAIN: [CONF_HOST]}, _LOGGER): return False - name = config.get("name", 'Samsung TV Remote') + + # Default the entity_name to 'Samsung TV Remote' + name = config.get(CONF_NAME, 'Samsung TV Remote') + # Generate a config for the Samsung lib remote_config = { "name": "HomeAssistant", @@ -60,7 +62,9 @@ class SamsungTVDevice(MediaPlayerDevice): # pylint: disable=too-many-public-methods def __init__(self, name, config): - + from samsungctl import Remote + # Save a reference to the imported class + self._remote_class = Remote self._name = name # Assume that the TV is not muted self._muted = False @@ -76,28 +80,26 @@ class SamsungTVDevice(MediaPlayerDevice): def get_remote(self): """ Creates or Returns a remote control instance """ - from samsungctl import Remote if self._remote is None: # We need to create a new instance to reconnect. - self._remote = Remote(self._config) + self._remote = self._remote_class(self._config) return self._remote def send_key(self, key): """ Sends a key to the tv and handles exceptions """ - from samsungctl import Remote try: self.get_remote().control(key) self._state = STATE_ON - except (Remote.UnhandledResponse, Remote.AccessDenied, - BrokenPipeError): + except (self._remote_class.UnhandledResponse, + self._remote_class.AccessDenied, BrokenPipeError): # We got a response so it's on. # BrokenPipe can occur when the commands is sent to fast self._state = STATE_ON self._remote = None return False - except (Remote.ConnectionClosed, socket.timeout, + except (self._remote_class.ConnectionClosed, socket.timeout, TimeoutError, OSError): self._state = STATE_OFF self._remote = None From 41919e733999aabcc9239909a67dd7ce688794db Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 31 Jan 2016 13:39:50 -0800 Subject: [PATCH 043/232] Update frontend for input_select --- homeassistant/components/frontend/version.py | 2 +- .../frontend/www_static/frontend.html | 222 +++++++++++++----- .../www_static/home-assistant-polymer | 2 +- 3 files changed, 167 insertions(+), 59 deletions(-) diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py index 9c7c3f49eb5..5e7bc01b149 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 = "a6e64ec7d87014e4260c552a2560cb9f" +VERSION = "b5daaa4815050f90f6c996a429bfeae1" diff --git a/homeassistant/components/frontend/www_static/frontend.html b/homeassistant/components/frontend/www_static/frontend.html index 344ed82aae3..a55f96ac931 100644 --- a/homeassistant/components/frontend/www_static/frontend.html +++ b/homeassistant/components/frontend/www_static/frontend.html @@ -1437,7 +1437,8 @@ var n=this._rootDataHost;return n?n._scopeElementClass(t,e):void 0},stamp:functi left: 0; }; - }